mirror of
https://github.com/Hopiu/angular.js.git
synced 2026-03-16 23:30:23 +00:00
started to add services
This commit is contained in:
parent
35a9108500
commit
11a6431f89
11 changed files with 290 additions and 167 deletions
123
src/Angular.js
123
src/Angular.js
|
|
@ -1,9 +1,61 @@
|
|||
|
||||
//////////////////////////////
|
||||
//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')
|
||||
document.getAttribute = function() {};
|
||||
|
||||
if (!window['console']) window['console']={'log':noop, 'error':noop};
|
||||
|
||||
var consoleNode,
|
||||
PRIORITY_FIRST = -99999;
|
||||
PRIORITY_WATCH = -1000;
|
||||
PRIORITY_LAST = 99999;
|
||||
NOOP = 'noop',
|
||||
NG_ERROR = 'ng-error',
|
||||
NG_EXCEPTION = 'ng-exception',
|
||||
|
|
@ -13,7 +65,7 @@ var consoleNode,
|
|||
msie = !!/(msie) ([\w.]+)/.exec(lowercase(navigator.userAgent)),
|
||||
jqLite = jQuery || jqLiteWrap,
|
||||
slice = Array.prototype.slice,
|
||||
angular = window['angular'] || (window['angular'] = {}),
|
||||
angular = window['angular'] || (window['angular'] = {}),
|
||||
angularTextMarkup = extensionMap(angular, 'textMarkup'),
|
||||
angularAttrMarkup = extensionMap(angular, 'attrMarkup'),
|
||||
angularDirective = extensionMap(angular, 'directive'),
|
||||
|
|
@ -21,11 +73,30 @@ var consoleNode,
|
|||
angularValidator = extensionMap(angular, 'validator'),
|
||||
angularFilter = extensionMap(angular, 'filter'),
|
||||
angularFormatter = extensionMap(angular, 'formatter'),
|
||||
angularService = extensionMap(angular, 'service'),
|
||||
angularCallbacks = extensionMap(angular, 'callbacks'),
|
||||
angularAlert = angular['alert'] || (angular['alert'] = function(){
|
||||
log(arguments); window.alert.apply(window, arguments);
|
||||
});
|
||||
angular['copy'] = copy;
|
||||
urlWatcher = new UrlWatcher(window.location);
|
||||
|
||||
function angularAlert(){
|
||||
log(arguments); window.alert.apply(window, arguments);
|
||||
};
|
||||
|
||||
extend(angular, {
|
||||
'compile': compile,
|
||||
'startUrlWatch': bind(urlWatcher, urlWatcher.start),
|
||||
'copy': copy,
|
||||
'extend': extend,
|
||||
'foreach': foreach,
|
||||
'noop':noop,
|
||||
'identity':identity,
|
||||
'isUndefined': isUndefined,
|
||||
'isDefined': isDefined,
|
||||
'isString': isString,
|
||||
'isFunction': isFunction,
|
||||
'isNumber': isNumber,
|
||||
'isArray': isArray,
|
||||
'alert': angularAlert
|
||||
});
|
||||
|
||||
function foreach(obj, iterator, context) {
|
||||
var key;
|
||||
|
|
@ -43,6 +114,17 @@ function foreach(obj, iterator, context) {
|
|||
return obj;
|
||||
}
|
||||
|
||||
function foreachSorted(obj, iterator, context) {
|
||||
var keys = [];
|
||||
for (var key in obj) keys.push(key);
|
||||
keys.sort();
|
||||
for ( var i = 0; i < keys.length; i++) {
|
||||
iterator.call(context, obj[keys[i]], keys[i]);
|
||||
}
|
||||
return keys;
|
||||
}
|
||||
|
||||
|
||||
function extend(dst) {
|
||||
foreach(arguments, function(obj){
|
||||
if (obj !== dst) {
|
||||
|
|
@ -285,19 +367,22 @@ function merge(src, dst) {
|
|||
}
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
angular['compile'] = function(element, config) {
|
||||
config = extend({
|
||||
'onUpdateView': noop,
|
||||
'server': "",
|
||||
'location': {'get':noop, 'set':noop, 'listen':noop}
|
||||
}, config||{});
|
||||
|
||||
function compile(element, config) {
|
||||
var compiler = new Compiler(angularTextMarkup, angularAttrMarkup, angularDirective, angularWidget);
|
||||
$element = jqLite(element),
|
||||
rootScope = {
|
||||
'$window': window
|
||||
};
|
||||
return rootScope['$root'] = compiler.compile($element)($element, rootScope);
|
||||
};
|
||||
rootScope = createScope({
|
||||
$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);
|
||||
}
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ Compiler.prototype = {
|
|||
element = jqLite(element);
|
||||
parentScope = parentScope || {};
|
||||
var scope = createScope(parentScope);
|
||||
parentScope.$root = parentScope.$root || scope;
|
||||
return extend(scope, {
|
||||
$element:element,
|
||||
$init: function() {
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ Lexer.OPERATORS = {
|
|||
'true':function(self){return true;},
|
||||
'false':function(self){return false;},
|
||||
'undefined':noop,
|
||||
'+':function(self, a,b){return (a||0)+(b||0);},
|
||||
'-':function(self, a,b){return (a||0)-(b||0);},
|
||||
'+':function(self, a,b){return (isDefined(a)?a:0)+(isDefined(b)?b:0);},
|
||||
'-':function(self, a,b){return (isDefined(a)?a:0)-(isDefined(b)?b:0);},
|
||||
'*':function(self, a,b){return a*b;},
|
||||
'/':function(self, a,b){return a/b;},
|
||||
'%':function(self, a,b){return a%b;},
|
||||
|
|
|
|||
55
src/Scope.js
55
src/Scope.js
|
|
@ -89,7 +89,11 @@ function createScope(parent, Class) {
|
|||
function API(){}
|
||||
function Behavior(){}
|
||||
|
||||
var instance, behavior, api, watchList = [], evalList = [];
|
||||
var instance, behavior, api, evalLists = {};
|
||||
if (isFunction(parent)) {
|
||||
Class = parent;
|
||||
parent = {};
|
||||
}
|
||||
|
||||
Class = Class || noop;
|
||||
parent = Parent.prototype = parent || {};
|
||||
|
|
@ -107,15 +111,10 @@ function createScope(parent, Class) {
|
|||
if (isDefined(exp)) {
|
||||
return expressionCompile(exp).apply(instance, slice.call(arguments, 1, arguments.length));
|
||||
} else {
|
||||
foreach(watchList, function(watch) {
|
||||
var value = instance.$tryEval(watch.watch, watch.handler);
|
||||
if (watch.last !== value) {
|
||||
instance.$tryEval(watch.listener, watch.handler, value, watch.last);
|
||||
watch.last = value;
|
||||
}
|
||||
});
|
||||
foreach(evalList, function(eval) {
|
||||
instance.$tryEval(eval.fn, eval.handler);
|
||||
foreachSorted(evalLists, function(list) {
|
||||
foreach(list, function(eval) {
|
||||
instance.$tryEval(eval.fn, eval.handler);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
|
@ -134,16 +133,24 @@ function createScope(parent, Class) {
|
|||
},
|
||||
|
||||
$watch: function(watchExp, listener, exceptionHandler) {
|
||||
var watch = expressionCompile(watchExp);
|
||||
watchList.push({
|
||||
watch: watch,
|
||||
last: watch.call(instance),
|
||||
handler: exceptionHandler,
|
||||
listener:expressionCompile(listener)
|
||||
var watch = expressionCompile(watchExp),
|
||||
last = watch.call(instance);
|
||||
instance.$onEval(PRIORITY_WATCH, function(){
|
||||
var value = watch.call(instance);
|
||||
if (last !== value) {
|
||||
instance.$tryEval(listener, exceptionHandler, value, last);
|
||||
last = value;
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
$onEval: function(expr, exceptionHandler){
|
||||
$onEval: function(priority, expr, exceptionHandler){
|
||||
if (!isNumber(priority)) {
|
||||
exceptionHandler = expr;
|
||||
expr = priority;
|
||||
priority = 0;
|
||||
}
|
||||
var evalList = evalLists[priority] || (evalLists[priority] = []);
|
||||
evalList.push({
|
||||
fn: expressionCompile(expr),
|
||||
handler: exceptionHandler
|
||||
|
|
@ -151,7 +158,21 @@ function createScope(parent, Class) {
|
|||
}
|
||||
});
|
||||
|
||||
if (isUndefined(instance.$root)) {
|
||||
behavior.$root = instance;
|
||||
behavior.$parent = instance;
|
||||
}
|
||||
|
||||
Class.apply(instance, slice.call(arguments, 2, arguments.length));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
function serviceAdapter(services) {
|
||||
return function(){
|
||||
var self = this;
|
||||
foreach(services, function(service, name){
|
||||
self[name] = service.call(self);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
|
||||
// ////////////////////////////
|
||||
// UrlWatcher
|
||||
// ////////////////////////////
|
||||
|
||||
function UrlWatcher(location) {
|
||||
this.location = location;
|
||||
this.delay = 25;
|
||||
this.setTimeout = function(fn, delay) {
|
||||
window.setTimeout(fn, delay);
|
||||
};
|
||||
this.listener = function(url) {
|
||||
return url;
|
||||
};
|
||||
this.expectedUrl = location.href;
|
||||
}
|
||||
|
||||
UrlWatcher.prototype = {
|
||||
listen: function(fn){
|
||||
this.listener = fn;
|
||||
},
|
||||
watch: function() {
|
||||
var self = this;
|
||||
var pull = function() {
|
||||
if (self.expectedUrl !== self.location.href) {
|
||||
var notify = self.location.hash.match(/^#\$iframe_notify=(.*)$/);
|
||||
if (notify) {
|
||||
if (!self.expectedUrl.match(/#/)) {
|
||||
self.expectedUrl += "#";
|
||||
}
|
||||
self.location.href = self.expectedUrl;
|
||||
var id = '_iframe_notify_' + notify[1];
|
||||
var notifyFn = angularCallbacks[id];
|
||||
delete angularCallbacks[id];
|
||||
try {
|
||||
(notifyFn||noop)();
|
||||
} catch (e) {
|
||||
alert(e);
|
||||
}
|
||||
} else {
|
||||
self.listener(self.location.href);
|
||||
self.expectedUrl = self.location.href;
|
||||
}
|
||||
}
|
||||
self.setTimeout(pull, self.delay);
|
||||
};
|
||||
pull();
|
||||
},
|
||||
|
||||
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 window.location.href;
|
||||
}
|
||||
};
|
||||
34
src/services.js
Normal file
34
src/services.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
angularService("$window", bind(window, identity, window));
|
||||
|
||||
angularService("$anchor", function(){
|
||||
var scope = this;
|
||||
function anchor(url){
|
||||
if (isDefined(url)) {
|
||||
if (url.charAt(0) == '#') url = url.substr(1);
|
||||
var pathQuery = url.split('?');
|
||||
anchor.path = decodeURIComponent(pathQuery[0]);
|
||||
anchor.param = {};
|
||||
foreach((pathQuery[1] || "").split('&'), function(keyValue){
|
||||
if (keyValue) {
|
||||
var parts = keyValue.split('=');
|
||||
var key = decodeURIComponent(parts[0]);
|
||||
var value = parts[1];
|
||||
if (!value) value = true;
|
||||
anchor.param[key] = decodeURIComponent(value);
|
||||
}
|
||||
});
|
||||
}
|
||||
var params = [];
|
||||
foreach(anchor.param, function(value, key){
|
||||
params.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
|
||||
});
|
||||
return (anchor.path ? anchor.path : '') + (params.length ? '?' + params.join('&') : '');
|
||||
};
|
||||
this.$config.location.watch(function(url){
|
||||
anchor(url);
|
||||
});
|
||||
this.$onEval(PRIORITY_LAST, function(){
|
||||
scope.$config.location.set(anchor());
|
||||
});
|
||||
return anchor;
|
||||
});
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
AngularTest = TestCase('AngularTest');
|
||||
|
||||
UrlWatcherTest = TestCase('UrlWatcherTest');
|
||||
|
||||
UrlWatcherTest.prototype.testUrlWatcher = function () {
|
||||
expectAsserts(2);
|
||||
var location = {href:"http://server", hash:""};
|
||||
var watcher = new UrlWatcher(location);
|
||||
watcher.delay = 1;
|
||||
watcher.listener = function(url){
|
||||
assertEquals('http://getangular.test', url);
|
||||
};
|
||||
watcher.setTimeout = function(fn, delay){
|
||||
assertEquals(1, delay);
|
||||
location.href = "http://getangular.test";
|
||||
watcher.setTimeout = function(fn, delay) {
|
||||
};
|
||||
fn();
|
||||
};
|
||||
watcher.watch();
|
||||
};
|
||||
|
||||
UrlWatcherTest.prototype.testItShouldFireOnUpdateEventWhenSpecialURLSet = function(){
|
||||
expectAsserts(2);
|
||||
var location = {href:"http://server", hash:"#$iframe_notify=1234"};
|
||||
var watcher = new UrlWatcher(location);
|
||||
angular.callbacks._iframe_notify_1234 = function () {
|
||||
assertEquals("undefined", typeof angularCallbacks._iframe_notify_1234);
|
||||
assertEquals("http://server2#", location.href);
|
||||
};
|
||||
watcher.delay = 1;
|
||||
watcher.expectedUrl = "http://server2";
|
||||
watcher.setTimeout = function(fn, delay){
|
||||
watcher.setTimeout = function(fn, delay) {};
|
||||
fn();
|
||||
};
|
||||
watcher.watch();
|
||||
};
|
||||
|
||||
FunctionTest = TestCase("FunctionTest");
|
||||
|
||||
FunctionTest.prototype.testEscapeHtml = function () {
|
||||
assertEquals("<div>&amp;</div>", escapeHtml('<div>&</div>'));
|
||||
};
|
||||
|
|
@ -201,24 +201,6 @@ BinderTest.prototype.XtestParseAnchor = function(){
|
|||
assertTrue(!binder.anchor.x);
|
||||
};
|
||||
|
||||
BinderTest.prototype.XtestWriteAnchor = function(){
|
||||
var binder = this.compile("<div/>").binder;
|
||||
binder.location.set('a');
|
||||
binder.anchor.a = 'b';
|
||||
binder.anchor.c = ' ';
|
||||
binder.anchor.d = true;
|
||||
binder.updateAnchor();
|
||||
assertEquals(binder.location.get(), "a#a=b&c=%20&d");
|
||||
};
|
||||
|
||||
BinderTest.prototype.XtestWriteAnchorAsPartOfTheUpdateView = function(){
|
||||
var binder = this.compile("<div/>").binder;
|
||||
binder.location.set('a');
|
||||
binder.anchor.a = 'b';
|
||||
binder.updateView();
|
||||
assertEquals(binder.location.get(), "a#a=b");
|
||||
};
|
||||
|
||||
BinderTest.prototype.testRepeaterUpdateBindings = function(){
|
||||
var a = this.compile('<ul><LI ng-repeat="item in model.items" ng-bind="item.a"/></ul>');
|
||||
var form = a.node;
|
||||
|
|
@ -821,3 +803,23 @@ BinderTest.prototype.testItShouldUseFormaterForText = function() {
|
|||
x.scope.$eval();
|
||||
assertEquals('1, 2, 3', input[0].value);
|
||||
};
|
||||
|
||||
BinderTest.prototype.XtestWriteAnchor = function(){
|
||||
var binder = this.compile("<div/>").binder;
|
||||
binder.location.set('a');
|
||||
binder.anchor.a = 'b';
|
||||
binder.anchor.c = ' ';
|
||||
binder.anchor.d = true;
|
||||
binder.updateAnchor();
|
||||
assertEquals(binder.location.get(), "a#a=b&c=%20&d");
|
||||
};
|
||||
|
||||
BinderTest.prototype.XtestWriteAnchorAsPartOfTheUpdateView = function(){
|
||||
var binder = this.compile("<div/>").binder;
|
||||
binder.location.set('a');
|
||||
binder.anchor.a = 'b';
|
||||
binder.updateView();
|
||||
assertEquals(binder.location.get(), "a#a=b");
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,8 @@ describe('scope/model', function(){
|
|||
model.$set('name', 'adam');
|
||||
expect(model.name).toEqual('adam');
|
||||
expect(model.$get('name')).toEqual('adam');
|
||||
expect(model.$parent).toEqual(parent);
|
||||
expect(model.$parent).toEqual(model);
|
||||
expect(model.$root).toEqual(model);
|
||||
});
|
||||
|
||||
//$eval
|
||||
|
|
@ -78,15 +79,50 @@ describe('scope/model', function(){
|
|||
expect(model.printed).toEqual(true);
|
||||
});
|
||||
|
||||
|
||||
|
||||
//$tryEval
|
||||
it('should report error on element', function(){
|
||||
|
||||
var scope = createScope();
|
||||
scope.$tryEval('throw "myerror";', function(error){
|
||||
scope.error = error;
|
||||
});
|
||||
expect(scope.error).toEqual('myerror');
|
||||
});
|
||||
|
||||
it('should report error on visible element', function(){
|
||||
var element = jqLite('<div></div>');
|
||||
var scope = createScope();
|
||||
scope.$tryEval('throw "myError"', element);
|
||||
expect(element.attr('ng-error')).toEqual('"myError"'); // errors are jsonified
|
||||
expect(element.hasClass('ng-exception')).toBeTruthy();
|
||||
});
|
||||
|
||||
// $onEval
|
||||
|
||||
it("should eval using priority", function(){
|
||||
var scope = createScope();
|
||||
scope.log = "";
|
||||
scope.$onEval('log = log + "middle;"');
|
||||
scope.$onEval(-1, 'log = log + "first;"');
|
||||
scope.$onEval(1, 'log = log + "last;"');
|
||||
scope.$eval();
|
||||
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(){
|
||||
var parent = createScope();
|
||||
var scope = createScope(parent);
|
||||
expect(scope.$root).toEqual(parent);
|
||||
expect(scope.$parent).toEqual(parent);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
25
test/UrlWatcherTest.js
Normal file
25
test/UrlWatcherTest.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
UrlWatcherTest = TestCase('UrlWatcherTest');
|
||||
|
||||
UrlWatcherTest.prototype.testUrlWatcher = function () {
|
||||
expectAsserts(2);
|
||||
var location = {href:"http://server", hash:""};
|
||||
var watcher = new UrlWatcher(location);
|
||||
watcher.delay = 1;
|
||||
watcher.watch(function(url){
|
||||
assertEquals('http://getangular.test', url);
|
||||
});
|
||||
watcher.setTimeout = function(fn, delay){
|
||||
assertEquals(1, delay);
|
||||
location.href = "http://getangular.test";
|
||||
watcher.setTimeout = function(fn, delay) {
|
||||
};
|
||||
fn();
|
||||
};
|
||||
watcher.start();
|
||||
};
|
||||
|
||||
FunctionTest = TestCase("FunctionTest");
|
||||
|
||||
FunctionTest.prototype.testEscapeHtml = function () {
|
||||
assertEquals("<div>&amp;</div>", escapeHtml('<div>&</div>'));
|
||||
};
|
||||
27
test/servicesSpec.js
Normal file
27
test/servicesSpec.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
describe("services", function(){
|
||||
var scope;
|
||||
|
||||
beforeEach(function(){
|
||||
scope = createScope({
|
||||
$config: {
|
||||
'location': {'get':noop, 'set':noop, 'watch':noop}
|
||||
}
|
||||
}, serviceAdapter(angularService));
|
||||
});
|
||||
|
||||
it("should inject $window", function(){
|
||||
expect(scope.$window).toEqual(window);
|
||||
});
|
||||
|
||||
it("should inject $anchor", function(){
|
||||
scope.$anchor('#path?key=value');
|
||||
expect(scope.$anchor.path).toEqual("path");
|
||||
expect(scope.$anchor.param).toEqual({key:'value'});
|
||||
|
||||
scope.$anchor.path = 'page=http://path';
|
||||
scope.$anchor.param = {k:'a=b'};
|
||||
|
||||
expect(scope.$anchor()).toEqual('page=http://path?k=a%3Db');
|
||||
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue