/* * This file is part of nzbget. See . * * Copyright (C) 2012-2019 Andrey Prygunkov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /* * Some code was borrowed from: * 1. Greg Weber's uiTableFilter jQuery plugin (http://gregweber.info/projects/uitablefilter) * 2. Denny Ferrassoli & Charles Christolini's TypeWatch jQuery plugin (http://github.com/dennyferra/TypeWatch) * 3. Justin Britten's tablesorterFilter jQuery plugin (http://www.justinbritten.com/work/2008/08/tablesorter-filter-results-based-on-search-string/) * 4. Allan Jardine's Bootstrap Pagination jQuery plugin for DataTables (http://datatables.net/) */ /* * In this module: * HTML tables with: * 1) very fast content updates; * 2) automatic pagination; * 3) search/filtering; * 4) drag and drop. * * What makes it unique and fast? * The tables are designed to be updated very often (up to 10 times per second). This has two challenges: * 1) updating of whole content is slow because the DOM updates are slow. * 2) if the DOM is updated during user interaction the user input is not processed correctly. * For example if the table is updated after the user pressed mouse key but before he/she released * the key, the click is not processed because the element, on which the click was performed, * doesn't exist after the update of DOM anymore. * * How Fasttable solves these problems? The solutions is to update only rows and cells, * which were changed by keeping the unchanged DOM-elements. * * Important: the UI of table must be designed in a way, that the cells which are frequently changed * (like remaining download size) should not be clickable, whereas the cells which are rarely changed * (e. g. Download name) can be clickable. */ (function($) { 'use strict'; $.fn.fasttable = function(method) { if (methods[method]) { return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 )); } else if ( typeof method === 'object' || ! method ) { return methods.init.apply( this, arguments ); } else { $.error( 'Method ' + method + ' does not exist on jQuery.fasttable' ); } }; var methods = { defaults : function() { return defaults; }, init : function(options) { return this.each(function() { var $this = $(this); var data = $this.data('fasttable'); // If the plugin hasn't been initialized yet if (!data) { /* Do more setup stuff here */ var config = {}; config = $.extend(config, defaults, options); config.filterInput = $(config.filterInput); config.filterClearButton = $(config.filterClearButton); config.pagerContainer = $(config.pagerContainer); config.infoContainer = $(config.infoContainer); config.dragBox = $(config.dragBox); config.dragContent = $(config.dragContent); config.dragBadge = $(config.dragBadge); config.selector = $('th.table-selector', $this); var searcher = new FastSearcher(); // Create a timer which gets reset upon every keyup event. // Perform filter only when the timer's wait is reached (user finished typing or paused long enough to elapse the timer). // Do not perform the filter is the query has not changed. // Immediately perform the filter if the ENTER key is pressed. var timer; config.filterInput.keyup(function() { var timerWait = 500; var overrideBool = false; var inputBox = this; // Was ENTER pushed? if (inputBox.keyCode == 13) { timerWait = 1; overrideBool = true; } var timerCallback = function() { var value = inputBox.value.trim(); var data = $this.data('fasttable'); if ((value != data.lastFilter) || overrideBool) { applyFilter(data, value); } }; // Reset the timer clearTimeout(timer); timer = setTimeout(timerCallback, timerWait); return false; }); config.filterClearButton.click(function() { var data = $this.data('fasttable'); data.config.filterInput.val(''); applyFilter(data, ''); }); config.pagerContainer.on('click', 'li', function (e) { e.preventDefault(); var data = $this.data('fasttable'); var pageNum = $(this).text(); if (pageNum.indexOf('Prev') > -1) { data.curPage--; } else if (pageNum.indexOf('Next') > -1) { data.curPage++; } else if (isNaN(parseInt(pageNum))) { return; } else { data.curPage = parseInt(pageNum); } refresh(data); }); var data = { target: $this, config: config, pageSize: parseInt(config.pageSize), maxPages: parseInt(config.maxPages), pageDots: Util.parseBool(config.pageDots), curPage: 1, checkedRows: {}, checkedCount: 0, lastClickedRowID: null, searcher: searcher }; initDragDrop(data); $this.on('click', 'thead > tr', function(e) { titleCheckClick(data, e); }); $this.on('click', 'tbody > tr', function(e) { itemCheckClick(data, e); }); $this.data('fasttable', data); } }); }, destroy : function() { return this.each(function() { var $this = $(this); // Namespacing FTW $(window).unbind('.fasttable'); $this.removeData('fasttable'); }); }, update : updateContent, setPageSize : setPageSize, setCurPage : setCurPage, applyFilter : function(filter) { applyFilter($(this).data('fasttable'), filter); }, filteredContent : function() { return $(this).data('fasttable').filteredContent; }, availableContent : function() { return $(this).data('fasttable').availableContent; }, checkedRows : function() { return $(this).data('fasttable').checkedRows; }, checkedCount : function() { return $(this).data('fasttable').checkedCount; }, pageCheckedCount : function() { return $(this).data('fasttable').pageCheckedCount; }, checkRow : function(id, checked) { checkRow($(this).data('fasttable'), id, checked); }, processShortcut : function(key) { return processShortcut($(this).data('fasttable'), key); }, }; function updateContent(content) { var data = $(this).data('fasttable'); if (content) { data.content = content; } refresh(data); blinkMovedRecords(data); } function applyFilter(data, filter) { data.lastFilter = filter; if (data.content) { data.curPage = 1; data.hasFilter = filter !== ''; data.searcher.compile(filter); refresh(data); } if (filter !== '' && data.config.filterInputCallback) { data.config.filterInputCallback(filter); } if (filter === '' && data.config.filterClearCallback) { data.config.filterClearCallback(); } } function refresh(data) { refilter(data); validateChecks(data); updatePager(data); updateInfo(data); updateSelector(data); updateTable(data); } function refilter(data) { data.availableContent = []; data.filteredContent = []; for (var i = 0; i < data.content.length; i++) { var item = data.content[i]; if (data.hasFilter && item.search === undefined && data.config.fillSearchCallback) { data.config.fillSearchCallback(item); } if (!data.hasFilter || data.searcher.exec(item.data)) { data.availableContent.push(item); if (!data.config.filterCallback || data.config.filterCallback(item)) { data.filteredContent.push(item); } } } } function updateTable(data) { var oldTable = data.target[0]; var newTable = buildTBody(data); updateTBody(data, oldTable, newTable); } function buildTBody(data) { var table = $('
')[0]; for (var i=0; i < data.pageContent.length; i++) { var item = data.pageContent[i]; var row = table.insertRow(table.rows.length); row.fasttableID = item.id; if (data.checkedRows[item.id]) { row.className = 'checked'; } if (data.config.renderRowCallback) { data.config.renderRowCallback(row, item); } if (!item.fields) { if (data.config.fillFieldsCallback) { data.config.fillFieldsCallback(item); } else { item.fields = []; } } for (var j=0; j < item.fields.length; j++) { var cell = row.insertCell(row.cells.length); cell.innerHTML = item.fields[j]; if (data.config.renderCellCallback) { data.config.renderCellCallback(cell, j, item); } } } titleCheckRedraw(data); if (data.config.renderTableCallback) { data.config.renderTableCallback(table); } return table; } function updateTBody(data, oldTable, newTable) { var headerRows = $('thead > tr', oldTable).length; var oldTRs = oldTable.rows; var newTRs = newTable.rows; var oldTBody = $('tbody', oldTable)[0]; var oldTRsLength = oldTRs.length - headerRows; // evlt. skip header row var newTRsLength = newTRs.length; for (var i=0; i < newTRs.length; ) { var newTR = newTRs[i]; if (i < oldTRsLength) { // update existing row var oldTR = oldTRs[i + headerRows]; // evlt. skip header row var oldTDs = oldTR.cells; var newTDs = newTR.cells; oldTR.className = newTR.className; oldTR.fasttableID = newTR.fasttableID; for (var j=0, n = 0; j < oldTDs.length; j++, n++) { var oldTD = oldTDs[j]; var newTD = newTDs[n]; var oldHtml = oldTD.outerHTML; var newHtml = newTD.outerHTML; if (oldHtml !== newHtml) { oldTR.replaceChild(newTD, oldTD); n--; } } i++; } else { // add new row oldTBody.appendChild(newTR); } } var maxTRs = newTRsLength + headerRows; // evlt. skip header row; while (oldTRs.length > maxTRs) { oldTable.deleteRow(oldTRs.length - 1); } } function updatePager(data) { data.pageCount = Math.ceil(data.filteredContent.length / data.pageSize); if (data.curPage < 1) { data.curPage = 1; } if (data.curPage > data.pageCount) { data.curPage = data.pageCount; } var startIndex = (data.curPage - 1) * data.pageSize; data.pageContent = data.filteredContent.slice(startIndex, startIndex + data.pageSize); var pagerObj = data.config.pagerContainer; var pagerHtml = buildPagerHtml(data); var oldPager = pagerObj[0]; var newPager = $(pagerHtml)[0]; updatePagerContent(data, oldPager, newPager); } function buildPagerHtml(data) { var iListLength = data.maxPages; var iStart, iEnd, iHalf = Math.floor(iListLength/2); if (data.pageCount < iListLength) { iStart = 1; iEnd = data.pageCount; } else if (data.curPage -1 <= iHalf) { iStart = 1; iEnd = iListLength; } else if (data.curPage - 1 >= (data.pageCount-iHalf)) { iStart = data.pageCount - iListLength + 1; iEnd = data.pageCount; } else { iStart = data.curPage - 1 - iHalf + 1; iEnd = iStart + iListLength - 1; } var pager = '
    '; pager += '← Prev'; if (iStart > 1) { pager += '
  • 1
  • '; if (iStart > 2 && data.pageDots) { pager += '
  • '; } } for (var j=iStart; j<=iEnd; j++) { pager += '' + j + ''; } if (iEnd != data.pageCount) { if (iEnd < data.pageCount - 1 && data.pageDots) { pager += '
  • '; } pager += '
  • ' + data.pageCount + '
  • '; } pager += 'Next →'; pager += '
'; return pager; } function updatePagerContent(data, oldPager, newPager) { var oldLIs = oldPager.getElementsByTagName('li'); var newLIs = newPager.getElementsByTagName('li'); var oldLIsLength = oldLIs.length; var newLIsLength = newLIs.length; for (var i=0, n=0; i < newLIs.length; i++, n++) { var newLI = newLIs[i]; if (n < oldLIsLength) { // update existing LI var oldLI = oldLIs[n]; var oldHtml = oldLI.outerHTML; var newHtml = newLI.outerHTML; if (oldHtml !== newHtml) { oldPager.replaceChild(newLI, oldLI); i--; } } else { // add new LI oldPager.appendChild(newLI); i--; } } while (oldLIs.length > newLIsLength) { oldPager.removeChild(oldPager.lastChild); } } function updateInfo(data) { if (data.content.length === 0) { var infoText = data.config.infoEmpty; } else if (data.curPage === 0) { var infoText = 'No matching records found (total ' + data.content.length + ')'; } else { var firstRecord = (data.curPage - 1) * data.pageSize + 1; var lastRecord = firstRecord + data.pageContent.length - 1; var infoText = 'Showing records ' + firstRecord + '-' + lastRecord + ' from ' + data.filteredContent.length; if (data.filteredContent.length != data.content.length) { infoText += ' filtered (total ' + data.content.length + ')'; } } data.config.infoContainer.html(infoText); if (data.config.updateInfoCallback) { data.config.updateInfoCallback({ total: data.content.length, available: data.availableContent.length, filtered: data.filteredContent.length, firstRecord: firstRecord, lastRecord: lastRecord }); } } function updateSelector(data) { data.pageCheckedCount = 0; if (data.checkedCount > 0 && data.filteredContent.length > 0) { for (var i = (data.curPage - 1) * data.pageSize; i < Math.min(data.curPage * data.pageSize, data.filteredContent.length); i++) { data.pageCheckedCount += data.checkedRows[data.filteredContent[i].id] ? 1 : 0; } } data.config.selector.css('display', data.pageCheckedCount === data.checkedCount ? 'none' : ''); if (data.checkedCount !== data.pageCheckedCount) { data.config.selector.text('' + (data.checkedCount - data.pageCheckedCount) + (data.checkedCount - data.pageCheckedCount > 1 ? ' records' : ' record') + ' selected on other pages'); } } function setPageSize(pageSize, maxPages, pageDots) { var data = $(this).data('fasttable'); data.pageSize = parseInt(pageSize); data.curPage = 1; if (maxPages !== undefined) { data.maxPages = maxPages; } if (pageDots !== undefined) { data.pageDots = pageDots; } refresh(data); } function setCurPage(page) { var data = $(this).data('fasttable'); data.curPage = parseInt(page); refresh(data); } function checkedIds(data) { var checkedRows = data.checkedRows; var checkedIds = []; for (var i = 0; i < data.content.length; i++) { var id = data.content[i].id; if (checkedRows[id]) { checkedIds.push(id); } } return checkedIds; } function titleCheckRedraw(data) { var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var hasSelectedItems = false; var hasUnselectedItems = false; for (var i = 0; i < filteredContent.length; i++) { if (checkedRows[filteredContent[i].id]) { hasSelectedItems = true; } else { hasUnselectedItems = true; } } var headerRow = $('thead > tr', data.target); if (hasSelectedItems && hasUnselectedItems) { headerRow.removeClass('checked').addClass('checkremove'); } else if (hasSelectedItems) { headerRow.removeClass('checkremove').addClass('checked'); } else { headerRow.removeClass('checked').removeClass('checkremove'); } } function itemCheckClick(data, event) { var checkmark = $(event.target).hasClass('check'); if (data.dragging || (!checkmark && !data.config.rowSelect)) { return; } var row = $(event.target).closest('tr', data.target)[0]; var id = row.fasttableID; var doToggle = true; var checkedRows = data.checkedRows; if (event.shiftKey && data.lastClickedRowID != null) { var checked = checkedRows[id]; doToggle = !checkRange(data, id, data.lastClickedRowID, !checked); } if (doToggle) { toggleCheck(data, id); } data.lastClickedRowID = id; refresh(data); } function titleCheckClick(data, event) { var checkmark = $(event.target).hasClass('check'); if (data.dragging || (!checkmark && !data.config.rowSelect)) { return; } var filteredContent = data.filteredContent; var checkedRows = data.checkedRows; var hasSelectedItems = false; for (var i = 0; i < filteredContent.length; i++) { if (checkedRows[filteredContent[i].id]) { hasSelectedItems = true; break; } } data.lastClickedRowID = null; checkAll(data, !hasSelectedItems); } function toggleCheck(data, id) { var checkedRows = data.checkedRows; if (checkedRows[id]) { checkedRows[id] = undefined; data.checkedCount--; } else { checkedRows[id] = true; data.checkedCount++; } } function checkAll(data, checked) { var filteredContent = data.filteredContent; for (var i = 0; i < filteredContent.length; i++) { checkRow(data, filteredContent[i].id, checked); } refresh(data); } function checkRange(data, from, to, checked) { var filteredContent = data.filteredContent; var indexFrom = indexOfID(filteredContent, from); var indexTo = indexOfID(filteredContent, to); if (indexFrom === -1 || indexTo === -1) { return false; } if (indexTo < indexFrom) { var tmp = indexTo; indexTo = indexFrom; indexFrom = tmp; } for (var i = indexFrom; i <= indexTo; i++) { checkRow(data, filteredContent[i].id, checked); } return true; } function checkRow(data, id, checked) { if (checked) { if (!data.checkedRows[id]) { data.checkedCount++; } data.checkedRows[id] = true; } else { if (data.checkedRows[id]) { data.checkedCount--; } data.checkedRows[id] = undefined; } } function indexOfID(content, id) { for (var i = 0; i < content.length; i++) { if (id === content[i].id) { return i; } } return -1; } function validateChecks(data) { var checkedRows = data.checkedRows; data.checkedRows = {} data.checkedCount = 0; for (var i = 0; i < data.content.length; i++) { if (checkedRows[data.content[i].id]) { data.checkedRows[data.content[i].id] = true; data.checkedCount++; } } } //*************** DRAG-N-DROP function initDragDrop(data) { data.target[0].addEventListener('mousedown', function(e) { mouseDown(data, e); }, true); data.target[0].addEventListener('touchstart', function(e) { mouseDown(data, e); }, true); data.moveIds = []; data.dropAfter = false; data.dropId = null; data.dragging = false; data.dragRow = $(''); data.cancelDrag = false; data.downPos = null; data.blinkIds = []; data.blinkState = null; data.wantBlink = false; } function touchToMouse(e) { if (e.type === 'touchstart' || e.type === 'touchmove' || e.type === 'touchend') { e.clientX = e.changedTouches[0].clientX; e.clientY = e.changedTouches[0].clientY; } } function mouseDown(data, e) { data.dragging = false; data.dropId = null; data.dragRow = $(e.target).closest('tr', data.target); var checkmark = $(e.target).hasClass('check') || ($(e.target).find('.check').length > 0 && !$('body').hasClass('phone')); var head = $(e.target).closest('tr', data.target).parent().is('thead'); if (head || !(checkmark || (data.config.rowSelect && e.type === 'mousedown')) || data.dragRow.length != 1 || e.ctrlKey || e.altKey || e.metaKey) { return; } touchToMouse(e); if (e.type === 'mousedown') { e.preventDefault(); } if (!data.config.dragEndCallback) { return; } data.downPos = { x: e.clientX, y: e.clientY }; data.mouseMove = function(e) { mouseMove(data, e); }; data.mouseUp = function(e) { mouseUp(data, e); }; data.keyDown = function(e) { keyDown(data, e); }; document.addEventListener('mousemove', data.mouseMove, true); document.addEventListener('touchmove', data.mouseMove, true); document.addEventListener('mouseup', data.mouseUp, true); document.addEventListener('touchend', data.mouseUp, true); document.addEventListener('touchcancel', data.mouseUp, true); document.addEventListener('keydown', data.keyDown, true); } function mouseMove(data, e) { touchToMouse(e); e.preventDefault(); if (e.touches && e.touches.length > 1) { data.cancelDrag = true; mouseUp(data, e); return; } if (!data.dragging) { if (Math.abs(data.downPos.x - e.clientX) < 5 && Math.abs(data.downPos.y - e.clientY) < 5) { return; } startDrag(data, e); if (data.dragCancel) { mouseUp(data, e); return; } } updateDrag(data, e.clientX, e.clientY); autoScroll(data, e.clientX, e.clientY); } function startDrag(data, e) { if (data.config.dragStartCallback) { data.config.dragStartCallback(); } var offsetX = $(document).scrollLeft(); var offsetY = $(document).scrollTop(); var rf = data.dragRow.offset(); data.dragOffset = { x: data.downPos.x - rf.left + offsetX, y: Math.min(Math.max(data.downPos.y - rf.top + offsetY, 0), data.dragRow.height()) }; var checkedRows = data.checkedRows; var chkIds = checkedIds(data); var id = data.dragRow[0].fasttableID; data.moveIds = checkedRows[id] ? chkIds : [id]; data.dragging = true; data.cancelDrag = false; buildDragBox(data); data.config.dragBox.css('display', 'block'); data.dragRow.addClass('drag-source'); $('html').addClass('drag-progress'); data.oldOverflowX = $('body').css('overflow-x'); $('body').css('overflow-x', 'hidden'); } function buildDragBox(data) { var tr = data.dragRow.clone(); var table = data.target.clone(); $('tr', table).remove(); $('thead', table).remove(); $('tbody', table).append(tr); table.css('margin', 0); data.config.dragContent.html(table); data.config.dragBadge.text(data.moveIds.length); data.config.dragBadge.css('display', data.moveIds.length > 1 ? 'block' : 'none'); data.config.dragBox.css({left: data.target.offset().left, width: data.dragRow.width()}); var tds = $('td', tr); $('td', data.dragRow).each(function(ind, el) { $(tds[ind]).css('width', $(el).width()); }); } function updateDrag(data, x, y) { var offsetX = $(document).scrollLeft(); var offsetY = $(document).scrollTop(); var posX = x + offsetX; var posY = y + offsetY; data.config.dragBox.css({ left: posX - data.dragOffset.x, top: Math.max(Math.min(posY - data.dragOffset.y, offsetY + $(window).height() - data.config.dragBox.height() - 2), offsetY + 2)}); var dt = data.config.dragBox.offset().top; var dh = data.config.dragBox.height(); var rows = $('tbody > tr', data.target); for (var i = 0; i < rows.length; i++) { var row = $(rows[i]); var rt = row.offset().top; var rh = row.height(); if (row[0] !== data.dragRow[0]) { if ((dt >= rt && dt <= rt + rh / 2) || (dt < rt && i == 0)) { data.dropAfter = false; row.before(data.dragRow); data.dropId = row[0].fasttableID; break; } if ((dt + dh >= rt + rh / 2 && dt + dh <= rt + rh) || (dt + dh > rt + rh && i === rows.length - 1)) { data.dropAfter = true; row.after(data.dragRow); data.dropId = row[0].fasttableID; break; } } } if (data.dropId === null) { data.dropId = data.dragRow[0].fasttableID; data.dropAfter = true; } } function autoScroll(data, x, y) { // works properly only if the table lays directly on the page (not in another scrollable div) data.scrollStep = (y > $(window).height() - 20 ? 1 : y < 20 ? -1 : 0) * 5; if (data.scrollStep !== 0 && !data.scrollTimer) { var scroll = function() { $(document).scrollTop($(document).scrollTop() + data.scrollStep); updateDrag(data, x, y + data.scrollStep); data.scrollTimer = data.scrollStep == 0 ? null : setTimeout(scroll, 10); } data.scrollTimer = setTimeout(scroll, 500); } } function mouseUp(data, e) { document.removeEventListener('mousemove', data.mouseMove, true); document.removeEventListener('touchmove', data.mouseMove, true); document.removeEventListener('mouseup', data.mouseUp, true); document.removeEventListener('touchend', data.mouseUp, true); document.removeEventListener('touchcancel', data.mouseUp, true); document.removeEventListener('keydown', data.keyDown, true); if (!data.dragging) { return; } data.dragging = false; data.cancelDrag = data.cancelDrag || e.type === 'touchcancel'; data.dragRow.removeClass('drag-source'); $('html').removeClass('drag-progress'); $('body').css('overflow-x', data.oldOverflowX); data.config.dragBox.hide(); data.scrollStep = 0; clearTimeout(data.scrollTimer); data.scrollTimer = null; moveRecords(data); } function keyDown(data, e) { if (e.keyCode == 27) // ESC-key { data.cancelDrag = true; e.preventDefault(); mouseUp(data, e); } } function moveRecords(data) { if (data.dropId !== null && !data.cancelDrag && !(data.moveIds.length == 1 && data.dropId == data.moveIds[0])) { data.blinkIds = data.moveIds; data.moveIds = []; data.blinkState = data.config.dragBlink === 'none' ? 0 : 3; data.wantBlink = data.blinkState > 0; moveRows(data); } else { data.dropId = null; } if (data.dropId === null) { data.moveIds = []; } refresh(data); data.config.dragEndCallback(data.dropId !== null ? { ids: data.blinkIds, position: data.dropId, direction: data.dropAfter ? 'after' : 'before' } : null); if (data.config.dragBlink === 'direct') { data.target.fasttable('update'); } } function moveRows(data) { var movedIds = data.blinkIds; var movedRecords = []; for (var i = 0; i < data.content.length; i++) { var item = data.content[i]; if (movedIds.indexOf(item.id) > -1) { movedRecords.push(item); data.content.splice(i, 1); i--; if (item.id === data.dropId) { if (i >= 0) { data.dropId = data.content[i].id; data.dropAfter = true; } else if (i + 1 < data.content.length) { data.dropId = data.content[i + 1].id; data.dropAfter = false; } else { data.dropId = null; } } } } if (data.dropId === null) { // restore content for (var j = 0; j < movedRecords.length; j++) { data.content.push(movedRecords[j]); } return; } for (var i = 0; i < data.content.length; i++) { if (data.content[i].id === data.dropId) { for (var j = movedRecords.length - 1; j >= 0; j--) { data.content.splice(data.dropAfter ? i + 1 : i, 0, movedRecords[j]); } break; } } } function blinkMovedRecords(data) { if (data.blinkIds.length > 0) { blinkProgress(data, data.wantBlink); data.wantBlink = false; } } function blinkProgress(data, recur) { var rows = $('tr', data.target); rows.removeClass('drag-finish'); rows.each(function(ind, el) { var id = el.fasttableID; if (data.blinkIds.indexOf(id) > -1 && (data.blinkState === 1 || data.blinkState === 3 || data.blinkState === 5)) { $(el).addClass('drag-finish'); } }); if (recur && data.blinkState > 0) { setTimeout(function() { data.blinkState -= 1; blinkProgress(data, true); }, 150); } if (data.blinkState === 0) { data.blinkIds = []; } } //*************** KEYBOARD function processShortcut(data, key) { switch (key) { case 'Left': data.curPage = Math.max(data.curPage - 1, 1); refresh(data); return true; case 'Shift+Left': data.curPage = 1; refresh(data); return true; case 'Right': data.curPage = Math.min(data.curPage + 1, data.pageCount); refresh(data); return true; case 'Shift+Right': data.curPage = data.pageCount; refresh(data); return true; case 'Shift+F': data.config.filterInput.focus(); return true; case 'Shift+C': data.config.filterClearButton.click(); return true; } } //*************** CONFIG var defaults = { filterInput: '#TableFilter', filterClearButton: '#TableClear', pagerContainer: '#TablePager', infoContainer: '#TableInfo', dragBox: '#TableDragBox', dragContent: '#TableDragContent', dragBadge: '#TableDragBadge', dragBlink: 'none', // none, direct, update pageSize: 10, maxPages: 5, pageDots: true, rowSelect: false, shortcuts: false, infoEmpty: 'No records', renderRowCallback: undefined, renderCellCallback: undefined, renderTableCallback: undefined, fillFieldsCallback: undefined, updateInfoCallback: undefined, filterInputCallback: undefined, filterClearCallback: undefined, fillSearchCallback: undefined, filterCallback: undefined, dragStartCallback: undefined, dragEndCallback: undefined }; })(jQuery); function FastSearcher() { 'use strict'; this.source; this.len = 0; this.p = 0; this.initLexer = function(source) { this.source = source; this.len = source.length; this.p = 0; } this.nextToken = function() { while (this.p < this.len) { var ch = this.source[this.p++]; switch (ch) { case ' ': case '\t': continue; case '-': case '(': case ')': case '|': return ch; default: this.p--; var token = ''; var quote = false; while (this.p < this.len) { var ch = this.source[this.p++]; if (quote) { if (ch === '"') { quote = false; ch = ''; } } else { if (ch === '"') { quote = true; ch = ''; } else if (' \t()|'.indexOf(ch) > -1) { this.p--; return token; } } token += ch; } return token; } } return null; } this.compile = function(searchstr) { var _this = this; this.initLexer(searchstr); function expression(greedy) { var node = null; while (true) { var token = _this.nextToken(); var node2 = null; switch (token) { case null: case ')': return node; case '-': node2 = expression(false); node2 = node2 ? _this.not(node2) : node2; break; case '(': node2 = expression(true); break; case '|': node2 = expression(false); break; default: node2 = _this.term(token); } if (node && node2) { node = token === '|' ? _this.or(node, node2) : _this.and(node, node2); } else if (node2) { node = node2; } if (!greedy && node) { return node; } } } this.root = expression(true); } this.root = null; this.data = null; this.exec = function(data) { this.data = data; return this.root ? this.root.eval() : true; } this.and = function(L, R) { return { L: L, R: R, eval: function() { return this.L.eval() && this.R.eval(); } }; } this.or = function(L, R) { return { L: L, R: R, eval: function() { return this.L.eval() || this.R.eval(); } }; } this.not = function(M) { return { M: M, eval: function() { return !this.M.eval();} }; } this.term = function(term) { return this.compileTerm(term); } var COMMANDS = [ ':', '>=', '<=', '<>', '>', '<', '=' ]; this.compileTerm = function(term) { var _this = this; var text = term.toLowerCase(); var field; var command; var commandIndex; for (var i = 0; i < COMMANDS.length; i++) { var cmd = COMMANDS[i]; var p = term.indexOf(cmd); if (p > -1 && (p < commandIndex || commandIndex === undefined)) { commandIndex = p; command = cmd; } } if (command !== undefined) { field = term.substring(0, commandIndex); text = text.substring(commandIndex + command.length); } return { command: command, text: text, field: field, eval: function() { return _this.evalTerm(this); } }; } this.evalTerm = function(term) { var text = term.text; var field = term.field; var content = this.fieldValue(this.data, field); if (content === undefined) { return false; } switch (term.command) { case undefined: case ':': return content.toString().toLowerCase().indexOf(text) > -1; case '=': return content.toString().toLowerCase() == text; case '<>': return content.toString().toLowerCase() != text; case '>': return parseInt(content) > parseInt(text); case '>=': return parseInt(content) >= parseInt(text); case '<': return parseInt(content) < parseInt(text); case '<=': return parseInt(content) <= parseInt(text); default: return false; } } this.fieldValue = function(data, field) { var value = ''; if (field !== undefined) { value = data[field]; if (value === undefined) { if (this.nameMap === undefined) { this.buildNameMap(data); } value = data[this.nameMap[field.toLowerCase()]]; } } else { if (data._search === true) { for (var prop in data) { value += ' ' + data[prop]; } } else { for (var i = 0; i < data._search.length; i++) { value += ' ' + data[data._search[i]]; } } } return value; } this.nameMap = undefined; this.buildNameMap = function(data) { this.nameMap = {}; for (var prop in data) { this.nameMap[prop.toLowerCase()] = prop; } } }