Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Fixes bug when $location.search() is not returning actual search part of current url. #9410

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 21 additions & 2 deletions docs/content/guide/accessibility.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,26 @@

# Accessibility with ngAria

You can use the `ngAria` module to have certain ARIA attributes automatically applied when you
use certain directives.
You can use the `ngAria` module to have common ARIA attributes automatically applied when you
use certain directives. To enable `ngAria`, just require the module into your application and
the code will hook into your ng-show/ng-hide, input, textarea, button, select and
ng-required directives and add the appropriate ARIA states and properties.

Currently, the following attributes are implemented:
* aria-hidden
* aria-checked
* aria-disabled
* aria-required
* aria-invalid
* aria-multiline
* aria-valuenow
* aria-valuemin
* aria-valuemax
* tabindex

You can disable individual attributes by using the `{@link ngAria.$ariaProvider#config config}` method.

###Example

```js
angular.module('myApp', ['ngAria'])...
Expand Down Expand Up @@ -39,3 +57,4 @@ Accessibility best practices that apply to web apps in general also apply to Ang

* [WebAim](http://webaim.org/)
* [Using WAI-ARIA in HTML](http://www.w3.org/TR/2014/WD-aria-in-html-20140626/)
* [Apps For All: Coding Accessible Web Applications](https://shop.smashingmagazine.com/apps-for-all-coding-accessible-web-applications.html)
1 change: 1 addition & 0 deletions src/.jshintrc
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"jqLite": false,
"jQuery": false,
"slice": false,
"splice": false,
"push": false,
"toString": false,
"ngMinErr": false,
Expand Down
2 changes: 2 additions & 0 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
jqLite: true,
jQuery: true,
slice: true,
splice: true,
push: true,
toString: true,
ngMinErr: true,
Expand Down Expand Up @@ -161,6 +162,7 @@ var /** holds major version number for IE or NaN for real browsers */
jqLite, // delay binding since jQuery could be loaded after us.
jQuery, // delay binding
slice = [].slice,
splice = [].splice,
push = [].push,
toString = Object.prototype.toString,
ngMinErr = minErr('ng'),
Expand Down
2 changes: 1 addition & 1 deletion src/jqLite.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
* - [`children()`](http://api.jquery.com/children/) - Does not support selectors
* - [`clone()`](http://api.jquery.com/clone/)
* - [`contents()`](http://api.jquery.com/contents/)
* - [`css()`](http://api.jquery.com/css/)
* - [`css()`](http://api.jquery.com/css/) - Only retrieves inline-styles, does not call `getComputedStyles()`
* - [`data()`](http://api.jquery.com/data/)
* - [`detach()`](http://api.jquery.com/detach/)
* - [`empty()`](http://api.jquery.com/empty/)
Expand Down
14 changes: 12 additions & 2 deletions src/ng/browser.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use strict';
/* global stripHash: true */

/**
* ! This is a private undocumented service !
Expand Down Expand Up @@ -153,8 +154,13 @@ function Browser(window, document, $log, $sniffer) {
// setter
if (url) {
if (lastBrowserUrl == url) return;
var sameBase = lastBrowserUrl && stripHash(lastBrowserUrl) === stripHash(url);
lastBrowserUrl = url;
if ($sniffer.history) {
// Don't use history API if only the hash changed
// due to a bug in IE10/IE11 which leads
// to not firing a `hashchange` nor `popstate` event
// in some cases (see #9143).
if (!sameBase && $sniffer.history) {
if (replace) history.replaceState(null, '', url);
else {
history.pushState(null, '', url);
Expand Down Expand Up @@ -182,6 +188,10 @@ function Browser(window, document, $log, $sniffer) {

function fireUrlChange() {
newLocation = null;
checkUrlChange();
}

function checkUrlChange() {
if (lastBrowserUrl == self.url()) return;

lastBrowserUrl = self.url();
Expand Down Expand Up @@ -237,7 +247,7 @@ function Browser(window, document, $log, $sniffer) {
* Needs to be exported to be able to check for changes that have been done in sync,
* as hashchange/popstate events fire in async.
*/
self.$$checkUrlChange = fireUrlChange;
self.$$checkUrlChange = checkUrlChange;

//////////////////////////////////////////////////////////////
// Misc API
Expand Down
48 changes: 33 additions & 15 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1150,27 +1150,28 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
maxPriority, ignoreDirective, previousCompileContext);
compile.$$addScopeClass($compileNodes);
var namespace = null;
var namespaceAdaptedCompileNodes = $compileNodes;
var lastCompileNode;
return function publicLinkFn(scope, cloneConnectFn, transcludeControllers, parentBoundTranscludeFn, futureParentElement){
assertArg(scope, 'scope');
if (!namespace) {
namespace = detectNamespaceForChildElements(futureParentElement);
}
if (namespace !== 'html' && $compileNodes[0] !== lastCompileNode) {
namespaceAdaptedCompileNodes = jqLite(
var $linkNode;
if (namespace !== 'html') {
// When using a directive with replace:true and templateUrl the $compileNodes
// (or a child element inside of them)
// might change, so we need to recreate the namespace adapted compileNodes
// for call to the link function.
// Note: This will already clone the nodes...
$linkNode = jqLite(
wrapTemplate(namespace, jqLite('<div>').append($compileNodes).html())
);
} else if (cloneConnectFn) {
// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
$linkNode = JQLitePrototype.clone.call($compileNodes);
} else {
$linkNode = $compileNodes;
}
// When using a directive with replace:true and templateUrl the $compileNodes
// might change, so we need to recreate the namespace adapted compileNodes.
lastCompileNode = $compileNodes[0];

// important!!: we must call our jqLite.clone() since the jQuery one is trying to be smart
// and sometimes changes the structure of the DOM.
var $linkNode = cloneConnectFn
? JQLitePrototype.clone.call(namespaceAdaptedCompileNodes) // IMPORTANT!!!
: namespaceAdaptedCompileNodes;

if (transcludeControllers) {
for (var controllerName in transcludeControllers) {
Expand Down Expand Up @@ -1622,7 +1623,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(directiveValue)) {
$template = [];
} else {
$template = jqLite(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
$template = removeComments(wrapTemplate(directive.templateNamespace, trim(directiveValue)));
}
compileNode = $template[0];

Expand Down Expand Up @@ -2104,7 +2105,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
if (jqLiteIsTextNode(content)) {
$template = [];
} else {
$template = jqLite(wrapTemplate(templateNamespace, trim(content)));
$template = removeComments(wrapTemplate(templateNamespace, trim(content)));
}
compileNode = $template[0];

Expand Down Expand Up @@ -2518,3 +2519,20 @@ function tokenDifference(str1, str2) {
}
return values;
}

function removeComments(jqNodes) {
jqNodes = jqLite(jqNodes);
var i = jqNodes.length;

if (i <= 1) {
return jqNodes;
}

while (i--) {
var node = jqNodes[i];
if (node.nodeType === 8) {
splice.call(jqNodes, i, 1);
}
}
return jqNodes;
}
20 changes: 10 additions & 10 deletions src/ng/directive/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,16 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
function getSelectedSet() {
var selectedSet = false;
if (multiple) {
var modelValue = ctrl.$modelValue;
if (trackFn && isArray(modelValue)) {
var viewValue = ctrl.$viewValue;
if (trackFn && isArray(viewValue)) {
selectedSet = new HashMap([]);
var locals = {};
for (var trackIndex = 0; trackIndex < modelValue.length; trackIndex++) {
locals[valueName] = modelValue[trackIndex];
selectedSet.put(trackFn(scope, locals), modelValue[trackIndex]);
for (var trackIndex = 0; trackIndex < viewValue.length; trackIndex++) {
locals[valueName] = viewValue[trackIndex];
selectedSet.put(trackFn(scope, locals), viewValue[trackIndex]);
}
} else {
selectedSet = new HashMap(modelValue);
selectedSet = new HashMap(viewValue);
}
}
return selectedSet;
Expand All @@ -470,7 +470,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
optionGroup,
option,
existingParent, existingOptions, existingOption,
modelValue = ctrl.$modelValue,
viewValue = ctrl.$viewValue,
values = valuesFn(scope) || [],
keys = keyName ? sortedKeys(values) : values,
key,
Expand Down Expand Up @@ -508,10 +508,10 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
} else {
if (trackFn) {
var modelCast = {};
modelCast[valueName] = modelValue;
modelCast[valueName] = viewValue;
selected = trackFn(scope, modelCast) === trackFn(scope, locals);
} else {
selected = modelValue === valueFn(scope, locals);
selected = viewValue === valueFn(scope, locals);
}
selectedSet = selectedSet || selected; // see if at least one item is selected
}
Expand All @@ -527,7 +527,7 @@ var selectDirective = ['$compile', '$parse', function($compile, $parse) {
});
}
if (!multiple) {
if (nullOption || modelValue === null) {
if (nullOption || viewValue === null) {
// insert null option if we have a placeholder, or the model is null
optionGroups[''].unshift({id:'', label:'', selected:!selectedSet});
} else if (!selectedSet) {
Expand Down
10 changes: 7 additions & 3 deletions src/ng/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ function $HttpProvider() {
var JSON_START = /^\s*(\[|\{[^\{])/,
JSON_END = /[\}\]]\s*$/,
PROTECTION_PREFIX = /^\)\]\}',?\n/,
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': 'application/json;charset=utf-8'};
APPLICATION_JSON = 'application/json',
CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};

/**
* @ngdoc property
Expand All @@ -114,12 +115,15 @@ function $HttpProvider() {
**/
var defaults = this.defaults = {
// transform incoming response data
transformResponse: [function(data) {
transformResponse: [function defaultHttpResponseTransform(data, headers) {
if (isString(data)) {
// strip json vulnerability protection prefix
data = data.replace(PROTECTION_PREFIX, '');
if (JSON_START.test(data) && JSON_END.test(data))
var contentType = headers('Content-Type');
if ((contentType && contentType.indexOf(APPLICATION_JSON) === 0) ||
(JSON_START.test(data) && JSON_END.test(data))) {
data = fromJson(data);
}
}
return data;
}],
Expand Down
4 changes: 4 additions & 0 deletions src/ng/location.js
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,7 @@ LocationHashbangInHtml5Url.prototype =
search = search.toString();
this.$$search = parseKeyValue(search);
} else if (isObject(search)) {
search = copy(search, {});
// remove object undefined or null properties
forEach(search, function(value, key) {
if (value == null) delete search[key];
Expand Down Expand Up @@ -720,6 +721,9 @@ function $LocationProvider(){

if (absHref && !elm.attr('target') && !event.isDefaultPrevented()) {
if ($location.$$parseLinkUrl(absHref, relHref)) {
// We do a preventDefault for all urls that are part of the angular application,
// in html5mode and also without, so that we are able to abort navigation without
// getting double entries in the location history.
event.preventDefault();
// update location manually
if ($location.absUrl() != $browser.url()) {
Expand Down
48 changes: 48 additions & 0 deletions test/ng/browserSpecs.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,17 @@ describe('browser', function() {
expect(locationReplace).not.toHaveBeenCalled();
});

it('should set location.href and not use pushState when the url only changed in the hash fragment to please IE10/11', function() {
sniffer.history = true;
browser.url(https://github.com/'http://server/#123');

expect(fakeWindow.location.href).toEqual('http://server/#123');

expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
expect(locationReplace).not.toHaveBeenCalled();
});

it('should use location.replace when history.replaceState not available', function() {
sniffer.history = false;
browser.url(https://github.com/'http://new.org',%20true);
Expand All @@ -456,6 +467,17 @@ describe('browser', function() {
expect(fakeWindow.location.href).toEqual('http://server/');
});

it('should use location.replace and not use replaceState when the url only changed in the hash fragment to please IE10/11', function() {
sniffer.history = true;
browser.url(https://github.com/'http://server/#123',%20true);

expect(locationReplace).toHaveBeenCalledWith('http://server/#123');

expect(pushState).not.toHaveBeenCalled();
expect(replaceState).not.toHaveBeenCalled();
expect(fakeWindow.location.href).toEqual('http://server/');
});

it('should return $browser to allow chaining', function() {
expect(browser.url(https://github.com/'http://any.com')).toBe(browser);
});
Expand Down Expand Up @@ -630,6 +652,9 @@ describe('browser', function() {
$provide.value('$browser', browser);
browser.pollFns = [];

sniffer.history = true;
$provide.value('$sniffer', sniffer);

$locationProvider.html5Mode(true);
}));

Expand All @@ -646,6 +671,29 @@ describe('browser', function() {
expect($location.path()).toBe('/someTestHash');
});
});

});

describe('integration test with $rootScope', function() {

beforeEach(module(function($provide, $locationProvider) {
$provide.value('$browser', browser);
browser.pollFns = [];
}));

it('should not interfere with legacy browser url replace behavior', function() {
inject(function($rootScope) {
var current = fakeWindow.location.href;
var newUrl = 'notyet';
sniffer.history = false;
browser.url(https://github.com/newUrl,%20true);
expect(browser.url()).toBe(newUrl);
$rootScope.$digest();
expect(browser.url()).toBe(newUrl);
expect(fakeWindow.location.href).toBe(current);
});
});

});

});
Loading