Last active
August 29, 2015 14:10
-
-
Save mathieuancelin/fca32499732733cddc7f to your computer and use it in GitHub Desktop.
No more string concat. Need jquery, underscore.js and sugar.js
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
| var Elem = Elem || {}; | |
| (function(exports) { | |
| function styleToString(attrs) { | |
| if (_.isUndefined(attrs)) return ''; | |
| var attrsArray = _.map(_.keys(attrs), function(key) { | |
| var keyName = key.dasherize(); | |
| if (key === 'className') { | |
| keyName = 'class'; | |
| } | |
| var value = attrs[key]; | |
| if (!_.isUndefined(value) && _.isFunction(value)) { | |
| value = value(); | |
| } | |
| if (!_.isUndefined(value)) { | |
| return keyName + ': ' + value + ';'; | |
| } else { | |
| return undefined; | |
| } | |
| }); | |
| attrsArray = _.filter(attrsArray, function(item) { return !_.isUndefined(item); }); | |
| return attrsArray.join(' '); | |
| } | |
| function classToArray(attrs) { | |
| if (_.isUndefined(attrs)) return []; | |
| var attrsArray = _.map(_.keys(attrs), function(key) { | |
| var value = attrs[key]; | |
| if (!_.isUndefined(value) && value === true) { | |
| return key.dasherize(); | |
| } else { | |
| return undefined; | |
| } | |
| }); | |
| attrsArray = _.filter(attrsArray, function(item) { return !_.isUndefined(item); }); | |
| return attrsArray; | |
| } | |
| function objToString(node, context) { | |
| if (_.isUndefined(node.attrs)) return ''; | |
| var attrsArray = _.map(_.keys(node.attrs), function(key) { | |
| var keyName = key.dasherize(); | |
| if (key === 'className') { | |
| keyName = 'class'; | |
| } | |
| if (context && keyName.startsWith('on')) { | |
| context.waitingHandlers.push({ | |
| root: context.root, | |
| id: node.__nodeId, | |
| event: keyName, | |
| callback: node.attrs[key] | |
| }); | |
| return undefined; | |
| } else { | |
| var value = node.attrs[key]; | |
| if (!_.isUndefined(value) && _.isFunction(value)) { | |
| value = value(); | |
| } | |
| if (!_.isUndefined(value)) { | |
| if (_.isObject(value) && keyName === 'style') { | |
| return keyName + '="' + styleToString(value) + '"'; | |
| } else if (_.isArray(value) && keyName === 'class') { | |
| return keyName + '="' + value.join(' ') + '"'; | |
| } else if (_.isObject(value) && keyName === 'class') { | |
| return keyName + '="' + classToArray(value).join(' ') + '"'; | |
| } else { | |
| return keyName + '="' + value + '"'; | |
| } | |
| } else { | |
| return undefined; | |
| } | |
| } | |
| }); | |
| attrsArray = _.filter(attrsArray, function(item) { return !_.isUndefined(item); }); | |
| return attrsArray.join(' '); | |
| } | |
| function wrapChildren(children) { | |
| if (children === 0) { | |
| return children; | |
| } else if (children === '') { | |
| return children; | |
| } | |
| return children || []; | |
| } | |
| function toHtml(node, context) { | |
| node.name = node.name || 'unknown'; | |
| node.attrs = node.attrs || {}; | |
| node.children = wrapChildren(node.children); | |
| var selfCloseTag = ((node.name === 'br' || | |
| node.name === 'hr' || | |
| node.name === 'img' || | |
| node.name === 'input' || | |
| node.name === 'link' || | |
| node.name === 'meta') && _.isUndefined(node.children)); | |
| var html = '<' + _.escape(node.name) + ' data-nodeid="' + _.escape(node.__nodeId) + '" ' + objToString(node, context); | |
| if (selfCloseTag) { | |
| html = html + '/>'; | |
| } else { | |
| html = html + '>'; | |
| if (_.isUndefined(node.children)) { | |
| // do nothing | |
| } else if (_.isArray(node.children)) { | |
| var elementsToHtml = _.chain(node.children).map(function(child) { | |
| if (_.isFunction(child)) { | |
| return child(); | |
| } else { | |
| return child; | |
| } | |
| }).filter(function(item) { | |
| return !_.isUndefined(item); | |
| }).map(function(child) { | |
| return toHtml(child, context); | |
| }).value().join(''); | |
| html = html + elementsToHtml; | |
| } else if (_.isNumber(node.children)) { | |
| html = html + node.children; | |
| } else if (_.isString(node.children)) { | |
| html = html + _.escape(node.children); | |
| } else if (_.isNull(node.children)) { | |
| // do nothing | |
| } else if (_.isBoolean(node.children)) { | |
| html = html + node.children; | |
| } else if (_.isRegExp(node.children)) { | |
| // do nothing | |
| } else if (_.isObject(node.children) && node.children.__isElement) { | |
| html = html + toHtml(node.children, context); | |
| } else if (_.isObject(node.children) && node.children.__asHtml) { | |
| html = html + node.children.__asHtml; | |
| } else { | |
| html = html + _.escape(node.children.toString()); | |
| } | |
| html = html + '</' + node.name + '>'; | |
| } | |
| return html; | |
| } | |
| function el(name, attrs, children) { | |
| if (_.isUndefined(children) && !_.isUndefined(attrs) && !attrs.__isAttrs) { | |
| children = attrs; | |
| attrs = {}; | |
| } | |
| var uuid = _.uniqueId('node_'); | |
| return { | |
| name: name || 'unknown', | |
| attrs: attrs || {}, | |
| children: wrapChildren(children), | |
| __isElement: true, | |
| __nodeId: uuid, | |
| __toHtml: function() { | |
| return toHtml(this); | |
| } | |
| }; | |
| } | |
| function renderToString(el, context) { | |
| if (!_.isUndefined(el)) { | |
| if (_.isArray(el)) { | |
| return _.chain(el).map(function(item) { | |
| if (_.isFunction(item)) { | |
| return item(); | |
| } else { | |
| return item; | |
| } | |
| }).filter(function (item) { | |
| return !_.isUndefined(item); | |
| }).map(function (item) { | |
| return toHtml(item, context); | |
| }).value().join(''); | |
| } else { | |
| return toHtml(el, context); | |
| } | |
| } else { | |
| return ''; | |
| } | |
| } | |
| var renderedNodes = {}; | |
| exports.elements = function() { | |
| var elems = []; | |
| _.each(arguments, function(item) { | |
| elems.push(item); | |
| }); | |
| return elems; | |
| }; | |
| exports.el = el; | |
| exports.sel = function(name, children) { | |
| return el(name, {}, children); | |
| }; | |
| exports.cel = function(name, attrs) { | |
| return el(name, attrs, []); | |
| }; | |
| exports.nbsp = function(times) { | |
| return el('span', { __asHtml: _.times(times || 1, function() { return ' '; }) }); | |
| }; | |
| exports.renderToString = renderToString; | |
| exports.render = function(el, node) { | |
| var ret; | |
| var waitingHandlers = []; | |
| var html = renderToString(el, { root: node, waitingHandlers: waitingHandlers }); | |
| if (_.isString(node)) { | |
| ret = $(node).html(html); | |
| } else if (node.jquery) { | |
| ret = node.html(html); | |
| } else { | |
| node.innerHTML = html; | |
| } | |
| _.each(waitingHandlers, function(handler) { | |
| $('[data-nodeid="' + handler.id + '"]').on(handler.event.replace('on', ''), function() { | |
| handler.callback.apply({}, arguments); | |
| }); | |
| }); | |
| return ret; | |
| }; | |
| exports.renderComponent = function(funct, node) { | |
| if (!renderedNodes[node]) { | |
| var nbrOfRender = 0; | |
| var oldHandlers = []; | |
| function render() { | |
| nbrOfRender = nbrOfRender + 1; | |
| var tree = funct(render, node); | |
| var waitingHandlers = []; | |
| var html = toHtml(tree, { root: node, waitingHandlers: waitingHandlers }); | |
| $(node).html(html); | |
| _.each(oldHandlers, function(handler) { | |
| $(handler.node).off(handler.event); | |
| }); | |
| oldHandlers = []; | |
| _.each(waitingHandlers, function(handler) { | |
| oldHandlers.push({ | |
| event: handler.event.replace('on', ''), | |
| node: '[data-nodeid="' + handler.id + '"]' | |
| }); | |
| $('[data-nodeid="' + handler.id + '"]').on(handler.event.replace('on', ''), function() { | |
| var focus = document.activeElement; | |
| var key = $(this).data('key'); | |
| var result = handler.callback.apply({ render: render }, arguments); | |
| if (result !== false) { | |
| render(); | |
| if (key) { | |
| var focusNode = $('[data-key="' + key + '"]'); | |
| focusNode.focus(); | |
| if (focusNode.val()) { | |
| var strLength = focusNode.val().length * 2; | |
| focusNode[0].setSelectionRange(strLength, strLength); | |
| } | |
| } | |
| } | |
| }); | |
| }); | |
| } | |
| render(); | |
| var api = { | |
| render: render, | |
| stats: { | |
| nbrOfRender: nbrOfRender | |
| }, | |
| toHtmlString: function() { return toHtml(funct(render, node)); } | |
| }; | |
| renderedNodes[node] = api; | |
| return api; | |
| } else { | |
| renderedNodes[node].render(); | |
| return renderedNodes[node]; | |
| } | |
| }; | |
| })(Elem); |
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
| var TodoApp = Elem.el('div', { className: 'col-md-4' }, [ | |
| Elem.el('h3', 'Todo List'), | |
| Elem.el('div', | |
| Elem.el('div', { className: 'row' }, | |
| Elem.el('form', { role: 'form' }, [ | |
| Elem.el('div', { className: ['form-group', 'col-md-10'] }, | |
| Elem.el('input', { | |
| placeholder: 'What do you have to do ?', | |
| type: 'text', className: 'form-control', value: '', | |
| }, []) | |
| ), | |
| Elem.el('div', { className: 'form-group' }, | |
| Elem.el('div', { className: 'btn-group' }, [ | |
| Elem.el('button', { type: 'button', className: 'btn btn-success' }, | |
| Elem.el('span', { | |
| className: 'glyphicon glyphicon-floppy-saved' | |
| }, []) | |
| ), | |
| Elem.el('button', { type: 'button', className: 'btn btn-danger' }, | |
| Elem.el('span', { className: 'glyphicon glyphicon-trash' }, []) | |
| ) | |
| ]) | |
| ) | |
| ]) | |
| ) | |
| ), | |
| Elem.el('ul', { className: 'list-group' }, [ | |
| Elem.el('li', { className: 'list-group-item' }, | |
| Elem.el('div', { className: 'row' }, [ | |
| Elem.el('div', { className: 'col-md-10' }, 'Buy some beer'), | |
| Elem.el('div', { className: 'col-md-2' }, | |
| Elem.el('span', { className: { label: true, labelSuccess: true }, style: { cursor: 'pointer' } }, 'Done') | |
| ) | |
| ]) | |
| ), | |
| Elem.el('li', { className: 'list-group-item' }, | |
| Elem.el('div', { className: 'row' }, [ | |
| Elem.el('div', { className: 'col-md-10' }, 'Buy some pizza'), | |
| Elem.el('div', { className: 'col-md-2' }, | |
| Elem.el('span', { className: { label: true, labelDefault: true }, style: { cursor: 'pointer' } }, 'Done') | |
| ) | |
| ]) | |
| ) | |
| ]) | |
| ]); | |
| Elem.render(TodoApp, '#todoApp'); |
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
| var tasks = []; | |
| function NewTask(props) { | |
| var text = ''; | |
| function deleteAllDone() { | |
| tasks = _.filter(tasks, function(item) { | |
| return item.done === false; | |
| }); | |
| } | |
| function createNewTask() { | |
| var task = { | |
| _id: _.uniqueId('task_'), | |
| name: text, | |
| done: false | |
| }; | |
| tasks.push(task); | |
| } | |
| function storeName(e) { | |
| text = e.target.value; | |
| return false; // don't redraw component here | |
| } | |
| return Elem.el('div', | |
| Elem.el('div', { className: 'row' }, | |
| Elem.el('form', { role: 'form' }, [ | |
| Elem.el('div', { className: ['form-group', 'col-md-10'] }, | |
| Elem.el('input', { | |
| onchange: storeName, | |
| placeholder: 'What do you have to do ?', | |
| type: 'text', className: 'form-control', value: '', | |
| }, []) | |
| ), | |
| Elem.el('div', { className: 'form-group' }, | |
| Elem.el('div', { className: 'btn-group' }, [ | |
| Elem.el('button', { | |
| type: 'button', | |
| className: 'btn btn-success', | |
| onclick: createNewTask | |
| }, | |
| Elem.el('span', { | |
| className: 'glyphicon glyphicon-floppy-saved' | |
| }, []) | |
| ), | |
| Elem.el('button', { | |
| onclick: deleteAllDone, | |
| type: 'button', | |
| className: 'btn btn-danger' | |
| }, | |
| Elem.el('span', { className: 'glyphicon glyphicon-trash' }, []) | |
| ) | |
| ]) | |
| ) | |
| ]) | |
| ) | |
| ); | |
| } | |
| function TaskItem(props) { | |
| function flipTaskState() { | |
| tasks = _.map(tasks, function(item) { | |
| if (props.task._id === item._id) { | |
| var newTask = _.clone(item); | |
| newTask.done = !props.task.done; | |
| return newTask; | |
| } | |
| return item; | |
| }); | |
| } | |
| return Elem.el('li', { className: 'list-group-item' }, | |
| Elem.el('div', { className: 'row' }, [ | |
| Elem.el('div', { className: 'col-md-10' }, props.task.name), | |
| Elem.el('div', { className: 'col-md-2' }, | |
| Elem.el('span', { | |
| onclick: flipTaskState, | |
| className: { | |
| label: true, | |
| labelSuccess: props.task.done, | |
| labelDefault: !props.task.done | |
| }, | |
| style: { | |
| cursor: 'pointer' | |
| } | |
| }, 'Done') | |
| ) | |
| ]) | |
| ); | |
| } | |
| function TodoApp() { | |
| return Elem.el('div', { className: 'col-md-4' }, [ | |
| Elem.el('h3', 'Todo List'), | |
| NewTask(), | |
| Elem.el('ul', { className: 'list-group' }, _.map(tasks, function(task) { | |
| return TaskItem({ task: task }); | |
| })) | |
| ]); | |
| } | |
| Elem.renderComponent(TodoApp, '#demo'); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment