From 00e8b78554eae159c269aa21976716c3ad1e0463 Mon Sep 17 00:00:00 2001 From: Dean Poulin Date: Fri, 30 Jan 2015 14:50:35 -0500 Subject: [PATCH 1/3] Updated App Structure and Build for Easier Development Workflow - Added bower dependency on Angular (Should this be 1.x vs 1.2.x?) - Added jshint - Fixed jshint errors in code line 1 col 13 Strings must use singlequote. line 18 col 35 Strings must use singlequote. line 36 col 19 Strings must use singlequote. line 140 col 44 Expected '!==' and instead saw '!='. line 158 col 20 Don't make functions within a loop. line 167 col 26 'ctrl' is already defined. line 204 col 18 Don't make functions within a loop. line 232 col 28 Strings must use singlequote. line 292 col 19 Bad line breaking before '&&'. line 301 col 59 Strings must use singlequote. - Added gulp build with uglify, ngAnnotate, etc... - Added .gitignore - New development workflow: 1) npm install 2) make development changes 3) gulp build 4) tag repository --- .bowerrc | 3 + .gitignore | 3 + .jshintrc | 26 ++++ bindonce.js | 325 ------------------------------------------- bindonce.min.js | 1 - bower.json | 8 +- dist/bindonce.js | 280 +++++++++++++++++++++++++++++++++++++ dist/bindonce.min.js | 7 + gulpfile.js | 40 ++++++ package.json | 23 ++- src/bindonce.js | 276 ++++++++++++++++++++++++++++++++++++ 11 files changed, 661 insertions(+), 331 deletions(-) create mode 100644 .bowerrc create mode 100644 .gitignore create mode 100644 .jshintrc delete mode 100644 bindonce.js delete mode 100644 bindonce.min.js create mode 100644 dist/bindonce.js create mode 100644 dist/bindonce.min.js create mode 100755 gulpfile.js create mode 100644 src/bindonce.js diff --git a/.bowerrc b/.bowerrc new file mode 100644 index 0000000..6b3d068 --- /dev/null +++ b/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory" : "src/bower_components" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..99b7b4d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +node_modules +src/bower_components \ No newline at end of file diff --git a/.jshintrc b/.jshintrc new file mode 100644 index 0000000..b58248e --- /dev/null +++ b/.jshintrc @@ -0,0 +1,26 @@ +{ + "esnext": true, + "bitwise": true, + "camelcase": true, + "curly": false, + "eqeqeq": true, + "immed": true, + "indent": 2, + "latedef": true, + "newcap": true, + "noarg": true, + "quotmark": "single", + "regexp": true, + "undef": true, + "unused": true, + "strict": true, + "trailing": true, + "smarttabs": true, + "white": true, + "node": true, + "browser": true, + "laxcomma": true, + "globals": { + "angular": false + } +} diff --git a/bindonce.js b/bindonce.js deleted file mode 100644 index 8742d89..0000000 --- a/bindonce.js +++ /dev/null @@ -1,325 +0,0 @@ -(function () { - "use strict"; - /** - * Bindonce - Zero watches binding for AngularJs - * @version v0.3.3 - * @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 'disabled': - binder.element.prop('disabled', 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; - } - }; - - angular.extend(this, 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: 'boDisabled', attribute: 'disabled' }, - { 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; - }); - }) -})(); diff --git a/bindonce.min.js b/bindonce.min.js deleted file mode 100644 index 555ded4..0000000 --- a/bindonce.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(){"use strict";var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10));var r={restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group==r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,l=r.controller[0];(i=l.cases["!"+a]||l.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,function(e){s(e,r.scope.$new())}));break;case"boSwitchWhen":var u=r.controller[0];u.cases["!"+r.attrs.boSwitchWhen]=u.cases["!"+r.attrs.boSwitchWhen]||[],u.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var u=r.controller[0];u.cases["?"]=u.cases["?"]||[],u.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"disabled":r.element.prop("disabled",a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,function(e,t){var a,i;t.match(/^boAttr./)&&r.attrs[t]&&(a=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),i=r.scope.$eval(r.attrs[t]),r.element.attr(a,i))});break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};angular.extend(this,l)}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}};return r}),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:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boDisabled",attribute:"disabled"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{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(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}(); \ No newline at end of file diff --git a/bower.json b/bower.json index 9242594..84671bb 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "angular-bindonce", - "version": "0.3.3", - "main": "bindonce.js", + "version": "0.3.4", + "main": "dist/bindonce.js", "description": "Zero watchers binding directives for AngularJS", "homepage": "https://github.com/Pasvaz/bindonce", "author": "Pasquale Vazzana ", @@ -13,9 +13,11 @@ "ignore": [ "**/.*", "node_modules", - "components" + "src", + "gulpfile.js" ], "dependencies": { + "angular": "1.2.x" }, "keywords": [ "angularjs", diff --git a/dist/bindonce.js b/dist/bindonce.js new file mode 100644 index 0000000..c2a6bb0 --- /dev/null +++ b/dist/bindonce.js @@ -0,0 +1,280 @@ +(function(){ +'use strict'; +/** + * @license Bindonce v0.3.3 - Zero watches binding for AngularJs + * 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); + } + + return { + 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 transcludeSelection = function (selectedTransclude) { + transclude(selectedTransclude, this.scope.$new()); + }; + + var setBinderAttr = function (attrValue, attrKey) { + var newAttr, newValue; + if (attrKey.match(/^boAttr./) && this.attrs[attrKey]) { + newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + newValue = this.scope.$eval(this.attrs[attrKey]); + this.element.attr(newAttr, newValue); + } + }; + + 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, transcludeSelection, binder); + } + break; + case 'boSwitchWhen': + var switchWhenCtrl = binder.controller[0]; + switchWhenCtrl.cases['!' + binder.attrs.boSwitchWhen] = (switchWhenCtrl.cases['!' + binder.attrs.boSwitchWhen] || []); + switchWhenCtrl.cases['!' + binder.attrs.boSwitchWhen].push({ + transclude: binder.transclude, + element: binder.element + }); + break; + case 'boSwitchDefault': + var switchDefaultCtrl = binder.controller[0]; + switchDefaultCtrl.cases['?'] = (switchDefaultCtrl.cases['?'] || []); + switchDefaultCtrl.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 'disabled': + binder.element.prop('disabled', value); + break; + case 'src': + binder.element.attr(binder.attr, value); + if (msie) binder.element.prop('src', value); + break; + case 'attr': + angular.forEach(binder.attrs, setBinderAttr, binder); + break; + case 'href': + case 'alt': + case 'title': + case 'id': + case 'value': + binder.element.attr(binder.attr, value); + break; + } + } + this.ran = true; + } + }; + + angular.extend(this, 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); + } + } + }; +}); + +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: 'boDisabled', attribute: 'disabled'}, + {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; + }); + }); + +})(); \ No newline at end of file diff --git a/dist/bindonce.min.js b/dist/bindonce.min.js new file mode 100644 index 0000000..4ee3174 --- /dev/null +++ b/dist/bindonce.min.js @@ -0,0 +1,7 @@ +!function(){"use strict";/** + * @license Bindonce v0.3.3 - Zero watches binding for AngularJs + * Link: https://github.com/Pasvaz/bindonce + * Author: Pasquale Vazzana + * License: MIT License, http://www.opensource.org/licenses/MIT + */ +var e=angular.module("pasvaz.bindonce",[]);e.directive("bindonce",function(){var e=function(e){if(e&&0!==e.length){var t=angular.lowercase(""+e);e=!("f"===t||"0"===t||"false"===t||"no"===t||"n"===t||"[]"===t)}else e=!1;return e},t=parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10);return isNaN(t)&&(t=parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent))||[])[1],10)),{restrict:"AM",controller:["$scope","$element","$attrs","$interpolate",function(r,a,i,n){var c=function(t,r,a){var i="show"===r?"":"none",n="hide"===r?"":"none";t.css("display",e(a)?i:n)},o=function(e,t){if(angular.isObject(t)&&!angular.isArray(t)){var r=[];angular.forEach(t,function(e,t){e&&r.push(t)}),t=r}t&&e.addClass(angular.isArray(t)?t.join(" "):t)},s=function(e,t){e.transclude(t,function(t){var r=e.element.parent(),a=e.element&&e.element[e.element.length-1],i=r&&r[0]||a&&a.parentNode,n=a&&a.nextSibling||null;angular.forEach(t,function(e){i.insertBefore(e,n)})})},l=function(e){s(e,this.scope.$new())},u=function(e,t){var r,a;t.match(/^boAttr./)&&this.attrs[t]&&(r=t.replace(/^boAttr/,"").replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase(),a=this.scope.$eval(this.attrs[t]),this.element.attr(r,a))},d={watcherRemover:void 0,binders:[],group:i.boName,element:a,ran:!1,addBinder:function(e){this.binders.push(e),this.ran&&this.runBinders()},setupWatcher:function(e){var t=this;this.watcherRemover=r.$watch(e,function(e){void 0!==e&&(t.removeWatcher(),t.checkBindonce(e))},!0)},checkBindonce:function(e){var t=this,r=e.$promise?e.$promise.then:e.then;"function"==typeof r?r(function(){t.runBinders()}):t.runBinders()},removeWatcher:function(){void 0!==this.watcherRemover&&(this.watcherRemover(),this.watcherRemover=void 0)},runBinders:function(){for(;this.binders.length>0;){var r=this.binders.shift();if(!this.group||this.group===r.group){var a=r.scope.$eval(r.interpolate?n(r.value):r.value);switch(r.attr){case"boIf":e(a)&&s(r,r.scope.$new());break;case"boSwitch":var i,d=r.controller[0];(i=d.cases["!"+a]||d.cases["?"])&&(r.scope.$eval(r.attrs.change),angular.forEach(i,l,r));break;case"boSwitchWhen":var b=r.controller[0];b.cases["!"+r.attrs.boSwitchWhen]=b.cases["!"+r.attrs.boSwitchWhen]||[],b.cases["!"+r.attrs.boSwitchWhen].push({transclude:r.transclude,element:r.element});break;case"boSwitchDefault":var h=r.controller[0];h.cases["?"]=h.cases["?"]||[],h.cases["?"].push({transclude:r.transclude,element:r.element});break;case"hide":case"show":c(r.element,r.attr,a);break;case"class":o(r.element,a);break;case"text":r.element.text(a);break;case"html":r.element.html(a);break;case"style":r.element.css(a);break;case"disabled":r.element.prop("disabled",a);break;case"src":r.element.attr(r.attr,a),t&&r.element.prop("src",a);break;case"attr":angular.forEach(r.attrs,u,r);break;case"href":case"alt":case"title":case"id":case"value":r.element.attr(r.attr,a)}}}this.ran=!0}};angular.extend(this,d)}],link:function(e,t,r,a){var i=r.bindonce&&e.$eval(r.bindonce);void 0!==i?a.checkBindonce(i):(a.setupWatcher(r.bindonce),t.bind("$destroy",a.removeWatcher))}}}),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:!0},{directiveName:"boSrc",attribute:"src"},{directiveName:"boHrefI",attribute:"href",interpolate:!0},{directiveName:"boHref",attribute:"href"},{directiveName:"boAlt",attribute:"alt"},{directiveName:"boTitle",attribute:"title"},{directiveName:"boId",attribute:"id"},{directiveName:"boStyle",attribute:"style"},{directiveName:"boDisabled",attribute:"disabled"},{directiveName:"boValue",attribute:"value"},{directiveName:"boAttr",attribute:"attr"},{directiveName:"boIf",transclude:"element",terminal:!0,priority:1e3},{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(t){var r=200;return e.directive(t.directiveName,function(){var e={priority:t.priority||r,transclude:t.transclude||!1,terminal:t.terminal||!1,require:["^bindonce"].concat(t.require||[]),controller:t.controller,compile:function(e,r,a){return function(e,r,i,n){var c=n[0],o=i.boParent;if(o&&c.group!==o){var s=c.element.parent();c=void 0;for(var l;9!==s[0].nodeType&&s.length;){if((l=s.data("$bindonceController"))&&l.group===o){c=l;break}s=s.parent()}if(!c)throw new Error("No bindonce controller: "+o)}c.addBinder({element:r,attr:t.attribute||t.directiveName,attrs:i,value:i[t.directiveName],interpolate:t.interpolate,group:o,transclude:a,controller:n.slice(1),scope:e})}}};return e})})}(); \ No newline at end of file diff --git a/gulpfile.js b/gulpfile.js new file mode 100755 index 0000000..56db168 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,40 @@ +'use strict'; + +var gulp = require('gulp'); +var $ = require('gulp-load-plugins')(); +var saveLicense = require('uglify-save-license'); +var wrap = require("gulp-wrap"); +var replace = require("gulp-replace"); +var ngAnnotate = require('gulp-ng-annotate'); + +gulp.task('scripts', function () { + return gulp.src(['src/*.js']) + .pipe(replace(/'use strict';/g, '')) + .pipe(replace(/"use strict";/g, '')) + .pipe(ngAnnotate()) + .pipe(wrap("(function(){\n'use strict';\n<%= contents %>\n})();")) + .pipe($.concat('bindonce.js')) + .pipe(gulp.dest('dist')) + .pipe($.uglify({preserveComments: saveLicense})) + .pipe($.rename('bindonce.min.js')) + .pipe(gulp.dest('dist')) + .pipe($.size()); +}); + +gulp.task('jshint', function () { + return gulp.src(['src/*.js']) + .pipe($.jshint()) + .pipe($.jshint.reporter('jshint-stylish')) + .pipe($.size()); +}); + +gulp.task('clean', function () { + return gulp.src(['.tmp', 'dist'], { read: false }) + .pipe($.rimraf()); +}); + +gulp.task('build', ['jshint', 'scripts']); + +gulp.task('default', ['clean'], function () { + gulp.start('build'); +}); diff --git a/package.json b/package.json index 9242594..963cce8 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,23 @@ "node_modules", "components" ], - "dependencies": { + "dependencies": {}, + "devDependencies": { + "gulp": "^3.8.7", + "gulp-concat": "^2.3.4", + "gulp-jshint": "^1.8.4", + "gulp-load-plugins": "^0.5.3", + "gulp-ng-annotate": "^0.5.2", + "gulp-rename": "^1.2.0", + "gulp-replace": "^0.5.2", + "gulp-rimraf": "^0.1.0", + "gulp-size": "^1.0.0", + "gulp-uglify": "^0.3.1", + "gulp-util": "^3.0.0", + "gulp-wrap": "^0.10.1", + "jshint-stylish": "^0.4.0", + "require-dir": "^0.1.0", + "uglify-save-license": "^0.4.1" }, "keywords": [ "angularjs", @@ -24,5 +40,8 @@ "binding", "watcher", "bindonce" - ] + ], + "engines": { + "node": ">=0.10.0" + } } diff --git a/src/bindonce.js b/src/bindonce.js new file mode 100644 index 0000000..c59e98f --- /dev/null +++ b/src/bindonce.js @@ -0,0 +1,276 @@ +/** + * @license Bindonce v0.3.3 - Zero watches binding for AngularJs + * Link: https://github.com/Pasvaz/bindonce + * Author: Pasquale Vazzana + * License: MIT License, http://www.opensource.org/licenses/MIT + */ +'use strict'; + +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); + } + + return { + restrict: 'AM', + controller: 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 transcludeSelection = function (selectedTransclude) { + transclude(selectedTransclude, this.scope.$new()); + }; + + var setBinderAttr = function (attrValue, attrKey) { + var newAttr, newValue; + if (attrKey.match(/^boAttr./) && this.attrs[attrKey]) { + newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase(); + newValue = this.scope.$eval(this.attrs[attrKey]); + this.element.attr(newAttr, newValue); + } + }; + + 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, transcludeSelection, binder); + } + break; + case 'boSwitchWhen': + var switchWhenCtrl = binder.controller[0]; + switchWhenCtrl.cases['!' + binder.attrs.boSwitchWhen] = (switchWhenCtrl.cases['!' + binder.attrs.boSwitchWhen] || []); + switchWhenCtrl.cases['!' + binder.attrs.boSwitchWhen].push({ + transclude: binder.transclude, + element: binder.element + }); + break; + case 'boSwitchDefault': + var switchDefaultCtrl = binder.controller[0]; + switchDefaultCtrl.cases['?'] = (switchDefaultCtrl.cases['?'] || []); + switchDefaultCtrl.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 'disabled': + binder.element.prop('disabled', value); + break; + case 'src': + binder.element.attr(binder.attr, value); + if (msie) binder.element.prop('src', value); + break; + case 'attr': + angular.forEach(binder.attrs, setBinderAttr, binder); + break; + case 'href': + case 'alt': + case 'title': + case 'id': + case 'value': + binder.element.attr(binder.attr, value); + break; + } + } + this.ran = true; + } + }; + + angular.extend(this, 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); + } + } + }; +}); + +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: 'boDisabled', attribute: 'disabled'}, + {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; + }); + }); From 74bce9cecb53762b57ebf5cf2e3c7af9f167966c Mon Sep 17 00:00:00 2001 From: Dean Poulin Date: Fri, 30 Jan 2015 15:16:54 -0500 Subject: [PATCH 2/3] Moved order so we only apply on final file. --- gulpfile.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 56db168..3ab392a 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -9,11 +9,11 @@ var ngAnnotate = require('gulp-ng-annotate'); gulp.task('scripts', function () { return gulp.src(['src/*.js']) + .pipe(ngAnnotate()) + .pipe($.concat('bindonce.js')) .pipe(replace(/'use strict';/g, '')) .pipe(replace(/"use strict";/g, '')) - .pipe(ngAnnotate()) .pipe(wrap("(function(){\n'use strict';\n<%= contents %>\n})();")) - .pipe($.concat('bindonce.js')) .pipe(gulp.dest('dist')) .pipe($.uglify({preserveComments: saveLicense})) .pipe($.rename('bindonce.min.js')) From 65cb2c45b60ed092ae6f13512811867a7ff1d0e2 Mon Sep 17 00:00:00 2001 From: Dean Poulin Date: Tue, 3 Feb 2015 13:48:38 -0500 Subject: [PATCH 3/3] Condensed unnecessary additional pipe to one. --- gulpfile.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 3ab392a..66d6113 100755 --- a/gulpfile.js +++ b/gulpfile.js @@ -11,8 +11,7 @@ gulp.task('scripts', function () { return gulp.src(['src/*.js']) .pipe(ngAnnotate()) .pipe($.concat('bindonce.js')) - .pipe(replace(/'use strict';/g, '')) - .pipe(replace(/"use strict";/g, '')) + .pipe($.replace(/(['"])use strict\1;/g, '')) .pipe(wrap("(function(){\n'use strict';\n<%= contents %>\n})();")) .pipe(gulp.dest('dist')) .pipe($.uglify({preserveComments: saveLicense}))