/turing.enumerable.js

https://github.com/dcx2011/turing.js · JavaScript · 391 lines · 168 code · 23 blank · 200 comment · 38 complexity · b1af5a930f04aad8f634370f367226fe MD5 · raw file

  1. /*!
  2. * Turing Enumerable
  3. * Copyright (C) 2010-2011 Alex R. Young
  4. * MIT Licensed
  5. */
  6. /**
  7. * The Turing Enumerable module.
  8. *
  9. * This is bound to DOM objects:
  10. *
  11. * global('p').each(function() {
  12. * // `this` contains a DOM element
  13. * });
  14. *
  15. */
  16. (function() {
  17. function EnumerableModule(global) {
  18. global.enumerable = {
  19. /**
  20. * Throw to break out of iterators.
  21. */
  22. Break: {},
  23. /**
  24. * Iterates using a function over a set of items. Example:
  25. *
  26. * turing.enumerable.each([1, 2, 3], function(n) {
  27. * console.log(n);
  28. * });
  29. *
  30. * @param {Object} enumerable A set of items that responds to `length`
  31. * @param {Function} callback The function to run
  32. * @param {Object} [context] An optional parameter to determine `this` in the callback
  33. * @returns {Object} The passed in enumerable object
  34. */
  35. each: function(enumerable, callback, context) {
  36. try {
  37. if (Array.prototype.forEach && enumerable.forEach === Array.prototype.forEach) {
  38. enumerable.forEach(callback, context);
  39. } else if (global.isNumber(enumerable.length)) {
  40. for (var i = 0, l = enumerable.length; i < l; i++) callback.call(enumerable, enumerable[i], i, enumerable);
  41. } else {
  42. for (var key in enumerable) {
  43. if (hasOwnProperty.call(enumerable, key)) callback.call(context, enumerable[key], key, enumerable);
  44. }
  45. }
  46. } catch(e) {
  47. if (e != global.enumerable.Break) throw e;
  48. }
  49. return enumerable;
  50. },
  51. /**
  52. * Changes a set of item using a function. Example:
  53. *
  54. * turing.enumerable.map([1, 2, 3], function(n) {
  55. * return n + 1;
  56. * });
  57. *
  58. * @param {Object} enumerable A set of items that responds to `length`
  59. * @param {Function} callback The function to run over each item
  60. * @param {Object} [context] An optional parameter to determine `this` in the callback
  61. * @returns {Array} The changed items
  62. */
  63. map: function(enumerable, callback, context) {
  64. if (Array.prototype.map && enumerable.map === Array.prototype.map) return enumerable.map(callback, context);
  65. var results = [];
  66. global.enumerable.each(enumerable, function(value, index, list) {
  67. results.push(callback.call(context, value, index, list));
  68. });
  69. return results;
  70. },
  71. /**
  72. * Removes items based on a callback. For example:
  73. *
  74. * var a = [1, 2, 3, 4, 5, 6, 7, 8];
  75. * turing.enumerable.filter(a, function(n) {
  76. * return n % 2 === 0;
  77. * });
  78. *
  79. * => [2, 4, 6, 8]
  80. *
  81. * @param {Object} enumerable A set of items that responds to `length`
  82. * @param {Function} callback The function to run over each item
  83. * @param {Object} [context] An optional parameter to determine `this` in the callback
  84. * @returns {Array} The filtered items
  85. */
  86. filter: function(enumerable, callback, context) {
  87. if (Array.prototype.filter && enumerable.filter === Array.prototype.filter)
  88. return enumerable.filter(callback, context);
  89. var results = [],
  90. pushIndex = !global.isArray(enumerable);
  91. global.enumerable.each(enumerable, function(value, index, list) {
  92. if (callback.call(context, value, index, list)) {
  93. if (pushIndex) {
  94. results.push([index, value]);
  95. } else {
  96. results.push(value);
  97. }
  98. }
  99. });
  100. return results;
  101. },
  102. /**
  103. * The opposite of filter. For example:
  104. *
  105. * var a = [1, 2, 3, 4, 5, 6, 7, 8];
  106. * turing.enumerable.reject(a, function(n) {
  107. * return n % 2 === 0;
  108. * });
  109. *
  110. * => [1, 3, 5, 7]
  111. *
  112. * @param {Object} enumerable A set of items that responds to `length`
  113. * @param {Function} callback The function to run over each item
  114. * @param {Object} [context] An optional parameter to determine `this` in the callback
  115. * @returns {Array} The rejected items
  116. */
  117. reject: function(enumerable, callback, context) {
  118. return this.filter(enumerable, function() {
  119. return !callback.apply(context, arguments);
  120. }, context);
  121. },
  122. /**
  123. * Find a single item. For example:
  124. *
  125. * var a = [1, 2, 3, 4, 5, 6, 7, 8];
  126. * turing.enumerable.detect(a, function(n) {
  127. * return n === 3;
  128. * });
  129. *
  130. * => 3
  131. *
  132. * @param {Object} enumerable A set of items that responds to `length`
  133. * @param {Function} callback The function to run over each item
  134. * @param {Object} [context] An optional parameter to determine `this` in the callback
  135. * @returns {Object} The item, if found
  136. */
  137. detect: function(enumerable, callback, context) {
  138. var result;
  139. global.enumerable.each(enumerable, function(value, index, list) {
  140. if (callback.call(context, value, index, list)) {
  141. result = value;
  142. throw global.enumerable.Break;
  143. }
  144. });
  145. return result;
  146. },
  147. /**
  148. * Runs a function over each item, collecting the results:
  149. *
  150. * var a = [1, 2, 3, 4, 5, 6, 7, 8];
  151. * turing.enumerable.reduce(a, 0, function(memo, n) {
  152. * return memo + n;
  153. * });
  154. *
  155. * => 36
  156. *
  157. * @param {Object} enumerable A set of items that responds to `length`
  158. * @param {Object} memo The initial accumulator value
  159. * @param {Function} callback The function to run over each item
  160. * @param {Object} [context] An optional parameter to determine `this` in the callback
  161. * @returns {Object} The accumulated results
  162. */
  163. reduce: function(enumerable, memo, callback, context) {
  164. if (Array.prototype.reduce && enumerable.reduce === Array.prototype.reduce)
  165. return enumerable.reduce(global.bind(callback, context), memo);
  166. global.enumerable.each(enumerable, function(value, index, list) {
  167. memo = callback.call(context, memo, value, index, list);
  168. });
  169. return memo;
  170. },
  171. /**
  172. * Flattens multidimensional arrays:
  173. *
  174. * turing.enumerable.flatten([[2, 4], [[6], 8]]);
  175. *
  176. * => [2, 4, 6, 8]
  177. *
  178. * @param {Object} enumerable A set of items that responds to `length`
  179. * @returns {Object} The flat array
  180. */
  181. flatten: function(array) {
  182. return global.enumerable.reduce(array, [], function(memo, value) {
  183. if (global.isArray(value)) return memo.concat(global.enumerable.flatten(value));
  184. memo.push(value);
  185. return memo;
  186. });
  187. },
  188. /**
  189. * Return the last items from a list:
  190. *
  191. * turing.enumerable.tail([1, 2, 3, 4, 5], 3);
  192. *
  193. * => [4, 5]
  194. *
  195. * @param {Object} enumerable A set of items that responds to `length`
  196. * @param {Number} start The index of the item to 'cut' the array
  197. * @returns {Object} A list of items
  198. */
  199. tail: function(enumerable, start) {
  200. start = typeof start === 'undefined' ? 1 : start;
  201. return Array.prototype.slice.apply(enumerable, [start]);
  202. },
  203. /**
  204. * Invokes `method` on a list of items:
  205. *
  206. * turing.enumerable.invoke(['hello', 'world'], 'substring', 0, 3);
  207. *
  208. * => ['hel', 'wor']
  209. *
  210. * @param {Object} enumerable A set of items that responds to `length`
  211. * @param {Function} method The method to invoke on each item
  212. * @returns {Object} The changed list
  213. */
  214. invoke: function(enumerable, method) {
  215. var args = global.enumerable.tail(arguments, 2);
  216. return global.enumerable.map(enumerable, function(value) {
  217. return (method ? value[method] : value).apply(value, args);
  218. });
  219. },
  220. /**
  221. * Pluck a property from each item of a list:
  222. *
  223. * turing.enumerable.pluck(['hello', 'world'], 'length');
  224. *
  225. * => [5, 5]
  226. *
  227. * @param {Object} enumerable A set of items that responds to `length`
  228. * @param {String} key The property to pluck
  229. * @returns {Object} The plucked properties
  230. */
  231. pluck: function(enumerable, key) {
  232. return global.enumerable.map(enumerable, function(value) {
  233. return value[key];
  234. });
  235. },
  236. /**
  237. * Determines if a list matches some items based on a callback:
  238. *
  239. * turing.enumerable.some([1, 2, 3], function(value) {
  240. * return value === 3;
  241. * });
  242. *
  243. * => true
  244. *
  245. * @param {Object} enumerable A set of items that responds to `length`
  246. * @param {Function} callback A function to run against each item
  247. * @param {Object} [context] An optional parameter to determine `this` in the callback
  248. * @returns {Boolean} True if an item was matched
  249. */
  250. some: function(enumerable, callback, context) {
  251. callback = callback || global.enumerable.identity;
  252. if (Array.prototype.some && enumerable.some === Array.prototype.some)
  253. return enumerable.some(callback, context);
  254. var result = false;
  255. global.enumerable.each(enumerable, function(value, index, list) {
  256. if (result = callback.call(context, value, index, list)) {
  257. throw global.enumerable.Break;
  258. }
  259. });
  260. return result;
  261. },
  262. /**
  263. * Checks if all items match the callback:
  264. *
  265. * turing.enumerable.all([1, 2, 3], function(value) {
  266. * return value < 4;
  267. * })
  268. *
  269. * => true
  270. *
  271. * @param {Object} enumerable A set of items that responds to `length`
  272. * @param {Function} callback A function to run against each item
  273. * @param {Object} [context] An optional parameter to determine `this` in the callback
  274. * @returns {Boolean} True if all items match
  275. */
  276. all: function(enumerable, callback, context) {
  277. callback = callback || global.enumerable.identity;
  278. if (Array.prototype.every && enumerable.every === Array.prototype.every)
  279. return enumerable.every(callback, context);
  280. var result = true;
  281. global.enumerable.each(enumerable, function(value, index, list) {
  282. if (!(result = result && callback.call(context, value, index, list))) {
  283. throw global.enumerable.Break;
  284. }
  285. });
  286. return result;
  287. },
  288. /**
  289. * Checks if one item matches a value:
  290. *
  291. * turing.enumerable.include([1, 2, 3], 3);
  292. *
  293. * => true
  294. *
  295. * @param {Object} enumerable A set of items that responds to `length`
  296. * @param {Object} target A value to find
  297. * @returns {Boolean} True if an item was found
  298. */
  299. include: function(enumerable, target) {
  300. if (Array.prototype.indexOf && enumerable.indexOf === Array.prototype.indexOf)
  301. return enumerable.indexOf(target) != -1;
  302. var found = false;
  303. global.enumerable.each(enumerable, function(value, key) {
  304. if (found = value === target) {
  305. throw global.enumerable.Break;
  306. }
  307. });
  308. return found;
  309. },
  310. /**
  311. * Chain enumerable calls:
  312. *
  313. * turing.enumerable.chain([1, 2, 3, 4])
  314. * .filter(function(n) { return n % 2 == 0; })
  315. * .map(function(n) { return n * 10; })
  316. * .values();
  317. *
  318. * => [20, 40]
  319. *
  320. * @param {Object} enumerable A set of items that responds to `length`
  321. * @returns {Object} The chained enumerable API
  322. */
  323. chain: function(enumerable) {
  324. return new global.enumerable.Chainer(enumerable);
  325. },
  326. identity: function(value) {
  327. return value;
  328. }
  329. };
  330. // Aliases
  331. global.enumerable.select = global.enumerable.filter;
  332. global.enumerable.collect = global.enumerable.map;
  333. global.enumerable.inject = global.enumerable.reduce;
  334. global.enumerable.rest = global.enumerable.tail;
  335. global.enumerable.any = global.enumerable.some;
  336. global.enumerable.every = global.enumerable.all;
  337. global.chainableMethods = ['map', 'collect', 'detect', 'filter', 'reduce', 'each',
  338. 'tail', 'rest', 'reject', 'pluck', 'any', 'some', 'all'];
  339. // Chainer class
  340. global.enumerable.Chainer = function(values) {
  341. this.results = values;
  342. };
  343. global.enumerable.Chainer.prototype.values = function() {
  344. return this.results;
  345. };
  346. global.enumerable.each(global.chainableMethods, function(methodName) {
  347. var method = global.enumerable[methodName];
  348. global.enumerable.Chainer.prototype[methodName] = function() {
  349. var args = Array.prototype.slice.call(arguments);
  350. args.unshift(this.results);
  351. this.results = method.apply(this, args);
  352. return this;
  353. }
  354. });
  355. global.init(function(arg) {
  356. if (arg.hasOwnProperty.length && typeof arg !== 'string') {
  357. return global.enumerable.chain(arg);
  358. }
  359. });
  360. }
  361. if (typeof module !== 'undefined') {
  362. module.exports = function(t) {
  363. return EnumerableModule(t);
  364. }
  365. } else {
  366. EnumerableModule(turing);
  367. }
  368. })();