- wizard
- submit form
- panels
- classList
- session token: preserve entryPoint
- disable/enable button
- target transition event
- expandable list
- checkbox count
- component inheritance
- persistent controllers
- watched prop()
- component events
- component redraw
- request retry
- router wrapper
- mithril + jss
- 3rd party destroy component
- loading true while wating for request
- caching views
- spies
- clickable table
- infinite scroll with fps
Last active
September 17, 2015 02:13
-
-
Save pelonpelon/c15b9fe8251d0f5fa31b to your computer and use it in GitHub Desktop.
Mithril Snippets 2
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
'use strict' | |
function createView(ctrl, opts, children) { | |
return m('h1', 'Cached') | |
} | |
function controller() { | |
return { | |
view: m.prop() | |
} | |
} | |
function view(ctrl, opts, children) { | |
var view = ctrl.view() | |
if (!view || opts.refresh) { | |
view = createView(ctrl, opts, children) | |
ctrl.view(view) | |
return view | |
} | |
return {subtree: 'retain'} | |
} | |
module.exports = { | |
controller: controller, | |
view: view | |
} |
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
//controller: function(){ | |
// this.flagMap = {} | |
//}, | |
//view: function(){ | |
// m("input[type=checkbox]", {onclick: m.withAttr("checked", function(checked) {ctrl.flagMap[itemInTheLoop.id] = checked})}) | |
//} | |
// you may also need to set a config in m("input") to delete the flagMap key, | |
//if items in the list can be deleted, | |
//e.g. config: function(el, init, ctx) {ctx.onunload = function() {delete ctrl.flagMap[itemInTheLoop.id]}} | |
Widget = {} | |
Widget.controller = function () { | |
var ctrl = this | |
ctrl.todos = [ | |
{ name: 'One', checked: false }, | |
{ name: 'Two', checked: true }, | |
{ name: 'Three', checked: false } | |
] | |
ctrl.toggle = function (index, isChecked) { | |
ctrl.todos[index].checked = isChecked | |
} | |
} | |
Widget.view = function (ctrl) { | |
return m('.todos', [ | |
m('p', "Number checked: " + ctrl.todos.filter(isChecked).length), | |
ctrl.todos.map(function (todo, idx) { | |
return m('label', [ | |
todo.name, | |
m('input[type=checkbox]', { | |
checked: todo.checked, | |
onchange: ctrl.withAttr('checked', function(isChecked) { ctrl.toggle(idx, isChecked) }) | |
}) | |
]) | |
}) | |
]) | |
} | |
function isChecked (todo) { | |
return todo.checked | |
} |
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
function classList( classes ) { | |
if ( Array.isArray( classes ) ) { | |
return classes.filter( function ( item ) { | |
return !!item; | |
} ).join( ' ' ); | |
} | |
// Assume Object | |
var keys = Object.keys( classes ); | |
return keys.filter( function ( key ) { | |
return !!classes[ key ]; | |
} ).join( ' ' ); | |
} | |
classList( [ | |
1 === 1 ? '1is1' : '', | |
0.1 + 0.2 === 0.3 ? '0.3is0.3' : '' | |
] ); | |
//-> "1is1" | |
classList( { | |
'1is1': 1 === 1, | |
'0.3is0.3': 0.1 + 0.2 === 0.3 | |
} ); | |
//-> "1is1" |
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 tableComponent = { | |
controller( rows ){ | |
this.rows = rows.map( cells => | |
cells.map( content => ( { | |
content, | |
active : m.prop( false ) | |
} ) ) | |
) | |
this.clear = () => this.rows.forEach( cells => | |
cells.forEach( cell => | |
cell.active( false ) | |
) | |
) | |
}, | |
view : ( { rows, clear } ) => | |
m( 'table', | |
rows.map( cells => | |
m( 'tr', | |
cells.map( cell => | |
m.component( cellComponent, cell, clear ) | |
) | |
) | |
) | |
) | |
} | |
var cellComponent = { | |
view : ( ctrl, cell, clear ) => | |
m( 'td', { | |
className : cell.active() | |
? 'active' | |
: '', | |
onclick : () => { | |
clear() | |
cell.active( true ) | |
} | |
}, | |
cell.content | |
) | |
} | |
var tableData = Object.keys( window ) | |
.reduce( ( table, key, index ) => { | |
if( ( index % 5 ) === 0 ) | |
table.push( [] ) | |
table[ table.length - 1 ].push( key ) | |
return table | |
}, [] ) | |
m.mount( | |
document.body, | |
m.component( | |
tableComponent, | |
tableData | |
) | |
) |
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
// pass events as arg to component | |
events: { | |
onclick: tapDelegate.handleClick | |
} | |
--- | |
// component | |
for (n in args.events) { | |
fn = args.events[n]; | |
props[n] = function(e) { | |
fn(e, component, ctrl); | |
}; | |
} |
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
// es6 | |
var component = { | |
controller : function( ...args ){ | |
this.redraw = force => | |
this.root | |
&& m.module( | |
this.root, | |
component.view( | |
force | |
? new component( ...args ) | |
: this, | |
...args | |
).children | |
}, | |
view : ctrl => m( '', { | |
config : el => ctrl.root = el | |
}, whatever ) | |
} |
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 datePickerComponent = { | |
view : ctrl => m( '.datePickerRoot', { | |
config : ( el, init ) => { | |
if( !init ){ | |
var datePicker = $( el ).datePicker() | |
ctrl.onunload = datePicker.destroy() | |
} | |
} ) | |
} |
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
class ExpandList | |
constructor: (@items) -> | |
@expanded = {} | |
toggle: (item) => | |
if @expanded[item] | |
delete @expanded[item] | |
else | |
@expanded[item] = true | |
class App | |
controller: => | |
# Get the model from somewhere: | |
list = ["A", "B", "C", "D"] | |
@expList = new ExpandList list | |
@ # Return this to set the App object to controller | |
// List needs key attr | |
view: => | |
m 'div', @expList.items.map (i) => | |
expanded = @expList.expanded[i] | |
m '.item', { | |
onclick: => @expList.toggle i, | |
style: if expanded then {height: "100px"} else {} | |
}, | |
# Display additional properties of the list | |
# objects here if expanded: | |
(if expanded then "- " else "+ ") + i | |
m.module document.body, new App | |
```css | |
.item { | |
font-size: 150%; | |
border: 1px solid gray; | |
padding: 4px; | |
cursor: pointer; | |
} | |
``` |
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
//model | |
var app = {} | |
app.state = {pageY: 0, pageHeight: window.innerHeight} | |
var items = [] | |
for(var i = 0; i < 5000; i++) { | |
items.push({ | |
title: 'Foo Bar ' + i | |
}) | |
} | |
//yes, window.innerHeight is a data source, so it goes in the model | |
window.addEventListener("scroll", function(e) { | |
app.state.pageY = Math.max(e.pageY || window.pageYOffset, 0); | |
app.state.pageHeight = window.innerHeight; | |
m.redraw() //notify view | |
}) | |
//controller | |
app.controller = function() { | |
this.items = items | |
this.draws = 0 | |
this.dps = 'Wait for it...' | |
this.last = 0 | |
setInterval( function(){ | |
this.dps = this.draws - this.last | |
this.last = this.draws | |
}.bind( this ), 1000 ) | |
} | |
//view | |
app.view = function(ctrl) { | |
var pageY = app.state.pageY | |
var begin = pageY / 31 | 0 | |
// Add 2 so that the top and bottom of the page are filled with | |
// next/prev item, not just whitespace if item not in full view | |
var end = begin + (app.state.pageHeight / 31 | 0 + 2) | |
var offset = pageY % 31 | |
ctrl.draws++ | |
return [ | |
m(".list", {style: {height: ctrl.items.length * 31 + "px", position: "relative", top: -offset + "px"}}, [ | |
m("ul", {style: {top: app.state.pageY + "px"}}, [ | |
ctrl.items.slice(begin, end).map(function(item) { | |
return m("li", item.title) | |
}) | |
]) | |
]), | |
m('.redrawCount', { | |
style : { | |
position: 'fixed', | |
bottom : 0, | |
width : '100%', | |
background: '#fff' | |
} | |
}, [ | |
m( 'p', 'Number of draws: ' + ctrl.draws ), | |
m( 'p', 'Frames per second: ' + ctrl.dps ) | |
] | |
) | |
] | |
} | |
m.module(document.body, 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
m.initComponent = function (component, options, content) { | |
var controller = new component.controller(options) | |
controller.render = function (options2, content2) { | |
return component.view(controller, options2 || options, content2 || content) | |
} | |
return controller | |
} |
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
Widget = { | |
controller: function () { | |
this.css = Widget.stylesheet().classes | |
}, | |
view: function (ctrl) { | |
// ctrl.css.head is not a true selector; it's a CSS class that JSS will uniquely generate during runtime | |
return m('.widget', [ | |
m('h3', { class: ctrl.css.head }), | |
m('div', { class: ctrl.css.body }) | |
]) | |
}, | |
styles: { | |
head: { | |
'font-size': '3rem' | |
}, | |
body: { | |
'padding': '2rem', | |
'margin': '0 0 0.5rem 0' | |
} | |
}, | |
// This could be a mixin | |
stylesheet: function () { | |
this._stylesheet || (this._stylesheet = jss.createStyleSheet(this.styles).attach()) | |
return this._stylesheet | |
} | |
} |
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
// could be a missing forced redraw here | |
function load(){ | |
if( arguments.length ){ | |
loading = true | |
m.redraw( true ) | |
var request = m.request.apply( undefined, arguments ) | |
request.then( function(){ | |
loading = false | |
} ) | |
return request | |
} | |
else { | |
return loading | |
} | |
} |
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
// Reusable panel. Provides header styling, etc. | |
var Panel = m.component({ | |
view: function(ctrl, args, children) { | |
return m('.mypanel', [ | |
m('.mypanel-header', [args.header || ''], children) | |
]); | |
} | |
}); | |
// One type of panel | |
var Menu = m.component({ | |
view: function(ctrl, args) { | |
return Panel({ | |
header: m('h3', 'Menu') | |
}, [ | |
m('li', 'Menu item 1'), | |
m('li', 'Menu item 2') | |
]); | |
} | |
}); | |
// Another panel | |
var SomeWidget = m.component({ | |
view: function(ctrl, args) { | |
return Panel({ | |
header: m('h3', 'Panel header') | |
}, 'Panel content'); | |
} | |
}); | |
// A page with multiple panels | |
var Page = m.component({ | |
controller: function() {}, | |
view: function(ctrl, args) { | |
return m('div', {}, [ | |
m('h1', args.header), | |
Menu(), | |
SomeWidget() | |
]); | |
} | |
}); | |
m.mount(document.body, Page({ | |
header: "Page" | |
})); |
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
// using a closure | |
m.route( document.body, '/', { | |
'/:path...' : { | |
controller : ( function closure( ctrl, initialised ){ | |
return function controller(){ | |
ctrl.last = new Date().toString() | |
if( !initialised ){ | |
initialised = true | |
ctrl.first = new Date().toString() | |
} | |
return ctrl | |
} | |
}( {} ) ), | |
view : function( ctrl ){ | |
return [ | |
m( 'h1', 'Hiya' ), | |
m( 'p', 'Welcome to route: ', m.route() ), | |
m( 'p', 'The controller was initialised at ', ctrl.first ), | |
m( 'p', '...And updated at ', ctrl.last ), | |
m( 'h2', 'Navigate about to trigger component re-init:' ), | |
m( 'ol', | |
m( 'li', | |
m( 'a', { | |
config : m.route, | |
href : '/here' | |
}, 'Here' ) | |
), | |
m( 'li', | |
m( 'a', { | |
config : m.route, | |
href : '/there' | |
}, 'There' ) | |
) | |
) | |
] | |
} | |
} | |
} ) |
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
function connect( connection ){ | |
if( !connection ) connection = m.deferred() | |
m.request( { | |
method : 'GET', | |
url : '/place' | |
} ).then( | |
connection.resolve | |
function( error ){ | |
// No idea how your server responds - change to suit | |
if( error === '404' ){ | |
connect( connection ) | |
} | |
else { | |
connection.reject( error ) | |
} | |
} | |
) | |
return connection.promise | |
} |
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 Router = function(module, name) { | |
return { | |
controller: function() { | |
// Do something generic like calling Google Analytics from here | |
console.log("Router", name) | |
return new module.controller() | |
}, | |
view: module.view | |
} | |
} | |
m.route(document.getElementById("page"), "/", { | |
"/": Router(app, "app"), | |
"/project/:id": Router(project, "project") | |
}); |
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
//I can't provide a set of routes to the user, so I need to give them a loading page while I wait for their session to be authenticated | |
// (and then give them access to all the routes their profile dictates) or denied (in which case it's /login /signup only). | |
// What I'm doing is re-initialising the m.route whenever permissions change. Again, if all the controllers specify m.redraw.strategy( | |
// 'diff' ), you won't do any unnecessary DOM thrashing. So if there's a session token in localStorage, I'll query the server with that// token to see if it's still valid. But in the meantime I show the user a loading page: | |
// Run on init | |
m.route( document.body, '/', { | |
// Using a ... at the end of a path param means it can be any length of segments. So ':path...' will capture anything. | |
'/:path...' : { | |
controller(){ | |
// Preserve entry point | |
localStorage.setItem( 'entryPoint', m.route() ); | |
}, | |
// Wait til we've determined what's going on | |
view : function(){ | |
return m( 'div', 'Loading...' ); | |
} | |
} | |
} ); | |
Then if the session comes back refreshed, I run | |
// Stop redraws: we're about to make multiple redraw triggers | |
m.startComputation(); | |
// Set up the real route | |
m.route( document.body, '/', authenticatedRouteMap ); | |
// Re-route to wherever the user was headed in the first place | |
// If it's a bad route, it'll revert to the default set above | |
m.route( localStorage.getItem( 'entryPoint' ) ); | |
// We're done | |
m.endComputation(); | |
// If the session comes back expired, they get what they would have in the first place: signup, login, etc. |
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
m.spy = function (m, plugins) { | |
plugins = type.call(plugins) === ARRAY ? plugins : [plugins]; | |
function res() { | |
var vdom = m.apply(null, arguments) | |
for (var i = 0, N = plugins.length; i < N; i++) { | |
plugin[i](vdom); | |
// or maybe this? | |
// vdom = plugin[i](vdom) || vdom; | |
} | |
return vdom; | |
} | |
for (var k in m) if (m.hasOwnProperty(k)) res[k] = m[k]; | |
return res; | |
} |
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
// Here's a small demo illustrating a model entity to fetch and save data from a web service, | |
// a controller that exposes the entity and a save method to the view, | |
// and a form template that can be updated by the user and submitted. | |
//model | |
var Thing = function(data) { | |
this.name = m.prop(data.name) | |
} | |
Thing.get = function(id) { | |
return m.request({method: "GET", url: "/api/things/:id", data: {id: id}, type: Thing}) | |
} | |
Thing.update = function(data) { | |
return m.request({method: "POST", url: "/api/things", data: data, type: Thing}) | |
} | |
//module | |
var demoModule = {} | |
//controller | |
demoModule.controller = function() { | |
var thing = Thing.get(1) | |
return { | |
thing: thing | |
save: function() { | |
Thing.update(thing) | |
} | |
} | |
} | |
//view | |
demoModule.view = function(ctrl) { | |
return m("form", [ | |
m("input[placeholder=Name]", { | |
oninput: m.withAttr("value", ctrl.thing.name), | |
value: ctrl.thing.name() | |
}, | |
m("button[type=button]", {onclick: ctrl.save}) | |
]) | |
} | |
//run | |
m.module(document.body, demoModule) |
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 test = false | |
m.module(document.body, { | |
view: function (ctrl) { | |
return m('.foo', { | |
class: test ? "bar" : "", | |
onclick: function() {test = !test}, | |
config: function(el, init) { | |
if (!init) el.addEventListener("transitionend", function() {alert(1)}) | |
} | |
}, "test"); | |
} | |
}); | |
// css | |
// .foo {background:red;transition:all 1s ease;} | |
// .bar {background:green;} |
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
watchedProp = function (initialValue, onChange) { | |
var value = initialValue || null; | |
return function getterSetter( input ) { | |
if( arguments.length && input !== value){ | |
value = input; | |
onChange(value); | |
} | |
return value; | |
} | |
}; |
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
class Wizard | |
constructor: -> | |
@firstName = m.prop() | |
@lastName = m.prop() | |
@step = m.prop 0 | |
firstStep: => | |
@step 1 | |
m.route '/wizard/step_1' | |
nextStep: => | |
@step 2 | |
m.route '/wizard/step_2' | |
lastStep: => | |
@step 3 | |
alert "Your name is " + @firstName() + " " + @lastName() | |
m.route '/wizard/step_3' | |
stepView: => | |
switch @step() | |
when 0 then m "a[href=#]", onclick: @firstStep, "Start wizard" | |
when 1 | |
[ | |
m "span", "First name: " | |
m "input", oninput: m.withAttr "value", @firstName | |
m "a[href=#]", onclick: @nextStep, "Next" | |
] | |
when 2 | |
[ | |
m "span", "Last name: " | |
m "input", oninput: m.withAttr "value", @lastName | |
m "a[href=#]", onclick: @lastStep, "Next" | |
] | |
when 3 then "Finished" | |
view: => | |
[ | |
m "header", "Step " + @step() | |
m "main", @stepView() | |
m "footer", "Footer" | |
] | |
wizard = new Wizard | |
# Create a simple module that uses the wizard | |
# to avoid re-initialization of the object and data | |
wizModule = | |
controller: -> | |
view: wizard.view | |
m.route.mode = 'pathname' | |
m.route document.body, '/', | |
'/': wizModule | |
'/wizard/step_1': wizModule | |
'/wizard/step_2': wizModule | |
'/wizard/step_3': wizModule |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment