injection is now working

This commit is contained in:
Misko Hevery 2010-04-03 17:04:36 -07:00
parent 35ca4fcb9c
commit a80a61839a
24 changed files with 262 additions and 245 deletions

View file

@ -23,7 +23,7 @@
-missing_break # missing break statement -missing_break # missing break statement
+missing_break_for_last_case # missing break statement for last case in switch +missing_break_for_last_case # missing break statement for last case in switch
+comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==) +comparison_type_conv # comparisons against null, 0, true, false, or an empty string allowing implicit type conversion (use === or !==)
+inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement -inc_dec_within_stmt # increment (++) and decrement (--) operators used as part of greater statement
+useless_void # use of the void type may be unnecessary (void is always undefined) +useless_void # use of the void type may be unnecessary (void is always undefined)
+multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs +multiple_plus_minus # unknown order of operations for successive plus (e.g. x+++y) or minus (e.g. x---y) signs
+use_of_label # use of label +use_of_label # use of label
@ -41,7 +41,7 @@
+useless_assign # useless assignment +useless_assign # useless assignment
+ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity +ambiguous_nested_stmt # block statements containing block statements should use curly braces to resolve ambiguity
+ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent) +ambiguous_else_stmt # the else statement could be matched with one of multiple if statements (use curly braces to indicate intent)
+missing_default_case # missing default case in switch statement -missing_default_case # missing default case in switch statement
+duplicate_case_in_switch # duplicate case in switch statements +duplicate_case_in_switch # duplicate case in switch statements
+default_not_at_end # the default case is not at the end of the switch statement +default_not_at_end # the default case is not at the end of the switch statement
+legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax +legacy_cc_not_understood # couldn't understand control comment using /*@keyword@*/ syntax

View file

@ -4,8 +4,8 @@
<link rel="stylesheet" type="text/css" href="style.css"></link> <link rel="stylesheet" type="text/css" href="style.css"></link>
<script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script> <script type="text/javascript" src="../src/angular-bootstrap.js#autobind"></script>
</head> </head>
<body ng-init="$window.$scope = $root"> <body ng-init="$window.$scope = this">
<table ng-repeat="i in [0, 1]"> <table>
<tr> <tr>
<th>Description</th> <th>Description</th>
<th>Test</th> <th>Test</th>
@ -14,7 +14,7 @@
<tr><th colspan="3">Input text field</th></tr> <tr><th colspan="3">Input text field</th></tr>
<tr> <tr>
<td>basic</td> <td>basic</td>
<td><input type="text" name="text.basic" /></td> <td><input type="text" name="text.basic" ng-required /></td>
<td>text.basic={{text.basic}}</td> <td>text.basic={{text.basic}}</td>
</tr> </tr>
<tr> <tr>
@ -70,7 +70,7 @@
</tr> </tr>
<tr><th colspan="3">Buttons</th></tr> <tr><th colspan="3">Buttons</th></tr>
<tr> <tr>
<td>ng-action</td> <td>ng-change<br/>ng-click</td>
<td> <td>
<form ng-init="button.count = 0"> <form ng-init="button.count = 0">
<input type="button" value="button" ng-change="button.count = button.count + 1"/> <br/> <input type="button" value="button" ng-change="button.count = button.count + 1"/> <br/>

View file

