diff --git a/evennia/web/webclient/static/webclient/js/evennia.js b/evennia/web/webclient/static/webclient/js/evennia.js index 27ab90a03..20b7c4d1b 100644 --- a/evennia/web/webclient/static/webclient/js/evennia.js +++ b/evennia/web/webclient/static/webclient/js/evennia.js @@ -52,6 +52,7 @@ An "emitter" object must have a function var Evennia = { debug: true, + initialized: false, // Initialize. // startup Evennia emitter and connection. @@ -68,13 +69,19 @@ An "emitter" object must have a function // Evennia.emit to return data to Client. // init: function(opts) { + if (this.initialized) { + // make it safe to call multiple times. + return; + } + this.initialized = true; + opts = opts || {}; this.emitter = opts.emitter || new DefaultEmitter(); if (opts.connection) { this.connection = opts.connection; } - else if (window.WebSocket) { + else if (window.WebSocket && wsactive) { this.connection = new WebsocketConnection(); if (!this.connection) { this.connection = new AjaxCometConnection(); @@ -82,7 +89,7 @@ An "emitter" object must have a function } else { this.connection = new AjaxCometConnection(); } - // this.connection = opts.connection || window.WebSocket ? new WebsocketConnection() : new AjaxCometConnection(); + log('Evennia initialized.') }, // Client -> Evennia. @@ -115,16 +122,17 @@ An "emitter" object must have a function // // Args: // event (event): Event received from Evennia - // data (obj): + // args (array): Arguments to listener + // kwargs (obj): keyword-args to listener // - emit: function (cmdname, data) { - log('emit called with args: + ' + cmdname + ',' + data); - if (data.cmdid) { - cmdmap[data.cmdid].apply(this, [data]); - delete cmdmap[cmddata.cmdid]; + emit: function (cmdname, args, kwargs) { + log('emit called with args: ' + cmdname + ',' + args + ',' + kwargs); + if (kwargs.cmdid) { + cmdmap[kwargs.cmdid].apply(this, [args, kwargs]); + delete cmdmap[kwargs.cmdid]; } else { - this.emitter.emit(cmdname, data); + this.emitter.emit(cmdname, args, kwargs); } }, @@ -135,7 +143,7 @@ An "emitter" object must have a function // the Server. An alternative can be overridden in Evennia.init. // var DefaultEmitter = function () { - var cmdmap = {}; + var listeners = {}; // Emit data to all listeners tied to a given cmdname // // Args: @@ -144,11 +152,11 @@ An "emitter" object must have a function // called as function(kwargs). // kwargs (obj): Argument to the listener. // - var emit = function (cmdname, kwargs) { - log('emit', cmdname, kwargs); + var emit = function (cmdname, args, kwargs) { + log('emit', cmdname, args, kwargs); - if (cmdmap[cmdname]) { - cmdmap[cmdname].apply(this, kwargs); + if (listeners[cmdname]) { + listeners[cmdname].apply(this, [args, kwargs]); }; }; @@ -161,7 +169,7 @@ An "emitter" object must have a function // var on = function (cmdname, listener) { if (typeof(listener === 'function')) { - cmdmap[cmdname] = listener; + listeners[cmdname] = listener; }; }; @@ -171,7 +179,7 @@ An "emitter" object must have a function // cmdname (str): Name of event to handle // var off = function (cmdname) { - delete cmdmap[cmdname] + delete listeners[cmdname] }; return {emit:emit, on:on, off:off} }; @@ -184,17 +192,21 @@ An "emitter" object must have a function // Handle Websocket open event websocket.onopen = function (event) { log('Websocket connection openened. ', event); - Evennia.emit('socket:open', event); + Evennia.emit('socket:open', [], event); }; // Handle Websocket close event websocket.onclose = function (event) { log('WebSocket connection closed.'); - Evennia.emit('socket:close', event); + Evennia.emit('socket:close', [], event); }; // Handle websocket errors websocket.onerror = function (event) { log("Websocket error to ", wsurl, event); - Evennia.emit('socket:error', event.data); + Evennia.emit('socket:error', [], event); + if (websocket.readyState === websocket.CLOSED) { + log("Websocket failed. Falling back to Ajax..."); + Evennia.connection = AjaxCometConnection(); + } }; // Handle incoming websocket data [cmdname, kwargs] websocket.onmessage = function (event) { @@ -203,13 +215,14 @@ An "emitter" object must have a function return; } // Parse the incoming data, send to emitter - // Incoming data is on the form [cmdname, kwargs] + // Incoming data is on the form [cmdname, args, kwargs] data = JSON.parse(data); log("incoming " + data); - Evennia.emit(data[0], data[1]); + Evennia.emit(data[0], data[1], data[2]); }; - websocket.msg = function(data) { - websocket.send(JSON.stringify(data)); + websocket.msg = function(cmdname, args, kwargs) { + // send + websocket.send(JSON.stringify([cmdname, args, kwargs])); }; return websocket; @@ -221,11 +234,11 @@ An "emitter" object must have a function log("Trying ajax ..."); var client_hash = '0'; // Send Client -> Evennia. Called by Evennia.send. - var msg = function(data) { + var msg = function(cmdname, args, kwargs) { $.ajax({type: "POST", url: "/webclientdata", async: true, cache: false, timeout: 30000, dataType: "json", - data: {mode:'input', msg: data, 'suid': client_hash}, + data: {mode:'input', msg: [cmdname, args, kwargs], 'suid': client_hash}, success: function(data) {}, error: function(req, stat, err) { log("COMET: Server returned error. " + err); @@ -243,7 +256,7 @@ An "emitter" object must have a function dataType: "json", data: {mode: 'receive', 'suid': client_hash}, success: function(data) { - Evennia.emit(data[0], data[1]) + Evennia.emit(data[0], data[1], data[2]) }, error: function() { poll() // timeout; immediately re-poll @@ -284,16 +297,10 @@ function log(msg) { } // Called when page has finished loading (kicks the client into gear) -$(document).ready(function(){ - - // a small timeout to stop 'loading' indicator in Chrome - setTimeout(function () { - log('Evennia initialized...') - Evennia.init() - - }, 500); - // set an idle timer to avoid proxy servers to time out on us (every 3 minutes) - setInterval(function() { - log('Idle tick.'); - }, 60000*3); +$(document).ready(function() { + setTimeout( function () { + Evennia.init() + }, + 500 + ); }); diff --git a/evennia/web/webclient/static/webclient/js/webclient_gui.js b/evennia/web/webclient/static/webclient/js/webclient_gui.js index 4287456b7..d7b38fecb 100644 --- a/evennia/web/webclient/static/webclient/js/webclient_gui.js +++ b/evennia/web/webclient/static/webclient/js/webclient_gui.js @@ -13,16 +13,152 @@ */ // -// GUI Helpers +// GUI Elements // +// +// Manage history for input line +// +var inputlog = function() { + var history_max = 21; + var history = new Array(); + var history_pos = 0; + + history[0] = ''; // the very latest input is empty for new entry. + + function history_back() { + // step backwards in history stack + history_pos = Math.min(++history_pos, history.length - 1); + return history[history.length - 1 - history_pos]; + } + function history_fwd() { + // step forwards in history stack + history_pos = Math.max(--history_pos, 0); + return history[history.length -1 - history_pos]; + } + function history_add(input) { + // add a new entry to history, don't repeat latest + if (input != history[history.length-1]) { + if (history.length >= history_max) { + history.shift(); // kill oldest entry + } + history[history.length-1] = input; + history[history.length] = ''; + } + } + return {back: history_back, + fwd: history_fwd, + add: history_add} +}; + +$.fn.appendCaret = function() { + /* jQuery extension that will forward the caret to the end of the input, and + won't harm other elements (although calling this on multiple inputs might + not have the expected consequences). + + Thanks to + http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area + for the good starting point. */ + return this.each(function() { + var range, + // Index at where to place the caret. + end, + self = this; + + if (self.setSelectionRange) { + // other browsers + end = self.value.length; + self.focus(); + // NOTE: Need to delay the caret movement until after the callstack. + setTimeout(function() { + self.setSelectionRange(end, end); + }, 0); + } + else if (self.createTextRange) { + // IE + end = self.value.length - 1; + range = self.createTextRange(); + range.collapse(true); + range.moveEnd('character', end); + range.moveStart('character', end); + // NOTE: I haven't tested to see if IE has the same problem as + // W3C browsers seem to have in this context (needing to fire + // select after callstack). + range.select(); + } + }); +}; + + +// GUI Event Handlers + +$(document).keydown( function(event) { + // catch all keyboard input, handle special chars + var code = event.which; + inputfield = $("#inputfield"); + inputfield.focus(); + + if (code === 13) { // Enter key sends text + outtext = inputfield.val() + inputlog.add(outtext) + Evennia.msg("text", [outtext], {}); + event.prevetDefault() + } + else if (code === 38) { // Arrow up + inputfield.val(inputlog.back()).appendCaret(); + } + else if (code === 40) { // Arrow down + inputfield.val(inputlog.fwd()).appendCaret(); + } + +}); + +// client size setter + +function set_window_size() { + var winh = $(document).height(); + var formh = $('#inputform').outerHeight(true); + $("#messagewindow").css({'height': winh - formh - 1}); +} + +// Event - called when window resizes +$(window).resize(set_window_size); + // // Listeners // +function doText(args, kwargs) { + // append message to previous ones + $("#messagewindow").append( + "
" + args[0] + "
"); +} + + +$(document).ready(function() { + // a small timeout to stop 'loading' indicator in Chrome + Evennia.init() + // register listeners + Evennia.emitter.on("text", doText); + Evennia.emitter.on("prompt", doPrompt); + set_window_size(); + + // set an idle timer to avoid proxy servers to time out on us (every 3 minutes) + setInterval(function() { + log('Idle tick.'); + Evennia.msg("text", ["idle"], {}); + }, + 60000*3 + ); +}); -// -// Senders -// diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 744afd4e2..d23edf7e9 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -14,60 +14,66 @@ JQuery available. - + - - - + + {% block jquery_import %} + + {% endblock %} - + + + + + {% block guilib_import %} + + {% endblock %}
- {% block Connecting %} + {% block connecting %} {% endblock %}
-
-

Javascript Error: The Evennia MUD client requires that you have Javascript activated.

-

Turn off eventual script blockers and/or switch to a web - browser supporting javascript.

-

For admins: The error could also be due to not being able - to access the online jQuery javascript library. If you are - testing the client without an internet connection, you have - to previously download the jQuery library from - http://code.jquery.com (it's just one file) and then edit - webclient.html to point to the local copy.

+
+

Javascript Error: The Evennia MUD client requires that you + have Javascript activated.

+

Turn off eventual script blockers and/or switch to a + web browser supporting javascript.

+ This error could also be due to not being able to access + the online jQuery javascript library.

+ +
- - - +
{% block client %} {% endblock %}
- - {% block guilib_import %} - - {% endblock %} - diff --git a/evennia/web/webclient/templates/webclient/webclient.html b/evennia/web/webclient/templates/webclient/webclient.html index fba9ce9d3..449466303 100644 --- a/evennia/web/webclient/templates/webclient/webclient.html +++ b/evennia/web/webclient/templates/webclient/webclient.html @@ -1,30 +1,21 @@ {% extends "webclient/base.html" %} -{% block guilib_import %} -{% endblock %} + -{% block connecting %} -{% endblock %} {% block client %} -

Webclient!

+
+
mainarea
+
+ +
+
-
- - -
- - {% endblock %} -