/** * Footable Memory * * Version 1.1.0 * * Requires browser support for localStorage. Fallback to cookies using * jQuery Cookie (https://github.com/carhartl/jquery-cookie) * * Stores table state in a cookie and reloads state when page is refreshed. * * Supports common FooTable features: * - Pagination * - Sorting * - Filtering * - Expansion * * Written to be compatible with multiple FooTables per page and with * JavaScript libraries like AngularJS and Ember that use hash based URLs. * * Disabled by default, to enable add the following section to the footable * options: * * $('#table').footable({ * memory: { * enabled: true * } * }); * * Based on FooTable Plugin Bookmarkable by Amy Farrell (https://github.com/akf) * * Created by Chris Laskey (https://github.com/chrislaskey) */ (function ($, w, undefined) { if (w.footable === undefined || w.foobox === null) { throw new Error('Please check and make sure footable.js is included in the page and is loaded prior to this script.'); } var defaults = { memory: { enabled: false } }; var storage; var storage_engines = {}; storage_engines.local_storage = (function($){ 'use strict'; var path_page = function(){ return location.pathname; }; var path_subpage = function(){ return location.hash || 'root'; }; var storage_key = function(index){ return path_page() + '/' + path_subpage() + '/index-' + index; }; var get = function(index){ var key = storage_key(index), as_string = localStorage.getItem(key); return as_string ? JSON.parse(as_string) : {}; }; var set = function(index, item){ var key = storage_key(index), as_string = JSON.stringify(item); localStorage.setItem(key, as_string); }; return { get: function(index){ return get(index); }, set: function(index, item){ set(index, item); } }; })($); storage_engines.cookie = (function($){ 'use strict'; /** * Stores footable bookmarkable data in a cookie * * By default will store each page in its own cookie. * Supports multiple FooTables per page. * Supports JS frameworks that use hashmap URLs (AngularJS, Ember, etc). * * For example take an example application: * * http://example.com/application-data (2 FooTables on this page) * http://example.com/application-data/#/details (1 FooTable on this page) * http://example.com/other-data (1 FooTable on this page) * * Would be stored like this: * * cookie['/application-data'] = { * '/': { * 1: { * key1: value1, * key2: value2 * }, * 2: { * key1: value1, * key2: value2 * } * }, * '#/details': { * 1: { * key1: value1, * key2: value2 * } * } * }; * * cookie['/other-data'] = { * '/': { * 1: { * key1: value1, * key2: value2 * }, * } * } * */ if( $.cookie ){ $.cookie.json = true; } var days_to_keep_data = 7; var path_page = function(){ return location.pathname; }; var path_subpage = function(){ return location.hash || '/'; }; var get_data = function(){ var page = path_page(), data = $.cookie(page); return data || {}; }; var get_table = function(index){ var subpage = path_subpage(), data = get_data(); if( data[subpage] && data[subpage][index] ){ return data[subpage][index]; } else { return {}; } }; var set = function(index, item){ var page = path_page(), subpage = path_subpage(), data = get_data(), options; if( !data[subpage] ){ data[subpage] = {}; } data[subpage][index] = item; options = { path: page, expires: days_to_keep_data }; $.cookie(page, data, options); }; return { get: function(index){ return get_table(index); }, set: function(index, item){ set(index, item); } }; })($); var set_storage_engine = (function(){ var test = 'footable-memory-plugin-storage-test'; try { localStorage.setItem(test, test); localStorage.removeItem(test); storage = storage_engines.local_storage; } catch(e) { try { $.cookie(test, test); storage = storage_engines.cookie; } catch(e) { throw new Error('FooTable Memory requires either localStorage or cookie support via jQuery $.cookie plugin (https://github.com/carhartl/jquery-cookie)'); } } })($); var state = (function($){ 'use strict'; /** * Gets and sets current table state */ var vars = {}; var get = {}; var set = {}; set.vars = function(ft){ vars.ft = ft; vars.table = $(ft.table); }; get.descending = function(){ var descending = false; $.each(vars.table.find('th'), function(index){ if( $(this).hasClass('footable-sorted-desc') ){ descending = true; } }); return descending; }; get.expanded = function(){ var indexes = []; $.each(vars.ft.table.rows, function(index, value){ if( $(this).hasClass('footable-detail-show') ){ indexes.push(index); } }); return indexes; }; set.expanded = function(data){ if( data.expanded ){ $.each(data.expanded, function(index, value){ // var row = $(vars.ft.table.rows[value]); // row.find('> td:first').trigger('footable_toggle_row'); // Trying to execute the lines above, but the expanded row // shows raw AngularJS template (with {{ values }}) instead // of the fully rendered result. // // Best guess is some things happen after // 'footable_initialized' event and row expanding can not // occur until after those fire. // // A hack to get around this is to wait an interval before // executing the intended commands. Wrapped in an // immediately executing function to ensure ft is the // current value. (function(ft){ setTimeout(function(){ var row = $(ft.table.rows[value]); row.find('> td:first').trigger('footable_toggle_row'); }, 150); })(vars.ft); }); } }; get.filter = function(){ return vars.table.data('filter') ? $(vars.table.data('filter')).val() : ''; }; set.filter = function(data){ if( data.filter ){ $(vars.table.data('filter')) .val(data.filter) .trigger('keyup'); } }; get.page = function(){ return vars.ft.pageInfo && vars.ft.pageInfo.currentPage !== undefined ? vars.ft.pageInfo.currentPage : 0; }; set.page = function(data){ if( data.page ){ vars.table.data('currentPage', data.page); // Delay triggering table until sort is updated, since both effect // pagination. } }; get.shown = function(){ return vars.table .find('tbody') .find('tr:not(.footable-row-detail)') .filter(':visible').length; }; get.sorted = function(){ if( vars.table.data('sorted') !== undefined ){ return vars.table.data('sorted'); } else { return -1; } }; set.sorted = function(data){ if( data.sorted >= 0 ) { // vars.table.data('footable-sort').doSort(data.sorted, !data.descending); // Trying to execute the line above, but only sort icon on the // <th> element gets set. The rows themselves do not get sorted. // // Best guess is some things happen after 'footable_initialized' event // and sorting can not occur until after those fire. // // A hack to get around this is to wait an interval before executing // the intended commands. Wrapped in an immediately executing // function to ensure ft is the current value. (function(ft){ setTimeout(function(){ $(ft.table).data('footable-sort').doSort(data.sorted, !data.descending); }, 150); })(vars.ft); } else { vars.table.trigger('footable_setup_paging'); } }; get.total = function(){ return vars.table .find('tbody') .find('tr:not(.footable-row-detail, .footable-filtered)').length; }; var get_state = function(){ return { descending: get.descending(), expanded: get.expanded(), filter: get.filter(), page: get.page(), shown: get.shown(), sorted: get.sorted(), total: get.total() }; }; var set_state = function(data){ set.filter(data); set.page(data); set.sorted(data); set.expanded(data); }; return { get: function(ft){ set.vars(ft); return get_state(); }, set: function(ft, data){ set.vars(ft); return set_state(data); } }; })($); var is_enabled = function(ft){ return ft.options.memory.enabled; }; var update = function(ft, event) { var index = ft.id, data = state.get(ft); storage.set(index, data); }; var load = function(ft){ var index = ft.id, data = storage.get(index); state.set(ft, data); ft.memory_plugin_loaded = true; }; function Memory() { var p = this; p.name = 'Footable Memory'; p.init = function(ft) { if (is_enabled(ft)) { $(ft.table).bind({ 'footable_initialized': function(){ load(ft); }, 'footable_page_filled footable_redrawn footable_filtered footable_sorted footable_row_expanded footable_row_collapsed': function(e) { if (ft.memory_plugin_loaded) { update(ft, e); } } }); } }; } w.footable.plugins.register(Memory, defaults); })(jQuery, window);