Greasy Fork is available in English.

object-utils

inheritance, mixins and other stuff, mainly to encapsulate objects

สคริปต์นี้ไม่ควรถูกติดตั้งโดยตรง มันเป็นคลังสำหรับสคริปต์อื่น ๆ เพื่อบรรจุด้วยคำสั่งเมทา // @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. })();