<!DOCTYPE html>
<html>
<head>
<link data-require="bootstrap-css@3.0.2" data-semver="3.0.2" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css" />
<link data-require="jqueryui@*" data-semver="1.10.0" rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/css/smoothness/jquery-ui-1.10.0.custom.min.css" />
<link rel="stylesheet" href="footable.core.css" />
<script data-require="jquery@*" data-semver="2.0.3" src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script data-require="jqueryui@*" data-semver="1.10.0" src="//cdnjs.cloudflare.com/ajax/libs/jqueryui/1.10.0/jquery-ui.js"></script>
<script data-require="bootstrap@*" data-semver="3.0.2" src="//netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js"></script>
<script src="footable.js"></script>
<script src="footable.paginate.js"></script>
<script src="footable.sort.js"></script>
<script src="footable.striping.js"></script>
<script data-require="knockout@*" data-semver="3.0.0" src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.0.0/knockout-debug.js"></script>
<script src="knockout-delegatedEvents.js"></script>
<script src="showModalBinding.js"></script>
<script src="footableBinding.js"></script>
<script src="tasks.js"></script>
</head>
<body>
<div id="tasks" class="panel panel-default">
<div class="panel-heading">
<span class="panel-title">
<i class="pad icon-gear"></i>
Tasks</span>
</div>
<div class="panel-body panel-trim"></div>
<div class="panel-body no-padding">
<div data-bind="template: { name: 'taskTable'}"></div>
<div data-bind="template: { name: 'taskModal'}"></div>
</div>
<div class="panel-footer"></div>
</div>
<script type="text/html" id="taskTable">
<table class="table table-striped footable toggle-circle" data-page-navigation=".paginate" data-page-size="3">
<thead>
<tr>
<th colspan="5">
<a data-bind="click: addItem" href="#">
<span class="btn btn-primary btn-xs pull-left">
<i class="icon-plus">Add Task</i>
</span>
</a>
</th>
</tr>
<tr>
<th>Desc</th>
<th data-hide="phone">Due On</th>
<th data-sort-ignore="true"></th>
</tr>
</thead>
<tbody data-bind="footable: tasks, delegatedHandler: 'click'">
<tr>
<td data-click="selectItem" data-bind="text: description"></td>
<td data-click="selectItem" data-bind="text: dueOn"></td>
<td class="text-center">
<div>
<a href="#" data-click="removeItem">
<span class="btn btn-danger btn-xs pull-right">
<i class="icon-remove">Remove Task</i>
</span>
</a>
</div>
</td>
</tr>
</tbody>
<tfoot>
<tr class="hide-if-no-paging">
<td colspan="100" class="text-center">
<div class="paginate">
<ul class="pagination"></ul>
</div>
</td>
</tr>
</tfoot>
</table>
</script>
<script type="text/html" id="taskModal">
<div class="modal fade" data-backdrop="static" data-bind="showModal: itemForEditing">
<div class="modal-dialog" data-bind="with: itemForEditing">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 data-bind=""></h3>
</div>
<div class="modal-body">
<p>
<label>Description: </label>
<input data-bind="value: description" />
</p>
<p>
<label>Due On: </label>
<input data-bind="value: dueOn" />
</p>
<p>
<div data-bind="visible: showTasks">
<label>Sub Tasks: </label>
<div data-bind="template: { name: 'taskTable', data: tasksObj}"></div>
<div data-bind="template: { name: 'taskModal', data: tasksObj}"></div>
</div>
</p>
</div>
<div class="modal-footer">
<a href="#" class="btn" data-bind="click: $parent.acceptEdit">Ok</a>
<a href="#" class="btn" data-bind="click: $parent.applyEdit">Apply</a>
<a href="#" class="btn" data-bind="click: $parent.cancelEdit">Cancel</a>
</div>
</div>
</div>
</div>
</script>
<script>
$(document).ready(function() {
var allData = [{"id":"70000000-0000-0000-0000-00000000901","description":"1","tasks":[{"id":"70000000-0000-0000-0000-00000000802","description":"2","tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"tasksObj":{"tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"parentId":"70000000-0000-0000-0000-00000000802"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000901"}],"tasksObj":{"tasks":[{"id":"70000000-0000-0000-0000-00000000802","description":"2","tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"tasksObj":{"tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"parentId":"70000000-0000-0000-0000-00000000802"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000901"}],"parentId":"70000000-0000-0000-0000-00000000901"},"showTasks":true,"parentTaskId":""},{"id":"70000000-0000-0000-0000-00000000902","description":"2","tasks":[],"tasksObj":{"tasks":[],"parentId":"70000000-0000-0000-0000-00000000902"},"showTasks":true,"parentTaskId":""},{"id":"70000000-0000-0000-0000-00000000903","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"70000000-0000-0000-0000-00000000903"},"showTasks":true,"parentTaskId":""}];
ko.applyBindings(new Tasks(allData),document.getElementById("tasks"));
});
</script>
</body>
</html>
@font-face {
font-family: 'footable';
src: url('fonts/footable.eot');
src: url('fonts/footable.eot?#iefix') format('embedded-opentype'), url('fonts/footable.woff') format('woff'), url('fonts/footable.ttf') format('truetype'), url('fonts/footable.svg#footable') format('svg');
font-weight: normal;
font-style: normal;
}
@media screen and (-webkit-min-device-pixel-ratio: 0) {
@font-face {
font-family: 'footable';
src: url('fonts/footable.svg#footable') format('svg');
font-weight: normal;
font-style: normal;
}
}
.footable {
width: 100%;
/** SORTING **/
/** PAGINATION **/
}
.footable.breakpoint > tbody > tr.footable-detail-show > td {
border-bottom: none;
}
.footable.breakpoint > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e001";
}
.footable.breakpoint > tbody > tr:hover:not(.footable-row-detail) {
cursor: pointer;
}
.footable.breakpoint > tbody > tr > td.footable-cell-detail {
background: #eee;
border-top: none;
}
.footable.breakpoint > tbody > tr > td > span.footable-toggle {
display: inline-block;
font-family: 'footable';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
-webkit-font-smoothing: antialiased;
padding-right: 5px;
font-size: 14px;
color: #888888;
}
.footable.breakpoint > tbody > tr > td > span.footable-toggle:before {
content: "\e000";
}
.footable.breakpoint.toggle-circle > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e005";
}
.footable.breakpoint.toggle-circle > tbody > tr > td > span.footable-toggle:before {
content: "\e004";
}
.footable.breakpoint.toggle-circle-filled > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e003";
}
.footable.breakpoint.toggle-circle-filled > tbody > tr > td > span.footable-toggle:before {
content: "\e002";
}
.footable.breakpoint.toggle-square > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e007";
}
.footable.breakpoint.toggle-square > tbody > tr > td > span.footable-toggle:before {
content: "\e006";
}
.footable.breakpoint.toggle-square-filled > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e009";
}
.footable.breakpoint.toggle-square-filled > tbody > tr > td > span.footable-toggle:before {
content: "\e008";
}
.footable.breakpoint.toggle-arrow > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e00f";
}
.footable.breakpoint.toggle-arrow > tbody > tr > td > span.footable-toggle:before {
content: "\e011";
}
.footable.breakpoint.toggle-arrow-small > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e013";
}
.footable.breakpoint.toggle-arrow-small > tbody > tr > td > span.footable-toggle:before {
content: "\e015";
}
.footable.breakpoint.toggle-arrow-circle > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e01b";
}
.footable.breakpoint.toggle-arrow-circle > tbody > tr > td > span.footable-toggle:before {
content: "\e01d";
}
.footable.breakpoint.toggle-arrow-circle-filled > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e00b";
}
.footable.breakpoint.toggle-arrow-circle-filled > tbody > tr > td > span.footable-toggle:before {
content: "\e00d";
}
.footable.breakpoint.toggle-arrow-tiny > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e01f";
}
.footable.breakpoint.toggle-arrow-tiny > tbody > tr > td > span.footable-toggle:before {
content: "\e021";
}
.footable.breakpoint.toggle-arrow-alt > tbody > tr.footable-detail-show > td > span.footable-toggle:before {
content: "\e017";
}
.footable.breakpoint.toggle-arrow-alt > tbody > tr > td > span.footable-toggle:before {
content: "\e019";
}
.footable.breakpoint.toggle-medium > tbody > tr > td > span.footable-toggle {
font-size: 18px;
}
.footable.breakpoint.toggle-large > tbody > tr > td > span.footable-toggle {
font-size: 24px;
}
.footable > thead > tr > th {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
.footable > thead > tr > th.footable-sortable:hover {
cursor: pointer;
}
.footable > thead > tr > th.footable-sorted > span.footable-sort-indicator:before {
content: "\e013";
}
.footable > thead > tr > th.footable-sorted-desc > span.footable-sort-indicator:before {
content: "\e012";
}
.footable > thead > tr > th > span.footable-sort-indicator {
display: inline-block;
font-family: 'footable';
speak: none;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
-webkit-font-smoothing: antialiased;
padding-left: 5px;
}
.footable > thead > tr > th > span.footable-sort-indicator:before {
content: "\e022";
}
.footable > tfoot .pagination {
margin: 0;
}
.footable.no-paging .hide-if-no-paging {
display: none;
}
.footable-row-detail-inner {
display: table;
}
.footable-row-detail-row {
display: table-row;
line-height: 1.5em;
}
.footable-row-detail-group {
display: block;
line-height: 2em;
font-size: 1.2em;
font-weight: bold;
}
.footable-row-detail-name {
display: table-cell;
font-weight: bold;
padding-right: 0.5em;
}
.footable-row-detail-value {
display: table-cell;
}
.footable-odd {
background-color: #f7f7f7;
}
/*!
* FooTable - Awesome Responsive Tables
* Version : 2.0.1.2
* http://fooplugins.com/plugins/footable-jquery/
*
* Requires jQuery - http://jquery.com/
*
* Copyright 2013 Steven Usher & Brad Vincent
* Released under the MIT license
* You are free to use FooTable in commercial projects as long as this copyright header is left intact.
*
* Date: 21 Sep 2013
*/
(function ($, w, undefined) {
w.footable = {
options: {
delay: 100, // The number of millseconds to wait before triggering the react event
breakpoints: { // The different screen resolution breakpoints
phone: 480,
tablet: 1024
},
parsers: { // The default parser to parse the value out of a cell (values are used in building up row detail)
alpha: function (cell) {
return $(cell).data('value') || $.trim($(cell).text());
},
numeric: function (cell) {
var val = $(cell).data('value') || $(cell).text().replace(/[^0-9.\-]/g, '');
val = parseFloat(val);
if (isNaN(val)) val = 0;
return val;
}
},
addRowToggle: true,
calculateWidthOverride: null,
toggleSelector: ' > tbody > tr:not(.footable-row-detail)', //the selector to show/hide the detail row
columnDataSelector: '> thead > tr:last-child > th, > thead > tr:last-child > td', //the selector used to find the column data in the thead
detailSeparator: ':', //the separator character used when building up the detail row
toggleHTMLElement: '<span />', // override this if you want to insert a click target rather than use a background image.
createGroupedDetail: function (data) {
var groups = { '_none': { 'name': null, 'data': [] } };
for (var i = 0; i < data.length; i++) {
var groupid = data[i].group;
if (groupid !== null) {
if (!(groupid in groups))
groups[groupid] = { 'name': data[i].groupName || data[i].group, 'data': [] };
groups[groupid].data.push(data[i]);
} else {
groups._none.data.push(data[i]);
}
}
return groups;
},
createDetail: function (element, data, createGroupedDetail, separatorChar, classes) {
/// <summary>This function is used by FooTable to generate the detail view seen when expanding a collapsed row.</summary>
/// <param name="element">This is the div that contains all the detail row information, anything could be added to it.</param>
/// <param name="data">
/// This is an array of objects containing the cell information for the current row.
/// These objects look like the below:
/// obj = {
/// 'name': String, // The name of the column
/// 'value': Object, // The value parsed from the cell using the parsers. This could be a string, a number or whatever the parser outputs.
/// 'display': String, // This is the actual HTML from the cell, so if you have images etc you want moved this is the one to use and is the default value used.
/// 'group': String, // This is the identifier used in the data-group attribute of the column.
/// 'groupName': String // This is the actual name of the group the column belongs to.
/// }
/// </param>
/// <param name="createGroupedDetail">The grouping function to group the data</param>
/// <param name="separatorChar">The separator charactor used</param>
/// <param name="classes">The array of class names used to build up the detail row</param>
var groups = createGroupedDetail(data);
for (var group in groups) {
if (groups[group].data.length === 0) continue;
if (group !== '_none') element.append('<div class="' + classes.detailInnerGroup + '">' + groups[group].name + '</div>');
for (var j = 0; j < groups[group].data.length; j++) {
var separator = (groups[group].data[j].name) ? separatorChar : '';
element.append('<div class="' + classes.detailInnerRow + '"><div class="' + classes.detailInnerName + '">' + groups[group].data[j].name + separator + '</div><div class="' + classes.detailInnerValue + '">' + groups[group].data[j].display + '</div></div>');
}
}
},
classes: {
main: 'footable',
loading: 'footable-loading',
loaded: 'footable-loaded',
toggle: 'footable-toggle',
disabled: 'footable-disabled',
detail: 'footable-row-detail',
detailCell: 'footable-row-detail-cell',
detailInner: 'footable-row-detail-inner',
detailInnerRow: 'footable-row-detail-row',
detailInnerGroup: 'footable-row-detail-group',
detailInnerName: 'footable-row-detail-name',
detailInnerValue: 'footable-row-detail-value',
detailShow: 'footable-detail-show'
},
triggers: {
initialize: 'footable_initialize', //trigger this event to force FooTable to reinitialize
resize: 'footable_resize', //trigger this event to force FooTable to resize
redraw: 'footable_redraw', //trigger this event to force FooTable to redraw
toggleRow: 'footable_toggle_row', //trigger this event to force FooTable to toggle a row
expandFirstRow: 'footable_expand_first_row', //trigger this event to force FooTable to expand the first row
expandAll: 'footable_expand_all', //trigger this event to force FooTable to expand all rows
collapseAll: 'footable_collapse_all' //trigger this event to force FooTable to collapse all rows
},
events: {
alreadyInitialized: 'footable_already_initialized', //fires when the FooTable has already been initialized
initializing: 'footable_initializing', //fires before FooTable starts initializing
initialized: 'footable_initialized', //fires after FooTable has finished initializing
resizing: 'footable_resizing', //fires before FooTable resizes
resized: 'footable_resized', //fires after FooTable has resized
redrawn: 'footable_redrawn', //fires after FooTable has redrawn
breakpoint: 'footable_breakpoint', //fires inside the resize function, when a breakpoint is hit
columnData: 'footable_column_data', //fires when setting up column data. Plugins should use this event to capture their own info about a column
rowDetailUpdating: 'footable_row_detail_updating', //fires before a detail row is updated
rowDetailUpdated: 'footable_row_detail_updated', //fires when a detail row is being updated
rowCollapsed: 'footable_row_collapsed', //fires when a row is collapsed
rowExpanded: 'footable_row_expanded', //fires when a row is expanded
rowRemoved: 'footable_row_removed', //fires when a row is removed
reset: 'footable_reset' //fires when FooTable is reset
},
debug: false, // Whether or not to log information to the console.
log: null
},
version: {
major: 0, minor: 5,
toString: function () {
return w.footable.version.major + '.' + w.footable.version.minor;
},
parse: function (str) {
version = /(\d+)\.?(\d+)?\.?(\d+)?/.exec(str);
return {
major: parseInt(version[1], 10) || 0,
minor: parseInt(version[2], 10) || 0,
patch: parseInt(version[3], 10) || 0
};
}
},
plugins: {
_validate: function (plugin) {
///<summary>Simple validation of the <paramref name="plugin"/> to make sure any members called by FooTable actually exist.</summary>
///<param name="plugin">The object defining the plugin, this should implement a string property called "name" and a function called "init".</param>
if (!$.isFunction(plugin)) {
if (w.footable.options.debug === true) console.error('Validation failed, expected type "function", received type "{0}".', typeof plugin);
return false;
}
var p = new plugin();
if (typeof p['name'] !== 'string') {
if (w.footable.options.debug === true) console.error('Validation failed, plugin does not implement a string property called "name".', p);
return false;
}
if (!$.isFunction(p['init'])) {
if (w.footable.options.debug === true) console.error('Validation failed, plugin "' + p['name'] + '" does not implement a function called "init".', p);
return false;
}
if (w.footable.options.debug === true) console.log('Validation succeeded for plugin "' + p['name'] + '".', p);
return true;
},
registered: [], // An array containing all registered plugins.
register: function (plugin, options) {
///<summary>Registers a <paramref name="plugin"/> and its default <paramref name="options"/> with FooTable.</summary>
///<param name="plugin">The plugin that should implement a string property called "name" and a function called "init".</param>
///<param name="options">The default options to merge with the FooTable's base options.</param>
if (w.footable.plugins._validate(plugin)) {
w.footable.plugins.registered.push(plugin);
if (typeof options === 'object') $.extend(true, w.footable.options, options);
}
},
load: function(instance){
var loaded = [], registered, i;
for(i = 0; i < w.footable.plugins.registered.length; i++){
try {
registered = w.footable.plugins.registered[i];
loaded.push(new registered(instance));
} catch (err) {
if (w.footable.options.debug === true) console.error(err);
}
}
return loaded;
},
init: function (instance) {
///<summary>Loops through all registered plugins and calls the "init" method supplying the current <paramref name="instance"/> of the FooTable as the first parameter.</summary>
///<param name="instance">The current instance of the FooTable that the plugin is being initialized for.</param>
for (var i = 0; i < instance.plugins.length; i++) {
try {
instance.plugins[i]['init'](instance);
} catch (err) {
if (w.footable.options.debug === true) console.error(err);
}
}
}
}
};
var instanceCount = 0;
$.fn.footable = function (options) {
///<summary>The main constructor call to initialize the plugin using the supplied <paramref name="options"/>.</summary>
///<param name="options">
///<para>A JSON object containing user defined options for the plugin to use. Any options not supplied will have a default value assigned.</para>
///<para>Check the documentation or the default options object above for more information on available options.</para>
///</param>
options = options || {};
var o = $.extend(true, {}, w.footable.options, options); //merge user and default options
return this.each(function () {
instanceCount++;
var footable = new Footable(this, o, instanceCount);
$(this).data('footable', footable);
});
};
//helper for using timeouts
function Timer() {
///<summary>Simple timer object created around a timeout.</summary>
var t = this;
t.id = null;
t.busy = false;
t.start = function (code, milliseconds) {
///<summary>Starts the timer and waits the specified amount of <paramref name="milliseconds"/> before executing the supplied <paramref name="code"/>.</summary>
///<param name="code">The code to execute once the timer runs out.</param>
///<param name="milliseconds">The time in milliseconds to wait before executing the supplied <paramref name="code"/>.</param>
if (t.busy) {
return;
}
t.stop();
t.id = setTimeout(function () {
code();
t.id = null;
t.busy = false;
}, milliseconds);
t.busy = true;
};
t.stop = function () {
///<summary>Stops the timer if its runnning and resets it back to its starting state.</summary>
if (t.id !== null) {
clearTimeout(t.id);
t.id = null;
t.busy = false;
}
};
}
function Footable(t, o, id) {
///<summary>Inits a new instance of the plugin.</summary>
///<param name="t">The main table element to apply this plugin to.</param>
///<param name="o">The options supplied to the plugin. Check the defaults object to see all available options.</param>
///<param name="id">The id to assign to this instance of the plugin.</param>
var ft = this;
ft.id = id;
ft.table = t;
ft.options = o;
ft.breakpoints = [];
ft.breakpointNames = '';
ft.columns = {};
ft.plugins = w.footable.plugins.load(ft);
var opt = ft.options,
cls = opt.classes,
evt = opt.events,
trg = opt.triggers,
indexOffset = 0;
// This object simply houses all the timers used in the FooTable.
ft.timers = {
resize: new Timer(),
register: function (name) {
ft.timers[name] = new Timer();
return ft.timers[name];
}
};
ft.init = function () {
var $window = $(w), $table = $(ft.table);
w.footable.plugins.init(ft);
if ($table.hasClass(cls.loaded)) {
//already loaded FooTable for the table, so don't init again
ft.raise(evt.alreadyInitialized);
return;
}
//raise the initializing event
ft.raise(evt.initializing);
$table.addClass(cls.loading);
// Get the column data once for the life time of the plugin
$table.find(opt.columnDataSelector).each(function () {
var data = ft.getColumnData(this);
ft.columns[data.index] = data;
});
// Create a nice friendly array to work with out of the breakpoints object.
for (var name in opt.breakpoints) {
ft.breakpoints.push({ 'name': name, 'width': opt.breakpoints[name] });
ft.breakpointNames += (name + ' ');
}
// Sort the breakpoints so the smallest is checked first
ft.breakpoints.sort(function (a, b) {
return a['width'] - b['width'];
});
$table
.unbind(trg.initialize)
//bind to FooTable initialize trigger
.bind(trg.initialize, function () {
//remove previous "state" (to "force" a resize)
$table.removeData('footable_info');
$table.data('breakpoint', '');
//trigger the FooTable resize
$table.trigger(trg.resize);
//remove the loading class
$table.removeClass(cls.loading);
//add the FooTable and loaded class
$table.addClass(cls.loaded).addClass(cls.main);
//raise the initialized event
ft.raise(evt.initialized);
})
.unbind(trg.redraw)
//bind to FooTable redraw trigger
.bind(trg.redraw, function () {
ft.redraw();
})
.unbind(trg.resize)
//bind to FooTable resize trigger
.bind(trg.resize, function () {
ft.resize();
})
.unbind(trg.expandFirstRow)
//bind to FooTable expandFirstRow trigger
.bind(trg.expandFirstRow, function () {
$table.find(opt.toggleSelector).first().not('.' + cls.detailShow).trigger(trg.toggleRow);
})
.unbind(trg.expandAll)
//bind to FooTable expandFirstRow trigger
.bind(trg.expandAll, function () {
$table.find(opt.toggleSelector).not('.' + cls.detailShow).trigger(trg.toggleRow);
})
.unbind(trg.collapseAll)
//bind to FooTable expandFirstRow trigger
.bind(trg.collapseAll, function () {
$table.find('.' + cls.detailShow).trigger(trg.toggleRow);
});
//trigger a FooTable initialize
$table.trigger(trg.initialize);
//bind to window resize
$window
.bind('resize.footable', function () {
ft.timers.resize.stop();
ft.timers.resize.start(function () {
ft.raise(trg.resize);
}, opt.delay);
});
};
ft.addRowToggle = function () {
if (!opt.addRowToggle) return;
var $table = $(ft.table),
hasToggleColumn = false;
//first remove all toggle spans
$table.find('span.' + cls.toggle).remove();
for (var c in ft.columns) {
var col = ft.columns[c];
if (col.toggle) {
hasToggleColumn = true;
var selector = '> tbody > tr:not(.' + cls.detail + ',.' + cls.disabled + ') > td:nth-child(' + (parseInt(col.index, 10) + 1) + ')';
$table.find(selector).not('.' + cls.detailCell).prepend($(opt.toggleHTMLElement).addClass(cls.toggle));
return;
}
}
//check if we have an toggle column. If not then add it to the first column just to be safe
if (!hasToggleColumn) {
$table
.find('> tbody > tr:not(.' + cls.detail + ',.' + cls.disabled + ') > td:first-child')
.not('.' + cls.detailCell)
.prepend($(opt.toggleHTMLElement).addClass(cls.toggle));
}
};
ft.setColumnClasses = function () {
$table = $(ft.table);
for (var c in ft.columns) {
var col = ft.columns[c];
if (col.className !== null) {
var selector = '', first = true;
$.each(col.matches, function (m, match) { //support for colspans
if (!first) selector += ', ';
selector += '> tbody > tr:not(.' + cls.detail + ') > td:nth-child(' + (parseInt(match, 10) + 1) + ')';
first = false;
});
//add the className to the cells specified by data-class="blah"
$table.find(selector).not('.' + cls.detailCell).addClass(col.className);
}
}
};
//moved this out into it's own function so that it can be called from other add-ons
ft.bindToggleSelectors = function () {
var $table = $(ft.table);
if (!ft.hasAnyBreakpointColumn()) return;
$table.find(opt.toggleSelector).unbind(trg.toggleRow).bind(trg.toggleRow, function (e) {
var $row = $(this).is('tr') ? $(this) : $(this).parents('tr:first');
ft.toggleDetail($row);
});
$table.find(opt.toggleSelector).unbind('click.footable').bind('click.footable', function (e) {
if ($table.is('.breakpoint') && $(e.target).is('td,.'+ cls.toggle)) {
$(this).trigger(trg.toggleRow);
}
});
};
ft.parse = function (cell, column) {
var parser = opt.parsers[column.type] || opt.parsers.alpha;
return parser(cell);
};
ft.getColumnData = function (th) {
var $th = $(th), hide = $th.data('hide'), index = $th.index();
hide = hide || '';
hide = jQuery.map(hide.split(','), function (a) {
return jQuery.trim(a);
});
var data = {
'index': index,
'hide': { },
'type': $th.data('type') || 'alpha',
'name': $th.data('name') || $.trim($th.text()),
'ignore': $th.data('ignore') || false,
'toggle': $th.data('toggle') || false,
'className': $th.data('class') || null,
'matches': [],
'names': { },
'group': $th.data('group') || null,
'groupName': null
};
if (data.group !== null) {
var $group = $(ft.table).find('> thead > tr.footable-group-row > th[data-group="' + data.group + '"], > thead > tr.footable-group-row > td[data-group="' + data.group + '"]').first();
data.groupName = ft.parse($group, { 'type': 'alpha' });
}
var pcolspan = parseInt($th.prev().attr('colspan') || 0, 10);
indexOffset += pcolspan > 1 ? pcolspan - 1 : 0;
var colspan = parseInt($th.attr('colspan') || 0, 10), curindex = data.index + indexOffset;
if (colspan > 1) {
var names = $th.data('names');
names = names || '';
names = names.split(',');
for (var i = 0; i < colspan; i++) {
data.matches.push(i + curindex);
if (i < names.length) data.names[i + curindex] = names[i];
}
} else {
data.matches.push(curindex);
}
data.hide['default'] = ($th.data('hide') === "all") || ($.inArray('default', hide) >= 0);
var hasBreakpoint = false;
for (var name in opt.breakpoints) {
data.hide[name] = ($th.data('hide') === "all") || ($.inArray(name, hide) >= 0);
hasBreakpoint = hasBreakpoint || data.hide[name];
}
data.hasBreakpoint = hasBreakpoint;
var e = ft.raise(evt.columnData, { 'column': { 'data': data, 'th': th } });
return e.column.data;
};
ft.getViewportWidth = function () {
return window.innerWidth || (document.body ? document.body.offsetWidth : 0);
};
ft.calculateWidth = function ($table, info) {
if (jQuery.isFunction(opt.calculateWidthOverride)) {
return opt.calculateWidthOverride($table, info);
}
if (info.viewportWidth < info.width) info.width = info.viewportWidth;
if (info.parentWidth < info.width) info.width = info.parentWidth;
return info;
};
ft.hasBreakpointColumn = function (breakpoint) {
for (var c in ft.columns) {
if (ft.columns[c].hide[breakpoint]) {
if (ft.columns[c].ignore) {
continue;
}
return true;
}
}
return false;
};
ft.hasAnyBreakpointColumn = function () {
for (var c in ft.columns) {
if (ft.columns[c].hasBreakpoint) {
return true;
}
}
return false;
};
ft.resize = function () {
var $table = $(ft.table);
if (!$table.is(':visible')) {
return;
} //we only care about FooTables that are visible
if (!ft.hasAnyBreakpointColumn()) {
return;
} //we only care about FooTables that have breakpoints
var info = {
'width': $table.width(), //the table width
'viewportWidth': ft.getViewportWidth(), //the width of the viewport
'parentWidth': $table.parent().width() //the width of the parent
};
info = ft.calculateWidth($table, info);
var pinfo = $table.data('footable_info');
$table.data('footable_info', info);
ft.raise(evt.resizing, { 'old': pinfo, 'info': info });
// This (if) statement is here purely to make sure events aren't raised twice as mobile safari seems to do
if (!pinfo || (pinfo && pinfo.width && pinfo.width !== info.width)) {
var current = null, breakpoint;
for (var i = 0; i < ft.breakpoints.length; i++) {
breakpoint = ft.breakpoints[i];
if (breakpoint && breakpoint.width && info.width <= breakpoint.width) {
current = breakpoint;
break;
}
}
var breakpointName = (current === null ? 'default' : current['name']),
hasBreakpointFired = ft.hasBreakpointColumn(breakpointName),
previousBreakpoint = $table.data('breakpoint');
$table
.data('breakpoint', breakpointName)
.removeClass('default breakpoint').removeClass(ft.breakpointNames)
.addClass(breakpointName + (hasBreakpointFired ? ' breakpoint' : ''));
//only do something if the breakpoint has changed
if (breakpointName !== previousBreakpoint) {
//trigger a redraw
$table.trigger(trg.redraw);
//raise a breakpoint event
ft.raise(evt.breakpoint, { 'breakpoint': breakpointName, 'info': info });
}
}
ft.raise(evt.resized, { 'old': pinfo, 'info': info });
};
ft.redraw = function () {
//add the toggler to each row
ft.addRowToggle();
//bind the toggle selector click events
ft.bindToggleSelectors();
//set any cell classes defined for the columns
ft.setColumnClasses();
var $table = $(ft.table),
breakpointName = $table.data('breakpoint'),
hasBreakpointFired = ft.hasBreakpointColumn(breakpointName);
$table
.find('> tbody > tr:not(.' + cls.detail + ')').data('detail_created', false).end()
.find('> thead > tr:last-child > th')
.each(function () {
var data = ft.columns[$(this).index()], selector = '', first = true;
$.each(data.matches, function (m, match) {
if (!first) {
selector += ', ';
}
var count = match + 1;
selector += '> tbody > tr:not(.' + cls.detail + ') > td:nth-child(' + count + ')';
selector += ', > tfoot > tr:not(.' + cls.detail + ') > td:nth-child(' + count + ')';
selector += ', > colgroup > col:nth-child(' + count + ')';
first = false;
});
selector += ', > thead > tr[data-group-row="true"] > th[data-group="' + data.group + '"]';
var $column = $table.find(selector).add(this);
if (data.hide[breakpointName] === false) $column.show();
else $column.hide();
if ($table.find('> thead > tr.footable-group-row').length === 1) {
var $groupcols = $table.find('> thead > tr:last-child > th[data-group="' + data.group + '"]:visible, > thead > tr:last-child > th[data-group="' + data.group + '"]:visible'),
$group = $table.find('> thead > tr.footable-group-row > th[data-group="' + data.group + '"], > thead > tr.footable-group-row > td[data-group="' + data.group + '"]'),
groupspan = 0;
$.each($groupcols, function () {
groupspan += parseInt($(this).attr('colspan') || 1, 10);
});
if (groupspan > 0) $group.attr('colspan', groupspan).show();
else $group.hide();
}
})
.end()
.find('> tbody > tr.' + cls.detailShow).each(function () {
ft.createOrUpdateDetailRow(this);
});
$table.find('> tbody > tr.' + cls.detailShow + ':visible').each(function () {
var $next = $(this).next();
if ($next.hasClass(cls.detail)) {
if (!hasBreakpointFired) $next.hide();
else $next.show();
}
});
// adding .footable-first-column and .footable-last-column to the first and last th and td of each row in order to allow
// for styling if the first or last column is hidden (which won't work using :first-child or :last-child)
$table.find('> thead > tr > th.footable-last-column, > tbody > tr > td.footable-last-column').removeClass('footable-last-column');
$table.find('> thead > tr > th.footable-first-column, > tbody > tr > td.footable-first-column').removeClass('footable-first-column');
$table.find('> thead > tr, > tbody > tr')
.find('> th:visible:last, > td:visible:last')
.addClass('footable-last-column')
.end()
.find('> th:visible:first, > td:visible:first')
.addClass('footable-first-column');
ft.raise(evt.redrawn);
};
ft.toggleDetail = function (row) {
var $row = (row.jquery) ? row : $(row),
$next = $row.next();
//check if the row is already expanded
if ($row.hasClass(cls.detailShow)) {
$row.removeClass(cls.detailShow);
//only hide the next row if it's a detail row
if ($next.hasClass(cls.detail)) $next.hide();
ft.raise(evt.rowCollapsed, { 'row': $row[0] });
} else {
ft.createOrUpdateDetailRow($row[0]);
$row.addClass(cls.detailShow)
.next().show();
ft.raise(evt.rowExpanded, { 'row': $row[0] });
}
};
ft.removeRow = function (row) {
var $row = (row.jquery) ? row : $(row);
if ($row.hasClass(cls.detail)) {
$row = $row.prev();
}
var $next = $row.next();
if ($row.data('detail_created') === true) {
//remove the detail row
$next.remove();
}
$row.remove();
//raise event
ft.raise(evt.rowRemoved);
};
ft.appendRow = function (row) {
var $row = (row.jquery) ? row : $(row);
$(ft.table).find('tbody').append($row);
//redraw the table
ft.redraw();
};
ft.getColumnFromTdIndex = function (index) {
/// <summary>Returns the correct column data for the supplied index taking into account colspans.</summary>
/// <param name="index">The index to retrieve the column data for.</param>
/// <returns type="json">A JSON object containing the column data for the supplied index.</returns>
var result = null;
for (var column in ft.columns) {
if ($.inArray(index, ft.columns[column].matches) >= 0) {
result = ft.columns[column];
break;
}
}
return result;
};
ft.createOrUpdateDetailRow = function (actualRow) {
var $row = $(actualRow), $next = $row.next(), $detail, values = [];
if ($row.data('detail_created') === true) return true;
if ($row.is(':hidden')) return false; //if the row is hidden for some reason (perhaps filtered) then get out of here
ft.raise(evt.rowDetailUpdating, { 'row': $row, 'detail': $next });
$row.find('> td:hidden').each(function () {
var index = $(this).index(), column = ft.getColumnFromTdIndex(index), name = column.name;
if (column.ignore === true) return true;
if (index in column.names) name = column.names[index];
values.push({ 'name': name, 'value': ft.parse(this, column), 'display': $.trim($(this).html()), 'group': column.group, 'groupName': column.groupName });
return true;
});
if (values.length === 0) return false; //return if we don't have any data to show
var colspan = $row.find('> td:visible').length;
var exists = $next.hasClass(cls.detail);
if (!exists) { // Create
$next = $('<tr class="' + cls.detail + '"><td class="' + cls.detailCell + '"><div class="' + cls.detailInner + '"></div></td></tr>');
$row.after($next);
}
$next.find('> td:first').attr('colspan', colspan);
$detail = $next.find('.' + cls.detailInner).empty();
opt.createDetail($detail, values, opt.createGroupedDetail, opt.detailSeparator, cls);
$row.data('detail_created', true);
ft.raise(evt.rowDetailUpdated, { 'row': $row, 'detail': $next });
return !exists;
};
ft.raise = function (eventName, args) {
if (ft.options.debug === true && $.isFunction(ft.options.log)) ft.options.log(eventName, 'event');
args = args || { };
var def = { 'ft': ft };
$.extend(true, def, args);
var e = $.Event(eventName, def);
if (!e.ft) {
$.extend(true, e, def);
} //pre jQuery 1.6 which did not allow data to be passed to event object constructor
$(ft.table).trigger(e);
return e;
};
//reset the state of FooTable
ft.reset = function() {
var $table = $(ft.table);
$table.removeData('footable_info')
.data('breakpoint', '')
.removeClass(cls.loading)
.removeClass(cls.loaded);
$table.find(opt.toggleSelector).unbind(trg.toggleRow).unbind('click.footable');
$table.find('> tbody > tr').removeClass(cls.detailShow);
$table.find('> tbody > tr.' + cls.detail).remove();
ft.raise(evt.reset);
};
ft.init();
return ft;
}
})(jQuery, window);
(function ($, w, undefined) {
if (w.footable === undefined || w.footable === 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 = {
paginate: true,
pageSize: 10,
pageNavigation: '.pagination',
firstText: '«',
previousText: '‹',
nextText: '›',
lastText: '»'
};
function pageInfo(ft) {
var $table = $(ft.table), $tbody = $table.find('> tbody');
this.pageNavigation = $table.data('page-navigation') || ft.options.pageNavigation;
this.pageSize = $table.data('page-size') || ft.options.pageSize;
this.firstText = $table.data('page-first-text') || ft.options.firstText;
this.previousText = $table.data('page-previous-text') || ft.options.previousText;
this.nextText = $table.data('page-next-text') || ft.options.nextText;
this.lastText = $table.data('page-last-text') || ft.options.lastText;
this.currentPage = 0;
this.pages = [];
this.control = false;
}
function Paginate() {
var p = this;
p.name = 'Footable Paginate';
p.init = function (ft) {
if (ft.options.paginate === true) {
if ($(ft.table).data('page') === false) return;
p.footable = ft;
$(ft.table)
.unbind('.paging')
.bind({
'footable_initialized.paging footable_row_removed.paging footable_redrawn.paging footable_sorted.paging footable_filtered.paging': function () {
p.setupPaging();
}
})
//save the filter object onto the table so we can access it later
.data('footable-paging', p);
}
};
p.setupPaging = function () {
var ft = p.footable,
$tbody = $(ft.table).find('> tbody');
if (!ft.pageInfo) {
ft.pageInfo = new pageInfo(ft);
}
p.createPages(ft, $tbody);
p.createNavigation(ft, $tbody);
p.fillPage(ft, $tbody, ft.pageInfo.currentPage);
};
p.createPages = function (ft, tbody) {
var pages = 1;
var info = ft.pageInfo;
var pageCount = pages * info.pageSize;
var page = [];
var lastPage = [];
info.pages = [];
var rows = tbody.find('> tr:not(.footable-filtered,.footable-row-detail)');
rows.each(function (i, row) {
page.push(row);
if (i === pageCount - 1) {
info.pages.push(page);
pages++;
pageCount = pages * info.pageSize;
page = [];
} else if (i >= rows.length - (rows.length % info.pageSize)) {
lastPage.push(row);
}
});
if (lastPage.length > 0) info.pages.push(lastPage);
if (info.currentPage >= info.pages.length) info.currentPage = info.pages.length - 1;
if (info.currentPage < 0) info.currentPage = 0;
if (info.pages.length === 1) {
//we only have a single page
$(ft.table).addClass('no-paging');
} else {
$(ft.table).removeClass('no-paging');
}
};
p.createNavigation = function (ft, tbody) {
var $nav = $(ft.table).find(ft.pageInfo.pageNavigation);
//if we cannot find the navigation control within the table, then try find it outside
if ($nav.length === 0) {
$nav = $(ft.pageInfo.pageNavigation);
//if the navigation control is inside another table, then get out
if ($nav.parents('table:first') !== $(ft.table)) return;
//if we found more than one navigation control, write error to console
if ($nav.length > 1 && ft.options.debug === true) console.error('More than one pagination control was found!');
}
//if we still cannot find the control, then don't do anything
if ($nav.length === 0) return;
//if the nav is not a UL, then find or create a UL
if (!$nav.is('ul')) {
if ($nav.find('ul:first').length === 0) {
$nav.append('<ul />');
}
$nav = $nav.find('ul');
}
$nav.find('li').remove();
var info = ft.pageInfo;
info.control = $nav;
if (info.pages.length > 0) {
$nav.append('<li class="footable-page-arrow"><a data-page="first" href="#first">' + ft.pageInfo.firstText + '</a>');
$nav.append('<li class="footable-page-arrow"><a data-page="prev" href="#prev">' + ft.pageInfo.previousText + '</a></li>');
$.each(info.pages, function (i, page) {
if (page.length > 0) {
$nav.append('<li class="footable-page"><a data-page="' + i + '" href="#">' + (i + 1) + '</a></li>');
}
});
$nav.append('<li class="footable-page-arrow"><a data-page="next" href="#next">' + ft.pageInfo.nextText + '</a></li>');
$nav.append('<li class="footable-page-arrow"><a data-page="last" href="#last">' + ft.pageInfo.lastText + '</a></li>');
}
$nav.find('a').click(function (e) {
e.preventDefault();
var page = $(this).data('page');
var newPage = info.currentPage;
if (page === 'first') {
newPage = 0;
} else if (page === 'prev') {
if (newPage > 0) newPage--;
} else if (page === 'next') {
if (newPage < info.pages.length - 1) newPage++;
} else if (page === 'last') {
newPage = info.pages.length - 1;
} else {
newPage = page;
}
p.paginate(ft, newPage);
});
p.setPagingClasses($nav, info.currentPage, info.pages.length);
};
p.paginate = function (ft, newPage) {
var info = ft.pageInfo;
if (info.currentPage !== newPage) {
var $tbody = $(ft.table).find('> tbody');
//raise a pre-pagin event so that we can cancel the paging if needed
var event = ft.raise('footable_paging', { page: newPage, size: info.pageSize });
if (event && event.result === false) return;
p.fillPage(ft, $tbody, newPage);
info.control.find('li').removeClass('active disabled');
p.setPagingClasses(info.control, info.currentPage, info.pages.length);
}
};
p.setPagingClasses = function (nav, currentPage, pageCount) {
nav.find('li.footable-page > a[data-page=' + currentPage + ']').parent().addClass('active');
if (currentPage >= pageCount - 1) {
nav.find('li.footable-page-arrow > a[data-page="next"]').parent().addClass('disabled');
nav.find('li.footable-page-arrow > a[data-page="last"]').parent().addClass('disabled');
}
if (currentPage < 1) {
nav.find('li.footable-page-arrow > a[data-page="first"]').parent().addClass('disabled');
nav.find('li.footable-page-arrow > a[data-page="prev"]').parent().addClass('disabled');
}
};
p.fillPage = function (ft, tbody, pageNumber) {
ft.pageInfo.currentPage = pageNumber;
tbody.find('> tr').hide();
$(ft.pageInfo.pages[pageNumber]).each(function () {
p.showRow(this, ft);
});
};
p.showRow = function (row, ft) {
var $row = $(row), $next = $row.next(), $table = $(ft.table);
if ($table.hasClass('breakpoint') && $row.hasClass('footable-detail-show') && $next.hasClass('footable-row-detail')) {
$row.add($next).show();
ft.createOrUpdateDetailRow(row);
}
else $row.show();
};
}
w.footable.plugins.register(Paginate, defaults);
})(jQuery, window);
(function ($, w, undefined) {
if (w.footable === undefined || w.footable === 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 = {
sort: true,
sorters: {
alpha: function (a, b) {
if (a === b) return 0;
if (a < b) return -1;
return 1;
},
numeric: function (a, b) {
return a - b;
}
},
classes: {
sort: {
sortable: 'footable-sortable',
sorted: 'footable-sorted',
descending: 'footable-sorted-desc',
indicator: 'footable-sort-indicator'
}
},
events: {
sort: {
sorting: 'footable_sorting',
sorted: 'footable_sorted'
}
}
};
function Sort() {
var p = this;
p.name = 'Footable Sortable';
p.init = function (ft) {
p.footable = ft;
if (ft.options.sort === true) {
$(ft.table)
.unbind('.sorting')
.bind({
'footable_initialized.sorting': function (e) {
var $table = $(ft.table),
$tbody = $table.find('> tbody'),
cls = ft.options.classes.sort,
column, $th;
if ($table.data('sort') === false) return;
$table.find('> thead > tr:last-child > th, > thead > tr:last-child > td').each(function (ec) {
$th = $(this), column = ft.columns[$th.index()];
if (column.sort.ignore !== true && !$th.hasClass(cls.sortable)) {
$th.addClass(cls.sortable);
$('<span />').addClass(cls.indicator).appendTo($th);
}
});
$table.find('> thead > tr:last-child > th.' + cls.sortable + ', > thead > tr:last-child > td.' + cls.sortable).unbind('click.footable').bind('click.footable', function (ec) {
ec.preventDefault();
$th = $(this);
var ascending = !$th.hasClass(cls.sorted);
p.doSort($th.index(), ascending);
return false;
});
var didSomeSorting = false;
for (var c in ft.columns) {
column = ft.columns[c];
if (column.sort.initial) {
var ascending = (column.sort.initial !== 'descending');
p.doSort(column.index, ascending);
break;
}
}
if (didSomeSorting) {
ft.bindToggleSelectors();
}
},
'footable_redrawn.sorting': function(e) {
var $table = $(ft.table),
cls = ft.options.classes.sort;
if ($table.data('sorted') >= 0) {
$table.find('> thead > tr:last-child > th').each(function(i){
var $th = $(this);
if ($th.hasClass(cls.sorted) || $th.hasClass(cls.descending)) {
p.doSort(i);
return;
}
});
}
},
'footable_column_data.sorting': function (e) {
var $th = $(e.column.th);
e.column.data.sort = e.column.data.sort || {};
e.column.data.sort.initial = $th.data('sort-initial') || false;
e.column.data.sort.ignore = $th.data('sort-ignore') || false;
e.column.data.sort.selector = $th.data('sort-selector') || null;
var match = $th.data('sort-match') || 0;
if (match >= e.column.data.matches.length) match = 0;
e.column.data.sort.match = e.column.data.matches[match];
}
})
//save the sort object onto the table so we can access it later
.data('footable-sort', p);
}
};
p.doSort = function(columnIndex, ascending) {
var ft = p.footable;
if ($(ft.table).data('sort') === false) return;
var $table = $(ft.table),
$tbody = $table.find('> tbody'),
column = ft.columns[columnIndex],
$th = $table.find('> thead > tr:last-child > th:eq(' + columnIndex + ')'),
cls = ft.options.classes.sort,
evt = ft.options.events.sort;
ascending = (ascending === undefined) ? $th.hasClass(cls.sorted) :
(ascending === 'toggle') ? !$th.hasClass(cls.sorted) : ascending;
if (column.sort.ignore === true) return true;
//raise a pre-sorting event so that we can cancel the sorting if needed
var event = ft.raise(evt.sorting, { column: column, direction: ascending ? 'ASC' : 'DESC' });
if (event && event.result === false) return;
$table.data('sorted', column.index);
$table.find('> thead > tr:last-child > th, > thead > tr:last-child > td').not($th).removeClass(cls.sorted + ' ' + cls.descending);
if (ascending === undefined) {
ascending = $th.hasClass(cls.sorted);
}
if (ascending) {
$th.removeClass(cls.descending).addClass(cls.sorted);
} else {
$th.removeClass(cls.sorted).addClass(cls.descending);
}
p.sort(ft, $tbody, column, ascending);
ft.bindToggleSelectors();
ft.raise(evt.sorted, { column: column, direction: ascending ? 'ASC' : 'DESC' });
};
p.rows = function (ft, tbody, column) {
var rows = [];
tbody.find('> tr').each(function () {
var $row = $(this), $next = null;
if ($row.hasClass(ft.options.classes.detail)) return true;
if ($row.next().hasClass(ft.options.classes.detail)) {
$next = $row.next().get(0);
}
var row = { 'row': $row, 'detail': $next };
if (column !== undefined) {
row.value = ft.parse(this.cells[column.sort.match], column);
}
rows.push(row);
return true;
}).detach();
return rows;
};
p.sort = function (ft, tbody, column, ascending) {
var rows = p.rows(ft, tbody, column);
var sorter = ft.options.sorters[column.type] || ft.options.sorters.alpha;
rows.sort(function (a, b) {
if (ascending) {
return sorter(a.value, b.value);
} else {
return sorter(b.value, a.value);
}
});
for (var j = 0; j < rows.length; j++) {
tbody.append(rows[j].row);
if (rows[j].detail !== null) {
tbody.append(rows[j].detail);
}
}
};
}
w.footable.plugins.register(Sort, defaults);
})(jQuery, window);
(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 = {
striping: {
enabled: true
},
classes: {
striping: {
odd: 'footable-odd',
even: 'footable-even'
}
}
};
function Striping() {
var p = this;
p.name = 'Footable Striping';
p.init = function (ft) {
p.footable = ft;
$(ft.table)
.unbind('striping')
.bind({
'footable_initialized.striping footable_row_removed.striping footable_redrawn.striping footable_sorted.striping footable_filtered.striping': function () {
if ($table.data('striping') === false) return;
p.setupStriping(ft);
}
});
};
p.setupStriping = function (ft) {
var rowIndex = 0;
$(ft.table).find('> tbody > tr:visible:not(.footable-row-detail)').each(function () {
var $row = $(this);
//Clean off old classes
$row.removeClass(ft.options.classes.striping.even).removeClass(ft.options.classes.striping.odd);
if (rowIndex % 2 === 0) {
$row.addClass(ft.options.classes.striping.even);
} else {
$row.addClass(ft.options.classes.striping.odd);
}
rowIndex++;
});
};
}
w.footable.plugins.register(Striping, defaults);
})(jQuery, window);
// knockout-delegatedEvents 0.1.2 | (c) 2013 Ryan Niemeyer | http://www.opensource.org/licenses/mit-license
;(function(factory) {
//CommonJS
if (typeof require === "function" && typeof exports === "object" && typeof module === "object") {
factory(require("knockout"), exports);
//AMD
} else if (typeof define === "function" && define.amd) {
define(["knockout", "exports"], factory);
//normal script tag
} else {
factory(ko, ko.actions = {});
}
}(function(ko, actions) {
var prefix = "ko_delegated_";
var createDelegatedHandler = function(eventName, root) {
return function(event) {
var data, method, action, owner, matchingParent, command, result,
el = event.target || event.srcElement,
context = ko.contextFor(el),
attr = "data-" + eventName,
key = prefix + eventName;
if (context) {
//loop until we either find an action, run out of elements, or hit the root element that has our delegated handler
while (!method && el) {
method = el.getAttribute(attr) || ko.utils.domData.get(el, key);
if (!method) {
el = el !== root ? el.parentNode : null;
}
}
if (method) {
//get context of the element that actually held the action
context = ko.contextFor(el);
if (context) {
data = context.$data;
if (typeof method === "string") {
//check defined actions
if (method in actions) {
command = actions[method];
if (command) {
action = typeof command === "function" ? command : command.action;
owner = command.owner || data;
}
}
//search for the action
else if (data && data[method] && typeof data[method] === "function") {
action = data[method];
owner = data;
}
//search parents for the action
if (!action) {
matchingParent = ko.utils.arrayFirst(context.$parents, function(parent) {
return parent[method] && typeof parent[method] === "function";
});
action = matchingParent && matchingParent[method];
owner = matchingParent;
}
}
//a binding handler was used to associate the element with a function
else if (typeof method === "function") {
action = method;
owner = data;
}
}
//execute the action as KO normally would
if (action) {
result = action.call(owner, data, event);
//prevent default action, if handler returns true
if (result !== true) {
if (event.preventDefault) {
event.preventDefault();
}
else {
event.returnValue = false;
}
}
}
}
}
};
};
//create a binding for an event to associate a function with the element
var createDelegatedBinding = function(event) {
var bindingName;
if (event) {
//capitalize first letter
bindingName = "delegated" + event.substr(0, 1).toUpperCase() + event.slice(1);
}
//create the binding, if it does not exist
if (!ko.bindingHandlers[bindingName]) {
ko.bindingHandlers[bindingName] = {
init: function(element, valueAccessor) {
var action = valueAccessor();
ko.utils.domData.set(element, prefix + event, action);
}
};
}
};
//add a handler on a parent element that responds to events from the children
ko.bindingHandlers.delegatedHandler = {
init: function(element, valueAccessor) {
var events = ko.utils.unwrapObservable(valueAccessor()) || [];
if (typeof events === "string") {
events = [events];
}
ko.utils.arrayForEach(events, function(event) {
createDelegatedBinding(event);
ko.utils.registerEventHandler(element, event, createDelegatedHandler(event, element));
});
}
};
}));
ko.bindingHandlers.showModal = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
$(element).draggable({ cancel: ".modal-body,.modal-footer" });
var modalsDiv = $("body").find("#modalsDiv");
if (modalsDiv.length === 0) {
modalsDiv = $("<div id='modalsDiv'></div>");
$("body").append(modalsDiv);
}
$(modalsDiv).append(element);
},
update: function (element, valueAccessor) {
var value = valueAccessor();
if (ko.utils.unwrapObservable(value)) {
$(element).modal('show');
}
else {
$(element).modal('hide');
}
}
};
ko.bindingHandlers.footable = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
$(element).closest("table").footable();
},
update: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
//this is called when the observableArray changes
//and after the foreach has rendered the contents
var value = ko.unwrap(valueAccessor()); //needed so that update is called
var footable = $(element).closest("table").data('footable');
//footable.reset();
//footable.init();
footable.redraw();
}
};
ko.bindingHandlers.footable.preprocess = function(value, name, addBindingCallback) {
//add foreach binding
addBindingCallback('foreach', '{ data: ' + value + '}');
return value;
};
var Task = (function (data) {
var id = ko.observable("00000000-0000-0000-0000-000000000000");
var description = ko.observable();
var dueOn = ko.observable();
var tasks = new Tasks();
var showTasks = ko.computed(function () {
var result = id() != "00000000-0000-0000-0000-000000000000";
return result;
});
var parentTaskId = "";
var initOrUpdate = function (task) {
id(task.id || "00000000-0000-0000-0000-000000000000");
description(task.description);
dueOn(task.dueOn);
tasks.initOrUpdate(task.tasks);
tasks.parentId = task.id;
parentTaskId = task.parentTaskId || "";
};
if (data) {
initOrUpdate(data);
}
return {
id: id,
description: description,
dueOn: dueOn,
tasks: tasks.tasks,
tasksObj: tasks,
showTasks: showTasks,
get parentTaskId() { return parentTaskId; },
set parentTaskId(parentId) { parentTaskId = parentId; },
initOrUpdate: initOrUpdate
};
});
var Tasks = (function (data) {
var parentId = "";
var tasks = ko.observableArray([]);
var selectedItem = ko.observable();
var itemForEditing = ko.observable();
var addItem = function (tasksData) {
var newItem = new Task();
newItem.parentTaskId = tasksData.parentId;
itemForEditing(newItem);
};
var selectItem = function (task) {
var newItem = new Task(ko.toJS(task));
itemForEditing(newItem);
selectedItem(task);
};
var removeItem = function (task) {
tasks.remove(task);
};
function acceptOrApply(apply) {
if (itemForEditing().id() === null || itemForEditing().id() === "00000000-0000-0000-0000-000000000000") {
var random1 = Math.floor((Math.random()*10)+1);
var random2 = Math.floor((Math.random()*10)+1);
var random3 = Math.floor((Math.random()*10)+1);
var guid = random1 + "0000000-0000-0000-0000-00000000" + random2 + "0" + random3;
itemForEditing().id(guid);
itemForEditing().initOrUpdate(ko.toJS(itemForEditing()));
tasks.push(itemForEditing());
if (apply) {
selectedItem(itemForEditing());
var newItem = new Task(ko.toJS(itemForEditing()));
itemForEditing(newItem);
} else {
itemForEditing(null);
}
} else {
selectedItem().initOrUpdate(ko.toJS(itemForEditing()));
var jsonData = ko.toJSON(selectedItem());
if (apply === undefined || apply === false) {
itemForEditing(null);
selectedItem(null);
}
}
}
var acceptEdit = function () {
acceptOrApply();
};
var applyEdit = function () {
acceptOrApply(true);
};
var cancelEdit = function () {
selectedItem(null);
itemForEditing(null);
};
var initOrUpdate = function (tasksData) {
tasksData.forEach(function (item) {
var tempItem = findItemById(item.id);
if (tempItem) {
tempItem.initOrUpdate(item);
} else {
tasks.push(new Task(item));
}
});
};
var findItemById = function (id) {
var result;
tasks().forEach(function (item) {
if (item.id() == id) {
result = item;
return;
}
});
return result;
};
if (data) {
initOrUpdate(data);
}
return {
tasks: tasks,
get parentId() { return parentId; },
set parentId(forId) { parentId = forId; },
addItem: addItem,
removeItem: removeItem,
selectItem: selectItem,
selectedItem: selectedItem,
itemForEditing: itemForEditing,
acceptEdit: acceptEdit,
applyEdit: applyEdit,
cancelEdit: cancelEdit,
initOrUpdate: initOrUpdate
};
});
allData = {"id":"70000000-0000-0000-0000-00000000901","description":"1","tasks":[{"id":"70000000-0000-0000-0000-00000000802","description":"2","tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"tasksObj":{"tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"parentId":"70000000-0000-0000-0000-00000000802"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000901"}],"tasksObj":{"tasks":[{"id":"70000000-0000-0000-0000-00000000802","description":"2","tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"tasksObj":{"tasks":[{"id":"50000000-0000-0000-0000-000000001001","description":"3","tasks":[],"tasksObj":{"tasks":[],"parentId":"50000000-0000-0000-0000-000000001001"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000802"}],"parentId":"70000000-0000-0000-0000-00000000802"},"showTasks":true,"parentTaskId":"70000000-0000-0000-0000-00000000901"}],"parentId":"70000000-0000-0000-0000-00000000901"},"showTasks":true,"parentTaskId":""};