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

- old
+ new

@@ -1,99 +1,182 @@ Terminus = { isIE: /\bMSIE\b/.test(navigator.userAgent), - connect: function(endpoint) { - if (this._client) return; + connect: function(host, port) { + if (this._bayeux) return; + this._host = host; this._pageId = Faye.random(); - this._id = window.name = window.name || 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 client = this._client = new Faye.Client(endpoint); + var endpoint = 'http://' + host + ':' + port + '/messaging', + bayeux = this._bayeux = new Faye.Client(endpoint), + self = this; - var subscription = this._client.subscribe('/terminus/clients/' + this.getId(), function(message) { - var command = message.command, - method = command.shift(), - driver = this.Driver, - worker = this.Worker, - posted = false, - 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]; - command.push(function(result) { - if (posted) return; - self.postResult(message.commandId, result); - posted = true; - }); + if (!url) + bayeux.subscribe('/terminus/sockets/' + id, function(message) { + window.name += '|' + message.url; + this.openSocket(message.url); + }, this); - worker.monitor = true; - driver[method].apply(driver, command); - worker.monitor = false; + var sub = bayeux.subscribe('/terminus/clients/' + id, this.handleMessage, this); + sub.callback(function() { + this.ping(); + if (url) this.openSocket(url); + }, this); }, this); - - subscription.callback(this.ping, this); }, - getId: function() { - try { return window.opener.Terminus.getId() + '/' + this._id } - catch (e) { return this._id } + browserDetails: function(callback, context) { + this.getId(function(id) { + callback.call(context, { + host: this._host, + id: id, + infinite: !!window.TERMINUS_INFINITE_REDIRECT, + page: this._pageId, + sockets: this._socketCapable, + ua: navigator.userAgent, + url: window.location.href + }); + }, this); }, - browserDetails: function() { - return { - id: this.getId(), - parent: (parent && parent !== window) ? parent.name : null, - page: this._pageId, - ua: navigator.userAgent, - url: window.location.href, - infinite: !!window.TERMINUS_INFINITE_REDIRECT - }; + 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) { + var getParentId = function() { + if (!parent.Terminus) return setTimeout(getParentId, 100); + parent.Terminus.getId(function(prefix) { + callback.call(context, prefix + '/' + id); + }); + }; + getParentId(); + } else { + callback.call(context, id); + } }, - getAttribute: function(node, name) { - return Terminus.isIE ? node.getAttributeNode(name).nodeValue - : node.getAttribute(name); + 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() { + var up = !!self._socket; + self._socket = null; + if (up) + self.openSocket(endpoint); + else + window.name = window.name.split('|')[0]; + }; + ws.onmessage = function(event) { + self.handleMessage(JSON.parse(event.data)); + }; }, ping: function() { if (this.disabled) return; - this._client.publish('/terminus/ping', this.browserDetails()); - var self = this; - setTimeout(function() { self.ping() }, 3000); + 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; - this._client.publish('/terminus/results', { - id: this.getId(), - commandId: commandId, - result: result - }); + 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); + }, + 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 (name === 'checked' || name === 'selected') - return callback(!!node[name]); - - callback(Terminus.getAttribute(node, name)); + if (!Terminus.isIE && (name === 'checked' || name === 'selected')) { + callback(!!node[name]); + } 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) { @@ -110,46 +193,62 @@ for (var i = 0, n = cookies.length; i < n; i++) { name = cookies[i].split('=')[0]; document.cookie = name + '=; expires=' + expiry.toGMTString() + '; path=/'; } - callback(); + 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(); + 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(callback); + 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); - Syn.drag({to: droppable}, draggable, callback); + + 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(); + callback(true); }, find: function(xpath, nodeId, callback) { - var root = this._node(nodeId) || document; + var root = nodeId ? this._node(nodeId) : document; + if (!root) return callback([]); var result = document.evaluate(xpath, root, null, XPathResult.ANY_TYPE, null), list = [], element; @@ -157,17 +256,13 @@ list.push(Terminus.Registry.put(element)); return callback(list); }, - frame_src: function(name, callback) { - var frame = document.getElementById(name); - callback(frame.src); - }, - 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; @@ -175,19 +270,21 @@ 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); @@ -199,44 +296,52 @@ case 'boolean': field.checked = value; break; } Syn.trigger('change', {}, field); - callback(); + callback(true); }, tag_name: function(nodeId, callback) { - callback(this._node(nodeId).tagName.toLowerCase()); + var node = this._node(nodeId); + if (!node) return callback(null); + callback(node.tagName.toLowerCase()); }, text: function(nodeId, callback) { - var node = this._node(nodeId), - text = node.textContent || node.innerText || '', + 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, ''); callback(text); }, trigger: function(nodeId, eventType, callback) { var node = this._node(nodeId); + if (!node) return callback(null); Syn.trigger(eventType, {}, node); - callback(); + 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 = []; @@ -247,22 +352,26 @@ callback(values); }, visit: function(url, callback) { window.location.href = url; - callback(); + callback(url); } }, Registry: { initialize: function() { this._namespace = new Faye.Namespace(); this._elements = {}; }, get: function(id) { - return this._elements[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(); this._elements[id] = element; @@ -273,17 +382,21 @@ Worker: { initialize: function() { this._callbacks = []; this._pending = 0; - this._wrapTimeouts(); + if (!Terminus.isIE) this._wrapTimeouts(); }, callback: function(callback, scope) { - if (this._pending === 0) - this._setTimeout.call(window, function() { callback.call(scope) }, 0); - else + 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; },