object-utils

inheritance, mixins and other stuff, mainly to encapsulate objects

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/22752/146271/object-utils.js

  1. // ==UserScript==
  2. // @name object-utils
  3. // @name:de object-utils
  4. // @namespace dannysaurus.camamba
  5. // @version 0.1
  6. // @license MIT License
  7. // @description inheritance, mixins and other stuff, mainly to encapsulate objects
  8. // @description:de inheritance, mixins and other stuff, mainly to encapsulate objects
  9. // ==/UserScript==
  10.  
  11. var LIB = LIB || {};
  12. /**
  13. *
  14. * @type {{mixin, extend, TruthyFalsy, Truthy, Falsy, Keeper, BackingData, emptyFunction, emptyDate}}
  15. */
  16. LIB.objectUtils = (function() {
  17. 'use strict';
  18. /**
  19. * Extends an object from another object through the prototype chain
  20. * @param {Object} superObj - The object to be extended
  21. * @param {Object} obj - The object extending the superObj
  22. * @return {boolean} true, if executed successfully
  23. */
  24. function _extend(superObj, obj){
  25. if (typeof superObj === 'undefined') return false;
  26. if (typeof obj === 'undefined') return false;
  27. // save properties of prototype of the obj
  28. var descriptors = {};
  29. Object.getOwnPropertyNames(obj.prototype).forEach(function(propName) {
  30. descriptors[propName] = Object.getOwnPropertyDescriptor(obj.prototype, propName);
  31. });
  32. // create a new extended prototype
  33. obj.prototype = Object.create(superObj.prototype, descriptors);
  34. obj.prototype.constructor = obj;
  35. return true;
  36. }
  37. /**
  38. * Clones properties from one object to another.
  39. * A property only gets cloned if it does not yet exist in the target object.
  40. * @param {Object} receiver - the target object receiver the properties
  41. * @param {Object} supplier - the source object supplying the properties
  42. * @param {Array} [props] - names of the relevant properties
  43. * @return {boolean} true, if executed successfully
  44. */
  45. function _mixin(receiver, supplier, props) {
  46. if (typeof supplier !== 'object' || typeof receiver !== 'object') {
  47. return false;
  48. }
  49. var propNames = Array.isArray(props) ? props : Object.getOwnPropertyNames(supplier);
  50. propNames.forEach(function(propName) {
  51. if (!Object.prototype.hasOwnProperty.call(receiver, propName)) {
  52. var desc = Object.getOwnPropertyDescriptor(supplier, propName);
  53. if (typeof desc !== 'undefined') {
  54. Object.defineProperty(receiver, propName, desc);
  55. }
  56. }
  57. });
  58. return true;
  59. }
  60.  
  61. /**
  62. * Has an object extending a super object by their prototype objects.
  63. * @param {Object} obj - The extending object
  64. * @return {{from: extendFrom, mixWith: mixWith}}
  65. */
  66. var extend = function(obj) {
  67. /**
  68. * Clones the prototype properties of the super object in the target object if the property does not yet exist there.
  69. * @param {Object} superObj - the source object supplying the properties
  70. * @param {Array} [props] - names of the relevant properties
  71. * @return {{thenWith: mixWith}}
  72. */
  73. var mixWith = function(superObj, props) {
  74. if (_mixin(obj.prototype, superObj.prototype, props)){
  75. return {
  76. thenWith : mixWith
  77. };
  78. }
  79. };
  80. /**
  81. * Extends the super object from the prototype chain
  82. * @param superObj The super object to be extended
  83. * @return {{mixWith: mixWith}}
  84. */
  85. var extendFrom = function (superObj) {
  86. if (_extend(superObj, obj)) {
  87. return {
  88. mixWith : mixWith
  89. };
  90. }
  91. };
  92.  
  93. return {
  94. from : extendFrom,
  95. mixWith : mixWith
  96. };
  97. };
  98.  
  99. /**
  100. * Defines an object that holds a value and specific values.
  101. * In case the value is a Truthy, Falsy or undefined it is overwritten with specific values.
  102. * @param value The common value.
  103. * @param [ifTruthy] if not undefined it overwrites value when it is a Truthy
  104. * @param [ifFalsy] if not undefined it overwrites value when it is a Falsy
  105. * @param [ifUndefined] if not undefined it overwrites value when it is undefined
  106. * @returns {TruthyFalsy}
  107. * @constructor
  108. */
  109. function TruthyFalsy(value, ifTruthy, ifFalsy, ifUndefined) {
  110. if (!this instanceof TruthyFalsy) {
  111. return new TruthyFalsy(value, ifTruthy, ifFalsy, ifUndefined);
  112. }
  113. this.value = value;
  114. if (ifTruthy !== 'undefined') { this.ifTruthy = ifTruthy; }
  115. if (ifFalsy !== 'undefined') { this.ifFalsy = ifFalsy; }
  116. if (ifUndefined !== 'undefined') { this.ifUndefined = ifUndefined; }
  117. }
  118. TruthyFalsy.prototype = {
  119. constructor : TruthyFalsy,
  120. valueOf: function() {
  121. var result = this.value;
  122. if (typeof this.ifUndefined !== "undefined" && typeof result === "undefined") {
  123. result = this.ifUndefined;
  124. } else if (typeof this.ifTruthy !== "undefined" && result) {
  125. result = this.ifTruthy;
  126. } else if (typeof this.ifFalsy !== "undefined" && result) {
  127. result = this.ifFalsy;
  128. }
  129. return result;
  130. },
  131. toString: function() {
  132. return String(this.valueOf());
  133. }
  134. };
  135.  
  136. function Falsy(value, ifFalsy, ifUndefined) {
  137. if (!(this instanceof Falsy)) {
  138. return new Falsy(value, ifFalsy, ifUndefined);
  139. }
  140. TruthyFalsy.call(this, value, undefined, ifFalsy, ifUndefined);
  141. }
  142. extend(Falsy).from(TruthyFalsy);
  143.  
  144. function Truthy(value, ifTruthy, ifUndefined) {
  145. if (!(this instanceof Truthy)) {
  146. return new Truthy(value, ifTruthy, ifUndefined);
  147. }
  148. TruthyFalsy.call(this, value, ifTruthy, undefined, ifUndefined);
  149. }
  150. extend(Truthy).from(TruthyFalsy);
  151.  
  152. /**
  153. * Keeps elements assigned by an index
  154. * @param {number} [initialCapacity] - initial size of the array used to store the elements
  155. * @constructor
  156. */
  157. function Keeper(initialCapacity) {
  158. if (!(this instanceof Keeper)) {
  159. return new Keeper(initialCapacity);
  160. }
  161. var _store = new Array(initialCapacity || 16);
  162. var _pointer = 0;
  163. Object.defineProperties(this, {
  164. store: {
  165. get: function() { return _store },
  166. configurable: false, enumerable: false
  167. },
  168. pointer: {
  169. get: function() { return _pointer },
  170. configurable: false, enumerable: false
  171. }
  172. })
  173. }
  174. Keeper.prototype = {
  175. constructor: Keeper,
  176. /**
  177. * Adds a new element
  178. * @param {*|Object} item - The element to keep. Must not be type of <code>undefined</code>.
  179. * @return {number} The index (key) of the added element.<br>
  180. * <code>-1</code> if the element could not be added.
  181. */
  182. push: function(item) {
  183. if (typeof item !== 'undefined') {
  184. var index = this.pointer;
  185. this.store[index] = item;
  186. for (this.pointer = index + 1; this.pointer <= this.store.length; this.pointer++) {
  187. if (typeof this.store[pointer] === 'undefined') { break; }
  188. }
  189. if (this.pointer === this.store.length) {
  190. this.store.length *= 2;
  191. }
  192. return index;
  193. }
  194. return -1;
  195. },
  196. /**
  197. * Removes an element.
  198. * @param index The index (key) of the element to be removed.
  199. * @return {boolean} <code>true</code> if the element could be removed successfully
  200. */
  201. remove: function(index) {
  202. if (index + 1 >= this.store.length) {
  203. this.store[index] = undefined;
  204. this.pointer = index;
  205. return true;
  206. }
  207. return false;
  208. },
  209. /**
  210. * Gets the element with the specified index
  211. * @param {number} index - Index (key) of the element
  212. * @return {*|Object} the element with the specified index or <code>undefined</code>
  213. */
  214. get: function (index) {
  215. if (index + 1 <= this.store.length) {
  216. return this.store[index];
  217. }
  218. return undefined;
  219. }
  220. };
  221.  
  222. /**
  223. * This callback is displayed as part of the Requester class.
  224. * @callback OnSetCallback
  225. * @param {*} oldValue - old value
  226. * @param {*} newValue - new value to be set
  227. */
  228.  
  229. /**
  230. * Defines an accessor properties
  231. * @param {Object} sourceNode - the source object with the defining property
  232. * @param {Object} targetNode - the target object to define the accessor property
  233. * @param {string} propertyName - the name of the property which must exist on the <code>sourceNode</code>
  234. * @param {OnSetCallback} [onSetCallback] - called when the property gets assigned a value on the <code>targetNode</code>
  235. */
  236. var definePropRecursive = function(sourceNode, targetNode, propertyName, onSetCallback) {
  237. if (typeof onSetCallback !== "function") {
  238. onSetCallback = emptyFunction;
  239. }
  240. var srcProperty = sourceNode[propertyName];
  241. var isNode = (srcProperty !== null && typeof srcProperty === 'object' && Object.getPrototypeOf(srcProperty) === Object.prototype);
  242. if (isNode) {
  243. var childrenPropertyNames = Object.keys(srcProperty);
  244. var targetProperty = targetNode[propertyName] = {};
  245. childrenPropertyNames.forEach(function(childPropName) {
  246. definePropRecursive(srcProperty, targetProperty, childPropName, onSetCallback);
  247. });
  248. } else {
  249. Object.defineProperty(targetNode, propertyName, {
  250. enumerable: true,
  251. configurable: true,
  252. get: function() { return sourceNode[propertyName]; },
  253. set: function(value) {
  254. onSetCallback(sourceNode[propertyName], value);
  255. sourceNode[propertyName] = value;
  256. }
  257. });
  258. }
  259. };
  260.  
  261. /**
  262. * Backing data that can be stored in the userscript database.
  263. * intially defines getter and setter on the front
  264. * @param {Object} frontObject - the object to define the properties on according to <code>dataObject</code>
  265. * @param {Object} dataObject - the Object defining the backing data structure and initial values
  266. * @param {string} dbKey - key for storing the data in the userscript db
  267. * @param {OnSetCallback} [onSet] - function called when a property of <code>dataObject</code> gets assigned a value through the <code>frontObject</code>
  268. * @returns {BackingData}
  269. * @constructor
  270. */
  271. function BackingData(frontObject, dataObject, dbKey, onSet) {
  272. if (!(this instanceof BackingData)) {
  273. return new BackingData(frontObject, dataObject, dbKey, onSet);
  274. }
  275. _mixin(this, dataObject);
  276. Object.defineProperties(this, {
  277. dbKey: { enumerable: false, configurable: true, writable: true, value: dbKey },
  278. frontObject: { enumerable: false, configurable: true, writable: true, value: frontObject },
  279. onSet: { enumerable: false, configurable: true, writable: true, value: onSet }
  280. });
  281. Object.keys(this).forEach(function(propName) {
  282. definePropRecursive(this, frontObject, propName, onSet);
  283. }, this);
  284. }
  285. BackingData.prototype = {
  286. constructor: BackingData,
  287. load: function() {
  288. var loadedObjSerialized = GM_getValue(this.dbKey);
  289. if (typeof loadedObjSerialized === "undefined") {
  290. return false;
  291. }
  292.  
  293. var loadedObj = JSON.parse(loadedObjSerialized);
  294. Object.keys(this).forEach(function(propName) {
  295. var prop = loadedObj[propName];
  296. if (typeof prop !== "undefined") {
  297. this[propName] = prop;
  298. }
  299. }, this);
  300. Object.keys(this).forEach(function(propName) {
  301. definePropRecursive(this, this.frontObject, propName, this.onSet);
  302. }, this);
  303. return true;
  304. },
  305. save: function() {
  306. var savingObj = {};
  307. _mixin(savingObj, this);
  308. var savingObjSerialized = JSON.stringify(savingObj);
  309. GM_setValue(this.dbKey, savingObjSerialized);
  310. },
  311. remove: function() {
  312. GM_deleteValue(this.dbKey);
  313. }
  314. };
  315.  
  316. /** placeholder for empty callbacks */
  317. function emptyFunction() {}
  318. /** placeholder for an undefined date */
  319. var emptyDate = new Date('invalid');
  320.  
  321. return {
  322. mixin: _mixin,
  323. extend: extend,
  324. TruthyFalsy: TruthyFalsy,
  325. Truthy: Truthy,
  326. Falsy: Falsy,
  327. Keeper: Keeper,
  328. BackingData: BackingData,
  329. get emptyFunction() { return emptyFunction },
  330. get emptyDate() { return emptyDate }
  331. };
  332. })();