lib/terminus/public/terminus.js in terminus-0.4.0 vs lib/terminus/public/terminus.js in terminus-0.5.0

- old
+ new

@@ -1,55 +1,55 @@ Terminus = { isIE: /\bMSIE\b/.test(navigator.userAgent), - + connect: function(host, port) { if (this._bayeux) return; - + this._host = host; this._pageId = Faye.random(); this._id = window.name = window.name || document.name || Faye.random(); this._id = this._id.split('|')[0]; - + var iframes = document.getElementsByTagName('iframe'), i = iframes.length; while (i--) iframes[i].contentDocument.name = iframes[i].id; - + this.Registry.initialize(); this.Worker.initialize(); this.AjaxMonitor.initialize(); - + Faye.Event.on(window, 'beforeunload', function() { Terminus.disabled = true }); - + var endpoint = 'http://' + host + ':' + port + '/messaging', bayeux = this._bayeux = new Faye.Client(endpoint), self = this; - + bayeux.addExtension({ outgoing: function(message, callback) { message.href = window.location.href; if (message.connectionType === 'websocket') self._socketCapable = true; callback(message); } }); - + this.getId(function(id) { var url = window.name.split('|')[1]; - + if (!url) bayeux.subscribe('/terminus/sockets/' + id, function(message) { window.name += '|' + message.url; this.openSocket(message.url); }, this); - + var sub = bayeux.subscribe('/terminus/clients/' + id, this.handleMessage, this); sub.callback(function() { this.ping(); if (url) this.openSocket(url); }, this); }, this); }, - + browserDetails: function(callback, context) { this.getId(function(id) { callback.call(context, { host: this._host, id: id, @@ -59,15 +59,15 @@ ua: navigator.userAgent, url: window.location.href }); }, this); }, - + getId: function(callback, context) { var id = this._id; if (this.isIE) return callback.call(context, id); - + if (opener && opener.Terminus) { opener.Terminus.getId(function(prefix) { callback.call(context, prefix + '/' + id); }); } else if (parent && parent !== window) { @@ -80,18 +80,18 @@ getParentId(); } else { callback.call(context, id); } }, - + openSocket: function(endpoint) { if (this.disabled || this._socket) return; - + var self = this, WS = window.MozWebSocket || window.WebSocket, ws = new WS(endpoint); - + ws.onopen = function() { self._socket = ws; up = true; }; ws.onclose = function() { @@ -104,192 +104,228 @@ }; ws.onmessage = function(event) { self.handleMessage(JSON.parse(event.data)); }; }, - + ping: function() { if (this.disabled) return; - + this.browserDetails(function(details) { this._bayeux.publish('/terminus/ping', details); var self = this; setTimeout(function() { self.ping() }, 3000); }, this); }, - + handleMessage: function(message) { var command = message.command, method = command.shift(), driver = this.Driver, worker = this.Worker, posted = false, self = this; - + command.push(function(result) { if (posted) return; self.postResult(message.commandId, result); posted = true; }); - + worker.monitor = true; driver[method].apply(driver, command); worker.monitor = false; }, - + postResult: function(commandId, result) { if (this.disabled || !commandId) return; - + if (this._socket) return this._socket.send(JSON.stringify({value: result})); - + this.getId(function(id) { this._bayeux.publish('/terminus/results', { id: id, commandId: commandId, result: result }); }, this); }, - + getAttribute: function(node, name) { return Terminus.isIE ? (node.getAttributeNode(name) || {}).nodeValue || false : node.getAttribute(name); }, - + + hideNodes: function(root, list) { + if (!root) return list; + list = list || []; + + var isScript = (root.tagName || '').toLowerCase() === 'script', + isHidden = (root.style || {}).display === 'none'; + + if (isScript || isHidden) { + var parent = root.parentNode, next = root.nextSibling; + if (!isScript) list.push([root, parent, next]); + if (parent) parent.removeChild(root); + } else { + var children = root.childNodes || []; + for (var i = 0, n = children.length; i < n; i++) { + this.hideNodes(children[i], list); + } + } + return list; + }, + + showNodes: function(hidden) { + var hide, node, parent, next; + for (var i = 0, n = hidden.length; i < n; i++) { + hide = hidden[i]; + node = hide[0]; + parent = hide[1]; + next = hide[2]; + + if (!parent) continue; + if (next) parent.insertBefore(node, next); + else parent.appendChild(node); + } + }, + Driver: { _node: function(id) { return Terminus.Registry.get(id); }, - + attribute: function(nodeId, name, callback) { var node = this._node(nodeId); if (!node) return callback(null); - + if (!Terminus.isIE && (name === 'checked' || name === 'selected')) { callback(!!node[name]); + } else if (node.tagName.toLowerCase() === 'textarea' && name === 'type') { + callback('textarea'); } else { callback(Terminus.getAttribute(node, name)); } }, - + set_attribute: function(nodeId, name, value, callback) { var node = this._node(nodeId); if (!node) return callback(null); node.setAttribute(name, value); callback(true); }, - + body: function(callback) { var html = document.getElementsByTagName('html')[0]; callback(html.outerHTML || '<html>\n' + html.innerHTML + '\n</html>\n'); }, - + clear_cookies: function(callback) { var cookies = document.cookie.split(';'), name; - + var expiry = new Date(); expiry.setTime(expiry.getTime() - 24*60*60*1000); - + for (var i = 0, n = cookies.length; i < n; i++) { name = cookies[i].split('=')[0]; document.cookie = name + '=; expires=' + expiry.toGMTString() + '; path=/'; } callback(true); }, - + click: function(nodeId, options, callback) { var element = this._node(nodeId), timeout = options.resynchronization_timeout; - + if (!element) return callback(true); - + Syn.trigger('click', {}, element); - + if (options.resynchronize === false) return callback(true); - + if (timeout) Terminus.Worker._setTimeout.call(window, function() { callback('failed to resynchronize, ajax request timed out'); }, 1000 * timeout); - + Terminus.Worker.callback(function() { callback(true); }); }, - + current_url: function(callback) { Terminus.browserDetails(function(details) { callback(details.url); }); }, - + drag: function(options, callback) { var draggable = this._node(options.from), droppable = this._node(options.to); - + if (!draggable || !droppable) return callback(null); - + Syn.drag({to: droppable}, draggable, function() { callback(true); }); }, - + evaluate: function(expression, callback) { callback(eval(expression)); }, - + execute: function(expression, callback) { eval(expression); callback(true); }, - + find: function(xpath, nodeId, callback) { var root = nodeId ? this._node(nodeId) : document; if (!root) return callback([]); - + var result = document.evaluate(xpath, root, null, XPathResult.ANY_TYPE, null), list = [], element; - + while (element = result.iterateNext()) list.push(Terminus.Registry.put(element)); - + return callback(list); }, - + is_visible: function(nodeId, callback) { var node = this._node(nodeId); if (!node) return callback(null); - + while (node.tagName && node.tagName.toLowerCase() !== 'body') { if (node.style.display === 'none' || node.type === 'hidden') return callback(false); node = node.parentNode; } callback(true); }, - + select: function(nodeId, callback) { var option = this._node(nodeId); if (!option) return callback(null); option.selected = true; Syn.trigger('change', {}, option.parentNode); callback(true); }, - + set: function(nodeId, value, callback) { var field = this._node(nodeId), max = Terminus.getAttribute(field, 'maxlength'); - + if (!field) return callback(null); if (field.type === 'file') return callback('not_allowed'); - + Syn.trigger('focus', {}, field); Syn.trigger('click', {}, field); - + switch (typeof value) { case 'string': if (max) value = value.substr(0, parseInt(max)); field.value = value; break; @@ -298,137 +334,146 @@ break; } Syn.trigger('change', {}, field); callback(true); }, - + tag_name: function(nodeId, callback) { var node = this._node(nodeId); if (!node) return callback(null); callback(node.tagName.toLowerCase()); }, - + text: function(nodeId, callback) { var node = this._node(nodeId); if (!node) return callback(null); - - var text = node.textContent || node.innerText || '', - scripts = node.getElementsByTagName('script'), - i = scripts.length; - - while (i--) text = text.replace(scripts[i].textContent || scripts[i].innerText, ''); - text = text.replace(/^\s*|\s*$/g, ''); + + var hidden = Terminus.hideNodes(node), + title = document.title; + + document.title = ''; + + var text = node.textContent || node.innerText || ''; + + document.title = title; + Terminus.showNodes(hidden); + + text = text.replace(/^\s*|\s*$/g, '').replace(/\s+/g, ' '); callback(text); }, - + trigger: function(nodeId, eventType, callback) { var node = this._node(nodeId); if (!node) return callback(null); Syn.trigger(eventType, {}, node); callback(true); }, - + unselect: function(nodeId, callback) { var option = this._node(nodeId); if (!option) return callback(null); if (!option.parentNode.multiple) return callback(false); option.selected = false; Syn.trigger('change', {}, option.parentNode); callback(true); }, - + value: function(nodeId, callback) { var node = this._node(nodeId); if (!node) return callback(null); - + if (node.tagName.toLowerCase() !== 'select' || !node.multiple) return callback(node.value); - + var options = node.children, values = []; - + for (var i = 0, n = options.length; i < n; i++) { if (options[i].selected) values.push(options[i].value); } callback(values); }, - + visit: function(url, callback) { window.location.href = url; callback(url); } }, - + Registry: { initialize: function() { this._namespace = new Faye.Namespace(); this._elements = {}; }, - + get: function(id) { var node = this._elements[id], root = node; while (root && root.tagName !== 'BODY' && root.tagName !== 'HTML') root = root.parentNode; if (!root) return null; return node; }, - + put: function(element) { - var id = this._namespace.generate(); + var id = element['data-terminus-id']; + if (!id) { + id = this._namespace.generate(); + element['data-terminus-id'] = id; + } this._elements[id] = element; return id; } }, - + Worker: { initialize: function() { this._callbacks = []; this._pending = 0; - + if (!Terminus.isIE) this._wrapTimeouts(); }, - + callback: function(callback, scope) { if (this._pending === 0) { if (this._setTimeout) this._setTimeout.call(window, function() { callback.call(scope) }, 0); else setTimeout(function() { callback.call(scope) }, 0); } else { this._callbacks.push([callback, scope]); } }, - + suspend: function() { this._pending += 1; }, - + resume: function() { if (this._pending === 0) return; this._pending -= 1; if (this._pending !== 0) return; - + var callback; for (var i = 0, n = this._callbacks.length; i < n; i++) { callback = this._callbacks[i]; callback[0].call(callback[1]); } this._callbacks = []; }, - + _wrapTimeouts: function() { var timeout = window.setTimeout, clear = window.clearTimeout, timeouts = {}, self = this; - + var finish = function(id) { if (!timeouts.hasOwnProperty(id)) return; delete timeouts[id]; self.resume(); }; - + window.setTimeout = function(callback, delay) { var id = timeout.call(window, function() { try { switch (typeof callback) { case 'function': callback(); break; @@ -436,50 +481,50 @@ } } finally { finish(id); } }, delay); - + if (self.monitor) { timeouts[id] = true; self.suspend(); } return id; }; - + window.clearTimeout = function(id) { finish(id); return clear(id); }; - + this._setTimeout = timeout; } }, - + AjaxMonitor: { initialize: function() { if (window.jQuery) this._patchJquery(); }, - + _patchJquery: function() { var ajax = jQuery.ajax; jQuery.ajax = function(url, settings) { var options = ((typeof url === 'string') ? settings : url) || {}, complete = options.complete, monitor = Terminus.Worker.monitor; - + options.complete = function() { var result; try { result = complete.apply(this, arguments); } finally { if (monitor) Terminus.Worker.resume(); } return result; }; - + if (monitor) Terminus.Worker.suspend(); - + if (typeof url === 'string') return ajax.call(jQuery, url, options); else return ajax.call(jQuery, options); };