diff --git a/todomvc/common/actions.js b/todomvc/common/actions.js index a4d7d595..fec82c65 100644 --- a/todomvc/common/actions.js +++ b/todomvc/common/actions.js @@ -2,8 +2,14 @@ (function(ref) { ref.actions = function(sendUpdate) { return { - saveTodo: function(todo) { - sendUpdate({ saveTodo: { title: todo } }); + saveTodo: function(todo, id) { + sendUpdate({ saveTodo: { title: todo, id: id } }); + }, + editTodo: function(todoId) { + sendUpdate({ editTodoId: todoId, editing: true }); + }, + cancelEdit: function(todoId) { + sendUpdate({ editTodoId: todoId, editing: false }); }, deleteTodoId: function(todoId) { sendUpdate({ deleteTodoId: todoId }); diff --git a/todomvc/common/store.js b/todomvc/common/store.js index 449661ec..70df6a87 100644 --- a/todomvc/common/store.js +++ b/todomvc/common/store.js @@ -1,6 +1,5 @@ (function(ref) { var STORAGE_KEY = "meiosis-todomvc"; - var nextId = 1; var findIndex = function(todos, todoId) { var index = -1; @@ -34,7 +33,7 @@ todos = replaceTodoAtIndex(todos, todo, findIndex(todos, todo.id)); } else { - todo.id = nextId++; + todo.id = new Date().getTime(); todos = todos.concat([todo]); } localStorage.setItem(STORAGE_KEY, JSON.stringify(todos)); diff --git a/todomvc/common/viewModel.js b/todomvc/common/viewModel.js new file mode 100644 index 00000000..b482bb94 --- /dev/null +++ b/todomvc/common/viewModel.js @@ -0,0 +1,20 @@ +/*global window*/ +(function(ref) { + ref.viewModel = function(createComponent) { + createComponent({ + receiveUpdate: function(model, update) { + if (update.editTodoId) { + for (var i = 0, t = model.todos.length; i < t; i++) { + var todo = model.todos[i]; + + if (todo.id === update.editTodoId) { + todo.editing = update.editing; + break; + } + } + } + return model; + } + }); + }; +})(window); diff --git a/todomvc/index.html b/todomvc/index.html index fa36ccb2..29442fdc 100644 --- a/todomvc/index.html +++ b/todomvc/index.html @@ -16,7 +16,9 @@ + + diff --git a/todomvc/js/meiosis-vanillajs.min.js.map b/todomvc/js/meiosis-vanillajs.min.js.map new file mode 100644 index 00000000..5a12ecf9 --- /dev/null +++ b/todomvc/js/meiosis-vanillajs.min.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["webpack:///meiosis-vanillajs.min.js","webpack:///webpack/bootstrap 334b670c6becef96c6d7","webpack:///./src/index.js","webpack:///./src/meiosis-vanillajs.js"],"names":["meiosisVanillaJs","modules","__webpack_require__","moduleId","installedModules","exports","module","id","loaded","call","m","c","p","_meiosisVanillajs","Object","defineProperty","value","renderIntoElement","element","view","innerHTML","renderIntoId","document","getElementById","renderIntoSelector","selector","querySelector","intoElement","render","intoId","intoSelector","on","target","type","callback","useCapture","addEventListener","dispatchEvent","handler","evt","targetElement","potentialElements","querySelectorAll","hasMatch","Array","prototype","indexOf","delegate"],"mappings":"AAAA,GAAIA,kBACK,SAAUC,GCGnB,QAAAC,GAAAC,GAGA,GAAAC,EAAAD,GACA,MAAAC,GAAAD,GAAAE,OAGA,IAAAC,GAAAF,EAAAD,IACAE,WACAE,GAAAJ,EACAK,QAAA,EAUA,OANAP,GAAAE,GAAAM,KAAAH,EAAAD,QAAAC,IAAAD,QAAAH,GAGAI,EAAAE,QAAA,EAGAF,EAAAD,QAvBA,GAAAD,KAqCA,OATAF,GAAAQ,EAAAT,EAGAC,EAAAS,EAAAP,EAGAF,EAAAU,EAAA,GAGAV,EAAA,KDOM,SAASI,EAAQD,EAASH,GAE/B,YE/CD,IAAAW,GAAAX,EAAA,EAEAI,GAAOD,QAAPQ,EAAAb,kBFqDM,SAASM,EAAQD,GAEtB,YAEAS,QAAOC,eAAeV,EAAS,cAC7BW,OAAO,GG5DV,IAAMC,GAAoB,SAAAC,GAAA,MACxB,UAAAC,GAAA,MAAQD,GAAQE,UAAYD,IAExBE,EAAe,SAAAd,GAAA,MACnBU,GAAkBK,SAASC,eAAehB,KAEtCiB,EAAqB,SAAAC,GAAA,MACzBR,GAAkBK,SAASI,cAAcD,KAErCE,EAAc,SAAAT,GAAA,OACfU,OAAQX,EAAkBC,KAEzBW,EAAS,SAAAtB,GAAA,OACVqB,OAAQP,EAAad,KAEpBuB,EAAe,SAAAL,GAAA,OAChBG,OAAQJ,EAAmBC,KAE1BM,EAAK,SAACC,EAAQC,EAAMC,EAAUC,GAAzB,MACVH,GAAOI,iBAAiBH,EAAMC,IAAYC,IAErCE,EAAgB,SAACL,EAAQP,EAAUa,GAAnB,MAA+B,UAAAC,GACnD,GAAMC,GAAgBD,EAAIP,OACpBS,EAAoBT,EAAOU,iBAAiBjB,GAC5CkB,EAAWC,MAAMC,UAAUC,QAAQrC,KAAKgC,EAAmBD,IAAkB,CAE/EG,IACFL,EAAQ7B,KAAK+B,EAAeD,KAI1BQ,EAAW,SAACf,EAAQP,EAAUQ,EAAMK,GAAzB,MAEhBP,GAAGC,EAAQC,EAAMI,EAAcL,EAAQP,EAAUa,GAAmB,SAATL,GAA4B,UAATA,IAEzEjC,GACJiB,oBACAI,eACAG,qBACAG,cACAE,SACAC,eACAC,KACAgB,WH+ED1C,GG5EQL","file":"meiosis-vanillajs.min.js","sourcesContent":["var meiosisVanillaJs =\n/******/ (function(modules) { // webpackBootstrap\n/******/ \t// The module cache\n/******/ \tvar installedModules = {};\n/******/\n/******/ \t// The require function\n/******/ \tfunction __webpack_require__(moduleId) {\n/******/\n/******/ \t\t// Check if module is in cache\n/******/ \t\tif(installedModules[moduleId])\n/******/ \t\t\treturn installedModules[moduleId].exports;\n/******/\n/******/ \t\t// Create a new module (and put it into the cache)\n/******/ \t\tvar module = installedModules[moduleId] = {\n/******/ \t\t\texports: {},\n/******/ \t\t\tid: moduleId,\n/******/ \t\t\tloaded: false\n/******/ \t\t};\n/******/\n/******/ \t\t// Execute the module function\n/******/ \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n/******/\n/******/ \t\t// Flag the module as loaded\n/******/ \t\tmodule.loaded = true;\n/******/\n/******/ \t\t// Return the exports of the module\n/******/ \t\treturn module.exports;\n/******/ \t}\n/******/\n/******/\n/******/ \t// expose the modules object (__webpack_modules__)\n/******/ \t__webpack_require__.m = modules;\n/******/\n/******/ \t// expose the module cache\n/******/ \t__webpack_require__.c = installedModules;\n/******/\n/******/ \t// __webpack_public_path__\n/******/ \t__webpack_require__.p = \"\";\n/******/\n/******/ \t// Load entry module and return exports\n/******/ \treturn __webpack_require__(0);\n/******/ })\n/************************************************************************/\n/******/ ([\n/* 0 */\n/***/ function(module, exports, __webpack_require__) {\n\n\t\"use strict\";\n\t\n\tvar _meiosisVanillajs = __webpack_require__(1);\n\t\n\tmodule.exports = _meiosisVanillajs.meiosisVanillaJs;\n\n/***/ },\n/* 1 */\n/***/ function(module, exports) {\n\n\t\"use strict\";\n\t\n\tObject.defineProperty(exports, \"__esModule\", {\n\t value: true\n\t});\n\tvar renderIntoElement = function renderIntoElement(element) {\n\t return function (view) {\n\t return element.innerHTML = view;\n\t };\n\t};\n\t\n\tvar renderIntoId = function renderIntoId(id) {\n\t return renderIntoElement(document.getElementById(id));\n\t};\n\t\n\tvar renderIntoSelector = function renderIntoSelector(selector) {\n\t return renderIntoElement(document.querySelector(selector));\n\t};\n\t\n\tvar intoElement = function intoElement(element) {\n\t return { render: renderIntoElement(element) };\n\t};\n\t\n\tvar intoId = function intoId(id) {\n\t return { render: renderIntoId(id) };\n\t};\n\t\n\tvar intoSelector = function intoSelector(selector) {\n\t return { render: renderIntoSelector(selector) };\n\t};\n\t\n\tvar on = function on(target, type, callback, useCapture) {\n\t return target.addEventListener(type, callback, !!useCapture);\n\t};\n\t\n\tvar dispatchEvent = function dispatchEvent(target, selector, handler) {\n\t return function (evt) {\n\t var targetElement = evt.target;\n\t var potentialElements = target.querySelectorAll(selector);\n\t var hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;\n\t\n\t if (hasMatch) {\n\t handler.call(targetElement, evt);\n\t }\n\t };\n\t};\n\t\n\tvar delegate = function delegate(target, selector, type, handler) {\n\t return(\n\t // https://developer.mozilla.org/en-US/docs/Web/Events/blur\n\t on(target, type, dispatchEvent(target, selector, handler), type === \"blur\" || type === \"focus\")\n\t );\n\t};\n\t\n\tvar meiosisVanillaJs = {\n\t renderIntoElement: renderIntoElement,\n\t renderIntoId: renderIntoId,\n\t renderIntoSelector: renderIntoSelector,\n\t intoElement: intoElement,\n\t intoId: intoId,\n\t intoSelector: intoSelector,\n\t on: on,\n\t delegate: delegate\n\t};\n\t\n\texports.meiosisVanillaJs = meiosisVanillaJs;\n\n/***/ }\n/******/ ]);\n\n\n/** WEBPACK FOOTER **\n ** meiosis-vanillajs.min.js\n **/"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap 334b670c6becef96c6d7\n **/","import { meiosisVanillaJs } from \"./meiosis-vanillajs\";\n\nmodule.exports = meiosisVanillaJs;\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/index.js\n **/","const renderIntoElement = element =>\n view => element.innerHTML = view;\n\nconst renderIntoId = id =>\n renderIntoElement(document.getElementById(id));\n\nconst renderIntoSelector = selector =>\n renderIntoElement(document.querySelector(selector));\n\nconst intoElement = element =>\n ({ render: renderIntoElement(element) });\n\nconst intoId = id =>\n ({ render: renderIntoId(id) });\n\nconst intoSelector = selector =>\n ({ render: renderIntoSelector(selector) });\n\nconst on = (target, type, callback, useCapture) =>\n\ttarget.addEventListener(type, callback, !!useCapture);\n\nconst dispatchEvent = (target, selector, handler) => evt => {\n const targetElement = evt.target;\n const potentialElements = target.querySelectorAll(selector);\n const hasMatch = Array.prototype.indexOf.call(potentialElements, targetElement) >= 0;\n\n if (hasMatch) {\n handler.call(targetElement, evt);\n }\n};\n\nconst delegate = (target, selector, type, handler) =>\n\t// https://developer.mozilla.org/en-US/docs/Web/Events/blur\n\ton(target, type, dispatchEvent(target, selector, handler), type === \"blur\" || type === \"focus\");\n\nconst meiosisVanillaJs = {\n renderIntoElement,\n renderIntoId,\n renderIntoSelector,\n intoElement,\n intoId,\n intoSelector,\n on,\n delegate\n};\n\nexport { meiosisVanillaJs };\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/meiosis-vanillajs.js\n **/"],"sourceRoot":""} \ No newline at end of file diff --git a/todomvc/js/meiosis.min.js b/todomvc/js/meiosis.min.js index 91f01c0c..7954d082 100644 --- a/todomvc/js/meiosis.min.js +++ b/todomvc/js/meiosis.min.js @@ -1,2 +1,2 @@ -var meiosis=function(e){function n(t){if(r[t])return r[t].exports;var i=r[t]={exports:{},id:t,loaded:!1};return e[t].call(i.exports,i,i.exports,n),i.loaded=!0,i.exports}var r={};return n.m=e,n.c=r,n.p="",n(0)}([function(e,n,r){"use strict";var t=r(1);e.exports=t.meiosis},function(e,n,r){"use strict";var t=r(2),i=r(3),o=function(e){var n=[],r=[],o=e.wire||i.defaultWire,u=o("meiosis"),a=e.merge||t.defaultMerge,c={},f=function(e){if(!(e&&(e.actions||e.nextUpdate||e.initialModel||e.ready||e.receiveUpdate||e.view)))throw new Error("Please specify a config when calling createComponent.");var t=e.initialModel||{};c=a(c,t);var i=o(),f=i.emit,s={sendUpdate:f},l=e.actions?a(s,e.actions(f)):s,v=e.receiveUpdate;v&&n.push(v);var d=e.ready;return d&&r.push(function(){return d(l)}),i.listen(function(r){n.forEach(function(e){return c=e(c,r)}),u.emit(c),e.nextUpdate&&e.nextUpdate(c,r,l)}),function(n){return e.view(n,l)}},s=function(t){0===n.length&&n.push(a);var i=function(n){e.render(t(n))};return u.listen(i),u.emit(c),r.forEach(function(e){e()}),i};return{createComponent:f,run:s}};n.meiosis=o},function(e,n){"use strict";var r=function(e){for(var n=[],r=1;r {\n let allReceiveUpdates: Array = [];\n let allReadies: Array = [];\n let allPostRenders: Array = [];\n\n const wire: WireCreator = adapters.wire || defaultWire;\n const rootWire = wire(\"meiosis\");\n\n const merge: Merger = adapters.merge || defaultMerge;\n\n let rootModel: any = {};\n\n const createComponent = (config: Config) => {\n if (!config || (\n !config.actions &&\n !config.nextUpdate &&\n !config.initialModel &&\n !config.ready &&\n !config.receiveUpdate &&\n !config.view\n )) {\n throw new Error(\"Please specify a config when calling createComponent.\");\n }\n const initialModel: any = config.initialModel || {};\n rootModel = merge(rootModel, initialModel);\n\n const componentWire: Wire = wire();\n const sendUpdate: Emitter = componentWire.emit;\n const sendUpdateActions = {sendUpdate};\n const actions = config.actions ? merge(sendUpdateActions, config.actions(sendUpdate)) : sendUpdateActions;\n\n const receiveUpdate: ReceiveUpdate = config.receiveUpdate;\n if (receiveUpdate) {\n allReceiveUpdates.push(receiveUpdate);\n }\n\n const ready: Ready = config.ready;\n if (ready) {\n allReadies.push(() => ready(actions));\n }\n\n const postRender: PostRender = config.postRender;\n if (postRender) {\n allPostRenders.push(postRender);\n }\n\n componentWire.listen((update: any) => {\n allReceiveUpdates.forEach((receiveUpdate: ReceiveUpdate) => {\n rootModel = receiveUpdate(rootModel, update);\n return rootModel;\n });\n\n rootWire.emit(rootModel);\n\n if (config.nextUpdate) {\n config.nextUpdate(rootModel, update, actions);\n }\n });\n\n return (model: any) => config.view(model, actions);\n };\n\n const run = (root: Component) => {\n if (allReceiveUpdates.length === 0) {\n allReceiveUpdates.push(merge);\n }\n const renderRoot = (model: any) => {\n const rootView = root(model);\n adapters.render(rootView);\n allPostRenders.forEach((postRender: PostRender) => postRender(rootView));\n };\n rootWire.listen(renderRoot);\n\n rootWire.emit(rootModel);\n allReadies.forEach((ready: Function) => ready());\n\n return renderRoot;\n };\n\n return { createComponent, run };\n};\n\nexport { meiosis };\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/meiosis.ts\n **/","interface Merger {\n (target: any, ...sources: Array): any;\n}\n\nconst defaultMerge: Merger = function(target: any, ...sources: Array) {\n if (target === undefined || target === null) {\n throw new TypeError(\"Cannot convert undefined or null to object\");\n }\n\n let output = Object(target);\n for (let index = 1; index < arguments.length; index++) {\n const source = arguments[index];\n if (source !== undefined && source !== null) {\n for (let nextKey in source) {\n if (source.hasOwnProperty(nextKey)) {\n output[nextKey] = source[nextKey];\n }\n }\n }\n }\n return output;\n};\n\nexport { Merger, defaultMerge };\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/merge.ts\n **/","interface Listener {\n (data: any): any;\n}\n\ninterface Emitter {\n (data: any): any;\n}\n\ninterface Wire {\n emit: Emitter;\n listen(listener: Listener): any;\n}\n\ninterface WireCreator {\n (wireName?: string): Wire;\n}\n\nconst defaultWire: WireCreator = (function() {\n let wires = {};\n let nextWireId = 1;\n\n const createWire = function(): Wire {\n let listener: Listener = null;\n const listen = (lstnr: Listener) => listener = lstnr;\n const emit = (data: any) => listener(data);\n\n return { emit, listen };\n };\n\n return function(wireName: string) {\n let name = wireName;\n if (!name) {\n name = \"wire_\" + nextWireId;\n nextWireId++;\n }\n let theWire: Wire = wires[name];\n if (!theWire) {\n theWire = createWire();\n wires[name] = theWire;\n }\n return theWire;\n };\n})();\n\nexport { Emitter, Listener, Wire, WireCreator, defaultWire };\n\n\n\n/** WEBPACK FOOTER **\n ** ./src/wire.ts\n **/"],"sourceRoot":""} \ No newline at end of file diff --git a/todomvc/vanillajs/main.js b/todomvc/vanillajs/main.js index 631c0e7a..ed6af133 100644 --- a/todomvc/vanillajs/main.js +++ b/todomvc/vanillajs/main.js @@ -9,8 +9,11 @@ view: ref.view, actions: ref.actions, ready: ref.ready, - receiveUpdate: ref.receiveUpdate + receiveUpdate: ref.receiveUpdate, + postRender: ref.postRender }); + ref.viewModel(createComponent); + Meiosis.run(Main); })(window); diff --git a/todomvc/vanillajs/postRender.js b/todomvc/vanillajs/postRender.js new file mode 100644 index 00000000..e06dbca5 --- /dev/null +++ b/todomvc/vanillajs/postRender.js @@ -0,0 +1,10 @@ +(function(ref) { + ref.postRender = function(_view) { + var input = document.querySelector("input.edit"); + + if (input) { + input.focus(); + input.selectionStart = input.value.length; + } + }; +})(window); diff --git a/todomvc/vanillajs/ready.js b/todomvc/vanillajs/ready.js index 005b4fb9..648edb19 100644 --- a/todomvc/vanillajs/ready.js +++ b/todomvc/vanillajs/ready.js @@ -8,9 +8,6 @@ if (evt.keyCode === ENTER_KEY) { actions.saveTodo(evt.target.value); } - else if (evt.keyCode === ESCAPE_KEY) { - // TODO - } }); meiosisVanillaJs.delegate(document.body, "input.toggle", "change", function(evt) { @@ -19,9 +16,54 @@ actions.setCompleted(todoId, completed); }); + meiosisVanillaJs.delegate(document.body, ".view label", "dblclick", function(evt) { + var todoId = parseInt(evt.target.dataset.id, 10); + actions.editTodo(todoId); + }); + + meiosisVanillaJs.delegate(document.body, "input.edit", "keyup", function(evt) { + var todoId = parseInt(evt.target.dataset.id, 10); + + if (evt.keyCode === ESCAPE_KEY) { + actions.cancelEdit(todoId); + } + else if (evt.keyCode === ENTER_KEY) { + actions.saveTodo(evt.target.value, todoId); + } + }); + + meiosisVanillaJs.delegate(document.body, "input.edit", "blur", function(evt) { + var todoId = parseInt(evt.target.dataset.id, 10); + actions.saveTodo(evt.target.value, todoId); + }); + meiosisVanillaJs.delegate(document.body, "button.destroy", "click", function(evt) { var todoId = parseInt(evt.target.dataset.id, 10); actions.deleteTodoId(todoId); }); + + /* + // select the target node + var target = document.querySelector("body"); + + // create an observer instance + var observer = new MutationObserver(function(_mutations) { + var input = document.querySelector("input.edit"); + + if (input) { + input.focus(); + input.selectionStart = input.value.length; + } + }); + + // configuration of the observer: + var config = { attributes: false, childList: true, characterData: false }; + + // pass in the target node, as well as the observer options + observer.observe(target, config); + + // later, you can stop observing + //observer.disconnect(); + */ }; })(window); diff --git a/todomvc/vanillajs/view.js b/todomvc/vanillajs/view.js index 31f519dd..1b98b549 100644 --- a/todomvc/vanillajs/view.js +++ b/todomvc/vanillajs/view.js @@ -20,15 +20,20 @@ }; var renderTodo = function(todo) { + var dataId = " data-id='" + todo.id + "'"; var completed = todo.completed ? " class='completed'" : ""; var checked = todo.completed ? " checked" : ""; + var editing = todo.editing ? " class='editing'" : ""; + var input = todo.editing ? + "" : ""; - return "" + + return "" + "
" + - "" + - "" + - "" + + "" + + "" + todo.title + "" + + "" + "
" + + input + ""; };