Created
February 18, 2015 23:05
-
-
Save tav/eb20bedd76ad69cdcc73 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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