From 0df93fd49c1687b2eddaa79faa1c0adbef82bf72 Mon Sep 17 00:00:00 2001
From: Misko Hevery
Date: Wed, 7 Apr 2010 10:17:15 -0700
Subject: clean up, fixes for app
---
src/Angular.js | 17 ++-
src/Compiler.js | 2 +-
src/Filters.js | 320 -----------------------------------------------------
src/Formatters.js | 23 ----
src/Scope.js | 2 +-
src/Validators.js | 113 -------------------
src/Widgets.js | 204 ----------------------------------
src/angular.prefix | 2 +-
src/angular.suffix | 8 ++
src/directives.js | 1 +
src/filters.js | 320 +++++++++++++++++++++++++++++++++++++++++++++++++++++
src/formatters.js | 23 ++++
src/validators.js | 113 +++++++++++++++++++
src/widgets.js | 229 ++++++++++++++++++++++++++++++++++++++
14 files changed, 713 insertions(+), 664 deletions(-)
delete mode 100644 src/Filters.js
delete mode 100644 src/Formatters.js
delete mode 100644 src/Validators.js
delete mode 100644 src/Widgets.js
create mode 100644 src/filters.js
create mode 100644 src/formatters.js
create mode 100644 src/validators.js
create mode 100644 src/widgets.js
(limited to 'src')
diff --git a/src/Angular.js b/src/Angular.js
index 2d67b2cb..3b5e1c90 100644
--- a/src/Angular.js
+++ b/src/Angular.js
@@ -102,7 +102,7 @@ function isTextNode(node) { return nodeName(node) == '#text'; }
function lowercase(value){ return isString(value) ? value.toLowerCase() : value; }
function uppercase(value){ return isString(value) ? value.toUpperCase() : 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) {
var results = [];
foreach(obj, function(value, index, list) {
@@ -274,6 +274,8 @@ function escapeAttr(html) {
}
function bind(_this, _function) {
+ if (!isFunction(_function))
+ throw "Not a function!";
var curryArgs = slice.call(arguments, 2, arguments.length);
return function() {
return _function.apply(_this, curryArgs.concat(slice.call(arguments, 0, arguments.length)));
@@ -347,3 +349,16 @@ function angularInit(config){
scope.$init();
}
}
+
+function angularJsConfig(document) {
+ var filename = /(.*)\/angular(-(.*))?.js(#(.*))?/,
+ scripts = document.getElementsByTagName("SCRIPT"),
+ match;
+ for(var j = 0; j < scripts.length; j++) {
+ match = (scripts[j].src || "").match(filename);
+ if (match) {
+ return match[5];
+ }
+ }
+ return "";
+}
diff --git a/src/Compiler.js b/src/Compiler.js
index 67c22461..ae2bcdb6 100644
--- a/src/Compiler.js
+++ b/src/Compiler.js
@@ -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
* bind to a new instance of elements. It also provides a list
* of child paths which contain child templates
diff --git a/src/Filters.js b/src/Filters.js
deleted file mode 100644
index dac8d31d..00000000
--- a/src/Filters.js
+++ /dev/null
@@ -1,320 +0,0 @@
-angularFilter.Meta = function(obj){
- if (obj) {
- for ( var key in obj) {
- this[key] = obj[key];
- }
- }
-};
-angularFilter.Meta.get = function(obj, attr){
- attr = attr || 'text';
- switch(typeof obj) {
- case "string":
- return attr == "text" ? obj : undefined;
- case "object":
- if (obj && typeof obj[attr] !== "undefined") {
- return obj[attr];
- }
- return undefined;
- default:
- return obj;
- }
-};
-
-var angularFilterGoogleChartApi;
-
-foreach({
- 'currency': function(amount){
- jQuery(this.$element).toggleClass('ng-format-negative', amount < 0);
- return '$' + angularFilter['number'].apply(this, [amount, 2]);
- },
-
- 'number': function(amount, fractionSize){
- if (isNaN(amount) || !isFinite(amount)) {
- return '';
- }
- fractionSize = typeof fractionSize == 'undefined' ? 2 : fractionSize;
- var isNegative = amount < 0;
- amount = Math.abs(amount);
- var pow = Math.pow(10, fractionSize);
- var text = "" + Math.round(amount * pow);
- var whole = text.substring(0, text.length - fractionSize);
- whole = whole || '0';
- var frc = text.substring(text.length - fractionSize);
- text = isNegative ? '-' : '';
- for (var i = 0; i < whole.length; i++) {
- if ((whole.length - i)%3 === 0 && i !== 0) {
- text += ',';
- }
- text += whole.charAt(i);
- }
- if (fractionSize > 0) {
- for (var j = frc.length; j < fractionSize; j++) {
- frc += '0';
- }
- text += '.' + frc.substring(0, fractionSize);
- }
- return text;
- },
-
- 'date': function(amount) {
- },
-
- 'json': function(object) {
- jQuery(this.$element).addClass("ng-monospace");
- return toJson(object, true);
- },
-
- 'trackPackage': (function(){
- var MATCHERS = [
- { name: "UPS",
- url: "http://wwwapps.ups.com/WebTracking/processInputRequest?sort_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=en_US&track.x=0&track.y=0&InquiryNumber1=",
- regexp: [
- /^1Z[0-9A-Z]{16}$/i]},
- { name: "FedEx",
- url: "http://www.fedex.com/Tracking?tracknumbers=",
- regexp: [
- /^96\d{10}?$/i,
- /^96\d{17}?$/i,
- /^96\d{20}?$/i,
- /^\d{15}$/i,
- /^\d{12}$/i]},
- { name: "USPS",
- url: "http://trkcnfrm1.smi.usps.com/PTSInternetWeb/InterLabelInquiry.do?origTrackNum=",
- regexp: [
- /^(91\d{20})$/i,
- /^(91\d{18})$/i]}];
- return function(trackingNo, noMatch) {
- trackingNo = trim(trackingNo);
- var tNo = trackingNo.replace(/ /g, '');
- var returnValue;
- foreach(MATCHERS, function(carrier){
- foreach(carrier.regexp, function(regexp){
- if (!returnValue && regexp.test(tNo)) {
- var text = carrier.name + ": " + trackingNo;
- var url = carrier.url + trackingNo;
- returnValue = new angularFilter.Meta({
- text:text,
- url:url,
- html: '' + text + '',
- trackingNo:trackingNo});
- }
- });
- });
- if (returnValue)
- return returnValue;
- else if (trackingNo)
- return noMatch || new angularFilter.Meta({text:trackingNo + " is not recognized"});
- else
- return null;
- };})(),
-
- 'link': function(obj, title) {
- var text = title || angularFilter.Meta.get(obj);
- var url = angularFilter.Meta.get(obj, "url") || angularFilter.Meta.get(obj);
- if (url) {
- if (angular.validator.email(url) === null) {
- url = "mailto:" + url;
- }
- var html = '' + text + '';
- return new angularFilter.Meta({text:text, url:url, html:html});
- }
- return obj;
- },
-
-
- 'bytes': (function(){
- var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
- return function(size) {
- if(size === null) return "";
-
- var suffix = 0;
- while (size > 1000) {
- size = size / 1024;
- suffix++;
- }
- var txt = "" + size;
- var dot = txt.indexOf('.');
- if (dot > -1 && dot + 2 < txt.length) {
- txt = txt.substring(0, dot + 2);
- }
- return txt + " " + SUFFIX[suffix];
- };
- })(),
-
- 'image': function(obj, width, height) {
- if (obj && obj.url) {
- var style = "";
- if (width) {
- style = ' style="max-width: ' + width +
- 'px; max-height: ' + (height || width) + 'px;"';
- }
- return new angularFilter.Meta({url:obj.url, text:obj.url,
- html:'
'});
- }
- return null;
- },
-
- 'lowercase': function (obj) {
- var text = angularFilter.Meta.get(obj);
- return text ? ("" + text).toLowerCase() : text;
- },
-
- 'uppercase': function (obj) {
- var text = angularFilter.Meta.get(obj);
- return text ? ("" + text).toUpperCase() : text;
- },
-
- 'linecount': function (obj) {
- var text = angularFilter.Meta.get(obj);
- if (text==='' || !text) return 1;
- return text.split(/\n|\f/).length;
- },
-
- 'if': function (result, expression) {
- return expression ? result : undefined;
- },
-
- 'unless': function (result, expression) {
- return expression ? undefined : result;
- },
-
- 'googleChartApi': extend(
- function(type, data, width, height) {
- data = data || {};
- var chart = {
- 'cht':type,
- 'chco':angularFilterGoogleChartApi['collect'](data, 'color'),
- 'chtt':angularFilterGoogleChartApi['title'](data),
- 'chdl':angularFilterGoogleChartApi['collect'](data, 'label'),
- 'chd':angularFilterGoogleChartApi['values'](data),
- 'chf':'bg,s,FFFFFF00'
- };
- if (_.isArray(data['xLabels'])) {
- chart['chxt']='x';
- chart['chxl']='0:|' + data.xLabels.join('|');
- }
- return angularFilterGoogleChartApi['encode'](chart, width, height);
- },
- {
- 'values': function(data){
- var seriesValues = [];
- foreach(data['series']||[], function(serie){
- var values = [];
- foreach(serie['values']||[], function(value){
- values.push(value);
- });
- seriesValues.push(values.join(','));
- });
- var values = seriesValues.join('|');
- return values === "" ? null : "t:" + values;
- },
-
- 'title': function(data){
- var titles = [];
- var title = data['title'] || [];
- foreach(_.isArray(title)?title:[title], function(text){
- titles.push(encodeURIComponent(text));
- });
- return titles.join('|');
- },
-
- 'collect': function(data, key){
- var outterValues = [];
- var count = 0;
- foreach(data['series']||[], function(serie){
- var innerValues = [];
- var value = serie[key] || [];
- foreach(_.isArray(value)?value:[value], function(color){
- innerValues.push(encodeURIComponent(color));
- count++;
- });
- outterValues.push(innerValues.join('|'));
- });
- return count?outterValues.join(','):null;
- },
-
- 'encode': function(params, width, height) {
- width = width || 200;
- height = height || width;
- var url = "http://chart.apis.google.com/chart?";
- var urlParam = [];
- params['chs'] = width + "x" + height;
- foreach(params, function(value, key){
- if (value) {
- urlParam.push(key + "=" + value);
- }
- });
- urlParam.sort();
- url += urlParam.join("&");
- return new angularFilter.Meta({url:url,
- html:'
'});
- }
- }
- ),
-
-
- 'qrcode': function(value, width, height) {
- return angularFilterGoogleChartApi['encode']({
- 'cht':'qr', 'chl':encodeURIComponent(value)}, width, height);
- },
- 'chart': {
- 'pie':function(data, width, height) {
- return angularFilterGoogleChartApi('p', data, width, height);
- },
- 'pie3d':function(data, width, height) {
- return angularFilterGoogleChartApi('p3', data, width, height);
- },
- 'pieConcentric':function(data, width, height) {
- return angularFilterGoogleChartApi('pc', data, width, height);
- },
- 'barHorizontalStacked':function(data, width, height) {
- return angularFilterGoogleChartApi('bhs', data, width, height);
- },
- 'barHorizontalGrouped':function(data, width, height) {
- return angularFilterGoogleChartApi('bhg', data, width, height);
- },
- 'barVerticalStacked':function(data, width, height) {
- return angularFilterGoogleChartApi('bvs', data, width, height);
- },
- 'barVerticalGrouped':function(data, width, height) {
- return angularFilterGoogleChartApi('bvg', data, width, height);
- },
- 'line':function(data, width, height) {
- return angularFilterGoogleChartApi('lc', data, width, height);
- },
- 'sparkline':function(data, width, height) {
- return angularFilterGoogleChartApi('ls', data, width, height);
- },
- 'scatter':function(data, width, height) {
- return angularFilterGoogleChartApi('s', data, width, height);
- }
- },
-
- 'html': function(html){
- return new angularFilter.Meta({html:html});
- },
-
- 'linky': function(text){
- if (!text) return text;
- function regExpEscape(text) {
- return text.replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1');
- }
- var URL = /(ftp|http|https|mailto):\/\/([^\(\)|\s]+)/;
- var match;
- var raw = text;
- var html = [];
- while (match=raw.match(URL)) {
- var url = match[0].replace(/[\.\;\,\(\)\{\}\<\>]$/,'');
- var i = raw.indexOf(url);
- html.push(escapeHtml(raw.substr(0, i)));
- html.push('');
- html.push(url);
- html.push('');
- raw = raw.substring(i + url.length);
- }
- html.push(escapeHtml(raw));
- return new angularFilter.Meta({text:text, html:html.join('')});
- }
-}, function(v,k){angularFilter[k] = v;});
-
-angularFilterGoogleChartApi = angularFilter['googleChartApi'];
diff --git a/src/Formatters.js b/src/Formatters.js
deleted file mode 100644
index ee63c1a5..00000000
--- a/src/Formatters.js
+++ /dev/null
@@ -1,23 +0,0 @@
-function formater(format, parse) {return {'format':format, 'parse':parse || format};}
-function toString(obj) {return isDefined(obj) ? "" + obj : obj;}
-extend(angularFormatter, {
- 'noop':formater(identity, identity),
- 'boolean':formater(toString, toBoolean),
- 'number':formater(toString, function(obj){return 1*obj;}),
-
- 'list':formater(
- function(obj) { return obj ? obj.join(", ") : obj; },
- function(value) {
- var list = [];
- foreach((value || '').split(','), function(item){
- item = trim(item);
- if (item) list.push(item);
- });
- return list;
- }
- ),
-
- 'trim':formater(
- function(obj) { return obj ? trim("" + obj) : ""; }
- )
-});
diff --git a/src/Scope.js b/src/Scope.js
index b41f7436..0bc551c4 100644
--- a/src/Scope.js
+++ b/src/Scope.js
@@ -17,7 +17,7 @@ function getter(instance, path, unboundFn) {
type = angular[type.charAt(0).toUpperCase()+type.substring(1)];
var fn = type ? type[[key.substring(1)]] : undefined;
if (fn) {
- instance = bind(fn, lastInstance, lastInstance);
+ instance = bind(lastInstance, fn, lastInstance);
return instance;
}
}
diff --git a/src/Validators.js b/src/Validators.js
deleted file mode 100644
index e3da0a81..00000000
--- a/src/Validators.js
+++ /dev/null
@@ -1,113 +0,0 @@
-foreach({
- 'noop': noop,
-
- 'regexp': function(value, regexp, msg) {
- if (!value.match(regexp)) {
- return msg ||
- "Value does not match expected format " + regexp + ".";
- } else {
- return null;
- }
- },
-
- 'number': function(value, min, max) {
- var num = 1 * value;
- if (num == value) {
- if (typeof min != 'undefined' && num < min) {
- return "Value can not be less than " + min + ".";
- }
- if (typeof min != 'undefined' && num > max) {
- return "Value can not be greater than " + max + ".";
- }
- return null;
- } else {
- return "Not a number";
- }
- },
-
- 'integer': function(value, min, max) {
- var numberError = angularValidator['number'](value, min, max);
- if (numberError) return numberError;
- if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) {
- return "Not a whole number";
- }
- return null;
- },
-
- 'date': function(value, min, max) {
- if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) {
- return null;
- }
- return "Value is not a date. (Expecting format: 12/31/2009).";
- },
-
- 'ssn': function(value) {
- if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {
- return null;
- }
- return "SSN needs to be in 999-99-9999 format.";
- },
-
- 'email': function(value) {
- if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {
- return null;
- }
- return "Email needs to be in username@host.com format.";
- },
-
- 'phone': function(value) {
- if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {
- return null;
- }
- if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) {
- return null;
- }
- return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
- },
-
- 'url': function(value) {
- if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {
- return null;
- }
- return "URL needs to be in http://server[:port]/path format.";
- },
-
- 'json': function(value) {
- try {
- fromJson(value);
- return null;
- } catch (e) {
- return e.toString();
- }
- },
-
- 'asynchronous': function(text, asynchronousFn) {
- var element = this['$element'];
- var cache = element.data('$validateState');
- if (!cache) {
- cache = { state: {}};
- element.data('$validateState', cache);
- }
- var state = cache.state[text];
- cache.lastKey = text;
- if (state === undefined) {
- // we have never seen this before, Request it
- element.addClass('ng-input-indicator-wait');
- state = cache.state[text] = null;
- (asynchronousFn || noop)(text, function(error){
- state = cache.state[text] = error ? error : false;
- if (cache.state[cache.lastKey] !== null) {
- element.removeClass('ng-input-indicator-wait');
- }
- elementError(element, NG_VALIDATION_ERROR, error);
- });
- }
-
- if (state === null){
- // request in flight, mark widget invalid, but don't show it to user
- (this['$invalidWidgets']||[]).push(this.$element);
- }
- return state;
- }
-
-}, function(v,k) {angularValidator[k] = v;});
diff --git a/src/Widgets.js b/src/Widgets.js
deleted file mode 100644
index 8e668c8f..00000000
--- a/src/Widgets.js
+++ /dev/null
@@ -1,204 +0,0 @@
-function modelAccessor(scope, element) {
- var expr = element.attr('name'),
- farmatterName = element.attr('ng-format') || NOOP,
- formatter = angularFormatter(farmatterName);
- if (!expr) throw "Required field 'name' not found.";
- if (!formatter) throw "Formatter named '" + farmatterName + "' not found.";
- return {
- get: function() {
- return formatter['format'](scope.$eval(expr));
- },
- set: function(value) {
- scope.$tryEval(expr + '=' + toJson(formatter['parse'](value)), element);
- }
- };
-}
-
-function compileValidator(expr) {
- return new Parser(expr).validator()();
-}
-
-function valueAccessor(scope, element) {
- var validatorName = element.attr('ng-validate') || NOOP,
- validator = compileValidator(validatorName),
- required = element.attr('ng-required'),
- lastError;
- required = required || required === '';
- if (!validator) throw "Validator named '" + validatorName + "' not found.";
- function validate(value) {
- var error = required && !trim(value) ? "Required" : validator({self:scope, scope:{get:scope.$get, set:scope.$set}}, value);
- if (error !== lastError) {
- elementError(element, NG_VALIDATION_ERROR, error);
- lastError = error;
- }
- return value;
- }
- return {
- get: function(){ return validate(element.val()); },
- set: function(value){ element.val(validate(value)); }
- };
-}
-
-function checkedAccessor(scope, element) {
- var domElement = element[0];
- return {
- get: function(){
- return !!domElement.checked;
- },
- set: function(value){
- domElement.checked = !!value;
- }
- };
-}
-
-function radioAccessor(scope, element) {
- var domElement = element[0];
- return {
- get: function(){
- return domElement.checked ? domElement.value : null;
- },
- set: function(value){
- domElement.checked = value == domElement.value;
- }
- };
-}
-
-function optionsAccessor(scope, element) {
- var options = element[0].options;
- return {
- get: function(){
- var values = [];
- foreach(options, function(option){
- if (option.selected) values.push(option.value);
- });
- return values;
- },
- set: function(values){
- var keys = {};
- foreach(values, function(value){ keys[value] = true; });
- foreach(options, function(option){
- option.selected = keys[option.value];
- });
- }
- };
-}
-
-function noopAccessor() { return { get: noop, set: noop }; }
-
-var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue('')),
- buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
- INPUT_TYPE = {
- 'text': textWidget,
- 'textarea': textWidget,
- 'hidden': textWidget,
- 'password': textWidget,
- 'button': buttonWidget,
- 'submit': buttonWidget,
- 'reset': buttonWidget,
- 'image': buttonWidget,
- '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 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, element) {
- var modelValue = model.get(), viewValue = view.get(), input = element[0];
- input.name = this.$id + '@' + input.name;
- 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') || '';
- initFn.call(scope, model, view, element);
- this.$eval(element.attr('ng-init')||'');
- // Don't register a handler if we are a button (noopAccessor) and there is no action
- if (action || modelAccessor !== noopAccessor) {
- element.bind(events, function(){
- model.set(view.get());
- scope.$tryEval(action, element);
- scope.$root.$eval();
- // 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());
- scope.$watch(model.get, view.set);
- };
-}
-
-function inputWidgetSelector(element){
- this.directives(true);
- return INPUT_TYPE[lowercase(element[0].type)] || noop;
-}
-
-angularWidget('INPUT', inputWidgetSelector);
-angularWidget('TEXTAREA', inputWidgetSelector);
-angularWidget('BUTTON', inputWidgetSelector);
-angularWidget('SELECT', function(element){
- this.descend(true);
- return inputWidgetSelector.call(this, element);
-});
-
-
-angularWidget('NG:INCLUDE', function(element){
- var compiler = this,
- src = element.attr("src");
- return element.attr('switch-instance') ? null : function(element){
- var scope = this, childScope;
- element.attr('switch-instance', 'compiled');
- scope.$browser.xhr('GET', src, function(code, response){
- element.html(response);
- childScope = createScope(scope);
- compiler.compile(element)(element, childScope);
- childScope.$init();
- });
- scope.$onEval(function(){ if (childScope) childScope.$eval(); });
- };
-});
-
-angularWidget('NG:SWITCH', function(element){
- var compiler = this,
- watchExpr = element.attr("on"),
- cases = [];
- eachNode(element, function(caseElement){
- var when = caseElement.attr('ng-switch-when');
- if (when) {
- cases.push({
- when: function(value){ return value == when; },
- element: caseElement,
- template: compiler.compile(caseElement)
- });
- }
- });
- element.html('');
- return function(element){
- var scope = this;
- this.$watch(watchExpr, function(value){
- element.html('');
- foreach(cases, function(switchCase){
- if (switchCase.when(value)) {
- element.append(switchCase.element);
- var childScope = createScope(scope);
- switchCase.template(switchCase.element, childScope);
- childScope.$init();
- }
- });
- });
- };
-});
diff --git a/src/angular.prefix b/src/angular.prefix
index 0552b2ed..a1b4e151 100644
--- a/src/angular.prefix
+++ b/src/angular.prefix
@@ -21,4 +21,4 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
-(function(window, document, onLoadDelegate){
+(function(window, document, previousOnLoad){
diff --git a/src/angular.suffix b/src/angular.suffix
index c5754df2..36d73df2 100644
--- a/src/angular.suffix
+++ b/src/angular.suffix
@@ -1 +1,9 @@
+
+ window.onload = function(){
+ try {
+ if (previousOnLoad) previousOnLoad();
+ } catch(e) {}
+ angularInit(parseKeyValue(angularJsConfig(document)));
+ };
+
})(window, document, window.onload);
diff --git a/src/directives.js b/src/directives.js
index d1b1dba3..5cee0978 100644
--- a/src/directives.js
+++ b/src/directives.js
@@ -12,6 +12,7 @@ angularDirective("ng-controller", function(expression){
if (!isFunction(controller))
throw "Reference '"+expression+"' is not a class.";
this.$become(controller);
+ (this.init || noop)();
};
});
diff --git a/src/filters.js b/src/filters.js
new file mode 100644
index 00000000..dac8d31d
--- /dev/null
+++ b/src/filters.js
@@ -0,0 +1,320 @@
+angularFilter.Meta = function(obj){
+ if (obj) {
+ for ( var key in obj) {
+ this[key] = obj[key];
+ }
+ }
+};
+angularFilter.Meta.get = function(obj, attr){
+ attr = attr || 'text';
+ switch(typeof obj) {
+ case "string":
+ return attr == "text" ? obj : undefined;
+ case "object":
+ if (obj && typeof obj[attr] !== "undefined") {
+ return obj[attr];
+ }
+ return undefined;
+ default:
+ return obj;
+ }
+};
+
+var angularFilterGoogleChartApi;
+
+foreach({
+ 'currency': function(amount){
+ jQuery(this.$element).toggleClass('ng-format-negative', amount < 0);
+ return '$' + angularFilter['number'].apply(this, [amount, 2]);
+ },
+
+ 'number': function(amount, fractionSize){
+ if (isNaN(amount) || !isFinite(amount)) {
+ return '';
+ }
+ fractionSize = typeof fractionSize == 'undefined' ? 2 : fractionSize;
+ var isNegative = amount < 0;
+ amount = Math.abs(amount);
+ var pow = Math.pow(10, fractionSize);
+ var text = "" + Math.round(amount * pow);
+ var whole = text.substring(0, text.length - fractionSize);
+ whole = whole || '0';
+ var frc = text.substring(text.length - fractionSize);
+ text = isNegative ? '-' : '';
+ for (var i = 0; i < whole.length; i++) {
+ if ((whole.length - i)%3 === 0 && i !== 0) {
+ text += ',';
+ }
+ text += whole.charAt(i);
+ }
+ if (fractionSize > 0) {
+ for (var j = frc.length; j < fractionSize; j++) {
+ frc += '0';
+ }
+ text += '.' + frc.substring(0, fractionSize);
+ }
+ return text;
+ },
+
+ 'date': function(amount) {
+ },
+
+ 'json': function(object) {
+ jQuery(this.$element).addClass("ng-monospace");
+ return toJson(object, true);
+ },
+
+ 'trackPackage': (function(){
+ var MATCHERS = [
+ { name: "UPS",
+ url: "http://wwwapps.ups.com/WebTracking/processInputRequest?sort_by=status&tracknums_displayed=1&TypeOfInquiryNumber=T&loc=en_US&track.x=0&track.y=0&InquiryNumber1=",
+ regexp: [
+ /^1Z[0-9A-Z]{16}$/i]},
+ { name: "FedEx",
+ url: "http://www.fedex.com/Tracking?tracknumbers=",
+ regexp: [
+ /^96\d{10}?$/i,
+ /^96\d{17}?$/i,
+ /^96\d{20}?$/i,
+ /^\d{15}$/i,
+ /^\d{12}$/i]},
+ { name: "USPS",
+ url: "http://trkcnfrm1.smi.usps.com/PTSInternetWeb/InterLabelInquiry.do?origTrackNum=",
+ regexp: [
+ /^(91\d{20})$/i,
+ /^(91\d{18})$/i]}];
+ return function(trackingNo, noMatch) {
+ trackingNo = trim(trackingNo);
+ var tNo = trackingNo.replace(/ /g, '');
+ var returnValue;
+ foreach(MATCHERS, function(carrier){
+ foreach(carrier.regexp, function(regexp){
+ if (!returnValue && regexp.test(tNo)) {
+ var text = carrier.name + ": " + trackingNo;
+ var url = carrier.url + trackingNo;
+ returnValue = new angularFilter.Meta({
+ text:text,
+ url:url,
+ html: '' + text + '',
+ trackingNo:trackingNo});
+ }
+ });
+ });
+ if (returnValue)
+ return returnValue;
+ else if (trackingNo)
+ return noMatch || new angularFilter.Meta({text:trackingNo + " is not recognized"});
+ else
+ return null;
+ };})(),
+
+ 'link': function(obj, title) {
+ var text = title || angularFilter.Meta.get(obj);
+ var url = angularFilter.Meta.get(obj, "url") || angularFilter.Meta.get(obj);
+ if (url) {
+ if (angular.validator.email(url) === null) {
+ url = "mailto:" + url;
+ }
+ var html = '' + text + '';
+ return new angularFilter.Meta({text:text, url:url, html:html});
+ }
+ return obj;
+ },
+
+
+ 'bytes': (function(){
+ var SUFFIX = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ return function(size) {
+ if(size === null) return "";
+
+ var suffix = 0;
+ while (size > 1000) {
+ size = size / 1024;
+ suffix++;
+ }
+ var txt = "" + size;
+ var dot = txt.indexOf('.');
+ if (dot > -1 && dot + 2 < txt.length) {
+ txt = txt.substring(0, dot + 2);
+ }
+ return txt + " " + SUFFIX[suffix];
+ };
+ })(),
+
+ 'image': function(obj, width, height) {
+ if (obj && obj.url) {
+ var style = "";
+ if (width) {
+ style = ' style="max-width: ' + width +
+ 'px; max-height: ' + (height || width) + 'px;"';
+ }
+ return new angularFilter.Meta({url:obj.url, text:obj.url,
+ html:'
'});
+ }
+ return null;
+ },
+
+ 'lowercase': function (obj) {
+ var text = angularFilter.Meta.get(obj);
+ return text ? ("" + text).toLowerCase() : text;
+ },
+
+ 'uppercase': function (obj) {
+ var text = angularFilter.Meta.get(obj);
+ return text ? ("" + text).toUpperCase() : text;
+ },
+
+ 'linecount': function (obj) {
+ var text = angularFilter.Meta.get(obj);
+ if (text==='' || !text) return 1;
+ return text.split(/\n|\f/).length;
+ },
+
+ 'if': function (result, expression) {
+ return expression ? result : undefined;
+ },
+
+ 'unless': function (result, expression) {
+ return expression ? undefined : result;
+ },
+
+ 'googleChartApi': extend(
+ function(type, data, width, height) {
+ data = data || {};
+ var chart = {
+ 'cht':type,
+ 'chco':angularFilterGoogleChartApi['collect'](data, 'color'),
+ 'chtt':angularFilterGoogleChartApi['title'](data),
+ 'chdl':angularFilterGoogleChartApi['collect'](data, 'label'),
+ 'chd':angularFilterGoogleChartApi['values'](data),
+ 'chf':'bg,s,FFFFFF00'
+ };
+ if (_.isArray(data['xLabels'])) {
+ chart['chxt']='x';
+ chart['chxl']='0:|' + data.xLabels.join('|');
+ }
+ return angularFilterGoogleChartApi['encode'](chart, width, height);
+ },
+ {
+ 'values': function(data){
+ var seriesValues = [];
+ foreach(data['series']||[], function(serie){
+ var values = [];
+ foreach(serie['values']||[], function(value){
+ values.push(value);
+ });
+ seriesValues.push(values.join(','));
+ });
+ var values = seriesValues.join('|');
+ return values === "" ? null : "t:" + values;
+ },
+
+ 'title': function(data){
+ var titles = [];
+ var title = data['title'] || [];
+ foreach(_.isArray(title)?title:[title], function(text){
+ titles.push(encodeURIComponent(text));
+ });
+ return titles.join('|');
+ },
+
+ 'collect': function(data, key){
+ var outterValues = [];
+ var count = 0;
+ foreach(data['series']||[], function(serie){
+ var innerValues = [];
+ var value = serie[key] || [];
+ foreach(_.isArray(value)?value:[value], function(color){
+ innerValues.push(encodeURIComponent(color));
+ count++;
+ });
+ outterValues.push(innerValues.join('|'));
+ });
+ return count?outterValues.join(','):null;
+ },
+
+ 'encode': function(params, width, height) {
+ width = width || 200;
+ height = height || width;
+ var url = "http://chart.apis.google.com/chart?";
+ var urlParam = [];
+ params['chs'] = width + "x" + height;
+ foreach(params, function(value, key){
+ if (value) {
+ urlParam.push(key + "=" + value);
+ }
+ });
+ urlParam.sort();
+ url += urlParam.join("&");
+ return new angularFilter.Meta({url:url,
+ html:'
'});
+ }
+ }
+ ),
+
+
+ 'qrcode': function(value, width, height) {
+ return angularFilterGoogleChartApi['encode']({
+ 'cht':'qr', 'chl':encodeURIComponent(value)}, width, height);
+ },
+ 'chart': {
+ 'pie':function(data, width, height) {
+ return angularFilterGoogleChartApi('p', data, width, height);
+ },
+ 'pie3d':function(data, width, height) {
+ return angularFilterGoogleChartApi('p3', data, width, height);
+ },
+ 'pieConcentric':function(data, width, height) {
+ return angularFilterGoogleChartApi('pc', data, width, height);
+ },
+ 'barHorizontalStacked':function(data, width, height) {
+ return angularFilterGoogleChartApi('bhs', data, width, height);
+ },
+ 'barHorizontalGrouped':function(data, width, height) {
+ return angularFilterGoogleChartApi('bhg', data, width, height);
+ },
+ 'barVerticalStacked':function(data, width, height) {
+ return angularFilterGoogleChartApi('bvs', data, width, height);
+ },
+ 'barVerticalGrouped':function(data, width, height) {
+ return angularFilterGoogleChartApi('bvg', data, width, height);
+ },
+ 'line':function(data, width, height) {
+ return angularFilterGoogleChartApi('lc', data, width, height);
+ },
+ 'sparkline':function(data, width, height) {
+ return angularFilterGoogleChartApi('ls', data, width, height);
+ },
+ 'scatter':function(data, width, height) {
+ return angularFilterGoogleChartApi('s', data, width, height);
+ }
+ },
+
+ 'html': function(html){
+ return new angularFilter.Meta({html:html});
+ },
+
+ 'linky': function(text){
+ if (!text) return text;
+ function regExpEscape(text) {
+ return text.replace(/([\/\.\*\+\?\|\(\)\[\]\{\}\\])/g, '\\$1');
+ }
+ var URL = /(ftp|http|https|mailto):\/\/([^\(\)|\s]+)/;
+ var match;
+ var raw = text;
+ var html = [];
+ while (match=raw.match(URL)) {
+ var url = match[0].replace(/[\.\;\,\(\)\{\}\<\>]$/,'');
+ var i = raw.indexOf(url);
+ html.push(escapeHtml(raw.substr(0, i)));
+ html.push('');
+ html.push(url);
+ html.push('');
+ raw = raw.substring(i + url.length);
+ }
+ html.push(escapeHtml(raw));
+ return new angularFilter.Meta({text:text, html:html.join('')});
+ }
+}, function(v,k){angularFilter[k] = v;});
+
+angularFilterGoogleChartApi = angularFilter['googleChartApi'];
diff --git a/src/formatters.js b/src/formatters.js
new file mode 100644
index 00000000..ee63c1a5
--- /dev/null
+++ b/src/formatters.js
@@ -0,0 +1,23 @@
+function formater(format, parse) {return {'format':format, 'parse':parse || format};}
+function toString(obj) {return isDefined(obj) ? "" + obj : obj;}
+extend(angularFormatter, {
+ 'noop':formater(identity, identity),
+ 'boolean':formater(toString, toBoolean),
+ 'number':formater(toString, function(obj){return 1*obj;}),
+
+ 'list':formater(
+ function(obj) { return obj ? obj.join(", ") : obj; },
+ function(value) {
+ var list = [];
+ foreach((value || '').split(','), function(item){
+ item = trim(item);
+ if (item) list.push(item);
+ });
+ return list;
+ }
+ ),
+
+ 'trim':formater(
+ function(obj) { return obj ? trim("" + obj) : ""; }
+ )
+});
diff --git a/src/validators.js b/src/validators.js
new file mode 100644
index 00000000..e3da0a81
--- /dev/null
+++ b/src/validators.js
@@ -0,0 +1,113 @@
+foreach({
+ 'noop': noop,
+
+ 'regexp': function(value, regexp, msg) {
+ if (!value.match(regexp)) {
+ return msg ||
+ "Value does not match expected format " + regexp + ".";
+ } else {
+ return null;
+ }
+ },
+
+ 'number': function(value, min, max) {
+ var num = 1 * value;
+ if (num == value) {
+ if (typeof min != 'undefined' && num < min) {
+ return "Value can not be less than " + min + ".";
+ }
+ if (typeof min != 'undefined' && num > max) {
+ return "Value can not be greater than " + max + ".";
+ }
+ return null;
+ } else {
+ return "Not a number";
+ }
+ },
+
+ 'integer': function(value, min, max) {
+ var numberError = angularValidator['number'](value, min, max);
+ if (numberError) return numberError;
+ if (!("" + value).match(/^\s*[\d+]*\s*$/) || value != Math.round(value)) {
+ return "Not a whole number";
+ }
+ return null;
+ },
+
+ 'date': function(value, min, max) {
+ if (value.match(/^\d\d?\/\d\d?\/\d\d\d\d$/)) {
+ return null;
+ }
+ return "Value is not a date. (Expecting format: 12/31/2009).";
+ },
+
+ 'ssn': function(value) {
+ if (value.match(/^\d\d\d-\d\d-\d\d\d\d$/)) {
+ return null;
+ }
+ return "SSN needs to be in 999-99-9999 format.";
+ },
+
+ 'email': function(value) {
+ if (value.match(/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/)) {
+ return null;
+ }
+ return "Email needs to be in username@host.com format.";
+ },
+
+ 'phone': function(value) {
+ if (value.match(/^1\(\d\d\d\)\d\d\d-\d\d\d\d$/)) {
+ return null;
+ }
+ if (value.match(/^\+\d{2,3} (\(\d{1,5}\))?[\d ]+\d$/)) {
+ return null;
+ }
+ return "Phone number needs to be in 1(987)654-3210 format in North America or +999 (123) 45678 906 internationaly.";
+ },
+
+ 'url': function(value) {
+ if (value.match(/^(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?$/)) {
+ return null;
+ }
+ return "URL needs to be in http://server[:port]/path format.";
+ },
+
+ 'json': function(value) {
+ try {
+ fromJson(value);
+ return null;
+ } catch (e) {
+ return e.toString();
+ }
+ },
+
+ 'asynchronous': function(text, asynchronousFn) {
+ var element = this['$element'];
+ var cache = element.data('$validateState');
+ if (!cache) {
+ cache = { state: {}};
+ element.data('$validateState', cache);
+ }
+ var state = cache.state[text];
+ cache.lastKey = text;
+ if (state === undefined) {
+ // we have never seen this before, Request it
+ element.addClass('ng-input-indicator-wait');
+ state = cache.state[text] = null;
+ (asynchronousFn || noop)(text, function(error){
+ state = cache.state[text] = error ? error : false;
+ if (cache.state[cache.lastKey] !== null) {
+ element.removeClass('ng-input-indicator-wait');
+ }
+ elementError(element, NG_VALIDATION_ERROR, error);
+ });
+ }
+
+ if (state === null){
+ // request in flight, mark widget invalid, but don't show it to user
+ (this['$invalidWidgets']||[]).push(this.$element);
+ }
+ return state;
+ }
+
+}, function(v,k) {angularValidator[k] = v;});
diff --git a/src/widgets.js b/src/widgets.js
new file mode 100644
index 00000000..3e9ba236
--- /dev/null
+++ b/src/widgets.js
@@ -0,0 +1,229 @@
+function modelAccessor(scope, element) {
+ var expr = element.attr('name'),
+ farmatterName = element.attr('ng-format') || NOOP,
+ formatter = angularFormatter(farmatterName);
+ if (!expr) throw "Required field 'name' not found.";
+ if (!formatter) throw "Formatter named '" + farmatterName + "' not found.";
+ return {
+ get: function() {
+ return formatter['format'](scope.$eval(expr));
+ },
+ set: function(value) {
+ scope.$tryEval(expr + '=' + toJson(formatter['parse'](value)), element);
+ }
+ };
+}
+
+function compileValidator(expr) {
+ return new Parser(expr).validator()();
+}
+
+function valueAccessor(scope, element) {
+ var validatorName = element.attr('ng-validate') || NOOP,
+ validator = compileValidator(validatorName),
+ required = element.attr('ng-required'),
+ lastError;
+ required = required || required === '';
+ if (!validator) throw "Validator named '" + validatorName + "' not found.";
+ function validate(value) {
+ var error = required && !trim(value) ? "Required" : validator({self:scope, scope:{get:scope.$get, set:scope.$set}}, value);
+ if (error !== lastError) {
+ elementError(element, NG_VALIDATION_ERROR, error);
+ lastError = error;
+ }
+ return value;
+ }
+ return {
+ get: function(){ return validate(element.val()); },
+ set: function(value){ element.val(validate(value)); }
+ };
+}
+
+function checkedAccessor(scope, element) {
+ var domElement = element[0];
+ return {
+ get: function(){
+ return !!domElement.checked;
+ },
+ set: function(value){
+ domElement.checked = !!value;
+ }
+ };
+}
+
+function radioAccessor(scope, element) {
+ var domElement = element[0];
+ return {
+ get: function(){
+ return domElement.checked ? domElement.value : null;
+ },
+ set: function(value){
+ domElement.checked = value == domElement.value;
+ }
+ };
+}
+
+function optionsAccessor(scope, element) {
+ var options = element[0].options;
+ return {
+ get: function(){
+ var values = [];
+ foreach(options, function(option){
+ if (option.selected) values.push(option.value);
+ });
+ return values;
+ },
+ set: function(values){
+ var keys = {};
+ foreach(values, function(value){ keys[value] = true; });
+ foreach(options, function(option){
+ option.selected = keys[option.value];
+ });
+ }
+ };
+}
+
+function noopAccessor() { return { get: noop, set: noop }; }
+
+var textWidget = inputWidget('keyup change', modelAccessor, valueAccessor, initWidgetValue('')),
+ buttonWidget = inputWidget('click', noopAccessor, noopAccessor, noop),
+ INPUT_TYPE = {
+ 'text': textWidget,
+ 'textarea': textWidget,
+ 'hidden': textWidget,
+ 'password': textWidget,
+ 'button': buttonWidget,
+ 'submit': buttonWidget,
+ 'reset': buttonWidget,
+ 'image': buttonWidget,
+ '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 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, element) {
+ var modelValue = model.get(), viewValue = view.get(), input = element[0];
+ input.name = this.$id + '@' + input.name;
+ 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') || '';
+ initFn.call(scope, model, view, element);
+ this.$eval(element.attr('ng-init')||'');
+ // Don't register a handler if we are a button (noopAccessor) and there is no action
+ if (action || modelAccessor !== noopAccessor) {
+ element.bind(events, function(){
+ model.set(view.get());
+ scope.$tryEval(action, element);
+ scope.$root.$eval();
+ // 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());
+ scope.$watch(model.get, view.set);
+ };
+}
+
+function inputWidgetSelector(element){
+ this.directives(true);
+ return INPUT_TYPE[lowercase(element[0].type)] || noop;
+}
+
+angularWidget('INPUT', inputWidgetSelector);
+angularWidget('TEXTAREA', inputWidgetSelector);
+angularWidget('BUTTON', inputWidgetSelector);
+angularWidget('SELECT', function(element){
+ this.descend(true);
+ return inputWidgetSelector.call(this, element);
+});
+
+
+angularWidget('NG:INCLUDE', function(element){
+ var compiler = this,
+ src = element.attr("src");
+ if (element.attr('switch-instance')) {
+ this.descend(true);
+ this.directives(true);
+ } else {
+ return function(element){
+ var scope = this, childScope;
+ element.attr('switch-instance', 'compiled');
+ scope.$browser.xhr('GET', src, function(code, response){
+ element.html(response);
+ childScope = createScope(scope);
+ compiler.compile(element)(element, childScope);
+ childScope.$init();
+ scope.$root.$eval();
+ });
+ scope.$onEval(function(){
+ if (childScope) childScope.$eval();
+ });
+ };
+ }
+});
+
+angularWidget('NG:SWITCH', function ngSwitch(element){
+ var compiler = this,
+ watchExpr = element.attr("on"),
+ whenFn = ngSwitch[element.attr("using") || 'equals'];
+ changeExpr = element.attr('change') || '',
+ cases = [];
+ if (!whenFn) throw "Using expression '" + usingExpr + "' unknown.";
+ eachNode(element, function(caseElement){
+ var when = caseElement.attr('ng-switch-when');
+ if (when) {
+ cases.push({
+ when: function(scope, value){
+ return whenFn.call(scope, value, when);
+ },
+ change: changeExpr,
+ element: caseElement,
+ template: compiler.compile(caseElement)
+ });
+ }
+ });
+ element.html('');
+ return function(element){
+ var scope = this, childScope;
+ this.$watch(watchExpr, function(value){
+ element.html('');
+ childScope = null;
+ foreach(cases, function(switchCase){
+ if (switchCase.when(childScope, value)) {
+ element.append(switchCase.element);
+ childScope = createScope(scope);
+ childScope.$tryEval(switchCase.change, element);
+ switchCase.template(switchCase.element, childScope);
+ childScope.$init();
+ }
+ });
+ });
+ scope.$onEval(function(){
+ if (childScope) childScope.$eval();
+ });
+ };
+}, {
+ equals: function(on, when) {
+ return on == when;
+ },
+ route: function(on, when) {
+ }
+});
--
cgit v1.2.3