bindonce.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. (function () {
  2. "use strict";
  3. /**
  4. * Bindonce - Zero watches binding for AngularJs
  5. * @version v0.3.3
  6. * @link https://github.com/Pasvaz/bindonce
  7. * @author Pasquale Vazzana <pasqualevazzana@gmail.com>
  8. * @license MIT License, http://www.opensource.org/licenses/MIT
  9. */
  10. var bindonceModule = angular.module('pasvaz.bindonce', []);
  11. bindonceModule.directive('bindonce', function ()
  12. {
  13. var toBoolean = function (value)
  14. {
  15. if (value && value.length !== 0)
  16. {
  17. var v = angular.lowercase("" + value);
  18. value = !(v === 'f' || v === '0' || v === 'false' || v === 'no' || v === 'n' || v === '[]');
  19. }
  20. else
  21. {
  22. value = false;
  23. }
  24. return value;
  25. };
  26. var msie = parseInt((/msie (\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
  27. if (isNaN(msie))
  28. {
  29. msie = parseInt((/trident\/.*; rv:(\d+)/.exec(angular.lowercase(navigator.userAgent)) || [])[1], 10);
  30. }
  31. var bindonceDirective =
  32. {
  33. restrict: "AM",
  34. controller: ['$scope', '$element', '$attrs', '$interpolate', function ($scope, $element, $attrs, $interpolate)
  35. {
  36. var showHideBinder = function (elm, attr, value)
  37. {
  38. var show = (attr === 'show') ? '' : 'none';
  39. var hide = (attr === 'hide') ? '' : 'none';
  40. elm.css('display', toBoolean(value) ? show : hide);
  41. };
  42. var classBinder = function (elm, value)
  43. {
  44. if (angular.isObject(value) && !angular.isArray(value))
  45. {
  46. var results = [];
  47. angular.forEach(value, function (value, index)
  48. {
  49. if (value) results.push(index);
  50. });
  51. value = results;
  52. }
  53. if (value)
  54. {
  55. elm.addClass(angular.isArray(value) ? value.join(' ') : value);
  56. }
  57. };
  58. var transclude = function (transcluder, scope)
  59. {
  60. transcluder.transclude(scope, function (clone)
  61. {
  62. var parent = transcluder.element.parent();
  63. var afterNode = transcluder.element && transcluder.element[transcluder.element.length - 1];
  64. var parentNode = parent && parent[0] || afterNode && afterNode.parentNode;
  65. var afterNextSibling = (afterNode && afterNode.nextSibling) || null;
  66. angular.forEach(clone, function (node)
  67. {
  68. parentNode.insertBefore(node, afterNextSibling);
  69. });
  70. });
  71. };
  72. var ctrl =
  73. {
  74. watcherRemover: undefined,
  75. binders: [],
  76. group: $attrs.boName,
  77. element: $element,
  78. ran: false,
  79. addBinder: function (binder)
  80. {
  81. this.binders.push(binder);
  82. // In case of late binding (when using the directive bo-name/bo-parent)
  83. // it happens only when you use nested bindonce, if the bo-children
  84. // are not dom children the linking can follow another order
  85. if (this.ran)
  86. {
  87. this.runBinders();
  88. }
  89. },
  90. setupWatcher: function (bindonceValue)
  91. {
  92. var that = this;
  93. this.watcherRemover = $scope.$watch(bindonceValue, function (newValue)
  94. {
  95. if (newValue === undefined) return;
  96. that.removeWatcher();
  97. that.checkBindonce(newValue);
  98. }, true);
  99. },
  100. checkBindonce: function (value)
  101. {
  102. var that = this, promise = (value.$promise) ? value.$promise.then : value.then;
  103. // since Angular 1.2 promises are no longer
  104. // undefined until they don't get resolved
  105. if (typeof promise === 'function')
  106. {
  107. promise(function ()
  108. {
  109. that.runBinders();
  110. });
  111. }
  112. else
  113. {
  114. that.runBinders();
  115. }
  116. },
  117. removeWatcher: function ()
  118. {
  119. if (this.watcherRemover !== undefined)
  120. {
  121. this.watcherRemover();
  122. this.watcherRemover = undefined;
  123. }
  124. },
  125. runBinders: function ()
  126. {
  127. while (this.binders.length > 0)
  128. {
  129. var binder = this.binders.shift();
  130. if (this.group && this.group != binder.group) continue;
  131. var value = binder.scope.$eval((binder.interpolate) ? $interpolate(binder.value) : binder.value);
  132. switch (binder.attr)
  133. {
  134. case 'boIf':
  135. if (toBoolean(value))
  136. {
  137. transclude(binder, binder.scope.$new());
  138. }
  139. break;
  140. case 'boSwitch':
  141. var selectedTranscludes, switchCtrl = binder.controller[0];
  142. if ((selectedTranscludes = switchCtrl.cases['!' + value] || switchCtrl.cases['?']))
  143. {
  144. binder.scope.$eval(binder.attrs.change);
  145. angular.forEach(selectedTranscludes, function (selectedTransclude)
  146. {
  147. transclude(selectedTransclude, binder.scope.$new());
  148. });
  149. }
  150. break;
  151. case 'boSwitchWhen':
  152. var ctrl = binder.controller[0];
  153. ctrl.cases['!' + binder.attrs.boSwitchWhen] = (ctrl.cases['!' + binder.attrs.boSwitchWhen] || []);
  154. ctrl.cases['!' + binder.attrs.boSwitchWhen].push({ transclude: binder.transclude, element: binder.element });
  155. break;
  156. case 'boSwitchDefault':
  157. var ctrl = binder.controller[0];
  158. ctrl.cases['?'] = (ctrl.cases['?'] || []);
  159. ctrl.cases['?'].push({ transclude: binder.transclude, element: binder.element });
  160. break;
  161. case 'hide':
  162. case 'show':
  163. showHideBinder(binder.element, binder.attr, value);
  164. break;
  165. case 'class':
  166. classBinder(binder.element, value);
  167. break;
  168. case 'text':
  169. binder.element.text(value);
  170. break;
  171. case 'html':
  172. binder.element.html(value);
  173. break;
  174. case 'style':
  175. binder.element.css(value);
  176. break;
  177. case 'disabled':
  178. binder.element.prop('disabled', value);
  179. break;
  180. case 'src':
  181. binder.element.attr(binder.attr, value);
  182. if (msie) binder.element.prop('src', value);
  183. break;
  184. case 'attr':
  185. angular.forEach(binder.attrs, function (attrValue, attrKey)
  186. {
  187. var newAttr, newValue;
  188. if (attrKey.match(/^boAttr./) && binder.attrs[attrKey])
  189. {
  190. newAttr = attrKey.replace(/^boAttr/, '').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
  191. newValue = binder.scope.$eval(binder.attrs[attrKey]);
  192. binder.element.attr(newAttr, newValue);
  193. }
  194. });
  195. break;
  196. case 'href':
  197. case 'alt':
  198. case 'title':
  199. case 'id':
  200. case 'value':
  201. binder.element.attr(binder.attr, value);
  202. break;
  203. }
  204. }
  205. this.ran = true;
  206. }
  207. };
  208. angular.extend(this, ctrl);
  209. }],
  210. link: function (scope, elm, attrs, bindonceController)
  211. {
  212. var value = attrs.bindonce && scope.$eval(attrs.bindonce);
  213. if (value !== undefined)
  214. {
  215. bindonceController.checkBindonce(value);
  216. }
  217. else
  218. {
  219. bindonceController.setupWatcher(attrs.bindonce);
  220. elm.bind("$destroy", bindonceController.removeWatcher);
  221. }
  222. }
  223. };
  224. return bindonceDirective;
  225. });
  226. angular.forEach(
  227. [
  228. { directiveName: 'boShow', attribute: 'show' },
  229. { directiveName: 'boHide', attribute: 'hide' },
  230. { directiveName: 'boClass', attribute: 'class' },
  231. { directiveName: 'boText', attribute: 'text' },
  232. { directiveName: 'boBind', attribute: 'text' },
  233. { directiveName: 'boHtml', attribute: 'html' },
  234. { directiveName: 'boSrcI', attribute: 'src', interpolate: true },
  235. { directiveName: 'boSrc', attribute: 'src' },
  236. { directiveName: 'boHrefI', attribute: 'href', interpolate: true },
  237. { directiveName: 'boHref', attribute: 'href' },
  238. { directiveName: 'boAlt', attribute: 'alt' },
  239. { directiveName: 'boTitle', attribute: 'title' },
  240. { directiveName: 'boId', attribute: 'id' },
  241. { directiveName: 'boStyle', attribute: 'style' },
  242. { directiveName: 'boDisabled', attribute: 'disabled' },
  243. { directiveName: 'boValue', attribute: 'value' },
  244. { directiveName: 'boAttr', attribute: 'attr' },
  245. { directiveName: 'boIf', transclude: 'element', terminal: true, priority: 1000 },
  246. { directiveName: 'boSwitch', require: 'boSwitch', controller: function () { this.cases = {}; } },
  247. { directiveName: 'boSwitchWhen', transclude: 'element', priority: 800, require: '^boSwitch' },
  248. { directiveName: 'boSwitchDefault', transclude: 'element', priority: 800, require: '^boSwitch' }
  249. ],
  250. function (boDirective)
  251. {
  252. var childPriority = 200;
  253. return bindonceModule.directive(boDirective.directiveName, function ()
  254. {
  255. var bindonceDirective =
  256. {
  257. priority: boDirective.priority || childPriority,
  258. transclude: boDirective.transclude || false,
  259. terminal: boDirective.terminal || false,
  260. require: ['^bindonce'].concat(boDirective.require || []),
  261. controller: boDirective.controller,
  262. compile: function (tElement, tAttrs, transclude)
  263. {
  264. return function (scope, elm, attrs, controllers)
  265. {
  266. var bindonceController = controllers[0];
  267. var name = attrs.boParent;
  268. if (name && bindonceController.group !== name)
  269. {
  270. var element = bindonceController.element.parent();
  271. bindonceController = undefined;
  272. var parentValue;
  273. while (element[0].nodeType !== 9 && element.length)
  274. {
  275. if ((parentValue = element.data('$bindonceController'))
  276. && parentValue.group === name)
  277. {
  278. bindonceController = parentValue;
  279. break;
  280. }
  281. element = element.parent();
  282. }
  283. if (!bindonceController)
  284. {
  285. throw new Error("No bindonce controller: " + name);
  286. }
  287. }
  288. bindonceController.addBinder(
  289. {
  290. element: elm,
  291. attr: boDirective.attribute || boDirective.directiveName,
  292. attrs: attrs,
  293. value: attrs[boDirective.directiveName],
  294. interpolate: boDirective.interpolate,
  295. group: name,
  296. transclude: transclude,
  297. controller: controllers.slice(1),
  298. scope: scope
  299. });
  300. };
  301. }
  302. };
  303. return bindonceDirective;
  304. });
  305. })
  306. })();