From 26a733eeab1a35bc69251a824dbdc04b24043174 Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Thu, 11 Dec 2014 20:20:32 +0000 Subject: [PATCH 1/7] Beautify + Linter Errors Just cleaning up a few linter warnings/errors and formatting before larger changes. --- bindonce.js | 1073 ++++++++++++++++++++++++++------------------------- 1 file changed, 545 insertions(+), 528 deletions(-) diff --git a/bindonce.js b/bindonce.js index 78d6f62..a34633a 100644 --- a/bindonce.js +++ b/bindonce.js @@ -1,528 +1,545 @@ -(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 indexOf = function (array, obj) - { - if (array.indexOf) return array.indexOf(obj); - - for (var i = 0; i < array.length; i++) - { - if (obj === array[i]) return i; - } - return -1; - } - - 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 (binder, newScope, saveNodes) - { - if (newScope) binder.newScope = binder.scope.$new(); - binder.transclude((binder.newScope || binder.scope), function (clone) - { - if (saveNodes) binder.nodes = binder.nodes || []; - var parent = binder.element.parent(); - var afterNode = binder.element && binder.element[binder.element.length - 1]; - var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; - var afterNextSibling = (afterNode && afterNode.nextSibling) || null; - // console.log('running Transclude', clone, binder, parent, parentNode); - angular.forEach(clone, function (node) - { - parentNode.insertBefore(node, afterNextSibling); - saveNodes && binder.nodes.push(node); - }); - }); - }; - - - var ctrl = - { - watcherRemover: undefined, - queue: [], - refreshQueue: [], - group: $attrs.boName, - element: $element, - refreshing: false, - isReady: false, - oneWatcher: false, - keepBinders: false, - - ready: function () - { - // console.log('Ready to go', ctrl.queue); - ctrl.isReady = true; - ctrl.runBinders(); - ctrl.refreshOn && $scope.$on(ctrl.refreshOn, ctrl.refresher); - }, - - addBinder: function (binder) - { - if (this.group && this.group != binder.group) return; - - // console.log('Adding', binder, ' to ', this.queue); - this.queue.push(binder); - if (!this.isReady || this.queue.length > 1) return; - - this.runBinders(); - }, - - setupWatcher: function (bindonceValue) - { - this.watcherRemover = $scope.$watch(bindonceValue, function (newValue) - { - if (newValue === undefined) return; - // console.log('Ran from Watcher'); - - !ctrl.oneWatcher && ctrl.removeWatcher(); - - if (!ctrl.isReady) - { - ctrl.checkBindonce(newValue); - } - else - { - ctrl.refresher(); - } - }, true); - }, - - checkBindonce: function (value) - { - var 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(ctrl.ready); - } - else - { - ctrl.ready(); - } - }, - - removeWatcher: function () - { - if (this.watcherRemover !== undefined) - { - this.watcherRemover(); - this.watcherRemover = undefined; - } - }, - - destroy: function () - { - ctrl.queue = []; - ctrl.refreshQueue = []; - ctrl.element = undefined; - ctrl.removeWatcher(); - }, - - runBinders: function () - { - while (this.queue.length > 0) - { - var binder = this.queue.shift(); - if (!binder.dead) - { - var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); - this.runBinder(binder, value); - - if (this.keepBinders) - { - this.refreshQueue.push(binder); - binder.stopRefresh = function () - { - // console.log('Destroy stopRefresh', binder); - ctrl.refreshQueue[indexOf(ctrl.refreshQueue, binder)] = null; - } - } - } - // console.log('after adding', binder, this.keepBinders, this.refreshQueue); - }; - }, - - runBinder: function (binder, value) - { - // console.log('Binder is', binder, value, binder.value); - switch (binder.attr) - { - case 'boIf': - if (toBoolean(value)) - { - transclude(binder, !binder.attrs.boNoScope, ctrl.keepBinders); - } - break; - case 'boSwitch': - //if (binder.lastValue && binder.lastValue === value) return; - //if (binder.selectedBinders) - //{ - // angular.forEach(binder.selectedBinders, function (selectedTransclude) - // { - // if (selectedTransclude.scope) - // { - // // console.log('deleting selectedTransclude', selectedTransclude); - // if (!binder.attrs.boNoScope) selectedTransclude.scope.$destroy(); - // //selectedTransclude.element.remove(); - // angular.forEach(selectedTransclude.nodes, function (node) - // { - // node.remove(); - // }); - // delete selectedTransclude.element; - // delete selectedTransclude.nodes; - // delete selectedTransclude.transclude; - // delete selectedTransclude.scope; - // } - // }); - // delete binder.selectedBinders; - //} - var switchCtrl = binder.controller[0]; - if ((binder.selectedBinders = switchCtrl.cases['!' + value] || switchCtrl.cases['?'])) - { - binder.scope.$eval(binder.attrs.change); //TODO: document ng-change on bo-switch - angular.forEach(binder.selectedBinders, function (selectedBinder) - { - if (selectedBinder.element) - { - transclude(selectedBinder, !binder.attrs.boNoScope, ctrl.keepBinders); - // console.log('created selectedBinder', selectedBinder); - } - }); - } - break; - case 'boSwitchWhen': - var switchCtrl = binder.controller[0]; - switchCtrl.cases['!' + binder.attrs.boSwitchWhen] = (switchCtrl.cases['!' + binder.attrs.boSwitchWhen] || []); - //if (indexOf(switchCtrl.cases['!' + binder.attrs.boSwitchWhen], binder) < 0) - switchCtrl.cases['!' + binder.attrs.boSwitchWhen].push(binder); - // console.debug('Added case ' + binder.attrs.boSwitchWhen, switchCtrl.cases) - //switchCtrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element }); - break; - case 'boSwitchDefault': - var switchCtrl = binder.controller[0]; - switchCtrl.cases['?'] = (switchCtrl.cases['?'] || []); - //if (indexOf(switchCtrl.cases['?'], binder) < 0) - switchCtrl.cases['?'].push(binder); - // console.debug('Added case default', switchCtrl.cases) - //switchCtrl.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; - }; - - // TODO: avoid runBinder if the value doesn't change - binder.lastValue = value; - }, - destroyBinder: function (binder, value) - { - var cleanSwtichCases = function (binder, cases) - { - for (var i = 0; i < cases.length; i++) - { - var switchCase = cases[i]; - if (switchCase === binder) - { - // console.debug('Removing Switch case ' + switchCase.lastValue, switchCase, 'from', cases); - cases.splice(i); - cleanNodes(switchCase); - break; - } - }; - }; - var cleanNodes = function (binder) - { - binder.newScope && binder.newScope.$destroy(); - delete binder.newScope; - angular.forEach(binder.nodes, function (node) - { - // console.debug('Deleting node', node, 'from', binder.nodes, binder.element); - node.remove(); - }); - delete binder.nodes; - }; - - // console.log('Destroying Binder', binder, value, binder.value); - switch (binder.attr) - { - case 'boIf': - cleanNodes(binder); - break; - case 'boSwitch': - //if (binder.lastValue && binder.lastValue === value) return; - if (binder.selectedBinders) - { - //angular.forEach(binder.selectedBinders, function (selectedTransclude) - //{ - // // console.log('deleting selectedTransclude', selectedTransclude); - //}); - delete binder.selectedBinders; - } - break; - case 'boSwitchWhen': - var switchCtrl = binder.controller[0]; - var cases = switchCtrl.cases['!' + binder.attrs.boSwitchWhen] || []; - cleanSwtichCases(binder, cases); - break; - case 'boSwitchDefault': - var switchCtrl = binder.controller[0]; - var cases = switchCtrl.cases['?'] || []; - cleanSwtichCases(binder, cases); - break; - }; - }, - - // temporary code, I know it sucks... don't blame me - refresher: function () - { - // console.log('Refresh requested $on', ctrl); - if (ctrl.refreshing) - { - // console.log('Refresh already in progress'); - return; - } - - // - ctrl.refreshing = true; - var i, max = ctrl.refreshQueue.length; - for (i = 0; i < max; i++) - { - // console.log('Going to refresh', binder, ctrl.refreshQueue); - var binder = ctrl.refreshQueue[i]; - if (binder && !binder.dead) // it should never happens - { - // TODO: lastValue check goes here - var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); - ctrl.destroyBinder(binder, value); - ctrl.runBinder(binder, value); - } - }; - ctrl.refreshing = false; - // - } - }; - - return ctrl; - }], - - link: function (scope, elm, attrs, bindonceController) - { - var value = attrs.bindonce && scope.$eval(attrs.bindonce); - bindonceController.oneWatcher = attrs.hasOwnProperty('oneWatcher'); - bindonceController.refreshOn = attrs.refreshOn && scope.$eval(attrs.refreshOn); - bindonceController.keepBinders = bindonceController.oneWatcher || bindonceController.refreshOn; - - if (value !== undefined) - { - bindonceController.checkBindonce(value); - } - else - { - bindonceController.setupWatcher(attrs.bindonce); - } - elm.bind("$destroy", bindonceController.destroy); - } - }; - - 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]; - - // TODO: document this feature: bo-parent - var name = attrs.boParent; - if (name && bindonceController.group !== name) - { - var elementParent = bindonceController.element.parent(); - bindonceController = undefined; - var parentController; - - while (elementParent[0].nodeType !== 9 && elementParent.length) - { - if ((parentController = elementParent.data('$bindonceController')) - && parentController.group === name) - { - bindonceController = parentController; - break; - } - elementParent = elementParent.parent(); - } - if (!bindonceController) - { - throw new Error("No bindonce controller: " + name); - } - } - // END bo-parent - - var binder = { - 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 - }; - - // TODO: improve the the garbage collection - // this whole part must be rewritten - var binderDestroy = function () - { - // console.warn('Destroying', binder); - if (binder != null) - { - binder.dead = true; - binder.stopRefresh && binder.stopRefresh(); - binder.scope = binder.element = binder.transclude = undefined; - delete binder.nodes; - binder = null; - } - } - - bindonceController.addBinder(binder); - //scope.$on('$destroy', binderDestroy); - elm.bind('$destroy', binderDestroy); - } - } - }; - - return bindonceDirective; - }); - }) -})(); +(function (angular) { + "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 indexOf = function (array, obj) { + if (array.indexOf) { + return array.indexOf(obj); + } + + for (var i = 0; i < array.length; i++) { + if (obj === array[i]) { + return i; + } + } + return -1; + }; + + 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 (binder, newScope, saveNodes) { + if (newScope) { + binder.newScope = binder.scope.$new(); + } + binder.transclude((binder.newScope || binder.scope), function (clone) { + if (saveNodes) { + binder.nodes = binder.nodes || []; + } + var parent = binder.element.parent(); + var afterNode = binder.element && binder.element[binder.element.length - 1]; + var parentNode = parent && parent[0] || afterNode && afterNode.parentNode; + var afterNextSibling = (afterNode && afterNode.nextSibling) || null; + // console.log('running Transclude', clone, binder, parent, parentNode); + angular.forEach(clone, function (node) { + parentNode.insertBefore(node, afterNextSibling); + saveNodes && binder.nodes.push(node); + }); + }); + }; + + + var ctrl = { + watcherRemover: undefined, + queue: [], + refreshQueue: [], + group: $attrs.boName, + element: $element, + refreshing: false, + isReady: false, + oneWatcher: false, + keepBinders: false, + + ready: function () { + // console.log('Ready to go', ctrl.queue); + ctrl.isReady = true; + ctrl.runBinders(); + ctrl.refreshOn && $scope.$on(ctrl.refreshOn, ctrl.refresher); + }, + + addBinder: function (binder) { + if (this.group && this.group != binder.group) { + return; + } + + // console.log('Adding', binder, ' to ', this.queue); + this.queue.push(binder); + if (!this.isReady || this.queue.length > 1) { + return; + } + + this.runBinders(); + }, + + setupWatcher: function (bindonceValue) { + this.watcherRemover = $scope.$watch(bindonceValue, function (newValue) { + if (newValue === undefined) { + return; + } + // console.log('Ran from Watcher'); + + !ctrl.oneWatcher && ctrl.removeWatcher(); + + if (!ctrl.isReady) { + ctrl.checkBindonce(newValue); + } else { + ctrl.refresher(); + } + }, true); + }, + + checkBindonce: function (value) { + var 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(ctrl.ready); + } else { + ctrl.ready(); + } + }, + + removeWatcher: function () { + if (this.watcherRemover !== undefined) { + this.watcherRemover(); + this.watcherRemover = undefined; + } + }, + + destroy: function () { + ctrl.queue = []; + ctrl.refreshQueue = []; + ctrl.element = undefined; + ctrl.removeWatcher(); + }, + + runBinders: function () { + while (this.queue.length > 0) { + var binder = this.queue.shift(); + if (!binder.dead) { + var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); + this.runBinder(binder, value); + + if (this.keepBinders) { + this.refreshQueue.push(binder); + binder.stopRefresh = function () { + // console.log('Destroy stopRefresh', binder); + ctrl.refreshQueue[indexOf(ctrl.refreshQueue, binder)] = null; + }; + } + } + // console.log('after adding', binder, this.keepBinders, this.refreshQueue); + } + }, + + runBinder: function (binder, value) { + // console.log('Binder is', binder, value, binder.value); + switch (binder.attr) { + case 'boIf': + if (toBoolean(value)) { + transclude(binder, !binder.attrs.boNoScope, ctrl.keepBinders); + } + break; + case 'boSwitch': + //if (binder.lastValue && binder.lastValue === value) return; + //if (binder.selectedBinders) + //{ + // angular.forEach(binder.selectedBinders, function (selectedTransclude) + // { + // if (selectedTransclude.scope) + // { + // // console.log('deleting selectedTransclude', selectedTransclude); + // if (!binder.attrs.boNoScope) selectedTransclude.scope.$destroy(); + // //selectedTransclude.element.remove(); + // angular.forEach(selectedTransclude.nodes, function (node) + // { + // node.remove(); + // }); + // delete selectedTransclude.element; + // delete selectedTransclude.nodes; + // delete selectedTransclude.transclude; + // delete selectedTransclude.scope; + // } + // }); + // delete binder.selectedBinders; + //} + var switchCtrl = binder.controller[0]; + if ((binder.selectedBinders = switchCtrl.cases['!' + value] || switchCtrl.cases['?'])) { + binder.scope.$eval(binder.attrs.change); //TODO: document ng-change on bo-switch + angular.forEach(binder.selectedBinders, function (selectedBinder) { + if (selectedBinder.element) { + transclude(selectedBinder, !binder.attrs.boNoScope, ctrl.keepBinders); + // console.log('created selectedBinder', selectedBinder); + } + }); + } + break; + case 'boSwitchWhen': + var switchCtrl = binder.controller[0]; + switchCtrl.cases['!' + binder.attrs.boSwitchWhen] = (switchCtrl.cases['!' + binder.attrs.boSwitchWhen] || []); + //if (indexOf(switchCtrl.cases['!' + binder.attrs.boSwitchWhen], binder) < 0) + switchCtrl.cases['!' + binder.attrs.boSwitchWhen].push(binder); + // console.debug('Added case ' + binder.attrs.boSwitchWhen, switchCtrl.cases) + //switchCtrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element }); + break; + case 'boSwitchDefault': + var switchCtrl = binder.controller[0]; + switchCtrl.cases['?'] = (switchCtrl.cases['?'] || []); + //if (indexOf(switchCtrl.cases['?'], binder) < 0) + switchCtrl.cases['?'].push(binder); + // console.debug('Added case default', switchCtrl.cases) + //switchCtrl.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; + } + + // TODO: avoid runBinder if the value doesn't change + binder.lastValue = value; + }, + destroyBinder: function (binder, value) { + var cleanSwtichCases = function (binder, cases) { + for (var i = 0; i < cases.length; i++) { + var switchCase = cases[i]; + if (switchCase === binder) { + // console.debug('Removing Switch case ' + switchCase.lastValue, switchCase, 'from', cases); + cases.splice(i); + cleanNodes(switchCase); + break; + } + } + }; + var cleanNodes = function (binder) { + binder.newScope && binder.newScope.$destroy(); + delete binder.newScope; + angular.forEach(binder.nodes, function (node) { + // console.debug('Deleting node', node, 'from', binder.nodes, binder.element); + node.remove(); + }); + delete binder.nodes; + }; + + // console.log('Destroying Binder', binder, value, binder.value); + switch (binder.attr) { + case 'boIf': + cleanNodes(binder); + break; + case 'boSwitch': + //if (binder.lastValue && binder.lastValue === value) return; + if (binder.selectedBinders) { + //angular.forEach(binder.selectedBinders, function (selectedTransclude) + //{ + // // console.log('deleting selectedTransclude', selectedTransclude); + //}); + delete binder.selectedBinders; + } + break; + case 'boSwitchWhen': + var switchCtrl = binder.controller[0]; + var cases = switchCtrl.cases['!' + binder.attrs.boSwitchWhen] || []; + cleanSwtichCases(binder, cases); + break; + case 'boSwitchDefault': + var switchCtrl = binder.controller[0]; + var cases = switchCtrl.cases['?'] || []; + cleanSwtichCases(binder, cases); + break; + } + }, + + // temporary code, I know it sucks... don't blame me + refresher: function () { + // console.log('Refresh requested $on', ctrl); + if (ctrl.refreshing) { + // console.log('Refresh already in progress'); + return; + } + + // + ctrl.refreshing = true; + var i, max = ctrl.refreshQueue.length; + for (i = 0; i < max; i++) { + // console.log('Going to refresh', binder, ctrl.refreshQueue); + var binder = ctrl.refreshQueue[i]; + if (binder && !binder.dead) // it should never happens + { + // TODO: lastValue check goes here + var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); + ctrl.destroyBinder(binder, value); + ctrl.runBinder(binder, value); + } + } + ctrl.refreshing = false; + // + } + }; + + return ctrl; + }], + + link: function (scope, elm, attrs, bindonceController) { + var value = attrs.bindonce && scope.$eval(attrs.bindonce); + bindonceController.oneWatcher = attrs.hasOwnProperty('oneWatcher'); + bindonceController.refreshOn = attrs.refreshOn && scope.$eval(attrs.refreshOn); + bindonceController.keepBinders = bindonceController.oneWatcher || bindonceController.refreshOn; + + if (value !== undefined) { + bindonceController.checkBindonce(value); + } else { + bindonceController.setupWatcher(attrs.bindonce); + } + elm.bind("$destroy", bindonceController.destroy); + } + }; + + 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]; + + // TODO: document this feature: bo-parent + var name = attrs.boParent; + if (name && bindonceController.group !== name) { + var elementParent = bindonceController.element.parent(); + bindonceController = undefined; + var parentController; + + while (elementParent[0].nodeType !== 9 && elementParent.length) { + if ((parentController = elementParent.data('$bindonceController')) && parentController.group === name) { + bindonceController = parentController; + break; + } + elementParent = elementParent.parent(); + } + if (!bindonceController) { + throw new Error("No bindonce controller: " + name); + } + } + // END bo-parent + + var binder = { + 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 + }; + + // TODO: improve the the garbage collection + // this whole part must be rewritten + var binderDestroy = function () { + // console.warn('Destroying', binder); + if (binder !== null) { + binder.dead = true; + binder.stopRefresh && binder.stopRefresh(); + binder.scope = binder.element = binder.transclude = undefined; + delete binder.nodes; + binder = null; + } + }; + + bindonceController.addBinder(binder); + //scope.$on('$destroy', binderDestroy); + elm.bind('$destroy', binderDestroy); + }; + } + }; + + return bindonceDirective; + }); + }); +})(angular); \ No newline at end of file From 3246cb9989d091359a25e3e347e40a4b4b37f762 Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Thu, 11 Dec 2014 20:47:35 +0000 Subject: [PATCH 2/7] Merging changes from master version 0.3.3 --- bindonce.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/bindonce.js b/bindonce.js index a34633a..237c0f0 100644 --- a/bindonce.js +++ b/bindonce.js @@ -2,7 +2,7 @@ "use strict"; /** * Bindonce - Zero watches binding for AngularJs - * @version v0.3.1 + * @version v0.3.3 * @link https://github.com/Pasvaz/bindonce * @author Pasquale Vazzana * @license MIT License, http://www.opensource.org/licenses/MIT @@ -137,7 +137,7 @@ checkBindonce: function (value) { var promise = (value.$promise) ? value.$promise.then : value.then; - // since Angular 1.2 promises are no longer + // since Angular 1.2 promises are no longer // undefined until they don't get resolved if (typeof promise === 'function') { promise(ctrl.ready); @@ -253,6 +253,9 @@ case 'style': binder.element.css(value); break; + case 'disabled': + binder.element.prop('disabled', value); + break; case 'src': binder.element.attr(binder.attr, value); if (msie) { @@ -358,7 +361,7 @@ } }; - return ctrl; + angular.extend(this, ctrl); }], link: function (scope, elm, attrs, bindonceController) { @@ -439,6 +442,10 @@ directiveName: 'boStyle', attribute: 'style' }, + { + directiveName: 'boDisabled', + attribute: 'disabled' + }, { directiveName: 'boValue', attribute: 'value' From 7df3fb22224e06e4c1908610863bbf71b2d80abd Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Fri, 12 Dec 2014 13:28:49 +0000 Subject: [PATCH 3/7] Resolving issues with the meaning of this and ctrl and learning how refreshOn is implemented to make changes to it. --- bindonce.js | 98 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 42 deletions(-) diff --git a/bindonce.js b/bindonce.js index 237c0f0..cb97e57 100644 --- a/bindonce.js +++ b/bindonce.js @@ -66,6 +66,7 @@ }; var transclude = function (binder, newScope, saveNodes) { + console.log('transclude'); if (newScope) { binder.newScope = binder.scope.$new(); } @@ -98,93 +99,98 @@ keepBinders: false, ready: function () { - // console.log('Ready to go', ctrl.queue); - ctrl.isReady = true; - ctrl.runBinders(); - ctrl.refreshOn && $scope.$on(ctrl.refreshOn, ctrl.refresher); - }, + //console.log('Ready to go', this.queue); + this.isReady = true; + this.runBinders(); + this.refreshOn && $scope.$on(this.refreshOn, this.refresher); + }.bind(this), addBinder: function (binder) { if (this.group && this.group != binder.group) { return; } - // console.log('Adding', binder, ' to ', this.queue); + //console.log('Adding', binder, ' to ', this.queue); this.queue.push(binder); if (!this.isReady || this.queue.length > 1) { + //console.log("not running binders", this.isReady, this.queue.length); + //console.log(this); + //console.log(ctrl); return; } - + //console.log("about to run binders"); this.runBinders(); - }, + }.bind(this), setupWatcher: function (bindonceValue) { this.watcherRemover = $scope.$watch(bindonceValue, function (newValue) { if (newValue === undefined) { return; } - // console.log('Ran from Watcher'); + //console.log('Ran from Watcher'); - !ctrl.oneWatcher && ctrl.removeWatcher(); + !this.oneWatcher && this.removeWatcher(); - if (!ctrl.isReady) { - ctrl.checkBindonce(newValue); + if (!this.isReady) { + this.checkBindonce(newValue); } else { - ctrl.refresher(); + this.refresher(); } }, true); - }, + }.bind(this), checkBindonce: function (value) { var 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(ctrl.ready); + promise(this.ready); } else { - ctrl.ready(); + this.ready(); } - }, + }.bind(this), removeWatcher: function () { if (this.watcherRemover !== undefined) { this.watcherRemover(); this.watcherRemover = undefined; } - }, + }.bind(this), destroy: function () { - ctrl.queue = []; - ctrl.refreshQueue = []; - ctrl.element = undefined; - ctrl.removeWatcher(); - }, + this.queue = []; + this.refreshQueue = []; + this.element = undefined; + this.removeWatcher(); + }.bind(this), runBinders: function () { + //console.log('runBinders'); while (this.queue.length > 0) { var binder = this.queue.shift(); if (!binder.dead) { var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); + //console.log(value); this.runBinder(binder, value); if (this.keepBinders) { this.refreshQueue.push(binder); binder.stopRefresh = function () { // console.log('Destroy stopRefresh', binder); - ctrl.refreshQueue[indexOf(ctrl.refreshQueue, binder)] = null; + this.refreshQueue[indexOf(this.refreshQueue, binder)] = null; }; } } // console.log('after adding', binder, this.keepBinders, this.refreshQueue); } - }, + }.bind(this), runBinder: function (binder, value) { - // console.log('Binder is', binder, value, binder.value); + //console.log('Binder is', binder, value, binder.value); switch (binder.attr) { case 'boIf': if (toBoolean(value)) { - transclude(binder, !binder.attrs.boNoScope, ctrl.keepBinders); + transclude(binder, !binder.attrs.boNoScope, this.keepBinders); } break; case 'boSwitch': @@ -215,7 +221,7 @@ binder.scope.$eval(binder.attrs.change); //TODO: document ng-change on bo-switch angular.forEach(binder.selectedBinders, function (selectedBinder) { if (selectedBinder.element) { - transclude(selectedBinder, !binder.attrs.boNoScope, ctrl.keepBinders); + transclude(selectedBinder, !binder.attrs.boNoScope, this.keepBinders); // console.log('created selectedBinder', selectedBinder); } }); @@ -283,7 +289,8 @@ // TODO: avoid runBinder if the value doesn't change binder.lastValue = value; - }, + }.bind(this), + destroyBinder: function (binder, value) { var cleanSwtichCases = function (binder, cases) { for (var i = 0; i < cases.length; i++) { @@ -332,33 +339,33 @@ cleanSwtichCases(binder, cases); break; } - }, + }.bind(this), // temporary code, I know it sucks... don't blame me refresher: function () { - // console.log('Refresh requested $on', ctrl); - if (ctrl.refreshing) { - // console.log('Refresh already in progress'); + //console.log('Refresh requested $on', this); + if (this.refreshing) { + console.log('Refresh already in progress'); return; } // - ctrl.refreshing = true; - var i, max = ctrl.refreshQueue.length; + this.refreshing = true; + var i, max = this.refreshQueue.length; for (i = 0; i < max; i++) { - // console.log('Going to refresh', binder, ctrl.refreshQueue); - var binder = ctrl.refreshQueue[i]; + //console.log('Going to refresh', binder, ctrl.refreshQueue); + var binder = this.refreshQueue[i]; if (binder && !binder.dead) // it should never happens { // TODO: lastValue check goes here var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value); - ctrl.destroyBinder(binder, value); - ctrl.runBinder(binder, value); + this.destroyBinder(binder, value); + this.runBinder(binder, value); } } - ctrl.refreshing = false; + this.refreshing = false; // - } + }.bind(this) }; angular.extend(this, ctrl); @@ -367,6 +374,11 @@ link: function (scope, elm, attrs, bindonceController) { var value = attrs.bindonce && scope.$eval(attrs.bindonce); bindonceController.oneWatcher = attrs.hasOwnProperty('oneWatcher'); + /*if (attrs.refreshOn) { + console.log('refreshOn:', attrs.refreshOn); + console.log(attrs.refreshOn); + console.log(scope.$eval(attrs.refreshOn)); + }*/ bindonceController.refreshOn = attrs.refreshOn && scope.$eval(attrs.refreshOn); bindonceController.keepBinders = bindonceController.oneWatcher || bindonceController.refreshOn; @@ -492,6 +504,7 @@ controller: boDirective.controller, compile: function (tElement, tAttrs, transclude) { return function (scope, elm, attrs, controllers) { + //console.log('bindonce directive compile start'); var bindonceController = controllers[0]; // TODO: document this feature: bo-parent @@ -529,7 +542,7 @@ // TODO: improve the the garbage collection // this whole part must be rewritten var binderDestroy = function () { - // console.warn('Destroying', binder); + //console.warn('Destroying', binder); if (binder !== null) { binder.dead = true; binder.stopRefresh && binder.stopRefresh(); @@ -542,6 +555,7 @@ bindonceController.addBinder(binder); //scope.$on('$destroy', binderDestroy); elm.bind('$destroy', binderDestroy); + //console.log('bindonce directive compile end'); }; } }; From 07a2326e699573efed9ff2ebd7c2f752dc687c17 Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Fri, 12 Dec 2014 14:04:42 +0000 Subject: [PATCH 4/7] New: classBinder removes classes if necessary --- bindonce.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/bindonce.js b/bindonce.js index cb97e57..d4e6dbe 100644 --- a/bindonce.js +++ b/bindonce.js @@ -51,18 +51,23 @@ }; var classBinder = function (elm, value) { + var removals = []; if (angular.isObject(value) && !angular.isArray(value)) { - var results = []; + var additions = []; angular.forEach(value, function (value, index) { if (value) { - results.push(index); + additions.push(index); + } else { + removals.push(index); } }); - value = results; } - if (value) { + if (additions) { elm.addClass(angular.isArray(value) ? value.join(' ') : value); } + if (removals.length > 0) { + elm.removeClass(removals.join(' ')); + } }; var transclude = function (binder, newScope, saveNodes) { From 48246fb3d67a59af99c5d5dcff8e3a7093cdcc56 Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Fri, 12 Dec 2014 15:28:28 +0000 Subject: [PATCH 5/7] Fix: some more 'this' issues --- bindonce.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bindonce.js b/bindonce.js index d4e6dbe..950e467 100644 --- a/bindonce.js +++ b/bindonce.js @@ -181,9 +181,9 @@ if (this.keepBinders) { this.refreshQueue.push(binder); binder.stopRefresh = function () { - // console.log('Destroy stopRefresh', binder); + //console.log('Destroy stopRefresh', binder); this.refreshQueue[indexOf(this.refreshQueue, binder)] = null; - }; + }.bind(this); } } // console.log('after adding', binder, this.keepBinders, this.refreshQueue); @@ -229,7 +229,7 @@ transclude(selectedBinder, !binder.attrs.boNoScope, this.keepBinders); // console.log('created selectedBinder', selectedBinder); } - }); + }.bind(this)); } break; case 'boSwitchWhen': From 40f81f33095d4b4b6f2b7ef3a12e36c418bcb261 Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Tue, 16 Dec 2014 14:27:06 +0000 Subject: [PATCH 6/7] Fix: classBinder silly coding, meaning of 'this' Fix: - silly error in classBinder - the context of this needs to be maintained in all functions that use the keyword. --- bindonce.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/bindonce.js b/bindonce.js index 950e467..2c62031 100644 --- a/bindonce.js +++ b/bindonce.js @@ -61,8 +61,11 @@ removals.push(index); } }); + if (additions.length > 0) { + value = additions; + } } - if (additions) { + if (value) { elm.addClass(angular.isArray(value) ? value.join(' ') : value); } if (removals.length > 0) { @@ -141,7 +144,7 @@ } else { this.refresher(); } - }, true); + }.bind(this), true); }.bind(this), checkBindonce: function (value) { From 2faa369e8b3c45494fcf656996c5b36c9040b1a5 Mon Sep 17 00:00:00 2001 From: Jakub Dostal Date: Tue, 16 Dec 2014 16:23:47 +0000 Subject: [PATCH 7/7] refresh on multiple events - it is possible to refresh on multiple events by including the event names in the refreshOn attr. - refreshOn syntax is now more sensible (the whole attribute is interpreted as a space separated list of event names). --- bindonce.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/bindonce.js b/bindonce.js index 2c62031..75a9bbb 100644 --- a/bindonce.js +++ b/bindonce.js @@ -110,7 +110,16 @@ //console.log('Ready to go', this.queue); this.isReady = true; this.runBinders(); - this.refreshOn && $scope.$on(this.refreshOn, this.refresher); + if (this.refreshOn !== undefined) { + if (this.refreshOn.constructor === Array) { + for (var index = 0; index < this.refreshOn.length; index++) { + //console.log('registering to refresh on:', this.refreshOn[index]); + $scope.$on(this.refreshOn[index], this.refresher); + } + } else { + $scope.$on(this.refreshOn, this.refresher); + } + } }.bind(this), addBinder: function (binder) { @@ -387,7 +396,10 @@ console.log(attrs.refreshOn); console.log(scope.$eval(attrs.refreshOn)); }*/ - bindonceController.refreshOn = attrs.refreshOn && scope.$eval(attrs.refreshOn); + if (attrs.refreshOn) { + bindonceController.refreshOn = attrs.refreshOn.split(" "); + //console.log("refreshOn populated with:", bindonceController.refreshOn); + } bindonceController.keepBinders = bindonceController.oneWatcher || bindonceController.refreshOn; if (value !== undefined) {