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;
},