UNPKG

underscore-keypath

Version:

Adds Key-Path mechanism extensions for underscore

345 lines (280 loc) 7.59 kB
(function () { var underscore; var root = this, isNodeJS, arrayPropertyResolvers, arrayPropertyWriters, mixins, aliases; isNodeJS = typeof module !== "undefined" && typeof require !== "undefined"; if (!isNodeJS && typeof _ !== "undefined") { underscore = _; } // what if this runs on meteor, underscore is already defined if (typeof underscore == "undefined") { if (isNodeJS) { underscore = require("underscore"); } else { underscore = root.underscore; } } if (typeof underscore === "undefined") { throw new Error("underscore.js is not found!"); } arrayPropertyResolvers = { "@first" : function (array) { return underscore.first(array); }, "@last" : function (array) { return underscore.last(array); }, "@max" : function (array) { return underscore.max(array); }, "@min" : function (array) { return underscore.min(array); }, "@size" : function (array) { return underscore.size(array); } }; arrayPropertyWriters = { "@first" : function (array, value) { array[0] = value; return value; }, "@last" : function (array, value) { array[Math.max(array.length - 1, 0)] = value; return value; } }; function replaceBracketContent(capture, g1, g2, offset) { return (offset !== 0 ? "." : "") + g1.replace(/\./g, "«dot»"); } function toSegments(keypath) { "use strict"; if (underscore.isArray(keypath)) { return Array.prototype.slice.call(keypath); } if (underscore.isString(keypath)) { keypath = keypath.replace(/\.\./g, "«dot»"); // issue #1 https://github.com/jeeeyul/underscore-keypath/issues/1 keypath = keypath.replace(/\['(([^']|\\')*)'\]/g, replaceBracketContent); keypath = keypath.replace(/\["(([^"]|\\")*)"\]/g, replaceBracketContent); keypath = keypath.replace(/([$A-Za-z_][0-9A-Za-z_$]*)\(\)/, "$1"); var result = keypath.split("."); result = underscore(result).map(function (each) { return each.replace(/«dot»/g, "."); }); return result; } throw new Error("keypath must be an array or a string"); } function capitalize(str) { "use strict"; str = str === null ? '' : String(str); return str.charAt(0).toUpperCase() + str.slice(1); } function getArrayProperty(array, name) { "use strict"; var resolver = arrayPropertyResolvers[name]; if (resolver) { return resolver(array); } return undefined; } function setArrayProperty(array, name, value) { "use strict"; if (!(array instanceof Array)) { return undefined; } var setter = arrayPropertyWriters[name]; if (setter) { return setter(array, value); } else { return undefined; } } function getProperty(obj, name) { "use strict"; if (name === "") { return obj; } if (name.indexOf("@") === 0) { return getArrayProperty(obj, name); } var target = obj["get" + capitalize(name)]; if (target === undefined) { target = obj["is" + capitalize(name)]; } if (target === undefined) { target = obj[name]; } if (typeof target === "function") { target = target.call(obj); } return target; } function hasProperty(obj, name) { "use strict"; if (name === "") { return true; } return typeof obj["get" + capitalize(name)] === "function" || typeof obj["is" + capitalize(name)] === "function" || underscore.has(obj, name); } function setProperty(obj, name, value) { "use strict"; var setter = obj["set" + capitalize(name)]; if (setter) { return setter.call(obj, value); } if (name.indexOf("@") === 0) { return setArrayProperty(obj, name, value); } obj[name] = value; return value; } function matchQuery(obj, query) { "use strict"; var match = true, eachKeyPath, expected; for (eachKeyPath in query) { if (query.hasOwnProperty(eachKeyPath)) { expected = query[eachKeyPath]; if (underscore(obj).valueForKeyPath(eachKeyPath) !== expected) { match = false; break; } } } return match; } mixins = { valueForKeyPath : function (obj, keypath, fallback) { "use strict"; var finger = obj, segments, next, parent; if (obj === null || obj === undefined) { return fallback; } segments = toSegments(keypath); next = segments.shift(); parent = undefined; while (finger !== null && finger !== undefined && typeof next === "string") { parent = finger; finger = getProperty(finger, next); if (typeof finger === "function") { finger = finger.call(parent); } next = segments.shift(); } if (finger === null || finger === undefined) { return fallback; } else { return finger; } }, setValueForKeyPath : function (obj, keypath, newValue) { "use strict"; var segments = toSegments(keypath), lastPropertyName, target; lastPropertyName = segments.pop(); target = underscore(obj).valueForKeyPath(segments.join(".")); if (target !== null && target !== undefined) { return setProperty(target, lastPropertyName, newValue); } return undefined; }, pluckByKeyPath : function (array, keypath) { "use strict"; var result = [], i, each; for (i = 0; i < array.length; i += 1) { each = array[i]; result[i] = underscore(each).valueForKeyPath(keypath); } return result; }, whereByKeyPath : function (array, query) { "use strict"; var result = [], i, each; for (i = 0; i < array.length; i += 1) { each = array[i]; if (matchQuery(each, query)) { result.push(each); } } return result; }, findWhereByKeyPath : function (array, query) { "use strict"; var i, each; for (i = 0; i < array.length; i += 1) { each = array[i]; if (matchQuery(each, query)) { return each; } } return null; }, sortByKeyPath : function (array, keyPath) { "use strict"; if (typeof keyPath !== "string") { return underscore.sortBy(array, keyPath); } return underscore.sortBy(array, function (it) { return underscore.valueForKeyPath(it, keyPath); }); }, groupByKeyPath : function (array, keyPath) { "use strict"; if (typeof keyPath !== "string") { return underscore.groupBy(array, keyPath); } return underscore.groupBy(array, function (it) { return underscore.valueForKeyPath(it, keyPath); }); }, indexByKeyPath : function (array, keyPath) { "use strict"; if (typeof keyPath !== "string") { return underscore.indexBy(array, keyPath); } return underscore.indexBy(array, function (it) { return underscore.valueForKeyPath(it, keyPath); }); }, countByKeyPath : function (array, keyPath) { "use strict"; if (typeof keyPath !== "string") { return underscore.countBy(array, keyPath); } return underscore.countBy(array, function (it) { return underscore.valueForKeyPath(it, keyPath); }); }, hasKeyPath : function (obj, keyPath) { "use strict"; var segments, last, target; if (obj === null || obj === undefined) { return false; } segments = toSegments(keyPath); last = segments.pop(); target = this.valueForKeyPath(obj, segments); if (target !== null && target !== undefined) { return hasProperty(target, last); } return false; }, eachByKeyPath : function (arr, keyPath, interceptor) { underscore.chain(arr) .pluckByKeyPath(keyPath) .each(interceptor); } }; aliases = { "getValueForKeyPath" : mixins.valueForKeyPath }; underscore.mixin(mixins); underscore.mixin(aliases); if (isNodeJS) { module.exports = underscore; module.exports.__debug__ = { toSegments : toSegments }; } }());