(function () { "use strict"; /** * Bindonce - Zero watches binding for AngularJs * @version v0.3.1 * @link https://github.com/Pasvaz/bindonce * @author Pasquale Vazzana * @license MIT License, http://www.opensource.org/licenses/MIT */ var bindonceModule = angular.module('pasvaz.bindonce', []); bindonceModule.directive('bindonce', function () { var toBoolean = function (value) { if (value && value.length !== 0) { var v = angular.lowercase("" + value); value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]'); } else { value = false; } return value; }; var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10); if (isNaN(msie)) { msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10); } var bindonceDirective = { restrict: "AM", controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate) { var showHideBinder = function (elm, attr, value) { var show = (attr === 'show') ? '' : 'none'; var hide = (attr === 'hide') ? '' : 'none'; elm.css('display', toBoolean(value) ? show : hide); }; var classBinder = function (elm, value) { if (angular.isObject(value) && !angular.isArray(value)) { var results = []; angular.forEach(value, function (value, index) { if (value) results.push(index); }); value = results; } if (value) { elm.addClass(angular.isArray(value) ? value.join(' ') : value); } }; var transclude = function (transcluder, scope) { transcluder.transclude(scope, function (clone) { var parent = transcluder.element.parent(); var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1]; var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; var afterNextSibling = (afterNode && afterNode.nextSibling) || null; angular.forEach(clone, function (node) { parentNode.insertBefore(node, afterNextSibling); }); }); }; var ctrl = { watcherRemover: undefined, binders: [], group: $attrs.boName, element: $element, ran: false, addBinder: function (binder) { this.binders.push(binder); // In case of late binding (when using the directive bo-name/bo-parent) // it happens only when you use nested bindonce, if the bo-children // are not dom children the linking can follow another order if (this.ran) { this.runBinders(); } }, setupWatcher: function (bindonceValue) { var that = this; this.watcherRemover = $scope.$watch(bindonceValue, function (newValue) { if (newValue === undefined) return; that.removeWatcher(); that.checkBindonce(newValue); }, true); }, checkBindonce: function (value) { var that = this, promise = (value.$promise) ? value.$promise.then : value.then; // since Angular 1.2 promises are no longer // undefined until they don't get resolved if (typeof promise === 'function') { promise(function () { that.runBinders(); }); } else { that.runBinders(); } }, removeWatcher: function () { if (this.watcherRemover !== undefined) { this.watcherRemover(); this.watcherRemover = undefined; } }, runBinders: function () { while (this.binders.length > 0) { var binder = this.binders.shift(); if (this.group && this.group != binder.group) continue; var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); switch (binder.attr) { case 'boIf': if (toBoolean(value)) { transclude(binder, binder.scope.$new()); } break; case 'boSwitch': var selectedTranscludes, switchCtrl = binder.controller[0]; if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?'])) { binder.scope.$eval(binder.attrs.change); angular.forEach(selectedTranscludes, function (selectedTransclude) { transclude(selectedTransclude, binder.scope.$new()); }); } break; case 'boSwitchWhen': var ctrl = binder.controller[0]; ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []); ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element }); break; case 'boSwitchDefault': var ctrl = binder.controller[0]; ctrl.cases['?'] = (ctrl.cases['?'] || []); ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element }); break; case 'hide': case 'show': showHideBinder(binder.element, binder.attr, value); break; case 'class': classBinder(binder.element, value); break; case 'text': binder.element.text(value); break; case 'html': binder.element.html(value); break; case 'style': binder.element.css(value); break; case 'src': binder.element.attr(binder.attr, value); if (msie) binder.element.prop('src', value); break; case 'attr': angular.forEach(binder.attrs, function (attrValue, attrKey) { var newAttr, newValue; if (attrKey.match(/^boAttr./) && binder.attrs[attrKey]) { newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); newValue = binder.scope.$eval(binder.attrs[attrKey]); binder.element.attr(newAttr, newValue); } }); break; case 'href': case 'alt': case 'title': case 'id': case 'value': binder.element.attr(binder.attr, value); break; } } this.ran = true; } }; return ctrl; }], link: function (scope, elm, attrs, bindonceController) { var value = attrs.bindonce && scope.$eval(attrs.bindonce); if (value !== undefined) { bindonceController.checkBindonce(value); } else { bindonceController.setupWatcher(attrs.bindonce); elm.bind("$destroy", bindonceController.removeWatcher); } } }; return bindonceDirective; }); angular.forEach( [ { directiveName: 'boShow', attribute: 'show' }, { directiveName: 'boHide', attribute: 'hide' }, { directiveName: 'boClass', attribute: 'class' }, { directiveName: 'boText', attribute: 'text' }, { directiveName: 'boBind', attribute: 'text' }, { directiveName: 'boHtml', attribute: 'html' }, { directiveName: 'boSrcI', attribute: 'src', interpolate: true }, { directiveName: 'boSrc', attribute: 'src' }, { directiveName: 'boHrefI', attribute: 'href', interpolate: true }, { directiveName: 'boHref', attribute: 'href' }, { directiveName: 'boAlt', attribute: 'alt' }, { directiveName: 'boTitle', attribute: 'title' }, { directiveName: 'boId', attribute: 'id' }, { directiveName: 'boStyle', attribute: 'style' }, { directiveName: 'boValue', attribute: 'value' }, { directiveName: 'boAttr', attribute: 'attr' }, { directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 }, { directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } }, { directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' }, { directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' } ], function (boDirective) { var childPriority = 200; return bindonceModule.directive(boDirective.directiveName, function () { var bindonceDirective = { priority: boDirective.priority || childPriority, transclude: boDirective.transclude || false, terminal: boDirective.terminal || false, require: ['^bindonce'].concat(boDirective.require || []), controller: boDirective.controller, compile: function (tElement, tAttrs, transclude) { return function (scope, elm, attrs, controllers) { var bindonceController = controllers[0]; var name = attrs.boParent; if (name && bindonceController.group !== name) { var element = bindonceController.element.parent(); bindonceController = undefined; var parentValue; while (element[0].nodeType !== 9 && element.length) { if ((parentValue = element.data('$bindonceController')) && parentValue.group === name) { bindonceController = parentValue; break; } element = element.parent(); } if (!bindonceController) { throw new Error("No bindonce controller: " + name); } } bindonceController.addBinder( { element: elm, attr: boDirective.attribute || boDirective.directiveName, attrs: attrs, value: attrs[boDirective.directiveName], interpolate: boDirective.interpolate, group: name, transclude: transclude, controller: controllers.slice(1), scope: scope }); }; } }; return bindonceDirective; }); }) })();