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

Commit

Permalink
feat(bootstrap): drop angular.js file name restrictions for autobind
Browse files Browse the repository at this point in the history
The last script element in the dom is always us if the script that
contains angular is loaded synchronously.

For async loading manual bootstrap needs to be performed.

Close #621
  • Loading branch information
IgorMinar committed Oct 26, 2011
1 parent 950d02b commit d7ba5bc
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 167 deletions.
33 changes: 17 additions & 16 deletions docs/content/guide/dev_guide.bootstrap.auto_bootstrap.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,7 @@ appending `#autobind` to the `<script src=...>` URL, like in this snippet:
<!doctype html>
<html>
<head>
<script type="text/javascript"
src="http://code.angularjs.org/angular.js#autobind"></script>
<script src="http://code.angularjs.org/angular.js#autobind"></script>
</head>
<body>
<div xmlns:ng="http://angularjs.org">
Expand All @@ -67,22 +66,24 @@ appending `#autobind` to the `<script src=...>` URL, like in this snippet:
As with `ng:autobind`, you can specify an element id that should be exclusively targeted for
compilation as the value of the `#autobind`, for example: `#autobind=angularContent`.

## Filename Restrictions for Auto-bootstrap
If angular.js file is being combined with other scripts into a single script file, then all of the
config options above apply to this processed script as well. That means if the contents of
`angular.js` were appended to `all-my-scripts.js`, then the app can be bootstrapped as:

In order for us to find the auto-bootstrap from a script attribute or URL fragment, the value of
the `script` `src` attribute that loads the angular script must match one of these naming
conventions:

- `angular.js`
- `angular-min.js`
- `angular-x.x.x.js`
- `angular-x.x.x.min.js`
- `angular-x.x.x-xxxxxxxx.js` (dev snapshot)
- `angular-x.x.x-xxxxxxxx.min.js` (dev snapshot)
- `angular-bootstrap.js` (used for development of angular)
<pre>
<!doctype html>
<html xmlns:ng="http://angularjs.org">
<head>
<script src="http://myapp.com/all-my-scripts.js" ng:autobind></script>
</head>
<body>
<div>
Hello {{'world'}}!
</div>
</body>
</html>
</pre>

Optionally, any of the filename formats above can be prepended with a relative or absolute URL that
ends with `/`.

## Global Angular Object

