-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbase.js
427 lines (366 loc) · 12.3 KB
/
base.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
;
(function($, window, document, undefined) {
$.widget('mondo.base', {
// save some memray
'$window': $(window),
'$html': $('html'),
'$body': $(document.body),
// used for some things like animating scrollTop in FF
'$dom': $('html, body'),
// options function just like normal $ plugin options
// properties here will be read from the dom later (in getOptionsFromData)
// so anything here is considered a default option
'options': {},
// list of external event calls that will be received
// (these are triggered by other widgets or whatever,
// this is just an easy way to map handlers to these events)
// event name : name of handling function
'events': {},
// since jquery ui is weird and decides to make static members out
// of things that look like instance variables, we wrap them in this object
'instanceVars': function() {
return {};
},
// each widget has a unique id, supplied later in generateUniqueID
'id': '',
// utility property to determine if the user is on a touch-capable device
'hasTouch': 'ontouchstart' in window,
// defines the element that triggers/listens for global events
// (this is where the "global event space" is contained)
'$global': $(document.body),
// flag for dealing with sessionStorage stuff
// (in case cookies are disabled etc)
'hasStorage': false,
/**
* Constructor. Runs on widget instantiation.
* Options are passed in via widget constructor function, which
* becomes this.options.
*
* @return {void}
*/
'_create': function() {
var widget = this;
// mm chaining
widget
// set up the private instance vars
.createInstanceVariables()
// set the widget's id
.generateUniqueID()
// read the dom for any overridden options
.getOptionsFromData()
// bind the events that others may trigger on this widget
.bindIncomingEvents()
// determine capabilities
.determineCapabilities()
// refresh to basically init stuff
.refresh();
return widget;
},
'createInstanceVariables': function() {
var widget = this,
instanceVars = (typeof widget.instanceVars === 'function' ? widget.instanceVars() : widget.instanceVars),
varName, initValue;
for (varName in instanceVars) {
if (instanceVars.hasOwnProperty(varName)) {
// if this property is already set, don't set it to the init val
if (!widget.hasOwnProperty(varName)) {
widget[varName] = instanceVars[varName];
}
}
}
return widget;
},
/**
* Utilizes JQuery UI's uniqueId function to establish a unique widget ID
* @return {String}
*/
'generateUniqueID': function(get) {
var widget = this,
$el = widget.element;
// wish this just returned the string, but instead it sets this element's
// ID attribute
$el.uniqueId && $el.uniqueId();
widget.id = $el.attr('id') || '' + Math.round(Math.random() * 1000) + (+new Date());
return (get ? widget.id : widget);
},
/**
* Generates a string to use for un/binding events on an element
* (Utilizes $'s event namespacing - 'click' becomes 'click.this-widget-id')
* This allows us to more precisely target events that we're binding/unbinding.
* Also, we can alter events based on device capabilities (e.g. click -> tap)
* @param {string} eventName Event name to return
* @return {string} Widget-specific event ID
*/
'generateUniqueEvent': function(eventName) {
var widget = this,
id = widget.id,
eventArray = eventName.split(' '),
returnEvent = '',
i;
for (i = 0; i < eventArray.length; i++) {
// convert clicks to taps
if (widget.hasTouch) {
switch (eventArray[i]) {
// case 'click':
// in a perfect world this would work
// eventArray[i] = 'tap';
// break;
case 'mousedown':
eventArray[i] = 'touchstart';
break;
case 'mouseup':
eventArray[i] = 'touchend';
break;
case 'mousemove':
eventArray[i] = 'touchmove';
break;
}
}
// we need to space out events if there are multiples
if (i > 0) {
returnEvent += ' ';
}
// actually generate the [event].[id] string
returnEvent += (eventArray[i] + '.' + id);
}
// return the string that's used to unbind/bind via $
return returnEvent;
},
/**
* Binds any event handler definitions described in this widget's events object
* Maps incoming events to functions already existing on the widget
* @return {void}
*/
'bindIncomingEvents': function() {
var widget = this,
events = widget.events,
$el = widget.element,
eventHandler,
listener;
for (listener in events) {
// if the widget has the defined handler..
if (!!widget[events[listener]]) {
$el.unbind(listener).on(listener, widget[events[listener]].bind(widget));
}
}
return widget;
},
/**
* Reads data- attributes from the widget element and overrides any default
* options with the found values.
* @return {void}
*/
'getOptionsFromData': function() {
var widget = this,
$el = widget.element,
prop,
options = {};
// $ already data()'d our data, which is cool, since it also parses values for us
for (prop in $el.data()) {
if (widget.options.hasOwnProperty(prop)) {
options[prop] = $el.data(prop);
}
}
// update the widget options with whatever was found + the default values
widget._setOptions(options);
return widget;
},
/**
* Setter for widget options
* Allows manipulation of values (e.g. formatting or capping) before
* setting widget property values
* @param {String} key Property name
* @param {any} value Property value
*/
'_setOption': function(key, value) {
var widget = this;
widget._super(key, value);
return widget;
},
/**
* Setter for multiple widget options
* @param {Object} options Hashmap of options to set on the widget
*/
'_setOptions': function(options) {
var widget = this;
widget._super(options);
widget.refresh();
return widget;
},
/**
* 'Re-init' function. Used when widget constructor is called on
* an element that already has it. Basically a straight refresh.
* @return {[type]} [description]
*/
'refresh': function() {
var widget = this;
return widget;
},
/**
* Widget delete function.
* Removes bindings, cleans stuff up, etc.
* @return {void}
*/
'_destroy': function() {
var widget = this;
widget.element.unbind();
return widget;
},
/**
* Utility function to create an object with relevant emission event data
* @param {string} eventName Event type to generate data for
* @return {object} Compiled information
*/
'generateEmitData': function(eventName) {
var widget = this;
return {
'widget': widget,
'element': widget.element,
'event': eventName,
'time': Date.now()
};
},
/**
* Function to bind an event on the global namespace.
* Prepends `global:` to the requested event name, and attaches
* handlers (multiple or singular) to that event
*
* @param {string} eventName Global event to bind
* @param {function} eventHandler Handling function to fire
* @param {boolean} preventMultiple Optional, force only this handler to exist for this event?
* @return {widget} this
*/
'bindGlobal': function(eventName, eventHandler, preventMultiple) {
// no event = no sale
if (!eventName || eventName === '' || !eventHandler || typeof eventHandler !== 'function') {
return widget;
}
// ensure global things are namespaced accordingly
if (eventName.indexOf('global:') < 0) {
eventName = 'global:' + eventName;
}
var widget = this,
eventID = widget.generateUniqueEvent(eventName);
// force only one to fire
if (preventMultiple) {
widget.$global.unbind(eventID);
}
// bind the actual global object with the event + handler
widget.$global.on(eventID, eventHandler);
return widget;
},
/**
* Alias for `emit(..., ..., true)`
* Fires an event on the global namespace
*
* @param {string} eventName Global event name to trigger
* @param {object} eventData Relevant event data to emit
* @return {widget} this
*/
'emitGlobal': function(eventName, eventData) {
var widget = this;
return widget.emit(eventName, eventData, true);
},
/**
* Custom event trigger function.
* Pre-populates some event data along with any addt'l info passed in
* @param {String} eventName Event name to trigger
* @param {Object} eventData Event-related info to pass with trigger
* @return {widget} this
*/
'emit': function(eventName, eventData, emitOnGlobal) {
var widget = this,
// default data passed with every widget event
emittedData = widget.generateEmitData(eventName),
// loop var
prop;
// replace any defaults with what was passed in,
// else any extra values are just added on
if (eventData) {
emittedData.eventData = eventData;
for (prop in eventData) {
emittedData[prop] = eventData[prop];
}
}
// determine if we need to do some global jiggery-pokery
if (emitOnGlobal) {
// ensure global things are namespaced accordingly
if (eventName.indexOf('global:') < 0) {
eventName = 'global:' + eventName;
}
widget.$global.trigger(eventName, emittedData);
} else {
// else we just trigger on this widget
widget.element.trigger(eventName, emittedData);
}
return widget;
},
/**
* Uniquely binds an event to provided element.
* Used primarily for functions that live in the `refresh` function,
* usually to find new elements and bind them as they enter the page.
*
* @param {jQuery} $el Element to bind
* @param {string} event Event to bind
* @param {function} handler Handling function
* @return {widget} this
*/
'safeBind': function($el, event, handler) {
var widget = this,
eventName = widget.generateUniqueEvent(event);
if (!($el instanceof $)) {
$el = $($el);
}
$el.unbind(eventName).on(eventName, handler);
return widget;
},
/**
* Unbinds an event from provided element,
* using `generateUniqueEvent` to provide the correct namespace.
*
* @param {jQuery} $el Element to unbind
* @param {string} event Event name to unbind
* @return {widget} This
*/
'unbind': function($el, event) {
var widget = this,
eventName = widget.generateUniqueEvent(event);
if (!($el instanceof $)) {
$el = $($el);
}
$el.unbind(eventName);
return widget;
},
/**
* Capability check to determine various function availability
* Currently checks for `sessionStorage` support
*
* @return {widget} this
*/
'determineCapabilities': function() {
var widget = this;
widget.hasStorage = widget._sessionStorageCheck();
return widget;
},
/**
* Test to determine if sessionStorage is available,
* without breaking permissions etc
* @return {boolean} Is sessionStorage available and usable?
*/
'_sessionStorageCheck': function() {
var widget = this;
try {
var tst = 'test-ignore';
if (window.sessionStorage) {
window.sessionStorage.setItem(tst, tst);
window.sessionStorage.getItem(tst);
return true;
} else {
return false;
}
} catch (err) {
return false;
}
}
});
})(jQuery, window, document);