assets/js/romo/base.js in romo-0.19.10 vs assets/js/romo/base.js in romo-0.20.0
- old
+ new
@@ -1,165 +1,255 @@
var Romo = function() {
- this._eventCallbacks = [];
+ this.popupStack = new RomoPopupStack();
+ this.parentChildElems = new RomoParentChildElems();
}
-// TODO: rework w/o jQuery
Romo.prototype.doInit = function() {
- this.parentChildElems = new RomoParentChildElems();
-
- $.each(this._eventCallbacks, function(idx, eventCallback) {
- $('body').on(eventCallback.eventName, eventCallback.callback);
- });
-
- this.triggerInitUI($('body'));
+ this.popupStack.doInit();
+ this.parentChildElems.doInit();
+ this.initElems(Romo.f('body'));
}
// element finders
Romo.prototype.f = function(selector) {
return this.array(document.querySelectorAll(selector));
}
-Romo.prototype.find = function(parentElem, selector) {
- return this.array(parentElem.querySelectorAll(selector));
+Romo.prototype.find = function(parentElems, selector) {
+ return this.array(parentElems).reduce(
+ Romo.proxy(function(foundElems, parentElem) {
+ return foundElems.concat(this.array(parentElem.querySelectorAll(selector)));
+ }, this),
+ []
+ );
}
-Romo.prototype.children = function(parentElem) {
- return this.array(parentElem.children);
+Romo.prototype.is = function(elem, selector) {
+ return (
+ elem.matches ||
+ elem.matchesSelector ||
+ elem.msMatchesSelector ||
+ elem.mozMatchesSelector ||
+ elem.webkitMatchesSelector ||
+ elem.oMatchesSelector
+ ).call(elem, selector);
+};
+
+Romo.prototype.children = function(parentElem, selector) {
+ var childElems = this.array(parentElem.children);
+ if (selector) {
+ return childElems.filter(function(childElem) {
+ return Romo.is(childElem, selector);
+ });
+ } else {
+ return childElems;
+ }
}
Romo.prototype.parent = function(childElem) {
return childElem.parentNode;
}
-Romo.prototype.siblings = function(elem) {
- return Array.prototype.filter.call(elem.parentNode.children, function(childElem) {
- return childElem !== elem;
- });
+Romo.prototype.parents = function(childElem, selector) {
+ var parentElem = this.parent(childElem);
+ if (parentElem && parentElem !== document) {
+ if (!selector || Romo.is(parentElem, selector)) {
+ if (Romo.is(parentElem, 'body')) {
+ return [parentElem];
+ } else {
+ return [parentElem].concat(this.parents(parentElem, selector));
+ }
+ } else {
+ if (Romo.is(parentElem, 'body')) {
+ return [];
+ } else {
+ return this.parents(parentElem, selector);
+ }
+ }
+ } else {
+ return [];
+ }
}
-Romo.prototype.prev = function(fromElem) {
- return fromElem.previousElementSibling;
+Romo.prototype.scrollableParents = function(childElem, selector) {
+ return Romo.parents(childElem, selector).filter(function(parentElem) {
+ return (
+ Romo._overflowScrollableRegex.test(Romo.css(parentElem, 'overflow')) ||
+ Romo._overflowScrollableRegex.test(Romo.css(parentElem, 'overflow-y')) ||
+ Romo._overflowScrollableRegex.test(Romo.css(parentElem, 'overflow-x'))
+ );
+ });
}
-Romo.prototype.next = function(fromElem) {
- return fromElem.nextElementSibling;
-}
-
Romo.prototype.closest = function(fromElem, selector) {
if (fromElem.closest) {
return fromElem.closest(selector);
} else {
- var matchesSelector = fromElem.matches ||
- fromElem.webkitMatchesSelector ||
- fromElem.mozMatchesSelector ||
- fromElem.msMatchesSelector;
while (fromElem) {
- if (matchesSelector.call(fromElem, selector)) {
+ if (Romo.is(fromElem, selector)) {
return fromElem;
} else {
fromElem = fromElem.parentElement;
}
}
return undefined;
}
}
-// TODO: rework w/o jQuery
-Romo.prototype.selectNext = function(elem, selector) {
- // like `$().next()`, but takes a selector; returns first next elem that
- // matches the given selector or an empty collection if non matches
- var el = elem.next();
- while(el.length) {
- if (selector === undefined || el.is(selector)) {
- return el;
+Romo.prototype.siblings = function(elem, selector) {
+ return Romo.children(Romo.parent(elem), selector).filter(function(childElem) {
+ return childElem !== elem;
+ });
+}
+
+Romo.prototype.prev = function(fromElem, selector) {
+ var elem = fromElem.previousElementSibling;
+ if (elem === null) {
+ elem = undefined;
+ }
+
+ while(elem) {
+ if (!selector || Romo.is(elem, selector)) {
+ return elem;
}
- el = el.next();
+ elem = elem.previousElementSibling;
+ if (elem === null) {
+ elem = undefined;
+ }
}
- return el;
+ return elem;
}
-// TODO: rework w/o jQuery
-Romo.prototype.selectPrev = function(elem, selector) {
- // like `$().prev()`, but takes a selector; returns first prev elem that
- // matches the given selector or an empty collection if non matches
- var el = elem.prev();
- while(el.length) {
- if (selector === undefined || el.is(selector)) {
- return el;
+Romo.prototype.next = function(fromElem, selector) {
+ var elem = fromElem.nextElementSibling;
+ if (elem === null) {
+ elem = undefined;
+ }
+
+ while(elem) {
+ if (!selector || Romo.is(elem, selector)) {
+ return elem;
}
- el = el.prev();
+ elem = elem.nextElementSibling;
+ if (elem === null) {
+ elem = undefined;
+ }
}
- return el;
+ return elem;
}
// attributes, styles, classes
Romo.prototype.attr = function(elem, attrName) {
- return elem.getAttribute ? elem.getAttribute(attrName) : undefined;
+ var a = elem.getAttribute(attrName);
+ if (a === null) {
+ return undefined;
+ } else {
+ return a;
+ }
}
-Romo.prototype.setAttr = function(elem, attrName, attrValue) {
- if (elem.setAttribute) {
- elem.setAttribute(attrName, attrValue);
- }
- return attrValue;
+Romo.prototype.setAttr = function(elems, attrName, attrValue) {
+ var v = String(attrValue);
+ Romo.array(elems).forEach(function(elem) {
+ elem.setAttribute(attrName, v);
+ });
+ return v;
}
+Romo.prototype.rmAttr = function(elems, attrName) {
+ Romo.array(elems).forEach(function(elem) {
+ elem.removeAttribute(attrName);
+ });
+}
+
Romo.prototype.data = function(elem, dataName) {
return this._deserializeValue(this.attr(elem, "data-"+dataName));
}
-Romo.prototype.setData = function(elem, dataName, dataValue) {
- return this.setAttr(elem, "data-"+dataName, String(dataValue));
+Romo.prototype.setData = function(elems, dataName, dataValue) {
+ return this.setAttr(elems, "data-"+dataName, dataValue);
}
+Romo.prototype.rmData = function(elems, dataName) {
+ this.rmAttr(elems, "data-"+dataName);
+}
+
Romo.prototype.style = function(elem, styleName) {
return elem.style[styleName];
}
-Romo.prototype.setStyle = function(elem, styleName, styleValue) {
- elem.style[styleName] = styleValue;
+Romo.prototype.setStyle = function(elems, styleName, styleValue) {
+ Romo.array(elems).forEach(function(elem) {
+ elem.style[styleName] = styleValue;
+ });
return styleValue;
}
+Romo.prototype.rmStyle = function(elems, styleName) {
+ Romo.array(elems).forEach(function(elem) {
+ elem.style[styleName] = '';
+ });
+}
+
Romo.prototype.css = function(elem, styleName) {
return window.getComputedStyle(elem, null).getPropertyValue(styleName);
}
-Romo.prototype.addClass = function(elem, className) {
- elem.classList.add(className);
+Romo.prototype.addClass = function(elems, className) {
+ var classNames = className.split(' ').filter(function(n){ return n; });
+ Romo.array(elems).forEach(function(elem) {
+ classNames.forEach(function(name) {
+ elem.classList.add(name);
+ });
+ });
return className;
}
-Romo.prototype.removeClass = function(elem, className) {
- elem.classList.remove(className);
+Romo.prototype.removeClass = function(elems, className) {
+ var classNames = className.split(' ').filter(function(n){ return n; });
+ Romo.array(elems).forEach(function(elem) {
+ classNames.forEach(function(name) {
+ elem.classList.remove(name);
+ });
+ });
return className;
}
-Romo.prototype.toggleClass = function(elem, className) {
- elem.classList.toggle(className);
+Romo.prototype.toggleClass = function(elems, className) {
+ var classNames = className.split(' ').filter(function(n){ return n; });
+ Romo.array(elems).forEach(function(elem) {
+ classNames.forEach(function(name) {
+ elem.classList.toggle(name);
+ });
+ });
return className;
}
Romo.prototype.hasClass = function(elem, className) {
return elem.classList.contains(className);
}
-Romo.prototype.show = function(elem) {
- elem.style.display = '';
+Romo.prototype.show = function(elems) {
+ Romo.array(elems).forEach(function(elem) {
+ elem.style.display = '';
+ });
}
-Romo.prototype.hide = function(elem) {
- elem.style.display = 'none';
+Romo.prototype.hide = function(elems) {
+ Romo.array(elems).forEach(function(elem) {
+ elem.style.display = 'none';
+ });
}
Romo.prototype.offset = function(elem) {
- var rect = elem.getBoundingClientRect();
+ var elemRect = elem.getBoundingClientRect();
+ var bodyRect = document.body.getBoundingClientRect();
return {
- top: rect.top + document.body.scrollTop,
- left: rect.left + document.body.scrollLeft
+ top: elemRect.top - bodyRect.top,
+ left: elemRect.left - bodyRect.left
};
}
Romo.prototype.scrollTop = function(elem) {
return ('scrollTop' in elem) ? elem.scrollTop : elem.pageYOffset;
@@ -167,236 +257,359 @@
Romo.prototype.scrollLeft = function(elem) {
return ('scrollLeft' in elem) ? elem.scrollLeft : elem.pageXOffset;
}
-Romo.prototype.setScrollTop = function(elem, value) {
- if ('scrollTop' in elem) {
- elem.scrollTop = value;
- } else {
- elem.scrollTo(elem.scrollX, value);
- }
+Romo.prototype.setScrollTop = function(elems, value) {
+ Romo.array(elems).forEach(function(elem) {
+ if ('scrollTop' in elem) {
+ elem.scrollTop = value;
+ } else {
+ elem.scrollTo(elem.scrollX, value);
+ }
+ });
}
-Romo.prototype.setScrollLeft = function(elem, value) {
- if ('scrollLeft' in elem) {
- elem.scrollLeft = value;
- } else {
- elem.scrollTo(value, elem.scrollY);
- }
+Romo.prototype.setScrollLeft = function(elems, value) {
+ Romo.array(elems).forEach(function(elem) {
+ if ('scrollLeft' in elem) {
+ elem.scrollLeft = value;
+ } else {
+ elem.scrollTo(value, elem.scrollY);
+ }
+ });
}
-// TODO: rework w/o jQuery
-Romo.prototype.getComputedStyle = function(node, styleName) {
- return window.getComputedStyle(node, null).getPropertyValue(styleName);
-}
-
-// TODO: rework w/o jQuery
Romo.prototype.parseZIndex = function(elem) {
// for the case where z-index is set directly on the elem
var val = this.parseElemZIndex(elem);
if (val !== 0) {
return val;
}
// for the case where z-index is inherited from a parent elem
- var parentIndexes = this.toArray(elem.parents()).reduce($.proxy(function(prev, curr) {
- var pval = this.parseElemZIndex($(curr));
- if (pval !== 0) {
- prev.push(pval);
+ var pval = 0;
+ var parentIndexes = Romo.parents(elem).forEach(Romo.proxy(function(parentElem) {
+ if (pval === 0) {
+ pval = this.parseElemZIndex(parentElem);
}
- return prev;
- }, this), []);
- parentIndexes.push(0); // in case z-index is 'auto' all the way up
- return parentIndexes[0];
+ }, this));
+
+ // z-index is 'auto' all the way up
+ return pval;
}
-// TODO: rework w/o jQuery
Romo.prototype.parseElemZIndex = function(elem) {
- var val = parseInt(this.getComputedStyle(elem[0], "z-index"));
+ var val = parseInt(this.css(elem, "z-index"), 10);
if (!isNaN(val)) {
return val;
}
return 0;
}
-// DOM manipulation
+// elems init
Romo.prototype.elems = function(htmlString) {
var context = document.implementation.createHTMLDocument();
// Set the base href for the created document so any parsed
// elements with URLs are based on the document's URL
var base = context.createElement('base');
base.href = document.location.href;
context.head.appendChild(base);
- context.body.innerHTML = htmlString;
- return this.array(context.body.children);
+ var results = Romo._elemsTagNameRegEx.exec(htmlString);
+ if (!results){ return []; }
+
+ var tagName = results[1].toLowerCase();
+ var wrap = Romo._elemsWrapMap[tagName];
+ if (!wrap) {
+ context.body.innerHTML = htmlString;
+ return this.array(context.body.children);
+ } else {
+ context.body.innerHTML = wrap[1] + htmlString + wrap[2];
+ var parentElem = context.body;
+ var i = wrap[0];
+ while(i-- !== 0) {
+ parentElem = parentElem.lastChild;
+ }
+ return this.array(parentElem.children)
+ }
}
-Romo.prototype.remove = function(elem) {
- return elem.parentNode.removeChild(elem);
+Romo.prototype.addElemsInitSelector = function(selector, componentClass) {
+ this._elemsInitComponents[selector] = componentClass;
}
+Romo.prototype.initElems = function(elems) {
+ return this._elemsInitTrigger(Romo.array(elems));
+}
+
+Romo.prototype.initElemsHtml = function(htmlString) {
+ return this.initElems(this.elems(htmlString));
+}
+
+// DOM manipulation
+
+Romo.prototype.remove = function(elems) {
+ return Romo.array(elems).map(function(elem) {
+ if (elem.parentNode) {
+ elem.parentNode.removeChild(elem);
+ }
+ return elem;
+ });
+}
+
Romo.prototype.replace = function(elem, replacementElem) {
elem.parentNode.replaceChild(replacementElem, elem);
return replacementElem;
}
Romo.prototype.replaceHtml = function(elem, htmlString) {
- return this.replace(elem, this.elems(htmlString)[0]);
+ var replacementElem = Romo.elems(htmlString)[0];
+ if (replacementElem === undefined && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
+ throw new Error("Invalid HTML string, doesn't contain an HTML element.");
+ }
+ return this.replace(elem, replacementElem);
}
+Romo.prototype.initReplace = function(elem, replacementElem) {
+ return this.initElems(this.replace(elem, replacementElem))[0];
+}
+
+Romo.prototype.initReplaceHtml = function(elem, htmlString) {
+ return this.initElems(this.replaceHtml(elem, htmlString))[0];
+}
+
Romo.prototype.update = function(elem, childElems) {
elem.innerHTML = '';
- return childElems.map(function(childElem) {
+ return Romo.array(childElems).map(function(childElem) {
return elem.appendChild(childElem);
});
}
Romo.prototype.updateHtml = function(elem, htmlString) {
- return this.update(elem, this.elems(htmlString));
+ var childElems = Romo.elems(htmlString);
+ if (childElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
+ }
+ return this.update(elem, childElems);
}
+Romo.prototype.updateText = function(elem, textString) {
+ elem.innerText = textString;
+}
+
+Romo.prototype.initUpdate = function(elem, childElems) {
+ return this.initElems(this.update(elem, childElems));
+}
+
+Romo.prototype.initUpdateHtml = function(elem, htmlString) {
+ return this.initElems(this.updateHtml(elem, htmlString));
+}
+
Romo.prototype.prepend = function(elem, childElems) {
var refElem = elem.firstChild;
- return childElems.reverse().map(function(childElem) {
+ return Romo.array(childElems).reverse().map(function(childElem) {
refElem = elem.insertBefore(childElem, refElem);
return refElem;
}).reverse();
}
Romo.prototype.prependHtml = function(elem, htmlString) {
- return this.prepend(elem, this.elems(htmlString));
+ var childElems = Romo.elems(htmlString);
+ if (childElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
+ }
+ return this.prepend(elem, childElems);
}
+Romo.prototype.initPrepend = function(elem, childElems) {
+ return this.initElems(this.prepend(elem, childElems));
+}
+
+Romo.prototype.initPrependHtml = function(elem, htmlString) {
+ return this.initElems(this.prependHtml(elem, htmlString));
+}
+
Romo.prototype.append = function(elem, childElems) {
- return childElems.map(function(childElem) {
+ return Romo.array(childElems).map(function(childElem) {
return elem.appendChild(childElem);
});
}
Romo.prototype.appendHtml = function(elem, htmlString) {
- return this.append(elem, this.elems(htmlString));
+ var childElems = Romo.elems(htmlString);
+ if (childElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
+ }
+ return this.append(elem, childElems);
}
+Romo.prototype.initAppend = function(elem, childElems) {
+ return this.initElems(this.append(elem, childElems));
+}
+
+Romo.prototype.initAppendHtml = function(elem, htmlString) {
+ return this.initElems(this.appendHtml(elem, htmlString));
+}
+
Romo.prototype.before = function(elem, siblingElems) {
var refElem = elem;
var parentElem = elem.parentNode;
- return siblingElems.reverse().map(function(siblingElem) {
+ return Romo.array(siblingElems).reverse().map(function(siblingElem) {
refElem = parentElem.insertBefore(siblingElem, refElem);
return refElem;
}).reverse();
}
Romo.prototype.beforeHtml = function(elem, htmlString) {
- return this.before(elem, this.elems(htmlString));
+ var siblingElems = Romo.elems(htmlString);
+ if (siblingElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
+ }
+ return this.before(elem, siblingElems);
}
+Romo.prototype.initBefore = function(elem, siblingElems) {
+ return this.initElems(this.before(elem, siblingElems));
+}
+
+Romo.prototype.initBeforeHtml = function(elem, htmlString) {
+ return this.initElems(this.beforeHtml(elem, htmlString));
+}
+
Romo.prototype.after = function(elem, siblingElems) {
var refElem = this.next(elem);
var parentElem = elem.parentNode;
- return siblingElems.map(function(siblingElem) {
+ return Romo.array(siblingElems).map(function(siblingElem) {
return parentElem.insertBefore(siblingElem, refElem);
});
}
Romo.prototype.afterHtml = function(elem, htmlString) {
- return this.after(elem, this.elems(htmlString));
+ var siblingElems = Romo.elems(htmlString);
+ if (siblingElems.length === 0 && (typeof(htmlString) !== 'string' || htmlString.trim() !== '')) {
+ throw new Error("Invalid HTML string, doesn't contain any HTML elements.");
+ }
+ return this.after(elem, siblingElems);
}
-// init UI (TODO: rework w/o jQuery)
-
-Romo.prototype.onInitUI = function(callback) {
- this._addEventCallback('romo:initUI', callback);
+Romo.prototype.initAfter = function(elem, siblingElems) {
+ return this.initElems(this.after(elem, siblingElems));
}
-Romo.prototype.triggerInitUI = function(elems) {
- elems.trigger('romo:initUI');
+Romo.prototype.initAfterHtml = function(elem, htmlString) {
+ return this.initElems(this.afterHtml(elem, htmlString));
}
-Romo.prototype.initUIElems = function(e, selector) {
- var elems = $(e.target).find(selector).get();
- if ($(e.target).is(selector)) {
- elems.push(e.target)
+// events
+
+Romo.prototype.on = function(elems, eventName, fn) {
+ var proxyFn = function(e) {
+ var result = fn.apply(e.target, e.detail === undefined ? [e] : [e].concat(e.detail));
+ if (result === false) {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+ return result;
}
- return $(elems);
-}
+ proxyFn._romofid = this._fn(fn)._romofid;
-Romo.prototype.initHtml = function(elems, data) {
- elems.each($.proxy(function(index, elem) {
- var htmlElems = $(data)
- $(elem).html(htmlElems);
- this.triggerInitUI(htmlElems);
+ Romo.array(elems).forEach(Romo.proxy(function(elem) {
+ elem.addEventListener(eventName, proxyFn);
+ var key = this._handlerKey(elem, eventName, proxyFn);
+ if (this._handlers[key] === undefined) {
+ this._handlers[key] = [];
+ }
+ this._handlers[key].push(proxyFn);
}, this));
}
-Romo.prototype.initReplace = function(elems, data) {
- elems.each($.proxy(function(index, elem) {
- var replacementElem = $(data);
- $(elem).replaceWith(replacementElem);
- this.triggerInitUI(replacementElem);
+Romo.prototype.off = function(elems, eventName, fn) {
+ Romo.array(elems).forEach(Romo.proxy(function(elem) {
+ var key = this._handlerKey(elem, eventName, fn);
+ (this._handlers[key] || []).forEach(function(proxyFn) {
+ elem.removeEventListener(eventName, proxyFn);
+ });
+ this._handlers[key] = [];
}, this));
}
-Romo.prototype.initPrepend = function(elems, data) {
- elems.each($.proxy(function(index, elem) {
- var prependedElem = $(data);
- $(elem).prepend(prependedElem);
- this.triggerInitUI(prependedElem);
- }, this));
+Romo.prototype.trigger = function(elems, customEventName, args) {
+ var event = undefined;
+ if (typeof window.CustomEvent === "function") {
+ event = new CustomEvent(customEventName, { detail: args });
+ } else {
+ event = document.createEvent('CustomEvent');
+ event.initCustomEvent(customEventName, false, false, args);
+ }
+ Romo.array(elems).forEach(function(elem) {
+ elem.dispatchEvent(event);
+ });
}
-Romo.prototype.initAppend = function(elems, data) {
- elems.each($.proxy(function(index, elem) {
- var appendedElem = $(data);
- $(elem).append(appendedElem);
- this.triggerInitUI(appendedElem);
- }, this));
+Romo.prototype.pushFn = function(fn) {
+ // push the function to delay running until the end of the reactor stack
+ setTimeout(fn, 1);
}
-Romo.prototype.initBefore = function(elems, data) {
- elems.each($.proxy(function(index, elem) {
- var insertedElem = $(data);
- $(elem).before(insertedElem);
- this.triggerInitUI(insertedElem);
- }, this));
+Romo.prototype.ready = function(eventHandlerFn) {
+ if (document.readyState === 'complete' || document.readyState !== 'loading') {
+ eventHandlerFn();
+ } else {
+ this.on(document, 'DOMContentLoaded', eventHandlerFn);
+ }
}
-Romo.prototype.initAfter = function(elems, data) {
- elems.each($.proxy(function(index, elem) {
- var insertedElem = $(data);
- $(elem).after(insertedElem);
- this.triggerInitUI(insertedElem);
- }, this));
-}
-
// page handling
Romo.prototype.reloadPage = function() {
window.location = window.location;
}
Romo.prototype.redirectPage = function(redirectUrl) {
window.location = redirectUrl;
}
-// param serialization (TODO: rework w/o jQuery)
+// param serialization
+// Romo.param({ a: 2, b: 'three', c: 4 }); #=> "a=2&b=three&c=4"
+// Romo.param({ a: [ 2, 3, 4 ] }); #=> "a[]=2&a[]=3&a[]=4"
+// Romo.param({ a: 2, b: '', c: 4 }); #=> "a=2&b=&c=4"
+// Romo.param({ a: 2, b: '', c: 4 }, { removeEmpty: true }); #=> "a=2&c=4"
+// Romo.param({ a: [ 2, 3, 4 ], b: [''] }); #=> "a[]=2&a[]=3&a[]=4&b[]="
+// Romo.param({ a: [ 2, 3, 4 ], b: [''] }, { removeEmpty: true }); #=> "a[]=2&a[]=3&a[]=4"
+// Romo.param({ a: '123-ABC' }); #=> "a=123%2DABC"
+// Romo.param({ a: '123-ABC' }, { decodeValues: true }); #=> "a=123-ABC"
+
Romo.prototype.param = function(data, opts) {
- var paramData = $.extend({}, data);
+ var keyValues = [];
- if (opts && opts.removeEmpty) {
- $.each(paramData, function(key, value) {
- if (value === '') {
- delete paramData[key];
+ var processKeyValue = function(keyValues, key, value, opts) {
+ var v = String(value);
+ if (!opts || !opts.removeEmpty || v !== '') {
+ keyValues.push([key, v]);
+ }
+ }
+
+ for (var key in data) {
+ var value = data[key];
+ if (Array.isArray(value)) {
+ if (!opts || !opts.removeEmpty || value.length !== 0) {
+ value.forEach(function(listValue) {
+ processKeyValue(keyValues, key+'[]', listValue, opts);
+ })
}
- })
+ } else {
+ processKeyValue(keyValues, key, value, opts);
+ }
}
- var paramString = $.param(paramData);
+ var paramString = keyValues.map(function(keyValue){
+ return keyValue.join('=');
+ }).join('&');
if (opts && opts.decodeValues) {
paramString = this.decodeParam(paramString);
}
@@ -441,17 +654,29 @@
// AJAX
Romo.prototype.ajax = function(settings) {
var httpMethod = (settings.type || 'GET').toUpperCase();
var xhrUrl = settings.url || window.location.toString();
- var xhrData = settings.data ? settings.data : null
- if (xhrData && httpMethod === 'GET') {
- var xhrQuery = Romo.param(xhrData);
- if (xhrQuery !== '') {
- xhrUrl = (xhrUrl + '&' + xhrQuery).replace(/[&?]{1,2}/, '?');
+ var xhrData = settings.data ? settings.data : undefined
+ if (xhrData && Object.keys(xhrData).length > 0) {
+ if (httpMethod === 'GET') {
+ var xhrQuery = Romo.param(xhrData);
+ if (xhrQuery !== '') {
+ xhrUrl = (xhrUrl + '&' + xhrQuery).replace(/[&?]{1,2}/, '?');
+ }
+ xhrData = undefined;
+ } else {
+ var formData = new FormData;
+ for (var name in xhrData) {
+ Romo.array(xhrData[name]).forEach(function(value){
+ formData.append(name, value)
+ });
+ }
+ xhrData = formData;
}
- xhrData = null;
+ } else {
+ xhrData = undefined;
}
var xhr = new XMLHttpRequest();
xhr.open(httpMethod, xhrUrl, true, settings.username, settings.password);
if (settings.responseType === 'arraybuffer' || settings.responseType === 'blob') {
@@ -464,100 +689,111 @@
if (settings.contentType) {
xhr.setRequestHeader('Content-Type', settings.contentType);
}
xhr.onreadystatechange = function() {
if (xhr.readyState === 4) {
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
+ if (settings.success !== undefined && ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304)) {
if (xhr.responseType === 'arraybuffer' || xhr.responseType === 'blob') {
settings.success.call(window, xhr.response, xhr.status, xhr, settings);
} else {
settings.success.call(window, xhr.responseText, xhr.status, xhr, settings);
}
- } else {
+ } else if(settings.error !== undefined) {
settings.error.call(window, xhr.statusText || null, xhr.status, xhr, settings);
}
}
};
xhr.send(xhrData);
},
-// events
+// utils
-Romo.prototype.on = function(elem, eventName, fn) {
- // var proxyFn = function(e) {
- // var result = fn.apply(elem, e.detail === undefined ? [e] : [e].concat(e.detail));
- // if (result === false) {
- // e.preventDefault();
- // e.stopPropagation();
- // }
- // return result;
- // }
- // proxyFn._romofid = this._fn(fn)._romofid;
+Romo.prototype.array = function(value) {
+ // short circuit `Romo.f`, `Romo.find`, and `Romo.elems` calls (and others
+ // that return NodeList or HTMLCollection objects), this ensures these calls
+ // remain fast and don't run through all of the logic to detect if an object
+ // is like an array
+ var valString = Object.prototype.toString.call(value);
+ if (
+ valString === '[object NodeList]' ||
+ valString === '[object HTMLCollection]' ||
+ Array.isArray(value)
+ ) {
+ return Array.prototype.slice.call(value)
+ }
- // var key = this._handlerKey(elem, eventName, proxyFn);
- // if (!this._handlers[key]) {
- // elem.addEventListener(eventName, proxyFn);
- // this._handlers[key] = proxyFn;
- // }
+ // short circuit for passing individual elems and "not truthy" values, this
+ // ensures these remain fast (the individual elems) and avoids running into
+ // the is like an array logic; this fixes issues with select and form elems
+ // being like an array and returning unexpected results. This also fixes
+ // passing in null/undefined values.
+ if (!value || typeof(value.nodeType) === 'number') {
+ return [value];
+ }
- // Giant Hack to temporarily support jQuery and non-jQuery triggers
- // see: https://bugs.jquery.com/ticket/11047
- $(elem).on(eventName, fn);
-}
+ var object = Object(value)
+ var length = undefined;
+ if (!!object && 'length' in object) {
+ length = object.length;
+ }
-Romo.prototype.off = function(elem, eventName, fn) {
- // var key = this._handlerKey(elem, eventName, fn);
- // var proxyFn = this._handlers[key];
- // if (proxyFn) {
- // elem.removeEventListener(eventName, proxyFn);
- // this._handlers[key] = undefined;
- // }
-
- // Giant Hack to temporarily support jQuery and non-jQuery triggers
- // see: https://bugs.jquery.com/ticket/11047
- $(elem).off(eventName, fn);
-}
-
-Romo.prototype.trigger = function(elem, customEventName, args) {
- // var event = undefined;
- // if (typeof window.CustomEvent === "function") {
- // event = new CustomEvent(customEventName, { detail: args });
- // } else {
- // event = document.createEvent('CustomEvent');
- // event.initCustomEvent(customEventName, false, false, args);
- // }
- // elem.dispatchEvent(event);
- $(elem).trigger(customEventName, args);
-}
-
-Romo.prototype.ready = function(eventHandlerFn) {
- if (document.readyState === 'complete' || document.readyState !== 'loading') {
- eventHandlerFn();
+ // some browsers return 'function' for HTML elements
+ var isFunction = (
+ typeof(object) === 'function' &&
+ typeof(object.nodeType) !== 'number'
+ );
+ var likeArray = (
+ typeof(value) !== 'string' &&
+ !isFunction &&
+ object !== window &&
+ ( Array.isArray(object) ||
+ length === 0 ||
+ ( typeof(length) === 'number' &&
+ length > 0 &&
+ (length - 1) in object
+ )
+ )
+ );
+ if(likeArray) {
+ return Array.prototype.slice.call(value);
} else {
- this.on(document, 'DOMContentLoaded', eventHandlerFn);
+ return [value];
}
}
-// utils
+Romo.prototype.assign = function(target) {
+ if(Object.assign) {
+ return Object.assign.apply(Object, arguments);
+ } else {
+ if (target == null) { // TypeError if undefined or null
+ throw new TypeError('Cannot convert undefined or null to object');
+ }
+ var to = Object(target);
-Romo.prototype.array = function(collection) {
- return Array.prototype.slice.call(collection);
+ for (var index = 1; index < arguments.length; index++) {
+ var nextSource = arguments[index];
+
+ if (nextSource != null) { // Skip over if undefined or null
+ for (var nextKey in nextSource) {
+ // Avoid bugs when hasOwnProperty is shadowed
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+ to[nextKey] = nextSource[nextKey];
+ }
+ }
+ }
+ }
+ return to;
+ }
}
Romo.prototype.proxy = function(fn, context) {
var proxyFn = fn.bind(context);
proxyFn._romofid = this._fn(fn)._romofid;
return proxyFn;
}
-// TODO: rework w/o jQuery
-Romo.prototype.toArray = function(elems) {
- // converts a collection of elements `$()` to an array of nodes
- return $.map(elems, function(node){ return node; })
-}
-
Romo.prototype.nonInputTextKeyCodes = function() {
// https://css-tricks.com/snippets/javascript/javascript-keycodes/
return [
9, /* tab */
13, /* enter */
@@ -601,13 +837,17 @@
Romo.prototype._eid = 1;
Romo.prototype._fid = 1;
Romo.prototype._el = function(elem) {
- elem._romoeid || (
- elem._romoeid = (this.attr(elem, 'data-romo-eid') || this.setAttr(elem, 'data-romo-eid', this._eid++))
- );
+ if (!elem._romoeid) {
+ if (elem !== window && elem !== document) {
+ elem._romoeid = (this.data(elem, 'romo-eid') || this.setData(elem, 'romo-eid', this._eid++));
+ } else {
+ elem._romoeid = elem === window ? 'window' : 'document';
+ }
+ }
return elem;
}
Romo.prototype._fn = function(fn) {
fn._romofid || (fn._romofid = this._fid++);
@@ -620,81 +860,268 @@
return 'eid--'+this._el(elem)._romoeid+'--'+eventName+'--fid--'+this._fn(fn)._romofid;
}
Romo.prototype._deserializeValue = function(value) {
try {
- if (value === "true") { return true; } // "true" => true
- if (value === "false") { return false; } // "false" => false
- if (value === "undefined") { return undefined; } // "undefined" => undefined
- if (value === "null") { return null; } // "null" => null
- if (+value+"" === value) { return +value; } // "42.5" => 42.5
- if (/^[\[\{]/.test(value)) { JSON.parse(value); } // JSON => parse if valid
- return value; // String => self
+ if (value === "true") { return true; } // "true" => true
+ if (value === "false") { return false; } // "false" => false
+ if (value === "undefined") { return undefined; } // "undefined" => undefined
+ if (value === "null") { return null; } // "null" => null
+ if (value === null) { return undefined; } // null => undefined
+ if (+value+"" === value) { return +value; } // "42.5" => 42.5
+ if (/^[\[\{]/.test(value)) { return JSON.parse(value); } // JSON => parse if valid
+ return value; // String => self
} catch(e) {
return value
}
}
-// TODO: rework w/o jQuery
-Romo.prototype._addEventCallback = function(name, callback) {
- this._eventCallbacks.push({ eventName: name, callback: callback });
+Romo.prototype._overflowScrollableRegex = /(auto|scroll)/;
+Romo.prototype._elemsTagNameRegEx = /<([a-z0-9-]+)[\s\/>]+/i;
+
+Romo.prototype._elemsWrapMap = {
+ 'caption': [1, "<table>", "</table>"],
+ 'colgroup': [1, "<table>", "</table>"],
+ 'col': [2, "<table><colgroup>", "</colgroup></table>"],
+ 'thead': [1, "<table>", "</table>"],
+ 'tbody': [1, "<table>", "</table>"],
+ 'tfoot': [1, "<table>", "</table>"],
+ 'tr': [2, "<table><tbody>", "</tbody></table>"],
+ 'th': [3, "<table><tbody><tr>", "</tr></tbody></table>"],
+ 'td': [3, "<table><tbody><tr>", "</tr></tbody></table>"]
+};
+
+Romo.prototype._elemsInitComponents = {};
+
+Romo.prototype._elemsInitTrigger = function(onElems) {
+ for (var selector in this._elemsInitComponents) {
+ var componentClass = this._elemsInitComponents[selector];
+ this._elemsInitFind(onElems, selector).forEach(function(initElem){ new componentClass(initElem); });
+ }
+ return onElems;
}
-// RomoParentChildElems (TODO: rework w/o jQuery)
+Romo.prototype._elemsInitFind = function(onElems, selector) {
+ var elems = onElems.filter(function(onElem){ return Romo.is(onElem, selector); });
+ return elems.concat(Romo.find(onElems, selector));;
+}
+// RomoComponent
+
+var RomoComponent = function(constructorFn) {
+ var component = function() {
+ RomoComponent.addEventFunctions(this);
+ constructorFn.apply(this, arguments);
+ }
+ component.prototype.romoEvFn = {};
+ component.prototype.doInit = function() {} // override as needed
+ return component;
+}
+
+RomoComponent.addEventFunctions = function(klassInstance) {
+ for(var name in klassInstance.romoEvFn) {
+ klassInstance[name] = RomoComponent.eventProxyFn(klassInstance.romoEvFn[name]);
+ }
+}
+
+RomoComponent.eventProxyFn = function(fn) {
+ return function(){ return fn.apply(this, arguments); };
+}
+
+// RomoPopupStack
+
+var RomoPopupStack = function() {
+ this.popupSelector = undefined;
+ this.styleClasses = [];
+ this.items = [];
+
+ this._buildItemClass();
+}
+
+RomoPopupStack.prototype.doInit = function(styleClass) {
+ this.bodyElem = Romo.f('body')[0];
+ Romo.on(this.bodyElem, 'click', Romo.proxy(this._onBodyClick, this));
+ Romo.on(this.bodyElem, 'keyup', Romo.proxy(this._onBodyKeyUp, this));
+ Romo.on(window, 'resize', Romo.proxy(this._onWindowResize, this));
+ Romo.on(window, 'scroll', Romo.proxy(this._onWindowScroll, this));
+}
+
+RomoPopupStack.prototype.addStyleClass = function(styleClass) {
+ this.styleClasses.push(styleClass);
+ this.popupSelector = this.styleClasses.map(function(s){ return '.'+s; }).join(', ');
+}
+
+RomoPopupStack.prototype.addElem = function(popupElem, boundOpenFn, boundCloseFn, boundPlaceFn) {
+ this.items.push(new this.itemClass(popupElem, boundCloseFn, boundPlaceFn));
+
+ // allow any body click events to propagate and run first. This ensures
+ // any existing stack is in the appropriate state before opening a new popup.
+ Romo.pushFn(boundOpenFn);
+}
+
+RomoPopupStack.prototype.closeThru = function(popupElem) {
+ // allow any body click events to propagate and run first. This ensures
+ // any existing stack is in the appropriate state before opening a new popup.
+ Romo.pushFn(Romo.proxy(function() {
+ if (this._includes(popupElem)) {
+ this.closeTo(popupElem);
+ this._closeTop();
+ }
+ }, this));
+}
+
+RomoPopupStack.prototype.closeTo = function(popupElem) {
+ if (this._includes(popupElem)) {
+ while (this.items.length > 0 && !this.items[this.items.length-1].isFor(popupElem)) {
+ this._closeTop();
+ }
+ }
+}
+
+RomoPopupStack.prototype.placeAllPopups = function(includingFixed) {
+ this.items.filter(function(item) {
+ return includingFixed || Romo.css(item.popupElem, 'position') !== 'fixed';
+ }).forEach(function(item){
+ item.placeFn();
+ });
+}
+
+// private
+
+RomoPopupStack.prototype._buildItemClass = function() {
+ this.itemClass = function(popupElem, closeFn, placeFn) {
+ this.popupElem = popupElem;
+ this.closeFn = closeFn;
+ this.placeFn = placeFn;
+ }
+ this.itemClass.prototype.isFor = function(popupElem) {
+ return this.popupElem === popupElem;
+ }
+}
+
+RomoPopupStack.prototype._closeTop = function() {
+ var item;
+ if (this.items.length > 0) {
+ item = this.items.pop();
+ item.closeFn();
+ Romo.trigger(this.bodyElem, 'romoPopupStack:popupClose', [item.popupElem, this]);
+ Romo.trigger(item.popupElem, 'romoPopupStack:popupClose', [this]);
+ }
+ return item;
+}
+
+RomoPopupStack.prototype._closeAll = function() {
+ while (this.items.length > 0) {
+ this._closeTop();
+ }
+}
+
+RomoPopupStack.prototype._includes = function(popupElem) {
+ return this.items.reduce(function(included, item) {
+ return included || item.isFor(popupElem);
+ }, false);
+}
+
+RomoPopupStack.prototype._onBodyClick = function(e) {
+ var popupElem = undefined;
+ if (Romo.is(e.target, this.popupSelector)) {
+ popupElem = e.target;
+ } else {
+ popupElem = Romo.parents(e.target, this.popupSelector)[0];
+ }
+
+ if (popupElem === undefined || !this._includes(popupElem)) {
+ this._closeAll();
+ } else {
+ this.closeTo(popupElem);
+ }
+}
+
+RomoPopupStack.prototype._onBodyKeyUp = function(e) {
+ var closedItem;
+ if (e.keyCode === 27 /* Esc */) {
+ closedItem = this._closeTop();
+ if (closedItem) {
+ Romo.trigger(closedItem.popupElem, 'romoPopupStack:popupClosedByEsc', [this]);
+ }
+ }
+}
+
+RomoPopupStack.prototype._onWindowResize = function(e) {
+ this.placeAllPopups(true);
+}
+
+RomoPopupStack.prototype._onWindowScroll = function(e) {
+ this.placeAllPopups();
+}
+
+// RomoParentChildElems
+
var RomoParentChildElems = function() {
this.attrName = 'romo-parent-elem-id';
this.elemId = 0;
this.elems = {};
+}
- var parentRemovedObserver = new MutationObserver($.proxy(function(mutationRecords) {
- mutationRecords.forEach($.proxy(function(mutationRecord) {
+RomoParentChildElems.prototype.doInit = function(parentElem, childElems) {
+ var parentRemovedObserver = new MutationObserver(Romo.proxy(function(mutationRecords) {
+ mutationRecords.forEach(Romo.proxy(function(mutationRecord) {
if (mutationRecord.type === 'childList' && mutationRecord.removedNodes.length > 0) {
- $.each($(mutationRecord.removedNodes), $.proxy(function(idx, node) {
- this.remove($(node));
+ mutationRecord.removedNodes.forEach(Romo.proxy(function(removedNode) {
+ this.remove(removedNode);
}, this));
}
}, this));
}, this));
- parentRemovedObserver.observe($('body')[0], {
+ parentRemovedObserver.observe(Romo.f('body')[0], {
childList: true,
subtree: true
});
}
RomoParentChildElems.prototype.add = function(parentElem, childElems) {
- parentElem.attr('data-'+this.attrName, this._push(childElems, parentElem.data(this.attrName)));
+ // delay adding b/c the parent elem may be manipulated in the DOM resulting in the parent elem
+ // being removed and then re-added to the DOM. if the child elems are associated immediately,
+ // any "remove" from DOM manipulation would incorrectly remove the popup.
+ Romo.pushFn(Romo.proxy(function() {
+ Romo.setData(parentElem, this.attrName, this._push(childElems, Romo.data(parentElem, this.attrName)));
+ }, this));
}
-RomoParentChildElems.prototype.remove = function(nodeElem) {
- if (nodeElem.data('romo-parent-removed-observer-disabled') !== true) {
- if (nodeElem.data(this.attrName) !== undefined) {
- this._removeChildElems(nodeElem); // node is a parent elem itself
+RomoParentChildElems.prototype.remove = function(elemNode) {
+ if (elemNode.nodeType !== Node.ELEMENT_NODE){ return false; }
+
+ if (Romo.data(elemNode, 'romo-parent-removed-observer-disabled') !== true) {
+ if (Romo.data(elemNode, this.attrName) !== undefined) {
+ // node is a parent elem itself
+ this._removeChildElems(elemNode);
}
- $.each(nodeElem.find('[data-'+this.attrName+']'), $.proxy(function(idx, parent) {
- this._removeChildElems($(parent));
+ Romo.find(elemNode, '[data-'+this.attrName+']').forEach(Romo.proxy(function(childParentElem) {
+ this._removeChildElems(childParentElem);
}, this));
}
}
-// private RomoParentChildElems
+// private
RomoParentChildElems.prototype._removeChildElems = function(parentElem) {
- $.each(this._pop(parentElem.data(this.attrName)), function(idx, elem) {
- $(elem).remove();
+ this._pop(Romo.data(parentElem, this.attrName)).forEach(function(childElem) {
+ Romo.remove(childElem);
+ Romo.trigger(childElem, 'romoParentChildElems:childRemoved', [childElem]);
});
};
RomoParentChildElems.prototype._push = function(items, id) {
if (id === undefined) {
id = String(this.elemId++);
}
if (this.elems[id] === undefined) {
this.elems[id] = []
}
- items.forEach($.proxy(function(item){ this.elems[id].push(item) }, this));
+ this.elems[id] = this.elems[id].concat(items);
+
return id;
};
RomoParentChildElems.prototype._pop = function(id) {
var items = this.elems[id];
@@ -702,11 +1129,9 @@
return items || [];
}
// Init
-window.Romo = new Romo();
-
-// TODO: rework w/o jQuery
-$(function() {
+var Romo = new Romo();
+Romo.ready(function() {
Romo.doInit();
-})
+});