aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Angular.js78
-rw-r--r--src/Scope.js2
-rw-r--r--src/Widgets.js78
-rw-r--r--src/angular-bootstrap.js39
-rw-r--r--src/delete/Model.js (renamed from src/moveToAngularCom/Model.js)0
-rw-r--r--src/markups.js1
-rw-r--r--src/services.js52
7 files changed, 189 insertions, 61 deletions
diff --git a/src/Angular.js b/src/Angular.js
index 8eef6ac0..d97c2282 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -1,3 +1,52 @@
+
+//////////////////////////////
+//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() {};
@@ -16,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'),
@@ -336,3 +385,30 @@ function compile(element, config) {
return compiler.compile($element)($element, rootScope);
}
/////////////////////////////////////////////////
+
+function parseKeyValue(keyValue) {
+ var obj = {}, key_value, key;
+ foreach((keyValue || "").split('&'), function(keyValue){
+ if (keyValue) {
+ key_value = keyValue.split('=');
+ key = decodeURIComponent(key_value[0]);
+ obj[key] = key_value[1] ? decodeURIComponent(key_value[1]) : true;
+ }
+ });
+ return obj;
+}
+
+function toKeyValue(obj) {
+ var parts = [];
+ foreach(obj, function(value, key){
+ parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
+ });
+ return parts.length ? parts.join('&') : '';
+};
+
+function angularInit(config){
+ if (config.autobind) {
+ compile(window.document, config).$init();
+ }
+}
+
diff --git a/src/Scope.js b/src/Scope.js
index ba86e24f..2b2db189 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -102,6 +102,7 @@ function createScope(parent, Class) {
instance = new Behavior();
extend(api, {
+ 'this': instance,
$parent: parent,
$bind: bind(instance, bind, instance),
$get: bind(instance, getter, instance),
@@ -163,6 +164,7 @@ function createScope(parent, Class) {
behavior.$parent = instance;
}
+ (parent.$onEval || noop)(instance.$eval);
Class.apply(instance, slice.call(arguments, 2, arguments.length));
return instance;
diff --git a/src/Widgets.js b/src/Widgets.js
index f172eae2..e42d981c 100644
--- a/src/Widgets.js
+++ b/src/Widgets.js
@@ -85,8 +85,8 @@ function optionsAccessor(scope, element) {
function noopAccessor() { return { get: noop, set: noop }; }
-var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''),
- buttonWidget = inputWidget('click', noopAccessor, noopAccessor, undefined),
+var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue('')),
+ buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
INPUT_TYPE = {
'text': textWidget,
'textarea': textWidget,
@@ -96,29 +96,42 @@ var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, ''),
'submit': buttonWidget,
'reset': buttonWidget,
'image': buttonWidget,
- 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, false),
- 'radio': inputWidget('click', modelAccessor, radioAccessor, undefined),
- 'select-one': inputWidget('click', modelAccessor, valueAccessor, null),
- 'select-multiple': inputWidget('click', modelAccessor, optionsAccessor, [])
+ 'checkbox': inputWidget('click', modelAccessor, checkedAccessor, initWidgetValue(false)),
+ 'radio': inputWidget('click', modelAccessor, radioAccessor, radioInit),
+ 'select-one': inputWidget('change', modelAccessor, valueAccessor, initWidgetValue(null)),
+ 'select-multiple': inputWidget('change', modelAccessor, optionsAccessor, initWidgetValue([]))
// 'file': fileWidget???
};
-function inputWidget(events, modelAccessor, viewAccessor, initValue) {
+function initWidgetValue(initValue) {
+ return function (model, view) {
+ var value = view.get() || copy(initValue);
+ if (isUndefined(model.get()) && isDefined(value))
+ model.set(value);
+ };
+}
+
+function radioInit(model, view) {
+ var modelValue = model.get(), viewValue = view.get();
+ if (isUndefined(modelValue)) model.set(null);
+ if (viewValue != null) model.set(viewValue);
+}
+
+function inputWidget(events, modelAccessor, viewAccessor, initFn) {
return function(element) {
var scope = this,
model = modelAccessor(scope, element),
view = viewAccessor(scope, element),
- action = element.attr('ng-change') || '',
- value = view.get() || copy(initValue);
- if (isUndefined(model.get()) && isDefined(value)) model.set(value);
+ action = element.attr('ng-change') || '';
+ initFn(model, view);
this.$eval(element.attr('ng-init')||'');
element.bind(events, function(){
model.set(view.get());
scope.$tryEval(action, element);
scope.$root.$eval();
- // if we have no initValue than we are just a button,
+ // if we have noop initFn than we are just a button,
// therefore we want to prevent default action
- return isDefined(initValue);
+ return initFn != noop;
});
view.set(model.get());
scope.$watch(model.get, view.set);
@@ -137,3 +150,44 @@ angularWidget('SELECT', function(element){
this.descend(true);
return inputWidgetSelector.call(this, element);
});
+
+
+angularWidget('INLINE', function(element){
+ element.replaceWith(this.element("div"));
+ var compiler = this,
+ behavior = element.attr("behavior"),
+ template = element.attr("template"),
+ initExpr = element.attr("init");
+ return function(boundElement){
+ var scope = this;
+ boundElement.load(template, function(){
+ var templateScope = compiler.compile(boundElement)(boundElement, scope);
+ templateScope.$tryEval(initExpr, boundElement);
+ templateScope.$init();
+ });
+ };
+});
+
+angularWidget('INCLUDE', function(element){
+ element.replaceWith(this.element("div"));
+ var matches = [];
+ element.find("INLINE").each(function(){
+ matches.push({match: jQuery(this).attr("match"), element: jQuery(this)});
+ });
+ var compiler = this,
+ watchExpr = element.attr("watch");
+ return function(boundElement){
+ var scope = this;
+ this.$watch(watchExpr, function(value){
+ foreach(matches, function(inline){
+ if(inline.match == value) {
+ var template = inline.element.attr("template");
+ boundElement.load(template, function(){
+ var templateScope = compiler.compile(boundElement)(boundElement, scope);
+ templateScope.$init();
+ });
+ }
+ });
+ });
+ };
+});
diff --git a/src/angular-bootstrap.js b/src/angular-bootstrap.js
index 7798afa5..b0a3aa4f 100644
--- a/src/angular-bootstrap.js
+++ b/src/angular-bootstrap.js
@@ -22,23 +22,16 @@
* THE SOFTWARE.
*/
(function(previousOnLoad){
- var filename = /(.*)\/angular-(.*).js(#(.*))?/;
- var scripts = document.getElementsByTagName("SCRIPT");
- var serverPath;
- var config = {};
+ var filename = /(.*)\/angular-(.*).js(#(.*))?/,
+ scripts = document.getElementsByTagName("SCRIPT"),
+ serverPath,
+ config,
+ match;
for(var j = 0; j < scripts.length; j++) {
- var match = (scripts[j].src || "").match(filename);
+ match = (scripts[j].src || "").match(filename);
if (match) {
serverPath = match[1];
- parseConfig(match[4]);
- }
- }
-
- function parseConfig(args) {
- var keyValues = args.split('&'), keyValue, i = 0;
- for (; i < keyValues.length; i++) {
- keyValue = keyValues[i].split('=');
- config[keyValue[0]] = keyValue[1] || true;
+ config = match[4];
}
}
@@ -53,7 +46,6 @@
addScript("/jqlite.js");
addScript("/Parser.js");
addScript("/Resource.js");
- addScript("/URLWatcher.js");
// Extension points
addScript("/apis.js");
@@ -63,17 +55,14 @@
addScript("/directives.js");
addScript("/markups.js");
addScript("/widgets.js");
+ addScript("/services.js");
- if (config.autobind) {
- window.onload = function(){
- try {
- if (previousOnLoad) previousOnLoad();
- } catch(e) {}
- var scope = angular.compile(window.document, config);
- if (config.rootScope) window[config.rootScope] = scope;
- scope.$init();
- };
- }
+ window.onload = function(){
+ try {
+ if (previousOnLoad) previousOnLoad();
+ } catch(e) {}
+ angularInit(parseKeyValue(config));
+ };
})(window.onload);
diff --git a/src/moveToAngularCom/Model.js b/src/delete/Model.js
index b09efd0e..b09efd0e 100644
--- a/src/moveToAngularCom/Model.js
+++ b/src/delete/Model.js
diff --git a/src/markups.js b/src/markups.js
index 6bc27c85..3ae713fb 100644
--- a/src/markups.js
+++ b/src/markups.js
@@ -51,6 +51,7 @@ angularTextMarkup('{{}}', function(text, textNode, parentElement) {
}
});
+// TODO: this should be widget not a markup
angularTextMarkup('OPTION', function(text, textNode, parentElement){
if (parentElement[0].nodeName == "OPTION") {
var select = document.createElement('select');
diff --git a/src/services.js b/src/services.js
index 14c71363..fc12b22b 100644
--- a/src/services.js
+++ b/src/services.js
@@ -1,34 +1,40 @@
angularService("$window", bind(window, identity, window));
-angularService("$anchor", function(){
+var URL_MATCH = /^(file|ftp|http|https):\/\/(\w+:{0,1}\w*@)?([\w\.]*)(:([0-9]+))?([^\?#]+)(\?([^#]*))?((#([^\?]*))?(\?([^\?]*))?)$/;
+var DEFAULT_PORTS = {'http': 80, 'https': 443, 'ftp':21};
+angularService("$location", function(){
var scope = this;
- function anchor(url){
+ function location(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 match = URL_MATCH.exec(url);
+ if (match) {
+ location.href = url;
+ location.protocol = match[1];
+ location.host = match[3] || '';
+ location.port = match[5] || DEFAULT_PORTS[location.href] || null;
+ location.path = match[6];
+ location.search = parseKeyValue(match[8]);
+ location.hash = match[9];
+ if (location.hash) location.hash = location.hash.substr(1);
+ location.hashPath = match[11] || '';
+ location.hashSearch = parseKeyValue(match[13]);
+ }
}
- var params = [];
- foreach(anchor.param, function(value, key){
- params.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
- });
- return (anchor.path ? anchor.path : '') + (params.length ? '?' + params.join('&') : '');
+ var hashKeyValue = toKeyValue(location.hashSearch);
+ return location.href +
+ (location.hashPath ? location.hashPath : '') +
+ (hashKeyValue ? '?' + hashKeyValue : '');
};
this.$config.location.watch(function(url){
- anchor(url);
+ location(url);
});
+ location(this.$config.location.get());
this.$onEval(PRIORITY_LAST, function(){
- scope.$config.location.set(anchor());
+ var href = location();
+ if (href != location.href) {
+ scope.$config.location.set(location());
+ location.href = href;
+ }
});
- return anchor;
+ return location;
});