@ -1,50 +1,3 @@
//////////////////////////////
//UrlWatcher
//////////////////////////////
function UrlWatcher(location) {
this.location = location;
this.delay = 25;
this.setTimeout = function(fn, delay) {
window.setTimeout(fn, delay);
};
this.expectedUrl = location.href;
this.listeners = [];
}
UrlWatcher.prototype = {
watch: function(fn){
this.listeners.push(fn);
},
start: function() {
var self = this;
(function pull () {
if (self.expectedUrl !== self.location.href) {
foreach(self.listeners, function(listener){
listener(self.location.href);
});
self.expectedUrl = self.location.href;
}
self.setTimeout(pull, self.delay);
})();
},
set: function(url) {
var existingURL = this.location.href;
if (!existingURL.match(/#/))
existingURL += '#';
if (existingURL != url)
this.location.href = url;
this.existingURL = url;
},
get: function() {
return this.location.href;
}
};
//////////////////////////////////// ////////////////////////////////////
if (typeof document.getAttribute == 'undefined') if (typeof document.getAttribute == 'undefined')
@ -53,9 +6,9 @@ if (typeof document.getAttribute == 'undefined')
if (!window['console']) window['console']={'log':noop, 'error':noop}; if (!window['console']) window['console']={'log':noop, 'error':noop};
var consoleNode, var consoleNode,
PRIORITY_FIRST = -99999; PRIORITY_FIRST = -99999,
PRIORITY_WATCH = -1000; PRIORITY_WATCH = -1000,
PRIORITY_LAST = 99999; PRIORITY_LAST = 99999,
NOOP = 'noop', NOOP = 'noop',
NG_ERROR = 'ng-error', NG_ERROR = 'ng-error',
NG_EXCEPTION = 'ng-exception', NG_EXCEPTION = 'ng-exception',
@ -74,16 +27,14 @@ var consoleNode,
angularFilter = extensionMap(angular, 'filter'), angularFilter = extensionMap(angular, 'filter'),
angularFormatter = extensionMap(angular, 'formatter'), angularFormatter = extensionMap(angular, 'formatter'),
angularService = extensionMap(angular, 'service'), angularService = extensionMap(angular, 'service'),
angularCallbacks = extensionMap(angular, 'callbacks'), angularCallbacks = extensionMap(angular, 'callbacks');
urlWatcher = new UrlWatcher(window.location);
function angularAlert(){ function angularAlert(){
log(arguments); window.alert.apply(window, arguments); log(arguments); window.alert.apply(window, arguments);
}; }
extend(angular, { extend(angular, {
'compile': compile, 'compile': compile,
'startUrlWatch': bind(urlWatcher, urlWatcher.start),
'copy': copy, 'copy': copy,
'extend': extend, 'extend': extend,
'foreach': foreach, 'foreach': foreach,
@ -166,7 +117,7 @@ function isFunction(value){ return typeof value == 'function';}
function isTextNode(node) { return nodeName(node) == '#text'; } function isTextNode(node) { return nodeName(node) == '#text'; }
function lowercase(value){ return isString(value) ? value.toLowerCase() : value; } function lowercase(value){ return isString(value) ? value.toLowerCase() : value; }
function uppercase(value){ return isString(value) ? value.toUpperCase() : value; } function uppercase(value){ return isString(value) ? value.toUpperCase() : value; }
function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }; function trim(value) { return isString(value) ? value.replace(/^\s*/, '').replace(/\s*$/, '') : value; }
function nodeName(element) { return (element[0] || element || {}).nodeName; } function nodeName(element) { return (element[0] || element || {}).nodeName; }
function map(obj, iterator, context) { function map(obj, iterator, context) {
var results = []; var results = [];
@ -174,7 +125,7 @@ function map(obj, iterator, context) {
results.push(iterator.call(context, value, index, list)); results.push(iterator.call(context, value, index, list));
}); });
return results; return results;
}; }
function size(obj) { function size(obj) {
var size = 0; var size = 0;
if (obj) { if (obj) {
@ -289,7 +240,7 @@ function copy(source, destination){
}); });
return destination; return destination;
} }
}; }
function setHtml(node, html) { function setHtml(node, html) {
if (isLeafNode(node)) { if (isLeafNode(node)) {
@ -367,22 +318,10 @@ function merge(src, dst) {
} }
} }
function compile(element, config) { function compile(element, parentScope, overrides) {
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget); var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
$element = jqLite(element), $element = jqLite(element);
rootScope = createScope({ return compiler.compile($element)($element, parentScope, overrides);
$element: $element,
$config: extend({
'onUpdateView': noop,
'server': "",
'location': {
'get':bind(urlWatcher, urlWatcher.get),
'set':bind(urlWatcher, urlWatcher.set),
'watch':bind(urlWatcher, urlWatcher.watch)
}
}, config || {})
}, serviceAdapter(angularService));
return compiler.compile($element)($element, rootScope);
} }
///////////////////////////////////////////////// /////////////////////////////////////////////////
@ -404,11 +343,10 @@ function toKeyValue(obj) {
parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value)); parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
}); });
return parts.length ? parts.join('&') : ''; return parts.length ? parts.join('&') : '';
}; }
function angularInit(config){ function angularInit(config){
if (config.autobind) { if (config.autobind) {
compile(window.document, config).$init(); compile(window.document, null, {'$config':config}).$init();
} }
} }

46
src/Browser.js Normal file
View file

