Skip to content

Instantly share code, notes, and snippets.

@tav
Created February 18, 2015 23:05
Show Gist options
  • Save tav/eb20bedd76ad69cdcc73 to your computer and use it in GitHub Desktop.
Save tav/eb20bedd76ad69cdcc73 to your computer and use it in GitHub Desktop.
Todos = xi.State
currentEntry: ''
items: []
getData: ->
currentEntry: @currentEntry
items: @items
TODO_NEW: ->
if @currentEntry != ''
@items.push {text: @currentEntry}
@currentEntry = ''
return
TODO_UPDATE_CURRENT_ENTRY: (text) ->
@currentEntry = text
return
TodoList = xi.View
render: ->
<ul>
{<li>{item.text}</li> for item in @props.items}
</ul>
TodoApp = xi.View
listen:
todos: Todos
onChange: (e) ->
xi.TODO_UPDATE_CURRENT_ENTRY e.target.value
onSubmit: (e) ->
e.preventDefault()
xi.TODO_NEW()
render: ->
<div>
<h3>TODO</h3>
<TodoList items={@state.todos.items} />
<form onSubmit={@onSubmit}>
<input onChange={@onChange} value={@state.todos.currentEntry} />
<button>{'Add #' + (@state.todos.items.length + 1)}</button>
</form>
</div>
React.render <TodoApp />, document.getElementById('app')
/* global exports, module, React */
function xi(root) {
'use strict';
var console = root.console,
createClass = React.createClass,
defProp = Object.defineProperty,
getKeys = Object.keys,
now = Date.now,
clock,
curDispatch = '',
detectors = {},
features = {},
handlers = {},
idKey = 'xi_' + rand(),
isArray = Array.isArray,
lastID = 0,
latest,
perf = root.performance,
pkg,
settings = {},
skew = 0,
subKey = idKey + '_sub',
tryErr = {e: {}};
if ((perf !== void 0) && perf.now) {
clock = function() {
return perf.now();
};
} else {
latest = now();
clock = function() {
var time = now();
if (time < latest) {
skew += latest - time + 1;
}
latest = time;
return time + skew;
};
}
function config(obj, value) {
var i, k, keys, l;
if (value === void 0) {
return settings[obj];
}
if (typeof obj === 'object') {
for (keys = getKeys(), i = 0, l = keys.length; i < l; i++) {
settings[k = keys[i]] = obj[k];
}
} else {
settings[obj] = value;
}
}
function createDispatcher(key) {
return function(data) {
if (curDispatch !== '') {
throw new Error("xi: cannot dispatch in the middle of dispatch to " + curDispatch);
}
return dispatch(key, data);
};
}
function createHandler(ctx, method, callOnly) {
return function(data) {
var resp = method.call(ctx, data);
if (callOnly || resp === false) {
return;
}
if (isPromise(resp)) {
resp.then(function() {
signalListeners(ctx);
});
} else {
signalListeners(ctx);
}
};
}
function detect(feature, detector) {
if (detector === void 0) {
var status = features[feature];
if (status === void 0) {
detector = detectors[feature];
if (detector !== void 0) {
return (features[feature] = detector());
}
}
return status;
}
detectors[feature] = detector;
}
function dispatch(key, data) {
var err,
i,
h = handlers[key],
l = h.length;
curDispatch = key;
for (i = 0; i < l; i++) {
err = tryFn1(h[i], data);
if (err === tryErr) {
console.log("xi: error dispatching " + key + ":");
console.log(data);
console.log(err.e);
}
}
curDispatch = '';
}
function equals(left, right) {
var i,
key,
litem,
lkeys,
ll,
ritem,
rkeys;
if (!left) {
return left === right;
}
lkeys = getKeys(left);
ll = lkeys.length;
rkeys = getKeys(right);
if (ll !== rkeys.length) {
return false;
}
for (i = 0; i < ll; i++) {
litem = left[key = lkeys[i]];
if (isObject(litem) || isArray(litem)) {
return false;
}
ritem = right[key];
if (isObject(ritem) || isArray(ritem)) {
return false;
}
if (litem !== ritem) {
return false;
}
}
return true;
}
function extend(obj, items) {
var keys = getKeys(items), i, key, l = keys.length;
for (i = 0; i < l; i++) {
obj[key = keys[i]] = items[key];
}
}
function isHandlerKey(key) {
var c,
i,
l = key.length;
for (i = 0; i < l; i++) {
c = key.charCodeAt(i);
if (c >= 65 && c <= 90) { // A-Z
continue;
}
if (c !== 95) { // _
return false;
}
}
if (key === '_') {
return false;
}
return true;
}
function isObject(obj) {
return typeof obj === 'object' && obj !== null;
}
function isPromise(obj) {
return isObject(obj) && obj.then !== void 0;
}
function rand() {
return Math.random().toString().slice(2);
}
function setHiddenProp(obj, prop, value) {
defProp(obj, prop, {value: value});
}
function signalListeners(ctx) {
var subscribers = ctx[subKey],
i,
ids = getKeys(subscribers),
l = ids.length,
reg;
for (i = 0; i < l; i++) {
reg = subscribers[ids[i]];
reg.setState(reg.getInitialState());
}
}
function State(spec) {
var callOnly,
i,
key,
keys = getKeys(spec),
nspec = {},
rawKey;
if (spec.getData === void 0) {
throw new TypeError("xi: missing method 'getData' in State definition");
}
setHiddenProp(nspec, subKey, {});
for (i = 0; i < keys.length; i++) {
key = rawKey = keys[i];
if (isHandlerKey(key)) {
if (key[0] === '_') {
key = key.slice(1);
callOnly = true;
} else {
callOnly = false;
}
if (handlers[key] === void 0) {
handlers[key] = [createHandler(nspec, spec[rawKey], callOnly)];
pkg[key] = createDispatcher(key);
} else {
handlers[key].push(createHandler(nspec, spec[rawKey], callOnly));
}
} else {
nspec[key] = spec[key];
}
}
return nspec;
}
function tryFn1(fn, arg) {
try {
return fn(arg);
} catch(err) {
tryErr.e = err;
return tryErr;
}
}
function View(spec) {
var j,
l,
listen = spec.listen,
oriMount,
oriUnmount,
stateKeys,
stateValues = [];
if (spec.render === void 0) {
throw new TypeError("xi: missing method 'render' in View definition");
}
if (listen === void 0) {
if (spec.shouldComponentUpdate === void 0) {
spec.shouldComponentUpdate = function (nextProps, nextState) {
if (!equals(this.props, nextProps)) {
return true;
}
if (!equals(this.state, nextState)) {
return true;
}
return false;
};
}
} else {
stateKeys = getKeys(listen);
l = stateKeys.length;
for (j = 0; j < l; j++) {
stateValues.push(listen[stateKeys[j]]);
}
oriMount = spec.componentWillMount;
spec.componentWillMount = function() {
var i,
sid = lastID++;
setHiddenProp(this, idKey, sid);
for (i = 0; i < l; i++) {
stateValues[i][subKey][sid] = this;
}
if (oriMount !== void 0) {
oriMount.call(this);
}
};
oriUnmount = spec.componentWillUnmount;
spec.componentWillUnmount = function() {
var i,
sid = this[idKey];
for (i = 0; i < l; i++) {
delete stateValues[i][subKey][sid];
}
if (oriUnmount !== void 0) {
oriUnmount.call(this);
}
};
if (spec.getInitialState !== void 0) {
throw new TypeError("xi: you cannot specify 'getInitialState' on a View with 'listen' defined");
}
spec.getInitialState = function() {
var i,
res = {};
for (i = 0; i < l; i++) {
res[stateKeys[i]] = stateValues[i].getData();
}
return res;
};
}
return createClass(spec);
}
pkg = {
// Constructors.
State: State,
View: View,
// Time-related utilities.
clock: clock,
// Other utilities.
config: config,
detect: detect,
extend: extend
};
return pkg;
}
if (typeof exports === 'object') {
module.exports = xi(this);
} else {
// Assume a browser-like global environment.
this.xi = xi(this);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment