// html5csv.js (C) Copyright 2013 Dr Paul Brewer
// This Javscript library is Free software and comes with ABSOLUTELY NO WARRANTY
//
// The author hereby provides permission to use this free software under the terms
// of the GNU General Public License which may be found at
// http://www.gnu.org/licenses/gpl-3.0.txt
//
// Some organizations prefer not to be bound by the terms of the GNU General Public License.
//
// Commercial licenses from the author are available, and may provide for uses not permitted
// under the GNU General Public License. The author may be contacted on Linked In
// via http://www.linkedin.com/in/drpaulbrewer
//
// Uses requiring a commercial license include serving or distributing an object code version
// of this software, without also supplying the fully readable, editable source code,
// (where object code includes any minified, compiled, compressed or obfuscated
// Javascript code that uses shortened names or space removal and is therefore no longer in
// the preferred form for editing by software developers), combining the software here in the
// same Javascript file with other proprietary software that is not provided as
// free software under terms similar to the GNU General Public License, embedding the
// software into a hardware device so that it can not be viewed or edited by the end user,
// or modifying or removing these notices and/or copyright notices in ways contrary
// to the free license. This may not be the entire list of uses requiring a commercial license.
//
//
if (typeof window.jQuery === 'undefined'){
console.log("CSV: jQuery is not loaded. Load jquery before loading csv.js");
throw "CSV: jQuery is not loaded";
}
window.CSV = (function(){
var csvFuncs = {
'push': push,
'call': call,
'hslice': hslice,
'table': table,
'editor': editor,
'jqplot': jqplot,
'appendCol': appendCol,
'ols': ols,
'save': save,
'download': download,
};
var csvShared = {
taskDelay: 50,
specialNames:{
'%U': ['uniformRandomMatrix','dim'],
'%N': ['normalRandomMatrix','dim'],
'%I': ['identityMatrix', 'dim'],
'%D': ['diagonalMatrix', 'diag'],
'%F': ['forij', 'dim', 'func']
},
finalCallback: function(e,data){
if (e) { console.log(e) }
},
fill: function(dim, x){
var il=dim[0],jl=dim[1];
var i,j;
var row = [], rows=[];
for(j=0;j=0){
row.push(rows[i][colnums[j]]);
} else if (typeof cols[j] === 'number'){
row.push(cols[j]);
} else if (typeof cols[j] === 'function'){
row.push((cols[j])(i,j,rows[i]));
} else row.push(null);
}
M.push(row);
}
return M;
},
parseCSV: function(t){
// this should probably be replaced by something more robust
// the concern is that csv files from various sources could
// be a mess, fields quoted differently even in same row,
// escaped special characters (or not) etc...
// tried csvParse in numeric.js - it seemed not to handle quoted well
var lines = t.split("\n");
var j=0,k=0,rows = [], line="";
var row, i, l, c0, quote, offset, sep, item;
for(j=0,k=lines.length;j 0)){
task = shared.todo.shift();
return setTimeout(
function(){
var func = task.f;
if (typeof func !== 'function')
func = csvFuncs[task.f];
if (typeof func !== 'function')
throw "CSV: shared.nextTask encountered unknown task in to do list";
try {
return func.apply(shared,task.a)
} catch(e) {
shared.todo = 'in error finalCallback';
shared.finalCallback(e, null);
shared.todo = 0;
return;
}
},
shared.taskDelay);
}
// task list empty
if ((typeof shared.todo === 'object') && (shared.todo.length===0)){
shared.todo = 'in finalCallback'; // now actions is no longer object
shared.finalCallback(null, shared.data);
shared.todo = 0; // when 0 will allow finalize to rearm
// call finalCallback first in order to give simple
// callbacks a chance to finish before finalize()() could restart
return;
}
throw "somehow called nextTask after finalCallback executed";
}
};
var CSVRETURN = {
'begin': begin,
'extend': extend,
};
function extend(newCsvFuncs, newCsvShared){
if (typeof newCsvFuncs === "object"){
$.extend(csvFuncs, newFuncs);
} else {
throw "CSV: extend newCsvFuncs must be either an object with function values to extend csvFuncs or null for no extensions";
}
if (typeof newCsvShared === "object"){
$.extend(csvShared, newCsvShared);
} else if (typeof newCsvShared === "undefined"){
// do nothing
} else
throw "CSV: extend newCsvShared must be either an object with functions and other values to extend newCsvShared or null/undefined for no extensions";
return CSVRETURN;
}
function planner(cando, candone, todo){
var methods = {};
var i,l;
function plan(F){
return function(){
todo.push({
f:F,
a: Array.prototype.slice.call(arguments)
});
return methods;
}
}
for(i=0,l=cando.length;i');
}
t('table', opt.table);
if (opt.caption){
t('caption', opt.captionOpt);
buf.push(opt.caption);
buf.push('');
}
i = 0;
if (opt.header || opt.thead){
row=rows[0];
t('thead', opt.thead);
t('tr', opt.theadtr, [0]);
for(j=0,k=row.length; j');
}
buf.push('');
i = 1;
}
t('tbody', opt.tbody);
for(;i');
}
buf.push('');
}
buf.push('');
return buf.join('');
}
function localFetch(csvname){
var splitname = csvname.split('/');
var D,J;
if ((splitname[0]!=='local') &&
(splitname[0]!=='session')
) throw "localFetch must be from local/ or session/, got: "+csvname;
J = window[splitname[0]+'Storage'].getItem(csvname);
if ((J === null) || (typeof J === 'undefined')) throw 'CSV: '+csvname+' not found';
if ((window.LZString) &&
(typeof window.LZString.decompressFromUTF16 === 'function')){
D = JSON.parse(LZString.decompressFromUTF16(J));
} else {
D = JSON.parse(J);
}
return D;
}
function localCreate(csvname, rows, meta){
var csvObject, J;
var splitname = csvname.split('/');
if ((splitname[0]!=='local') &&
(splitname[0]!=='session')
) throw "localCreate must save to local/ or session/, got: "+csvname;
csvObject = {'name': csvname,
'rows': rows,
'createDate': (''+new Date())
};
if (meta) csvObject.meta = meta;
if ((window.LZString) &&
(typeof window.LZString.compressToUTF16 === 'function')){
J = LZString.compressToUTF16(JSON.stringify(csvObject));
} else {
J = JSON.stringify(csvObject);
}
window[splitname[0]+'Storage'].setItem(csvname, J);
// dont call nextTask() here , localFetch() will do that
}
function fetch(){
var shared = this;
var doNextTask = true;
if (!shared.data) shared.data = {};
if (!shared.data.meta) shared.data.meta = {};
try {
if (shared.init.options.meta){
$.extend(shared.data.meta,
shared.init.options.meta);
}
} catch(e){}; // do nothing if the fields do not exist
var parseAjaxReply = function(ajaxData){
shared.data = (
(typeof ajaxData === 'text') &&
(shared.init.options.parseCSV)
) ? {rows: shared.parseCSV(ajaxData), meta:{}} : shared.init.options.extractData(ajaxData);
shared.nextTask();
}
if (shared.isLocal){
shared.data = localFetch(shared.init.csvName);
} else if (shared.init.data){
shared.data.rows = shared.init.data;
} else if (shared.init.csvString){
shared.data = shared.parseCSV(shared.init.csvString);
delete shared.init.csvString;
} else if (shared.init.getURL){
return $.get(
shared.init.getURL,
'',
parseAjaxReply
);
} else if (shared.init.specialName){
(function(){
var special = shared.init.specialName;
var dict = shared.specialNames[special];
var func = shared[dict[0]];
var i,l;
var actualArgs=[];
for(i=1,l=dict.length;i0) &&
(newrows[0].length>0) &&
shared.data &&
shared.data.rows){
Array.prototype.push.apply(shared.data.rows, newrows);
if (isLocalCSV){
localCreate(shared.data.rows, shared.data.meta);
} else {
shared.ajaxMapper('push',shared.init.csvName, newrows);
}
}
}
function call(){
var shared = this;
var remainingActions = shared.todo.length;
var args = Array.prototype.slice.call(arguments);
var func = args.shift();
var flag = func.apply(shared, args);
// allow for safely automatically calling nextTask()
// also allow experts to explicitly call nextTask()
// and allow experts to explicitly stop or defer
if (typeof flag === 'undefined'){
if (shared.todo.length === remainingActions){
return shared.nextTask();
}
} else if ( (flag ==='stop') || (flag === 'defer') ) {
return 0;
} else {
// any other return is an error
throw 'CSV: call() user function returned something other than "stop" or "defer"';
}
}
function hslice(arg1,arg2){
var shared = this;
var header = shared.data.rows[0].slice(0), copy=[], include=false;
var data = shared.data;
var row = [];
var tests = [];
var t = null;
var i,l,j,k;
if (typeof arg1 === 'number'){
data.rows = data.rows.slice(arg1,arg2);
data.rows.unshift(header);
return shared.nextTask();
}
if ((typeof arg1 === 'object') && (arg1.length===header.length)){
for(j=0,k=arg1.length;j=0) && (arg1[j].length===2)){
if (typeof arg1[j][0] === 'number'){
tests.push([k,1,arg1[j][0]]);
}
if (typeof arg1[j][1] === 'number'){
tests.push([k,-1,arg1[j][1]]);
}
}
}
}
} else throw "CSV: hslice incorrect args";
// compiled tests, now run
copy[0]=header;
k=tests.length;
for(i=1,l=data.rows.length;i= t[2]) ) ||
( (t[1]===-1) && (row[t[0]] <= t[2]) )
);
}
if (include) copy.push(row);
}
data.rows = copy;
if (copy.length === 1) console.log("CSV: hslice WARNING empty data -- supplied filters too strong, eliminated all row data");
return shared.nextTask();
}
function table(divId, tableMakerOpt, b, e){
var shared = this;
var div;
var rows = shared.data.rows.slice((b || 0),e);
if ((tableMakerOpt === null) || (typeof tableMakerOpt === 'undefined')) tableMakerOpt = {};
// if we need header data from row 0, make sure it is there
if (tableMakerOpt.header && (b>0)) rows.unshift(shared.data.rows[0]);
if (divId.indexOf('#')===0) divId=divId.substr(1); // strip #
div = $('#'+divId);
if (div.length>0){
div.html(makeTable(rows, tableMakerOpt));
} else {
$(document.body).append(['
',
makeTable(rows, tableMakerOpt),
'
'
].join(''));
}
if (tableMakerOpt && tableMakerOpt.dontCallNextTask) return true;
return shared.nextTask();
}
function editor(divId, header, b, e, precall, onCell){
var shared = this;
var opt = {};
var brow = b || 0;
var stamp = 1 * new Date();
var isDone = false;
var doneId='editorDone'+stamp;
var weCreatedThisDiv = ($('#'+divId).length===0);
function onCellChange(){
var col = 1*$(this).data("col");
var row = 1*$(this).data("row");
var val = $(this).val();
val = (isNaN(1.0*val))? val: (1.0*val);
if (typeof onCell === 'function'){
onCell(row,col,val,shared.data);
} else {
if ( (row < shared.data.rows.length) &&
(col < shared.data.rows[row].length)
) shared.data.rows[row][col] = val;
}
}
function onDone(){
if (!isDone){
isDone = true;
$('.'+opt.iclass).off('change',onCellChange);
if (weCreatedThisDiv){
$('#'+divId).remove();
} else {
$('#'+divId).html("");
}
return shared.nextTask();
}
}
if (header) opt.header = true;
opt.itype = 'text';
opt.isize = 6;
opt.iclass = 'editor'+stamp;
opt.doneButtonText = 'Done';
opt.cell = function(i,j,v){
return '';
};
opt.dontCallNextTask = true;
if (typeof precall === 'function'){
precall(opt);
}
table.apply(shared, [divId,opt,brow,e]);
$('#'+divId).append('');
$('#'+doneId).click(onDone);
$('.'+opt.iclass).on('change',onCellChange);
}
function pairs(pspec){
var shared = this;
var rows = shared.data.rows;
var i,l,output = [];
if ((typeof pspec !== 'object') || (pspec.length!==2))
throw "CSV: pairs pairspec must have length 2, got:"+pspec.length;
if ((typeof pspec[0] === 'number') &&
(typeof pspec[1] === 'number')){
for(i=0,l=rows.length; i');
}
$('#'+plotName).html(""); // clear div contents
plots[plotName] = $.jqplot(plotName, plotPairs.map(function(p){ return pairs.apply(shared,[p])}), plotOptions);
}
if (typeof after === 'function'){
after(plots);
}
return shared.nextTask();
}
function appendCol(colName, colOrFunc, rowprops){
var i,j,k,l;
var shared = this;
var rows = shared.data.rows;
var header = [];
var newc;
var colData = [];
var h=0;
if (typeof colName === 'string'){
header = rows[0].slice(0);
h = 1;
}
newc = (header.length)?
(header.length) :
( Math.max.apply(null, rows.each(function(r){ return r.length; })) );
var rowobj = {};
var row = [];
if (typeof colOrFunc === 'object'){
if (colOrFunc.length===(rows.length-h)){
colData = colOrFunc.slice(0);
} else if (rowprops === 'strict'){
throw "CSV: addCol new columnn data length "+colOrFunc.length+" needed "+(rows.length-h);
} else {
for(i=0,l=(rows.length-h); i0)){
if (arg1.indexOf("\n") >= 0){
// arg1 is CSV data
shared.init.csvString = arg1;
} else if ((arg1.indexOf("/")===0) ||
(arg1.indexOf("http://")==0) ||
(arg1.indexOf("https://")==0) ) {
shared.init.getURL = arg1;
} else if ( arg1.indexOf("/")>0 ){
// arg1 is a csvName
shared.init.csvName = arg1;
} else if (
(typeof shared.specialNames === 'object') &&
( arg1 in shared.specialNames ) &&
(shared.specialNames.hasOwnProperty(arg1))
){
shared.init.specialName = arg1;
} else {
// maybe it is a jQuery selector
shared.init.jqName = arg1;
}
} else if (typeof arg1 === "function"){
shared.init.fetcher = arg1;
} else if (typeof arg1 === "object" && (arg1.length>0)){
shared.init.data = arg1;
} else if (typeof arg1 === "object"){
if (typeof arg1.url === "string"){
shared.init.ajax = arg1;
} else throw "CSV: begin unknown parameter object arg1 "+JSON.stringify(arg1);
} else if (typeof arg1 === 'number'){
shared.init.fill = arg1;
} else throw "CSV: unknown parameter arg1 "+JSON.stringify(arg1);
shared.isLocal = (shared.init.csvName) && (
(shared.init.csvName.indexOf('local')===0) || (shared.init.csvName.indexOf('session')===0) );
shared.cando = Object.keys(csvFuncs);
shared.candone = [
'finalize', finalize,
'go', go
];
shared.todo = [{f: fetch, a: null}];
function finalize(func, taskms){
if (taskms){ shared.taskDelay = taskms }
if (typeof func === 'function') shared.finalCallback = func;
// we can't return nextTask directly, because someone might
// add parmeters and that would cause a malfunction
// making sure repeated calls are ignored requires
// having some flags, and then resetting things when the
// workflow completes so that it is "armed" again
var armed = true;
var replay = shared.todo.slice(0);
var count = 0;
return function(){
if (shared.todo === 0){
delete shared.data;
shared.todo = replay.slice(0);
armed = true;
}
if (armed) {
armed = false;
shared.nextTask();
++count;
return count;
}
console.log("CSV: ignored call to finalize() while previous call in progress");
return null;
}
}
function go(func, taskms){
(finalize(func, taskms))();
// starts list of chained tasks executing
// go signifies the end of a request -->
// so do not return methods to chain more requests
}
return planner(shared.cando, shared.candone, shared.todo);
}
return CSVRETURN;
})();
/* Quadro Design - JQuery Interfaces
From eCore 0.4, Ender 2007 */
var isDev;
var QuadroUI = window.QuadroUI || {};
// Generic Ready Function
QuadroUI.init = function() {
if ($("body").data("quadroInitDone") === 1)
return;
$("body").data("quadroInitDone", 1);
isDev = false;
if ($("#isDev").val() == "yes") isDev = true;
// Setup Calendar
// Edit Demo - http://paulthedutchman.nl/calendar_standalone/
/* $('.calendarDiv').fullCalendar({
theme: true, editable: true,
header: {left: 'prev,today',center: 'month,agendaWeek,agendaDay', right:'next'},
buttonIcons: false, aspectRatio: 2,
// add event name to title attribute on mouseover
eventMouseover: function(event, jsEvent, view) {if (view.name !== 'agendaDay') {$(jsEvent.target).attr('title', event.title);}}
});
*/
stopclock(); showtime(); // Start on-page clock
tablesorterFixups(); // Register parser and fixups for TableSorter plugin
$('textarea').autogrow(); // Auto-grow textareas
// Add 'Back' buttons to page
$(document).on('click', 'input[name=Back]', function() {history.go(-1); return false;});
setupSortableTables(document);
// Augment UI Components using various UI Augmentations
setupEnhancedUI();
// Sticky sidebar to page
if (getOption('sticky_nav') == "on")
$("#sidebar").attr("style", "position: fixed; right: 5px; margin: 0px; top: 100px; z-index: 10;");
// tableCollapse();
// tabMGR - Active tab persistence across page loads
// if ($("#busy1"))
// $("#busy1").activity().show();
setupTabMgr();
// Disable click event on masked objects
$(".disableClick").addClass("disableMask");
$(".disableClick").on('mousedown', function (e) {e.stopImmediatePropagation(); e.stopPropagation(); return false;});
// Start any WYWISYG editors on-page (fckEditor)
if ($.fck && $.fck.start)
$.fck.start({path: '/intranet/PHP/fckEditBase/', ToolbarSet: "Custom", Config: {ToolbarSet: "Custom"}});
};
$(document).ready(function() {
QuadroUI.init();
});
function buildTableSorterOptions() {
var tableOptions = {widthFixed: true, sortList: [[0,1]], dateFormat: "ddmmyyyy", cancelSelection: false,
widgets: ['editable', 'zebra'],
headerTemplate: '{content} {icon}',
onRenderHeader: function(index){
if ($(this).find('div').text().length > 2 && !$(this).hasClass('noCorners')) {
// console.log("Adding roundedConers to " + index + " (class check: " + $(this).hasClass('noCorners') + ")");
$(this).find('div').addClass('roundedCorners header' + index );
} else {
// console.log("Stripping roundedConers from " + index + " (class check: " + $(this).hasClass('noCorners') + ")");
$(this).find('div').removeClass('roundedCorners');
}
}};
if (getOption('sticky_headers') == "on") {
tableOptions["widgets"] = ['editable', 'zebra', 'stickyHeaders'];
tableOptions["widgetOptions"] = {
stickyHeaders_offset : '#mainheader',
stickyHeaders_zIndex : 9
};
}
return tableOptions;
}
function setupSortableTables(context) {
var tableOptions = buildTableSorterOptions();
var $context = $(context || document);
var $sortTables = $context.find(".sortTable").add($context.filter(".sortTable")).not(".containsStickyHeaders").not(".quadro-tablesorter-ready");
var $sortTables2 = $context.find(".sortTable2").add($context.filter(".sortTable2")).not(".containsStickyHeaders").not(".quadro-tablesorter-ready");
$sortTables.tablesorter(tableOptions).addClass("quadro-tablesorter-ready");
$sortTables2.tablesorter(tableOptions).addClass("quadro-tablesorter-ready");
if (getOption('sticky_headers') == "on") {
fixStickyHeaderCloneWidth();
setTimeout(fixStickyHeaderCloneWidth, 0);
$(window).off('scroll.quadroStickyHeaders resize.quadroStickyHeaders').on('scroll.quadroStickyHeaders resize.quadroStickyHeaders', fixStickyHeaderCloneWidth);
}
if ($sortTables.length > 0)
tableAddExport($sortTables);
}
/////////////////////////////////////////////////////////////////////////////////////
//
// applyStyles() - Applies CSS styling to page elements
function applyStyles() {
// Style Divs and Tables w/ corners [2012 Method]
$('.navTable').addClass('rounded');
$('.navTable2').addClass('rounded').addClass('orange');
$('.navTableThin').addClass('rounded').addClass('green');
// Style Form Elements with standard schema
$('input[type=text], textarea, select, input').addClass('element');
}
function setupEnhancedUI() {
// Augment any form inputs using various enhancements
// Switch all select boxes to use quadroSelect.
// Supports two filtermodes - 'filter', and 'cycle'
if (getOption('omnibox_all') == "on") {
console.log("[setupEnhancedUI] Omnibox Replacement (All) is Enabled");
$('select').quadroSelect({filterMode: 'filter'});
} else if (getOption("omnibox_ts") == "on") {
console.log("[setupEnhancedUI] Omnibox Replacement (Timesheet Only) is Enabled");
$('.omnibox').quadroSelect({filterMode: 'filter'});
}
// Setup textExt plugin
setupAutocomplete(); // Register textExt autocomplete on Job and Client text entry fields
// Reapply Styling
applyStyles();
}
// TabMGR - Tab Manager
// Handles persisting current tab between reloads, and selecting initial tab based on the request URL...
var $tabs;
var skipTabMgr = false; // Don't run the TabMgr setup loop - magic flag :)
var tabCaching = true; // Default to use tab content cachhing
function setTabCache(cacheMode) {
try {
tabCaching = cacheMode;
$tabs.tabs('option', 'cache', cacheMode);
} catch(e) {
console.log("[setTabCache] Exception Caught: " + e);
}
}
var globalRun = 0;
function setupTabMgr() {
var dataStore = window.sessionStorage || {getItem: function() {return 0;}, setItem: function() {}};
globalRun = globalRun + 1;
try {
$.ajaxSetup({'cache':true}); // Enable AJAX caching
// If the 'tab-active' class is assigned to a tab, then we've already ran TabMGR!
// Something is trying to run me again... abort, because that's a slippery slope!
var selectedTab = $(".tab-active").index();
if (selectedTab > -1 || skipTabMgr) {
console.log("[TabMGR] Ignoring attempt to run TabMGR twice [selectedTab: " + selectedTab + "]");
return;
}
console.log("TabMgr Runs: " + globalRun);
// Snip the significant bits from request URL relative to /jobs/{job}/ARGUMENTS.
// The site can live under different roots, so avoid fixed-depth path indexes here.
var pathParts = window.location.pathname.split('/');
var jobsIndex = $.inArray('jobs', pathParts);
var routeParts = (jobsIndex > -1) ? pathParts.slice(jobsIndex + 2) : pathParts.slice(4);
var indexKey = routeParts.slice(0, 2).join("/");
var currentIndex = dataStore.getItem(indexKey);
var requestedIndex = -1;
// Try to find a tab matching request URI to activate..
// This restores the ability to jump straight into (for example) /jobs/0009/drawings via a bookmark etc
if (routeParts.length > 0 && routeParts[0].length > 0) {
$('#tabs .ui-tabs-nav a').each(function() {
var tabURL = $(this).attr('href');
var tabLink = document.createElement('a');
tabLink.href = tabURL;
var tabPathParts = tabLink.pathname.split('/');
var tabJobsIndex = $.inArray('jobs', tabPathParts);
var tabRouteParts = (tabJobsIndex > -1) ? tabPathParts.slice(tabJobsIndex + 2) : tabPathParts.slice(4);
if (tabRouteParts[0] == routeParts[0]) {
requestedIndex = $(this).parent().index();
}
});
}
if (requestedIndex > -1)
currentIndex = requestedIndex;
// Sanity-check... if currentIndex exceeds sensible bounds, reset to first tab!
if (currentIndex < 1 || currentIndex > 15) currentIndex = 0;
// Initialize jQuery UI Tabs
$tabs = $( "#tabs" ).tabs({
active: currentIndex, // Set activate tab (to tab number currentIndex)
beforeLoad: function(event, ui) {
if (tabCaching) ui.ajaxSettings.cache = true;
if (tabCaching && // If caching is enabled...
ui.tab.data("cache.tabs")) { // and tab already has loaded content
// console.log('TabMgr - Preventing click (tabCaching: ' + tabCaching + ', contents: ' +ui.tab.data("cache.tabs") + ')');
event.preventDefault(); // abort re-loading!
return;
}
ui.panel.html('
Loading...
');
},
load: function(event, ui) { // Tab Loaded - Hide progress throbber
event.preventDefault();
// debugger;
var tabID = ui.panel.id;
setupSortableTables(ui.panel);
return tabCaching;
},
activate: function(event, ui) { // Tab Activated - Save new tab index into persistant storage
var newIndex = ui.newTab.parent().children().index(ui.newTab);
dataStore.setItem( indexKey, newIndex );
$(this).addClass("tab-active");
},
cache: tabCaching, // Set initial cache state based on tabCaching flag
spinner: false, // Don't use in-title spinner, we'll roll our own...
});
} catch(e) {
console.log("[tabMgr] Exception Caught: " + e);
}
setTabCache(true);
}
function reloadTab() {
var forceFullReload = true; // The tab-only reloader isn't quite working yet,
// .. so default to the backup full-page reload for now
try {
if (!$tabs || $tabs == undefined) {
console.log("reloadTabs() - Skipping as $tabs is undefined");
return false;
}
// forceFullReload - Force a full page reload, if this flag is set..
if (forceFullReload) {
console.log("reloadTab() called... using temporary full-reload hack!");
window.location.reload();
return;
}
/// Otherwise, try and reload just the tab contents, not the full papge.
/// Still somewhat experimental, hence the above flag for a full-reload alternative..
console.log("reloadTab() called... using experimental tab-only reload method!");
skipTabMgr = true;
setTabCache(false);
var current_index = $tabs.tabs("option","active");
var currentURL = $(".ui-state-active a").prop('href');
$tabs.tabs('load', current_index, currentURL + "&reload=123");
console.log("reloadTab() - Reloading tab #" + current_index + " from " + currentURL);
} catch(e) {
console.log("[reloadTab()] Exception Caught: " + e);
}
}
function ajaxEdit_selectTemplate(myObj, calledFunc, templateVar, selectId) {
var templateHtml = window[templateVar];
if (typeof templateHtml == 'undefined') {
console.log("Editor: Sorry, couldn't find " + templateVar + " variable");
return false;
}
if (calledFunc == "focus") {
if ($(myObj).data("inEdit") == 1)
return;
var originalText = $(myObj).text();
$(myObj).attr('contenteditable', 'false');
$(myObj).data("inEdit", 1);
$(myObj).html(templateHtml);
$(selectId).off('focusout.ajaxEdit').on('focusout.ajaxEdit', function() {
$(this).parent().data("inEdit", 0);
$(this).parent().blur();
});
$(selectId + ' option:contains("' + originalText + '")').prop('selected', true);
$(selectId).focus();
} else if (calledFunc == "blur") {
if ($(myObj).data("inEdit") == undefined)
return false;
var val = $(selectId).find(":selected").text();
$(myObj).html(val);
$(myObj).attr('contenteditable', 'true');
}
}
function ajaxEdit_codeList(myObj, calledFunc) {
return ajaxEdit_selectTemplate(myObj, calledFunc, 'codeListTemplate', '#jobCode');
}
function ajaxEdit_jobList(myObj, calledFunc) {
return ajaxEdit_selectTemplate(myObj, calledFunc, 'jobListTemplate', '#jobID');
}
function ajaxEdit_userList(myObj, calledFunc) {
return ajaxEdit_selectTemplate(myObj, calledFunc, 'userListTemplate', '#userID');
}
// Make content editable, and bind AJAX callback handler
function ajaxEdit_Setup(selector, editMode) {
$(selector).each(function() {
var initial = {};
var name = $(this).attr("id");
// The first time an Editable gets focus, save the initial value :)
$(this).bind('focus', function() {
if(!(name in initial)) {initial[name] = $(this).text()}
if ($(this).data("editor")) {
try {window['ajaxEdit_' + $(this).data("editor")](this, "focus");} catch(e) {}
}
});
// AJAX Edit Handler
$(this).bind('blur', function(e) {
if ($(this).data("inEdit") == 1) // If in edit mode, don't acknowledge children stealing focus
return;
if ($(this).data("editor")) // Call custom editor hooks
try {window['ajaxEdit_' + $(this).data("editor")](this, "blur");} catch(e) {}
if (initial[name] == $(this).text()) return;
var myObj = $(this);
window.OXAjax.request(editMode, [], {
method: 'GET',
data: {index: $(this).attr("id"), value: $(this).text(), pair: $(this).data("pair")}
}).done(function(data) {
if (data.success) { // Update succeeded
// Update table cells with server pushed data (.data, .css or plain HTML)
if (data.updates)
while(x=data.updates.pop()) {
if (typeof x.data != 'undefined')
$("[id='" +x.name + "']").data(x.data, x.value);
else if (typeof x.css != 'undefined')
$("[id='" +x.name + "']").css(x.css, x.value);
else if (typeof x.value != 'undefined')
$("[id='" +x.name + "']").html(x.value);
}
initial[name] = $(myObj).text();
$('#ajaxNote').css('background-color', 'green');
} else { // Update failed - Revert Change
$(myObj).text(initial[name]);
$('#ajaxNote').css('background-color', 'red');
}
if (data.text)
$('#ajaxNote').html(data.text).fadeIn(1000).delay(900).fadeOut(2000);
}).fail(function() {
$(myObj).text(initial[name]);
$('#ajaxNote').css('background-color', 'red').html('Inline edit failed').fadeIn(1000).delay(900).fadeOut(2000);
});
});
});
}
// setupAutocomplete - Initialize textExt jQuery plugin for autocomplete on Job and Client text fields
function setupAutocomplete() {
var config = {
plugins: 'autocomplete arrow prompt filter ajax',
pluginsTags: 'ajax autocomplete arrow prompt tags',
url : '/intranet/PHP/autocomplete.php?',
urlTags : '/intranet/PHP/autocompleteTags.php?',
autocomplete: {dropdownPosition: 'below'},
ext: {
itemManager: { // Custom Item-Manager for case-insensitive key/value responses
getItem: function(item) {console.log("getItem"); return item;},
stringToItem: function(str) {console.log("stringToItem"); return { name: str };},
itemToString: function(item) {console.log("Item to string:"); console.log(item.name); return item.name;},
itemContains: function(item, needle) {return item.name.toLowerCase().indexOf(needle.toLowerCase()) >= 0;}
},
autocomplete: {
selectFromDropdown: function() {
var self = this;
var suggestion = self.selectedSuggestionElement().data("text-suggestion");
if(suggestion) {
var item = self.itemManager().itemToString(suggestion);
self.val(item);
self.core().hiddenInput().val(item);
}
self.trigger("hideDropdown");
}
}
}
};
function TextExtPatch_QTrak() {};
$.fn.textext.TextExtPatch_QTrak = TextExtPatch_QTrak;
// $.fn.textext.addPatch('qtrak',TextExtPatch_QTrak);
var p = TextExtPatch_QTrak.prototype;
p.init = function(core) {var self = this; console.log("Init"); core.on({ getFormData : self.onGetFormData });};
p.onGetFormData = function(e, data) {
var val = this.input().val();
console.log("onGetFormData = " + val);
data[100] = {"input": val, "form" : val + "MODMOD"};
};
var baseURL = config.url + "&mode=";
var baseURLTags = config.urlTags + "&mode=";
function setupAutoInput(selector, mode, prompt, fullMode) {
$(selector).each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
var autoURL = baseURL + mode;
if (fullMode) autoURL = autoURL + "&full";
if ($(this).hasClass("auto-inactive")) autoURL = autoURL + "&inactive=1";
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: prompt, ajax: {url: autoURL, dataType: 'json', cacheResult: !fullMode, prefixCache: !fullMode}});
if (fullMode) {
$(this).textext()[0].ajax().load(mode + "-full");
$(this).textext()[0].autocomplete().showDropdown();
$(this).textext()[0].autocomplete().hideDropdown();
}
});
}
// [tags-]: Setup textext instances for tag-based auto-complete
$('.auto-tags').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
if ($(this).attr('val').length > 1)
var startTags = (new Function("return " + $(this).attr('val') + ""))() // Convert string to JS object
else
var startTags = (new Function("return [" + $(this).attr('val') + "]"))() // Convert string to JS object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.pluginsTags, tagsItems: startTags,
prompt: "Enter tags seperated\nby Comma or Enter", ajax: {url: baseURLTags + "client", dataType: 'json', cacheResult: true, prefixCache: false}});
// Hide prompt if we pre-populated with tags..
if (startTags.length > 1) $(this).textext()[0].prompt().hidePrompt();
});
// [auto-]: Setup textext instances for auto-completing input fields (on-demand)
setupAutoInput('.auto-client', 'client', 'Find client..', false);
setupAutoInput('.auto-user', 'user', 'Find user..', false);
setupAutoInput('.auto-clientuser', 'clientuser', 'Client logins..', false);
setupAutoInput('.auto-job', 'job', 'Find job..', false);
// [-full]: preload full datasets for dropdown-like behavior
setupAutoInput('.auto-job-full', 'job', 'Find job..', true);
setupAutoInput('.auto-client-full', 'client', 'Find client..', true);
setupAutoInput('.auto-user-full', 'user', 'Find user..', true);
setupAutoInput('.auto-clientuser-full', 'clientuser', 'Client login..', true);
}
// replaceSpanText() = Replace any text nodes of $elemnt with $text, but leave other child DOM elements alone
function replaceSpanText(element, text) {
var textNode = element.contents().filter(function() {return this.nodeType == 3;});
textNode.replaceWith(text);
}
// Display a confirmation dialog before spawning popup..
function confirmPopup($message, $actionPath, $actionTitle) {
if (!confirm($message))
return false;
qt_popup($actionPath, $actionTitle);
}
// setReloadReal() - Actually trigger the page reload. Do not call directly, use the setReload() function below!!
function setReloadReal(close, newUrl) {
window.parent.reload = 1;
if (typeof(close) != "undefined") {
window.parent.qt_popup('');
}
// If the optional 'newUrl' argument was given, change the URL instead of reloading in-place
if (typeof(newUrl) != "undefined" && newUrl != "")
window.parent.location.href = newUrl;
else
window.parent.location.reload();
}
// setReload() - Schedule a reload of the main page, eg. for when data has been changed by a popup.
// 'close' flag = Close popup, 'timer' = # of seconds till reload triggers
// 'newUrl' is optional, if supplied will change the page URL (if omitted, will just reload in-place)
function setReload(close, timer, newUrl) {
var runReload = function() {
setReloadReal(close, newUrl);
};
if (timer < 1) // Trigger immediately
runReload();
else
setTimeout(runReload, timer * 1000);
}
// qt_popup(url, title) = Show AJAX Q-Trak popup dialog
var reload = 0;
function qt_popup(url, title, args) {
if (typeof(args) == 'undefined') args = {};
if (url == "") // Close
return $("#dialog-fullcalendar").dialog("close");
if (typeof(postExternal) == 'undefined') {
postExternal = {};
}
$("#dialog-fullcalendar").dialog({modal: true, title: title, width: "80%",
position: ['middle', 150], // Aligns horizontally in middle, 20px from top
create: function(event, ui) {$(this).parents(".ui-dialog").css("border","2px solid #00b3c6");},
close: function (e, ui) {
$(this).hide();
if(reload == 1)
window.parent.location.reload();
reload = 0;
}});
if (typeof(args["height"]) != 'undefined')
$("#dialog-fullcalendar").css("height", args["height"]);
$('html', $('#dialog-fullCalIFrame').contents()).html("Loading..");
// Build postData for optional posting
var postData = {};
if (typeof(args["postData"]) != 'undefined')
postData = args["postData"];
postData['popup'] = true;
$.post(url, postData, function(data) {
if (data == null) {
console.log("Quadro AJAX Poster is working with a null 'data' field");
data = "";
}
data = data.replace("", "");
data = data.replace("{{URL}}", url);
// Because jQuery filters script tags when accessing iFrames using the .contents()
// built-in, we inject the code the oldest way possible... good ol DOM.writeln()!
var iframe = $('iframe#dialog-fullCalIFrame').get(0);
var iframedoc = iframe.document;
if (iframe.contentDocument) iframedoc = iframe.contentDocument; else if (iframe.contentWindow) iframedoc = iframe.contentWindow.document;
if (!iframedoc) {console.log("qt_popup: Couldn't write iFrame contents!"); return;}
iframedoc.open(); iframedoc.writeln(data); iframedoc.close();
});
return;
}
// progressBar($percent, $element) - Draw Progress Bar.
// $percent is between -1 and 100 (-1 = Hide Element)
// $element = jQuery Selector
function progressBar(percent, element, fuzz) {
if (typeof progressBar.Visible == 'undefined') {progressBar.Visible = new Array(); progressBar.LastValue = new Array();}
// Init Progress Bar
if (typeof progressBar.Visible[element] == 'undefined') {
$(element).html("--");
progressBar.Visible[element] = $(element).is(":visible");
progressBar.LastValue[element] = percent;
replaceSpanText($(element).find('div').parent(), "N/A");
}
// Bail if value hasn't changed (stupid wasteful callback! :)
if (progressBar.LastValue[element] == percent) {return;}
progressBar.LastValue[element] = percent;
var barElement = $(element).find('div');
var barWidth = (percent * $(element).width() / 100); // Original
// 'Magic Number' - Hide bar if $percent < 0
if (percent < 0) {progressBar.Visible[element] = 0; return $(element).hide();}
// Otherwise, unhide progress bar (if hidden) & animate!
if (!progressBar.Visible[element]) {
$(element).show();
progressBar.Visible[element] = 1;
}
barElement.width(barWidth);
replaceSpanText(barElement.parent(), percent + "%");
}
// tablesorterFixups() - Custom parsers/hooks and fixups for TableSorter plugin
// Trying to obsolete these!
function tablesorterFixups() {
// Fix TableSorter currency handler
$.tablesorter.addParser({
id: "ecurrency",
is: function(s) {
return /^[£$€?.]/.test(s);
},
format: function(s) {
return $.tablesorter.formatFloat(s.replace(new RegExp(/[^\-0-9.]/g),""));
},
type: "Numeric"
});
// Fix AlphaNum Pair
$.tablesorter.addParser({
id: "firstnum",
is: function(s) {
return false;
},
format: function(s) {
var firstnum = parseInt(s.replace(/(^.?)(\d.?\w)(.+$)/i,'$2'));
if (isNaN(firstnum))
firstnum = 0;
return $.tablesorter.formatFloat(firstnum);
},
type: "numeric"
});
// TableSorter: Handle mangled dates (eg, w/ extra text or HTML embedded)
// Used by, for example, the Tasks List
$.tablesorter.addParser({
id: "extraDate",
is: function(s) {
return /\d{1,2}[\/-]\d{1,2}[\/-]\d{2,4}/.test(s);
},
format: function(s,table) {
var c = table.config;
s = s.replace(new RegExp(/-/g),"/");
s = s.replace(/<.*?>/g,"");
s = s.replace(new RegExp(/(\d{1,2})[\/-](\d{1,2})[\/-](\d{4})(.*)/), "$3/$2/$1");
return $.tablesorter.formatFloat(new Date(s).getTime());
},
type: "numeric"
});
}
function fixStickyHeaderCloneWidth() {
$('table.containsStickyHeaders').each(function() {
var stickyTable = this;
var $stickyTable = $(stickyTable);
var $table = $stickyTable.prevAll('table.hasStickyHeaders:first');
if (!$table.length)
return;
var tableWidth = Math.round($table.outerWidth()) + 'px';
stickyTable.style.setProperty('width', tableWidth, 'important');
stickyTable.style.setProperty('min-width', tableWidth, 'important');
stickyTable.style.setProperty('max-width', tableWidth, 'important');
var $sourceCells = $table.children('thead:first').children('tr:not(.sticky-false)').children(':visible');
var $stickyCells = $stickyTable.children('thead:first').children('tr:not(.sticky-false)').children(':visible');
$stickyCells.each(function(index) {
var sourceCell = $sourceCells.eq(index)[0];
if (!sourceCell)
return;
var cellWidth = Math.round($(sourceCell).outerWidth()) + 'px';
this.style.setProperty('box-sizing', 'border-box', 'important');
this.style.setProperty('width', cellWidth, 'important');
this.style.setProperty('min-width', cellWidth, 'important');
this.style.setProperty('max-width', cellWidth, 'important');
var sourceInner = $(sourceCell).find('.tablesorter-header-inner')[0];
var stickyInner = $(this).find('.tablesorter-header-inner')[0];
if (sourceInner && stickyInner) {
var innerWidth = Math.round($(sourceInner).outerWidth()) + 'px';
stickyInner.style.setProperty('width', innerWidth, 'important');
stickyInner.style.setProperty('max-width', innerWidth, 'important');
}
});
});
}
// Register 'Export to Excel' (sic) button in tableSorter headers
function tableAddExport(tID) {
var dlType
var tableParent = (typeof tID == "string") ? tID : ".sortTable";
var $tables = (typeof tID == "string") ? $(tID + ":visible") : $(tID).filter(":visible");
$tables = $tables.not(".containsStickyHeaders").not(".noExport").not(".quadro-export-ready");
if ($tables.length < 1)
return;
var buttonHTML = " ";
buttonHTML = buttonHTML + "
";
else
buttonHTML = buttonHTML + "XLS";
$tables.before(buttonHTML).addClass("quadro-export-ready");
// For each newly created button, iterate and store the linked table ID in a custom attr - tableParent
$(".printBtn, .tableExport").each(function(index) {
var RealParent = $(this).parent().parent().nextAll(".sortTable");
$(this).attr("tableParent", $(RealParent).attr("id"));
});
function printCallback(doc) {
console.log("printCallback");
// doc.find("body *").css("background-image", "").css("background", "");
console.log("Area: " + doc);
}
$(".printBtn").off('click.quadroPrint').on('click.quadroPrint', function(e){
e.preventDefault();
// Locate parent table for this button
var myParent = $(this).parent().parent().nextAll(".sortTable").first();
$(myParent).printThis({removeInline: false, loadCSS: "/intranet/HTML/css/table-print.css", callback: printCallback });
return false; // Cancel click event.
});
$('.tableExport').off('click.quadroExport').on('click.quadroExport', function (e) {
e.preventDefault();
// Locate parent table for this button
var myParent = $(this).parent().parent().nextAll(".sortTable").first();
var filename = window.location.href.match(/intranet\/(.*)$/)[1].replace(/\//g, '_') + new Date().getTime();
// Mark this table for download
$("table").removeClass("csvDown");
$(myParent).addClass("csvDown");
// Call html5csv.js helper :D
if (getOption('table_exportCSV') == "on")
CSV.begin(".csvDown", {keepHTML: 0}).download(filename, 'csv').go();
else
CSV.begin(".csvDown", {keepHTML: 1}).download(filename, 'xls').go();
return false;
});
}
// no longer used? safe to remove?
function tableCollapse() {
$(".collapse").mousedown(function() {
var o = this;
var p = $(this);
while (!$(p).is("table")) {
p = $(p).parent();
}
t = $(p).parent();
$(".collapse",p).each(function(i) {
if (this == o) {
$("tr",p).find("td:eq(" + i + ")").css("display","none");
}
});
$(this).parent().css("display","none");
// $(p).siblings(".reset_collapse").html("