web/data/cubicweb.sortable.js
changeset 0 b97547f5f1fa
equal deleted inserted replaced
-1:000000000000 0:b97547f5f1fa
       
     1 /* Adapted from MochiKit's example to use custom cubicweb attribute
       
     2    and a stable sort (merge sort) instead of default's js array sort
       
     3 
       
     4 merge sort JS implementation was found here :
       
     5 http://en.literateprograms.org/Merge_sort_(JavaScript)
       
     6 
       
     7 
       
     8 On page load, the SortableManager:
       
     9 
       
    10 - Finds the table by its id (sortable_table).
       
    11 - Parses its thead for columns with a "mochi:format" attribute.
       
    12 - Parses the data out of the tbody based upon information given in the
       
    13  "cubicweb:sorvalue" attribute, and clones the tr elements for later re-use.
       
    14 - Clones the column header th elements for use as a template when drawing
       
    15  sort arrow columns.
       
    16 - Stores away a reference to the tbody, as it will be replaced on each sort.
       
    17 
       
    18 On sort request:
       
    19 
       
    20 - Sorts the data based on the given key and direction
       
    21 - Creates a new tbody from the rows in the new ordering
       
    22 - Replaces the column header th elements with clickable versions, adding an
       
    23  indicator (↑ or ↓) to the most recently sorted column.
       
    24 
       
    25 */
       
    26 
       
    27 //************** merge sort implementation ***************//
       
    28 Sortable = {}
       
    29 
       
    30 Sortable.msort = function(array, begin, end, cmpfunc) {
       
    31     var size=end-begin;
       
    32     if(size<2) return;
       
    33     
       
    34     var begin_right=begin+Math.floor(size/2);
       
    35     
       
    36     Sortable.msort(array, begin, begin_right, cmpfunc);
       
    37     Sortable.msort(array, begin_right, end, cmpfunc);
       
    38     Sortable.merge(array, begin, begin_right, end, cmpfunc);
       
    39 }
       
    40 
       
    41 Sortable.merge_sort = function(array, cmpfunc) {
       
    42     Sortable.msort(array, 0, array.length, cmpfunc);
       
    43 }
       
    44 
       
    45 Sortable.merge = function(array, begin, begin_right, end, cmpfunc) {
       
    46     for(;begin<begin_right; ++begin) {
       
    47 	// if array[begin] > array[begin_right]
       
    48 	if(cmpfunc(array[begin], array[begin_right]) == 1) {
       
    49 	    var v = array[begin];
       
    50 	    array[begin] = array[begin_right];
       
    51 	    Sortable.insert(array, begin_right, end, v, cmpfunc);
       
    52 	}
       
    53     }
       
    54 }
       
    55 
       
    56 Array.prototype.swap=function(a, b) {
       
    57     var tmp = this[a];
       
    58     this[a] = this[b];
       
    59     this[b] = tmp;
       
    60 }
       
    61 
       
    62 
       
    63 Sortable.insert = function(array, begin, end, v, cmpfunc) {
       
    64     // while(begin+1<end && array[begin+1]<v) {
       
    65     while(begin+1<end && cmpfunc(array[begin+1], v) == -1) {
       
    66 	array.swap(begin, begin+1);
       
    67 	++begin;
       
    68     }
       
    69     array[begin]=v;
       
    70 }
       
    71 
       
    72 //************** auto-sortable tables ***************//
       
    73 
       
    74 Sortable.SortableManager = function () {
       
    75     this.thead = null;
       
    76     this.tbody = null;
       
    77     this.columns = [];
       
    78     this.rows = [];
       
    79     this.sortState = {};
       
    80     this.sortkey = 0;
       
    81 };
       
    82 
       
    83 mouseOverFunc = function () {
       
    84     addElementClass(this, "over");
       
    85 };
       
    86 
       
    87 mouseOutFunc = function () {
       
    88     removeElementClass(this, "over");
       
    89 };
       
    90 
       
    91 Sortable.ignoreEvent = function (ev) {
       
    92     if (ev && ev.preventDefault) {
       
    93 	ev.preventDefault();
       
    94 	ev.stopPropagation();
       
    95     } else if (typeof(event) != 'undefined') {
       
    96 	event.cancelBubble = false;
       
    97 	event.returnValue = false;
       
    98     }
       
    99 };
       
   100 
       
   101 
       
   102 Sortable.getTableHead = function(table) {
       
   103     var thead = table.getElementsByTagName('thead')[0];
       
   104     if ( !thead ) {
       
   105 	thead = table.getElementsByTagName('tr')[0];
       
   106     }
       
   107     return thead;
       
   108 }
       
   109 
       
   110 Sortable.getTableBody = function(table) {
       
   111     var tbody = table.getElementsByTagName('tbody')[0];
       
   112     if ( !tbody ) {
       
   113 	tobdy = table; // XXX
       
   114     }
       
   115     return tbody;
       
   116 }
       
   117 
       
   118 jQuery.extend(Sortable.SortableManager.prototype, {
       
   119     
       
   120     "initWithTable" : function (table) {
       
   121 	/***  Initialize the SortableManager with a table object  ***/
       
   122 	// Find the thead
       
   123 	this.thead = Sortable.getTableHead(table);
       
   124 	// get the mochi:format key and contents for each column header
       
   125 	var cols = this.thead.getElementsByTagName('th');
       
   126 	for (var i = 0; i < cols.length; i++) {
       
   127 	    var node = cols[i];
       
   128 	    var o = node.childNodes;
       
   129 	    node.onclick = this.onSortClick(i);
       
   130 	    node.onmousedown = Sortable.ignoreEvent;
       
   131 	    node.onmouseover = mouseOverFunc;
       
   132 	    node.onmouseout = mouseOutFunc;
       
   133 	    this.columns.push({
       
   134 		"element": node,
       
   135 		"proto": node.cloneNode(true)
       
   136 	    });
       
   137 	}
       
   138 	// scrape the tbody for data
       
   139 	this.tbody = Sortable.getTableBody(table);
       
   140 	// every row
       
   141 	var rows = this.tbody.getElementsByTagName('tr');
       
   142 	for (var i = 0; i < rows.length; i++) {
       
   143 	    // every cell
       
   144 	    var row = rows[i];
       
   145 	    var cols = row.getElementsByTagName('td');
       
   146 	    var rowData = [];
       
   147 	    for (var j = 0; j < cols.length; j++) {
       
   148 		// scrape the text and build the appropriate object out of it
       
   149 		var cell = cols[j];
       
   150 		rowData.push([evalJSON(cell.getAttribute('cubicweb:sortvalue'))]);
       
   151 	    }
       
   152 	    // stow away a reference to the TR and save it
       
   153 	    rowData.row = row.cloneNode(true);
       
   154 	    this.rows.push(rowData);
       
   155 	}
       
   156 	// do initial sort on first column
       
   157 	// this.drawSortedRows(null, true, false);
       
   158 
       
   159     },
       
   160 
       
   161     "onSortClick" : function (name) {
       
   162 	/*** Return a sort function for click events  ***/
       
   163 	return method(this, function () {
       
   164 	    var order = this.sortState[name];
       
   165 	    if (order == null) {
       
   166 		order = true;
       
   167 	    } else if (name == this.sortkey) {
       
   168 		order = !order;
       
   169 	    }
       
   170 	    this.drawSortedRows(name, order, true);
       
   171 	});
       
   172     },
       
   173     
       
   174     "drawSortedRows" : function (key, forward, clicked) {
       
   175 	/***  Draw the new sorted table body, and modify the column headers
       
   176               if appropriate
       
   177          ***/
       
   178 	this.sortkey = key;
       
   179 	// sort based on the state given (forward or reverse)
       
   180 	var cmp = (forward ? keyComparator : reverseKeyComparator);
       
   181 	Sortable.merge_sort(this.rows, cmp(key));
       
   182 	
       
   183 	// save it so we can flip next time
       
   184 	this.sortState[key] = forward;
       
   185 	// get every "row" element from this.rows and make a new tbody
       
   186 	var newRows = [];
       
   187 	for (var i=0; i < this.rows.length; i++){
       
   188 	    var row = this.rows[i].row;
       
   189 	    if (i%2) {
       
   190 		removeElementClass(row, 'even');
       
   191 		addElementClass(row, 'odd');
       
   192 	    } else {
       
   193 		removeElementClass(row, 'odd');
       
   194 		addElementClass(row, 'even');
       
   195 	    }
       
   196 	    newRows.push(row);
       
   197 	}
       
   198 	// var newBody = TBODY(null, map(itemgetter("row"), this.rows));
       
   199 	var newBody = TBODY(null, newRows);
       
   200 	// swap in the new tbody
       
   201 	this.tbody = swapDOM(this.tbody, newBody);
       
   202 	for (var i = 0; i < this.columns.length; i++) {
       
   203 	    var col = this.columns[i];
       
   204 	    var node = col.proto.cloneNode(true);
       
   205 	    // remove the existing events to minimize IE leaks
       
   206 	    col.element.onclick = null;
       
   207 	    col.element.onmousedown = null;
       
   208 	    col.element.onmouseover = null;
       
   209 	    col.element.onmouseout = null;
       
   210 	    // set new events for the new node
       
   211 	    node.onclick = this.onSortClick(i);
       
   212 	    node.onmousedown = Sortable.ignoreEvent;
       
   213 	    node.onmouseover = mouseOverFunc;
       
   214 	    node.onmouseout = mouseOutFunc;
       
   215 	    // if this is the sorted column
       
   216 	    if (key == i) {
       
   217 		// \u2193 is down arrow, \u2191 is up arrow
       
   218 		// forward sorts mean the rows get bigger going down
       
   219 		var arrow = (forward ? "\u2193" : "\u2191");
       
   220 		// add the character to the column header
       
   221 		node.appendChild(SPAN(null, arrow));
       
   222 		if (clicked) {
       
   223 		    node.onmouseover();
       
   224 		}
       
   225 	    }
       
   226 
       
   227 	    // swap in the new th
       
   228 	    col.element = swapDOM(col.element, node);
       
   229 	}
       
   230     }
       
   231 });
       
   232 
       
   233 var sortableManagers = [];
       
   234 
       
   235 /*
       
   236  * Find each table under `rootNode` and make them sortable
       
   237  */
       
   238 Sortable.makeTablesSortable = function(rootNode) {
       
   239     var tables = getElementsByTagAndClassName('table', 'listing', rootNode);
       
   240     for(var i=0; i < tables.length; i++) {
       
   241 	var sortableManager = new Sortable.SortableManager();
       
   242 	sortableManager.initWithTable(tables[i]);
       
   243 	sortableManagers.push(sortableManagers);
       
   244     }
       
   245 }
       
   246 
       
   247 jQuery(document).ready(Sortable.makeTablesSortable);
       
   248 
       
   249 CubicWeb.provide('sortable.js');