aboutsummaryrefslogtreecommitdiffstats
path: root/src/Widgets.js
diff options
context:
space:
mode:
Diffstat (limited to 'src/Widgets.js')
-rw-r--r--src/Widgets.js774
1 files changed, 774 insertions, 0 deletions
diff --git a/src/Widgets.js b/src/Widgets.js
new file mode 100644
index 00000000..de74533a
--- /dev/null
+++ b/src/Widgets.js
@@ -0,0 +1,774 @@
+// Copyright (C) 2009 BRAT Tech LLC
+
+
+nglr.WidgetFactory = function(serverUrl, database) {
+ this.nextUploadId = 0;
+ this.serverUrl = serverUrl;
+ this.database = database;
+ this.createSWF = swfobject.createSWF;
+ this.onChangeListener = function(){};
+};
+
+nglr.WidgetFactory.prototype.createController = function(input, scope) {
+ var controller;
+ var type = input.attr('type').toLowerCase();
+ var exp = input.attr('name');
+ if (exp) exp = exp.split(':').pop();
+ var event = "change";
+ var bubbleEvent = true;
+ if (type == 'button' || type == 'submit' || type == 'reset' || type == 'image') {
+ controller = new nglr.ButtonController(input[0], exp);
+ event = "click";
+ bubbleEvent = false;
+ } else if (type == 'text' || type == 'textarea' || type == 'hidden' || type == 'password') {
+ controller = new nglr.TextController(input[0], exp);
+ event = "keyup change";
+ } else if (type == 'checkbox') {
+ controller = new nglr.CheckboxController(input[0], exp);
+ event = "click";
+ } else if (type == 'radio') {
+ controller = new nglr.RadioController(input[0], exp);
+ event="click";
+ } else if (type == 'select-one') {
+ controller = new nglr.SelectController(input[0], exp);
+ } else if (type == 'select-multiple') {
+ controller = new nglr.MultiSelectController(input[0], exp);
+ } else if (type == 'file') {
+ controller = this.createFileController(input, exp);
+ } else {
+ throw 'Unknown type: ' + type;
+ }
+ input.data('controller', controller);
+ var binder = scope.get('$binder');
+ var action = function() {
+ if (controller.updateModel(scope)) {
+ var action = jQuery(controller.view).attr('ng-action') || "";
+ if (scope.evalWidget(controller, action)) {
+ binder.updateView(scope);
+ }
+ }
+ return bubbleEvent;
+ };
+ jQuery(controller.view, ":input").
+ bind(event, action);
+ return controller;
+};
+
+nglr.WidgetFactory.prototype.createFileController = function(fileInput) {
+ var uploadId = '__uploadWidget_' + (this.nextUploadId++);
+ var view = nglr.FileController.template(uploadId);
+ fileInput.after(view);
+ var att = {
+ data:this.serverUrl + "/admin/ServerAPI.swf",
+ width:"95", height:"20", align:"top",
+ wmode:"transparent"};
+ var par = {
+ flashvars:"uploadWidgetId=" + uploadId,
+ allowScriptAccess:"always"};
+ var swfNode = this.createSWF(att, par, uploadId);
+ fileInput.remove();
+ var cntl = new nglr.FileController(view, fileInput[0].name, swfNode, this.serverUrl + "/data/" + this.database);
+ jQuery(swfNode).data('controller', cntl);
+ return cntl;
+};
+
+nglr.WidgetFactory.prototype.createTextWidget = function(textInput) {
+ var controller = new nglr.TextController(textInput);
+ controller.onChange(this.onChangeListener);
+ return controller;
+};
+
+/////////////////////
+// FileController
+///////////////////////
+
+nglr.FileController = function(view, scopeName, uploader, databaseUrl) {
+ this.view = view;
+ this.uploader = uploader;
+ this.scopeName = scopeName;
+ this.attachmentsPath = databaseUrl + '/_attachments';
+ this.value = null;
+ this.lastValue = undefined;
+};
+
+nglr.FileController.dispatchEvent = function(id, event, args) {
+ var object = document.getElementById(id);
+ var controller = jQuery(object).data("controller");
+ nglr.FileController.prototype['_on_' + event].apply(controller, args);
+};
+
+nglr.FileController.template = function(id) {
+ return jQuery('<span class="ng-upload-widget">' +
+ '<input type="checkbox" ng-non-bindable="true"/>' +
+ '<object id="' + id + '" />' +
+ '<a></a>' +
+ '<span/>' +
+ '</span>');
+};
+
+nglr.FileController.prototype._on_cancel = function() {
+};
+
+nglr.FileController.prototype._on_complete = function() {
+};
+
+nglr.FileController.prototype._on_httpStatus = function(status) {
+ nglr.alert("httpStatus:" + this.scopeName + " status:" + status);
+};
+
+nglr.FileController.prototype._on_ioError = function() {
+ nglr.alert("ioError:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_open = function() {
+ nglr.alert("open:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_progress = function(bytesLoaded, bytesTotal) {
+};
+
+nglr.FileController.prototype._on_securityError = function() {
+ nglr.alert("securityError:" + this.scopeName);
+};
+
+nglr.FileController.prototype._on_uploadCompleteData = function(data) {
+ var value = nglr.fromJson(data);
+ value.url = this.attachmentsPath + '/' + value.id + '/' + value.text;
+ this.view.find("input").attr('checked', true);
+ var scope = this.view.scope();
+ this.value = value;
+ this.updateModel(scope);
+ this.value = null;
+ scope.get('$binder').updateView();
+};
+
+nglr.FileController.prototype._on_select = function(name, size, type) {
+ this.name = name;
+ this.view.find("a").text(name).attr('href', name);
+ this.view.find("span").text(angular.filter.bytes(size));
+ this.upload();
+};
+
+nglr.FileController.prototype.updateModel = function(scope) {
+ var isChecked = this.view.find("input").attr('checked');
+ var value = isChecked ? this.value : null;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.set(this.scopeName, value);
+ return true;
+ }
+};
+
+nglr.FileController.prototype.updateView = function(scope) {
+ var modelValue = scope.get(this.scopeName);
+ if (modelValue && this.value !== modelValue) {
+ this.value = modelValue;
+ this.view.find("a").
+ attr("href", this.value.url).
+ text(this.value.text);
+ this.view.find("span").text(angular.filter.bytes(this.value.size));
+ }
+ this.view.find("input").attr('checked', !!modelValue);
+};
+
+nglr.FileController.prototype.upload = function() {
+ if (this.name) {
+ this.uploader.uploadFile(this.attachmentsPath);
+ }
+};
+
+
+///////////////////////
+// NullController
+///////////////////////
+nglr.NullController = function(view) {this.view = view;};
+nglr.NullController.prototype.updateModel = function() { return true; };
+nglr.NullController.prototype.updateView = function() { };
+nglr.NullController.instance = new nglr.NullController();
+
+
+///////////////////////
+// ButtonController
+///////////////////////
+nglr.ButtonController = function(view) {this.view = view;};
+nglr.ButtonController.prototype.updateModel = function(scope) { return true; };
+nglr.ButtonController.prototype.updateView = function(scope) {};
+
+///////////////////////
+// TextController
+///////////////////////
+nglr.TextController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.validator = view.getAttribute('ng-validate');
+ this.required = typeof view.attributes['ng-required'] != "undefined";
+ this.lastErrorText = null;
+ this.lastValue = undefined;
+ this.initialValue = view.value;
+ var widget = view.getAttribute('ng-widget');
+ if (widget === 'datepicker') {
+ jQuery(view).datepicker();
+ }
+};
+
+nglr.TextController.prototype.updateModel = function(scope) {
+ var value = this.view.value;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.TextController.prototype.updateView = function(scope) {
+ var view = this.view;
+ var value = scope.get(this.exp);
+ if (typeof value === "undefined") {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ value = value ? value : '';
+ if (this.lastValue != value) {
+ view.value = value;
+ this.lastValue = value;
+ }
+ var isValidationError = false;
+ view.removeAttribute('ng-error');
+ if (this.required) {
+ isValidationError = !(value && value.length > 0);
+ }
+ var errorText = isValidationError ? "Required Value" : null;
+ if (!isValidationError && this.validator && value) {
+ errorText = scope.validate(this.validator, value);
+ isValidationError = !!errorText;
+ }
+ if (this.lastErrorText !== errorText) {
+ this.lastErrorText = isValidationError;
+ if (errorText !== null) {
+ view.setAttribute('ng-error', errorText);
+ scope.markInvalid(this);
+ }
+ jQuery(view).toggleClass('ng-validation-error', isValidationError);
+ }
+};
+
+///////////////////////
+// CheckboxController
+///////////////////////
+nglr.CheckboxController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.checked ? view.value : "";
+};
+
+nglr.CheckboxController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ var value = input.checked ? input.value : '';
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.CheckboxController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var value = scope.eval(this.exp);
+ if (typeof value === "undefined") {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ input.checked = input.value == (''+value);
+};
+
+///////////////////////
+// SelectController
+///////////////////////
+nglr.SelectController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = view.value;
+};
+
+nglr.SelectController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ if (input.selectedIndex < 0) {
+ scope.setEval(this.exp, null);
+ } else {
+ var value = this.view.value;
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+ }
+};
+
+nglr.SelectController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var value = scope.get(this.exp);
+ if (typeof value === 'undefined') {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ if (value !== this.lastValue) {
+ input.value = value ? value : "";
+ this.lastValue = value;
+ }
+};
+
+///////////////////////
+// MultiSelectController
+///////////////////////
+nglr.MultiSelectController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastValue = undefined;
+ this.initialValue = this.selected();
+};
+
+nglr.MultiSelectController.prototype.selected = function () {
+ var value = [];
+ var options = this.view.options;
+ for ( var i = 0; i < options.length; i++) {
+ var option = options[i];
+ if (option.selected) {
+ value.push(option.value);
+ }
+ }
+ return value;
+};
+
+nglr.MultiSelectController.prototype.updateModel = function(scope) {
+ var value = this.selected();
+ // TODO: This is wrong! no caching going on here as we are always comparing arrays
+ if (this.lastValue === value) {
+ return false;
+ } else {
+ scope.setEval(this.exp, value);
+ this.lastValue = value;
+ return true;
+ }
+};
+
+nglr.MultiSelectController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var selected = scope.get(this.exp);
+ if (typeof selected === "undefined") {
+ selected = this.initialValue;
+ scope.setEval(this.exp, selected);
+ }
+ if (selected !== this.lastValue) {
+ var options = input.options;
+ for ( var i = 0; i < options.length; i++) {
+ var option = options[i];
+ option.selected = _.include(selected, option.value);
+ }
+ this.lastValue = selected;
+ }
+};
+
+///////////////////////
+// RadioController
+///////////////////////
+nglr.RadioController = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.lastChecked = undefined;
+ this.lastValue = undefined;
+ this.inputValue = view.value;
+ this.initialValue = view.checked ? view.value : null;
+};
+
+nglr.RadioController.prototype.updateModel = function(scope) {
+ var input = this.view;
+ if (this.lastChecked) {
+ return false;
+ } else {
+ input.checked = true;
+ this.lastValue = scope.setEval(this.exp, this.inputValue);
+ this.lastChecked = true;
+ return true;
+ }
+};
+
+nglr.RadioController.prototype.updateView = function(scope) {
+ var input = this.view;
+ var value = scope.get(this.exp);
+ if (this.initialValue && typeof value === "undefined") {
+ value = this.initialValue;
+ scope.setEval(this.exp, value);
+ }
+ if (this.lastValue != value) {
+ this.lastChecked = input.checked = this.inputValue == (''+value);
+ this.lastValue = value;
+ }
+};
+
+///////////////////////
+//ElementController
+///////////////////////
+nglr.BindUpdater = function(view, exp) {
+ this.view = view;
+ this.exp = nglr.Binder.parseBindings(exp);
+ this.hasError = false;
+ this.scopeSelf = {element:view};
+};
+
+nglr.BindUpdater.toText = function(obj) {
+ var e = nglr.escapeHtml;
+ switch(typeof obj) {
+ case "string":
+ case "boolean":
+ case "number":
+ return e(obj);
+ case "function":
+ return nglr.BindUpdater.toText(obj());
+ case "object":
+ if (nglr.isNode(obj)) {
+ return nglr.outerHTML(obj);
+ } else if (obj instanceof angular.filter.Meta) {
+ switch(typeof obj.html) {
+ case "string":
+ case "number":
+ return obj.html;
+ case "function":
+ return obj.html();
+ case "object":
+ if (nglr.isNode(obj.html))
+ return nglr.outerHTML(obj.html);
+ default:
+ break;
+ }
+ switch(typeof obj.text) {
+ case "string":
+ case "number":
+ return e(obj.text);
+ case "function":
+ return e(obj.text());
+ default:
+ break;
+ }
+ }
+ if (obj === null)
+ return "";
+ return e(nglr.toJson(obj, true));
+ default:
+ return "";
+ }
+};
+
+nglr.BindUpdater.prototype.updateModel = function(scope) {};
+nglr.BindUpdater.prototype.updateView = function(scope) {
+ var html = [];
+ var parts = this.exp;
+ var length = parts.length;
+ for(var i=0; i<length; i++) {
+ var part = parts[i];
+ var binding = nglr.Binder.binding(part);
+ if (binding) {
+ scope.evalWidget(this, binding, this.scopeSelf, function(value){
+ html.push(nglr.BindUpdater.toText(value));
+ }, function(e, text){
+ nglr.setHtml(this.view, text);
+ });
+ if (this.hasError) {
+ return;
+ }
+ } else {
+ html.push(nglr.escapeHtml(part));
+ }
+ }
+ nglr.setHtml(this.view, html.join(''));
+};
+
+nglr.BindAttrUpdater = function(view, attrs) {
+ this.view = view;
+ this.attrs = attrs;
+};
+
+nglr.BindAttrUpdater.prototype.updateModel = function(scope) {};
+nglr.BindAttrUpdater.prototype.updateView = function(scope) {
+ var jNode = jQuery(this.view);
+ var attributeTemplates = this.attrs;
+ if (this.hasError) {
+ this.hasError = false;
+ jNode.
+ removeClass('ng-exception').
+ removeAttr('ng-error');
+ }
+ var isImage = jNode.is('img');
+ for (var attrName in attributeTemplates) {
+ var attributeTemplate = nglr.Binder.parseBindings(attributeTemplates[attrName]);
+ var attrValues = [];
+ for ( var i = 0; i < attributeTemplate.length; i++) {
+ var binding = nglr.Binder.binding(attributeTemplate[i]);
+ if (binding) {
+ try {
+ var value = scope.eval(binding, {element:jNode[0], attrName:attrName});
+ if (value && (value.constructor !== nglr.array || value.length !== 0))
+ attrValues.push(value);
+ } catch (e) {
+ this.hasError = true;
+ console.error('BindAttrUpdater', e);
+ var jsonError = nglr.toJson(e, true);
+ attrValues.push('[' + jsonError + ']');
+ jNode.
+ addClass('ng-exception').
+ attr('ng-error', jsonError);
+ }
+ } else {
+ attrValues.push(attributeTemplate[i]);
+ }
+ }
+ var attrValue = attrValues.length ? attrValues.join('') : null;
+ if(isImage && attrName == 'src' && !attrValue)
+ attrValue = scope.get('config.server') + '/images/blank.gif';
+ jNode.attr(attrName, attrValue);
+ }
+};
+
+nglr.EvalUpdater = function(view, exp) {
+ this.view = view;
+ this.exp = exp;
+ this.hasError = false;
+};
+nglr.EvalUpdater.prototype.updateModel = function(scope) {};
+nglr.EvalUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp);
+};
+
+nglr.HideUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.HideUpdater.prototype.updateModel = function(scope) {};
+nglr.HideUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (nglr.toBoolean(hideValue)) {
+ view.hide();
+ } else {
+ view.show();
+ }
+ });
+};
+
+nglr.ShowUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ShowUpdater.prototype.updateModel = function(scope) {};
+nglr.ShowUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(hideValue){
+ var view = jQuery(this.view);
+ if (nglr.toBoolean(hideValue)) {
+ view.show();
+ } else {
+ view.hide();
+ }
+ });
+};
+
+nglr.ClassUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ if (classValue !== null && classValue !== undefined) {
+ this.view.className = classValue;
+ }
+ });
+};
+
+nglr.ClassEvenUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassEvenUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassEvenUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 1);
+ });
+};
+
+nglr.ClassOddUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.ClassOddUpdater.prototype.updateModel = function(scope) {};
+nglr.ClassOddUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(classValue){
+ var index = scope.get('$index');
+ jQuery(this.view).toggleClass(classValue, index % 2 === 0);
+ });
+};
+
+nglr.StyleUpdater = function(view, exp) { this.view = view; this.exp = exp; };
+nglr.StyleUpdater.prototype.updateModel = function(scope) {};
+nglr.StyleUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.exp, {}, function(styleValue){
+ jQuery(this.view).attr('style', "").css(styleValue);
+ });
+};
+
+///////////////////////
+// RepeaterUpdater
+///////////////////////
+nglr.RepeaterUpdater = function(view, repeaterExpression, template, prefix) {
+ this.view = view;
+ this.template = template;
+ this.prefix = prefix;
+ this.children = [];
+ var match = repeaterExpression.match(/^\s*(.+)\s+in\s+(.*)\s*$/);
+ if (! match) {
+ throw "Expected ng-repeat in form of 'item in collection' but got '" +
+ repeaterExpression + "'.";
+ }
+ var keyValue = match[1];
+ this.iteratorExp = match[2];
+ match = keyValue.match(/^([\$\w]+)|\(([\$\w]+)\s*,\s*([\$\w]+)\)$/);
+ if (!match) {
+ throw "'item' in 'item in collection' should be identifier or (key, value) but get '" +
+ keyValue + "'.";
+ }
+ this.valueExp = match[3] || match[1];
+ this.keyExp = match[2];
+};
+
+nglr.RepeaterUpdater.prototype.updateModel = function(scope) {};
+nglr.RepeaterUpdater.prototype.updateView = function(scope) {
+ scope.evalWidget(this, this.iteratorExp, {}, function(iterator){
+ var self = this;
+ if (!iterator) {
+ iterator = [];
+ if (scope.isProperty(this.iteratorExp)) {
+ scope.set(this.iteratorExp, iterator);
+ }
+ }
+ var iteratorLength = iterator.length;
+ var childrenLength = this.children.length;
+ var cursor = this.view;
+ var time = 0;
+ var child = null;
+ var keyExp = this.keyExp;
+ var valueExp = this.valueExp;
+ var i = 0;
+ jQuery.each(iterator, function(key, value){
+ if (i < childrenLength) {
+ // reuse children
+ child = self.children[i];
+ child.scope.set(valueExp, value);
+ } else {
+ // grow children
+ var name = self.prefix +
+ valueExp + " in " + self.iteratorExp + "[" + i + "]";
+ var childScope = new nglr.Scope(scope.state, name);
+ childScope.set('$index', i);
+ if (keyExp)
+ childScope.set(keyExp, key);
+ childScope.set(valueExp, value);
+ child = { scope:childScope, element:self.template(childScope, self.prefix, i) };
+ cursor.after(child.element);
+ self.children.push(child);
+ }
+ cursor = child.element;
+ var s = new Date().getTime();
+ child.scope.updateView();
+ time += new Date().getTime() - s;
+ i++;
+ });
+ // shrink children
+ for ( var r = childrenLength; r > iteratorLength; --r) {
+ var unneeded = this.children.pop();
+ unneeded.element.removeNode();
+ }
+ // Special case for option in select
+ if (child && child.element[0].nodeName === "OPTION") {
+ var select = jQuery(child.element[0].parentNode);
+ var cntl = select.data('controller');
+ if (cntl) {
+ cntl.lastValue = undefined;
+ cntl.updateView(scope);
+ }
+ }
+ });
+};
+
+//////////////////////////////////
+// PopUp
+//////////////////////////////////
+
+nglr.PopUp = function(doc) {
+ this.doc = doc;
+};
+
+nglr.PopUp.OUT_EVENT = "mouseleave mouseout click dblclick keypress keyup";
+
+nglr.PopUp.prototype.bind = function () {
+ var self = this;
+ this.doc.find('.ng-validation-error,.ng-exception').
+ live("mouseover", nglr.PopUp.onOver);
+};
+
+nglr.PopUp.onOver = function(e) {
+ nglr.PopUp.onOut();
+ var jNode = jQuery(this);
+ jNode.bind(nglr.PopUp.OUT_EVENT, nglr.PopUp.onOut);
+ var position = jNode.position();
+ var de = document.documentElement;
+ var w = self.innerWidth || (de&&de.clientWidth) || document.body.clientWidth;
+ var hasArea = w - position.left;
+ var width = 300;
+ var title = jNode.hasClass("ng-exception") ? "EXCEPTION:" : "Validation error...";
+ var msg = jNode.attr("ng-error");
+
+ var x;
+ var arrowPos = hasArea>(width+75) ? "left" : "right";
+ var tip = jQuery(
+ "<div id='ng-callout' style='width:"+width+"px'>" +
+ "<div class='ng-arrow-"+arrowPos+"'/>" +
+ "<div class='ng-title'>"+title+"</div>" +
+ "<div class='ng-content'>"+msg+"</div>" +
+ "</div>");
+ jQuery("body").append(tip);
+ if(arrowPos === 'left'){
+ x = position.left + this.offsetWidth + 11;
+ }else{
+ x = position.left - (width + 15);
+ tip.find('.ng-arrow-right').css({left:width+1});
+ }
+
+ tip.css({left: x+"px", top: (position.top - 3)+"px"});
+ return true;
+};
+
+nglr.PopUp.onOut = function() {
+ jQuery('#ng-callout').
+ unbind(nglr.PopUp.OUT_EVENT, nglr.PopUp.onOut).
+ remove();
+ return true;
+};
+
+//////////////////////////////////
+// Status
+//////////////////////////////////
+
+
+nglr.Status = function(body) {
+ this.loader = body.append(nglr.Status.DOM).find("#ng-loading");
+ this.requestCount = 0;
+};
+
+nglr.Status.DOM ='<div id="ng-spacer"></div><div id="ng-loading">loading....</div>';
+
+nglr.Status.prototype.beginRequest = function () {
+ if (this.requestCount === 0) {
+ this.loader.show();
+ }
+ this.requestCount++;
+};
+
+nglr.Status.prototype.endRequest = function () {
+ this.requestCount--;
+ if (this.requestCount === 0) {
+ this.loader.hide("fold");
+ }
+};