diff --git a/.idea/dictionaries/exodus4d.xml b/.idea/dictionaries/exodus4d.xml index 32c7157f..575932c2 100644 --- a/.idea/dictionaries/exodus4d.xml +++ b/.idea/dictionaries/exodus4d.xml @@ -18,6 +18,7 @@ mouseover nonblock pnotify + raphaël revalidate scrollbar scrollbars @@ -27,6 +28,7 @@ stargate tbody textarea + timelimit \ No newline at end of file diff --git a/fonts/FontAwesome.otf b/fonts/FontAwesome.otf deleted file mode 100644 index 81c9ad94..00000000 Binary files a/fonts/FontAwesome.otf and /dev/null differ diff --git a/fonts/fontawesome-webfont.eot b/fonts/fontawesome-webfont.eot index 84677bc0..33b2bb80 100644 Binary files a/fonts/fontawesome-webfont.eot and b/fonts/fontawesome-webfont.eot differ diff --git a/fonts/fontawesome-webfont.svg b/fonts/fontawesome-webfont.svg index d907b25a..1ee89d43 100644 --- a/fonts/fontawesome-webfont.svg +++ b/fonts/fontawesome-webfont.svg @@ -1,6 +1,6 @@ - + @@ -147,14 +147,14 @@ - + - + @@ -275,7 +275,7 @@ - + @@ -411,7 +411,7 @@ - + @@ -438,7 +438,7 @@ - + @@ -513,8 +513,53 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/fonts/fontawesome-webfont.ttf b/fonts/fontawesome-webfont.ttf index 96a3639c..ed9372f8 100644 Binary files a/fonts/fontawesome-webfont.ttf and b/fonts/fontawesome-webfont.ttf differ diff --git a/fonts/fontawesome-webfont.woff b/fonts/fontawesome-webfont.woff index 628b6a52..8b280b98 100644 Binary files a/fonts/fontawesome-webfont.woff and b/fonts/fontawesome-webfont.woff differ diff --git a/index.htm b/index.htm index 1bfeb06d..e60b912c 100644 --- a/index.htm +++ b/index.htm @@ -34,12 +34,13 @@ Pathfinder + - + diff --git a/js/app.js b/js/app.js index 8433b075..1c58439f 100644 --- a/js/app.js +++ b/js/app.js @@ -1,21 +1,23 @@ requirejs.config({ - baseUrl: 'build_js', // user build_js files, change to "js" for un-compressed source + baseUrl: 'js', // user build_js files, change to "js" for un-compressed source stubModules: ['text'], // Exclude these modules on build paths: { layout: 'layout', - jquery: 'lib/jquery-1.11.1.min', // v1.11.1 jQuery + jquery: 'lib/jquery-1.11.2.min', // v1.11.2 jQuery //jquery: "lib/jquery-2.1.1.min", // v2.1.1 jQuery bootstrap: 'lib/bootstrap.min', // v3.3.0 Bootstrap js code - http://getbootstrap.com/javascript/ text: 'lib/requirejs/text', // v2.0.12 A RequireJS/AMD loader plugin for loading text resources. - throttleDebounce: 'lib/jquery.ba-throttle-debounce.min', // v1.1 Handle/throttle jquery events - http://benalman.com/projects/jquery-throttle-debounce-plugin/ + mustache: 'lib/mustache.min', // v1.0.0 Javascript template engine - http://mustache.github.io/ velocity: 'lib/velocity.min', // v1.2.1 animation engine - http://julian.com/research/velocity/ + velocityUI: 'lib/velocity.ui.min', // v5.0.3 plugin for velocity - http://julian.com/research/velocity/#uiPack templates: '../templates', // template dir slidebars: 'lib/slidebars', // v0.10 Slidebars - side menu plugin http://plugins.adchsm.me/slidebars/ jsPlumb: 'lib/dom.jsPlumb-1.7.2-min', // v1.7.2 jsPlumb (Vanilla)- main map draw plugin http://www.jsplumb.org/ customScrollbar: 'lib/jquery.mCustomScrollbar.concat.min', // v3.1.11 Custom scroll bars - http://manos.malihu.gr/ - datatables: 'lib/jquery.dataTables.min', // v1.10.3 DataTables - tables - datatablesBootstrap: 'lib/dataTables.bootstrap', // DataTables - not used (bootstrap style) + datatables: 'lib/datatables/jquery.dataTables.min', // v1.10.3 DataTables - https://datatables.net/ + datatablesBootstrap: 'lib/datatables/dataTables.bootstrap', // DataTables - not used (bootstrap style) + datatablesTableTools: 'lib/datatables/extensions/TableTools/js/dataTables.tableTools', // v2.2.3 TableTools (PlugIn) - https://datatables.net/extensions/tabletools/ xEditable: 'lib/bootstrap-editable.min', // v1.5.1 X-editable - in placed editing morris: 'lib/morris.min', // v0.5.0 Morris.js - graphs and charts raphael: 'lib/raphael-min', // v2.1.2 Raphaël - required for morris (dependency) @@ -24,7 +26,7 @@ requirejs.config({ dragToSelect: 'lib/jquery.dragToSelect', // v1.1 Drag to Select - http://andreaslagerkvist.com/jquery/drag-to-select/ hoverIntent: 'lib/jquery.hoverIntent.minified', // v1.8.0 Hover intention - http://cherne.net/brian/resources/jquery.hoverIntent.html fullScreen: 'lib/jquery.fullscreen.min', // v0.5.0 Full screen mode - https://github.com/private-face/jquery.fullscreen - + select2: 'lib/select2.min', // v4.0.0 Drop Down customization - https://select2.github.io/ pnotify: 'lib/pnotify/pnotify.core', // v2.0.1 PNotify - notification core file @@ -44,8 +46,8 @@ requirejs.config({ velocity: { deps: ['jquery'] }, - throttleDebounce: { - deps: ['jquery'] + velocityUI: { + deps: ['velocity'] }, slidebars: { deps: ['jquery'] @@ -56,6 +58,9 @@ requirejs.config({ datatables: { deps: ['jquery'] }, + datatablesTableTools: { + deps: ['datatables'] + }, datatablesBootstrap: { deps: ['datatables'] }, @@ -84,6 +89,10 @@ requirejs.config({ }, fullScreen: { deps : ['jquery'] + }, + select2: { + deps : ['jquery'], + exports: 'Select2' } } }); diff --git a/js/app/init.js b/js/app/init.js index 3ac9422e..c47e6ddd 100644 --- a/js/app/init.js +++ b/js/app/init.js @@ -8,8 +8,17 @@ define(['jquery'], function($) { var Config = { timer: { - mapUpdatePing: 3000, // ping for map update - userUpdatePing: 2000 // ping for map user update + mapUpdate: { + delay: 3000, // delay between ping calls + executionLimit: 300 // log timelimit: main map update ping + }, + userUpdate: { + delay: 2000, // delay between ping calls + executionLimit: 100 // log timelimit: map user update ping + }, + mapModuleData: { + executionLimit: 100 // log timelimit: get all mapData + } }, path: { img: 'img/' @@ -19,9 +28,25 @@ define(['jquery'], function($) { eveCentral: 'http://api.eve-central.com/api/' // jump rout api }, animationSpeed: { - headerLink: 100 // links in head bar + headerLink: 100, // links in head bar + mapDeleteSystem: 200 // remove system from map }, classes: { + // log types + logTypes: { + info: { + class: 'pf-log-info', + label: 'info' + }, + warning: { + class: 'pf-log-warning', + label: 'warning' + }, + error: { + class: 'pf-log-error', + label: 'error' + } + }, // map types mapTypes: { standard: { @@ -242,7 +267,9 @@ define(['jquery'], function($) { [ 'Label', { label: ' save mass', - cssClass: ['pf-map-connection-overlay', 'mass'].join(' ') + cssClass: ['pf-map-connection-overlay', 'mass'].join(' '), + width:50, length:30, + location: 0.5 } ] ] } diff --git a/js/app/logging.js b/js/app/logging.js new file mode 100644 index 00000000..4e0e7607 --- /dev/null +++ b/js/app/logging.js @@ -0,0 +1,466 @@ +/** + * logging + */ + +define([ + 'jquery', + 'app/init', + 'app/util', + 'bootbox' +], function($, Init, Util, bootbox) { + + 'use strict'; + + var logData = []; // cache object for all log entries + var logDataTable = null; // "Datatables" Object + + // Morris charts data + var maxGraphDataCount = 30; // max date entries for a graph + var chartData = {}; // chart Data object for all Morris Log graphs + + var config = { + dialogDynamicAreaClass: 'pf-dialog-dynamic-area', // class for dynamic dialog area + logGraphClass: 'pf-log-graph' // class for all log Morris graphs + }; + + /** + * get log time string + * @returns {string} + */ + var getLogTime = function(){ + + var logTimeFormatOptions = { + month: '2-digit', + day: '2-digit', + year: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }; + + var logTime = Util.getServerTime().toLocaleString('en-UK', logTimeFormatOptions); + + return logTime; + }; + + /** + * shows the logging dialog + */ + var showDialog = function(){ + + // dialog content + + var content = $('
'); + + // content row for log graphs + var rowElementGraphs = $('
', { + class: 'row' + }); + + content.append(rowElementGraphs); + + // log table area -------------------------------------------------- + var logTableArea = $('
', { + class: config.dialogDynamicAreaClass + }); + + var logTable = $('', { + class: ['compact', 'stripe', 'order-column', 'row-border'].join(' ') + }); + + var tableHeadline = $('

', { + text: 'Log table' + }); + + // add content Structure to dome before table initialization + content.append(tableHeadline); + + logTableArea.append(logTable); + content.append(logTableArea); + + // init log table + logDataTable = logTable.DataTable( { + paging: true, + ordering: true, + order: [ 1, 'desc' ], + autoWidth: false, + hover: false, + pageLength: 15, + data: logData, // load cached logs (if available) + language: { + emptyTable: 'No entries', + zeroRecords: 'No entries found', + lengthMenu: 'Show _MENU_ entries', + info: 'Showing _START_ to _END_ of _TOTAL_ entries' + }, + columns: [ + { + title: '', + width: '18px', + searchable: false + },{ + title: '  ', + width: '80px', + class: 'text-right' + },{ + title: '  ', + width: '35px', + class: 'text-right', + sType: 'html', + searchable: false + },{ + title: 'description', + searchable: true + },{ + title: '   ', + width: '18px', + class: 'text-right' + },{ + title: 'Prozess-ID   ', + class: 'text-right', + width: '80px' + } + ] + }); + + // open dialog + var logDialog = bootbox.dialog({ + title: 'Task-Manager', + message: content, + className: 'modal-lg', + buttons: { + close: { + label: 'close', + className: 'btn-primary' + } + } + }); + + + // modal dialog is shown + logDialog.on('shown.bs.modal', function(e) { + + // show Morris graphs ---------------------------------------------------------- + + // function for chart label formation + var labelYFormat = function(y){ + return Math.round(y) + 'ms'; + }; + + + for(var key in chartData) { + if(chartData.hasOwnProperty(key)) { + // create a chart for each key + + var colElementGraph = $('
', { + class: ['col-md-6'].join(' ') + }); + + + // graph element + var graphElement = $('
', { + class: config.logGraphClass + }); + + var graphArea = $('
', { + class: config.dialogDynamicAreaClass + }).append( graphElement ); + + // headline + var headline = $('

', { + text: key + }).prepend( + $('', { + class: ['txt-color', 'txt-color-grayLight'].join(' '), + text: 'Prozess-ID: ' + }) + ); + + // show update ping between function calls + var updateElement = $('', { + class: ['txt-color', 'txt-color-blue', 'pull-right'].join(' ') + }); + headline.append(updateElement).append('
'); + + // show average execution time + var averageElement = $('', { + class: 'pull-right' + }); + headline.append(averageElement); + + colElementGraph.append( headline ); + colElementGraph.append( graphArea ); + + graphArea.showLoadingAnimation(); + + rowElementGraphs.append( colElementGraph ); + + // cache DOM Elements that will be updated frequently + chartData[key].averageElement = averageElement; + chartData[key].updateElement = updateElement; + + chartData[key].graph = Morris.Area({ + element: graphElement, + data: [], + xkey: 'x', + ykeys: ['y'], + labels: [key], + units: 'ms', + parseTime: false, + ymin: 0, + yLabelFormat: labelYFormat, + padding: 10, + hideHover: true, + pointSize: 3, + lineColors: ['#375959'], + pointFillColors: ['#477372'], + pointStrokeColors: ['#313335'], + lineWidth: 3, + grid: false, + gridTextSize: 9, + gridTextFamily: 'Oxygen Bold', + behaveLikeLine: true, + goals: [], + goalLineColors: ['#5cb85c'], + smooth: false, + fillOpacity: 0.3, + resize: true + }); + + graphArea.hideLoadingAnimation(); + + } + } + + // ------------------------------------------------------------------------------ + // add TableTool Buttons + + var tt = new $.fn.DataTable.TableTools( logDataTable, { + sSwfPath: 'js/lib/datatables/extensions/TableTools/swf/copy_csv_xls.swf', + aButtons: [ 'copy', 'csv', 'print' ] + }); + + $(tt.fnContainer()).insertBefore('.bootbox-body div.dataTables_wrapper'); + + // add button icons + $('.DTTT_button_csv').prepend( $('', { + class: ['fa', 'fa-fw', 'fa-download'].join(' ') + })); + $('.DTTT_button_copy').prepend( $('', { + class: ['fa', 'fa-fw', 'fa-clipboard'].join(' ') + })); + $('.DTTT_button_print').prepend( $('', { + class: ['fa', 'fa-fw', 'fa-print'].join(' ') + })); + }); + + + // modal dialog is closed + logDialog.on('hidden.bs.modal', function(e) { + // clear memory -> destroy all charts + for (var key in chartData) { + if (chartData.hasOwnProperty(key)) { + chartData[key].graph = null; + } + } + }); + + // modal dialog before hide + logDialog.on('hide.bs.modal', function(e) { + + // destroy logTable + logDataTable.destroy(true); + logDataTable= null; + + // remove event -> prevent calling this multiple times + $(this).off('hide.bs.modal'); + }); + + }; + + /** + * updates the log graph for a log key + * @param key + * @param duration + */ + var updateLogGraph = function(key, duration){ + + // check if graph data already exist + if( !(chartData.hasOwnProperty(key))){ + chartData[key] = {}; + chartData[key].data = []; + chartData[key].graph = null; + chartData[key].averageElement = null; + chartData[key].updateElement = null; + } + + // add new value + chartData[key].data.unshift(duration); + + if(chartData[key].data.length > maxGraphDataCount){ + chartData[key].data = chartData[key].data.slice(0, maxGraphDataCount); + } + + function getGraphData(data) { + var tempChartData = { + data: [], + dataSum: 0, + average: 0 + }; + + for(var x = 0; x < maxGraphDataCount; x++){ + var value = 0; + if(data[x]){ + value = data[x]; + tempChartData.dataSum = Number( (tempChartData.dataSum + value).toFixed(2) ); + } + + tempChartData.data.push({ + x: x, + y: value + }); + } + + // calculate average + tempChartData.average = Number( ( tempChartData.dataSum / data.length ).toFixed(2) ); + + return tempChartData; + } + + var tempChartData = getGraphData(chartData[key].data); + + // add new data to graph (Morris chart) - if is already initialized + if(chartData[key].graph !== null){ + var avgElement = chartData[key].averageElement; + var updateElement = chartData[key].updateElement; + + var delay = Init.timer[key].delay; + + if(delay){ + updateElement[0].textContent = ' delay: ' + delay + 'ms '; + } + + // set/change average line + chartData[key].graph.options.goals = [tempChartData.average]; + + // change avg. display + avgElement[0].textContent = 'Avg. ' + tempChartData.average + 'ms'; + + var avgType = getLogTypeByDuration(key, tempChartData.average); + var avgTypeClass = Util.getLogInfo( avgType, 'class' ); + + //change avg. display class + if( + !avgElement.hasClass(avgTypeClass) + ){ + // avg type changed! + avgElement.removeClass().addClass('pull-right txt-color ' + avgTypeClass); + + // change goals line color + if(avgType === 'warning'){ + chartData[key].graph.options.goalLineColors = ['#e28a0d']; + $(document).setProgramStatus('problem'); + }else{ + chartData[key].graph.options.goalLineColors = ['#5cb85c']; + } + } + + // set new data and redraw + chartData[key].graph.setData( tempChartData.data ); + } + + return tempChartData.data; + }; + + /** + * get the log "type" by log duration (ms). + * If duration > warning limit -> show as warning + * @param logKey + * @param logDuration + * @returns {string} + */ + var getLogTypeByDuration = function(logKey, logDuration){ + + var logType = 'info'; + if( logDuration > Init.timer[logKey].executionLimit ){ + logType = 'warning'; + } + return logType; + }; + + /** + * init logging -> set global log event + */ + var init = function(){ + + + var maxEntries = 150; + + // set global logging listener + $(window).on('pf:log', function(e, logKey, options){ + + // check required logging information + if( + options && + options.duration && + options.description + ){ + var logDescription = options.description; + var logDuration = options.duration; + + // add new row to log table (time and message) + var logRowData = ['', getLogTime(), '', logDescription, '', '']; + + // check log type by duration + var logType = getLogTypeByDuration(logKey, logDuration); + + var typeClass = Util.getLogInfo( logType, 'class' ); + + logRowData[0] = ''; + + logRowData[2] = '' + logDuration + 'ms'; + + // update graph data + updateLogGraph(logKey, logDuration); + + logRowData[4] = '123'; + logRowData[5] = logKey; + + if(logDataTable){ + // add row if dataTable is initialized before new log + logDataTable.row.add( logRowData ).draw(false); + }else{ + // add row data to cache + logData.push(logRowData); + } + } + + + // delete old log entries from table --------------------------------- + var rowCount = logData.length; + + if( rowCount >= maxEntries ){ + + if(logDataTable){ + logDataTable.rows(0, {order:'index'}).remove().draw(false); + }else{ + logData.shift(); + } + } + + // cache logs in order to keep previous logs in table after reopening the dialog + if(logDataTable){ + logData = logDataTable.rows({order:'index'}).data(); + } + + }); + }; + + + return { + init: init, + getLogTime: getLogTime, + showDialog: showDialog + }; +}); \ No newline at end of file diff --git a/js/app/main.js b/js/app/main.js index 34b2a7c8..12d00ac1 100644 --- a/js/app/main.js +++ b/js/app/main.js @@ -5,13 +5,15 @@ define([ 'jquery', 'app/init', + 'app/util', 'app/render', - 'velocity', + 'app/logging', 'app/ccp', + 'velocity', + 'velocityUI', 'app/page', - 'app/module_map', - 'throttleDebounce' -], function($, Init, Render, Velocity, CCP) { + 'app/module_map' +], function($, Init, Util, Render, Logging, CCP) { 'use strict'; @@ -23,7 +25,10 @@ define([ $(function() { //CCP.requestTrust(); + // init logging + Logging.init(); + // load page $('body').loadPageStructure(); // Map init options @@ -318,7 +323,7 @@ define([ status: 'friendly', position: { x: 5, - y: 7 + y: 200 }, updated: 1420903681 },{ @@ -364,7 +369,7 @@ define([ // current user Data for a map - var userData ={ + var tempUserData ={ currentUserData: { ship: 'Legion', name: 'Exodus 4D', @@ -388,7 +393,7 @@ define([ name: 'Exodus 4D', ship: { id: 55, - name: 'legion' + name: 'Legion' }, status: 'corp' } @@ -426,6 +431,28 @@ define([ } ] } + },{ + config: { // map config + id: 2 // map id + }, + data: { + systems:[ // systems in map + { + id: 50, // system id + user: [ + { + id: 6, + name: 'Schleiferius', + ship: { + id: 69, + name: 'Tengu' + }, + status: 'corp' + } + ] + } + ] + } } ]}; @@ -437,6 +464,14 @@ define([ var mapDataUpdateActive = true; // allow update "map data" var userDataUpdateActive = true; // allow update "user data" + var mapUpdateKey = 'mapUpdate'; + var mapUpdateDelay = Init.timer[mapUpdateKey].delay; + + var mapModuleDatakey = 'mapModuleData'; + + var mapUserUpdateKey = 'userUpdate'; + var mapUserUpdateDelay = Init.timer[mapUserUpdateKey].delay; + // ping for main map update var triggerMapUpdatePing = function(tempMapData){ @@ -445,11 +480,16 @@ define([ $(document).setProgramStatus('online'); mapDataUpdateActive = false; - console.time('updateMapData') + + Util.timeStart(mapUpdateKey); // load map module ========================================== mapDataUpdateActive = mapModule.updateMapModule(tempMapData); - console.timeEnd('updateMapData') + var duration = Util.timeStop(mapUpdateKey); + + // log execution time + Util.log(mapUpdateKey, {duration: duration, description: 'updateMapModule'}); + }else{ // not finished in time -> to slow or error $(document).setProgramStatus('problem'); @@ -457,33 +497,45 @@ define([ // get updated map data if(mapDataUpdateActive === true){ - console.time('getMapData') + Util.timeStart(mapModuleDatakey); var mapData = mapModule.getMapModuleData(); - console.timeEnd('getMapData') + var mapDataLogDuration = Util.timeStop(mapModuleDatakey); + + // log execution time + Util.log(mapModuleDatakey, {duration: mapDataLogDuration, description: 'getMapModuleData'}); + } }; triggerMapUpdatePing(mapData); - setInterval(triggerMapUpdatePing, Init.timer.mapUpdatePing, mapData); + setInterval(triggerMapUpdatePing, mapUpdateDelay, mapData); - // ping for user data update - var triggerUserUpdatePing = function(tempUserData){ + // ping for user data update ------------------------------------------------------- + + + var triggerUserUpdatePing = function(userData){ // prevent multiple requests simultaneously if(userDataUpdateActive === true){ $(document).setProgramStatus('online'); userDataUpdateActive = false; - console.time('updateUserData'); + + Util.timeStart(mapUserUpdateKey); userDataUpdateActive = mapModule.updateMapModuleData(userData); - console.timeEnd('updateUserData'); + var duration = Util.timeStop(mapUserUpdateKey); + + // log execution time + Util.log(mapUserUpdateKey, {duration: duration, description:'updateMapModuleData'}); + + }else{ // not finished in time -> to slow or error $(document).setProgramStatus('problem'); } }; - setInterval(triggerUserUpdatePing, Init.timer.userUpdatePing, mapData); + setInterval(triggerUserUpdatePing, mapUserUpdateDelay, tempUserData); }); diff --git a/js/app/map/map.js b/js/app/map/map.js index ab0282ca..8c2b8205 100644 --- a/js/app/map/map.js +++ b/js/app/map/map.js @@ -4,12 +4,14 @@ define([ 'app/util', 'app/render', 'bootbox', + 'app/ccp', 'jsPlumb', 'customScrollbar', 'dragToSelect', + 'select2', 'hoverIntent', 'app/map/contextmenu' -], function($, Init, Util, Render, bootbox) { +], function($, Init, Util, Render, bootbox, CCP) { "use strict"; @@ -42,7 +44,9 @@ define([ systemBodyItemClass: 'pf-system-body-item', // class for a system body entry systemBodyItemStatusClass: 'pf-user-status', systemBodyRightClass: 'pf-system-body-right', - dynamicElementWrapperId: 'pf-dialog-wrapper', // wrapper div for dynamic content (dialogs, context-menus,...) + systemTooltipInnerClass: 'pf-system-tooltip-inner', // class for system tooltip content + systemTooltipInnerIdPrefix: 'pf-system-tooltip-inner-', // id prefix for system tooltip content + dynamicElementWrapperId: 'pf-dialog-wrapper', // wrapper div for dynamic content (dialogs, context-menus,...) // endpoint classes endpointSourceClass: 'pf-map-endpoint-source', @@ -170,6 +174,9 @@ define([ // find expand arrow var systemHeadExpand = $( system.find('.' + config.systemHeadExpandClass) ); + var oldCacheKey = system.data('userCache'); + var oldUserCount = system.data('userCount'); + oldUserCount = (oldUserCount !== undefined ? oldUserCount : 0); var userCounter = 0; system.data('currentUser', false); @@ -179,27 +186,25 @@ define([ system.data('currentUser', true); } - var oldCacheKey = system.data('userCache'); - // add user information if( data && data.user ){ - var cacheArray = []; // loop all active pilots and build cache-key for(var i = 0; i < data.user.length; i++){ + userCounter++; var tempUserData = data.user[i]; cacheArray.push(tempUserData.id + '_' + tempUserData.ship.name); } - var cacheKey = cacheArray.join('_'); // check for if cacheKey has changed if(cacheKey !== oldCacheKey){ // set new CacheKey system.data('userCache', cacheKey); + system.data('userCount', userCounter); // remove all content systemBody.empty(); @@ -207,7 +212,6 @@ define([ // loop "again" and build DOM object with user information for(var j = 0; j < data.user.length; j++){ var userData = data.user[j]; - userCounter++; var statusClass = getStatusClassForUser(userData.status); var userName = userData.name; @@ -235,68 +239,119 @@ define([ // ================================================================= - // user count changed -> adapt tooltip - system.tooltip('destroy'); + // user count changed -> change tooltip content + var tooltipOptions = {placement: 'top', trigger: 'manual'}; - system.attr('title', userCounter); + // set tooltip color + var highlight = false; + var tooltipIconClass = ''; + if(userCounter > oldUserCount){ + highlight = 'good'; + tooltipIconClass = 'fa-caret-up'; + }else if(userCounter < oldUserCount){ + highlight = 'bad'; + tooltipIconClass = 'fa-caret-down'; + } + + tooltipOptions.id = systemId; + tooltipOptions.highlight = highlight; + tooltipOptions.title = ''; + tooltipOptions.title += ' ' + userCounter; // show system head - systemHeadExpand.velocity({ + systemHeadExpand.velocity('stop', true).velocity({ width: '10px' },{ duration: 50, display: 'inline-block', progress: function(){ - // revalidate element size and repaint + //revalidate element size and repaint map.revalidate( systemId ); }, complete: function(){ - // show system body - systemBody.velocity({ - height: config.systemBodyItemHeight + 'px' - },{ - duration: 50, - display: 'auto', - progress: function(){ - // revalidate element size and repaint - map.revalidate( systemId ); - } - }); + system.toggleBody(true, map, {complete: function(){ + // complete callback function + // show active user tooltip + system.toggleSystemTooltip('show', tooltipOptions); + }}); + - // show active user tooltip - toggleSystemTooltip([system], 'show', {placement: 'top', trigger: 'manual'}); } }); } }else{ // no user data found for this system system.data('userCache', false); + system.data('userCount', 0); + systemBody.empty(); if( oldCacheKey && oldCacheKey.length > 0 ){ + // remove tooltip + system.toggleSystemTooltip('destroy', {}); + // no user -> clear SystemBody - systemHeadExpand.velocity('reverse',{ + systemHeadExpand.velocity('stop', true).velocity('reverse',{ display: 'none', complete: function(){ - systemBody.velocity('reverse',{ - display: 'none', - progress: function(){ - // revalidate element size and repaint - map.revalidate( systemId ); - systemBody.empty(); - } - }); + system.toggleBody(false, map, {}); } }); } - } + }; + + $.fn.toggleBody = function(type, map, callback){ + var system = $(this); + var systemBody = system.find('.' + config.systemBodyClass); + + var systemDomId = system.attr('id'); + + if(type === true){ + // show minimal body + systemBody.velocity({ + height: config.systemBodyItemHeight + 'px' + },{ + duration: 50, + display: 'auto', + progress: function(){ + //revalidate element size and repaint + map.revalidate( systemDomId ); + }, + complete: function(){ + map.revalidate( systemDomId ); + + if(callback.complete){ + callback.complete(); + } - + } + }); + }else if(type === false){ + // hide body + // remove all inline styles -> possible relict from previous hover-extend + systemBody.velocity({ + height: 0 + 'px', + width: '100%', + 'min-width': 'none' + },{ + duration: 50, + display: 'none', + begin: function(){ + }, + progress: function(){ + // revalidate element size and repaint + map.revalidate( systemDomId ); + }, + complete: function(){ + map.revalidate( systemDomId ); + } + }); + } }; /** @@ -305,15 +360,107 @@ define([ * @param show * @param options */ - var toggleSystemTooltip = function(systems, show, options){ + $.fn.toggleSystemTooltip = function(show, options){ - for(var i = 0; i < systems.length; i++){ - if(options){ - $(systems[i]).tooltip(options); + // tooltip colors + var colorClasses = { + good: 'txt-color-green', + bad: 'txt-color-red' + }; + + return this.each(function(){ + var system = $(this); + var tooltipId = 0; + var tooltipClassHighlight = false; + + // do not update tooltips while a system is dragged + if(system.hasClass('jsPlumb_dragged')){ + // skip system + return true; } - } - $(systems).tooltip(show); + if(show === 'destroy'){ + system.tooltip( show ); + }else if(show === 'hide'){ + system.tooltip( show ); + } else if(show === 'toggle'){ + system.tooltip( show ); + }else if(show === 'show'){ + + // check if tooltip is currently visible + var tooltipActive = (system.attr('aria-describedby') !== undefined ? true : false); + + if(options === undefined){ + options = {}; + } + + // optional color highlight + if(colorClasses.hasOwnProperty( options.highlight )){ + tooltipClassHighlight = colorClasses[ options.highlight ]; + } + + if( + tooltipActive === false && + options.id + ){ + // init new tooltip + tooltipId = config.systemTooltipInnerIdPrefix + options.id; + + var template = ''; + + options.html = true; + options.animation = true; + options.template = template; + + system.attr('title', options.title); + + system.tooltip(options); + + system.tooltip(show); + + if(tooltipClassHighlight !== false){ + // set tooltip observer and set new class after open -> due to transition effect + + system.on('shown.bs.tooltip', function() { + $('#' + tooltipId).addClass( tooltipClassHighlight ); + // remove observer -> color should not be changed every time a tooltip toggles e.g. dragging system + $(this).off('shown.bs.tooltip'); + }); + } + }else{ + // update/change/toggle tooltip text or color without tooltip reload + + var tooltipInner = false; + if( + options.title || + tooltipClassHighlight !== false + ){ + tooltipInner = system.tooltip('fixTitle') + .data('bs.tooltip') + .$tip.find('.tooltip-inner'); + + if(options.title){ + tooltipInner.html( options.title ); + } + + if(tooltipClassHighlight !== false){ + tooltipInner.removeClass( colorClasses.good + ' ' + colorClasses.bad).addClass(tooltipClassHighlight); + } + } + + // show() can be forced + if(options.show === true){ + system.tooltip('show'); + } + + } + } + + + }); }; /** @@ -448,14 +595,15 @@ define([ }; /** - * draw a new map with all systems and connections + * draw a new map or update an existing map with all its systems and connections + * @param parentElement * @param mapConfig + * @returns {*} */ var updateMap = function(parentElement, mapConfig){ var mapContainer = mapConfig.map.getContainer(); - // prevent jsPlumb from re-painting during main-map update -> performance boost :) mapConfig.map.doWhileSuspended(function() { @@ -481,7 +629,6 @@ define([ // append mapWrapper to parent element (at the top) $(parentElement).prepend(mapWrapper); - // set main Container for current map -> the container exists now in DOM !! very important mapConfig.map.setContainer($('#' + config.mapIdPrefix + mapConfig.config.id)); @@ -489,14 +636,14 @@ define([ setMapObserver(mapConfig.map); } + mapContainer = $(mapContainer); + // add additional information mapContainer.data('name', mapConfig.config.name); mapContainer.data('scope', mapConfig.config.scope); mapContainer.data('icon', mapConfig.config.icon); mapContainer.data('type', mapConfig.config.type); - mapContainer = $(mapContainer); - // get map data var mapData = mapContainer.getMapData(); @@ -613,6 +760,117 @@ define([ } }); + + return mapContainer; + }; + + /** + * make all systems appear visual on the map with its connections + * @param show + * @param callback + */ + $.fn.visualizeMap = function(show, callback){ + var mapElement = $(this); + + // start map update counter -> prevent map updates during animations + mapElement.getMapOverlay().startMapUpdateCounter(); + + var systemElements = mapElement.find('.' + config.systemClass); + var endpointElements = mapElement.find('._jsPlumb_endpoint'); + var connectorElements = mapElement.find('._jsPlumb_connector'); + var overlayElements = mapElement.find('._jsPlumb_overlay, .tooltip'); + + // if map empty (no systems), execute callback and return + // no visual effects in IGB (glitches) + if( + systemElements.length === 0 || + CCP.isInGameBrowser() === true + ){ + callback(); + return; + } + + // show nice animation + if(show === 'show'){ + // hide elements + systemElements.css('opacity', 0); + endpointElements.css('opacity', 0); + connectorElements.css('opacity', 0); + overlayElements.css('opacity', 0); + + systemElements.velocity('transition.whirlIn', { + stagger: 50, + drag: true, + duration: 100, + //display: 'auto', + complete: function(){ + // show connections + endpointElements.velocity('transition.fadeIn', { + stagger: 50, + drag: true, + duration: 50 + }); + + connectorElements.velocity('transition.flipBounceXIn', { + stagger: 50, + drag: true, + duration: 1000 + }); + + overlayElements.delay(500).velocity('transition.fadeIn', { + stagger: 50, + drag: true, + duration: 200, + display: 'auto', + complete: function(){ + callback(); + } + }); + + } + }); + }else if(show === 'hide'){ + + $('.mCSB_container').velocity('callout.shake', { + stagger: 0, + drag: false, + duration: 200, + display: 'auto' + }); + + overlayElements.velocity('transition.fadeOut', { + stagger: 50, + drag: true, + duration: 200, + display: 'auto' + }); + + endpointElements.velocity('transition.fadeOut', { + stagger: 0, + drag: true, + duration: 50, + display: 'block', + complete: function(){ + // show connections + connectorElements.velocity('transition.fadeOut', { + stagger: 0, + drag: true, + duration: 20, + display: 'block' + }); + + systemElements.delay(100).velocity('transition.slideUpOut', { + stagger: 50, + drag: true, + duration: 200, + display: 'block', + complete: function(){ + callback(); + } + }); + } + }); + } }; /** @@ -726,11 +984,14 @@ define([ map.removeAllEndpoints(system); // hide tooltip - toggleSystemTooltip(system, 'hide'); + system.toggleSystemTooltip('hide', {}); // remove system - system.fadeOut(300, function(){ - $(this).remove() ; + system.velocity('transition.whirlOut', { + duration: Init.animationSpeed.mapDeleteSystem, + complete: function(){ + $(this).remove() ; + } }); }; @@ -826,6 +1087,7 @@ define([ var counterChart = mapOverlay.getMapCounter(); var seconds = 10; + var fadeEffectDuration = 200; // get counter interval (in case there is an active one) var interval = counterChart.data('interval'); @@ -834,7 +1096,7 @@ define([ clearInterval(interval); } - mapOverlay.fadeIn(200); + mapOverlay.velocity('stop').velocity('transition.whirlIn', { duration: fadeEffectDuration }); var counterChartLabel = counterChart.find('span'); @@ -848,13 +1110,12 @@ define([ if(seconds <= 0){ clearInterval(mapUpdateCounter); - - setTimeout(function(){ - mapOverlay.fadeOut(200); - counterChart.data('interval', false); - }, 800); - - return; + mapOverlay.velocity('transition.whirlOut', { + duration: fadeEffectDuration, + complete: function(){ + counterChart.data('interval', false); + } + }); } }; @@ -1236,7 +1497,7 @@ define([ selectedSystems = $.unique( selectedSystems ); // hide tooltip - toggleSystemTooltip( selectedSystems, 'hide' ); + $(selectedSystems).toggleSystemTooltip('hide', {}); }, drag: function(){ @@ -1252,7 +1513,7 @@ define([ }, 200); // render tooltip - toggleSystemTooltip([dragSystem], 'show'); + dragSystem.toggleSystemTooltip('show', {show: true}); // drag system is not always selected var selectedSystems = mapContainer.getSelectedSystems().get(); @@ -1298,27 +1559,28 @@ define([ var hoverSystemId = hoverSystem.attr('id'); // get ship counter and calculate expand height - var shipCounter = parseInt( system.attr('data-original-title') ); + var userCount = parseInt( hoverSystem.data('userCount') ); - var expandheight = shipCounter * config.systemBodyItemHeight; + var expandHeight = userCount * config.systemBodyItemHeight; - systemBody.velocity( + systemBody.velocity('stop').velocity( { - height: expandheight + 'px', - width: 100, + height: expandHeight + 'px', + width: 150, 'min-width': '150px' },{ - duration: 100, + easing: 'easeInOutQuart', + duration: 150, progress: function(){ // repaint connections of current system map.revalidate( hoverSystemId ); }, complete: function(){ map.revalidate( hoverSystemId ); - $(this).find('.' + config.systemBodyRightClass).velocity({ + $(this).find('.' + config.systemBodyRightClass).velocity('stop').velocity({ opacity: 1 },{ - duration: 50, + duration: 150, display: 'auto' }); } @@ -1330,13 +1592,15 @@ define([ var hoverSystem = $(this).parents('.' + config.systemClass); var hoverSystemId = hoverSystem.attr('id'); - systemBody.find('.' + config.systemBodyRightClass).velocity( { - opacity: 0 + systemBody.find('.' + config.systemBodyRightClass).velocity('stop').velocity( { + opacity: 0, + 'min-width': '0px' },{ - duration: 100, + easing: 'easeInOutQuart', + duration: 150, display: 'none', complete: function(){ - systemBody.velocity('reverse', { + systemBody.velocity('stop').velocity('reverse', { complete: function(){ // overwrite "complete" function from first "hover"-open map.revalidate( hoverSystemId ); @@ -1820,8 +2084,6 @@ define([ }); - - // catch menu events ==================================================== // toggle "snap to grid" option @@ -1884,6 +2146,7 @@ define([ hiddenOptions.push('scope_stargate'); }else if(scope === 'jumpbridge'){ hiddenOptions.push('frigate'); + hiddenOptions.push('preserve_mass'); hiddenOptions.push('change_status'); hiddenOptions.push('scope_jumpbridge'); }else if(scope === 'wh'){ @@ -2131,10 +2394,13 @@ define([ status: systemStatus }; - requirejs(['text!templates/modules/system_dialog.html', 'lib/mustache'], function(template, Mustache) { + requirejs(['text!templates/modules/system_dialog.html', 'mustache'], function(template, Mustache) { var content = Mustache.render(template, data); + // disable modal focus event -> otherwise select2 is not working! -> quick fix + $.fn.modal.Constructor.prototype.enforceFocus = function() {}; + var systemDialog = bootbox.dialog({ title: 'Add new system', message: content, @@ -2199,6 +2465,9 @@ define([ if(!newSystemData.hasOwnProperty('id')){ newSystemData.id = config.tempId++; } + if(!newSystemData.hasOwnProperty('alias')){ + newSystemData.alias = ''; + } if(!newSystemData.hasOwnProperty('effect')){ newSystemData.effect = ''; } @@ -2215,6 +2484,39 @@ define([ } ); + + // init dialog + systemDialog.on('shown.bs.modal', function(e) { + + var selectData = []; + for(var i = 0; i < 5000; i++){ + selectData.push({ + id: i, + text: i + 'test' + }); + } + + $.when( + $(".js-example-basic-single").select2({ + // multiple: true, + data: selectData, + placeholder: 'Name', + allowClear: true + }) + ).done(function(){ + $('#testId').css({'display': 'block'}); + }); + + }); + + + + + + + + + // make dialog editable var modalFields = $('.bootbox .modal-dialog').find('.pf-editable-system-status'); @@ -2222,6 +2524,7 @@ define([ mode: 'inline', emptytext: 'unknown', onblur: 'submit', + showbuttons: false, source: systemStatus }); @@ -2237,15 +2540,29 @@ define([ * @param currentUserData * @returns {boolean} */ + $.fn.updateUserData = function(userData, currentUserData){ + + var returnStatus = true; + // get all systems var systems = $(this).find('.' + config.systemClass); // get new map instance or load existing var map = getMapInstance(userData.config.id); + var mapElement = map.getContainer(); + + // container must exist! otherwise systems cant be updated - if(map.getContainer() !== undefined){ + if(mapElement !== undefined){ + + mapElement = $(mapElement); + + // check if map is frozen + if(mapElement.data('frozen') === true){ + return returnStatus; + } // data for header update var headerUpdateData = { @@ -2294,7 +2611,7 @@ define([ $(document).trigger('pf:updateHeaderData', headerUpdateData); } - return true; + return returnStatus; }; /** @@ -2353,7 +2670,7 @@ define([ systemData.rally = tempSystem.data('rally'); systemData.currentUser = tempSystem.data('currentUser'); systemData.updated = tempSystem.data('updated'); - systemData.userCount = (tempSystem.attr('data-original-title') ? parseInt( tempSystem.attr('data-original-title') ) : 0); + systemData.userCount = (tempSystem.data('userCount') ? parseInt( tempSystem.data('userCount') ) : 0); // position ------------------------------- var positionData = {}; @@ -2420,6 +2737,7 @@ define([ if(typeof activeInstances[mapId] !== 'object'){ // create new instance + jsPlumb.Defaults.LogEnabled = true; var newJsPlumbInstance = jsPlumb.getInstance({ Container: null, // will be set as soon as container is connected to DOM PaintStyle:{ @@ -2432,7 +2750,8 @@ define([ Endpoint : ['Dot', {radius: 6}], // Endpoint: 'Blank', // does not work... :( ReattachConnections: false, // re-attach connection if dragged with mouse to "nowhere" - Scope: Init.defaultMapScope // default map scope for connections + Scope: Init.defaultMapScope, // default map scope for connections + LogEnabled: true }); // register all available connection types @@ -2521,8 +2840,6 @@ define([ // init jsPlumb jsPlumb.ready(function() { - - // get new map instance or load existing mapConfig.map = getMapInstance(mapConfig.config.id); @@ -2533,15 +2850,25 @@ define([ } // draw/update map initial map and set container - updateMap(parentElement, mapConfig); + var mapContainer = updateMap(parentElement, mapConfig); if(newMap){ - // init custom scrollbars + // init custom scrollbars and add overlay parentElement.initMapScrollbar(); - - Util.showNotify({title: 'Map initialized', text: mapConfig.config.name + ' - loaded', type: 'success'}); } + // callback function after tab switch + function switchTabCallback( mapName ){ + Util.showNotify({title: 'Map initialized', text: mapName + ' - loaded', type: 'success'}); + + return false; + } + + // show nice visualization effect + mapContainer.visualizeMap('show', function(){ + switchTabCallback( mapConfig.config.name ); + }); + }); }; diff --git a/js/app/module_map.js b/js/app/module_map.js index c8379871..7ff31ed7 100644 --- a/js/app/module_map.js +++ b/js/app/module_map.js @@ -5,7 +5,8 @@ define([ 'app/render', 'bootbox', 'morris', - 'datatables', + //'datatables', + 'datatablesTableTools', 'xEditable', 'app/map/map', 'app/counter' @@ -18,11 +19,11 @@ define([ var config = { dynamicElementWrapperId: 'pf-dialog-wrapper', // parent Element for dynamic content (dialogs,..) mapTabElementId: 'pf-map-tab-element', // id for map tab element (tabs + content) - mapTabBarId: 'pf-map-tabs', - mapTabIdPrefix: 'pf-map-tab-', - mapTabClass: 'pf-map-tab', + mapTabBarId: 'pf-map-tabs', // id for map tab bar + mapTabIdPrefix: 'pf-map-tab-', // id prefix for a map tab + mapTabClass: 'pf-map-tab', // class for a map tab mapTabLinkTextClass: 'nav-tabs-link', // class for span elements in a tab - mapTabContentClass: 'pf-map-tab-content', + mapTabContentClass: 'pf-map-tab-content', // class for tab content container mapTabContentSystemInfoClass: 'pf-map-tab-content-system', mapWrapperClass: 'pf-map-wrapper', // scrollable mapClass: 'pf-map', // class for each map @@ -77,13 +78,7 @@ define([ systemKillsGraphData: {} // data for system kills info graph }; - - - var saveMapData = function(mapData){ - - // TODO: save map - console.log(mapData); - }; + var mapTabChangeBlocked = false; // flag for preventing map tab switch /** * get all maps for a maps module @@ -118,7 +113,7 @@ define([ * @returns {*} */ var getTabElements = function(){ - return $('#' + config.mapTabBarId).find('a[data-toggle="tab"]'); + return $('#' + config.mapTabBarId).find('a'); }; /** @@ -154,7 +149,7 @@ define([ id: config.signatureReaderDialogId }; - requirejs(['text!templates/modules/signature_reader_dialog.html', 'lib/mustache'], function(template, Mustache) { + requirejs(['text!templates/modules/signature_reader_dialog.html', 'mustache'], function(template, Mustache) { var content = Mustache.render(template, data); @@ -295,7 +290,7 @@ define([ moduleElement.showSignatureReaderDialog(systemData); }).prepend( $('', { - class: ['fa', 'fa-copy', 'fa-fw'].join(' ') + class: ['fa', 'fa-clipboard', 'fa-fw'].join(' ') }) ) ).append( @@ -977,14 +972,8 @@ define([ chartData.push(tempData); } - var serverDate= new Date( - localDate.getUTCFullYear(), - localDate.getUTCMonth(), - localDate.getUTCDate(), - localDate.getUTCHours(), - localDate.getUTCMinutes(), - localDate.getUTCSeconds() - ); + // get current server time + var serverDate= Util.getServerTime(); // get all kills until current server time var dateStringEnd = String( serverDate.getFullYear() ); @@ -1685,14 +1674,18 @@ define([ /** - * updates complete map module (all maps) + * updates only visible/active map module * @param userData * @returns {boolean} */ + var test = 1 $.fn.updateMapModuleData = function(userData){ + var mapModule = $(this); + + test++; // get all active map elements for module - var mapElements = $(this).getMaps(); + var mapElement = mapModule.getActiveMap(); var currentUserData = null; @@ -1701,26 +1694,46 @@ define([ currentUserData = userData.currentUserData; } - // get map Data - for(var i = 0; i < mapElements.length; i++){ - var mapElement = $(mapElements[i]); + if(mapElement !== false){ var mapId = mapElement.data('id'); - var mapUserData = null; + //var tempMapUserData = null; // get user data for each active map + var tempMapUserDataClone = null; for(var j = 0; j < userData.mapUserData.length; j++){ - var tempMapData = userData.mapUserData[j]; + //var tempMapData = userData.mapUserData[j]; + var tempMapData = JSON.parse(JSON.stringify(userData.mapUserData[j])); if(tempMapData.config.id === mapId){ // map userData found - mapUserData = tempMapData; + + // clone object (pass by value) due to object manipulation + tempMapUserDataClone = JSON.parse(JSON.stringify(tempMapData)); + + // TODO remove !!!! + if( (test % 2) === 0){ + tempMapUserDataClone.data.systems[0].user.push({ + id: 7, + name: 'Lijama', + ship: { + id: 59, + name: 'Archon' + }, + status: 'corp' + }) + }else if((test % 3) === 0){ + tempMapUserDataClone.data.systems = new Array(); + + } + break; } } // update map - if(mapUserData){ - mapElement.updateUserData(mapUserData, currentUserData); + if(tempMapUserDataClone){ + //console.log('User: ' + tempMapUserDataClone.data.systems[0].user.length); + mapElement.updateUserData(tempMapUserDataClone, currentUserData); } } @@ -1800,7 +1813,7 @@ define([ // link element ------- var linkElement = $('', { href: '#' + config.mapTabIdPrefix + options.id - }).attr('role', 'tab').attr('data-toggle', 'tab').data('map-id', options.id); + }).attr('role', 'tab').data('map-id', options.id); // icon element ------ var iconElement = $('', { @@ -1833,6 +1846,43 @@ define([ tabContent.append(contentElement); + + // init tab ========================================================= + linkElement.on('click', function(e){ + e.preventDefault(); + + // callback function after tab switch + function switchTabCallback(mapElement, tabLinkElement){ + tabLinkElement.tab('show'); + // unfreeze map + mapElement.data('frozen', false); + return false; + } + + if(mapTabChangeBlocked === false){ + + var tabLinkElement = $(this); + var mapId = tabLinkElement.data('map-id'); + + var mapElement = $('#' + config.mapTabElementId).getActiveMap(); + + if(mapId !== mapElement.data('id')){ + // block tabs until switch is done + mapTabChangeBlocked = true; + + // freeze active map -> no user data update while map switch + mapElement.data('frozen', true); + + // hide current map with animation + mapElement.visualizeMap('hide', function(){ + // un-block map tabs + mapTabChangeBlocked = switchTabCallback(mapElement, tabLinkElement); + }); + } + } + }); + + return { listElement: newListElement, contentElement: contentElement @@ -1918,7 +1968,6 @@ define([ if(tabMapElement.length > 0){ // tab element already exists - var tabElements = getTabElements(); // mapIds that are currently active @@ -2043,7 +2092,7 @@ define([ tabContentElements.initContentStructure(); // load first map i in first tab content container - $( tabContentElements[0] ).updateMapData(tempMapData[0]); + $( tabContentElements[0] ).loadMap( tempMapData[0] ); } if(tabsChanged === true){ @@ -2075,7 +2124,7 @@ define([ // load map var currentTabContentElement = $('#' + config.mapTabIdPrefix + mapId); - $( currentTabContentElement).updateMapData( tabMapData); + $( currentTabContentElement).loadMap( tabMapData ); // "wake up" scrollbar for map and get previous state back var scrollableElement = currentTabContentElement.find('.' + config.mapWrapperClass); @@ -2093,6 +2142,7 @@ define([ // disable map if new map is selected -> not "add button" if(newMapId > 0){ + var currentTabContentElement = $('#' + config.mapTabIdPrefix + oldMapId); // disable scrollbar for map that will be hidden. "freeze" current state @@ -2127,17 +2177,4 @@ define([ return data; }; - /** - * load OR updates a map module with its data - * @param mapData - * @returns {*} - */ - $.fn.updateMapData = function(mapData){ - - return this.each(function(){ - $(this).loadMap(mapData); - }); - }; - - }); \ No newline at end of file diff --git a/js/app/notification.js b/js/app/notification.js index 2cc43727..aaf84821 100644 --- a/js/app/notification.js +++ b/js/app/notification.js @@ -112,7 +112,7 @@ define([ customConfig.delay = 10000; customConfig.desktop.desktop = true; -console.log(customConfig.desktop) + // make browser tab blink startTabBlink(customConfig.title); diff --git a/js/app/page.js b/js/app/page.js index 25683367..50fd925f 100644 --- a/js/app/page.js +++ b/js/app/page.js @@ -8,9 +8,11 @@ define([ 'app/render', 'bootbox', 'app/ccp', + 'app/ui/map_info', + 'app/logging', 'slidebars', 'app/module_map' -], function($, Init, Util, Render, bootbox, CCP) { +], function($, Init, Util, Render, bootbox, CCP, MapInfo, Logging) { 'use strict'; @@ -44,8 +46,9 @@ define([ menuHeadMenuLogoClass: 'pf-head-menu-logo', // class for main menu logo menuButtonFullScreenId: 'pf-menu-button-fullscreen', // id for menu button "full screen" - // map module - mapModuleId: 'pf-map-module', // main map module + // global dialog + dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar + dialogNavigationListItemClass: 'pf-dialog-navigation-list-item', // class for map manual li main navigation elements // system effect dialog systemEffectDialogWrapperClass: 'pf-system-effect-dialog-wrapper', // class for system effect dialog @@ -55,13 +58,6 @@ define([ // map manual dialog mapManualScrollspyId: 'pf-manual-scrollspy', // id for map manual scrollspy - mapManualScrollspyNavClass: 'pf-manual-scrollspy-nav', // class for map manual scrollspy navigation - mapManualNavigationListItemClass: 'pf-manual-navigation-list-item', // class for map manual li main navigation elements - - // map info dialog - mapInfoSystemsId: 'pf-map-info-systems', // id for map info systems box - mapInfoConnectionsId: 'pf-map-info-connections', // id for map info connections box - mapInfoTableClass: 'pf-map-info-table', // class for data // helper element dynamicElementWrapperId: 'pf-dialog-wrapper' @@ -71,6 +67,9 @@ define([ systemEffectDialog: false // system effect info dialog }; + var programStatusCounter = 0; // current count down in s until next status change is possible + var programStatusInterval = false; // interval timer until next status change is possible + /** * load main page structure elements and navigation container into body @@ -97,9 +96,7 @@ define([ id: config.pageId, class: config.pageClass }).append( - $('
', { - id: config.mapModuleId - }) + Util.getMapModule() ).append( $('
', { id: config.dynamicElementWrapperId @@ -217,11 +214,6 @@ define([ } }); } - - - - - }; /** @@ -251,7 +243,7 @@ define([ class: 'glyphicon glyphicon-th' }) ).on('click', function(){ - $('#' + config.mapModuleId).getActiveMap().triggerMenuEvent('Grid', {button: this}); + Util.getMapModule().getActiveMap().triggerMenuEvent('Grid', {button: this}); }) ).append( $('', { @@ -264,6 +256,17 @@ define([ ).on('click', function(){ $(document).triggerMenuEvent('EditMap', {newMap: false}); }) + ).append( + $('', { + class: 'list-group-item', + href: '#' + }).html('  Task-Manager').prepend( + $('',{ + class: 'fa fa-tasks fa-fw' + }) + ).on('click', function(){ + $(document).triggerMenuEvent('ShowTaskManager'); + }) ) ); }; @@ -312,7 +315,12 @@ define([ // current location $('.' + config.headCurrentLocationClass).find('a').on('click', function(){ - $('#' + config.mapModuleId).getActiveMap().triggerMenuEvent('SelectSystem', {systemId: $(this).data('systemId') }); + Util.getMapModule().getActiveMap().triggerMenuEvent('SelectSystem', {systemId: $(this).data('systemId') }); + }); + + // program status + $('.' + config.headProgramStatusClass).on('click', function(){ + $(document).triggerMenuEvent('ShowTaskManager'); }); $(document).on('pf:closeMenu', function(e){ @@ -324,8 +332,8 @@ define([ var tooltipElements = $('#' + config.pageHeaderId).find('[title]'); tooltipElements.tooltip({placement: 'bottom'}); - // trigger load main map module -> header is required for drag&drop position - $('#' + config.mapModuleId).trigger('pf:initModule'); + // trigger load main map module -> header is required for "System" drag&drop position + Util.getMapModule().trigger('pf:initModule'); } } @@ -409,7 +417,7 @@ define([ $(document).on('pf:menuShowMapInfo', function(e){ // show map information dialog - showMapInfoDialog(); + MapInfo.showDialog(); return false; }); @@ -418,7 +426,7 @@ define([ var mapData = false; if(data.newMap === false){ - var activeMap = $('#' + config.mapModuleId).getActiveMap(); + var activeMap = Util.getMapModule().getActiveMap(); if(activeMap){ mapData = activeMap.getMapData(true); @@ -429,6 +437,12 @@ define([ return false; }); + $(document).on('pf:menuShowTaskManager', function(e, data){ + // show log dialog + Logging.showDialog(); + return false; + }); + $(document).on('pf:menuFullScreen', function(e, data){ if(CCP.isInGameBrowser() === false){ @@ -459,7 +473,7 @@ define([ // update header links with current map data $(document).on('pf:updateHeaderData', function(e, data){ - var activeMap = $('#' + config.mapModuleId).getActiveMap(); + var activeMap = Util.getMapModule().getActiveMap(); var userCount = 0; var currentLocationData = {}; @@ -472,10 +486,8 @@ define([ userCount = data.userCount; currentLocationData = data; } - updateHeaderActiveUserCount(userCount); updateHeaderCurrentLocation(currentLocationData); - }); }; @@ -490,11 +502,13 @@ define([ if(badge.data('userCount') !== userCount){ badge.data('userCount', userCount); - if(userCount > 0){ - badge.text(userCount); + badge.text(userCount); + + badge.toggleClass('txt-color-green', (userCount > 0) ); + badge.toggleClass('txt-color-red', (userCount === 0) ); + + if(! activeUserElement.is(':visible')){ activeUserElement.velocity('fadeIn', {duration: Init.animationSpeed.headerLink}); - }else{ - activeUserElement.velocity('reverse'); } } }; @@ -513,6 +527,7 @@ define([ ){ var tempSystemName = locationData.currentSystemName; var tempSystemId = locationData.currentSystemId; + if( tempSystemName === undefined || tempSystemId === undefined @@ -524,11 +539,16 @@ define([ linkElement.data('systemName', tempSystemName); linkElement.data('systemId', tempSystemId); - if(locationData.currentSystemName){ + if( + tempSystemName !== false && + tempSystemId !== false + ){ textElement.text(locationData.currentSystemName); currentLocationElement.velocity('fadeIn', {duration: Init.animationSpeed.headerLink}); }else{ - currentLocationElement.velocity('reverse'); + if(currentLocationElement.is(':visible')){ + currentLocationElement.velocity('fadeOut', {duration: Init.animationSpeed.headerLink}); + } } } }; @@ -540,7 +560,7 @@ define([ var formData = {}; - requirejs(['text!templates/modules/map_dialog.html', 'lib/mustache'], function(template, Mustache) { + requirejs(['text!templates/modules/map_dialog.html', 'mustache'], function(template, Mustache) { var data = { id: config.newMapDialogId, @@ -553,9 +573,11 @@ define([ var content = Mustache.render(template, data); var dialogTitle = 'New map'; - + var dialogSaveButton = 'add map'; if(mapData !== false){ dialogTitle = 'Edit map'; + dialogSaveButton = 'save map'; + content = $(content); content.find('select[name="icon"]').val( mapData.config.icon ); content.find('input[name="name"]').val( mapData.config.name ); @@ -564,7 +586,6 @@ define([ } - var mapInfoDialog = bootbox.dialog({ title: dialogTitle, message: content, @@ -574,7 +595,7 @@ define([ className: 'btn-default' }, success: { - label: 'add map', + label: '' + dialogSaveButton, className: 'btn-primary', callback: function() { @@ -582,7 +603,7 @@ define([ var form = $('#' + config.newMapDialogId).find('form'); var newMapData = form.getFormValues(); - saveMapData(newMapData); + // TODO save map data } } } @@ -591,297 +612,19 @@ define([ }); }; - /** - * shows the map information modal dialog - * @param mapData - */ - var showMapInfoDialog = function(){ - var mapData = $('#' + config.mapModuleId).getActiveMap().getMapData(true); - - if(mapData !== false){ - requirejs(['text!templates/modules/map_info_dialog.html', 'lib/mustache'], function(template, Mustache) { - - var data = { - mapInfoSystemsId: config.mapInfoSystemsId, - mapInfoConnectionsId: config.mapInfoConnectionsId, - mapDataConfig: mapData.config, - mapName: mapData.config.name, - mapTypeClass: Util.getInfoForMap( mapData.config.type, 'class'), - mapTypeLabel: Util.getInfoForMap( mapData.config.type, 'label') - }; - - var content = Mustache.render(template, data); - - var mapInfoDialog = bootbox.dialog({ - title: 'Map information', - message: content, - buttons: { - success: { - label: 'close', - className: 'btn-primary', - callback: function() { - $(mapInfoDialog).modal('hide'); - } - } - } - }); - - mapInfoDialog.on('shown.bs.modal', function(e) { - // modal on open - - var systemsElement = $('#' + config.mapInfoSystemsId); - var connectionsElement = $('#' + config.mapInfoConnectionsId); - - var loadingOptions = { - icon: { - size: 'fa-2x' - } - }; - - - var systemTable = $('

', { - class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ') - }); - systemsElement.append(systemTable); - - systemsElement.showLoadingAnimation(loadingOptions); - - var connectionTable = $('
', { - class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ') - }); - connectionsElement.append(connectionTable); - - connectionsElement.showLoadingAnimation(loadingOptions); - - // systems table ================================================== - - // prepare data for dataTables - var systemsData = []; - for(var i = 0; i < mapData.data.systems.length; i++){ - var tempSystemData = mapData.data.systems[i]; - - var tempData = []; - - // current position - if(tempSystemData.currentUser === true){ - tempData.push( '' ); - }else{ - tempData.push( '' ); - } - - // active pilots - if(tempSystemData.userCount > 0){ - tempData.push(tempSystemData.userCount); - }else{ - tempData.push( '' ); - } - - // type - tempData.push(tempSystemData.type); - - // name - tempData.push( tempSystemData.name ); - - // alias - if( tempSystemData.name !== tempSystemData.alias){ - tempData.push( tempSystemData.alias ); - }else{ - tempData.push( '' ); - } - - // status - var systemStatusClass = Util.getStatusInfoForSystem(tempSystemData.status, 'class'); - if(systemStatusClass !== ''){ - tempData.push( '' ); - }else{ - tempData.push( '' ); - } - - // effect - var systemEffectClass = Util.getEffectInfoForSystem(tempSystemData.effect, 'class'); - if(systemEffectClass !== ''){ - tempData.push( '' ); - }else{ - tempData.push( '' ); - } - - // trueSec - var systemTrueSecClass = Util.getTrueSecClassForSystem(tempSystemData.trueSec); - if(systemTrueSecClass !== ''){ - tempData.push( '' + tempSystemData.trueSec.toFixed(1) + '' ); - }else{ - tempData.push( '' ); - } - - // locked - if(tempSystemData.locked === true){ - tempData.push( '' ); - }else{ - tempData.push( '' ); - } - - // rally point - if(tempSystemData.rally === true){ - tempData.push( '' ); - }else{ - tempData.push( '' ); - } - - systemsData.push(tempData); - } - - var systemsDataTable = systemTable.dataTable( { - paging: false, - ordering: true, - order: [ 0, 'desc' ], - autoWidth: false, - hover: false, - data: systemsData, - columnDefs: [], - language: { - emptyTable: 'Map is empty', - zeroRecords: 'No systems found', - lengthMenu: 'Show _MENU_ systems', - info: 'Showing _START_ to _END_ of _TOTAL_ systems' - }, - columns: [ - { - title: '', - width: '15px', - searchable: false - },{ - title: '', - width: '18px', - searchable: false - },{ - title: 'type', - width: '50px' - },{ - title: 'system', - width: '50px' - },{ - title: 'alias' - },{ - title: 'status', - width: '30px', - class: 'text-center', - orderable: false, - searchable: false - },{ - title: 'effect', - width: '30px', - class: 'text-center', - orderable: false, - searchable: false - },{ - title: 'sec.', - width: '20px', - class: 'text-center', - orderable: false, - searchable: false - },{ - title: '', - width: '30px', - class: 'text-center', - searchable: false - },{ - title: '', - width: '30px', - className: 'text-center', - searchable: false - } - ] - }); - - systemsElement.hideLoadingAnimation(); - - // connections table ================================================== - - // prepare data for dataTables - var connectionData = []; - for(var j = 0; j < mapData.data.connections.length; j++){ - var tempConnectionData = mapData.data.connections[j]; - - var tempConData = []; - - tempConData.push( Util.getScopeInfoForMap( tempConnectionData.scope, 'label') ); - - // source system name - tempConData.push( tempConnectionData.sourceName ); - - // connection - var connectionClasses = []; - for(var k = 0; k < tempConnectionData.type.length; k++){ - connectionClasses.push( Util.getConnectionInfo( tempConnectionData.type[k], 'cssClass') ); - - } - - connectionClasses = connectionClasses.join(' '); - - tempConData.push( '
' ); - - - tempConData.push( tempConnectionData.targetName ); - - connectionData.push(tempConData); - } - - var connectionDataTable = connectionTable.dataTable( { - paging: false, - ordering: true, - order: [ 0, 'desc' ], - autoWidth: false, - hover: false, - data: connectionData, - columnDefs: [], - language: { - emptyTable: 'No connections', - zeroRecords: 'No connections found', - lengthMenu: 'Show _MENU_ connections', - info: 'Showing _START_ to _END_ of _TOTAL_ connections' - }, - columns: [ - { - title: 'scope', - width: '50px', - orderable: false - },{ - title: 'source system' - },{ - title: 'connection', - width: '80px', - class: 'text-center', - orderable: false, - searchable: false - },{ - title: 'target system' - } - ] - }); - - - connectionsElement.hideLoadingAnimation(); - - - }); - - }); - } - - }; /** * shows the map manual modal dialog */ var showMapManual = function(){ - requirejs(['text!templates/modules/map_manual_dialog.html', 'lib/mustache'], function(template, Mustache) { + requirejs(['text!templates/modules/map_manual_dialog.html', 'mustache'], function(template, Mustache) { var data = { + dialogNavigationClass: config.dialogNavigationClass, + dialogNavLiClass: config.dialogNavigationListItemClass, scrollspyId: config.mapManualScrollspyId, - scrollspyNavClass: config.mapManualScrollspyNavClass, - scrollspyNavLiClass: config.mapManualNavigationListItemClass, pieChartClass : Init.classes.pieChart.pieChartMapCounterClass, mapCounterClass : Init.classes.pieChart.pieChartMapCounterClass, @@ -899,7 +642,6 @@ define([ var mapManualDialog = bootbox.dialog({ title: 'Pathfinder manual', message: content, - className: 'medium', buttons: { success: { label: 'close', @@ -928,7 +670,7 @@ define([ mapManualDialog.on('shown.bs.modal', function(e) { // modal on open scrolLBreakpointElements = $('.pf-manual-scroll-break'); - scrollNavLiElements = $('.' + config.mapManualNavigationListItemClass); + scrollNavLiElements = $('.' + config.dialogNavigationListItemClass); }); var scrollspyElement = $('#' + config.mapManualScrollspyId); @@ -975,7 +717,7 @@ define([ scrollspyElement.find('.' + data.mapCounterClass).initMapUpdateCounter(); // set navigation button observer - var mainNavigationLinks = $('.' + config.mapManualScrollspyNavClass).find('a'); + var mainNavigationLinks = $('.' + config.dialogNavigationClass).find('a'); // text anchor links var subNavigationLinks = scrollspyElement.find('a[data-target]'); @@ -989,7 +731,7 @@ define([ // scroll to anchor scrollspyElement.mCustomScrollbar("scrollTo", $(this).attr('data-target')); - var mainNavigationLiElement = $(this).parent('.' + config.mapManualNavigationListItemClass); + var mainNavigationLiElement = $(this).parent('.' + config.dialogNavigationListItemClass); whileScrolling(); @@ -1047,7 +789,7 @@ define([ */ var showJumpInfoDialog = function(){ - requirejs(['text!templates/modules/jump_info_dialog.html', 'lib/mustache'], function(template, Mustache) { + requirejs(['text!templates/modules/jump_info_dialog.html', 'mustache'], function(template, Mustache) { var data = {}; @@ -1141,6 +883,8 @@ define([ }; + + /** * trigger "program status" in head * @param status @@ -1181,18 +925,54 @@ define([ // change status, on status changed if(iconClass !== false){ - statusElement.velocity('fadeOut', { - duration: Init.animationSpeed.headerLink, - complete: function(){ - statusElement.removeClass('txt-color-green txt-color-orange txt-color-red'); - icon.removeClass('fa-wifi fa-warning fa-bolt'); - statusElement.addClass(textClass); - icon.addClass(iconClass); - textElement.text(text); + if(! statusElement.hasClass(textClass) ){ + + if(! programStatusInterval){ + + var timer = function(){ + + // change status on first timer iteration + if(programStatusCounter === Init.timer.programStatusVisible){ + + statusElement.velocity('stop').velocity('fadeOut', { + duration: Init.animationSpeed.headerLink, + complete: function(){ + statusElement.removeClass('txt-color-green txt-color-orange txt-color-red'); + icon.removeClass('fa-wifi fa-warning fa-bolt'); + statusElement.addClass(textClass); + icon.addClass(iconClass); + textElement.text(text); + } + }).velocity('fadeIn', { + duration: Init.animationSpeed.headerLink + }); + + } + + + programStatusCounter--; + + if(programStatusCounter <= 0){ + clearInterval(programStatusInterval); + programStatusInterval = false; + } + }; + + if(! programStatusInterval){ + programStatusCounter = Init.timer.programStatusVisible; + programStatusInterval = setInterval(timer, 1000); + + } + + } - }).velocity('fadeIn', { - duration: Init.animationSpeed.headerLink - }); + + + + } + + + } diff --git a/js/app/render.js b/js/app/render.js index b78f4514..35a12b30 100644 --- a/js/app/render.js +++ b/js/app/render.js @@ -2,7 +2,7 @@ * Render controller */ -define(['jquery', 'lib/mustache'], function($, Mustache) { +define(['jquery', 'mustache'], function($, Mustache) { "use strict"; diff --git a/js/app/ui/map_info.js b/js/app/ui/map_info.js new file mode 100644 index 00000000..bc8873a2 --- /dev/null +++ b/js/app/ui/map_info.js @@ -0,0 +1,415 @@ +/** + * map info dialog + */ + +define([ + 'jquery', + 'app/util', + 'bootbox' +], function($, Util, bootbox) { + + 'use strict'; + + var config = { + // global dialog + dialogNavigationClass: 'pf-dialog-navigation-list', // class for dialog navigation bar + dialogNavigationListItemClass: 'pf-dialog-navigation-list-item', // class for map manual li main navigation elements + + // map info dialog + mapInfoId: 'pf-map-info', // id for map info + mapInfoSystemsId: 'pf-map-info-systems', // id for map info systems box + mapInfoConnectionsId: 'pf-map-info-connections', // id for map info connections box + mapInfoTableClass: 'pf-map-info-table', // class for data + + loadingOptions: { // config for loading overlay + icon: { + size: 'fa-2x' + } + } + }; + + /** + * loads the map info data into an element + * @param mapData + */ + $.fn.loadMapInfoData = function(mapData){ + + var mapElement = $(this); + + mapElement.empty(); + + mapElement.showLoadingAnimation(config.loadingOptions); + + var countSystems = mapData.data.systems.length; + var countConnections = mapData.data.connections.length; + + + var dlElementLeft = $('
', { + class: 'dl-horizontal', + css: {'float': 'left'} + }).append( + $('
').text( 'Icon' ) + ).append( + $('
').append( + $('', { + class: ['fa', 'fa-fw', mapData.config.icon].join(' ') + }) + ) + ).append( + $('
').text( 'Name' ) + ).append( + $('
').text( mapData.config.name ) + ).append( + $('
').text( 'Type' ) + ).append( + $('
', { + class: Util.getInfoForMap( mapData.config.type, 'class') + }).text( Util.getInfoForMap( mapData.config.type, 'label') ) + ); + + mapElement.append(dlElementLeft); + + var dlElementRight = $('
', { + class: 'dl-horizontal', + css: {'float': 'right'} + }).append( + $('
').text( 'Systems' ) + ).append( + $('
').text( countSystems ) + ).append( + $('
').text( 'Connections' ) + ).append( + $('
').text( countConnections ) + ); + + mapElement.append(dlElementRight); + + + mapElement.hideLoadingAnimation(); + + }; + + /** + * loads the system info table into an element + * @param mapData + */ + $.fn.loadSystemInfoTable = function(mapData){ + + var systemsElement = $(this); + + systemsElement.empty(); + + var systemTable = $('
', { + class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ') + }); + systemsElement.append(systemTable); + + systemsElement.showLoadingAnimation(config.loadingOptions); + + + // prepare data for dataTables + var systemsData = []; + for(var i = 0; i < mapData.data.systems.length; i++){ + var tempSystemData = mapData.data.systems[i]; + + var tempData = []; + + // current position + if(tempSystemData.currentUser === true){ + tempData.push( '' ); + }else{ + tempData.push( '' ); + } + + // active pilots + if(tempSystemData.userCount > 0){ + tempData.push(tempSystemData.userCount); + }else{ + tempData.push( '' ); + } + + // type + tempData.push(tempSystemData.type); + + // name + tempData.push( tempSystemData.name ); + + // alias + if( tempSystemData.name !== tempSystemData.alias){ + tempData.push( tempSystemData.alias ); + }else{ + tempData.push( '' ); + } + + // status + var systemStatusClass = Util.getStatusInfoForSystem(tempSystemData.status, 'class'); + if(systemStatusClass !== ''){ + tempData.push( '' ); + }else{ + tempData.push( '' ); + } + + // effect + var systemEffectClass = Util.getEffectInfoForSystem(tempSystemData.effect, 'class'); + if(systemEffectClass !== ''){ + tempData.push( '' ); + }else{ + tempData.push( '' ); + } + + // trueSec + var systemTrueSecClass = Util.getTrueSecClassForSystem(tempSystemData.trueSec); + if(systemTrueSecClass !== ''){ + tempData.push( '' + tempSystemData.trueSec.toFixed(1) + '' ); + }else{ + tempData.push( '' ); + } + + // locked + if(tempSystemData.locked === true){ + tempData.push( '' ); + }else{ + tempData.push( '' ); + } + + // rally point + if(tempSystemData.rally === true){ + tempData.push( '' ); + }else{ + tempData.push( '' ); + } + + systemsData.push(tempData); + } + + var systemsDataTable = systemTable.dataTable( { + paging: false, + ordering: true, + order: [ 0, 'desc' ], + autoWidth: false, + hover: false, + data: systemsData, + columnDefs: [], + language: { + emptyTable: 'Map is empty', + zeroRecords: 'No systems found', + lengthMenu: 'Show _MENU_ systems', + info: 'Showing _START_ to _END_ of _TOTAL_ systems' + }, + columns: [ + { + title: '', + width: '15px', + searchable: false + },{ + title: '', + width: '18px', + searchable: false + },{ + title: 'type', + width: '50px' + },{ + title: 'system', + width: '50px' + },{ + title: 'alias' + },{ + title: 'status', + width: '30px', + class: 'text-center', + orderable: false, + searchable: false + },{ + title: 'effect', + width: '30px', + class: 'text-center', + orderable: false, + searchable: false + },{ + title: 'sec.', + width: '20px', + class: 'text-center', + orderable: false, + searchable: false + },{ + title: '', + width: '30px', + class: 'text-center', + searchable: false + },{ + title: '', + width: '30px', + className: 'text-center', + searchable: false + } + ] + }); + + systemsElement.hideLoadingAnimation(); + + }; + + /** + * loads the connection info table into an element + * @param mapData + */ + $.fn.loadConnectionInfoTable = function(mapData){ + var connectionsElement = $(this); + + connectionsElement.empty(); + + var connectionTable = $('
', { + class: ['compact', 'stripe', 'order-column', 'row-border', config.mapInfoTableClass].join(' ') + }); + connectionsElement.append(connectionTable); + + connectionsElement.showLoadingAnimation(config.loadingOptions); + + // connections table ================================================== + + // prepare data for dataTables + var connectionData = []; + for(var j = 0; j < mapData.data.connections.length; j++){ + var tempConnectionData = mapData.data.connections[j]; + + var tempConData = []; + + tempConData.push( Util.getScopeInfoForMap( tempConnectionData.scope, 'label') ); + + // source system name + tempConData.push( tempConnectionData.sourceName ); + + // connection + var connectionClasses = []; + for(var k = 0; k < tempConnectionData.type.length; k++){ + connectionClasses.push( Util.getConnectionInfo( tempConnectionData.type[k], 'cssClass') ); + + } + + connectionClasses = connectionClasses.join(' '); + + tempConData.push( '
' ); + + + tempConData.push( tempConnectionData.targetName ); + + connectionData.push(tempConData); + } + + var connectionDataTable = connectionTable.dataTable( { + paging: false, + ordering: true, + order: [ 0, 'desc' ], + autoWidth: false, + hover: false, + data: connectionData, + columnDefs: [], + language: { + emptyTable: 'No connections', + zeroRecords: 'No connections found', + lengthMenu: 'Show _MENU_ connections', + info: 'Showing _START_ to _END_ of _TOTAL_ connections' + }, + columns: [ + { + title: 'scope', + width: '50px', + orderable: false + },{ + title: 'source system' + },{ + title: 'connection', + width: '80px', + class: 'text-center', + orderable: false, + searchable: false + },{ + title: 'target system' + } + ] + }); + + + connectionsElement.hideLoadingAnimation(); + }; + + /** + * shows the map information modal dialog + */ + var showDialog = function(){ + + var activeMap = Util.getMapModule().getActiveMap(); + var mapData = activeMap.getMapData(true); + + if(mapData !== false){ + requirejs(['text!templates/modules/map_info_dialog.html', 'mustache'], function(template, Mustache) { + + var data = { + dialogNavigationClass: config.dialogNavigationClass, + dialogNavLiClass: config.dialogNavigationListItemClass, + mapInfoId: config.mapInfoId, + mapInfoSystemsId: config.mapInfoSystemsId, + mapInfoConnectionsId: config.mapInfoConnectionsId + }; + + var content = Mustache.render(template, data); + + + + var mapInfoDialog = bootbox.dialog({ + title: 'Map information', + message: content, + buttons: { + success: { + label: 'close', + className: 'btn-primary', + callback: function() { + $(mapInfoDialog).modal('hide'); + } + } + } + }); + + mapInfoDialog.on('shown.bs.modal', function(e) { + // modal on open + + var mapElement = $('#' + config.mapInfoId); + var systemsElement = $('#' + config.mapInfoSystemsId); + var connectionsElement = $('#' + config.mapInfoConnectionsId); + + // set navigation button observer + var mainNavigationLinks = $('.' + config.dialogNavigationClass).find('a'); + mainNavigationLinks.on('click', function(){ + var menuAction = $(this).attr('data-action'); + + if(menuAction === 'refresh'){ + // get new map data + var mapData = activeMap.getMapData(true); + + mapElement.loadMapInfoData(mapData); + systemsElement.loadSystemInfoTable(mapData); + connectionsElement.loadConnectionInfoTable(mapData); + } + }); + + // load map data + mapElement.loadMapInfoData(mapData); + + // load system table + systemsElement.loadSystemInfoTable(mapData); + + // load connection table + connectionsElement.loadConnectionInfoTable(mapData); + }); + + }); + } + + }; + + + + return { + showDialog: showDialog + }; +}); \ No newline at end of file diff --git a/js/app/util.js b/js/app/util.js index 562e51f4..887ff06d 100644 --- a/js/app/util.js +++ b/js/app/util.js @@ -12,9 +12,11 @@ define([ var config = { ajaxOverlayClass: 'pf-loading-overlay', ajaxOverlayWrapperClass: 'pf-loading-overlay-wrapper', - ajaxOverlayVisibleClass: 'pf-loading-overlay-visible', - formEditableFieldClass: 'pf-editable', // Class for all xEditable fields + formEditableFieldClass: 'pf-editable', // Class for all xEditable fields + + // map module + mapModuleId: 'pf-map-module', // main map module // available map ions mapIcons: [ @@ -47,7 +49,84 @@ define([ }; + var stopTimerCache = {}; // cache for stopwatch timer + /** + * get date obj with current EVE Server Time. -> Server Time === UTC time + * @returns {Date} + */ + var getServerTime = function(){ + + var localDate = new Date(); + + var serverDate= new Date( + localDate.getUTCFullYear(), + localDate.getUTCMonth(), + localDate.getUTCDate(), + localDate.getUTCHours(), + localDate.getUTCMinutes(), + localDate.getUTCSeconds() + ); + + return serverDate; + }; + + /** + * start time measurement by a unique string identifier + * @param timerName + */ + var timeStart = function(timerName){ + + if( !(stopTimerCache.hasOwnProperty(timerName)) ){ + + if(typeof performance === 'object'){ + stopTimerCache[timerName] = performance.now(); + }else{ + stopTimerCache[timerName] = new Date().getTime(); + } + }else{ + console.log('nooo') + } + }; + + /** + * get time delta between timeStart() and timeStop() by a unique string identifier + * @param timerName + * @returns {number} + */ + var timeStop = function(timerName){ + + var duration = 0; + + if( stopTimerCache.hasOwnProperty(timerName) ){ + // check browser support for performance API + var timeNow = 0; + + if(typeof performance === 'object'){ + timeNow = performance.now(); + }else{ + timeNow = new Date(); + } + + // format ms time duration + duration = Number( (timeNow - stopTimerCache[timerName] ).toFixed(2) ); + + // delete key + delete( stopTimerCache[timerName]); + } + + return duration; + }; + + + /** + * trigger main logging event with log information + * @param message + * @param options + */ + var log = function(logKey, options){ + $(window).trigger('pf:log', [logKey, options]); + }; /** * displays a loading indicator on an element @@ -80,10 +159,11 @@ define([ $(this).append(overlay); // fade in - setTimeout(function(){ - $(overlay).addClass( config.ajaxOverlayVisibleClass ); - }, 10); - + $(overlay).velocity({ + opacity: 0.6 + },{ + duration: 200 + }); }; /** @@ -92,13 +172,12 @@ define([ $.fn.hideLoadingAnimation = function(){ var overlay = $(this).find('.' + config.ajaxOverlayClass ); - $(overlay).removeClass( config.ajaxOverlayVisibleClass ); - - // remove overlay after fade out transition - setTimeout(function(){ - $(overlay).remove(); - }, 150); + $(overlay).velocity('reverse', { + complete: function(){ + $(this).remove(); + } + }); }; /** @@ -151,6 +230,24 @@ define([ ); }; + /** + * add a temporary class elements for a certain time + * @param className + * @param duration + * @returns {*} + */ + $.fn.addTemporaryClass = function(className, duration){ + + var elements = this; + setTimeout(function() { + elements.removeClass(className); + }, duration); + + return this.each(function() { + $(this).addClass(className); + }); + }; + /** * trigger a notification (on screen or desktop) * @param customConfig @@ -163,6 +260,21 @@ define([ }); }; + /** + * get log entry info + * @param logType + * @param option + * @returns {string} + */ + var getLogInfo = function(logType, option){ + var logInfo = ''; + + if(Init.classes.logTypes.hasOwnProperty(logType)){ + logInfo = Init.classes.logTypes[logType][option]; + } + + return logInfo; + }; // ================================================================================================== @@ -195,6 +307,22 @@ define([ }; + /** + * get the map module object or create a new module + * @returns {*|HTMLElement} + */ + var getMapModule = function(){ + + var mapModule = $('#' + config.mapModuleId); + if(mapModule.length === 0){ + mapModule = $('
', { + id: config.mapModuleId + }); + } + + return mapModule; + }; + /** * get all available map icons * @returns {*} @@ -542,7 +670,13 @@ define([ }; return { + getServerTime: getServerTime, + timeStart: timeStart, + timeStop: timeStop, + log: log, showNotify: showNotify, + getLogInfo: getLogInfo, + getMapModule: getMapModule, getMapIcons: getMapIcons, getMapTypes: getMapTypes, getInfoForMap: getInfoForMap, diff --git a/js/lib/dataTables.bootstrap.js b/js/lib/datatables/dataTables.bootstrap.js similarity index 98% rename from js/lib/dataTables.bootstrap.js rename to js/lib/datatables/dataTables.bootstrap.js index 2b77e7b3..5436e3c9 100644 --- a/js/lib/dataTables.bootstrap.js +++ b/js/lib/datatables/dataTables.bootstrap.js @@ -170,7 +170,7 @@ if ( DataTable.TableTools ) { // Define as an AMD module if possible if ( typeof define === 'function' && define.amd ) { - define( ['jquery', 'datatables'], factory ); + define( ['jquery', ''], factory ); } else if ( typeof exports === 'object' ) { // Node/CommonJS diff --git a/js/lib/datatables/extensions/TableTools/Readme.txt b/js/lib/datatables/extensions/TableTools/Readme.txt new file mode 100644 index 00000000..d83ea9f5 --- /dev/null +++ b/js/lib/datatables/extensions/TableTools/Readme.txt @@ -0,0 +1,41 @@ +# TableTools + +TableTools is a plug-in for the DataTables HTML table enhancer, which adds a highly customisable button toolbar to a DataTable. Key features include: + +* Copy to clipboard +* Save table data as CSV, XLS or PDF files +* Print view for clean printing +* Row selection options +* Easy use predefined buttons +* Simple customisation of buttons +* Well defined API for advanced control + + +# Installation + +To use TableTools, first download DataTables ( http://datatables.net/download ) and place the unzipped TableTools package into a `extensions` directory in the DataTables package (in DataTables 1.9- use the `extras` directory). This will allow the pages in the examples to operate correctly. To see the examples running, open the `examples` directory in your web-browser. + + +# Basic usage + +TableTools is initialised using the `T` option that it adds to DataTables' `dom` option. For example: + +```js +$(document).ready( function () { + $('#example').DataTable( { + dom: 'T<"clear">lfrtip' + } ); +} ); +``` + + +# Documentation / support + +* Documentation: http://datatables.net/extensions/tabletools/ +* DataTables support forums: http://datatables.net/forums + + +# GitHub + +If you fancy getting involved with the development of TableTools and help make it better, please refer to its GitHub repo: https://github.com/DataTables/TableTools + diff --git a/js/lib/datatables/extensions/TableTools/js/dataTables.tableTools.js b/js/lib/datatables/extensions/TableTools/js/dataTables.tableTools.js new file mode 100644 index 00000000..4fd3939c --- /dev/null +++ b/js/lib/datatables/extensions/TableTools/js/dataTables.tableTools.js @@ -0,0 +1,3215 @@ +/*! TableTools 2.2.3 + * 2009-2014 SpryMedia Ltd - datatables.net/license + * + * ZeroClipboard 1.0.4 + * Author: Joseph Huckaby - MIT licensed + */ + +/** + * @summary TableTools + * @description Tools and buttons for DataTables + * @version 2.2.3 + * @file dataTables.tableTools.js + * @author SpryMedia Ltd (www.sprymedia.co.uk) + * @contact www.sprymedia.co.uk/contact + * @copyright Copyright 2009-2014 SpryMedia Ltd. + * + * This source file is free software, available under the following license: + * MIT license - http://datatables.net/license/mit + * + * This source file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details. + * + * For details please refer to: http://www.datatables.net + */ + + +/* Global scope for TableTools for backwards compatibility. + * Will be removed in 2.3 + */ +var TableTools; + +(function(window, document, undefined) { + + +var factory = function( $, DataTable ) { +"use strict"; + + +//include ZeroClipboard.js +/* ZeroClipboard 1.0.4 + * Author: Joseph Huckaby + */ + +var ZeroClipboard_TableTools = { + + version: "1.0.4-TableTools2", + clients: {}, // registered upload clients on page, indexed by id + moviePath: '', // URL to movie + nextId: 1, // ID of next movie + + $: function(thingy) { + // simple DOM lookup utility function + if (typeof(thingy) == 'string') { + thingy = document.getElementById(thingy); + } + if (!thingy.addClass) { + // extend element with a few useful methods + thingy.hide = function() { this.style.display = 'none'; }; + thingy.show = function() { this.style.display = ''; }; + thingy.addClass = function(name) { this.removeClass(name); this.className += ' ' + name; }; + thingy.removeClass = function(name) { + this.className = this.className.replace( new RegExp("\\s*" + name + "\\s*"), " ").replace(/^\s+/, '').replace(/\s+$/, ''); + }; + thingy.hasClass = function(name) { + return !!this.className.match( new RegExp("\\s*" + name + "\\s*") ); + }; + } + return thingy; + }, + + setMoviePath: function(path) { + // set path to ZeroClipboard.swf + this.moviePath = path; + }, + + dispatch: function(id, eventName, args) { + // receive event from flash movie, send to client + var client = this.clients[id]; + if (client) { + client.receiveEvent(eventName, args); + } + }, + + register: function(id, client) { + // register new client to receive events + this.clients[id] = client; + }, + + getDOMObjectPosition: function(obj) { + // get absolute coordinates for dom element + var info = { + left: 0, + top: 0, + width: obj.width ? obj.width : obj.offsetWidth, + height: obj.height ? obj.height : obj.offsetHeight + }; + + if ( obj.style.width !== "" ) { + info.width = obj.style.width.replace("px",""); + } + + if ( obj.style.height !== "" ) { + info.height = obj.style.height.replace("px",""); + } + + while (obj) { + info.left += obj.offsetLeft; + info.top += obj.offsetTop; + obj = obj.offsetParent; + } + + return info; + }, + + Client: function(elem) { + // constructor for new simple upload client + this.handlers = {}; + + // unique ID + this.id = ZeroClipboard_TableTools.nextId++; + this.movieId = 'ZeroClipboard_TableToolsMovie_' + this.id; + + // register client with singleton to receive flash events + ZeroClipboard_TableTools.register(this.id, this); + + // create movie + if (elem) { + this.glue(elem); + } + } +}; + +ZeroClipboard_TableTools.Client.prototype = { + + id: 0, // unique ID for us + ready: false, // whether movie is ready to receive events or not + movie: null, // reference to movie object + clipText: '', // text to copy to clipboard + fileName: '', // default file save name + action: 'copy', // action to perform + handCursorEnabled: true, // whether to show hand cursor, or default pointer cursor + cssEffects: true, // enable CSS mouse effects on dom container + handlers: null, // user event handlers + sized: false, + + glue: function(elem, title) { + // glue to DOM element + // elem can be ID or actual DOM element object + this.domElement = ZeroClipboard_TableTools.$(elem); + + // float just above object, or zIndex 99 if dom element isn't set + var zIndex = 99; + if (this.domElement.style.zIndex) { + zIndex = parseInt(this.domElement.style.zIndex, 10) + 1; + } + + // find X/Y position of domElement + var box = ZeroClipboard_TableTools.getDOMObjectPosition(this.domElement); + + // create floating DIV above element + this.div = document.createElement('div'); + var style = this.div.style; + style.position = 'absolute'; + style.left = '0px'; + style.top = '0px'; + style.width = (box.width) + 'px'; + style.height = box.height + 'px'; + style.zIndex = zIndex; + + if ( typeof title != "undefined" && title !== "" ) { + this.div.title = title; + } + if ( box.width !== 0 && box.height !== 0 ) { + this.sized = true; + } + + // style.backgroundColor = '#f00'; // debug + if ( this.domElement ) { + this.domElement.appendChild(this.div); + this.div.innerHTML = this.getHTML( box.width, box.height ).replace(/&/g, '&'); + } + }, + + positionElement: function() { + var box = ZeroClipboard_TableTools.getDOMObjectPosition(this.domElement); + var style = this.div.style; + + style.position = 'absolute'; + //style.left = (this.domElement.offsetLeft)+'px'; + //style.top = this.domElement.offsetTop+'px'; + style.width = box.width + 'px'; + style.height = box.height + 'px'; + + if ( box.width !== 0 && box.height !== 0 ) { + this.sized = true; + } else { + return; + } + + var flash = this.div.childNodes[0]; + flash.width = box.width; + flash.height = box.height; + }, + + getHTML: function(width, height) { + // return HTML for movie + var html = ''; + var flashvars = 'id=' + this.id + + '&width=' + width + + '&height=' + height; + + if (navigator.userAgent.match(/MSIE/)) { + // IE gets an OBJECT tag + var protocol = location.href.match(/^https/i) ? 'https://' : 'http://'; + html += ''; + } + else { + // all other browsers get an EMBED tag + html += ''; + } + return html; + }, + + hide: function() { + // temporarily hide floater offscreen + if (this.div) { + this.div.style.left = '-2000px'; + } + }, + + show: function() { + // show ourselves after a call to hide() + this.reposition(); + }, + + destroy: function() { + // destroy control and floater + if (this.domElement && this.div) { + this.hide(); + this.div.innerHTML = ''; + + var body = document.getElementsByTagName('body')[0]; + try { body.removeChild( this.div ); } catch(e) {} + + this.domElement = null; + this.div = null; + } + }, + + reposition: function(elem) { + // reposition our floating div, optionally to new container + // warning: container CANNOT change size, only position + if (elem) { + this.domElement = ZeroClipboard_TableTools.$(elem); + if (!this.domElement) { + this.hide(); + } + } + + if (this.domElement && this.div) { + var box = ZeroClipboard_TableTools.getDOMObjectPosition(this.domElement); + var style = this.div.style; + style.left = '' + box.left + 'px'; + style.top = '' + box.top + 'px'; + } + }, + + clearText: function() { + // clear the text to be copy / saved + this.clipText = ''; + if (this.ready) { + this.movie.clearText(); + } + }, + + appendText: function(newText) { + // append text to that which is to be copied / saved + this.clipText += newText; + if (this.ready) { this.movie.appendText(newText) ;} + }, + + setText: function(newText) { + // set text to be copied to be copied / saved + this.clipText = newText; + if (this.ready) { this.movie.setText(newText) ;} + }, + + setCharSet: function(charSet) { + // set the character set (UTF16LE or UTF8) + this.charSet = charSet; + if (this.ready) { this.movie.setCharSet(charSet) ;} + }, + + setBomInc: function(bomInc) { + // set if the BOM should be included or not + this.incBom = bomInc; + if (this.ready) { this.movie.setBomInc(bomInc) ;} + }, + + setFileName: function(newText) { + // set the file name + this.fileName = newText; + if (this.ready) { + this.movie.setFileName(newText); + } + }, + + setAction: function(newText) { + // set action (save or copy) + this.action = newText; + if (this.ready) { + this.movie.setAction(newText); + } + }, + + addEventListener: function(eventName, func) { + // add user event listener for event + // event types: load, queueStart, fileStart, fileComplete, queueComplete, progress, error, cancel + eventName = eventName.toString().toLowerCase().replace(/^on/, ''); + if (!this.handlers[eventName]) { + this.handlers[eventName] = []; + } + this.handlers[eventName].push(func); + }, + + setHandCursor: function(enabled) { + // enable hand cursor (true), or default arrow cursor (false) + this.handCursorEnabled = enabled; + if (this.ready) { + this.movie.setHandCursor(enabled); + } + }, + + setCSSEffects: function(enabled) { + // enable or disable CSS effects on DOM container + this.cssEffects = !!enabled; + }, + + receiveEvent: function(eventName, args) { + var self; + + // receive event from flash + eventName = eventName.toString().toLowerCase().replace(/^on/, ''); + + // special behavior for certain events + switch (eventName) { + case 'load': + // movie claims it is ready, but in IE this isn't always the case... + // bug fix: Cannot extend EMBED DOM elements in Firefox, must use traditional function + this.movie = document.getElementById(this.movieId); + if (!this.movie) { + self = this; + setTimeout( function() { self.receiveEvent('load', null); }, 1 ); + return; + } + + // firefox on pc needs a "kick" in order to set these in certain cases + if (!this.ready && navigator.userAgent.match(/Firefox/) && navigator.userAgent.match(/Windows/)) { + self = this; + setTimeout( function() { self.receiveEvent('load', null); }, 100 ); + this.ready = true; + return; + } + + this.ready = true; + this.movie.clearText(); + this.movie.appendText( this.clipText ); + this.movie.setFileName( this.fileName ); + this.movie.setAction( this.action ); + this.movie.setCharSet( this.charSet ); + this.movie.setBomInc( this.incBom ); + this.movie.setHandCursor( this.handCursorEnabled ); + break; + + case 'mouseover': + if (this.domElement && this.cssEffects) { + //this.domElement.addClass('hover'); + if (this.recoverActive) { + this.domElement.addClass('active'); + } + } + break; + + case 'mouseout': + if (this.domElement && this.cssEffects) { + this.recoverActive = false; + if (this.domElement.hasClass('active')) { + this.domElement.removeClass('active'); + this.recoverActive = true; + } + //this.domElement.removeClass('hover'); + } + break; + + case 'mousedown': + if (this.domElement && this.cssEffects) { + this.domElement.addClass('active'); + } + break; + + case 'mouseup': + if (this.domElement && this.cssEffects) { + this.domElement.removeClass('active'); + this.recoverActive = false; + } + break; + } // switch eventName + + if (this.handlers[eventName]) { + for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) { + var func = this.handlers[eventName][idx]; + + if (typeof(func) == 'function') { + // actual function reference + func(this, args); + } + else if ((typeof(func) == 'object') && (func.length == 2)) { + // PHP style object + method, i.e. [myObject, 'myMethod'] + func[0][ func[1] ](this, args); + } + else if (typeof(func) == 'string') { + // name of function + window[func](this, args); + } + } // foreach event handler defined + } // user defined handler for event + } + +}; + +// For the Flash binding to work, ZeroClipboard_TableTools must be on the global +// object list +window.ZeroClipboard_TableTools = ZeroClipboard_TableTools; +//include TableTools.js +/* TableTools + * 2009-2014 SpryMedia Ltd - datatables.net/license + */ + +/*globals TableTools,ZeroClipboard_TableTools*/ + + +(function($, window, document) { + +/** + * TableTools provides flexible buttons and other tools for a DataTables enhanced table + * @class TableTools + * @constructor + * @param {Object} oDT DataTables instance. When using DataTables 1.10 this can + * also be a jQuery collection, jQuery selector, table node, DataTables API + * instance or DataTables settings object. + * @param {Object} oOpts TableTools options + * @param {String} oOpts.sSwfPath ZeroClipboard SWF path + * @param {String} oOpts.sRowSelect Row selection options - 'none', 'single', 'multi' or 'os' + * @param {Function} oOpts.fnPreRowSelect Callback function just prior to row selection + * @param {Function} oOpts.fnRowSelected Callback function just after row selection + * @param {Function} oOpts.fnRowDeselected Callback function when row is deselected + * @param {Array} oOpts.aButtons List of buttons to be used + */ +TableTools = function( oDT, oOpts ) +{ + /* Santiy check that we are a new instance */ + if ( ! this instanceof TableTools ) + { + alert( "Warning: TableTools must be initialised with the keyword 'new'" ); + } + + // In 1.10 we can use the API to get the settings object from a number of + // sources + var dtSettings = $.fn.dataTable.Api ? + new $.fn.dataTable.Api( oDT ).settings()[0] : + oDT.fnSettings(); + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public class variables + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * @namespace Settings object which contains customisable information for TableTools instance + */ + this.s = { + /** + * Store 'this' so the instance can be retrieved from the settings object + * @property that + * @type object + * @default this + */ + "that": this, + + /** + * DataTables settings objects + * @property dt + * @type object + * @default From the oDT init option + */ + "dt": dtSettings, + + /** + * @namespace Print specific information + */ + "print": { + /** + * DataTables draw 'start' point before the printing display was shown + * @property saveStart + * @type int + * @default -1 + */ + "saveStart": -1, + + /** + * DataTables draw 'length' point before the printing display was shown + * @property saveLength + * @type int + * @default -1 + */ + "saveLength": -1, + + /** + * Page scrolling point before the printing display was shown so it can be restored + * @property saveScroll + * @type int + * @default -1 + */ + "saveScroll": -1, + + /** + * Wrapped function to end the print display (to maintain scope) + * @property funcEnd + * @type Function + * @default function () {} + */ + "funcEnd": function () {} + }, + + /** + * A unique ID is assigned to each button in each instance + * @property buttonCounter + * @type int + * @default 0 + */ + "buttonCounter": 0, + + /** + * @namespace Select rows specific information + */ + "select": { + /** + * Select type - can be 'none', 'single' or 'multi' + * @property type + * @type string + * @default "" + */ + "type": "", + + /** + * Array of nodes which are currently selected + * @property selected + * @type array + * @default [] + */ + "selected": [], + + /** + * Function to run before the selection can take place. Will cancel the select if the + * function returns false + * @property preRowSelect + * @type Function + * @default null + */ + "preRowSelect": null, + + /** + * Function to run when a row is selected + * @property postSelected + * @type Function + * @default null + */ + "postSelected": null, + + /** + * Function to run when a row is deselected + * @property postDeselected + * @type Function + * @default null + */ + "postDeselected": null, + + /** + * Indicate if all rows are selected (needed for server-side processing) + * @property all + * @type boolean + * @default false + */ + "all": false, + + /** + * Class name to add to selected TR nodes + * @property selectedClass + * @type String + * @default "" + */ + "selectedClass": "" + }, + + /** + * Store of the user input customisation object + * @property custom + * @type object + * @default {} + */ + "custom": {}, + + /** + * SWF movie path + * @property swfPath + * @type string + * @default "" + */ + "swfPath": "", + + /** + * Default button set + * @property buttonSet + * @type array + * @default [] + */ + "buttonSet": [], + + /** + * When there is more than one TableTools instance for a DataTable, there must be a + * master which controls events (row selection etc) + * @property master + * @type boolean + * @default false + */ + "master": false, + + /** + * Tag names that are used for creating collections and buttons + * @namesapce + */ + "tags": {} + }; + + + /** + * @namespace Common and useful DOM elements for the class instance + */ + this.dom = { + /** + * DIV element that is create and all TableTools buttons (and their children) put into + * @property container + * @type node + * @default null + */ + "container": null, + + /** + * The table node to which TableTools will be applied + * @property table + * @type node + * @default null + */ + "table": null, + + /** + * @namespace Nodes used for the print display + */ + "print": { + /** + * Nodes which have been removed from the display by setting them to display none + * @property hidden + * @type array + * @default [] + */ + "hidden": [], + + /** + * The information display saying telling the user about the print display + * @property message + * @type node + * @default null + */ + "message": null + }, + + /** + * @namespace Nodes used for a collection display. This contains the currently used collection + */ + "collection": { + /** + * The div wrapper containing the buttons in the collection (i.e. the menu) + * @property collection + * @type node + * @default null + */ + "collection": null, + + /** + * Background display to provide focus and capture events + * @property background + * @type node + * @default null + */ + "background": null + } + }; + + /** + * @namespace Name space for the classes that this TableTools instance will use + * @extends TableTools.classes + */ + this.classes = $.extend( true, {}, TableTools.classes ); + if ( this.s.dt.bJUI ) + { + $.extend( true, this.classes, TableTools.classes_themeroller ); + } + + + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public class methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * Retreieve the settings object from an instance + * @method fnSettings + * @returns {object} TableTools settings object + */ + this.fnSettings = function () { + return this.s; + }; + + + /* Constructor logic */ + if ( typeof oOpts == 'undefined' ) + { + oOpts = {}; + } + + + TableTools._aInstances.push( this ); + this._fnConstruct( oOpts ); + + return this; +}; + + + +TableTools.prototype = { + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * Public methods + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + + /** + * Retreieve the settings object from an instance + * @returns {array} List of TR nodes which are currently selected + * @param {boolean} [filtered=false] Get only selected rows which are + * available given the filtering applied to the table. By default + * this is false - i.e. all rows, regardless of filtering are + selected. + */ + "fnGetSelected": function ( filtered ) + { + var + out = [], + data = this.s.dt.aoData, + displayed = this.s.dt.aiDisplay, + i, iLen; + + if ( filtered ) + { + // Only consider filtered rows + for ( i=0, iLen=displayed.length ; i 0 ) + { + sTitle = anTitle[0].innerHTML; + } + } + + /* Strip characters which the OS will object to - checking for UTF8 support in the scripting + * engine + */ + if ( "\u00A1".toString().length < 4 ) { + return sTitle.replace(/[^a-zA-Z0-9_\u00A1-\uFFFF\.,\-_ !\(\)]/g, ""); + } else { + return sTitle.replace(/[^a-zA-Z0-9_\.,\-_ !\(\)]/g, ""); + } + }, + + + /** + * Calculate a unity array with the column width by proportion for a set of columns to be + * included for a button. This is particularly useful for PDF creation, where we can use the + * column widths calculated by the browser to size the columns in the PDF. + * @param {Object} oConfig Button configuration object + * @returns {Array} Unity array of column ratios + */ + "fnCalcColRatios": function ( oConfig ) + { + var + aoCols = this.s.dt.aoColumns, + aColumnsInc = this._fnColumnTargets( oConfig.mColumns ), + aColWidths = [], + iWidth = 0, iTotal = 0, i, iLen; + + for ( i=0, iLen=aColumnsInc.length ; i","#"===a.firstChild.getAttribute("href")})||jb("type|href|height|width",function(a,b,c){return c?void 0:a.getAttribute(b,"type"===b.toLowerCase()?1:2)}),c.attributes&&ib(function(a){return a.innerHTML="",a.firstChild.setAttribute("value",""),""===a.firstChild.getAttribute("value")})||jb("value",function(a,b,c){return c||"input"!==a.nodeName.toLowerCase()?void 0:a.defaultValue}),ib(function(a){return null==a.getAttribute("disabled")})||jb(L,function(a,b,c){var d;return c?void 0:a[b]===!0?b.toLowerCase():(d=a.getAttributeNode(b))&&d.specified?d.value:null}),fb}(a);m.find=s,m.expr=s.selectors,m.expr[":"]=m.expr.pseudos,m.unique=s.uniqueSort,m.text=s.getText,m.isXMLDoc=s.isXML,m.contains=s.contains;var t=m.expr.match.needsContext,u=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,v=/^.[^:#\[\.,]*$/;function w(a,b,c){if(m.isFunction(b))return m.grep(a,function(a,d){return!!b.call(a,d,a)!==c});if(b.nodeType)return m.grep(a,function(a){return a===b!==c});if("string"==typeof b){if(v.test(b))return m.filter(b,a,c);b=m.filter(b,a)}return m.grep(a,function(a){return m.inArray(a,b)>=0!==c})}m.filter=function(a,b,c){var d=b[0];return c&&(a=":not("+a+")"),1===b.length&&1===d.nodeType?m.find.matchesSelector(d,a)?[d]:[]:m.find.matches(a,m.grep(b,function(a){return 1===a.nodeType}))},m.fn.extend({find:function(a){var b,c=[],d=this,e=d.length;if("string"!=typeof a)return this.pushStack(m(a).filter(function(){for(b=0;e>b;b++)if(m.contains(d[b],this))return!0}));for(b=0;e>b;b++)m.find(a,d[b],c);return c=this.pushStack(e>1?m.unique(c):c),c.selector=this.selector?this.selector+" "+a:a,c},filter:function(a){return this.pushStack(w(this,a||[],!1))},not:function(a){return this.pushStack(w(this,a||[],!0))},is:function(a){return!!w(this,"string"==typeof a&&t.test(a)?m(a):a||[],!1).length}});var x,y=a.document,z=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,A=m.fn.init=function(a,b){var c,d;if(!a)return this;if("string"==typeof a){if(c="<"===a.charAt(0)&&">"===a.charAt(a.length-1)&&a.length>=3?[null,a,null]:z.exec(a),!c||!c[1]&&b)return!b||b.jquery?(b||x).find(a):this.constructor(b).find(a);if(c[1]){if(b=b instanceof m?b[0]:b,m.merge(this,m.parseHTML(c[1],b&&b.nodeType?b.ownerDocument||b:y,!0)),u.test(c[1])&&m.isPlainObject(b))for(c in b)m.isFunction(this[c])?this[c](b[c]):this.attr(c,b[c]);return this}if(d=y.getElementById(c[2]),d&&d.parentNode){if(d.id!==c[2])return x.find(a);this.length=1,this[0]=d}return this.context=y,this.selector=a,this}return a.nodeType?(this.context=this[0]=a,this.length=1,this):m.isFunction(a)?"undefined"!=typeof x.ready?x.ready(a):a(m):(void 0!==a.selector&&(this.selector=a.selector,this.context=a.context),m.makeArray(a,this))};A.prototype=m.fn,x=m(y);var B=/^(?:parents|prev(?:Until|All))/,C={children:!0,contents:!0,next:!0,prev:!0};m.extend({dir:function(a,b,c){var d=[],e=a[b];while(e&&9!==e.nodeType&&(void 0===c||1!==e.nodeType||!m(e).is(c)))1===e.nodeType&&d.push(e),e=e[b];return d},sibling:function(a,b){for(var c=[];a;a=a.nextSibling)1===a.nodeType&&a!==b&&c.push(a);return c}}),m.fn.extend({has:function(a){var b,c=m(a,this),d=c.length;return this.filter(function(){for(b=0;d>b;b++)if(m.contains(this,c[b]))return!0})},closest:function(a,b){for(var c,d=0,e=this.length,f=[],g=t.test(a)||"string"!=typeof a?m(a,b||this.context):0;e>d;d++)for(c=this[d];c&&c!==b;c=c.parentNode)if(c.nodeType<11&&(g?g.index(c)>-1:1===c.nodeType&&m.find.matchesSelector(c,a))){f.push(c);break}return this.pushStack(f.length>1?m.unique(f):f)},index:function(a){return a?"string"==typeof a?m.inArray(this[0],m(a)):m.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(a,b){return this.pushStack(m.unique(m.merge(this.get(),m(a,b))))},addBack:function(a){return this.add(null==a?this.prevObject:this.prevObject.filter(a))}});function D(a,b){do a=a[b];while(a&&1!==a.nodeType);return a}m.each({parent:function(a){var b=a.parentNode;return b&&11!==b.nodeType?b:null},parents:function(a){return m.dir(a,"parentNode")},parentsUntil:function(a,b,c){return m.dir(a,"parentNode",c)},next:function(a){return D(a,"nextSibling")},prev:function(a){return D(a,"previousSibling")},nextAll:function(a){return m.dir(a,"nextSibling")},prevAll:function(a){return m.dir(a,"previousSibling")},nextUntil:function(a,b,c){return m.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return m.dir(a,"previousSibling",c)},siblings:function(a){return m.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return m.sibling(a.firstChild)},contents:function(a){return m.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:m.merge([],a.childNodes)}},function(a,b){m.fn[a]=function(c,d){var e=m.map(this,b,c);return"Until"!==a.slice(-5)&&(d=c),d&&"string"==typeof d&&(e=m.filter(d,e)),this.length>1&&(C[a]||(e=m.unique(e)),B.test(a)&&(e=e.reverse())),this.pushStack(e)}});var E=/\S+/g,F={};function G(a){var b=F[a]={};return m.each(a.match(E)||[],function(a,c){b[c]=!0}),b}m.Callbacks=function(a){a="string"==typeof a?F[a]||G(a):m.extend({},a);var b,c,d,e,f,g,h=[],i=!a.once&&[],j=function(l){for(c=a.memory&&l,d=!0,f=g||0,g=0,e=h.length,b=!0;h&&e>f;f++)if(h[f].apply(l[0],l[1])===!1&&a.stopOnFalse){c=!1;break}b=!1,h&&(i?i.length&&j(i.shift()):c?h=[]:k.disable())},k={add:function(){if(h){var d=h.length;!function f(b){m.each(b,function(b,c){var d=m.type(c);"function"===d?a.unique&&k.has(c)||h.push(c):c&&c.length&&"string"!==d&&f(c)})}(arguments),b?e=h.length:c&&(g=d,j(c))}return this},remove:function(){return h&&m.each(arguments,function(a,c){var d;while((d=m.inArray(c,h,d))>-1)h.splice(d,1),b&&(e>=d&&e--,f>=d&&f--)}),this},has:function(a){return a?m.inArray(a,h)>-1:!(!h||!h.length)},empty:function(){return h=[],e=0,this},disable:function(){return h=i=c=void 0,this},disabled:function(){return!h},lock:function(){return i=void 0,c||k.disable(),this},locked:function(){return!i},fireWith:function(a,c){return!h||d&&!i||(c=c||[],c=[a,c.slice?c.slice():c],b?i.push(c):j(c)),this},fire:function(){return k.fireWith(this,arguments),this},fired:function(){return!!d}};return k},m.extend({Deferred:function(a){var b=[["resolve","done",m.Callbacks("once memory"),"resolved"],["reject","fail",m.Callbacks("once memory"),"rejected"],["notify","progress",m.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return m.Deferred(function(c){m.each(b,function(b,f){var g=m.isFunction(a[b])&&a[b];e[f[1]](function(){var a=g&&g.apply(this,arguments);a&&m.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f[0]+"With"](this===d?c.promise():this,g?[a]:arguments)})}),a=null}).promise()},promise:function(a){return null!=a?m.extend(a,d):d}},e={};return d.pipe=d.then,m.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[1^a][2].disable,b[2][2].lock),e[f[0]]=function(){return e[f[0]+"With"](this===e?d:this,arguments),this},e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=d.call(arguments),e=c.length,f=1!==e||a&&m.isFunction(a.promise)?e:0,g=1===f?a:m.Deferred(),h=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?d.call(arguments):e,c===i?g.notifyWith(b,c):--f||g.resolveWith(b,c)}},i,j,k;if(e>1)for(i=new Array(e),j=new Array(e),k=new Array(e);e>b;b++)c[b]&&m.isFunction(c[b].promise)?c[b].promise().done(h(b,k,c)).fail(g.reject).progress(h(b,j,i)):--f;return f||g.resolveWith(k,c),g.promise()}});var H;m.fn.ready=function(a){return m.ready.promise().done(a),this},m.extend({isReady:!1,readyWait:1,holdReady:function(a){a?m.readyWait++:m.ready(!0)},ready:function(a){if(a===!0?!--m.readyWait:!m.isReady){if(!y.body)return setTimeout(m.ready);m.isReady=!0,a!==!0&&--m.readyWait>0||(H.resolveWith(y,[m]),m.fn.triggerHandler&&(m(y).triggerHandler("ready"),m(y).off("ready")))}}});function I(){y.addEventListener?(y.removeEventListener("DOMContentLoaded",J,!1),a.removeEventListener("load",J,!1)):(y.detachEvent("onreadystatechange",J),a.detachEvent("onload",J))}function J(){(y.addEventListener||"load"===event.type||"complete"===y.readyState)&&(I(),m.ready())}m.ready.promise=function(b){if(!H)if(H=m.Deferred(),"complete"===y.readyState)setTimeout(m.ready);else if(y.addEventListener)y.addEventListener("DOMContentLoaded",J,!1),a.addEventListener("load",J,!1);else{y.attachEvent("onreadystatechange",J),a.attachEvent("onload",J);var c=!1;try{c=null==a.frameElement&&y.documentElement}catch(d){}c&&c.doScroll&&!function e(){if(!m.isReady){try{c.doScroll("left")}catch(a){return setTimeout(e,50)}I(),m.ready()}}()}return H.promise(b)};var K="undefined",L;for(L in m(k))break;k.ownLast="0"!==L,k.inlineBlockNeedsLayout=!1,m(function(){var a,b,c,d;c=y.getElementsByTagName("body")[0],c&&c.style&&(b=y.createElement("div"),d=y.createElement("div"),d.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",c.appendChild(d).appendChild(b),typeof b.style.zoom!==K&&(b.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",k.inlineBlockNeedsLayout=a=3===b.offsetWidth,a&&(c.style.zoom=1)),c.removeChild(d))}),function(){var a=y.createElement("div");if(null==k.deleteExpando){k.deleteExpando=!0;try{delete a.test}catch(b){k.deleteExpando=!1}}a=null}(),m.acceptData=function(a){var b=m.noData[(a.nodeName+" ").toLowerCase()],c=+a.nodeType||1;return 1!==c&&9!==c?!1:!b||b!==!0&&a.getAttribute("classid")===b};var M=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,N=/([A-Z])/g;function O(a,b,c){if(void 0===c&&1===a.nodeType){var d="data-"+b.replace(N,"-$1").toLowerCase();if(c=a.getAttribute(d),"string"==typeof c){try{c="true"===c?!0:"false"===c?!1:"null"===c?null:+c+""===c?+c:M.test(c)?m.parseJSON(c):c}catch(e){}m.data(a,b,c)}else c=void 0}return c}function P(a){var b;for(b in a)if(("data"!==b||!m.isEmptyObject(a[b]))&&"toJSON"!==b)return!1;return!0}function Q(a,b,d,e){if(m.acceptData(a)){var f,g,h=m.expando,i=a.nodeType,j=i?m.cache:a,k=i?a[h]:a[h]&&h; -if(k&&j[k]&&(e||j[k].data)||void 0!==d||"string"!=typeof b)return k||(k=i?a[h]=c.pop()||m.guid++:h),j[k]||(j[k]=i?{}:{toJSON:m.noop}),("object"==typeof b||"function"==typeof b)&&(e?j[k]=m.extend(j[k],b):j[k].data=m.extend(j[k].data,b)),g=j[k],e||(g.data||(g.data={}),g=g.data),void 0!==d&&(g[m.camelCase(b)]=d),"string"==typeof b?(f=g[b],null==f&&(f=g[m.camelCase(b)])):f=g,f}}function R(a,b,c){if(m.acceptData(a)){var d,e,f=a.nodeType,g=f?m.cache:a,h=f?a[m.expando]:m.expando;if(g[h]){if(b&&(d=c?g[h]:g[h].data)){m.isArray(b)?b=b.concat(m.map(b,m.camelCase)):b in d?b=[b]:(b=m.camelCase(b),b=b in d?[b]:b.split(" ")),e=b.length;while(e--)delete d[b[e]];if(c?!P(d):!m.isEmptyObject(d))return}(c||(delete g[h].data,P(g[h])))&&(f?m.cleanData([a],!0):k.deleteExpando||g!=g.window?delete g[h]:g[h]=null)}}}m.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(a){return a=a.nodeType?m.cache[a[m.expando]]:a[m.expando],!!a&&!P(a)},data:function(a,b,c){return Q(a,b,c)},removeData:function(a,b){return R(a,b)},_data:function(a,b,c){return Q(a,b,c,!0)},_removeData:function(a,b){return R(a,b,!0)}}),m.fn.extend({data:function(a,b){var c,d,e,f=this[0],g=f&&f.attributes;if(void 0===a){if(this.length&&(e=m.data(f),1===f.nodeType&&!m._data(f,"parsedAttrs"))){c=g.length;while(c--)g[c]&&(d=g[c].name,0===d.indexOf("data-")&&(d=m.camelCase(d.slice(5)),O(f,d,e[d])));m._data(f,"parsedAttrs",!0)}return e}return"object"==typeof a?this.each(function(){m.data(this,a)}):arguments.length>1?this.each(function(){m.data(this,a,b)}):f?O(f,a,m.data(f,a)):void 0},removeData:function(a){return this.each(function(){m.removeData(this,a)})}}),m.extend({queue:function(a,b,c){var d;return a?(b=(b||"fx")+"queue",d=m._data(a,b),c&&(!d||m.isArray(c)?d=m._data(a,b,m.makeArray(c)):d.push(c)),d||[]):void 0},dequeue:function(a,b){b=b||"fx";var c=m.queue(a,b),d=c.length,e=c.shift(),f=m._queueHooks(a,b),g=function(){m.dequeue(a,b)};"inprogress"===e&&(e=c.shift(),d--),e&&("fx"===b&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return m._data(a,c)||m._data(a,c,{empty:m.Callbacks("once memory").add(function(){m._removeData(a,b+"queue"),m._removeData(a,c)})})}}),m.fn.extend({queue:function(a,b){var c=2;return"string"!=typeof a&&(b=a,a="fx",c--),arguments.lengthh;h++)b(a[h],c,g?d:d.call(a[h],h,b(a[h],c)));return e?a:j?b.call(a):i?b(a[0],c):f},W=/^(?:checkbox|radio)$/i;!function(){var a=y.createElement("input"),b=y.createElement("div"),c=y.createDocumentFragment();if(b.innerHTML="
a",k.leadingWhitespace=3===b.firstChild.nodeType,k.tbody=!b.getElementsByTagName("tbody").length,k.htmlSerialize=!!b.getElementsByTagName("link").length,k.html5Clone="<:nav>"!==y.createElement("nav").cloneNode(!0).outerHTML,a.type="checkbox",a.checked=!0,c.appendChild(a),k.appendChecked=a.checked,b.innerHTML="",k.noCloneChecked=!!b.cloneNode(!0).lastChild.defaultValue,c.appendChild(b),b.innerHTML="",k.checkClone=b.cloneNode(!0).cloneNode(!0).lastChild.checked,k.noCloneEvent=!0,b.attachEvent&&(b.attachEvent("onclick",function(){k.noCloneEvent=!1}),b.cloneNode(!0).click()),null==k.deleteExpando){k.deleteExpando=!0;try{delete b.test}catch(d){k.deleteExpando=!1}}}(),function(){var b,c,d=y.createElement("div");for(b in{submit:!0,change:!0,focusin:!0})c="on"+b,(k[b+"Bubbles"]=c in a)||(d.setAttribute(c,"t"),k[b+"Bubbles"]=d.attributes[c].expando===!1);d=null}();var X=/^(?:input|select|textarea)$/i,Y=/^key/,Z=/^(?:mouse|pointer|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=/^([^.]*)(?:\.(.+)|)$/;function ab(){return!0}function bb(){return!1}function cb(){try{return y.activeElement}catch(a){}}m.event={global:{},add:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m._data(a);if(r){c.handler&&(i=c,c=i.handler,e=i.selector),c.guid||(c.guid=m.guid++),(g=r.events)||(g=r.events={}),(k=r.handle)||(k=r.handle=function(a){return typeof m===K||a&&m.event.triggered===a.type?void 0:m.event.dispatch.apply(k.elem,arguments)},k.elem=a),b=(b||"").match(E)||[""],h=b.length;while(h--)f=_.exec(b[h])||[],o=q=f[1],p=(f[2]||"").split(".").sort(),o&&(j=m.event.special[o]||{},o=(e?j.delegateType:j.bindType)||o,j=m.event.special[o]||{},l=m.extend({type:o,origType:q,data:d,handler:c,guid:c.guid,selector:e,needsContext:e&&m.expr.match.needsContext.test(e),namespace:p.join(".")},i),(n=g[o])||(n=g[o]=[],n.delegateCount=0,j.setup&&j.setup.call(a,d,p,k)!==!1||(a.addEventListener?a.addEventListener(o,k,!1):a.attachEvent&&a.attachEvent("on"+o,k))),j.add&&(j.add.call(a,l),l.handler.guid||(l.handler.guid=c.guid)),e?n.splice(n.delegateCount++,0,l):n.push(l),m.event.global[o]=!0);a=null}},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,n,o,p,q,r=m.hasData(a)&&m._data(a);if(r&&(k=r.events)){b=(b||"").match(E)||[""],j=b.length;while(j--)if(h=_.exec(b[j])||[],o=q=h[1],p=(h[2]||"").split(".").sort(),o){l=m.event.special[o]||{},o=(d?l.delegateType:l.bindType)||o,n=k[o]||[],h=h[2]&&new RegExp("(^|\\.)"+p.join("\\.(?:.*\\.|)")+"(\\.|$)"),i=f=n.length;while(f--)g=n[f],!e&&q!==g.origType||c&&c.guid!==g.guid||h&&!h.test(g.namespace)||d&&d!==g.selector&&("**"!==d||!g.selector)||(n.splice(f,1),g.selector&&n.delegateCount--,l.remove&&l.remove.call(a,g));i&&!n.length&&(l.teardown&&l.teardown.call(a,p,r.handle)!==!1||m.removeEvent(a,o,r.handle),delete k[o])}else for(o in k)m.event.remove(a,o+b[j],c,d,!0);m.isEmptyObject(k)&&(delete r.handle,m._removeData(a,"events"))}},trigger:function(b,c,d,e){var f,g,h,i,k,l,n,o=[d||y],p=j.call(b,"type")?b.type:b,q=j.call(b,"namespace")?b.namespace.split("."):[];if(h=l=d=d||y,3!==d.nodeType&&8!==d.nodeType&&!$.test(p+m.event.triggered)&&(p.indexOf(".")>=0&&(q=p.split("."),p=q.shift(),q.sort()),g=p.indexOf(":")<0&&"on"+p,b=b[m.expando]?b:new m.Event(p,"object"==typeof b&&b),b.isTrigger=e?2:3,b.namespace=q.join("."),b.namespace_re=b.namespace?new RegExp("(^|\\.)"+q.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,b.result=void 0,b.target||(b.target=d),c=null==c?[b]:m.makeArray(c,[b]),k=m.event.special[p]||{},e||!k.trigger||k.trigger.apply(d,c)!==!1)){if(!e&&!k.noBubble&&!m.isWindow(d)){for(i=k.delegateType||p,$.test(i+p)||(h=h.parentNode);h;h=h.parentNode)o.push(h),l=h;l===(d.ownerDocument||y)&&o.push(l.defaultView||l.parentWindow||a)}n=0;while((h=o[n++])&&!b.isPropagationStopped())b.type=n>1?i:k.bindType||p,f=(m._data(h,"events")||{})[b.type]&&m._data(h,"handle"),f&&f.apply(h,c),f=g&&h[g],f&&f.apply&&m.acceptData(h)&&(b.result=f.apply(h,c),b.result===!1&&b.preventDefault());if(b.type=p,!e&&!b.isDefaultPrevented()&&(!k._default||k._default.apply(o.pop(),c)===!1)&&m.acceptData(d)&&g&&d[p]&&!m.isWindow(d)){l=d[g],l&&(d[g]=null),m.event.triggered=p;try{d[p]()}catch(r){}m.event.triggered=void 0,l&&(d[g]=l)}return b.result}},dispatch:function(a){a=m.event.fix(a);var b,c,e,f,g,h=[],i=d.call(arguments),j=(m._data(this,"events")||{})[a.type]||[],k=m.event.special[a.type]||{};if(i[0]=a,a.delegateTarget=this,!k.preDispatch||k.preDispatch.call(this,a)!==!1){h=m.event.handlers.call(this,a,j),b=0;while((f=h[b++])&&!a.isPropagationStopped()){a.currentTarget=f.elem,g=0;while((e=f.handlers[g++])&&!a.isImmediatePropagationStopped())(!a.namespace_re||a.namespace_re.test(e.namespace))&&(a.handleObj=e,a.data=e.data,c=((m.event.special[e.origType]||{}).handle||e.handler).apply(f.elem,i),void 0!==c&&(a.result=c)===!1&&(a.preventDefault(),a.stopPropagation()))}return k.postDispatch&&k.postDispatch.call(this,a),a.result}},handlers:function(a,b){var c,d,e,f,g=[],h=b.delegateCount,i=a.target;if(h&&i.nodeType&&(!a.button||"click"!==a.type))for(;i!=this;i=i.parentNode||this)if(1===i.nodeType&&(i.disabled!==!0||"click"!==a.type)){for(e=[],f=0;h>f;f++)d=b[f],c=d.selector+" ",void 0===e[c]&&(e[c]=d.needsContext?m(c,this).index(i)>=0:m.find(c,this,null,[i]).length),e[c]&&e.push(d);e.length&&g.push({elem:i,handlers:e})}return h]","i"),hb=/^\s+/,ib=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,jb=/<([\w:]+)/,kb=/\s*$/g,rb={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:k.htmlSerialize?[0,"",""]:[1,"X
","
"]},sb=db(y),tb=sb.appendChild(y.createElement("div"));rb.optgroup=rb.option,rb.tbody=rb.tfoot=rb.colgroup=rb.caption=rb.thead,rb.th=rb.td;function ub(a,b){var c,d,e=0,f=typeof a.getElementsByTagName!==K?a.getElementsByTagName(b||"*"):typeof a.querySelectorAll!==K?a.querySelectorAll(b||"*"):void 0;if(!f)for(f=[],c=a.childNodes||a;null!=(d=c[e]);e++)!b||m.nodeName(d,b)?f.push(d):m.merge(f,ub(d,b));return void 0===b||b&&m.nodeName(a,b)?m.merge([a],f):f}function vb(a){W.test(a.type)&&(a.defaultChecked=a.checked)}function wb(a,b){return m.nodeName(a,"table")&&m.nodeName(11!==b.nodeType?b:b.firstChild,"tr")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function xb(a){return a.type=(null!==m.find.attr(a,"type"))+"/"+a.type,a}function yb(a){var b=pb.exec(a.type);return b?a.type=b[1]:a.removeAttribute("type"),a}function zb(a,b){for(var c,d=0;null!=(c=a[d]);d++)m._data(c,"globalEval",!b||m._data(b[d],"globalEval"))}function Ab(a,b){if(1===b.nodeType&&m.hasData(a)){var c,d,e,f=m._data(a),g=m._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;e>d;d++)m.event.add(b,c,h[c][d])}g.data&&(g.data=m.extend({},g.data))}}function Bb(a,b){var c,d,e;if(1===b.nodeType){if(c=b.nodeName.toLowerCase(),!k.noCloneEvent&&b[m.expando]){e=m._data(b);for(d in e.events)m.removeEvent(b,d,e.handle);b.removeAttribute(m.expando)}"script"===c&&b.text!==a.text?(xb(b).text=a.text,yb(b)):"object"===c?(b.parentNode&&(b.outerHTML=a.outerHTML),k.html5Clone&&a.innerHTML&&!m.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):"input"===c&&W.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):"option"===c?b.defaultSelected=b.selected=a.defaultSelected:("input"===c||"textarea"===c)&&(b.defaultValue=a.defaultValue)}}m.extend({clone:function(a,b,c){var d,e,f,g,h,i=m.contains(a.ownerDocument,a);if(k.html5Clone||m.isXMLDoc(a)||!gb.test("<"+a.nodeName+">")?f=a.cloneNode(!0):(tb.innerHTML=a.outerHTML,tb.removeChild(f=tb.firstChild)),!(k.noCloneEvent&&k.noCloneChecked||1!==a.nodeType&&11!==a.nodeType||m.isXMLDoc(a)))for(d=ub(f),h=ub(a),g=0;null!=(e=h[g]);++g)d[g]&&Bb(e,d[g]);if(b)if(c)for(h=h||ub(a),d=d||ub(f),g=0;null!=(e=h[g]);g++)Ab(e,d[g]);else Ab(a,f);return d=ub(f,"script"),d.length>0&&zb(d,!i&&ub(a,"script")),d=h=e=null,f},buildFragment:function(a,b,c,d){for(var e,f,g,h,i,j,l,n=a.length,o=db(b),p=[],q=0;n>q;q++)if(f=a[q],f||0===f)if("object"===m.type(f))m.merge(p,f.nodeType?[f]:f);else if(lb.test(f)){h=h||o.appendChild(b.createElement("div")),i=(jb.exec(f)||["",""])[1].toLowerCase(),l=rb[i]||rb._default,h.innerHTML=l[1]+f.replace(ib,"<$1>")+l[2],e=l[0];while(e--)h=h.lastChild;if(!k.leadingWhitespace&&hb.test(f)&&p.push(b.createTextNode(hb.exec(f)[0])),!k.tbody){f="table"!==i||kb.test(f)?""!==l[1]||kb.test(f)?0:h:h.firstChild,e=f&&f.childNodes.length;while(e--)m.nodeName(j=f.childNodes[e],"tbody")&&!j.childNodes.length&&f.removeChild(j)}m.merge(p,h.childNodes),h.textContent="";while(h.firstChild)h.removeChild(h.firstChild);h=o.lastChild}else p.push(b.createTextNode(f));h&&o.removeChild(h),k.appendChecked||m.grep(ub(p,"input"),vb),q=0;while(f=p[q++])if((!d||-1===m.inArray(f,d))&&(g=m.contains(f.ownerDocument,f),h=ub(o.appendChild(f),"script"),g&&zb(h),c)){e=0;while(f=h[e++])ob.test(f.type||"")&&c.push(f)}return h=null,o},cleanData:function(a,b){for(var d,e,f,g,h=0,i=m.expando,j=m.cache,l=k.deleteExpando,n=m.event.special;null!=(d=a[h]);h++)if((b||m.acceptData(d))&&(f=d[i],g=f&&j[f])){if(g.events)for(e in g.events)n[e]?m.event.remove(d,e):m.removeEvent(d,e,g.handle);j[f]&&(delete j[f],l?delete d[i]:typeof d.removeAttribute!==K?d.removeAttribute(i):d[i]=null,c.push(f))}}}),m.fn.extend({text:function(a){return V(this,function(a){return void 0===a?m.text(this):this.empty().append((this[0]&&this[0].ownerDocument||y).createTextNode(a))},null,a,arguments.length)},append:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.appendChild(a)}})},prepend:function(){return this.domManip(arguments,function(a){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var b=wb(this,a);b.insertBefore(a,b.firstChild)}})},before:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this)})},after:function(){return this.domManip(arguments,function(a){this.parentNode&&this.parentNode.insertBefore(a,this.nextSibling)})},remove:function(a,b){for(var c,d=a?m.filter(a,this):this,e=0;null!=(c=d[e]);e++)b||1!==c.nodeType||m.cleanData(ub(c)),c.parentNode&&(b&&m.contains(c.ownerDocument,c)&&zb(ub(c,"script")),c.parentNode.removeChild(c));return this},empty:function(){for(var a,b=0;null!=(a=this[b]);b++){1===a.nodeType&&m.cleanData(ub(a,!1));while(a.firstChild)a.removeChild(a.firstChild);a.options&&m.nodeName(a,"select")&&(a.options.length=0)}return this},clone:function(a,b){return a=null==a?!1:a,b=null==b?a:b,this.map(function(){return m.clone(this,a,b)})},html:function(a){return V(this,function(a){var b=this[0]||{},c=0,d=this.length;if(void 0===a)return 1===b.nodeType?b.innerHTML.replace(fb,""):void 0;if(!("string"!=typeof a||mb.test(a)||!k.htmlSerialize&&gb.test(a)||!k.leadingWhitespace&&hb.test(a)||rb[(jb.exec(a)||["",""])[1].toLowerCase()])){a=a.replace(ib,"<$1>");try{for(;d>c;c++)b=this[c]||{},1===b.nodeType&&(m.cleanData(ub(b,!1)),b.innerHTML=a);b=0}catch(e){}}b&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(){var a=arguments[0];return this.domManip(arguments,function(b){a=this.parentNode,m.cleanData(ub(this)),a&&a.replaceChild(b,this)}),a&&(a.length||a.nodeType)?this:this.remove()},detach:function(a){return this.remove(a,!0)},domManip:function(a,b){a=e.apply([],a);var c,d,f,g,h,i,j=0,l=this.length,n=this,o=l-1,p=a[0],q=m.isFunction(p);if(q||l>1&&"string"==typeof p&&!k.checkClone&&nb.test(p))return this.each(function(c){var d=n.eq(c);q&&(a[0]=p.call(this,c,d.html())),d.domManip(a,b)});if(l&&(i=m.buildFragment(a,this[0].ownerDocument,!1,this),c=i.firstChild,1===i.childNodes.length&&(i=c),c)){for(g=m.map(ub(i,"script"),xb),f=g.length;l>j;j++)d=i,j!==o&&(d=m.clone(d,!0,!0),f&&m.merge(g,ub(d,"script"))),b.call(this[j],d,j);if(f)for(h=g[g.length-1].ownerDocument,m.map(g,yb),j=0;f>j;j++)d=g[j],ob.test(d.type||"")&&!m._data(d,"globalEval")&&m.contains(h,d)&&(d.src?m._evalUrl&&m._evalUrl(d.src):m.globalEval((d.text||d.textContent||d.innerHTML||"").replace(qb,"")));i=c=null}return this}}),m.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){m.fn[a]=function(a){for(var c,d=0,e=[],g=m(a),h=g.length-1;h>=d;d++)c=d===h?this:this.clone(!0),m(g[d])[b](c),f.apply(e,c.get());return this.pushStack(e)}});var Cb,Db={};function Eb(b,c){var d,e=m(c.createElement(b)).appendTo(c.body),f=a.getDefaultComputedStyle&&(d=a.getDefaultComputedStyle(e[0]))?d.display:m.css(e[0],"display");return e.detach(),f}function Fb(a){var b=y,c=Db[a];return c||(c=Eb(a,b),"none"!==c&&c||(Cb=(Cb||m("