<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id='app'></div>
<script src="http://cdn.jsdelivr.net/mithril/0.1.27/mithril.js"></script>
<script src="mithril.elements.js"></script>
<script src="app.js"></script>
</body>
</html>
.accordian {
width:150px;
background-color:#ddd;
}
.item {padding:5px};
.item div {background-color:white;padding 1px;}
var dataStack = [];
function calculateView(data,top){
// calculate the begin and end indicies of the scrollable section
var begin = top / data.itemHeight | 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 + data.rows + 2;
data.items = data.content.slice(begin,end);
data.offset = top + data.itemHeight;
}
m.element('table', {
controller:function(){
var data = dataStack.pop();
var scroller={scrollTop:0};
this.setup = function(element,done){
if (!done){
scroller = element;
element.addEventListener('scroll', function(e) {
calculateView(data,scroller.scrollTop);
m.redraw(); //notify view
});
}
};
this.height = Math.min(data.content.length,data.rows) * data.itemHeight + 'px';
},
view:function(ctrl,content){
return m('$table',
{style:{display:'block',overflow:'scroll', height:ctrl.height},config:ctrl.setup},
content
)
}
})
m.element('thead', {
controller: function() {
},
view: function(ctrl, content) {
return m('$thead', {style:{display:'block', position:'fixed'}}, content.map(function(th){
return m('$th',th)
}))
}
});
m.element('tbody', {
controller: function(data) {
data.itemHeight=20;
this.data = data;
this.rows = data.rows;
this.height = data.content.length * data.itemHeight + 'px';
calculateView(data,0);
dataStack.push(data);
},
view: function(ctrl, content) {
return m('$tbody', {style:{height: ctrl.height,display:'block',position:'relative', top: ctrl.data.offset + 'px'}}, ctrl.data.items.map(function(tr,r){
return m('$tr',content(tr));
}))
}
});
m.module(document.getElementById('app'),{
controller:function(){
this.data=[];
for(var i=0;i<5000;i++){
this.data.push({
name:'name'+i,
posts:i,
lastTopic:'lastTopic '+i
})
}
},
view:function(ctrl){
var hugeArray = ctrl.data;
return m('table', [
m('thead', ['Name','Posts','Last Topic']),
m('tbody',{state:{rows:12, content:hugeArray}}, function(content){return [
m('td', content.name),
m('td', content.posts),
m('td', content.lastTopic)
]}
)
])
}
});
/*
* Mithril.Elements
* Copyright (c) 2014 Phil Toms (@PhilToms3).
*
* This source code is licensed under the MIT license found in the
* LICENSE.txt file in the root directory of this source tree.
*/
'use strict';
var m = (function app(window, mithril) {
var OBJECT = '[object Object]', ARRAY = '[object Array]', STRING = '[object String]', FUNCTION = "[object Function]";
var type = {}.toString;
// save the mithril API
mithril = mithril || require('mithril');
var redraw = mithril.redraw;
var strategy = redraw.strategy;
function merge(obj1,obj2,filter){
var classAttrName = 'class' in obj1 ? 'class' : 'className';
var classes = obj1[classAttrName]|| '';
Object.keys(obj2).forEach(function(k){
if (k.indexOf('class')>=0){
classes += ' ' + obj2[k];
} else if ((filter||' ').indexOf(k)<0) {
obj1[k] = obj2[k];
}
});
if (classes && classes.trim()) {
obj1[classAttrName]=classes.trim();
}
return obj1;
}
mithril.redraw = function(force) {
// key into mithril page lifecycle
if (strategy()==='all'){
controllers={};
}
lastId=0;
return redraw(force);
};
mithril.redraw.strategy = strategy;
var elements = {}, controllers={},lastId=0;
var m = function(module, attrs, children) {
var tag = module.tag || module;
var args = [tag].concat([].slice.call(arguments,1));
var cell = mithril.apply(null,args);
var element = elements[cell.tag];
// pass through if not registered or escaped
if (element && tag[0]!=='$') {
var hasAttrs = attrs != null && type.call(attrs) === OBJECT && !("tag" in attrs) && !("subtree" in attrs);
if (!hasAttrs && !children){
children=attrs;
}
attrs = merge(module.attrs || {}, cell.attrs);
var state = hasAttrs && attrs.state;
var id = module.id || (state && state.id!==undefined? state.id : (attrs.key!==undefined? attrs.key : (attrs.id!==undefined? attrs.id :undefined)));
id = cell.tag + (id===undefined? lastId++ : id);
// once-only element initialization. But note:
// module.id - singleton
// controllers[id] - cached
// default - new instance
var ctrl = (module.id && module) || controllers[id] || new element.controller(state);
controllers[id]=ctrl;
var inner = cell.children.length==1? cell.children[0]:cell.children;
var c_cell = element.view(ctrl, inner);
if (c_cell){
cell=c_cell;
if (type.call(cell) !== ARRAY) {
merge(cell.attrs,attrs,'state');
}
}
}
// tidy up tag
if (cell.tag && cell.tag[0]==='$'){
cell.tag=cell.tag.substr(1);
}
return cell;
};
function DefaultController(state){
this.state = state;
}
var sId=0;
m.element = function(root, module){
if (type.call(root) !== STRING) throw new Error('selector m.element(selector, module) should be a string');
// all elements have controllers
module.controller = module.controller || DefaultController;
// add a programmable interface to the element
module.instance = function(state){
var ctrl = new module.controller(state);
ctrl.tag = root;
ctrl.id = '$ctrl_' + root + sId++;
return ctrl;
}
// nothing more to do here, element initialization is lazily
// deferred to first redraw
return (elements[root] = module);
};
// build the new API
return merge(m,mithril);
})(typeof window != "undefined" ? window : {},m);
if (typeof module != "undefined" && module !== null && module.exports) module.exports = m;
else if (typeof define == "function" && define.amd) define(function() {return m});