diff --git a/src/Angular.js b/src/Angular.js index 5ed39bb929c2..1ade94b502c4 100644 --- a/src/Angular.js +++ b/src/Angular.js @@ -58,6 +58,7 @@ shallowCopy: true, equals: true, csp: true, + jq: true, concat: true, sliceArgs: true, bind: true, @@ -899,7 +900,61 @@ var csp = function() { return (csp.isActive_ = active); }; +/** + * @ngdoc directive + * @module ng + * @name ngJq + * + * @element ANY + * @param {string=} the name of the library available under `window` + * to be used for angular.element + * @description + * Use this directive to force the angular.element library. This should be + * used to force either jqLite by leaving ng-jq blank or setting the name of + * the jquery variable under window (eg. jQuery). + * + * Since this directive is global for the angular library, it is recommended + * that it's added to the same element as ng-app or the HTML element, but it is not mandatory. + * It needs to be noted that only the first instance of `ng-jq` will be used and all others + * ignored. + * + * @example + * This example shows how to force jqLite using the `ngJq` directive to the `html` tag. + ```html + + + ... + ... + + ``` + * @example + * This example shows how to use a jQuery based library of a different name. + * The library name must be available at the top most 'window'. + ```html + + + ... + ... + + ``` + */ +var jq = function() { + if (isDefined(jq.name_)) return jq.name_; + var el; + var i, ii = ngAttrPrefixes.length; + for (i = 0; i < ii; ++i) { + if (el = document.querySelector('[' + ngAttrPrefixes[i].replace(':', '\\:') + 'jq]')) { + break; + } + } + var name; + if (el) { + name = getNgAttribute(el, "jq"); + } + + return (jq.name_ = name); +}; function concat(array1, array2, index) { return array1.concat(slice.call(array2, index)); @@ -1472,7 +1527,12 @@ function bindJQuery() { } // bind to jQuery if present; - jQuery = window.jQuery; + var jqName = jq(); + jQuery = window.jQuery; // use default jQuery. + if (isDefined(jqName)) { // `ngJq` present + jQuery = jqName === null ? undefined : window[jqName]; // if empty; use jqLite. if not empty, use jQuery specified by `ngJq`. + } + // Use jQuery if it exists with proper functionality, otherwise default to us. // Angular 1.2+ requires jQuery 1.7+ for on()/off() support. // Angular 1.3+ technically requires at least jQuery 2.1+ but it may work with older diff --git a/test/.jshintrc b/test/.jshintrc index abea027a9349..98c963364465 100644 --- a/test/.jshintrc +++ b/test/.jshintrc @@ -60,6 +60,7 @@ "shallowCopy": false, "equals": false, "csp": false, + "jq": false, "concat": false, "sliceArgs": false, "bind": false, diff --git a/test/AngularSpec.js b/test/AngularSpec.js index b6f5e9d29643..4a94e1720024 100644 --- a/test/AngularSpec.js +++ b/test/AngularSpec.js @@ -615,6 +615,94 @@ describe('angular', function() { }); + describe('jq', function() { + var element; + + beforeEach(function() { + element = document.createElement('html'); + }); + + afterEach(function() { + delete jq.name_; + }); + + it('should return undefined when jq is not set, no jQuery found (the default)', function() { + expect(jq()).toBe(undefined); + }); + + it('should return empty string when jq is enabled manually via [ng-jq] with empty string', function() { + element.setAttribute('ng-jq', ''); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[ng-jq]') return element; + }); + expect(jq()).toBe(''); + }); + + it('should return empty string when jq is enabled manually via [data-ng-jq] with empty string', function() { + element.setAttribute('data-ng-jq', ''); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[data-ng-jq]') return element; + }); + expect(jq()).toBe(''); + expect(document.querySelector).toHaveBeenCalledWith('[data-ng-jq]'); + }); + + it('should return empty string when jq is enabled manually via [x-ng-jq] with empty string', function() { + element.setAttribute('x-ng-jq', ''); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[x-ng-jq]') return element; + }); + expect(jq()).toBe(''); + expect(document.querySelector).toHaveBeenCalledWith('[x-ng-jq]'); + }); + + it('should return empty string when jq is enabled manually via [ng:jq] with empty string', function() { + element.setAttribute('ng:jq', ''); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[ng\\:jq]') return element; + }); + expect(jq()).toBe(''); + expect(document.querySelector).toHaveBeenCalledWith('[ng\\:jq]'); + }); + + it('should return "jQuery" when jq is enabled manually via [ng-jq] with value "jQuery"', function() { + element.setAttribute('ng-jq', 'jQuery'); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[ng-jq]') return element; + }); + expect(jq()).toBe('jQuery'); + expect(document.querySelector).toHaveBeenCalledWith('[ng-jq]'); + }); + + it('should return "jQuery" when jq is enabled manually via [data-ng-jq] with value "jQuery"', function() { + element.setAttribute('data-ng-jq', 'jQuery'); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[data-ng-jq]') return element; + }); + expect(jq()).toBe('jQuery'); + expect(document.querySelector).toHaveBeenCalledWith('[data-ng-jq]'); + }); + + it('should return "jQuery" when jq is enabled manually via [x-ng-jq] with value "jQuery"', function() { + element.setAttribute('x-ng-jq', 'jQuery'); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[x-ng-jq]') return element; + }); + expect(jq()).toBe('jQuery'); + expect(document.querySelector).toHaveBeenCalledWith('[x-ng-jq]'); + }); + + it('should return "jQuery" when jq is enabled manually via [ng:jq] with value "jQuery"', function() { + element.setAttribute('ng:jq', 'jQuery'); + spyOn(document, 'querySelector').andCallFake(function(selector) { + if (selector === '[ng\\:jq]') return element; + }); + expect(jq()).toBe('jQuery'); + expect(document.querySelector).toHaveBeenCalledWith('[ng\\:jq]'); + }); + }); + + describe('parseKeyValue', function() { it('should parse a string into key-value pairs', function() { expect(parseKeyValue('')).toEqual({});