-
Notifications
You must be signed in to change notification settings - Fork 10
/
index.js
451 lines (398 loc) · 11.3 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
var enabledPrefixes = {}; // Only allow agave to be enabled once per prefix
// Extend objects with Agave methods, using the prefix provided.
var enable = function(prefix){
"use strict";
prefix = prefix || '';
if ( enabledPrefixes[prefix] ) {
return;
}
const MILLISECONDS_IN_SECOND = 1000,
SECONDS_IN_MINUTE = 60,
MINUTES_IN_HOUR = 60,
HOURS_IN_DAY = 24,
DAYS_IN_WEEK = 7,
DAYS_IN_MONTH = 30,
DAYS_IN_YEAR = 365;
const SUNDAY = 0,
MONDAY = 1,
TUESDAY = 2,
WEDNESDAY = 3,
THURSDAY = 4,
FRIDAY = 5,
SATURDAY = 6;
// object.getKeys() returns an array of keys
var getKeys = function(){
return Object.keys(this);
};
// object.getSize() returns the number of properties in the object
var getSize = function() {
return Object.keys(this).length;
};
// string.reverse()
var reverse = function() {
return this.split("").reverse().join("");
};
// string.leftStrip(stripChars) returns the string with the leading chars removed
var leftStrip = function(stripChars) {
var result = this;
while ( true ) {
// Note result could be zero characters
if ( ! stripChars.includes(result.charAt(0)) || ! result) {
return result;
} else {
result = result.slice(1);
}
}
};
// string.rightStrip(stripChars) returns the string with the trailing chars removed
var rightStrip = function(stripChars) {
return this[prefix+'reverse']()[prefix+'leftStrip'](stripChars)[prefix+'reverse']();
};
// string.strip(stripChars) returns the string with the leading and trailing chars removed
var strip = function(stripChars) {
return this[prefix+'leftStrip'](stripChars)[prefix+'rightStrip'](stripChars);
};
// object.getPath - get the value of the nested keys provided in the object.
// If any are missing, return undefined. Used for checking JSON results.
var getPath = function(pathItems) {
var currentObject = this;
var delim = '/';
var result;
var stillChecking = true;
// Handle Unix style paths
if ( typeof(pathItems) === 'string' ) {
pathItems = pathItems[prefix+'strip'](delim).split(delim);
}
pathItems.forEach( function(pathItem) {
if ( stillChecking ) {
if ( ( currentObject === null ) || ( ! currentObject.hasOwnProperty(pathItem) ) ) {
result = undefined;
stillChecking = false;
} else {
result = currentObject[pathItem];
currentObject = currentObject[pathItem];
}
}
});
return result;
};
// object.extent(object) adds the keys/values from the newObject provided
var objectExtend = function(newObject) {
for ( var key in newObject ) {
this[key] = newObject[key];
}
return this;
};
// Run after it hasn't been invoked for 'wait' ms.
// Useful to stop repeated calls to a function overlapping each other (sometimes called 'bouncing')
var throttle = function(wait, immediate) {
var timeoutID;
var originalFunction = this;
return function() {
var context = this;
var delayedFunction = function() {
timeoutID = null;
if ( ! immediate ) {
originalFunction.apply(context, arguments);
}
};
var callNow = immediate && ! timeoutID;
clearTimeout(timeoutID);
timeoutID = setTimeout(delayedFunction, wait);
if (callNow) {
originalFunction.apply(context, arguments);
}
};
};
// Run repeatedly
var functionRepeat = function(first, second, third){
var args, interval, leadingEdge;
if ( arguments.length === 2 ) {
args = [];
interval = first;
leadingEdge = second;
} else {
args = first;
interval = second;
leadingEdge = third;
}
if ( leadingEdge ) {
this.apply(null, args);
}
return setInterval(function(){
this.apply(null, args);
}.bind(this), interval);
};
// Extend an array with another array.
// Cleverness alert: since .apply() accepts an array of args, we use the newArray as all the args to push()
var arrayExtend = function(newArray) {
Array.prototype.push.apply(this, newArray);
return this;
};
// See https://stackoverflow.com/questions/37576685/using-async-await-with-a-foreach-loop
var forEachAsync = async function (iteratorFunction) {
for (let item of this) {
await iteratorFunction(item)
}
}
// string.toHash() return a hashed value of a string
// From http://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
var toHash = function(){
var hash = 0,
length = this.length,
char;
if ( ! length ) {
return hash;
}
for (var index = 0; index < length; index++) {
char = this.charCodeAt(index);
hash = ((hash<<5)-hash)+char;
hash |= 0; // Convert to 32bit integer
}
return hash;
};
// Clone an object recursively
var clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var key in this) {
if (this[key] && typeof this[key] == "object") {
newObj[key] = this[key][prefix+'clone']();
} else {
newObj[key] = this[key];
}
}
return newObj;
};
// compare an object with another object
var compare = function(otherObject){
var hashObject = function(object){
return JSON.stringify(object)[prefix+'toHash']();
};
return ( hashObject(this) === hashObject(otherObject) );
};
// Iterate over an objects keys
// Unlike a regular for ( var key in object )
// an additional scope is created, which avoids last-item looping probs
var objectForEach = function(callback){
for ( var key in this ) {
callback(key, this[key]);
}
};
var arrayClone = function(){
return this.slice();
};
// Array remove removes an item from an array, if it exists
var arrayRemove = function(member){
var index = this.indexOf(member);
if (index !== -1 ) {
this.splice(index, 1);
return true;
}
return false;
};
var arrayFirst= function(count){
if ( ! count ) {
return this[0];
} else {
return this.slice(Math.max(arr.length - count, 1));
}
};
var arrayLast = function(count){
if ( ! count ) {
return this[this.length - 1];
} else {
return this.slice(Math.max(this.length - count, 1));
}
};
// Helper function for before() and after()
var getTimeOrNow = function(date) {
return (date || new Date()).getTime();
};
// Return Number of seconds to time delta from date (or now if not specified)
var before = function(date) {
var time = getTimeOrNow(date);
return new Date(time-(+this));
};
// Return Number of seconds to time delta after date (or now if not specified)
var after = function(date) {
var time = getTimeOrNow(date);
return new Date(time+(+this));
};
var toSeconds = function() {
return this * MILLISECONDS_IN_SECOND;
}
var toMinutes = function() {
return this.seconds * SECONDS_IN_MINUTE;
}
var toHours = function() {
return this.minutes * MINUTES_IN_HOUR;
}
var toDays = function() {
return this.hours * HOURS_IN_DAY;
}
var toWeeks = function() {
return this.days * DAYS_IN_WEEK;
}
var toMonths = function() {
return this.days * DAYS_IN_MONTH;
}
var toYears = function() {
return this.days * DAYS_IN_YEAR;
}
var isOnWeekend = function(){
return this.getDay() === SUNDAY || this.getDay() === SATURDAY
}
var daysUntil = function(fakeNowDate){
var now = new Date() || fakeNowDate;
var difference = this.getTime() - now.getTime();
return Math.round(Math.abs(difference/1..day));
}
var withoutTime = function(){
var copy = new Date(this)
copy.setHours(0, 0, 0, 0, 0)
return copy;
}
var dateClone = function(){
return new Date(this.getTime())
}
var kind = function(item) {
var getPrototype = function(item) {
return Object.prototype.toString.call(item).slice(8, -1);
};
var kind, Undefined;
if (item === null ) {
kind = 'null';
} else {
if ( item === Undefined ) {
kind = 'undefined';
} else {
var prototype = getPrototype(item);
if ( ( prototype === 'Number' ) && isNaN(item) ) {
kind = 'NaN';
} else {
kind = prototype;
}
}
}
return kind;
};
// Polyfill if Element.prototype.matches doesn't exist.
var prefixedMatchesMethod = ( ! global.Element || Element.prototype.msMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.webkitMatchesSelector || Element.prototype.oMatchesSelector);
// Add method as a non-enumerable property on obj with the name methodName
var addMethod = function( global, objectName, prefix, methodName, method) {
var objectToExtend = global[objectName];
methodName = prefix ? prefix+methodName: methodName;
// Check - NodeLists and Elements don't always exist on all JS implementations
if ( objectToExtend ) {
// Don't add if the method already exists
if ( ! objectToExtend.prototype.hasOwnProperty(methodName) ) {
Object.defineProperty( objectToExtend.prototype, methodName, {
value: method,
writable: true
});
}
}
};
// There's not always a 1:1 match of functions to method names. Eg, some objects share methods,
// others re-use inbuilt methods from other objects.
var newMethods = {
'Array':{
'extend':arrayExtend,
'clone':arrayClone,
'remove':arrayRemove,
'first':arrayFirst,
'last':arrayLast,
forEachAsync
},
'Object':{
getKeys,
getSize,
getPath,
clone,
'forEach': objectForEach,
'extend': objectExtend,
compare
},
'String':{
reverse,
leftStrip,
rightStrip,
strip,
toHash,
'forEach':Array.prototype.forEach // Strings and NodeLists don't have .forEach() standard but the one from Array works fine
},
'Function':{
throttle,
'repeat': functionRepeat
},
'Number':{
before,
after
},
'Date':{
isOnWeekend,
withoutTime,
'clone': dateClone,
daysUntil,
'daysAgo': daysUntil
}
};
for ( var objectName in newMethods ) {
for ( var methodName in newMethods[objectName] ) {
addMethod(global, objectName, prefix, methodName, newMethods[objectName][methodName]);
}
}
// Add sttribute as a non-enumerable property on obj with the name methodName
var addNewAttribute = function( global, objectName, prefix, methodName, method) {
var objectToExtend = global[objectName];
methodName = prefix ? prefix+methodName: methodName;
// Check - NodeLists and Elements don't always exist on all JS implementations
if ( objectToExtend ) {
// Don't add if the method already exists
if ( ! objectToExtend.prototype.hasOwnProperty(methodName) ) {
Object.defineProperty( objectToExtend.prototype, methodName, {
get: method
});
}
}
};
var addTimeExtension = function(name, getterFunction){
Object.defineProperty(Number.prototype, name, {
get: getterFunction
})
}
var newAttributes = {
'Number':{
'second':toSeconds,
'seconds':toSeconds,
'minute':toMinutes,
'minutes':toMinutes,
'hour':toHours,
'hours':toHours,
'day':toDays,
'days':toDays,
'week':toWeeks,
'weeks':toWeeks,
'month':toMonths,
'months':toMonths,
'year':toYears,
'years':toYears
}
}
for ( var objectToGetNewAttribute in newAttributes ) {
for ( var attributeName in newAttributes[objectToGetNewAttribute] ) {
addNewAttribute(global, objectToGetNewAttribute, prefix, attributeName, newAttributes[objectToGetNewAttribute][attributeName]);
}
}
// Add a function to the global
var addGlobal = function( global, globalName, prefix, globalFunction) {
globalName = prefix ? prefix+globalName: globalName;
// Don't add if the global already exists
if ( ! global.hasOwnProperty(globalName) ) {
global[globalName] = globalFunction;
}
};
addGlobal(global, 'kind', prefix, kind);
enabledPrefixes[prefix] = true;
}.bind();
module.exports = enable;