realtime form prototype, almost in good condition
parent
cce50390a0
commit
c509c28c18
@ -1,80 +1,201 @@
|
|||||||
|
require.config({ paths: { 'json.sortify': '/bower_components/json.sortify/dist/JSON.sortify' } });
|
||||||
define([
|
define([
|
||||||
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
'/api/config?cb=' + Math.random().toString(16).substring(2),
|
||||||
'/common/RealtimeTextarea.js',
|
'/common/realtime-input.js',
|
||||||
'/common/messages.js',
|
|
||||||
'/common/crypto.js',
|
'/common/crypto.js',
|
||||||
'/common/TextPatcher.js',
|
'/common/TextPatcher.js',
|
||||||
|
'json.sortify',
|
||||||
|
'/form/ula.js',
|
||||||
|
'/common/json-ot.js',
|
||||||
'/bower_components/jquery/dist/jquery.min.js',
|
'/bower_components/jquery/dist/jquery.min.js',
|
||||||
'/customize/pad.js'
|
'/customize/pad.js'
|
||||||
], function (Config, Realtime, Messages, Crypto, TextPatcher) {
|
], function (Config, Realtime, Crypto, TextPatcher, Sortify, Formula, JsonOT) {
|
||||||
var $ = window.jQuery;
|
var $ = window.jQuery;
|
||||||
$(window).on('hashchange', function() {
|
|
||||||
window.location.reload();
|
var key;
|
||||||
});
|
var channel = '';
|
||||||
if (window.location.href.indexOf('#') === -1) {
|
var hash = false;
|
||||||
window.location.href = window.location.href + '#' + Crypto.genKey();
|
if (!/#/.test(window.location.href)) {
|
||||||
return;
|
key = Crypto.genKey();
|
||||||
|
} else {
|
||||||
|
hash = window.location.hash.slice(1);
|
||||||
|
channel = hash.slice(0,32);
|
||||||
|
key = hash.slice(32);
|
||||||
}
|
}
|
||||||
|
|
||||||
var module = window.APP = {};
|
var module = window.APP = {
|
||||||
var key = Crypto.parseKey(window.location.hash.substring(1));
|
TextPatcher: TextPatcher,
|
||||||
|
Sortify: Sortify,
|
||||||
|
Formula: Formula,
|
||||||
|
};
|
||||||
var initializing = true;
|
var initializing = true;
|
||||||
|
|
||||||
/* elements that we need to listen to */
|
var uid = module.uid = Formula.uid;
|
||||||
/*
|
|
||||||
* text
|
var getInputType = Formula.getInputType;
|
||||||
* password
|
var $elements = module.elements = $('input, select, textarea')
|
||||||
* radio
|
|
||||||
* checkbox
|
var eventsByType = Formula.eventsByType;
|
||||||
* number
|
|
||||||
* range
|
|
||||||
* select
|
|
||||||
* textarea
|
|
||||||
*/
|
|
||||||
|
|
||||||
var $textarea = $('textarea');
|
var Map = module.Map = {};
|
||||||
|
|
||||||
|
var UI = module.UI = {
|
||||||
|
ids: [],
|
||||||
|
each: function (f) {
|
||||||
|
UI.ids.forEach(function (id, i, list) {
|
||||||
|
f(UI[id], i, list);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var cursorTypes = ['textarea', 'password', 'text'];
|
||||||
|
|
||||||
|
var canonicalize = function (text) { return text.replace(/\r\n/g, '\n'); };
|
||||||
|
$elements.each(function (element) {
|
||||||
|
var $this = $(this);
|
||||||
|
|
||||||
|
var id = uid();
|
||||||
|
var type = getInputType($this);
|
||||||
|
|
||||||
|
$this // give each element a uid
|
||||||
|
.data('rtform-uid', id)
|
||||||
|
// get its type
|
||||||
|
.data('rt-ui-type', type);
|
||||||
|
|
||||||
|
UI.ids.push(id);
|
||||||
|
|
||||||
|
var component = UI[id] = {
|
||||||
|
id: id,
|
||||||
|
$: $this,
|
||||||
|
element: element,
|
||||||
|
type: type,
|
||||||
|
preserveCursor: cursorTypes.indexOf(type) !== -1,
|
||||||
|
name: $this.prop('name'),
|
||||||
|
};
|
||||||
|
|
||||||
|
component.value = (function () {
|
||||||
|
var checker = ['radio', 'checkbox'].indexOf(type) !== -1;
|
||||||
|
|
||||||
|
if (checker) {
|
||||||
|
return function (content) {
|
||||||
|
return typeof content !== 'undefined'?
|
||||||
|
$this.prop('checked', !!content):
|
||||||
|
$this.prop('checked');
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return function (content) {
|
||||||
|
return typeof content !== 'undefined' ?
|
||||||
|
$this.val(content):
|
||||||
|
canonicalize($this.val());
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}());
|
||||||
|
|
||||||
|
var update = component.update = function () { Map[id] = component.value(); };
|
||||||
|
update();
|
||||||
|
});
|
||||||
|
|
||||||
var config = module.config = {
|
var config = module.config = {
|
||||||
websocketURL: Config.websocketURL + '_old',
|
initialState: Sortify(Map) || '{}',
|
||||||
|
websocketURL: Config.websocketURL,
|
||||||
userName: Crypto.rand64(8),
|
userName: Crypto.rand64(8),
|
||||||
channel: key.channel,
|
channel: channel,
|
||||||
cryptKey: key.cryptKey
|
cryptKey: key,
|
||||||
|
crypto: Crypto,
|
||||||
|
transformFunction: JsonOT.validate
|
||||||
};
|
};
|
||||||
|
|
||||||
var setEditable = function (bool) {/* allow editing */};
|
var setEditable = module.setEditable = function (bool) {
|
||||||
var canonicalize = function (text) {/* canonicalize all the things */};
|
/* (dis)allow editing */
|
||||||
|
$elements.each(function () {
|
||||||
|
$(this).attr('disabled', !bool);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
setEditable(false);
|
setEditable(false);
|
||||||
|
|
||||||
var onInit = config.onInit = function (info) { };
|
var onInit = config.onInit = function (info) {
|
||||||
|
var realtime = module.realtime = info.realtime;
|
||||||
|
window.location.hash = info.channel + key;
|
||||||
|
|
||||||
var onRemote = config.onRemote = function (info) {
|
// create your patcher
|
||||||
if (initializing) { return; }
|
module.patchText = TextPatcher.create({
|
||||||
/* integrate remote changes */
|
realtime: realtime,
|
||||||
|
logging: true,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
var onLocal = config.onLocal = function () {
|
var onLocal = config.onLocal = function () {
|
||||||
if (initializing) { return; }
|
if (initializing) { return; }
|
||||||
/* serialize local changes */
|
/* serialize local changes */
|
||||||
|
readValues();
|
||||||
|
module.patchText(Sortify(Map));
|
||||||
};
|
};
|
||||||
|
|
||||||
var onReady = config.onReady = function (info) {
|
var readValues = function () {
|
||||||
var realtime = module.realtime = info.realtime;
|
UI.each(function (ui, i, list) {
|
||||||
|
Map[ui.id] = ui.value();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// create your patcher
|
var updateValues = function () {
|
||||||
module.patchText = TextPatcher.create({
|
var userDoc = module.realtime.getUserDoc();
|
||||||
realtime: realtime
|
var parsed = JSON.parse(userDoc);
|
||||||
|
|
||||||
|
console.log(userDoc);
|
||||||
|
|
||||||
|
UI.each(function (ui, i, list) {
|
||||||
|
var newval = parsed[ui.id];
|
||||||
|
var oldval = ui.value();
|
||||||
|
|
||||||
|
if (newval === oldval) { return; }
|
||||||
|
|
||||||
|
var op;
|
||||||
|
var element = ui.element;
|
||||||
|
if (ui.preserveCursor) {
|
||||||
|
op = TextPatcher.diff(oldval, newval);
|
||||||
|
var selects = ['selectionStart', 'selectionEnd'].map(function (attr) {
|
||||||
|
var before = element[attr];
|
||||||
|
var after = TextPatcher.transformCursor(element[attr], op);
|
||||||
|
return after;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ui.value(newval);
|
||||||
|
ui.update();
|
||||||
|
|
||||||
|
if (op) {
|
||||||
|
console.log(selects);
|
||||||
|
element.selectionStart = selects[0];
|
||||||
|
element.selectionEnd = selects[1];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// get ready
|
var onRemote = config.onRemote = function (info) {
|
||||||
|
if (initializing) { return; }
|
||||||
|
/* integrate remote changes */
|
||||||
|
updateValues();
|
||||||
|
};
|
||||||
|
|
||||||
|
var onReady = config.onReady = function (info) {
|
||||||
|
updateValues();
|
||||||
|
|
||||||
|
console.log("READY");
|
||||||
setEditable(true);
|
setEditable(true);
|
||||||
initializing = false;
|
initializing = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
var onAbort = config.onAbort = function (info) {};
|
var onAbort = config.onAbort = function (info) {
|
||||||
|
window.alert("Network Connection Lost");
|
||||||
|
};
|
||||||
|
|
||||||
var rt = Realtime.start(config);
|
var rt = Realtime.start(config);
|
||||||
|
|
||||||
// bind to events...
|
UI.each(function (ui, i, list) {
|
||||||
|
var type = ui.type;
|
||||||
|
var events = eventsByType[type];
|
||||||
|
ui.$.on(events, onLocal);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
|
||||||
|
```Javascript
|
||||||
|
/* elements that we need to listen to */
|
||||||
|
/*
|
||||||
|
* text => $(text).val()
|
||||||
|
* password => $(password).val()
|
||||||
|
* radio => $(radio).prop('checked')
|
||||||
|
* checkbox => $(checkbox).prop('checked')
|
||||||
|
* number => $(number).val() // returns string, no default
|
||||||
|
* range => $(range).val()
|
||||||
|
* select => $(select).val()
|
||||||
|
* textarea => $(textarea).val()
|
||||||
|
*/
|
||||||
|
```
|
@ -0,0 +1,24 @@
|
|||||||
|
define([], function () {
|
||||||
|
var ula = {};
|
||||||
|
|
||||||
|
var uid = ula.uid = (function () {
|
||||||
|
var i = 0;
|
||||||
|
var prefix = 'rt_';
|
||||||
|
return function () { return prefix + i++; };
|
||||||
|
}());
|
||||||
|
|
||||||
|
ula.getInputType = function ($el) { return $el[0].type; };
|
||||||
|
|
||||||
|
ula.eventsByType = {
|
||||||
|
text: 'change keyup',
|
||||||
|
password: 'change keyup',
|
||||||
|
radio: 'change click',
|
||||||
|
checkbox: 'change click',
|
||||||
|
number: 'change',
|
||||||
|
range: 'keyup change',
|
||||||
|
'select-one': 'change',
|
||||||
|
textarea: 'change keyup',
|
||||||
|
};
|
||||||
|
|
||||||
|
return ula;
|
||||||
|
});
|
Loading…
Reference in New Issue