// 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 + "

" + "
"; buttonHTML = buttonHTML + "

" + "" + "
"; if (getOption('table_exportCSV') == "on") buttonHTML = buttonHTML + "CSV
"; else buttonHTML = buttonHTML + "XLS"; $(tID+":visible").not(".containsStickyHeaders").not(".noExport").before(buttonHTML) // 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").attr( "href", "javascript:void(0)").click(function(){ // Locate parent table for this button var myParent = $(event.target).parent().parent().nextAll(".sortTable"); $(myParent).printThis({removeInline: false, loadCSS: "/intranet/HTML/css/table-print.css", callback: printCallback }); return( false ); // Cancel click event. }); $('.tableExport').click(function (event) { // Locate parent table for this button var myParent = $(event.target).parent().parent().nextAll(".sortTable"); 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(); }); } // 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("

Show All

").find(".show_all_collapsed").click(function(){ $(".reset_collapse").html("* Unhide Column").find(".show_all_collapsed").click(function(){ $("th,td",p).css("display",""); $(this).parent().remove(); return false; }); return false; }); }; /////////////////////// // Common Library common = { // cleanly prints an array/object for the alert(). TODO; REMOVE -- ONLY FOR DEMO. dump: function (arr,level) { var dumped_text = ""; if(!level) level = 0; //The padding given at the beginning of the line. var level_padding = ""; for(var j=0;j \"" + value + "\"\n"; } } } else { //Stings/Chars/Numbers etc. dumped_text = "===>"+arr+"<===("+typeof(arr)+")"; } return dumped_text; }, sanitize: function(str) { if (typeof str != 'string') return ''; str = str.replace(/[^a-zA-Z 0-9]+/g,''); if (str.length > 10) str = str.substr(0,9); return str; }, serialize: function(o) { var a = []; o.find('input, textarea').each(function() { var n = this.name || this.id; var t = this.type; if ( (t == 'checkbox') && !this.checked ) return; a.push({name: n, value: this.value}); }).end(); return a; }, trim: function(str) { return str.replace(/^\s*|\s*$/g,""); }, hash: function(str) { var hash = 0; if (!str || (str.length == 0)) return hash; for (i = 0; i < str.length; i++) { char = str.charCodeAt(i); hash = ((hash<<5)-hash)+char; hash = hash & hash; // Convert to 32bit integer } return hash; } }; ////////////// // 'Classic' JavaScript // Combo Box from http://www.cs.tut.fi/~jkorpela/forms/combo.html function combo_last_choice(selection) {return selection.selectedIndex==selection.length - 1;} function combo_activate(field) {field.disabled=false; if(document.styleSheets)field.style.visibility = 'visible'; field.focus(); } function combo_process_choice(selection,textfield) { if(combo_last_choice(selection)) { combo_activate(textfield); } else { textfield.disabled = true; if(document.styleSheets)textfield.style.visibility = 'hidden'; textfield.value = ''; } } // Non-Jquery Clock var timerID = null; var timerRunning = false; function showtime () { if (!document.getElementById('face')) return; var now = new Date(); var hours = now.getHours(); var minutes = now.getMinutes(); var seconds = now.getSeconds() var timeValue = "" + ((hours >12) ? hours -12 :hours) if (timeValue == "0") timeValue = 12; timeValue += ((minutes < 10) ? ":0" : ":") + minutes; timeValue += ((seconds < 10) ? ":0" : ":") + seconds; timeValue += (hours >= 12) ? " P.M." : " A.M."; document.getElementById('face').innerHTML = timeValue; timerID = setTimeout("showtime()",1000); timerRunning = true; } function stopclock (){ if(timerRunning) clearTimeout(timerID); timerRunning = false; } function setJobDetail(detail, clientid, contactlist) { $("#jobDescription").val(detail); $("#client").val(clientid); $("#contactList").parent().html(contactlist); } function dumpObject(obj, maxDepth) { var dump = function(obj, name, depth, tab){ if (depth > maxDepth) { return name + ' - Max depth\n'; } if (typeof(obj) == 'object') { var child = null; var output = tab + name + '\n'; tab += '\t'; for(var item in obj){ child = obj[item]; if (typeof(child) == 'object') { output += dump(child, item, depth + 1, tab); } else { output += tab + item + ': ' + child + '\n'; } } } return output; } return dump(obj, '', 0, ''); } function ObjectDump(obj, name) { this.result = "[ " + name + " ]\n"; this.indent = 0; this.dumpLayer = function(obj) { this.indent += 2; for (var i in obj) { if(typeof(obj[i]) == "object") { this.result += "\n" + " ".substring(0,this.indent) + i + ": " + "\n"; this.dumpLayer(obj[i]); } else { this.result += " ".substring(0,this.indent) + i + ": " + obj[i] + "\n"; } } this.indent -= 2; } this.showResult = function() { var pre = document.createElement('pre'); pre.innerHTML = this.result; document.body.appendChild(pre); } this.dumpLayer(obj); this.showResult(); } // Fetch value of named Query String (optional URL to parse) function getParameterByName(name, url) { if (!url) url = window.location.href; name = name.replace(/[\[\]]/g, '\\$&'); var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), results = regex.exec(url); if (!results) return null; if (!results[2]) return ''; return decodeURIComponent(results[2].replace(/\+/g, ' ')); } // Animated scroll down to anchor href function scrollToAnchor(aid, offset = 0){ var aTag = $("a[name='"+ aid +"']"); $('html,body').animate({scrollTop: aTag.offset().top + offset},'slow'); } // jQuery 1.8 compatibility hacks jQuery.curCSS = function(element, prop, val) { return jQuery(element).css(prop, val); }; $.browser = {}; $.browser.msie = false;