@ -0,0 +1,46 @@
//////////////////////////////
// Browser
//////////////////////////////
function Browser(location) {
this.location = location;
this.delay = 25;
this.setTimeout = function(fn, delay) {
window.setTimeout(fn, delay);
};
this.expectedUrl = location.href;
this.listeners = [];
}
Browser.prototype = {
watchUrl: function(fn){
this.listeners.push(fn);
},
startUrlWatcher: function() {
var self = this;
(function pull () {
if (self.expectedUrl !== self.location.href) {
foreach(self.listeners, function(listener){
listener(self.location.href);
});
self.expectedUrl = self.location.href;
}
self.setTimeout(pull, self.delay);
})();
},
setUrl: function(url) {
var existingURL = this.location.href;
if (!existingURL.match(/#/))
existingURL += '#';
if (existingURL != url)
this.location.href = url;
this.existingURL = url;
},
getUrl: function() {
return this.location.href;
}
};

View file

@ -1,5 +1,5 @@
/** /**
* Template provides directions an how to bind to a given element. = * Template provides directions an how to bind to a given element.
* It contains a list of init functions which need to be called to * It contains a list of init functions which need to be called to
* bind to a new instance of elements. It also provides a list * bind to a new instance of elements. It also provides a list
* of child paths which contain child templates * of child paths which contain child templates
@ -43,7 +43,7 @@ Template.prototype = {
}, },
empty: function() { empty: function() {
return this.inits.length == 0 && this.paths.length == 0; return this.inits.length === 0 && this.paths.length === 0;
} }
}; };
@ -63,8 +63,9 @@ Compiler.prototype = {
var template = this.templatize(rawElement) || new Template(); var template = this.templatize(rawElement) || new Template();
return function(element, parentScope){ return function(element, parentScope){
element = jqLite(element); element = jqLite(element);
parentScope = parentScope || {}; var scope = parentScope && parentScope.$eval ?
var scope = createScope(parentScope); parentScope :
createScope(parentScope || {}, angularService);
return extend(scope, { return extend(scope, {
$element:element, $element:element,
$init: function() { $init: function() {
@ -161,7 +162,7 @@ function eachNode(element, fn){
function eachAttribute(element, fn){ function eachAttribute(element, fn){
var i, attrs = element[0].attributes || [], size = attrs.length, chld, attr, attrValue = {}; var i, attrs = element[0].attributes || [], size = attrs.length, chld, attr, attrValue = {};
for (i = 0; i < size; i++) { for (i = 0; i < size; i++) {
var attr = attrs[i]; attr = attrs[i];
attrValue[attr.name] = attr.value; attrValue[attr.name] = attr.value;
} }
foreach(attrValue, fn); foreach(attrValue, fn);

View file

@ -1,5 +1,5 @@
function formater(format, parse) {return {'format':format, 'parse':parse || format};} function formater(format, parse) {return {'format':format, 'parse':parse || format};}
function toString(obj) {return isDefined(obj) ? "" + obj : obj;}; function toString(obj) {return isDefined(obj) ? "" + obj : obj;}
extend(angularFormatter, { extend(angularFormatter, {
'noop':formater(identity, identity), 'noop':formater(identity, identity),
'boolean':formater(toString, toBoolean), 'boolean':formater(toString, toBoolean),

View file

@ -4,11 +4,11 @@ function toJson(obj, pretty){
var buf = []; var buf = [];
toJsonArray(buf, obj, pretty ? "\n " : null, []); toJsonArray(buf, obj, pretty ? "\n " : null, []);
return buf.join(''); return buf.join('');
}; }
function toPrettyJson(obj) { function toPrettyJson(obj) {
return toJson(obj, true); return toJson(obj, true);
}; }
function fromJson(json) { function fromJson(json) {
if (!json) return json; if (!json) return json;
@ -21,7 +21,7 @@ function fromJson(json) {
error("fromJson error: ", json, e); error("fromJson error: ", json, e);
throw e; throw e;
} }
}; }
angular['toJson'] = toJson; angular['toJson'] = toJson;
angular['fromJson'] = fromJson; angular['fromJson'] = fromJson;
@ -102,4 +102,4 @@ function toJsonArray(buf, obj, pretty, stack){
if (typeof obj == "object") { if (typeof obj == "object") {
stack.pop(); stack.pop();
} }
}; }

View file

@ -4,7 +4,7 @@ function Lexer(text, parsStrings){
this.dateParseLength = parsStrings ? 20 : -1; this.dateParseLength = parsStrings ? 20 : -1;
this.tokens = []; this.tokens = [];
this.index = 0; this.index = 0;
}; }
Lexer.OPERATORS = { Lexer.OPERATORS = {
'null':function(self){return null;}, 'null':function(self){return null;},
@ -244,7 +244,7 @@ function Parser(text, parseStrings){
this.text = text; this.text = text;
this.tokens = new Lexer(text, parseStrings).parse(); this.tokens = new Lexer(text, parseStrings).parse();
this.index = 0; this.index = 0;
}; }
Parser.ZERO = function(){ Parser.ZERO = function(){
return 0; return 0;

View file

@ -15,7 +15,7 @@ Route.prototype = {
var self = this; var self = this;
var url = this.template; var url = this.template;
params = params || {}; params = params || {};
foreach(this.urlParams, function(value, urlParam){ foreach(this.urlParams, function(_, urlParam){
var value = params[urlParam] || self.defaults[urlParam] || ""; var value = params[urlParam] || self.defaults[urlParam] || "";
url = url.replace(new RegExp(":" + urlParam + "(\\W)"), value + "$1"); url = url.replace(new RegExp(":" + urlParam + "(\\W)"), value + "$1");
}); });
@ -57,7 +57,7 @@ ResourceFactory.prototype = {
function Resource(value){ function Resource(value){
copy(value || {}, this); copy(value || {}, this);
}; }
foreach(actions, function(action, name){ foreach(actions, function(action, name){
var isGet = action.method == 'GET'; var isGet = action.method == 'GET';
@ -105,7 +105,7 @@ ResourceFactory.prototype = {
var params = {}; var params = {};
var callback = noop; var callback = noop;
switch(arguments.length) { switch(arguments.length) {
case 2: params = a1, callback = a2; case 2: params = a1; callback = a2;
case 1: if (typeof a1 == 'function') callback = a1; else params = a1; case 1: if (typeof a1 == 'function') callback = a1; else params = a1;
case 0: break; case 0: break;
default: default:

View file

@ -26,7 +26,7 @@ function getter(instance, path) {
return bind(lastInstance, instance); return bind(lastInstance, instance);
} }
return instance; return instance;
}; }
function setter(instance, path, value){ function setter(instance, path, value){
var element = path.split('.'); var element = path.split('.');
@ -41,7 +41,7 @@ function setter(instance, path, value){
} }
instance[element.shift()] = value; instance[element.shift()] = value;
return value; return value;
}; }
var compileCache = {}; var compileCache = {};
function expressionCompile(exp){ function expressionCompile(exp){
@ -54,7 +54,7 @@ function expressionCompile(exp){
compileCache[exp] = expFn; compileCache[exp] = expFn;
} }
return parserNewScopeAdapter(expFn); return parserNewScopeAdapter(expFn);
}; }
// return expFn // return expFn
// TODO(remove this hack) // TODO(remove this hack)
@ -85,21 +85,16 @@ function errorHandlerFor(element, error) {
} }
var scopeId = 0; var scopeId = 0;
function createScope(parent, Class) { function createScope(parent, services, existing) {
function Parent(){} function Parent(){}
function API(){} function API(){}
function Behavior(){} function Behavior(){}
var instance, behavior, api, evalLists = {}; var instance, behavior, api, evalLists = {}, servicesCache = extend({}, existing);
if (isFunction(parent)) {
Class = parent;
parent = {};
}
Class = Class || noop; parent = Parent.prototype = (parent || {});
parent = Parent.prototype = parent || {};
api = API.prototype = new Parent(); api = API.prototype = new Parent();
behavior = Behavior.prototype = extend(new API(), Class.prototype); behavior = Behavior.prototype = new API();
instance = new Behavior(); instance = new Behavior();
extend(api, { extend(api, {
@ -161,22 +156,28 @@ function createScope(parent, Class) {
} }
}); });
if (isUndefined(instance.$root)) { if (!parent.$root) {
behavior.$root = instance; api.$root = instance;
behavior.$parent = instance; api.$parent = instance;
} }
(parent.$onEval || noop)(instance.$eval); function inject(name){
Class.apply(instance, slice.call(arguments, 2, arguments.length)); var service = getter(servicesCache, name), factory, args = [];
if (isUndefined(service)) {
factory = services[name];
if (!isFunction(factory))
throw "Don't know how to inject '" + name + "'.";
foreach(factory.inject, function(dependency){
args.push(inject(dependency));
});
setter(servicesCache, name, service = factory.apply(instance, args));
}
return service;
}
foreach(services, function(_, name){
instance[name] = inject(name);
});
return instance; return instance;
} }
function serviceAdapter(services) {
return function(){
var self = this;
foreach(services, function(service, name){
self[name] = service.call(self);
});
};
};

View file

@ -23,7 +23,7 @@ function valueAccessor(scope, element) {
validator = compileValidator(validatorName), validator = compileValidator(validatorName),
required = element.attr('ng-required'), required = element.attr('ng-required'),
lastError; lastError;
required = required || required == ''; required = required || required === '';
if (!validator) throw "Validator named '" + validatorName + "' not found."; if (!validator) throw "Validator named '" + validatorName + "' not found.";
function validate(value) { function validate(value) {
var error = required && !trim(value) ? "Required" : validator({self:scope, scope:{get:scope.$get, set:scope.$set}}, value); var error = required && !trim(value) ? "Required" : validator({self:scope, scope:{get:scope.$get, set:scope.$set}}, value);
@ -115,7 +115,7 @@ function radioInit(model, view, element) {
var modelValue = model.get(), viewValue = view.get(), input = element[0]; var modelValue = model.get(), viewValue = view.get(), input = element[0];
input.name = this.$id + '@' + input.name; input.name = this.$id + '@' + input.name;
if (isUndefined(modelValue)) model.set(null); if (isUndefined(modelValue)) model.set(null);
if (viewValue != null) model.set(viewValue); if (viewValue !== null) model.set(viewValue);
} }
function inputWidget(events, modelAccessor, viewAccessor, initFn) { function inputWidget(events, modelAccessor, viewAccessor, initFn) {
@ -126,14 +126,17 @@ function inputWidget(events, modelAccessor, viewAccessor, initFn) {
action = element.attr('ng-change') || ''; action = element.attr('ng-change') || '';
initFn.call(scope, model, view, element); initFn.call(scope, model, view, element);
this.$eval(element.attr('ng-init')||''); this.$eval(element.attr('ng-init')||'');
element.bind(events, function(){ // Don't register a handler if we are a button (noopAccessor) and there is no action
model.set(view.get()); if (action || modelAccessor !== noopAccessor) {
scope.$tryEval(action, element); element.bind(events, function(){
scope.$root.$eval(); model.set(view.get());
// if we have noop initFn than we are just a button, scope.$tryEval(action, element);
// therefore we want to prevent default action scope.$root.$eval();
return initFn != noop; // if we have noop initFn than we are just a button,
}); // therefore we want to prevent default action
return initFn != noop;
});
}
view.set(model.get()); view.set(model.get());
scope.$watch(model.get, view.set); scope.$watch(model.get, view.set);
}; };

View file

@ -46,6 +46,7 @@
addScript("/jqlite.js"); addScript("/jqlite.js");
addScript("/Parser.js"); addScript("/Parser.js");
addScript("/Resource.js"); addScript("/Resource.js");
addScript("/Browser.js");
// Extension points // Extension points
addScript("/apis.js"); addScript("/apis.js");

View file

@ -52,7 +52,8 @@ function compileBindTemplate(template){
}; };
} }
return fn; return fn;
}; }
angularDirective("ng-bind-template", function(expression){ angularDirective("ng-bind-template", function(expression){
var templateFn = compileBindTemplate(expression); var templateFn = compileBindTemplate(expression);
return function(element) { return function(element) {
@ -120,7 +121,7 @@ angularWidget("@ng-repeat", function(expression, element){
assign(childScope = children[index]); assign(childScope = children[index]);
} else { } else {
// grow children // grow children
assign(childScope = template(element.clone(), currentScope)); assign(childScope = template(element.clone(), createScope(currentScope)));
lastElement.after(childScope.$element); lastElement.after(childScope.$element);
childScope.$index = index; childScope.$index = index;
childScope.$element.attr('ng-repeat-index', index); childScope.$element.attr('ng-repeat-index', index);
@ -144,7 +145,7 @@ angularDirective("ng-click", function(expression, element){
var self = this; var self = this;
element.click(function(){ element.click(function(){
self.$tryEval(expression, element); self.$tryEval(expression, element);
self.$eval(); self.$root.$eval();
return false; return false;
}); });
}; };
@ -180,8 +181,8 @@ function ngClass(selector) {
} }
angularDirective("ng-class", ngClass(function(){return true;})); angularDirective("ng-class", ngClass(function(){return true;}));
angularDirective("ng-class-odd", ngClass(function(i){return i % 2 == 0;})); angularDirective("ng-class-odd", ngClass(function(i){return i % 2 === 0;}));
angularDirective("ng-class-even", ngClass(function(i){return i % 2 == 1;})); angularDirective("ng-class-even", ngClass(function(i){return i % 2 === 1;}));
angularDirective("ng-show", function(expression, element){ angularDirective("ng-show", function(expression, element){
return function(element){ return function(element){

View file

@ -5,7 +5,7 @@
var jqCache = {}; var jqCache = {};
var jqName = 'ng-' + new Date().getTime(); var jqName = 'ng-' + new Date().getTime();
var jqId = 1; var jqId = 1;
function jqNextId() { return jqId++; } function jqNextId() { return (jqId++); }
var addEventListener = window.document.attachEvent ? var addEventListener = window.document.attachEvent ?
function(element, type, fn) { function(element, type, fn) {
@ -31,7 +31,7 @@ function jqClearData(element) {
delete jqCache[cacheId]; delete jqCache[cacheId];
delete element[jqName]; delete element[jqName];
} }
}; }
function JQLite(element) { function JQLite(element) {
this[0] = element; this[0] = element;

View file

@ -16,16 +16,16 @@ function parseBindings(string) {
if (lastIndex != string.length) if (lastIndex != string.length)
results.push(string.substr(lastIndex, string.length - lastIndex)); results.push(string.substr(lastIndex, string.length - lastIndex));
return results.length === 0 ? [ string ] : results; return results.length === 0 ? [ string ] : results;
}; }
function binding(string) { function binding(string) {
var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/); var binding = string.replace(/\n/gm, ' ').match(/^\{\{(.*)\}\}$/);
return binding ? binding[1] : null; return binding ? binding[1] : null;
}; }
function hasBindings(bindings) { function hasBindings(bindings) {
return bindings.length > 1 || binding(bindings[0]) !== null; return bindings.length > 1 || binding(bindings[0]) !== null;
}; }
angularTextMarkup('{{}}', function(text, textNode, parentElement) { angularTextMarkup('{{}}', function(text, textNode, parentElement) {
var bindings = parseBindings(text), var bindings = parseBindings(text),

View file

@ -1,8 +1,11 @@
angularService("$window", bind(window, identity, window)); angularService("$window", bind(window, identity, window));
angularService("$document", function(window){
return jqLite(window.document);
}, {inject:['$window']});
var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?((#([^\?]*))?(\?([^\?]*))?)$/; var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?((#([^\?]*))?(\?([^\?]*))?)$/;
var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21}; var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
angularService("$location", function(){ angularService("$location", function(browser){
var scope = this; var scope = this;
function location(url){ function location(url){
if (isDefined(url)) { if (isDefined(url)) {
@ -24,17 +27,29 @@ angularService("$location", function(){
return location.href + return location.href +
(location.hashPath ? location.hashPath : '') + (location.hashPath ? location.hashPath : '') +
(hashKeyValue ? '?' + hashKeyValue : ''); (hashKeyValue ? '?' + hashKeyValue : '');
}; }
this.$config.location.watch(function(url){ browser.watchUrl(function(url){
location(url); location(url);
}); });
location(this.$config.location.get()); location(browser.getUrl());
this.$onEval(PRIORITY_LAST, function(){ this.$onEval(PRIORITY_LAST, function(){
var href = location(); var href = location();
if (href != location.href) { if (href != location.href) {
scope.$config.location.set(location()); browser.setUrl(href);
location.href = href; location.href = href;
} }
}); });
return location; return location;
}); }, {inject: ['$browser']});
if (!angularService['$browser']) {
var browserSingleton;
angularService('$browser', function browserFactory(){
if (!browserSingleton) {
browserSingleton = new Browser(window.location);
browserSingleton.startUrlWatcher();
}
return browserSingleton;
});
}

View file

@ -19,6 +19,7 @@ BinderTest.prototype.tearDown = function(){
if (this.element && this.element.dealoc) this.element.dealoc(); if (this.element && this.element.dealoc) this.element.dealoc();
}; };
BinderTest.prototype.testChangingTextfieldUpdatesModel = function(){ BinderTest.prototype.testChangingTextfieldUpdatesModel = function(){
var state = this.compile('<input type="text" name="model.price" value="abc">', {model:{}}); var state = this.compile('<input type="text" name="model.price" value="abc">', {model:{}});
state.scope.$eval(); state.scope.$eval();
@ -707,7 +708,7 @@ BinderTest.prototype.testItShouldDisplayErrorWhenActionIsSyntacticlyIncorect = f
var second = jqLite(c.node[0].childNodes[1]); var second = jqLite(c.node[0].childNodes[1]);
first.click(); first.click();
assertEquals("ABC", c.scope.$get('greeting')); assertEquals("ABC", c.scope.greeting);
second.click(); second.click();
assertTrue(second.hasClass("ng-exception")); assertTrue(second.hasClass("ng-exception"));
@ -821,5 +822,3 @@ BinderTest.prototype.XtestWriteAnchorAsPartOfTheUpdateView = function(){
binder.updateView(); binder.updateView();
assertEquals(binder.location.get(), "a#a=b"); assertEquals(binder.location.get(), "a#a=b");
}; };

View file

@ -1,11 +1,11 @@
UrlWatcherTest = TestCase('UrlWatcherTest'); BrowserTest = TestCase('BrowserTest');
UrlWatcherTest.prototype.testUrlWatcher = function () { BrowserTest.prototype.testUrlWatcher = function () {
expectAsserts(2); expectAsserts(2);
var location = {href:"http://server", hash:""}; var location = {href:"http://server", hash:""};
var watcher = new UrlWatcher(location); var watcher = new Browser(location);
watcher.delay = 1; watcher.delay = 1;
watcher.watch(function(url){ watcher.watchUrl(function(url){
assertEquals('http://getangular.test', url); assertEquals('http://getangular.test', url);
}); });
watcher.setTimeout = function(fn, delay){ watcher.setTimeout = function(fn, delay){
@ -15,7 +15,7 @@ UrlWatcherTest.prototype.testUrlWatcher = function () {
}; };
fn(); fn();
}; };
watcher.start(); watcher.startUrlWatcher();
}; };
FunctionTest = TestCase("FunctionTest"); FunctionTest = TestCase("FunctionTest");

View file

@ -1,8 +1,4 @@
xdescribe('compiler', function(){ describe('compiler', function(){
function element(html) {
return jQuery(html)[0];
}
var compiler, textMarkup, directives, widgets, compile, log; var compiler, textMarkup, directives, widgets, compile, log;
beforeEach(function(){ beforeEach(function(){
@ -29,25 +25,25 @@ xdescribe('compiler', function(){
widgets = {}; widgets = {};
compiler = new Compiler(textMarkup, attrMarkup, directives, widgets); compiler = new Compiler(textMarkup, attrMarkup, directives, widgets);
compile = function(html){ compile = function(html){
var e = element("<div>" + html + "</div>"); var e = jqLite("<div>" + html + "</div>");
var view = compiler.compile(e)(e); var scope = compiler.compile(e)(e);
view.init(); scope.$init();
return view.scope; return scope;
}; };
}); });
it('should recognize a directive', function(){ it('should recognize a directive', function(){
var e = element('<div directive="expr" ignore="me"></div>'); var e = jqLite('<div directive="expr" ignore="me"></div>');
directives.directive = function(expression, element){ directives.directive = function(expression, element){
log += "found"; log += "found";
expect(expression).toEqual("expr"); expect(expression).toEqual("expr");
expect(element[0]).toEqual(e); expect(element).toEqual(e);
return function initFn() { return function initFn() {
log += ":init"; log += ":init";
}; };
}; };
var template = compiler.compile(e); var template = compiler.compile(e);
var init = template(e).init; var init = template(e).$init;
expect(log).toEqual("found"); expect(log).toEqual("found");
init(); init();
expect(log).toEqual("found:init"); expect(log).toEqual("found:init");
@ -61,13 +57,13 @@ xdescribe('compiler', function(){
it('should watch scope', function(){ it('should watch scope', function(){
var scope = compile('<span watch="name"/>'); var scope = compile('<span watch="name"/>');
expect(log).toEqual(""); expect(log).toEqual("");
scope.updateView(); scope.$eval();
scope.set('name', 'misko'); scope.$set('name', 'misko');
scope.updateView(); scope.$eval();
scope.updateView(); scope.$eval();
scope.set('name', 'adam'); scope.$set('name', 'adam');
scope.updateView(); scope.$eval();
scope.updateView(); scope.$eval();
expect(log).toEqual(":misko:adam"); expect(log).toEqual(":misko:adam");
}); });
@ -83,29 +79,17 @@ xdescribe('compiler', function(){
element.removeAttr("duplicate"); element.removeAttr("duplicate");
var template = this.compile(element); var template = this.compile(element);
return function(marker) { return function(marker) {
this.$addEval(function() { this.$onEval(function() {
marker.after(template(element.clone()).element); marker.after(template(element.clone()).element);
}); });
}; };
}; };
var scope = compile('before<span duplicate="expr">x</span>after'); var scope = compile('before<span duplicate="expr">x</span>after');
expect($(scope.element).html()).toEqual('before<!--marker-->after'); expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment>after</div>');
scope.updateView(); scope.$eval();
expect($(scope.element).html()).toEqual('before<!--marker--><span>x</span>after'); expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment>after</div>');
scope.updateView(); scope.$eval();
expect($(scope.element).html()).toEqual('before<!--marker--><span>x</span><span>x</span>after'); expect(sortedHtml(scope.$element)).toEqual('<div>before<#comment></#comment>after</div>');
});
it('should allow for exculsive tags which suppress others', function(){
directives.exclusive = function(){
return function() {
log += ('exclusive');
};
};
directives.exclusive.exclusive = true;
compile('<span hello="misko", exclusive/>');
expect(log).toEqual('exclusive');
}); });
it('should process markup before directives', function(){ it('should process markup before directives', function(){
@ -117,7 +101,7 @@ xdescribe('compiler', function(){
} }
}); });
var scope = compile('before<span>middle</span>after'); var scope = compile('before<span>middle</span>after');
expect(scope.element.innerHTML).toEqual('before<span hello="middle">replaced</span>after'); expect(scope.$element[0].innerHTML).toEqual('before<span hello="middle">replaced</span>after');
expect(log).toEqual("hello middle"); expect(log).toEqual("hello middle");
}); });
@ -129,7 +113,7 @@ xdescribe('compiler', function(){
}; };
}; };
var scope = compile('<ng:button>push me</ng:button>'); var scope = compile('<ng:button>push me</ng:button>');
expect(scope.element.innerHTML).toEqual('<div>button</div>'); expect(scope.$element[0].innerHTML).toEqual('<div>button</div>');
expect(log).toEqual('init'); expect(log).toEqual('init');
}); });

View file

@ -14,12 +14,12 @@ TestCase("formatterTest", {
}, },
testBoolean: function() { testBoolean: function() {
assertEquals('true', angular.formatter.boolean.format(true)); assertEquals('true', angular.formatter['boolean'].format(true));
assertEquals('false', angular.formatter.boolean.format(false)); assertEquals('false', angular.formatter['boolean'].format(false));
assertEquals(true, angular.formatter.boolean.parse("true")); assertEquals(true, angular.formatter['boolean'].parse("true"));
assertEquals(false, angular.formatter.boolean.parse("")); assertEquals(false, angular.formatter['boolean'].parse(""));
assertEquals(false, angular.formatter.boolean.parse("false")); assertEquals(false, angular.formatter['boolean'].parse("false"));
assertEquals(null, angular.formatter.boolean.parse(null)); assertEquals(null, angular.formatter['boolean'].parse(null));
}, },
testNumber: function() { testNumber: function() {

View file

@ -171,7 +171,7 @@ ParserTest.prototype.testComparison = function(){
assertEquals(scope.$eval("1>2"), 1>2); assertEquals(scope.$eval("1>2"), 1>2);
assertEquals(scope.$eval("2>=1"), 2>=1); assertEquals(scope.$eval("2>=1"), 2>=1);
assertEquals(true==2<3, scope.$eval("true==2<3")); assertEquals(true === 2<3, scope.$eval("true==2<3"));
}; };

View file

@ -65,20 +65,6 @@ describe('scope/model', function(){
expect(model.$bind(function(){return this.name;})()).toEqual('misko'); expect(model.$bind(function(){return this.name;})()).toEqual('misko');
}); });
//$behavior
it('should behave as class', function(){
function Printer(brand){
this.brand = brand;
};
Printer.prototype.print = function(){
this.printed = true;
};
var model = createScope({ name: 'parent' }, Printer, 'hp');
expect(model.brand).toEqual('hp');
model.print();
expect(model.printed).toEqual(true);
});
//$tryEval //$tryEval
it('should report error on element', function(){ it('should report error on element', function(){
var scope = createScope(); var scope = createScope();
@ -108,16 +94,6 @@ describe('scope/model', function(){
expect(scope.log).toEqual('first;middle;last;'); expect(scope.log).toEqual('first;middle;last;');
}); });
// Services are initialized
it("should inject services", function(){
var scope = createScope(serviceAdapter({
$window: function(){
return window;
}
}));
expect(scope.$window).toEqual(window);
});
it("should have $root and $parent", function(){ it("should have $root and $parent", function(){
var parent = createScope(); var parent = createScope();
var scope = createScope(parent); var scope = createScope(parent);
@ -125,4 +101,37 @@ describe('scope/model', function(){
expect(scope.$parent).toEqual(parent); expect(scope.$parent).toEqual(parent);
}); });
// Service injection
it('should inject services', function(){
var scope = createScope(null, {
service:function(){
return "ABC";
}
});
expect(scope.service).toEqual("ABC");
});
it('should inject arugments', function(){
var scope = createScope(null, {
name:function(){
return "misko";
},
greet: extend(function(name) {
return 'hello ' + name;
}, {inject:['name']})
});
expect(scope.greet).toEqual("hello misko");
});
it('should throw error on missing dependency', function(){
try {
createScope(null, {
greet: extend(function(name) {
}, {inject:['name']})
});
} catch(e) {
expect(e).toEqual("Don't know how to inject 'name'.");
}
});
}); });

View file

@ -2,11 +2,7 @@ describe("services", function(){
var scope; var scope;
beforeEach(function(){ beforeEach(function(){
scope = createScope({ scope = createScope(null, angularService, {});
$config: {
'location': {'get':noop, 'set':noop, 'watch':noop}
}
}, serviceAdapter(angularService));
}); });
it("should inject $window", function(){ it("should inject $window", function(){
@ -46,4 +42,15 @@ describe("services", function(){
expect(scope.$location()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html'); expect(scope.$location()).toEqual('file:///Users/Shared/misko/work/angular.js/scenario/widgets.html');
}); });
xit('should add stylesheets', function(){
scope.$document = {
getElementsByTagName: function(name){
expect(name).toEqual('LINK');
return [];
}
};
scope.$document.addStyleSheet('css/angular.css');
});
}); });

View file

@ -3,7 +3,7 @@ dump = bind(jstd.console, jstd.console.log);
function nakedExpect(obj) { function nakedExpect(obj) {
return expect(angular.fromJson(angular.toJson(obj))); return expect(angular.fromJson(angular.toJson(obj)));
}; }
swfobject = { swfobject = {
createSwf:function() { createSwf:function() {
@ -27,15 +27,27 @@ function report(reportTest){
}); });
} }
MockLocation = function() { function MockBrowser() {
this.url = "http://server"; this.url = "http://server";
this.watches = [];
}
MockBrowser.prototype = {
getUrl: function(){
return this.url;
},
setUrl: function(url){
this.url = url;
},
watchUrl: function(fn) {
this.watches.push(fn);
}
}; };
MockLocation.prototype.get = function(){
return this.url; angularService('$browser', function(){
}; return new MockBrowser();
MockLocation.prototype.set = function(url){ });
this.url = url;
};
function childNode(element, index) { function childNode(element, index) {
return jqLite(element[0].childNodes[index]); return jqLite(element[0].childNodes[index]);
@ -80,7 +92,7 @@ function sortedHtml(element) {
} }
})(element[0]); })(element[0]);
return html; return html;
}; }
function isVisible(node) { function isVisible(node) {
var display = node.css('display'); var display = node.css('display');