Skip to content

Instantly share code, notes, and snippets.

@pelonpelon
Last active September 17, 2015 02:18
Show Gist options
  • Save pelonpelon/c13e9d0adff426db5034 to your computer and use it in GitHub Desktop.
Save pelonpelon/c13e9d0adff426db5034 to your computer and use it in GitHub Desktop.
function theView() {
return things.map(function(thing) {
return m("tr", {index: index, onclick: function() {doStuff(thing)}}, thing.name) //anonymous function
})
}
function theView() {
return things.map(function(thing) {
return m("tr", {index: index, onclick: doStuff.bind(this, thing)}, thing.name) //partial application
})
}
var home = {}
new function(vm) {
vm.init = function() {
vm.name = "foo"
}
}(home.vm = {})
//or with controllers
foo.controller = function() {
var foo = {bar: 1}
return {
foo: foo
doStuff: doStuff
}
function doStuff() {
alert(foo.bar)
}
}
// I use busting technique to prevent caching ajax results:
var util = {};
util.bust = function () {
return "?bust=" + (new Date()).getTime();
};
// ...
var Model = {}
Model.list = function () {
return m.request({ method: "GET", url: "/things" + util.bust(), config: util.xhrConfig })
}
var tableComponent = {
controller : function(){
this.active = m.prop( false )
},
view : function( ctrl, rows ){
return m( 'table',
rows.map( function( cells ){
return m( 'tr',
cells.map( function( cell ){
return m.component( cellComponent, cell, ctrl.active )
} )
)
} )
)
}
}
var cellComponent = {
view : function( ctrl, cell, active ){
return m( 'td', {
className : active() === ctrl
? 'active'
: '',
onclick : function(){
active( ctrl )
}
},
cell
)
}
}
var tableData = Object.keys( window )
.reduce( function( 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
)
)
var config = function( forceRedraw ) {
return function( element, isInitialized ) {
if ( !isInitialized || forceRedraw ) {
// Do stuff
}
};
};
var app ={
call : m.prop( false )
}
var routeMessages = {
'welcome' : 'welcome',
'demo' : 'back'
}
app.view = function(ctrl) {
return m(".container", [
m('a.nav',{
config : m.route,
href : app.call() ? "/welcome" : "/demo"
},'CLICK ME!'),
m("p.content", ctrl.message ),
m("label", m("input[type=checkbox]", {
onclick : m.withAttr( "checked", app.call ),
checked : app.call()
} ), " Toggle call")
])
}
m.route(document.body, '/welcome', {
"/:key...": {
controller : function(){
this.message = routeMessages[ m.route.param( "key" ) ]
},
view : app.view
}
})
var layout = {}
layout.controller = function(module) {
this.controller = new module.controller
this.view = module.view
}
layout.view = function(ctrl) {
return m(".layout", [
ctrl.view(ctrl.controller)
])
}
layout.wrap = function(routes) {
var map = {}
Object.keys(routes).map(function(r) {
map[r] = {
controller: function() {
return new layout.controller(routes[r])
},
view: layout.view
}
})
return map
}
//Then wrap modules like this:
m.route(document, "/", layout.wrap({
"/": posts
}))
//With this, the inner modules don't need to redefine the layout stuff all the time.
// Just to add, normally I try not to pass ids around,
// if I'm planning on finding their respective list item.
// Instead I pass the item itself as an argument, since then you can call array.indexOf to find the item
function theView() {
return things.map(function(thing) {
return m("tr", {index: index, onclick: function() {doStuff(thing)}}, thing.name) //anonymous function
})
}
function theView() {
return things.map(function(thing) {
return m("tr", {index: index, onclick: doStuff.bind(this, thing)}, thing.name) //partial application
})
}
var m = require('mithril');
var Navbar = require('../components/Navbar.js');
var Auth = require('../models/Auth.js');
var Login = module.exports = {
controller: function(){
var ctrl = this;
ctrl.navbar = new Navbar.controller();
ctrl.error = m.prop('');
this.login = function(e){
e.preventDefault();
Auth.login(e.target.email.value, e.target.password.value)
.then(function(){
m.route(Auth.originalRoute || '/');
}, function(err){
ctrl.error(m(".alert.alert-danger.animated.fadeInUp", err.message));
});
};
},
view: function(ctrl){
return [Navbar.view(ctrl.navbar), m(".container", [
m("form.text-center.row.form-signin", {onsubmit:ctrl.login.bind(ctrl)},
m('.col-sm-6.col-sm-offset-3', [
m("h1", "login"),
ctrl.error(),
m('.form-group', [
m("label.sr-only[for='inputEmail']", "Email address"),
m("input.form-control[name='email'][autofocus][id='inputEmail'][placeholder='Email address'][required][type='email']"),
]),
m('.form-group', [
m("label.sr-only[for='inputPassword']", "Password"),
m("input.form-control[name='password'][autocomplete='off'][id='inputPassword'][placeholder='Password'][required][type='password']"),
]),
m('.form-group',
m("button.btn.btn-lg.btn-primary.btn-block[type='submit']", "Sign in")
)
])
)
])];
}
};
var unwrap = function(data, xhr) {
return {
status: xhr.status,
text: xhr.statusText,
data: data
};
};
m.request({method: 'GET', url: apiUrl + path, unwrapError: unwrap });
// return errors in a predictable way
var requestWithFeedback = function(args) {
var data = m.prop()
args.background = true
m.request(args).then(data).then(function() { m.redraw() })
return {
data: data,
ready: function() {return !!data()}
}
};
// more elaborate
var requestWithFeedback = function(args) {
var data = m.prop()
args.background = true
m.request(args).then(data).then(function() { m.redraw() })
return {
data: data,
ready: function() {return !!data()}
}
};
var mod = {
controller : function() {
this.test = requestWithFeedback({
method : "POST",
url : "/echo/json/",
serialize: serialize,
config: asFormUrlEncoded,
data : {
json : "{\"name\" : \"testing\"}"
}
});
},
view : function(ctrl) {
console.log(ctrl.test.ready(), ctrl.test.data());
return m("div", ctrl.test.ready() ? 'loaded' : 'loading');
}
};
m.module(document.body, {controller: mod.controller, view: mod.view});
function serialize(obj) {
var str = [];
for(var p in obj)
if (obj.hasOwnProperty(p)) {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
}
return str.join("&");
}
function asFormUrlEncoded(xhr) {
xhr.setRequestHeader("Content-type","application/x-www-form-urlencoded"); }
// Authentication
var Layout = function(module) {
return {
controller: function() {
return new Layout.controller(module)
},
view: Layout.view
}
}
Layout.controller = function(module) {
Auth.loggedIn.then(null, function() {
m.route("/login")
})
this.content = module.view.bind(this, new module.controller)
}
Layout.view = function(ctrl) {
return m(".container", ctrl.content())
}
//want to protect this
var MyModule = {
controller: function() {},
view: function() {}
}
m.route(document.body, "/", {
"/": Layout(MyModule),
"/login": Login //assuming a Login module also exists
})
*****
// Invoke as follows:
m.route( authRoutes( /* whatever you were passing to m.route in the first place */ ) );
function authRoutes( root, defaultRoute, router ){
for( var route in router ){
var module = router[ route ];
// We define a higher-level module for each route
router[ route ] = {
// View stays the same
view : module.view,
// Controller is only the same if module has a `skipAuth` flag
controller : module.skipAuth ? module.controller : function authController(){
// Auth is asynch, so stop rendering until the outcome is known
// (otherwise the view will render with an empty ctrl argument)
m.startComputation();
Auth.loggedIn().then( success, module.skipAuth ? success : failure );
function success(){
// Redirect to the original module, resume rendering
m.module( root, module );
m.endComputation();
}
function failure(){
m.module( root, router[ '/login' ] );
m.endComputation();
}
}
};
}
}
// similar
var m = window ? window.m : require('mithril');
if (!m && require) m = require('mithril');
var authonice = module.exports = {
token: m.prop(localStorage.token),
// is the user logged in?
loggedIn: function(){
return !!authonice.token();
},
// trade credentials for a token
login: function(email, password){
return m.request({
method: 'POST',
url: authonice.mountPoint + '/login',
data: {email:email, password:password},
unwrapSuccess: function(res) {
localStorage.token = res;
return res;
}
}).then(this.token);
},
// forget token
logout: function(){
authonice.token(false);
delete localStorage.token;
},
// signup on the server for new login credentials
register: function(email, password){
return m.request({
method: 'POST',
url: authonice.mountPoint + '/register',
data: {email:email, password:password}
});
},
// ensure verify token is correct
verify: function(token){
return m.request({
method: 'POST',
url: authonice.mountPoint + '/verify',
data: {token: token}
});
},
// get current user object
user: function(){
return authonice.req(authonice.mountPoint + '/user');
},
// make an authenticated request
req: function(options){
if (typeof options == 'string'){
options = {method:'GET', url:options};
}
var oldConfig = options.config || function(){};
options.config = function(xhr) {
xhr.setRequestHeader("authoniceorization", "Bearer " + authonice.token());
oldConfig(xhr);
};
// try request, if auth error, redirect
var deferred = m.deferred();
m.request(options).then(deferred.resolve, function(err){
if (err.status === 401){
authonice.originalRoute = m.route();
m.route(authonice.loginRoute);
}
});
return deferred.promise;
}
};
// configuration
authonice.mountPoint = '/auth';
authonice.loginRoute = '/login';
},{"mithril":"mithril"}]},{},[1])(1)
});
// Event capturing for Mithril.js
// Demo here: http://jsfiddle.net/barney/vsw8r3Lh/
m.capture = function capturingEventHandler( eventName, handler ){
function bindCapturingHandler( element ){
element.addEventListener( eventName, handler, true );
}
return function config( element, init ){
if( !init ) bindCapturingHandler( element );
};
};
var Post = function(data) {
var data = data || {};
this.id = m.prop(data.id);
this.title = m.prop(data.title);
this.content = m.prop(data.content);
}
#
Post.save = function(args) {
return m.request({method: 'POST', url: '/posts', data: args, unwrapError: unwrap})
}
# allow for added headers later
var req = function(args) {
return m.request(args)
}
Post.list = function() {
return req({method: 'GET', url: '/posts', type: app.Post})
}
//use the rejection handler of the promise to bind errors to a getter-setter
self.error = m.prop("")
self.add = function() {
/*...*/
Post.save()
.then(null, self.error)
}
//You can define the unwrapError function to drill down into a response
//to get the desired error message string out of the JSON structure.
//(There's also the extract option which lets you handle the response at the XHR level)
//So, if your response looks like {msg: "something died"},
//then you could write unwrapError: function(e) {return e.msg},
//and then to display the error, you would simply call vm.error() in the view,
//which would return the "something died" string.
var mod = {
globalId: 1,
controller: function() {
this.globalId = mod.globalId;
mod.globalId += 1;
},
view: function(ctrl) {
return m('span', [ 'I am ', ctrl.globalId ]);
}
};
var parent = {
view: function() {
return m('div', [
m('div', [m.module(mod, { key: 1 })]),
m('div', [m.module(mod, { key: 1 })])
]);
}
};
m.module(document.body, parent);
// will show "I am 1" and "I am 2", instead of two "I am 1"s.
var onKey = (function(){
var keymap = {
'enter' : 13,
'space' : 31,
'tab' : 9,
'esc' : 27,
'left' : 37,
'up' : 38,
'right' : 39,
'down' : 40
};
return function bind( key, callback ){
if( key in keymap ){
key = keymap[ key ];
}
return function handler( e ){
if( e && key === e.keyCode || key === String.fromCharCode( e.keyCode ) ){
callback.call( this, e );
}
else {
m.redraw.strategy( 'none' );
}
};
};
}());
//Usage
m('input', {
onkeypress: onKey('enter', yourkeypresshandler)
})
function theView() {
return things.map(function(thing) {
return m("tr", {index: index, onclick: doStuff.bind(this, thing)}, thing.name) //partial application
})
}
// Expose the return function so you can execute it after your first render
var release = ( function preventInteraction(){
// All interactive elements on the page
var interactives = [].slice.call( document.querySelectorAll('a,area,button,input,select,textarea,[contenteditable],[tabindex]') );
// Prevent any kind of activity
interactives.map( function preventFocus( el ){
el.addEventListener( 'click', stop, false );
el.addEventListener( 'focus', blur, false );
} );
return function(){
// On first render, remove the event listeners
interactives.map( function reinstate( el ){
el.removeEventListener( 'click', stop );
el.removeEventListener( 'focus', blur );
} );
};
function blur( e ){
e.target.blur();
}
function stop( e ){
e.preventDefault();
}
}() );
// Our modules, simple for the sake of example.
// The modules don't need any animator-specific code, nor are there any conditions that must be met.
var page1 = {
controller : function(){},
view : function(){
return m( '.page.page1', [
m('h1', 'Page 1!' ),
m( 'a', {
config : m.route,
href : '/route2'
}, 'Go to page 2' ),
' ',
m( 'a', {
config : m.route,
href : '/route3'
}, 'Go to page 3' )
] );
}
};
var page2 = {
controller : function(){},
view : function(){
return m( '.page.page2', [
m('h1', 'Page 2!' ),
m( 'a', {
config : m.route,
href : '/route1'
}, 'Go to page 1' ),
' ',
m( 'a', {
config : m.route,
href : '/route3'
}, 'Go to page 3' )
] );
}
};
var page3 = {
controller : function(){},
view : function(){
return m( '.page.page3', [
m('h1', 'Page 3!' ),
m( 'a', {
config : m.route,
href : '/route1'
}, 'Go to page 1' ),
' ',
m( 'a', {
config : m.route,
href : '/route2'
}, 'Go to page 2' )
] );
}
};
// A convenience wrapper to bind slideIn and slideOut functions (below) to a module using the animator plugin:
// https://gist.github.com/barneycarroll/c69fbe0786e37c941baf
var slidingPage = animator( slideIn, slideOut );
// Pass slidingPage variations of each page into the route.
m.route( document.body, '/route1', {
'/route1' : slidingPage( page1 ),
'/route2' : slidingPage( page2 ),
'/route3' : slidingPage( page3 )
} );
// Animation for sliding in. This is a bit basic, but you could do anything.
function slideIn( el, callback ){
el.style.left = '-100%';
el.style.top = '0';
el.style.position = 'fixed';
el.style.transition = 'left .6s ease-in-out';
setTimeout( function transit(){
el.style.left = '0%';
} );
el.addEventListener( 'transitionend', callback, false );
}
// Slide out.
function slideOut( el, callback ){
el.style.left = '0%';
el.style.top = '0';
el.style.position = 'fixed';
el.style.transition = 'left .6s ease-in-out';
setTimeout( function transit(){
el.style.left = '100%';
} );
// Remember to fire the callback when the animation is finished.
el.addEventListener( 'transitionend', callback, false );
}
// per Leo
var submodule = function(module, args) {
return module.view.bind(this, new module.controller(args))
}
root.controller = function() {
this.search = submodule(search)
this.filters = submodule(filters)
this.list = submodule(list)
}
root.view = function(ctrl) {
ctrl.search(),
ctrl.filters(),
ctrl.list()
}
// per Barney Carroll
m.route( document.body, '/', {
'/' : login,
'/:orgId/products' : page( products ),
'/:orgId/history' : page( history )
} );
var login = {
controller : function(){ /* ... */ },
view : function(){ /* ... */ }
};
var products = {
controller : function(){ /* ... */ },
view : function(){ /* ... */ }
};
var history = {
controller : function(){ /* ... */ },
view : function(){ /* ... */ }
};
// Site furniture
var topLevelMenu = {
controller : function(){ /* ... */ },
view : function(){ /* ... */ }
};
var footer = {
controller : function(){ /* ... */ },
view : function(){ /* ... */ }
};
function page(){
// Each argument is another module. Turn these into an array so we can iterate over them.
var submodules = [].slice.call( arguments );
return {
controller : function(){
// Initialize each submodules' controller classes.
this.subcontrollers = submodules.map( function( module ){
return new module.controller();
} );
},
view : function( ctrl ){
return m( '.page', [
// Insert top menu
topLevelMenu.view( new topLevelMenu.controller() ),
// Within div.content, initialise each submodule passed to the page function
m( '.content', submodules.map( function( module, index ){
return module.view( ctrl.subcontrollers[ index ] );
} ) ),
// Insert footer
footer.view( new footer.controller() )
] );
}
};
}
//per Stephan Hoyer
function baseView(scope) {
return [
m('header', [
// some header stuff
// variables from the current scope (like current active link) are
// possible, simply pass them via the scope variable
]),
m('main', scope.content),
];
}
// and in the currently active view
function view(scope) {
return baseView({
content: m('div', 'content goes here'),
activeWhatever: 'foo'
});
}
//helpers
var target
function tenant(id, module) {
return {
controller: module.controller,
view: function(ctrl) {
return target == id ? module.view(ctrl) : {subtree: "retain"}
}
}
}
function local(id, callback) {
return function(e) {
target = id
callback.call(this, e)
}
}
//a module
var MyModule = {
controller: function() {
this.doStuff = function() {alert(1)}
},
view: function() {
return m("button[type=button]", {
onclick: local("MyModule", ctrl.doStuff)
}, "redraw only MyModule")
}
}
//init
m.module(element, tenant("MyModule", MyModule))
//If you use browserify and mithril (what I strongly entourage you to do)
//there is an even simpler way. Browserify lets you include any file you want
//and base64 encodes it into the build. So first put your icons in a directory of your choice.
//Then you create a module called `icons.js`:
var fs = require('fs');
var m = require('mithril');
function icon(svg) {
return m('.icon', m.trust(svg));
}
module.exports = {
icon0: icon(fs.readFileSync('directory_of_your_choice/icon0.svg')),
icon1: icon(fs.readFileSync('directory_of_your_choice/icon1.svg')),
icon2: icon(fs.readFileSync('directory_of_your_choice/icon2.svg'))
};
//Browserify spots these files are read with `fs.readFileSync` loads them as Base64-encoded strings.
//The module then creates a `div` with class `icon` and embeds the svg into that div.
//The `m.trust` function prevents the svg from beeing html-encoded.
//Now you have a module with all your icons in it. To use them simply embed them into your view functions:
var icons = require('./icons');
function myView(scope) {
return m('.content', [
icons.icon1
'My fancy icon div'
]);
}
/*
The output then looks something like this
html
<div class="icon">
<svg>
<!-- <path>s and whatever other shapes in here -->
</svg>
</div>
*/
Pay attention to use the brfs-transform along with browserify. This embeds the readFileSync-ed files.
```
browserify -t brfs index.js
```
The advantages of this usage are:
* No building svg sprites
* Advantages of SVG over Fonts (better handling and styling)
* One less file, since the icons are embedded in your javascript file
* Easy to maintain
* Minimal code
* Pretty output
* Mithril-way of doing things
function Tabs(options) {
options = options || {};
var onTabClick = function(i) {
return function(e) {
e.preventDefault();
view.activeTab = i;
}
};
var view = function() {
console.log('hello from view');
var children = [];
var tabs = [];
var tabNames = arguments[0];
for (var i = 1; i < arguments.length; i++) {
tabs[i] = m('span', {onclick: onTabClick(i-1)}, tabNames[i-1]);
children[i] = m('div', {
style: {
display: ((i-1) == view.activeTab ? 'block' : 'none')
}
}, arguments[i]);
}
return m('.tabs',
m('.tabs-header', tabs),
m('.tabs-body', children));
};
view.activeTab = 0; // exported
return view;
}
var tabs = Tabs({});
m.module(document.body, {
controller: function() {
this.circle = function(element) {
$(element).empty().percentcircle();
}
},
view: function (ctrl) {
return m('.contenainer',
m('h1', 'Tabs'),
m('p', 'Active tab: ' + tabs.activeTab),
tabs(['A', 'B', 'C'],
m('p', 'Tab 1 content'),
m('p', 'Tab 2 content'),
m('p', 'Tab 2 content')
)
);
}
});
view = function () {
var listOfStuff = ...;
return m("div", { class: "container" },
listOfStuff.map(function (stuff, idx) {
return m("div", { "data-stuff-idx": idx, "class": "stuff-class", onclick: onStuffClick }, stuff.whatever);
}
}
onStuffClick = function (e) {
var stuffIdx = e.target.dataset.stuffIdx;
... do something with the idx ..
}
var show = true
var module = {
view: function() {
return [
m(".foo", {key: 1, config: test, onclick: function() {show = !show}}, "click me"),
show ? m(".bar", {key: 2}, "toggles") : null
]
}
}
function routeModule( module ){
return {
controller : function routeLogic(){
// Mithril executes this every time route changes.
var data = 'Perform all your logic here.';
return new module.controller( data );
},
view : module.view
}
m.route( document.body, '/', {
'/page1' : routeModule( module1 ),
'/page2' : routeModule( module2 )
} );
// similar
var m = require('mithril');
var Navbar = require('./components/Navbar.js');
var Auth = require('./models/Auth.js');
var linksIn = [
{label:'calendar', href:'/calendar'},
{label:'logout', href:'/logout'}
];
var linksOut = [
{label:'login', href:'/login'},
{label:'register', href:'/register'}
];
var navCtrl = new Navbar.controller('tvparty');
var Page = function(page){
this.controller = function(){
navCtrl.links(Auth.token() ? linksIn : linksOut);
return new page.controller();
};
this.view = function(ctrl){
return [
m('nav', Navbar.view(navCtrl)),
m('section.container', page.view(ctrl)),
];
};
};
m.route.mode = 'pathname';
m.route(document.body, "/", {
"/": new Page(require('./pages/Home.js')),
"/login": new Page(require('./pages/Login.js')),
"/logout": new Page(require('./pages/Logout.js')),
"/register": new Page(require('./pages/Register.js')),
"/verify/:code": Page(require('./pages/Verify.js')),
"/calendar": new Page(require('./pages/Calendar.js'))
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment