// 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;
// Generic Ready Function
$(document).ready(function() {
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
$('input[name=Back]').click(function() {history.go(-1); return false;});
// Setup Sortable Tables
$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'];
$(".sortTable").tablesorter($tableOptions); // Create tableSorter instances
$(".sortTable2").tablesorter($tableOptions); // Create tableSorter instances
tableAddExport(".sortTable"); // Add 'Excel export' and 'Print This' button to tables
// Setup textExt plugin
setupAutocomplete(); // Register textExt autocomplete on Job and Client text entry fields
// Sticky sidebar to page
if (getOption('sticky_nav') == "on")
$("#sidebar").attr("style", "position: fixed; right: 5px; margin: 0px; top: 100px; z-index: 10;");
// Apply Styles/CSS to page elements
applyStyles();
// tableCollapse();
// tabMGR - Active tab persistence across page loads
// if ($("#busy1"))
// $("#busy1").activity().show();
setupTabMgr();
// Disable click event on masked objects
$(".disableClick").addClass("disableMask");
$(".disableClick").mousedown(function (e) {e.stopImmediatePropagation(); e.stopPropagation(); return false;})
// Start any WYWISYG editors on-page (fckEditor)
$.fck.start({path: '/intranet/PHP/fckEditBase/', ToolbarSet: "Custom", Config: {ToolbarSet: "Custom"}});
}
);
/////////////////////////////////////////////////////////////////////////////////////
//
// 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]').addClass('element');
$('textarea').addClass('element');
$('select').addClass('element');
$('input').addClass('element');
}
// 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;
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 most significant bits from request URL - assuming the format is /rootpath/module/id/ARGUMENTS
// where the ARGUMENTS remainder is what we want to match on..
var indexKey = window.location.pathname.split('/').slice(4,6).join("/");
var currentIndex = dataStore.getItem(indexKey);
// If no saved tab index, 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 (currentIndex < 1 && indexKey.length > 1) {
$('#tabs .ui-tabs-nav a').each(function() {
var tabURL = $(this).attr('href');
if (tabURL.indexOf(indexKey) !== -1) {
var newIndex = $(this).parent().index();
currentIndex = newIndex;
}
});
}
// 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;
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_codeList(myObj, calledFunc) {
if (typeof codeListTemplate == 'undefined') {
console.log("Editor: Sorry, couldn't find codeListTemplate Variable");
return false;
}
if (calledFunc == "focus") {
if ($(myObj).data("inEdit") == 1)
return;
var origcode = $(myObj).text();
$(myObj).attr('contenteditable', 'false');
$(myObj).data("inEdit", 1); // Prevent objects .blur() method from running
$(myObj).html(codeListTemplate);
// Re-enable .blur() and call it when select loses focus..
$("#jobCode").bind('focusout', function(e) {$(this).parent().data("inEdit", 0); $(this).parent().blur();});
$('#jobCode option:contains("' + origcode + '")').prop('selected', true);
$("#jobCode").focus();
} else if (calledFunc == "blur") {
if ($(myObj).data("inEdit") == undefined)
return false;
var val = $("#jobCode").find(":selected").text();
$(myObj).html(val);
$(myObj).attr('contenteditable', 'true');
}
}
function ajaxEdit_jobList(myObj, calledFunc) {
console.log("In joblist for " + calledFunc);
if (typeof jobListTemplate == 'undefined') {
console.log("Editor: Sorry, couldn't find jobListTemplate Variable");
return false;
}
if (calledFunc == "focus") {
if ($(myObj).data("inEdit") == 1)
return;
var origJob = $(myObj).text();
$(myObj).attr('contenteditable', 'false');
$(myObj).data("inEdit", 1); // Prevent objects .blur() method from running
console.log("In joblist for SET inEDIT");
$(myObj).html(jobListTemplate);
// Re-enable .blur() and call it when select loses focus..
$("#jobID").bind('focusout', function(e) {$(this).parent().data("inEdit", 0); $(this).parent().blur();});
console.log("In joblist for SELECTING " + origJob);
$('#jobID option:contains("' + origJob + '")').prop('selected', true);
$("#jobID").focus();
} else if (calledFunc == "blur") {
if ($(myObj).data("inEdit") == undefined)
return false;
console.log("In joblist for DOING BLUR and inEdit = " + $(myObj).data("inEdit") );
var val = $("#jobID").find(":selected").text()
$(myObj).html(val);
$(myObj).attr('contenteditable', 'true');
return;
}
}
function ajaxEdit_userList(myObj, calledFunc) {
if (typeof userListTemplate == 'undefined') {
console.log("Editor: Sorry, couldn't find userListTemplate Variable");
return false;
}
if (calledFunc == "focus") {
if ($(myObj).data("inEdit") == 1)
return;
var origuser = $(myObj).text();
$(myObj).attr('contenteditable', 'false');
$(myObj).data("inEdit", 1); // Prevent objects .blur() method from running
$(myObj).html(userListTemplate);
// Re-enable .blur() and call it when select loses focus..
$("#userID").bind('focusout', function(e) {$(this).parent().data("inEdit", 0); $(this).parent().blur();});
$('#userID option:contains("' + origuser + '")').prop('selected', true);
$("#userID").focus();
} else if (calledFunc == "blur") {
if ($(myObj).data("inEdit") == undefined)
return false;
var val = $("#userID").find(":selected").text()
$(myObj).html(val);
$(myObj).attr('contenteditable', 'true');
}
}
// 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);
$.getJSON(window.location.pathname, {index: $(this).attr("id"), value: $(this).text(), pair: $(this).data("pair"), ajax: editMode}, 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);
});
});
});
}
// 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=";
// [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 'Job', 'User' & 'Client' input fields (loads data on-demand, predictively)
$('.auto-client').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Find client..", ajax: {url: baseURL + "client", dataType: 'json', cacheResult: true}});
});
// -user: Intranet Users
$('.auto-user').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Find user..", ajax: {url: baseURL + "user", dataType: 'json', cacheResult: true}});
});
// -clientuser: Client Contacts w/ login rights
$('.auto-clientuser').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Client logins..", ajax: {url: baseURL + "clientuser", dataType: 'json', cacheResult: true}});
});
$('.auto-job').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
var autoURL = baseURL + "job";
if ($(this).hasClass("auto-inactive")) autoURL = autoURL + "&inactive=1";
console.log("Configging auto-job");
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Find job..", ajax: {url: autoURL, dataType: 'json', cacheResult: true}});
});
// [-full]: These varients are populated with the full dataset on-init, so as to function like normal dropdowns.
// We have to force the ajax load, and force the dropdown to populate by toggling show/hide.
$('.auto-job-full').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
var autoURL = baseURL + "job&full";
if ($(this).hasClass("auto-inactive")) autoURL = autoURL + "&inactive=1";
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Find job..", ajax: {url: autoURL, dataType: 'json', prefixCache: false}});
$(this).textext()[0].ajax().load("job-full");
$(this).textext()[0].autocomplete().showDropdown();
$(this).textext()[0].autocomplete().hideDropdown();
});
$('.auto-client-full').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Find client..", ajax: {url: baseURL + "client&full", dataType: 'json', prefixCache: false}});
$(this).textext()[0].ajax().load("client-full");
$(this).textext()[0].autocomplete().showDropdown();
$(this).textext()[0].autocomplete().hideDropdown();
});
$('.auto-user-full').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Find user..", ajax: {url: baseURL + "user&full", dataType: 'json', prefixCache: false}});
$(this).textext()[0].ajax().load("user-full");
$(this).textext()[0].autocomplete().showDropdown();
$(this).textext()[0].autocomplete().hideDropdown();
});
$('.auto-clientuser-full').each(function() {
if ($(this).textext()[0] !== undefined) return; // Prevent dual-instancing a single object
$(this).textext({ext: config.ext, autocomplete: config.autocomplete, plugins: config.plugins,
prompt: "Client login..", ajax: {url: baseURL + "clientuser&full", dataType: 'json', prefixCache: false}});
$(this).textext()[0].ajax().load("clientuser-full");
$(this).textext()[0].autocomplete().showDropdown();
$(this).textext()[0].autocomplete().hideDropdown();
});
}
// 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 (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) {
// Build a call to setReloadReal() using supplied arguments
reloadCmd = 'setReloadReal(' + close + ', "' + newUrl + '")';
if (timer < 1) // Trigger immediately
eval(reloadCmd);
else
setTimeout(reloadCmd, 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%",
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) {
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"
});
}
// Register 'Export to Excel' (sic) button in tableSorter headers
function tableAddExport(tID) {
var dlType
var buttonHTML = " ";
buttonHTML = buttonHTML + "