romo 0.19.10 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,16 +1,12 @@
1
1
  var Romo = function() {
2
- this._eventCallbacks = [];
2
+ this.popupStack = new RomoPopupStack();
3
+ this.parentChildElems = new RomoParentChildElems();
3
4
  }
4
5
 
5
- // TODO: rework w/o jQuery
6
6
  Romo.prototype.doInit = function() {
7
- this.parentChildElems = new RomoParentChildElems();
8
-
9
- $.each(this._eventCallbacks, function(idx, eventCallback) {
10
- $('body').on(eventCallback.eventName, eventCallback.callback);
11
- });
12
-
13
- this.triggerInitUI($('body'));
7
+ this.popupStack.doInit();
8
+ this.parentChildElems.doInit();
9
+ this.initElems(Romo.f('body'));
14
10
  }
15
11
 
16
12
  // element finders
@@ -19,42 +15,78 @@ Romo.prototype.f = function(selector) {
19
15
  return this.array(document.querySelectorAll(selector));
20
16
  }
21
17
 
22
- Romo.prototype.find = function(parentElem, selector) {
23
- return this.array(parentElem.querySelectorAll(selector));
18
+ Romo.prototype.find = function(parentElems, selector) {
19
+ return this.array(parentElems).reduce(
20
+ Romo.proxy(function(foundElems, parentElem) {
21
+ return foundElems.concat(this.array(parentElem.querySelectorAll(selector)));
22
+ }, this),
23
+ []
24
+ );
24
25
  }
25
26
 
26
- Romo.prototype.children = function(parentElem) {
27
- return this.array(parentElem.children);
27
+ Romo.prototype.is = function(elem, selector) {
28
+ return (
29
+ elem.matches ||
30
+ elem.matchesSelector ||
31
+ elem.msMatchesSelector ||
32
+ elem.mozMatchesSelector ||
33
+ elem.webkitMatchesSelector ||
34
+ elem.oMatchesSelector
35
+ ).call(elem, selector);
36
+ };
37
+
38
+ Romo.prototype.children = function(parentElem, selector) {
39
+ var childElems = this.array(parentElem.children);
40
+ if (selector) {
41
+ return childElems.filter(function(childElem) {
42
+ return Romo.is(childElem, selector);
43
+ });
44
+ } else {
45
+ return childElems;
46
+ }
28
47
  }
29
48
 
30
49
  Romo.prototype.parent = function(childElem) {
31
50
  return childElem.parentNode;
32
51
  }
33
52
 
34
- Romo.prototype.siblings = function(elem) {
35
- return Array.prototype.filter.call(elem.parentNode.children, function(childElem) {
36
- return childElem !== elem;
37
- });
38
- }
39
-
40
- Romo.prototype.prev = function(fromElem) {
41
- return fromElem.previousElementSibling;
53
+ Romo.prototype.parents = function(childElem, selector) {
54
+ var parentElem = this.parent(childElem);
55
+ if (parentElem && parentElem !== document) {
56
+ if (!selector || Romo.is(parentElem, selector)) {
57
+ if (Romo.is(parentElem, 'body')) {
58
+ return [parentElem];
59
+ } else {
60
+ return [parentElem].concat(this.parents(parentElem, selector));
61
+ }
62
+ } else {
63
+ if (Romo.is(parentElem, 'body')) {
64
+ return [];
65
+ } else {
66
+ return this.parents(parentElem, selector);
67
+ }
68
+ }
69
+ } else {
70
+ return [];
71
+ }
42
72
  }
43
73
 
44
- Romo.prototype.next = function(fromElem) {
45
- return fromElem.nextElementSibling;
74
+ Romo.prototype.scrollableParents = function(childElem, selector) {
75
+ return Romo.parents(childElem, selector).filter(function(parentElem) {
76
+ return (
77
+ Romo._overflowScrollableRegex.test(Romo.css(parentElem, 'overflow')) ||
78
+ Romo._overflowScrollableRegex.test(Romo.css(parentElem, 'overflow-y')) ||
79
+ Romo._overflowScrollableRegex.test(Romo.css(parentElem, 'overflow-x'))
80
+ );
81
+ });
46
82
  }
47
83
 
48
84
  Romo.prototype.closest = function(fromElem, selector) {
49
85
  if (fromElem.closest) {
50
86
  return fromElem.closest(selector);
51
87
  } else {
52
- var matchesSelector = fromElem.matches ||
53
- fromElem.webkitMatchesSelector ||
54
- fromElem.mozMatchesSelector ||
55
- fromElem.msMatchesSelector;
56
88
  while (fromElem) {
57
- if (matchesSelector.call(fromElem, selector)) {
89
+ if (Romo.is(fromElem, selector)) {
58
90
  return fromElem;
59
91
  } else {
60
92
  fromElem = fromElem.parentElement;
@@ -64,80 +96,133 @@ Romo.prototype.closest = function(fromElem, selector) {
64
96
  }
65
97
  }
66
98
 
67
- // TODO: rework w/o jQuery
68
- Romo.prototype.selectNext = function(elem, selector) {
69
- // like `$().next()`, but takes a selector; returns first next elem that
70
- // matches the given selector or an empty collection if non matches
71
- var el = elem.next();
72
- while(el.length) {
73
- if (selector === undefined || el.is(selector)) {
74
- return el;
99
+ Romo.prototype.siblings = function(elem, selector) {
100
+ return Romo.children(Romo.parent(elem), selector).filter(function(childElem) {
101
+ return childElem !== elem;
102
+ });
103
+ }
104
+
105
+ Romo.prototype.prev = function(fromElem, selector) {
106
+ var elem = fromElem.previousElementSibling;
107
+ if (elem === null) {
108
+ elem = undefined;
109
+ }
110
+
111
+ while(elem) {
112
+ if (!selector || Romo.is(elem, selector)) {
113
+ return elem;
114
+ }
115
+ elem = elem.previousElementSibling;
116
+ if (elem === null) {
117
+ elem = undefined;
75
118
  }
76
- el = el.next();
77
119
  }
78
- return el;
120
+ return elem;
79
121
  }
80
122
 
81
- // TODO: rework w/o jQuery
82
- Romo.prototype.selectPrev = function(elem, selector) {
83
- // like `$().prev()`, but takes a selector; returns first prev elem that
84
- // matches the given selector or an empty collection if non matches
85
- var el = elem.prev();
86
- while(el.length) {
87
- if (selector === undefined || el.is(selector)) {
88
- return el;
123
+ Romo.prototype.next = function(fromElem, selector) {
124
+ var elem = fromElem.nextElementSibling;
125
+ if (elem === null) {
126
+ elem = undefined;
127
+ }
128
+
129
+ while(elem) {
130
+ if (!selector || Romo.is(elem, selector)) {
131
+ return elem;
132
+ }
133
+ elem = elem.nextElementSibling;
134
+ if (elem === null) {
135
+ elem = undefined;
89
136
  }
90
- el = el.prev();
91
137
  }
92
- return el;
138
+ return elem;
93
139
  }
94
140
 
95
141
  // attributes, styles, classes
96
142
 
97
143
  Romo.prototype.attr = function(elem, attrName) {
98
- return elem.getAttribute ? elem.getAttribute(attrName) : undefined;
144
+ var a = elem.getAttribute(attrName);
145
+ if (a === null) {
146
+ return undefined;
147
+ } else {
148
+ return a;
149
+ }
99
150
  }
100
151
 
101
- Romo.prototype.setAttr = function(elem, attrName, attrValue) {
102
- if (elem.setAttribute) {
103
- elem.setAttribute(attrName, attrValue);
104
- }
105
- return attrValue;
152
+ Romo.prototype.setAttr = function(elems, attrName, attrValue) {
153
+ var v = String(attrValue);
154
+ Romo.array(elems).forEach(function(elem) {
155
+ elem.setAttribute(attrName, v);
156
+ });
157
+ return v;
158
+ }
159
+
160
+ Romo.prototype.rmAttr = function(elems, attrName) {
161
+ Romo.array(elems).forEach(function(elem) {
162
+ elem.removeAttribute(attrName);
163
+ });
106
164
  }
107
165
 
108
166
  Romo.prototype.data = function(elem, dataName) {
109
167
  return this._deserializeValue(this.attr(elem, "data-"+dataName));
110
168
  }
111
169
 
112
- Romo.prototype.setData = function(elem, dataName, dataValue) {
113
- return this.setAttr(elem, "data-"+dataName, String(dataValue));
170
+ Romo.prototype.setData = function(elems, dataName, dataValue) {
171
+ return this.setAttr(elems, "data-"+dataName, dataValue);
172
+ }
173
+
174
+ Romo.prototype.rmData = function(elems, dataName) {
175
+ this.rmAttr(elems, "data-"+dataName);
114
176
  }
115
177
 
116
178
  Romo.prototype.style = function(elem, styleName) {
117
179
  return elem.style[styleName];
118
180
  }
119
181
 
120
- Romo.prototype.setStyle = function(elem, styleName, styleValue) {
121
- elem.style[styleName] = styleValue;
182
+ Romo.prototype.setStyle = function(elems, styleName, styleValue) {
183
+ Romo.array(elems).forEach(function(elem) {
184
+ elem.style[styleName] = styleValue;
185
+ });
122
186
  return styleValue;
123
187
  }
124
188
 
189
+ Romo.prototype.rmStyle = function(elems, styleName) {
190
+ Romo.array(elems).forEach(function(elem) {
191
+ elem.style[styleName] = '';
192
+ });
193
+ }
194
+
125
195
  Romo.prototype.css = function(elem, styleName) {
126
196
  return window.getComputedStyle(elem, null).getPropertyValue(styleName);
127
197
  }
128
198
 
129
- Romo.prototype.addClass = function(elem, className) {
130
- elem.classList.add(className);
199
+ Romo.prototype.addClass = function(elems, className) {
200
+ var classNames = className.split(' ').filter(function(n){ return n; });
201
+ Romo.array(elems).forEach(function(elem) {
202
+ classNames.forEach(function(name) {
203
+ elem.classList.add(name);
204
+ });
205
+ });
131
206
  return className;
132
207
  }
133
208
 
134
- Romo.prototype.removeClass = function(elem, className) {
135
- elem.classList.remove(className);
209
+ Romo.prototype.removeClass = function(elems, className) {
210
+ var classNames = className.split(' ').filter(function(n){ return n; });
211
+ Romo.array(elems).forEach(function(elem) {
212
+ classNames.forEach(function(name) {
213
+ elem.classList.remove(name);
214
+ });
215
+ });
136
216
  return className;
137
217
  }
138
218
 
139
- Romo.prototype.toggleClass = function(elem, className) {
140
- elem.classList.toggle(className);
219
+ Romo.prototype.toggleClass = function(elems, className) {
220
+ var classNames = className.split(' ').filter(function(n){ return n; });
221
+ Romo.array(elems).forEach(function(elem) {
222
+ classNames.forEach(function(name) {
223
+ elem.classList.toggle(name);
224
+ });
225
+ });
141
226
  return className;
142
227
  }
143
228
 
@@ -145,19 +230,24 @@ Romo.prototype.hasClass = function(elem, className) {
145
230
  return elem.classList.contains(className);
146
231
  }
147
232
 
148
- Romo.prototype.show = function(elem) {
149
- elem.style.display = '';
233
+ Romo.prototype.show = function(elems) {
234
+ Romo.array(elems).forEach(function(elem) {
235
+ elem.style.display = '';
236
+ });
150
237
  }
151
238
 
152
- Romo.prototype.hide = function(elem) {
153
- elem.style.display = 'none';
239
+ Romo.prototype.hide = function(elems) {
240
+ Romo.array(elems).forEach(function(elem) {
241
+ elem.style.display = 'none';
242
+ });
154
243
  }
155
244
 
156
245
  Romo.prototype.offset = function(elem) {
157
- var rect = elem.getBoundingClientRect();
246
+ var elemRect = elem.getBoundingClientRect();
247
+ var bodyRect = document.body.getBoundingClientRect();
158
248
  return {
159
- top: rect.top + document.body.scrollTop,
160
- left: rect.left + document.body.scrollLeft
249
+ top: elemRect.top - bodyRect.top,
250
+ left: elemRect.left - bodyRect.left
161
251
  };
162
252
  }
163
253
 
@@ -169,28 +259,26 @@ Romo.prototype.scrollLeft = function(elem) {
169
259
  return ('scrollLeft' in elem) ? elem.scrollLeft : elem.pageXOffset;
170
260
  }
171
261
 
172
- Romo.prototype.setScrollTop = function(elem, value) {
173
- if ('scrollTop' in elem) {
174
- elem.scrollTop = value;
175
- } else {
176
- elem.scrollTo(elem.scrollX, value);
177
- }
178
- }
179
-
180
- Romo.prototype.setScrollLeft = function(elem, value) {
181
- if ('scrollLeft' in elem) {
182
- elem.scrollLeft = value;
183
- } else {
184
- elem.scrollTo(value, elem.scrollY);
185
- }
262
+ Romo.prototype.setScrollTop = function(elems, value) {
263
+ Romo.array(elems).forEach(function(elem) {
264
+ if ('scrollTop' in elem) {
265
+ elem.scrollTop = value;
266
+ } else {
267
+ elem.scrollTo(elem.scrollX, value);
268
+ }
269
+ });
186
270
  }
187
271
 
188
- // TODO: rework w/o jQuery
189
- Romo.prototype.getComputedStyle = function(node, styleName) {
190
- return window.getComputedStyle(node, null).getPropertyValue(styleName);
272
+ Romo.prototype.setScrollLeft = function(elems, value) {
273
+ Romo.array(elems).forEach(function(elem) {
274
+ if ('scrollLeft' in elem) {
275
+ elem.scrollLeft = value;
276
+ } else {
277
+ elem.scrollTo(value, elem.scrollY);
278
+ }
279
+ });
191
280
  }
192
281
 
193
- // TODO: rework w/o jQuery
194
282
  Romo.prototype.parseZIndex = function(elem) {
195
283
  // for the case where z-index is set directly on the elem
196
284
  var val = this.parseElemZIndex(elem);
@@ -199,27 +287,26 @@ Romo.prototype.parseZIndex = function(elem) {
199
287
  }
200
288
 
201
289
  // for the case where z-index is inherited from a parent elem
202
- var parentIndexes = this.toArray(elem.parents()).reduce($.proxy(function(prev, curr) {
203
- var pval = this.parseElemZIndex($(curr));
204
- if (pval !== 0) {
205
- prev.push(pval);
290
+ var pval = 0;
291
+ var parentIndexes = Romo.parents(elem).forEach(Romo.proxy(function(parentElem) {
292
+ if (pval === 0) {
293
+ pval = this.parseElemZIndex(parentElem);
206
294
  }
207
- return prev;
208
- }, this), []);
209
- parentIndexes.push(0); // in case z-index is 'auto' all the way up
210
- return parentIndexes[0];
295
+ }, this));
296
+
297
+ // z-index is 'auto' all the way up
298
+ return pval;
211
299
  }
212
300
 
213
- // TODO: rework w/o jQuery
214
301
  Romo.prototype.parseElemZIndex = function(elem) {
215
- var val = parseInt(this.getComputedStyle(elem[0], "z-index"));
302
+ var val = parseInt(this.css(elem, "z-index"), 10);
216
303
  if (!isNaN(val)) {
217
304
  return val;
218
305
  }
219
306
  return 0;
220
307
  }
221
308
 
222
- // DOM manipulation
309
+ // elems init
223
310
 
224
311
  Romo.prototype.elems = function(htmlString) {
225
312
  var context = document.implementation.createHTMLDocument();
@@ -230,12 +317,46 @@ Romo.prototype.elems = function(htmlString) {
230
317
  base.href = document.location.href;
231
318
  context.head.appendChild(base);
232
319
 
233
- context.body.innerHTML = htmlString;
234
- return this.array(context.body.children);
320
+ var results = Romo._elemsTagNameRegEx.exec(htmlString);
321
+ if (!results){ return []; }
322
+
323
+ var tagName = results[1].toLowerCase();
324
+ var wrap = Romo._elemsWrapMap[tagName];
325
+ if (!wrap) {
326
+ context.body.innerHTML = htmlString;
327
+ return this.array(context.body.children);
328
+ } else {
329
+ context.body.innerHTML = wrap[1] + htmlString + wrap[2];
330
+ var parentElem = context.body;
331
+ var i = wrap[0];
332
+ while(i-- !== 0) {
333
+ parentElem = parentElem.lastChild;
334
+ }
335
+ return this.array(parentElem.children)
336
+ }
337
+ }
338
+
339
+ Romo.prototype.addElemsInitSelector = function(selector, componentClass) {
340
+ this._elemsInitComponents[selector] = componentClass;
341
+ }
342
+
343
+ Romo.prototype.initElems = function(elems) {
344
+ return this._elemsInitTrigger(Romo.array(elems));
345
+ }
346
+
347
+ Romo.prototype.initElemsHtml = function(htmlString) {
348
+ return this.initElems(this.elems(htmlString));
235
349
  }
236
350
 
237
- Romo.prototype.remove = function(elem) {
238
- return elem.parentNode.removeChild(elem);
351
+ // DOM manipulation
352
+
353
+ Romo.prototype.remove = function(elems) {
354
+ return Romo.array(elems).map(function(elem) {
355
+ if (elem.parentNode) {
356
+ elem.parentNode.removeChild(elem);
357
+ }
358
+ return elem;
359
+ });
239
360
  }
240
361
 
241
362
  Romo.prototype.replace = function(elem, replacementElem) {
@@ -244,131 +365,200 @@ Romo.prototype.replace = function(elem, replacementElem) {
244
365
  }
245
366
 
246
367
  Romo.prototype.replaceHtml = function(elem, htmlString) {
247
- return this.replace(elem, this.elems(htmlString)[0]);
368
+ var replacementElem = Romo.elems(htmlString)[0];
369
+ if (replacementElem === undefined && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
370
+ throw new Error("Invalid HTML string, doesn't contain an HTML element.");
371
+ }
372
+ return this.replace(elem, replacementElem);
373
+ }
374
+
375
+ Romo.prototype.initReplace = function(elem, replacementElem) {
376
+ return this.initElems(this.replace(elem, replacementElem))[0];
377
+ }
378
+
379
+ Romo.prototype.initReplaceHtml = function(elem, htmlString) {
380
+ return this.initElems(this.replaceHtml(elem, htmlString))[0];
248
381
  }
249
382
 
250
383
  Romo.prototype.update = function(elem, childElems) {
251
384
  elem.innerHTML = '';
252
- return childElems.map(function(childElem) {
385
+ return Romo.array(childElems).map(function(childElem) {
253
386
  return elem.appendChild(childElem);
254
387
  });
255
388
  }
256
389
 
257
390
  Romo.prototype.updateHtml = function(elem, htmlString) {
258
- return this.update(elem, this.elems(htmlString));
391
+ var childElems = Romo.elems(htmlString);
392
+ if (childElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
393
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
394
+ }
395
+ return this.update(elem, childElems);
396
+ }
397
+
398
+ Romo.prototype.updateText = function(elem, textString) {
399
+ elem.innerText = textString;
400
+ }
401
+
402
+ Romo.prototype.initUpdate = function(elem, childElems) {
403
+ return this.initElems(this.update(elem, childElems));
404
+ }
405
+
406
+ Romo.prototype.initUpdateHtml = function(elem, htmlString) {
407
+ return this.initElems(this.updateHtml(elem, htmlString));
259
408
  }
260
409
 
261
410
  Romo.prototype.prepend = function(elem, childElems) {
262
411
  var refElem = elem.firstChild;
263
- return childElems.reverse().map(function(childElem) {
412
+ return Romo.array(childElems).reverse().map(function(childElem) {
264
413
  refElem = elem.insertBefore(childElem, refElem);
265
414
  return refElem;
266
415
  }).reverse();
267
416
  }
268
417
 
269
418
  Romo.prototype.prependHtml = function(elem, htmlString) {
270
- return this.prepend(elem, this.elems(htmlString));
419
+ var childElems = Romo.elems(htmlString);
420
+ if (childElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
421
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
422
+ }
423
+ return this.prepend(elem, childElems);
424
+ }
425
+
426
+ Romo.prototype.initPrepend = function(elem, childElems) {
427
+ return this.initElems(this.prepend(elem, childElems));
428
+ }
429
+
430
+ Romo.prototype.initPrependHtml = function(elem, htmlString) {
431
+ return this.initElems(this.prependHtml(elem, htmlString));
271
432
  }
272
433
 
273
434
  Romo.prototype.append = function(elem, childElems) {
274
- return childElems.map(function(childElem) {
435
+ return Romo.array(childElems).map(function(childElem) {
275
436
  return elem.appendChild(childElem);
276
437
  });
277
438
  }
278
439
 
279
440
  Romo.prototype.appendHtml = function(elem, htmlString) {
280
- return this.append(elem, this.elems(htmlString));
441
+ var childElems = Romo.elems(htmlString);
442
+ if (childElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
443
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
444
+ }
445
+ return this.append(elem, childElems);
446
+ }
447
+
448
+ Romo.prototype.initAppend = function(elem, childElems) {
449
+ return this.initElems(this.append(elem, childElems));
450
+ }
451
+
452
+ Romo.prototype.initAppendHtml = function(elem, htmlString) {
453
+ return this.initElems(this.appendHtml(elem, htmlString));
281
454
  }
282
455
 
283
456
  Romo.prototype.before = function(elem, siblingElems) {
284
457
  var refElem = elem;
285
458
  var parentElem = elem.parentNode;
286
- return siblingElems.reverse().map(function(siblingElem) {
459
+ return Romo.array(siblingElems).reverse().map(function(siblingElem) {
287
460
  refElem = parentElem.insertBefore(siblingElem, refElem);
288
461
  return refElem;
289
462
  }).reverse();
290
463
  }
291
464
 
292
465
  Romo.prototype.beforeHtml = function(elem, htmlString) {
293
- return this.before(elem, this.elems(htmlString));
466
+ var siblingElems = Romo.elems(htmlString);
467
+ if (siblingElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
468
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
469
+ }
470
+ return this.before(elem, siblingElems);
471
+ }
472
+
473
+ Romo.prototype.initBefore = function(elem, siblingElems) {
474
+ return this.initElems(this.before(elem, siblingElems));
475
+ }
476
+
477
+ Romo.prototype.initBeforeHtml = function(elem, htmlString) {
478
+ return this.initElems(this.beforeHtml(elem, htmlString));
294
479
  }
295
480
 
296
481
  Romo.prototype.after = function(elem, siblingElems) {
297
482
  var refElem = this.next(elem);
298
483
  var parentElem = elem.parentNode;
299
- return siblingElems.map(function(siblingElem) {
484
+ return Romo.array(siblingElems).map(function(siblingElem) {
300
485
  return parentElem.insertBefore(siblingElem, refElem);
301
486
  });
302
487
  }
303
488
 
304
489
  Romo.prototype.afterHtml = function(elem, htmlString) {
305
- return this.after(elem, this.elems(htmlString));
490
+ var siblingElems = Romo.elems(htmlString);
491
+ if (siblingElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
492
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
493
+ }
494
+ return this.after(elem, siblingElems);
306
495
  }
307
496
 
308
- // init UI (TODO: rework w/o jQuery)
309
-
310
- Romo.prototype.onInitUI = function(callback) {
311
- this._addEventCallback('romo:initUI', callback);
497
+ Romo.prototype.initAfter = function(elem, siblingElems) {
498
+ return this.initElems(this.after(elem, siblingElems));
312
499
  }
313
500
 
314
- Romo.prototype.triggerInitUI = function(elems) {
315
- elems.trigger('romo:initUI');
501
+ Romo.prototype.initAfterHtml = function(elem, htmlString) {
502
+ return this.initElems(this.afterHtml(elem, htmlString));
316
503
  }
317
504
 
318
- Romo.prototype.initUIElems = function(e, selector) {
319
- var elems = $(e.target).find(selector).get();
320
- if ($(e.target).is(selector)) {
321
- elems.push(e.target)
322
- }
323
- return $(elems);
324
- }
505
+ // events
325
506
 
326
- Romo.prototype.initHtml = function(elems, data) {
327
- elems.each($.proxy(function(index, elem) {
328
- var htmlElems = $(data)
329
- $(elem).html(htmlElems);
330
- this.triggerInitUI(htmlElems);
331
- }, this));
332
- }
507
+ Romo.prototype.on = function(elems, eventName, fn) {
508
+ var proxyFn = function(e) {
509
+ var result = fn.apply(e.target, e.detail === undefined ? [e] : [e].concat(e.detail));
510
+ if (result === false) {
511
+ e.preventDefault();
512
+ e.stopPropagation();
513
+ }
514
+ return result;
515
+ }
516
+ proxyFn._romofid = this._fn(fn)._romofid;
333
517
 
334
- Romo.prototype.initReplace = function(elems, data) {
335
- elems.each($.proxy(function(index, elem) {
336
- var replacementElem = $(data);
337
- $(elem).replaceWith(replacementElem);
338
- this.triggerInitUI(replacementElem);
518
+ Romo.array(elems).forEach(Romo.proxy(function(elem) {
519
+ elem.addEventListener(eventName, proxyFn);
520
+ var key = this._handlerKey(elem, eventName, proxyFn);
521
+ if (this._handlers[key] === undefined) {
522
+ this._handlers[key] = [];
523
+ }
524
+ this._handlers[key].push(proxyFn);
339
525
  }, this));
340
526
  }
341
527
 
342
- Romo.prototype.initPrepend = function(elems, data) {
343
- elems.each($.proxy(function(index, elem) {
344
- var prependedElem = $(data);
345
- $(elem).prepend(prependedElem);
346
- this.triggerInitUI(prependedElem);
528
+ Romo.prototype.off = function(elems, eventName, fn) {
529
+ Romo.array(elems).forEach(Romo.proxy(function(elem) {
530
+ var key = this._handlerKey(elem, eventName, fn);
531
+ (this._handlers[key] || []).forEach(function(proxyFn) {
532
+ elem.removeEventListener(eventName, proxyFn);
533
+ });
534
+ this._handlers[key] = [];
347
535
  }, this));
348
536
  }
349
537
 
350
- Romo.prototype.initAppend = function(elems, data) {
351
- elems.each($.proxy(function(index, elem) {
352
- var appendedElem = $(data);
353
- $(elem).append(appendedElem);
354
- this.triggerInitUI(appendedElem);
355
- }, this));
538
+ Romo.prototype.trigger = function(elems, customEventName, args) {
539
+ var event = undefined;
540
+ if (typeof window.CustomEvent === "function") {
541
+ event = new CustomEvent(customEventName, { detail: args });
542
+ } else {
543
+ event = document.createEvent('CustomEvent');
544
+ event.initCustomEvent(customEventName, false, false, args);
545
+ }
546
+ Romo.array(elems).forEach(function(elem) {
547
+ elem.dispatchEvent(event);
548
+ });
356
549
  }
357
550
 
358
- Romo.prototype.initBefore = function(elems, data) {
359
- elems.each($.proxy(function(index, elem) {
360
- var insertedElem = $(data);
361
- $(elem).before(insertedElem);
362
- this.triggerInitUI(insertedElem);
363
- }, this));
551
+ Romo.prototype.pushFn = function(fn) {
552
+ // push the function to delay running until the end of the reactor stack
553
+ setTimeout(fn, 1);
364
554
  }
365
555
 
366
- Romo.prototype.initAfter = function(elems, data) {
367
- elems.each($.proxy(function(index, elem) {
368
- var insertedElem = $(data);
369
- $(elem).after(insertedElem);
370
- this.triggerInitUI(insertedElem);
371
- }, this));
556
+ Romo.prototype.ready = function(eventHandlerFn) {
557
+ if (document.readyState === 'complete' || document.readyState !== 'loading') {
558
+ eventHandlerFn();
559
+ } else {
560
+ this.on(document, 'DOMContentLoaded', eventHandlerFn);
561
+ }
372
562
  }
373
563
 
374
564
  // page handling
@@ -381,20 +571,43 @@ Romo.prototype.redirectPage = function(redirectUrl) {
381
571
  window.location = redirectUrl;
382
572
  }
383
573
 
384
- // param serialization (TODO: rework w/o jQuery)
574
+ // param serialization
575
+
576
+ // Romo.param({ a: 2, b: 'three', c: 4 }); #=> "a=2&b=three&c=4"
577
+ // Romo.param({ a: [ 2, 3, 4 ] }); #=> "a[]=2&a[]=3&a[]=4"
578
+ // Romo.param({ a: 2, b: '', c: 4 }); #=> "a=2&b=&c=4"
579
+ // Romo.param({ a: 2, b: '', c: 4 }, { removeEmpty: true }); #=> "a=2&c=4"
580
+ // Romo.param({ a: [ 2, 3, 4 ], b: [''] }); #=> "a[]=2&a[]=3&a[]=4&b[]="
581
+ // Romo.param({ a: [ 2, 3, 4 ], b: [''] }, { removeEmpty: true }); #=> "a[]=2&a[]=3&a[]=4"
582
+ // Romo.param({ a: '123-ABC' }); #=> "a=123%2DABC"
583
+ // Romo.param({ a: '123-ABC' }, { decodeValues: true }); #=> "a=123-ABC"
385
584
 
386
585
  Romo.prototype.param = function(data, opts) {
387
- var paramData = $.extend({}, data);
586
+ var keyValues = [];
587
+
588
+ var processKeyValue = function(keyValues, key, value, opts) {
589
+ var v = String(value);
590
+ if (!opts || !opts.removeEmpty || v !== '') {
591
+ keyValues.push([key, v]);
592
+ }
593
+ }
388
594
 
389
- if (opts && opts.removeEmpty) {
390
- $.each(paramData, function(key, value) {
391
- if (value === '') {
392
- delete paramData[key];
595
+ for (var key in data) {
596
+ var value = data[key];
597
+ if (Array.isArray(value)) {
598
+ if (!opts || !opts.removeEmpty || value.length !== 0) {
599
+ value.forEach(function(listValue) {
600
+ processKeyValue(keyValues, key+'[]', listValue, opts);
601
+ })
393
602
  }
394
- })
603
+ } else {
604
+ processKeyValue(keyValues, key, value, opts);
605
+ }
395
606
  }
396
607
 
397
- var paramString = $.param(paramData);
608
+ var paramString = keyValues.map(function(keyValue){
609
+ return keyValue.join('=');
610
+ }).join('&');
398
611
 
399
612
  if (opts && opts.decodeValues) {
400
613
  paramString = this.decodeParam(paramString);
@@ -443,13 +656,25 @@ Romo.prototype.decodeParamMap = [
443
656
  Romo.prototype.ajax = function(settings) {
444
657
  var httpMethod = (settings.type || 'GET').toUpperCase();
445
658
  var xhrUrl = settings.url || window.location.toString();
446
- var xhrData = settings.data ? settings.data : null
447
- if (xhrData && httpMethod === 'GET') {
448
- var xhrQuery = Romo.param(xhrData);
449
- if (xhrQuery !== '') {
450
- xhrUrl = (xhrUrl + '&' + xhrQuery).replace(/[&?]{1,2}/, '?');
659
+ var xhrData = settings.data ? settings.data : undefined
660
+ if (xhrData && Object.keys(xhrData).length > 0) {
661
+ if (httpMethod === 'GET') {
662
+ var xhrQuery = Romo.param(xhrData);
663
+ if (xhrQuery !== '') {
664
+ xhrUrl = (xhrUrl + '&' + xhrQuery).replace(/[&?]{1,2}/, '?');
665
+ }
666
+ xhrData = undefined;
667
+ } else {
668
+ var formData = new FormData;
669
+ for (var name in xhrData) {
670
+ Romo.array(xhrData[name]).forEach(function(value){
671
+ formData.append(name, value)
672
+ });
673
+ }
674
+ xhrData = formData;
451
675
  }
452
- xhrData = null;
676
+ } else {
677
+ xhrData = undefined;
453
678
  }
454
679
 
455
680
  var xhr = new XMLHttpRequest();
@@ -466,13 +691,13 @@ Romo.prototype.ajax = function(settings) {
466
691
  }
467
692
  xhr.onreadystatechange = function() {
468
693
  if (xhr.readyState === 4) {
469
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
694
+ if (settings.success !== undefined && ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) {
470
695
  if (xhr.responseType === 'arraybuffer' || xhr.responseType === 'blob') {
471
696
  settings.success.call(window, xhr.response, xhr.status, xhr, settings);
472
697
  } else {
473
698
  settings.success.call(window, xhr.responseText, xhr.status, xhr, settings);
474
699
  }
475
- } else {
700
+ } else if(settings.error !== undefined) {
476
701
  settings.error.call(window, xhr.statusText || null, xhr.status, xhr, settings);
477
702
  }
478
703
  }
@@ -480,67 +705,84 @@ Romo.prototype.ajax = function(settings) {
480
705
  xhr.send(xhrData);
481
706
  },
482
707
 
483
- // events
708
+ // utils
484
709
 
485
- Romo.prototype.on = function(elem, eventName, fn) {
486
- // var proxyFn = function(e) {
487
- // var result = fn.apply(elem, e.detail === undefined ? [e] : [e].concat(e.detail));
488
- // if (result === false) {
489
- // e.preventDefault();
490
- // e.stopPropagation();
491
- // }
492
- // return result;
493
- // }
494
- // proxyFn._romofid = this._fn(fn)._romofid;
495
-
496
- // var key = this._handlerKey(elem, eventName, proxyFn);
497
- // if (!this._handlers[key]) {
498
- // elem.addEventListener(eventName, proxyFn);
499
- // this._handlers[key] = proxyFn;
500
- // }
501
-
502
- // Giant Hack to temporarily support jQuery and non-jQuery triggers
503
- // see: https://bugs.jquery.com/ticket/11047
504
- $(elem).on(eventName, fn);
505
- }
506
-
507
- Romo.prototype.off = function(elem, eventName, fn) {
508
- // var key = this._handlerKey(elem, eventName, fn);
509
- // var proxyFn = this._handlers[key];
510
- // if (proxyFn) {
511
- // elem.removeEventListener(eventName, proxyFn);
512
- // this._handlers[key] = undefined;
513
- // }
514
-
515
- // Giant Hack to temporarily support jQuery and non-jQuery triggers
516
- // see: https://bugs.jquery.com/ticket/11047
517
- $(elem).off(eventName, fn);
518
- }
519
-
520
- Romo.prototype.trigger = function(elem, customEventName, args) {
521
- // var event = undefined;
522
- // if (typeof window.CustomEvent === "function") {
523
- // event = new CustomEvent(customEventName, { detail: args });
524
- // } else {
525
- // event = document.createEvent('CustomEvent');
526
- // event.initCustomEvent(customEventName, false, false, args);
527
- // }
528
- // elem.dispatchEvent(event);
529
- $(elem).trigger(customEventName, args);
530
- }
710
+ Romo.prototype.array = function(value) {
711
+ // short circuit `Romo.f`, `Romo.find`, and `Romo.elems` calls (and others
712
+ // that return NodeList or HTMLCollection objects), this ensures these calls
713
+ // remain fast and don't run through all of the logic to detect if an object
714
+ // is like an array
715
+ var valString = Object.prototype.toString.call(value);
716
+ if (
717
+ valString === '[object NodeList]' ||
718
+ valString === '[object HTMLCollection]' ||
719
+ Array.isArray(value)
720
+ ) {
721
+ return Array.prototype.slice.call(value)
722
+ }
531
723
 
532
- Romo.prototype.ready = function(eventHandlerFn) {
533
- if (document.readyState === 'complete' || document.readyState !== 'loading') {
534
- eventHandlerFn();
724
+ // short circuit for passing individual elems and "not truthy" values, this
725
+ // ensures these remain fast (the individual elems) and avoids running into
726
+ // the is like an array logic; this fixes issues with select and form elems
727
+ // being like an array and returning unexpected results. This also fixes
728
+ // passing in null/undefined values.
729
+ if (!value || typeof(value.nodeType) === 'number') {
730
+ return [value];
731
+ }
732
+
733
+ var object = Object(value)
734
+ var length = undefined;
735
+ if (!!object && 'length' in object) {
736
+ length = object.length;
737
+ }
738
+
739
+ // some browsers return 'function' for HTML elements
740
+ var isFunction = (
741
+ typeof(object) === 'function' &&
742
+ typeof(object.nodeType) !== 'number'
743
+ );
744
+ var likeArray = (
745
+ typeof(value) !== 'string' &&
746
+ !isFunction &&
747
+ object !== window &&
748
+ ( Array.isArray(object) ||
749
+ length === 0 ||
750
+ ( typeof(length) === 'number' &&
751
+ length > 0 &&
752
+ (length - 1) in object
753
+ )
754
+ )
755
+ );
756
+ if(likeArray) {
757
+ return Array.prototype.slice.call(value);
535
758
  } else {
536
- this.on(document, 'DOMContentLoaded', eventHandlerFn);
759
+ return [value];
537
760
  }
538
761
  }
539
762
 
540
- // utils
763
+ Romo.prototype.assign = function(target) {
764
+ if(Object.assign) {
765
+ return Object.assign.apply(Object, arguments);
766
+ } else {
767
+ if (target == null) { // TypeError if undefined or null
768
+ throw new TypeError('Cannot convert undefined or null to object');
769
+ }
770
+ var to = Object(target);
771
+
772
+ for (var index = 1; index < arguments.length; index++) {
773
+ var nextSource = arguments[index];
541
774
 
542
- Romo.prototype.array = function(collection) {
543
- return Array.prototype.slice.call(collection);
775
+ if (nextSource != null) { // Skip over if undefined or null
776
+ for (var nextKey in nextSource) {
777
+ // Avoid bugs when hasOwnProperty is shadowed
778
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
779
+ to[nextKey] = nextSource[nextKey];
780
+ }
781
+ }
782
+ }
783
+ }
784
+ return to;
785
+ }
544
786
  }
545
787
 
546
788
  Romo.prototype.proxy = function(fn, context) {
@@ -550,12 +792,6 @@ Romo.prototype.proxy = function(fn, context) {
550
792
  return proxyFn;
551
793
  }
552
794
 
553
- // TODO: rework w/o jQuery
554
- Romo.prototype.toArray = function(elems) {
555
- // converts a collection of elements `$()` to an array of nodes
556
- return $.map(elems, function(node){ return node; })
557
- }
558
-
559
795
  Romo.prototype.nonInputTextKeyCodes = function() {
560
796
  // https://css-tricks.com/snippets/javascript/javascript-keycodes/
561
797
  return [
@@ -603,9 +839,13 @@ Romo.prototype._eid = 1;
603
839
  Romo.prototype._fid = 1;
604
840
 
605
841
  Romo.prototype._el = function(elem) {
606
- elem._romoeid || (
607
- elem._romoeid = (this.attr(elem, 'data-romo-eid') || this.setAttr(elem, 'data-romo-eid', this._eid++))
608
- );
842
+ if (!elem._romoeid) {
843
+ if (elem !== window && elem !== document) {
844
+ elem._romoeid = (this.data(elem, 'romo-eid') || this.setData(elem, 'romo-eid', this._eid++));
845
+ } else {
846
+ elem._romoeid = elem === window ? 'window' : 'document';
847
+ }
848
+ }
609
849
  return elem;
610
850
  }
611
851
 
@@ -622,66 +862,252 @@ Romo.prototype._handlerKey = function(elem, eventName, fn) {
622
862
 
623
863
  Romo.prototype._deserializeValue = function(value) {
624
864
  try {
625
- if (value === "true") { return true; } // "true" => true
626
- if (value === "false") { return false; } // "false" => false
627
- if (value === "undefined") { return undefined; } // "undefined" => undefined
628
- if (value === "null") { return null; } // "null" => null
629
- if (+value+"" === value) { return +value; } // "42.5" => 42.5
630
- if (/^[\[\{]/.test(value)) { JSON.parse(value); } // JSON => parse if valid
631
- return value; // String => self
865
+ if (value === "true") { return true; } // "true" => true
866
+ if (value === "false") { return false; } // "false" => false
867
+ if (value === "undefined") { return undefined; } // "undefined" => undefined
868
+ if (value === "null") { return null; } // "null" => null
869
+ if (value === null) { return undefined; } // null => undefined
870
+ if (+value+"" === value) { return +value; } // "42.5" => 42.5
871
+ if (/^[\[\{]/.test(value)) { return JSON.parse(value); } // JSON => parse if valid
872
+ return value; // String => self
632
873
  } catch(e) {
633
874
  return value
634
875
  }
635
876
  }
636
877
 
637
- // TODO: rework w/o jQuery
638
- Romo.prototype._addEventCallback = function(name, callback) {
639
- this._eventCallbacks.push({ eventName: name, callback: callback });
878
+ Romo.prototype._overflowScrollableRegex = /(auto|scroll)/;
879
+ Romo.prototype._elemsTagNameRegEx = /<([a-z0-9-]+)[\s\/>]+/i;
880
+
881
+ Romo.prototype._elemsWrapMap = {
882
+ 'caption': [1, "<table>", "</table>"],
883
+ 'colgroup': [1, "<table>", "</table>"],
884
+ 'col': [2, "<table><colgroup>", "</colgroup></table>"],
885
+ 'thead': [1, "<table>", "</table>"],
886
+ 'tbody': [1, "<table>", "</table>"],
887
+ 'tfoot': [1, "<table>", "</table>"],
888
+ 'tr': [2, "<table><tbody>", "</tbody></table>"],
889
+ 'th': [3, "<table><tbody><tr>", "</tr></tbody></table>"],
890
+ 'td': [3, "<table><tbody><tr>", "</tr></tbody></table>"]
891
+ };
892
+
893
+ Romo.prototype._elemsInitComponents = {};
894
+
895
+ Romo.prototype._elemsInitTrigger = function(onElems) {
896
+ for (var selector in this._elemsInitComponents) {
897
+ var componentClass = this._elemsInitComponents[selector];
898
+ this._elemsInitFind(onElems, selector).forEach(function(initElem){ new componentClass(initElem); });
899
+ }
900
+ return onElems;
901
+ }
902
+
903
+ Romo.prototype._elemsInitFind = function(onElems, selector) {
904
+ var elems = onElems.filter(function(onElem){ return Romo.is(onElem, selector); });
905
+ return elems.concat(Romo.find(onElems, selector));;
906
+ }
907
+
908
+ // RomoComponent
909
+
910
+ var RomoComponent = function(constructorFn) {
911
+ var component = function() {
912
+ RomoComponent.addEventFunctions(this);
913
+ constructorFn.apply(this, arguments);
914
+ }
915
+ component.prototype.romoEvFn = {};
916
+ component.prototype.doInit = function() {} // override as needed
917
+ return component;
918
+ }
919
+
920
+ RomoComponent.addEventFunctions = function(klassInstance) {
921
+ for(var name in klassInstance.romoEvFn) {
922
+ klassInstance[name] = RomoComponent.eventProxyFn(klassInstance.romoEvFn[name]);
923
+ }
924
+ }
925
+
926
+ RomoComponent.eventProxyFn = function(fn) {
927
+ return function(){ return fn.apply(this, arguments); };
928
+ }
929
+
930
+ // RomoPopupStack
931
+
932
+ var RomoPopupStack = function() {
933
+ this.popupSelector = undefined;
934
+ this.styleClasses = [];
935
+ this.items = [];
936
+
937
+ this._buildItemClass();
938
+ }
939
+
940
+ RomoPopupStack.prototype.doInit = function(styleClass) {
941
+ this.bodyElem = Romo.f('body')[0];
942
+ Romo.on(this.bodyElem, 'click', Romo.proxy(this._onBodyClick, this));
943
+ Romo.on(this.bodyElem, 'keyup', Romo.proxy(this._onBodyKeyUp, this));
944
+ Romo.on(window, 'resize', Romo.proxy(this._onWindowResize, this));
945
+ Romo.on(window, 'scroll', Romo.proxy(this._onWindowScroll, this));
946
+ }
947
+
948
+ RomoPopupStack.prototype.addStyleClass = function(styleClass) {
949
+ this.styleClasses.push(styleClass);
950
+ this.popupSelector = this.styleClasses.map(function(s){ return '.'+s; }).join(', ');
951
+ }
952
+
953
+ RomoPopupStack.prototype.addElem = function(popupElem, boundOpenFn, boundCloseFn, boundPlaceFn) {
954
+ this.items.push(new this.itemClass(popupElem, boundCloseFn, boundPlaceFn));
955
+
956
+ // allow any body click events to propagate and run first. This ensures
957
+ // any existing stack is in the appropriate state before opening a new popup.
958
+ Romo.pushFn(boundOpenFn);
959
+ }
960
+
961
+ RomoPopupStack.prototype.closeThru = function(popupElem) {
962
+ // allow any body click events to propagate and run first. This ensures
963
+ // any existing stack is in the appropriate state before opening a new popup.
964
+ Romo.pushFn(Romo.proxy(function() {
965
+ if (this._includes(popupElem)) {
966
+ this.closeTo(popupElem);
967
+ this._closeTop();
968
+ }
969
+ }, this));
970
+ }
971
+
972
+ RomoPopupStack.prototype.closeTo = function(popupElem) {
973
+ if (this._includes(popupElem)) {
974
+ while (this.items.length > 0 && !this.items[this.items.length-1].isFor(popupElem)) {
975
+ this._closeTop();
976
+ }
977
+ }
978
+ }
979
+
980
+ RomoPopupStack.prototype.placeAllPopups = function(includingFixed) {
981
+ this.items.filter(function(item) {
982
+ return includingFixed || Romo.css(item.popupElem, 'position') !== 'fixed';
983
+ }).forEach(function(item){
984
+ item.placeFn();
985
+ });
640
986
  }
641
987
 
642
- // RomoParentChildElems (TODO: rework w/o jQuery)
988
+ // private
989
+
990
+ RomoPopupStack.prototype._buildItemClass = function() {
991
+ this.itemClass = function(popupElem, closeFn, placeFn) {
992
+ this.popupElem = popupElem;
993
+ this.closeFn = closeFn;
994
+ this.placeFn = placeFn;
995
+ }
996
+ this.itemClass.prototype.isFor = function(popupElem) {
997
+ return this.popupElem === popupElem;
998
+ }
999
+ }
1000
+
1001
+ RomoPopupStack.prototype._closeTop = function() {
1002
+ var item;
1003
+ if (this.items.length > 0) {
1004
+ item = this.items.pop();
1005
+ item.closeFn();
1006
+ Romo.trigger(this.bodyElem, 'romoPopupStack:popupClose', [item.popupElem, this]);
1007
+ Romo.trigger(item.popupElem, 'romoPopupStack:popupClose', [this]);
1008
+ }
1009
+ return item;
1010
+ }
1011
+
1012
+ RomoPopupStack.prototype._closeAll = function() {
1013
+ while (this.items.length > 0) {
1014
+ this._closeTop();
1015
+ }
1016
+ }
1017
+
1018
+ RomoPopupStack.prototype._includes = function(popupElem) {
1019
+ return this.items.reduce(function(included, item) {
1020
+ return included || item.isFor(popupElem);
1021
+ }, false);
1022
+ }
1023
+
1024
+ RomoPopupStack.prototype._onBodyClick = function(e) {
1025
+ var popupElem = undefined;
1026
+ if (Romo.is(e.target, this.popupSelector)) {
1027
+ popupElem = e.target;
1028
+ } else {
1029
+ popupElem = Romo.parents(e.target, this.popupSelector)[0];
1030
+ }
1031
+
1032
+ if (popupElem === undefined || !this._includes(popupElem)) {
1033
+ this._closeAll();
1034
+ } else {
1035
+ this.closeTo(popupElem);
1036
+ }
1037
+ }
1038
+
1039
+ RomoPopupStack.prototype._onBodyKeyUp = function(e) {
1040
+ var closedItem;
1041
+ if (e.keyCode === 27 /* Esc */) {
1042
+ closedItem = this._closeTop();
1043
+ if (closedItem) {
1044
+ Romo.trigger(closedItem.popupElem, 'romoPopupStack:popupClosedByEsc', [this]);
1045
+ }
1046
+ }
1047
+ }
1048
+
1049
+ RomoPopupStack.prototype._onWindowResize = function(e) {
1050
+ this.placeAllPopups(true);
1051
+ }
1052
+
1053
+ RomoPopupStack.prototype._onWindowScroll = function(e) {
1054
+ this.placeAllPopups();
1055
+ }
1056
+
1057
+ // RomoParentChildElems
643
1058
 
644
1059
  var RomoParentChildElems = function() {
645
1060
  this.attrName = 'romo-parent-elem-id';
646
1061
  this.elemId = 0;
647
1062
  this.elems = {};
1063
+ }
648
1064
 
649
- var parentRemovedObserver = new MutationObserver($.proxy(function(mutationRecords) {
650
- mutationRecords.forEach($.proxy(function(mutationRecord) {
1065
+ RomoParentChildElems.prototype.doInit = function(parentElem, childElems) {
1066
+ var parentRemovedObserver = new MutationObserver(Romo.proxy(function(mutationRecords) {
1067
+ mutationRecords.forEach(Romo.proxy(function(mutationRecord) {
651
1068
  if (mutationRecord.type === 'childList' && mutationRecord.removedNodes.length > 0) {
652
- $.each($(mutationRecord.removedNodes), $.proxy(function(idx, node) {
653
- this.remove($(node));
1069
+ mutationRecord.removedNodes.forEach(Romo.proxy(function(removedNode) {
1070
+ this.remove(removedNode);
654
1071
  }, this));
655
1072
  }
656
1073
  }, this));
657
1074
  }, this));
658
1075
 
659
- parentRemovedObserver.observe($('body')[0], {
1076
+ parentRemovedObserver.observe(Romo.f('body')[0], {
660
1077
  childList: true,
661
1078
  subtree: true
662
1079
  });
663
1080
  }
664
1081
 
665
1082
  RomoParentChildElems.prototype.add = function(parentElem, childElems) {
666
- parentElem.attr('data-'+this.attrName, this._push(childElems, parentElem.data(this.attrName)));
1083
+ // delay adding b/c the parent elem may be manipulated in the DOM resulting in the parent elem
1084
+ // being removed and then re-added to the DOM. if the child elems are associated immediately,
1085
+ // any "remove" from DOM manipulation would incorrectly remove the popup.
1086
+ Romo.pushFn(Romo.proxy(function() {
1087
+ Romo.setData(parentElem, this.attrName, this._push(childElems, Romo.data(parentElem, this.attrName)));
1088
+ }, this));
667
1089
  }
668
1090
 
669
- RomoParentChildElems.prototype.remove = function(nodeElem) {
670
- if (nodeElem.data('romo-parent-removed-observer-disabled') !== true) {
671
- if (nodeElem.data(this.attrName) !== undefined) {
672
- this._removeChildElems(nodeElem); // node is a parent elem itself
1091
+ RomoParentChildElems.prototype.remove = function(elemNode) {
1092
+ if (elemNode.nodeType !== Node.ELEMENT_NODE){ return false; }
1093
+
1094
+ if (Romo.data(elemNode, 'romo-parent-removed-observer-disabled') !== true) {
1095
+ if (Romo.data(elemNode, this.attrName) !== undefined) {
1096
+ // node is a parent elem itself
1097
+ this._removeChildElems(elemNode);
673
1098
  }
674
- $.each(nodeElem.find('[data-'+this.attrName+']'), $.proxy(function(idx, parent) {
675
- this._removeChildElems($(parent));
1099
+ Romo.find(elemNode, '[data-'+this.attrName+']').forEach(Romo.proxy(function(childParentElem) {
1100
+ this._removeChildElems(childParentElem);
676
1101
  }, this));
677
1102
  }
678
1103
  }
679
1104
 
680
- // private RomoParentChildElems
1105
+ // private
681
1106
 
682
1107
  RomoParentChildElems.prototype._removeChildElems = function(parentElem) {
683
- $.each(this._pop(parentElem.data(this.attrName)), function(idx, elem) {
684
- $(elem).remove();
1108
+ this._pop(Romo.data(parentElem, this.attrName)).forEach(function(childElem) {
1109
+ Romo.remove(childElem);
1110
+ Romo.trigger(childElem, 'romoParentChildElems:childRemoved', [childElem]);
685
1111
  });
686
1112
  };
687
1113
 
@@ -692,7 +1118,8 @@ RomoParentChildElems.prototype._push = function(items, id) {
692
1118
  if (this.elems[id] === undefined) {
693
1119
  this.elems[id] = []
694
1120
  }
695
- items.forEach($.proxy(function(item){ this.elems[id].push(item) }, this));
1121
+ this.elems[id] = this.elems[id].concat(items);
1122
+
696
1123
  return id;
697
1124
  };
698
1125
 
@@ -704,9 +1131,7 @@ RomoParentChildElems.prototype._pop = function(id) {
704
1131
 
705
1132
  // Init
706
1133
 
707
- window.Romo = new Romo();
708
-
709
- // TODO: rework w/o jQuery
710
- $(function() {
1134
+ var Romo = new Romo();
1135
+ Romo.ready(function() {
711
1136
  Romo.doInit();
712
- })
1137
+ });