|
|
@@ -0,0 +1,269 @@ |
|
|
(function() { |
|
|
|
|
|
var Bloop = window.Bloop = {}; |
|
|
|
|
|
var mountId = 0; |
|
|
function newMountId() { |
|
|
return mountId++; |
|
|
} |
|
|
|
|
|
Bloop.createClass = function(proto) { |
|
|
function _component(props, children) { |
|
|
if(this.constructor !== _component) { |
|
|
return new _component(props, children); |
|
|
} |
|
|
|
|
|
var args = _.toArray(arguments); |
|
|
if(!(_.isPlainObject(props) && !props.type)) { |
|
|
children = props; |
|
|
props = null; |
|
|
args.unshift(null); |
|
|
} |
|
|
|
|
|
if(!Array.isArray(children)) { |
|
|
children = args.slice(1); |
|
|
} |
|
|
|
|
|
_.keys(proto).forEach(function(k) { |
|
|
var val = proto[k]; |
|
|
if(_.isFunction(val)) { |
|
|
val = val.bind(this); |
|
|
} |
|
|
this[k] = val; |
|
|
}, this); |
|
|
|
|
|
this.state = this.getInitialState ? this.getInitialState() : null; |
|
|
|
|
|
if(props && props.state) { |
|
|
_.keys(this.state).forEach(function(k) { |
|
|
props.state[k] = this.state[k]; |
|
|
}, this); |
|
|
this.state = props.state; |
|
|
delete props.state; |
|
|
} |
|
|
|
|
|
this.props = props; |
|
|
this.children = children; |
|
|
this.type = "custom"; |
|
|
|
|
|
if(this.init) { |
|
|
this.init(); |
|
|
} |
|
|
} |
|
|
|
|
|
_component.prototype = Object.create(Bloop.createClass.prototype); |
|
|
_component.prototype.constructor = _component; |
|
|
|
|
|
return _component; |
|
|
}; |
|
|
|
|
|
Bloop.renderComponent = function(comp, node) { |
|
|
var virtualDOM = comp.render(); |
|
|
var changed = false; |
|
|
|
|
|
if(!comp.mountId) { |
|
|
comp.mountId = 'rerender-' + newMountId(); |
|
|
var mount = document.createElement('div'); |
|
|
mount.id = comp.mountId; |
|
|
mount._virtualDOM = virtualDOM; |
|
|
mount.appendChild(renderVirtualDOM(virtualDOM)); |
|
|
node.appendChild(mount); |
|
|
|
|
|
if(comp.componentDidRender) { |
|
|
comp.componentDidRender(); |
|
|
} |
|
|
} |
|
|
else { |
|
|
var mount = document.querySelector('#' + comp.mountId); |
|
|
var prevDOM = mount._virtualDOM; |
|
|
|
|
|
if(serializeVirtualDOM(virtualDOM) !== serializeVirtualDOM(prevDOM)) { |
|
|
var dom = renderVirtualDOM(virtualDOM); |
|
|
|
|
|
changed = true; |
|
|
mount.innerHTML = ''; |
|
|
mount.appendChild(dom); |
|
|
mount._virtualDOM = virtualDOM; |
|
|
|
|
|
if(comp.componentDidRender) { |
|
|
comp.componentDidRender(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return changed; |
|
|
}; |
|
|
|
|
|
function serializeVirtualDOM(dom) { |
|
|
var str = dom.type; |
|
|
if(dom.props) { |
|
|
_.keys(dom.props).forEach(function(k) { |
|
|
var prop = dom.props[k]; |
|
|
if(_.isFunction(prop)) { |
|
|
str += k + 'function'; |
|
|
} |
|
|
else if(dom.props[k]) { |
|
|
str += k + dom.props[k].toString(); |
|
|
} |
|
|
}); |
|
|
} |
|
|
if(dom.children) { |
|
|
dom.children.forEach(function(child) { |
|
|
if(child) { |
|
|
str += child.type ? serializeVirtualDOM(child) : child; |
|
|
} |
|
|
}); |
|
|
} |
|
|
return str; |
|
|
} |
|
|
|
|
|
function renderComponentToString(comp) { |
|
|
var dom = renderVirtualDOM(comp.render()); |
|
|
return dom.outerHTML; |
|
|
} |
|
|
|
|
|
function renderVirtualDOM(comp) { |
|
|
if(comp && comp.type) { |
|
|
// render native thing |
|
|
var node = document.createElement(comp.type); |
|
|
var props = comp.props; |
|
|
var children = comp.children; |
|
|
|
|
|
if(props) { |
|
|
for(var k in props) { |
|
|
if(props.hasOwnProperty(k)) { |
|
|
if(k.indexOf('on') === 0) { |
|
|
node.addEventListener(k.substring(2).toLowerCase(), props[k]); |
|
|
} |
|
|
else { |
|
|
node[k] = props[k]; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
children.forEach(function(child) { |
|
|
if(child != null) { |
|
|
node.appendChild(renderVirtualDOM(child)); |
|
|
} |
|
|
}); |
|
|
|
|
|
return node; |
|
|
} |
|
|
else { |
|
|
return document.createTextNode(comp); |
|
|
} |
|
|
} |
|
|
|
|
|
function DOMCreator(type) { |
|
|
return function(props, children) { |
|
|
var args = _.toArray(arguments); |
|
|
if(!(_.isPlainObject(props) && !props.type)) { |
|
|
children = props; |
|
|
props = null; |
|
|
args.unshift(null); |
|
|
} |
|
|
|
|
|
if(!Array.isArray(children)) { |
|
|
children = args.slice(1); |
|
|
} |
|
|
|
|
|
return { |
|
|
type: type, |
|
|
props: props, |
|
|
children: children.map(function(child) { |
|
|
if(child instanceof Bloop.createClass) { |
|
|
return child.render(); |
|
|
} |
|
|
return child; |
|
|
}) |
|
|
}; |
|
|
}; |
|
|
} |
|
|
|
|
|
Bloop.dom = { |
|
|
div: DOMCreator('div'), |
|
|
span: DOMCreator('span'), |
|
|
a: DOMCreator('a'), |
|
|
p: DOMCreator('p'), |
|
|
button: DOMCreator('button'), |
|
|
ul: DOMCreator('ul'), |
|
|
li: DOMCreator('li'), |
|
|
header: DOMCreator('header'), |
|
|
h1: DOMCreator('h1'), |
|
|
h2: DOMCreator('h2'), |
|
|
h3: DOMCreator('h3'), |
|
|
h4: DOMCreator('h4'), |
|
|
input: DOMCreator('input'), |
|
|
textarea: DOMCreator('textarea') |
|
|
}; |
|
|
|
|
|
// routes |
|
|
|
|
|
var ROUTES = []; |
|
|
|
|
|
function addRoutes(/* routes */) { |
|
|
var routes = _.toArray(_.groupBy(arguments, function(el, i) { |
|
|
return Math.floor(i / 2); |
|
|
})); |
|
|
ROUTES = ROUTES.concat(routes); |
|
|
} |
|
|
|
|
|
function replaceRoutes(routes) { |
|
|
ROUTES = routes; |
|
|
} |
|
|
|
|
|
function changeRoute(path) { |
|
|
history.pushState(null, null, path); |
|
|
syncRoute(path); |
|
|
} |
|
|
|
|
|
function syncRoute(path) { |
|
|
if(path[0] !== '/') { |
|
|
var parts = window.location.pathname.split('/'); |
|
|
parts.pop(); |
|
|
path = '/' + parts.join('/') + path; |
|
|
} |
|
|
|
|
|
ROUTES.some(function(route) { |
|
|
var targetPath = route[0]; |
|
|
var targetFunc = route[1]; |
|
|
|
|
|
var params = _.zip(targetPath.split('/'), path.split('/')).reduce(function(acc, check) { |
|
|
var routePart = check[0]; |
|
|
var input = check[1]; |
|
|
|
|
|
if(acc && routePart[0] == ':' && input) { |
|
|
acc.push(input); |
|
|
return acc; |
|
|
} |
|
|
else if(routePart !== input) { |
|
|
return null; |
|
|
} |
|
|
else { |
|
|
return acc; |
|
|
} |
|
|
}, []); |
|
|
|
|
|
if(params) { |
|
|
targetFunc.apply(null, params); |
|
|
return true; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
function redirect(path) { |
|
|
history.replaceState(null, null, path); |
|
|
syncRoute(path); |
|
|
} |
|
|
|
|
|
window.addEventListener('popstate', function(e) { |
|
|
syncRoute(window.location.pathname); |
|
|
}); |
|
|
|
|
|
window.addRoutes = addRoutes; |
|
|
window.changeRoute = changeRoute; |
|
|
window.syncRoute = syncRoute; |
|
|
window.redirect = redirect; |
|
|
|
|
|
})(); |