<!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});