realtime form prototype, almost in good condition

pull/1/head
ansuz 9 years ago
parent cce50390a0
commit c509c28c18

@ -11,33 +11,59 @@
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
} }
form {
border: 3px solid black;
border-radius: 5px;
padding: 15px;
font-weight: bold !important;
font-size: 18px !important;
}
input[type="text"],
input[type="password"],
input[type="number"],
input[type="range"],
select
{
margin-top: 5px;
margin-bottom: 5px;
width: 80%;
}
textarea {
width: 80%;
height: 40vh;
font-weight: bold;
font-size: 18px;
}
</style> </style>
</head> </head>
<body> <body>
<form> <form>
<input type="text" name="text"><br> <input type="radio" name="radio" value="one" checked>One
<input type="password" name="password"><br> <input type="radio" name="radio" value="two">Two
<input type="radio" name="radio" value="one" checked>One<br>
<input type="radio" name="radio" value="two">Two<br>
<input type="radio" name="radio" value="three">Three<br> <input type="radio" name="radio" value="three">Three<br>
<input type="checkbox" name="checkbox1" value="1">Checkbox One<br> <input type="checkbox" name="checkbox1" value="1">Checkbox One
<input type="checkbox" name="checkbox2" value="2">Checkbox Two<br> <input type="checkbox" name="checkbox2" value="2">Checkbox Two<br>
<input type="number" name="number" min="1" max="5">Number<br> <input type="text" name="text" placeholder="Text Input"><br>
<input type="password" name="password" placeholder="Passwords"><br>
<input type="number" name="number" min="1" max="5" placeholder="Numbers">Number<br>
<input type="range" name="range" min="0" max="10">Ranges<br> <input type="range" name="range" min="0" max="100">Ranges<br>
<select> <select name="select">
<option value="one">One</option> <option value="one">One</option>
<option value="two">Two</option> <option value="two">Two</option>
<option value="three">Three</option> <option value="three">Three</option>
<option value="four">Four</option> <option value="four">Four</option>
</select> Dropdowns<br> </select> Dropdowns<br>
<textarea rows="4" cols="50"> </textarea><br> <textarea name="textarea"></textarea><br>
</form> </form>

@ -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…
Cancel
Save