Expand Down
40 changes: 17 additions & 23 deletions src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ var _undefined = undefined,
angularService = extensionMap(angular, 'service'),
angularCallbacks = extensionMap(angular, 'callbacks'),
nodeName_,
rngScript = /^(|.*\/)angular(-.*?)?(\.min)?.js(\?[^#]*)?(#(.*))?$/,
uid = ['0', '0', '0'],
DATE_ISOSTRING_LN = 24;

Expand Down Expand Up @@ -953,35 +952,30 @@ function angularInit(config, document){
var autobind = config.autobind;

if (autobind) {
var element = isString(autobind) ? document.getElementById(autobind) : document,
scope = compile(element)(createScope()),
$browser = scope.$service('$browser');

if (config.css)
$browser.addCss(config.base_url + config.css);
scope.$apply();
var element = isString(autobind) ? document.getElementById(autobind) : document;
compile(element)().$apply();
}
}

function angularJsConfig(document) {
bindJQuery();
var scripts = document.getElementsByTagName("script"),
var scripts = document.getElementsByTagName('script'),
script = scripts[scripts.length-1],
scriptSrc = script.src,
config = {},
match;
for(var j = 0; j < scripts.length; j++) {
match = (scripts[j].src || "").match(rngScript);
if (match) {
config.base_url = match[1];
extend(config, parseKeyValue(match[6]));
eachAttribute(jqLite(scripts[j]), function(value, name){
if (/^ng:/.exec(name)) {
name = name.substring(3).replace(/-/g, '_');
value = value || true;
config[name] = value;
}
});
hashPos;

hashPos = scriptSrc.indexOf('#');
if (hashPos != -1) extend(config, parseKeyValue(scriptSrc.substr(hashPos+1)));

eachAttribute(jqLite(script), function(value, name){
if (/^ng:/.exec(name)) {
name = name.substring(3).replace(/-/g, '_');
value = value || true;
config[name] = value;
}
}
});

return config;
}

Expand Down
4 changes: 1 addition & 3 deletions src/angular-bootstrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,7 @@
// empty the cache to prevent mem leaks
globalVars = {};

var config = angularJsConfig(document);

angularInit(config, document);
angularInit({autobind:true}, document);
}

if (window.addEventListener) {
Expand Down
4 changes: 3 additions & 1 deletion src/angular.suffix
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@

var config = angularJsConfig(document);

jqLiteWrap(document).ready(function() {
angularInit(angularJsConfig(document), document);
angularInit(config, document);
});

})(window, document);
164 changes: 40 additions & 124 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -228,181 +228,97 @@ describe('angular', function() {
});


describe ('rngScript', function() {
it('should match angular.js', function() {
expect('angular.js'.match(rngScript)).not.toBeNull();
expect('../angular.js'.match(rngScript)).not.toBeNull();
expect('foo/angular.js'.match(rngScript)).not.toBeNull();

expect('foo.js'.match(rngScript)).toBeNull();
expect('foo/foo.js'.match(rngScript)).toBeNull();
expect('my-angular-app.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app.js'.match(rngScript)).toBeNull();
});

it('should match angular.min.js', function() {
expect('angular.min.js'.match(rngScript)).not.toBeNull();
expect('../angular.min.js'.match(rngScript)).not.toBeNull();
expect('foo/angular.min.js'.match(rngScript)).not.toBeNull();

expect('my-angular-app.min.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app.min.js'.match(rngScript)).toBeNull();
});

it('should match angular-bootstrap.js', function() {
expect('angular-bootstrap.js'.match(rngScript)).not.toBeNull();
expect('../angular-bootstrap.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-bootstrap.js'.match(rngScript)).not.toBeNull();

expect('my-angular-app-bootstrap.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app-bootstrap.js'.match(rngScript)).toBeNull();
});

it('should match angular-0.9.0.js', function() {
expect('angular-0.9.0.js'.match(rngScript)).not.toBeNull();
expect('../angular-0.9.0.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-0.9.0.js'.match(rngScript)).not.toBeNull();

expect('my-angular-app-0.9.0.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app-0.9.0.js'.match(rngScript)).toBeNull();
});

it('should match angular-0.9.0.min.js', function() {
expect('angular-0.9.0.min.js'.match(rngScript)).not.toBeNull();
expect('../angular-0.9.0.min.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-0.9.0.min.js'.match(rngScript)).not.toBeNull();

expect('my-angular-app-0.9.0.min.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app-0.9.0.min.js'.match(rngScript)).toBeNull();
});

it('should match angular-0.9.0-de0a8612.js', function() {
expect('angular-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
expect('../angular-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();

expect('my-angular-app-0.9.0-de0a8612.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app-0.9.0-de0a8612.js'.match(rngScript)).toBeNull();
});

it('should match angular-0.9.0-de0a8612.min.js', function() {
expect('angular-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
expect('../angular-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();

expect('my-angular-app-0.9.0-de0a8612.min.js'.match(rngScript)).toBeNull();
expect('foo/../my-angular-app-0.9.0-de0a8612.min.js'.match(rngScript)).toBeNull();
});

it('should match angular-scenario.js', function() {
expect('angular-scenario.js'.match(rngScript)).not.toBeNull();
expect('angular-scenario.min.js'.match(rngScript)).not.toBeNull();
expect('../angular-scenario.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-scenario.min.js'.match(rngScript)).not.toBeNull();
});

it('should match angular-scenario-0.9.0(.min).js', function() {
expect('angular-scenario-0.9.0.js'.match(rngScript)).not.toBeNull();
expect('angular-scenario-0.9.0.min.js'.match(rngScript)).not.toBeNull();
expect('../angular-scenario-0.9.0.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-scenario-0.9.0.min.js'.match(rngScript)).not.toBeNull();
});

it('should match angular-scenario-0.9.0-de0a8612(.min).js', function() {
expect('angular-scenario-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
expect('angular-scenario-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
expect('../angular-scenario-0.9.0-de0a8612.js'.match(rngScript)).not.toBeNull();
expect('foo/angular-scenario-0.9.0-de0a8612.min.js'.match(rngScript)).not.toBeNull();
});
});
describe('angularJsConfig', function() {
it('should always consider angular.js script tag to be the last script tag', function() {
var doc = {
getElementsByTagName: function(tagName) {
expect(tagName).toEqual('script');
return [{nodeName: 'SCRIPT', src: 'random.js',
attributes: [{name: 'ng:autobind', value: 'wrong'}]},
{nodeName: 'SCRIPT', src: 'angular.js',
attributes: [{name: 'ng:autobind', value: 'correct'}]}];
}
};

expect(angularJsConfig(doc)).toEqual({autobind: 'correct'});

describe('angularJsConfig', function() {
it('should find angular.js script tag and config', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(tagName).toEqual('script');
return [{nodeName: 'SCRIPT', src: 'random.js'},
{nodeName: 'SCRIPT', src: 'angular.js'},
{nodeName: 'SCRIPT', src: 'my-angular-app.js'}];
}
doc = {
getElementsByTagName: function(tagName) {
expect(tagName).toEqual('script');
return [{nodeName: 'SCRIPT', src: 'angular.js',
attributes: [{name: 'ng:autobind', value: 'wrong'}]},
{nodeName: 'SCRIPT', src: 'concatinatedAndObfuscadedScriptWithOurScript.js',
attributes: [{name: 'ng:autobind', value: 'correct'}]}];
}
};

expect(angularJsConfig(doc)).toEqual({base_url: ''});
expect(angularJsConfig(doc)).toEqual({autobind: 'correct'});
});


it('should extract angular config from the ng: attributes',
function() {
it('should extract angular config from the ng: attributes', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
return [{
nodeName: 'SCRIPT',
src: 'angularjs/angular.js',
attributes: [{name: 'ng:autobind', value:'elementIdToCompile'},
{name: 'ng:css', value: 'css/my_custom_angular.css'}] }];
}};

expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
expect(angularJsConfig(doc)).toEqual({
autobind: 'elementIdToCompile',
css: 'css/my_custom_angular.css'});
css: 'css/my_custom_angular.css'
});
});


it('should extract angular config and default autobind value to true if present', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
return [{
nodeName: 'SCRIPT',
src: 'angularjs/angular.js',
attributes: [{name: 'ng:autobind', value:undefined}]}];
}};

expect(angularJsConfig(doc)).toEqual({autobind: true,
base_url: 'angularjs/'});
expect(angularJsConfig(doc)).toEqual({autobind: true});
});


it('should extract angular autobind config from the script hashpath attributes', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
return [{
nodeName: 'SCRIPT',
src: 'angularjs/angular.js#autobind'}];
}};

expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
autobind: true});
expect(angularJsConfig(doc)).toEqual({autobind: true});
});


it('should extract autobind config with element id from the script hashpath', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
return [{
nodeName: 'SCRIPT',
src: 'angularjs/angular.js#autobind=foo'}];
}};

expect(angularJsConfig(doc)).toEqual({base_url: 'angularjs/',
autobind: 'foo'});
expect(angularJsConfig(doc)).toEqual({autobind: 'foo'});
});


it("should default to versioned ie-compat file if angular file is versioned", function() {
it('should default to versioned ie-compat file if angular file is versioned', function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
return [{
nodeName: 'SCRIPT',
src: 'js/angular-0.9.0.js'}];
}};

expect(angularJsConfig(doc)).toEqual({base_url: 'js/'});
});


it("should default to versioned ie-compat file if angular file is versioned and minified", function() {
var doc = { getElementsByTagName: function(tagName) {
expect(lowercase(tagName)).toEqual('script');
return [{nodeName: 'SCRIPT',
src: 'js/angular-0.9.0-cba23f00.min.js'}];
}};

expect(angularJsConfig(doc)).toEqual({base_url: 'js/'});
expect(angularJsConfig(doc)).toEqual({});
});
});

Expand Down

0 comments on commit d7ba5bc

Please sign in to comment.