From a7320cf2e2e9e74ab68032f2ad4c96a6fe87ca3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Tue, 13 Nov 2012 22:58:03 -0800 Subject: [PATCH] Implement new DomElement micro lib, supporting creation of elements, getting and setting of style, content, and events, in an elegant fashion. --- src/browser/DomElement.js | 215 +++++++++++++++++++++++++++----------- src/browser/DomEvent.js | 4 +- src/ui/View.js | 2 +- 3 files changed, 157 insertions(+), 64 deletions(-) diff --git a/src/browser/DomElement.js b/src/browser/DomElement.js index b78da03e..b3dc2090 100644 --- a/src/browser/DomElement.js +++ b/src/browser/DomElement.js @@ -19,67 +19,160 @@ * @namespace * @private */ -var DomElement = { - getBounds: function(el, viewport) { - var rect = el.getBoundingClientRect(), - doc = el.ownerDocument, - body = doc.body, - docEl = doc.documentElement, - x = rect.left - (docEl.clientLeft || body.clientLeft || 0), - y = rect.top - (docEl.clientTop || body.clientTop || 0); - if (!viewport) { - var win = DomElement.getViewport(doc); - x += win.pageXOffset || docEl.scrollLeft || body.scrollLeft; - y += win.pageYOffset || docEl.scrollTop || body.scrollTop; +var DomElement = new function() { + + // We use a mix of Bootstrap.js legacy and Bonzo.js magic, ported over and + // furhter simplified to a subset actually required by Paper.js + + var special = /^(checked|value|selected|disabled)$/i, + translated = { text: 'textContent', html: 'innerHTML' }, + unitless = { lineHeight: 1, zoom: 1, zIndex: 1, opacity: 1 }; + + function create(nodes, parent) { + var res = []; + for (var i = 0, l = nodes && nodes.length; i < l;) { + var tag = nodes[i++]; + if (typeof tag === 'string') { + var el = document.createElement(tag); + // Do we have attributes? + if (typeof nodes[i] === 'object' && !Array.isArray(nodes[i])) + DomElement.set(el, nodes[i++]); + // Do we have children? + if (Array.isArray(nodes[i])) + create(nodes[i++], el); + // Are we adding to a parent? + if (parent) + parent.appendChild(el); + res.push(el); + } } - return new Rectangle(x, y, rect.width, rect.height); - }, - - getOffset: function(el, viewport) { - return this.getBounds(el, viewport).getPoint(); - }, - - getSize: function(el) { - return this.getBounds(el, true).getSize(); - }, - - /** - * Checks if element is invisibile (display: none, ...) - */ - isInvisible: function(el) { - return this.getSize(el).equals([0, 0]); - }, - - /** - * Checks if element is visibile in current viewport - */ - isVisible: function(el) { - // See if the viewport bounds intersect with the windows rectangle - // which always starts at 0, 0 - return !this.isInvisible(el) && this.getViewportBounds(el).intersects( - this.getBounds(el, true)); - }, - - getViewport: function(doc) { - return doc.defaultView || doc.parentWindow; - }, - - getViewportBounds: function(el) { - var doc = el.ownerDocument, - view = this.getViewport(doc), - body = doc.getElementsByTagName( - doc.compatMode === 'CSS1Compat' ? 'html' : 'body')[0]; - return Rectangle.create(0, 0, - view.innerWidth || body.clientWidth, - view.innerHeight || body.clientHeight - ); - }, - - getComputedStyle: function(el, name) { - if (el.currentStyle) - return el.currentStyle[Base.camelize(name)]; - var style = this.getViewport(el.ownerDocument) - .getComputedStyle(el, null); - return style ? style.getPropertyValue(Base.hyphenate(name)) : null; + return res; } + + return { + create: function(tag, attributes, children) { + var res = create(arguments); + return res.length == 1 ? res[0] : res; + }, + + find: function(selector) { + return document.querySelector(selector); + }, + + findAll: function(selector) { + return document.querySelectorAll(selector); + }, + + get: function(el, key) { + return el + ? special.test(key) + ? key === 'value' + ? el[key] + : typeof el[key] === 'string' + : key in translated + ? el[translated[key]] + : el.getAttribute(key) + : null; + }, + + set: function(el, key, value) { + if (typeof key !== 'string') { + for (var name in key) + if (key.hasOwnProperty(name)) + this.set(el, name, key[name]); + } else if (special.test(key)) { + el[key] = value; + } else if (key in translated) { + el[translated[key]] = value; + } else if (key === 'style') { + this.setStyle(el, value); + } else if (key === 'events') { + DomEvent.add(el, value); + } else { + el.setAttribute(key, value); + } + return el; + }, + + getStyle: function(el, key) { + var style = el.ownerDocument.defaultView.getComputedStyle(el, ''); + return el.style[key] || style && style[key] || null; + }, + + setStyle: function(el, key, value) { + if (typeof key !== 'string') { + for (var name in key) + if (key.hasOwnProperty(name)) + this.setStyle(el, name, key[name]); + } else { + if (/^-?[\d\.]+$/.test(value) && !(key in unitless)) + value += 'px'; + el.style[key] = value; + } + return el; + }, + + hasClass: function(el, cls) { + return new RegExp('\\s*' + cls + '\\s*').test(el.className); + }, + + addClass: function(el, cls) { + el.className = (el.className + ' ' + cls).trim(); + }, + + removeClass: function(el, cls) { + el.className = el.className.replace( + new RegExp('\\s*' + cls + '\\s*'), ' ').trim(); + }, + + getBounds: function(el, viewport) { + var rect = el.getBoundingClientRect(), + doc = el.ownerDocument, + body = doc.body, + html = doc.documentElement, + x = rect.left - (html.clientLeft || body.clientLeft || 0), + y = rect.top - (html.clientTop || body.clientTop || 0); + if (!viewport) { + var view = doc.defaultView; + x += view.pageXOffset || html.scrollLeft || body.scrollLeft; + y += view.pageYOffset || html.scrollTop || body.scrollTop; + } + return new Rectangle(x, y, rect.width, rect.height); + }, + + getViewportBounds: function(el) { + var doc = el.ownerDocument, + view = doc.defaultView, + html = doc.documentElement; + return Rectangle.create(0, 0, + view.innerWidth || html.clientWidth, + view.innerHeight || html.clientHeight + ); + }, + + getOffset: function(el, viewport) { + return this.getBounds(el, viewport).getPoint(); + }, + + getSize: function(el) { + return this.getBounds(el, true).getSize(); + }, + + /** + * Checks if element is invisibile (display: none, ...) + */ + isInvisible: function(el) { + return this.getSize(el).equals([0, 0]); + }, + + /** + * Checks if element is visibile in current viewport + */ + isInView: function(el) { + // See if the viewport bounds intersect with the windows rectangle + // which always starts at 0, 0 + return !this.isInvisible(el) && this.getViewportBounds(el).intersects( + this.getBounds(el, true)); + } + }; }; diff --git a/src/browser/DomEvent.js b/src/browser/DomEvent.js index 4e142cfc..27e42257 100644 --- a/src/browser/DomEvent.js +++ b/src/browser/DomEvent.js @@ -102,7 +102,7 @@ DomEvent.requestAnimationFrame = new function() { // is defined in callbacks, and if not, clear request again so we won't // use the faulty method. request(function(time) { - if (time == undefined) + if (time == null) request = null; }); } @@ -140,7 +140,7 @@ DomEvent.requestAnimationFrame = new function() { func = entry[0], el = entry[1]; if (!el || (PaperScript.getAttribute(el, 'keepalive') == 'true' - || focused) && DomElement.isVisible(el)) { + || focused) && DomElement.isInView(el)) { // Handle callback and remove it from callbacks list. callbacks.splice(i, 1); func(Date.now()); diff --git a/src/ui/View.js b/src/ui/View.js index 9f1aa626..37da8ed9 100644 --- a/src/ui/View.js +++ b/src/ui/View.js @@ -306,7 +306,7 @@ var View = this.View = Base.extend(Callback, /** @lends View# */{ * @return {Boolean} Whether the view is visible. */ isVisible: function() { - return DomElement.isVisible(this._element); + return DomElement.isInView(this._element); }, /**