Level: expert • Mithril.js Version: latest
dom m.route oninit onupdate todomvc
Here is another TodoMVC real world example, this time a more traditional one. As a realistic application example it shows Mithril.js' most used concepts, methods and lifecycle methodes. State is managed in this example with simple object literals.
\n"},{"name":"model.js","content":"// model.js\nconst model = {\n todos: [],\n filters: [\n {filter: 'All', text: 'All'},\n {filter: false, text: 'Active'},\n {filter: true, text: 'Completed'}\n ],\n filter: 'All',\n all: true,\n enter: 13\n}\n\nexport default model\n"},{"name":"actions.js","content":"// actions.js\nimport model from './model'\n\nconst actions = {\n init: () => {model.todos = JSON.parse(localStorage.getItem('todos-mithril')) || []},\n clone: x => JSON.parse(JSON.stringify(x)),\n save: () => localStorage.setItem('todos-mithril', JSON.stringify(model.todos)),\n push: t => {\n model.todos.push(actions.clone(t))\n actions.save()\n },\n done: x => {\n if (x === 'all') {\n model.todos.forEach(x => x.completed = model.all)\n model.all = !model.all\n }\n else model.todos[x].completed = !model.todos[x].completed\n actions.save()\n },\n clear: () => {\n model.todos = model.todos.filter(x => !x.completed)\n model.all = true\n }\n}\n\nexport default actions\n"},{"name":"header.js","content":"// header.js\nimport model from './model'\nimport actions from './actions'\n\nconst header = () => {\n let todon = {}\n\n const add = () => {\n todon.id = Date.now()\n todon.title.trim()\n todon.completed = false\n actions.push(todon)\n todon = {}\n }\n\n return {\n view: () =>\n m('header.header',\n m('h1', 'todos'),\n m('input.new-todo', {\n autofocus: true,\n placeholder: 'What needs to be done?',\n oninput: e => todon.title = e.target.value,\n onkeydown: e => {if (e.keyCode === model.enter && e.target.value.trim()) add()},\n value: todon.title\n })\n )\n }\n}\n\nexport default header\n"},{"name":"main.js","content":"// main.js\nimport model from './model'\nimport actions from './actions'\n\nconst main = () => {\n const escape = 27\n let todo = {}\n\n return {\n view: () =>\n m('section.main',\n m('input.toggle-all', {\n id: 'toggle-all',\n type: 'checkbox',\n checked: model.todos.every(x => x.completed)\n }),\n m('label', {\n for: 'toggle-all',\n onclick: () => actions.done('all')\n }, 'Mark all as complete'),\n model.todos\n .filter(x => model.filter === 'All' ? x : x.completed === model.filter)\n .map((x,i) =>\n m('ul.todo-list',\n m('li', {\n class: `${x.completed ? 'completed' : 'active'} ${x.id === todo.id && 'editing'}`,\n },\n m('.view',\n m('input.toggle', {\n type: 'checkbox',\n checked: x.completed,\n onchange: () => actions.done(i)\n }),\n m('label', {ondblclick: () => todo = actions.clone(x)}, x.title),\n m('button.destroy', {onclick: () => model.todos.splice(i,1)})\n ),\n m('input.edit', {\n onupdate: ({dom}) => dom.focus(),\n oninput: e => todo.title = e.target.value,\n onblur: e => {\n if (Object.keys(todo).length) {\n if (!todo.title.trim()) {\n model.todos.splice(i,1)\n todo = {}\n }\n else {\n model.todos[i] = actions.clone(todo)\n actions.save()\n todo = {}\n }\n }\n },\n onkeydown: e => {\n if (e.keyCode === model.enter) e.target.blur()\n else if (e.keyCode === escape) todo = {}\n },\n value: todo.title\n })\n )\n )\n )\n )\n }\n}\n\nexport default main\n"},{"name":"footer.js","content":"// footer.js\nimport model from './model'\nimport actions from './actions'\n\nconst footer = () => {\n const left = () => model.todos.filter(x => !x.completed).length\n\n return {\n view: () =>\n m('footer.footer',\n m('span.todo-count',\n m('strong', left()), ` item${left() === 1 ? '' : 's'} left`\n ),\n m('ul.filters',\n model.filters.map(x =>\n m('li',\n m('a', {\n class: model.filter === x.filter && 'selected',\n onclick: () => {model.filter = x.filter; m.route.set('/' + x.text)}\n }, x.text)\n )\n )\n ),\n model.todos.find(x => x.completed) &&\n m('button.clear-completed', {\n onclick: actions.clear\n }, 'Clear completed')\n )\n }\n}\n\nexport default footer\n"},{"name":"app.js","content":"// app.js\nimport model from './model'\nimport actions from './actions'\nimport header from './header'\nimport main from './main'\nimport footer from './footer'\n\nconst app = {\n oninit: () => actions.init(),\n view: () => [\n m(header),\n model.todos.length > 0 && m(main),\n model.todos.length > 0 && m(footer)\n ]\n}\n\nm.route(document.querySelector('.todoapp'), '/All', {'/:filter': app})\n"},{"name":".css","content":"html,\nbody {\n\tmargin: 0;\n\tpadding: 0;\n}\n\nbutton {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tbackground: none;\n\tfont-size: 100%;\n\tvertical-align: baseline;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\tcolor: inherit;\n\t-webkit-appearance: none;\n\tappearance: none;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\nbody {\n\tfont: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;\n\tline-height: 1.4em;\n\tbackground: #f5f5f5;\n\tcolor: #4d4d4d;\n\tmin-width: 230px;\n\tmax-width: 550px;\n\tmargin: 0 auto;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tfont-weight: 300;\n}\n\n:focus {\n\toutline: 0;\n}\n\n.hidden {\n\tdisplay: none;\n}\n\n.todoapp {\n\tbackground: #fff;\n\tmargin: 130px 0 40px 0;\n\tposition: relative;\n\tbox-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),\n\t 0 25px 50px 0 rgba(0, 0, 0, 0.1);\n}\n\n.todoapp input::-webkit-input-placeholder {\n\tfont-style: italic;\n\tfont-weight: 300;\n\tcolor: #e6e6e6;\n}\n\n.todoapp input::-moz-placeholder {\n\tfont-style: italic;\n\tfont-weight: 300;\n\tcolor: #e6e6e6;\n}\n\n.todoapp input::input-placeholder {\n\tfont-style: italic;\n\tfont-weight: 300;\n\tcolor: #e6e6e6;\n}\n\n.todoapp h1 {\n\tposition: absolute;\n\ttop: -155px;\n\twidth: 100%;\n\tfont-size: 100px;\n\tfont-weight: 100;\n\ttext-align: center;\n\tcolor: rgba(175, 47, 47, 0.15);\n\t-webkit-text-rendering: optimizeLegibility;\n\t-moz-text-rendering: optimizeLegibility;\n\ttext-rendering: optimizeLegibility;\n}\n\n.new-todo,\n.edit {\n\tposition: relative;\n\tmargin: 0;\n\twidth: 100%;\n\tfont-size: 24px;\n\tfont-family: inherit;\n\tfont-weight: inherit;\n\tline-height: 1.4em;\n\tborder: 0;\n\tcolor: inherit;\n\tpadding: 6px;\n\tborder: 1px solid #999;\n\tbox-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);\n\tbox-sizing: border-box;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.new-todo {\n\tpadding: 16px 16px 16px 60px;\n\tborder: none;\n\tbackground: rgba(0, 0, 0, 0.003);\n\tbox-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);\n}\n\n.main {\n\tposition: relative;\n\tz-index: 2;\n\tborder-top: 1px solid #e6e6e6;\n}\n\n.toggle-all {\n\twidth: 1px;\n\theight: 1px;\n\tborder: none; /* Mobile Safari */\n\topacity: 0;\n\tposition: absolute;\n\tright: 100%;\n\tbottom: 100%;\n}\n\n.toggle-all + label {\n\twidth: 60px;\n\theight: 34px;\n\tfont-size: 0;\n\tposition: absolute;\n\ttop: -52px;\n\tleft: -13px;\n\t-webkit-transform: rotate(90deg);\n\ttransform: rotate(90deg);\n}\n\n.toggle-all + label:before {\n\tcontent: '❯';\n\tfont-size: 22px;\n\tcolor: #e6e6e6;\n\tpadding: 10px 27px 10px 27px;\n}\n\n.toggle-all:checked + label:before {\n\tcolor: #737373;\n}\n\n.todo-list {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n}\n\n.todo-list li {\n\tposition: relative;\n\tfont-size: 24px;\n\tborder-bottom: 1px solid #ededed;\n}\n\n.todo-list li:last-child {\n\tborder-bottom: none;\n}\n\n.todo-list li.editing {\n\tborder-bottom: none;\n\tpadding: 0;\n}\n\n.todo-list li.editing .edit {\n\tdisplay: block;\n\twidth: calc(100% - 43px);\n\tpadding: 12px 16px;\n\tmargin: 0 0 0 43px;\n}\n\n.todo-list li.editing .view {\n\tdisplay: none;\n}\n\n.todo-list li .toggle {\n\ttext-align: center;\n\twidth: 40px;\n\t/* auto, since non-WebKit browsers doesn't support input styling */\n\theight: auto;\n\tposition: absolute;\n\ttop: 0;\n\tbottom: 0;\n\tmargin: auto 0;\n\tborder: none; /* Mobile Safari */\n\t-webkit-appearance: none;\n\tappearance: none;\n}\n\n.todo-list li .toggle {\n\topacity: 0;\n}\n\n.todo-list li .toggle + label {\n\t/*\n\t\tFirefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433\n\t\tIE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/\n\t*/\n\tbackground-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');\n\tbackground-repeat: no-repeat;\n\tbackground-position: center left;\n}\n\n.todo-list li .toggle:checked + label {\n\tbackground-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');\n}\n\n.todo-list li label {\n\tword-break: break-all;\n\tpadding: 15px 15px 15px 60px;\n\tdisplay: block;\n\tline-height: 1.2;\n\ttransition: color 0.4s;\n}\n\n.todo-list li.completed label {\n\tcolor: #d9d9d9;\n\ttext-decoration: line-through;\n}\n\n.todo-list li .destroy {\n\tdisplay: none;\n\tposition: absolute;\n\ttop: 0;\n\tright: 10px;\n\tbottom: 0;\n\twidth: 40px;\n\theight: 40px;\n\tmargin: auto 0;\n\tfont-size: 30px;\n\tcolor: #cc9a9a;\n\tmargin-bottom: 11px;\n\ttransition: color 0.2s ease-out;\n}\n\n.todo-list li .destroy:hover {\n\tcolor: #af5b5e;\n}\n\n.todo-list li .destroy:after {\n\tcontent: '×';\n}\n\n.todo-list li:hover .destroy {\n\tdisplay: block;\n}\n\n.todo-list li .edit {\n\tdisplay: none;\n}\n\n.todo-list li.editing:last-child {\n\tmargin-bottom: -1px;\n}\n\n.footer {\n\tcolor: #777;\n\tpadding: 10px 15px;\n\theight: 20px;\n\ttext-align: center;\n\tborder-top: 1px solid #e6e6e6;\n}\n\n.footer:before {\n\tcontent: '';\n\tposition: absolute;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\theight: 50px;\n\toverflow: hidden;\n\tbox-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),\n\t 0 8px 0 -3px #f6f6f6,\n\t 0 9px 1px -3px rgba(0, 0, 0, 0.2),\n\t 0 16px 0 -6px #f6f6f6,\n\t 0 17px 2px -6px rgba(0, 0, 0, 0.2);\n}\n\n.todo-count {\n\tfloat: left;\n\ttext-align: left;\n}\n\n.todo-count strong {\n\tfont-weight: 300;\n}\n\n.filters {\n\tmargin: 0;\n\tpadding: 0;\n\tlist-style: none;\n\tposition: absolute;\n\tright: 0;\n\tleft: 0;\n}\n\n.filters li {\n\tdisplay: inline;\n}\n\n.filters li a {\n\tcolor: inherit;\n\tmargin: 3px;\n\tpadding: 3px 7px;\n\ttext-decoration: none;\n\tborder: 1px solid transparent;\n\tborder-radius: 3px;\n}\n\n.filters li a:hover {\n\tborder-color: rgba(175, 47, 47, 0.1);\n cursor: pointer;\n}\n\n.filters li a.selected {\n\tborder-color: rgba(175, 47, 47, 0.2);\n}\n\n.clear-completed,\nhtml .clear-completed:active {\n\tfloat: right;\n\tposition: relative;\n\tline-height: 20px;\n\ttext-decoration: none;\n\tcursor: pointer;\n}\n\n.clear-completed:hover {\n\ttext-decoration: underline;\n}\n\n.info {\n\tmargin: 65px auto 0;\n\tcolor: #bfbfbf;\n\tfont-size: 10px;\n\ttext-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);\n\ttext-align: center;\n}\n\n.info p {\n\tline-height: 1;\n}\n\n.info a {\n\tcolor: inherit;\n\ttext-decoration: none;\n\tfont-weight: 400;\n}\n\n.info a:hover {\n\ttext-decoration: underline;\n}\n\n/*\n\tHack to remove background from Mobile Safari.\n\tCan't use it globally since it destroys checkboxes in Firefox\n*/\n@media screen and (-webkit-min-device-pixel-ratio:0) {\n\t.toggle-all,\n\t.todo-list li .toggle {\n\t\tbackground: none;\n\t}\n\n\t.todo-list li .toggle {\n\t\theight: 40px;\n\t}\n}\n\n@media (max-width: 430px) {\n\t.footer {\n\t\theight: 50px;\n\t}\n\n\t.filters {\n\t\tbottom: 10px;\n\t}\n}\n"}], links: [{"name":"mithril@latest","type":"script","url":"https://unpkg.com/mithril@latest"}] });
Type | Name | URL |
---|---|---|
script | mithril@latest | https://unpkg.com/mithril@latest |
<body>
<section class="todoapp"></section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://todomvc.com">people</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<!-- Scripts here. Don't remove ↓ -->
<!-- <script src="node_modules/todomvc-common/base.js"></script>-->
<!-- <script src="js/app.js"></script>-->
</body>
// model.js
const model = {
todos: [],
filters: [
{filter: 'All', text: 'All'},
{filter: false, text: 'Active'},
{filter: true, text: 'Completed'}
],
filter: 'All',
all: true,
enter: 13
}
export default model
// actions.js
import model from './model'
const actions = {
init: () => {model.todos = JSON.parse(localStorage.getItem('todos-mithril')) || []},
clone: x => JSON.parse(JSON.stringify(x)),
save: () => localStorage.setItem('todos-mithril', JSON.stringify(model.todos)),
push: t => {
model.todos.push(actions.clone(t))
actions.save()
},
done: x => {
if (x === 'all') {
model.todos.forEach(x => x.completed = model.all)
model.all = !model.all
}
else model.todos[x].completed = !model.todos[x].completed
actions.save()
},
clear: () => {
model.todos = model.todos.filter(x => !x.completed)
model.all = true
}
}
export default actions
// header.js
import model from './model'
import actions from './actions'
const header = () => {
let todon = {}
const add = () => {
todon.id = Date.now()
todon.title.trim()
todon.completed = false
actions.push(todon)
todon = {}
}
return {
view: () =>
m('header.header',
m('h1', 'todos'),
m('input.new-todo', {
autofocus: true,
placeholder: 'What needs to be done?',
oninput: e => todon.title = e.target.value,
onkeydown: e => {if (e.keyCode === model.enter && e.target.value.trim()) add()},
value: todon.title
})
)
}
}
export default header
// main.js
import model from './model'
import actions from './actions'
const main = () => {
const escape = 27
let todo = {}
return {
view: () =>
m('section.main',
m('input.toggle-all', {
id: 'toggle-all',
type: 'checkbox',
checked: model.todos.every(x => x.completed)
}),
m('label', {
for: 'toggle-all',
onclick: () => actions.done('all')
}, 'Mark all as complete'),
model.todos
.filter(x => model.filter === 'All' ? x : x.completed === model.filter)
.map((x,i) =>
m('ul.todo-list',
m('li', {
class: `${x.completed ? 'completed' : 'active'} ${x.id === todo.id && 'editing'}`,
},
m('.view',
m('input.toggle', {
type: 'checkbox',
checked: x.completed,
onchange: () => actions.done(i)
}),
m('label', {ondblclick: () => todo = actions.clone(x)}, x.title),
m('button.destroy', {onclick: () => model.todos.splice(i,1)})
),
m('input.edit', {
onupdate: ({dom}) => dom.focus(),
oninput: e => todo.title = e.target.value,
onblur: e => {
if (Object.keys(todo).length) {
if (!todo.title.trim()) {
model.todos.splice(i,1)
todo = {}
}
else {
model.todos[i] = actions.clone(todo)
actions.save()
todo = {}
}
}
},
onkeydown: e => {
if (e.keyCode === model.enter) e.target.blur()
else if (e.keyCode === escape) todo = {}
},
value: todo.title
})
)
)
)
)
}
}
export default main
// footer.js
import model from './model'
import actions from './actions'
const footer = () => {
const left = () => model.todos.filter(x => !x.completed).length
return {
view: () =>
m('footer.footer',
m('span.todo-count',
m('strong', left()), ` item${left() === 1 ? '' : 's'} left`
),
m('ul.filters',
model.filters.map(x =>
m('li',
m('a', {
class: model.filter === x.filter && 'selected',
onclick: () => {model.filter = x.filter; m.route.set('/' + x.text)}
}, x.text)
)
)
),
model.todos.find(x => x.completed) &&
m('button.clear-completed', {
onclick: actions.clear
}, 'Clear completed')
)
}
}
export default footer
// app.js
import model from './model'
import actions from './actions'
import header from './header'
import main from './main'
import footer from './footer'
const app = {
oninit: () => actions.init(),
view: () => [
m(header),
model.todos.length > 0 && m(main),
model.todos.length > 0 && m(footer)
]
}
m.route(document.querySelector('.todoapp'), '/All', {'/:filter': app})
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #4d4d4d;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
:focus {
outline: 0;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 300;
color: #e6e6e6;
}
.todoapp h1 {
position: absolute;
top: -155px;
width: 100%;
font-size: 100px;
font-weight: 100;
text-align: center;
color: rgba(175, 47, 47, 0.15);
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
border: 0;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
width: 60px;
height: 34px;
font-size: 0;
position: absolute;
top: -52px;
left: -13px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all + label:before {
content: '❯';
font-size: 22px;
color: #e6e6e6;
padding: 10px 27px 10px 27px;
}
.toggle-all:checked + label:before {
color: #737373;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: calc(100% - 43px);
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
}
.todo-list li.completed label {
color: #d9d9d9;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #cc9a9a;
margin-bottom: 11px;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover {
color: #af5b5e;
}
.todo-list li .destroy:after {
content: '×';
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
color: #777;
padding: 10px 15px;
height: 20px;
text-align: center;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: rgba(175, 47, 47, 0.1);
cursor: pointer;
}
.filters li a.selected {
border-color: rgba(175, 47, 47, 0.2);
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 20px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #bfbfbf;
font-size: 10px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
As a prerequisite for this snippet, the latest version of Mithril.js framework is required. The example really targets users on expert level, that are familiar with all aspects of Mithril.js and JavaScript itself.
In this code sample Mithril.js' m.route
API method is use, besides the basic hyperscript function m().
Moreover, it can also be seen how lifecycle methods (as known as hooks) like onupdate
and oninit
can be used.
The example was written by osban, last edits were made on 29 October 2021. The author has contributed some more snippets. Click here to see them all.
Did you note a typo or something else? So let me know by opening an issue. Or much better: just fork the repository on GitHub, push your commits and send a pull request. Ready to start your work? Then click on the edit link below. Thanks in advance!