From b047e9d951dff4e8094337713657eb2673124a52 Mon Sep 17 00:00:00 2001 From: Mark Friedrich Date: Sun, 15 Jul 2018 16:07:32 +0200 Subject: [PATCH] - fixed some JS _race condition_ render issues --- js/app/map/map.js | 147 ++----------------------- js/app/map/util.js | 166 +++++++++++++++++++++++++++++ js/app/mappage.js | 4 +- js/app/module_map.js | 2 +- js/app/page.js | 6 +- public/js/v1.3.6/app/map/map.js | 147 ++----------------------- public/js/v1.3.6/app/map/util.js | 166 +++++++++++++++++++++++++++++ public/js/v1.3.6/app/mappage.js | 4 +- public/js/v1.3.6/app/module_map.js | 2 +- public/js/v1.3.6/app/page.js | 6 +- 10 files changed, 362 insertions(+), 288 deletions(-) diff --git a/js/app/map/map.js b/js/app/map/map.js index 18db09ef..2b005d4e 100644 --- a/js/app/map/map.js +++ b/js/app/map/map.js @@ -1223,81 +1223,6 @@ define([ return connection; }; - /** - * make all systems appear visual on the map with its connections - * @param show - * @param callback - */ - $.fn.visualizeMap = function(show, callback){ - let mapElement = $(this); - - // start map update counter -> prevent map updates during animations - mapElement.getMapOverlay('timer').startMapUpdateCounter(); - - let systemElements = mapElement.find('.' + config.systemClass); - let endpointElements = mapElement.find('.jsplumb-endpoint:visible'); - let connectorElements = mapElement.find('.jsplumb-connector:visible'); - let overlayElements = mapElement.find('.jsplumb-overlay:visible, .tooltip'); - - let hideElements = (elements) => { - if(elements.length > 0){ - // disable transition for next opacity change - elements.addClass('pf-notransition'); - // hide elements - elements.css('opacity', 0); - // Trigger a reflow, flushing the CSS changes - // -> http://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily - elements[0].offsetHeight; // jshint ignore:line - elements.removeClass('pf-notransition'); - } - - return elements; - }; - - let mapElements = systemElements.add(endpointElements).add(connectorElements); - - // show nice animation - if(show === 'show'){ - hideElements(systemElements); - hideElements(endpointElements); - hideElements(connectorElements); - hideElements(overlayElements); - - overlayElements.velocity('transition.fadeIn', { - duration: 60, - display: 'auto' - }); - - mapElements.velocity({ - translateY: [ 0, -20], - opacity: [ 1, 0 ] - }, { - duration: 150, - easing: 'easeOut', - complete: function(){ - callback(); - } - }); - }else if(show === 'hide'){ - - overlayElements.velocity('transition.fadeOut', { - duration: 60, - display: 'auto' - }); - - mapElements.velocity({ - translateY: [ -20, 0 ], - opacity: [ 0, 1 ] - }, { - duration: 150, - easing: 'easeOut', - complete: function(){ - callback(); - } - }); - } - }; - /** * mark a system as source * @param map @@ -3315,11 +3240,6 @@ define([ */ let initMapOptions = (mapConfig, options) => { - /** - * init map options promise - * @param resolve - * @param reject - */ let initMapOptionsExecutor = (resolve, reject) => { let payload = { action: 'initMapOptions', @@ -3329,63 +3249,18 @@ define([ }; if(options.showAnimation){ - /** - * callback after visualizeMap is done - * @param mapName - * @param mapContainer - */ - let switchTabCallback = (mapContainer, mapConfig) => { - Util.showNotify({title: 'Map initialized', text: mapConfig.name + ' - loaded', type: 'success'}); - - let mapWrapper = mapContainer.parents('.' + config.mapWrapperClass); - - // auto scroll map to previous position ----------------------------------------------------------- - let promiseStore = MapUtil.getLocaleData('map', mapContainer.data('id') ); - promiseStore.then(data => { - // This code runs once the value has been loaded from offline storage - if(data && data.scrollOffset){ - mapWrapper.scrollToPosition([data.scrollOffset.y, data.scrollOffset.x]); - } - }); - - // update main menu options based on the active map ----------------------------------------------- - $(document).trigger('pf:updateMenuOptions', { - mapConfig: mapConfig - }); - - // init magnetizer -------------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapMagnetizer', - toggle: false - }); - - // init grid snap --------------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapSnapToGrid', - toggle: false - }); - - // init endpoint overlay -------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapEndpoint', - toggle: false - }); - - // init compact system UI -------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapCompact', - toggle: false - }); - }; - - // show nice visualization effect --------------------------------------------------------------------- - let mapContainer = $(mapConfig.map.getContainer()); - mapContainer.visualizeMap('show', function(){ - switchTabCallback(mapContainer, mapConfig.config); - }); + let mapElement = $(mapConfig.map.getContainer()); + MapUtil.setMapDefaultOptions(mapElement, mapConfig.config) + .then(payload => MapUtil.visualizeMap(mapElement, 'show')) + .then(payload => MapUtil.scrollToDefaultPosition(mapElement)) + .then(payload => { + Util.showNotify({title: 'Map initialized', text: mapConfig.config.name + ' - loaded', type: 'success'}); + }) + .then(() => resolve(payload)); + }else{ + // nothing to do here... + resolve(payload); } - - resolve(payload); }; return new Promise(initMapOptionsExecutor); diff --git a/js/app/map/util.js b/js/app/map/util.js index c4098429..18b07ecf 100644 --- a/js/app/map/util.js +++ b/js/app/map/util.js @@ -19,6 +19,8 @@ define([ mapLocalStoragePrefix: 'map_', // prefix for map data local storage key mapTabContentClass: 'pf-map-tab-content', // Tab-Content element (parent element) + mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable) + mapClass: 'pf-map', // class for all maps mapGridClass: 'pf-grid-small', // class for map grid snapping mapCompactClass: 'pf-compact', // class for map compact system UI @@ -961,6 +963,167 @@ define([ } }; + /** + * show map animations when a new map gets visual + * @param mapElement + * @param show + * @returns {Promise} + */ + let visualizeMap = (mapElement, show) => { + + let visualizeMapExecutor = (resolve, reject) => { + // start map update counter -> prevent map updates during animations + mapElement.getMapOverlay('timer').startMapUpdateCounter(); + + let systemElements = mapElement.find('.' + config.systemClass); + let endpointElements = mapElement.find('.jsplumb-endpoint:visible'); + let connectorElements = mapElement.find('.jsplumb-connector:visible'); + let overlayElements = mapElement.find('.jsplumb-overlay:visible, .tooltip'); + + let hideElements = (elements) => { + if(elements.length > 0){ + // disable transition for next opacity change + elements.addClass('pf-notransition'); + // hide elements + elements.css('opacity', 0); + // Trigger a reflow, flushing the CSS changes + // -> http://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily + elements[0].offsetHeight; // jshint ignore:line + elements.removeClass('pf-notransition'); + } + + return elements; + }; + + let mapElements = systemElements.add(endpointElements).add(connectorElements); + + // show nice animation + if(show === 'show'){ + hideElements(systemElements); + hideElements(endpointElements); + hideElements(connectorElements); + hideElements(overlayElements); + + overlayElements.velocity('transition.fadeIn', { + duration: 60, + display: 'auto' + }); + + mapElements.velocity({ + translateY: [ 0, -20], + opacity: [ 1, 0 ] + }, { + duration: 150, + easing: 'easeOut', + complete: function(){ + resolve({ + action: 'visualizeMap', + data: false + }); + } + }); + }else if(show === 'hide'){ + + overlayElements.velocity('transition.fadeOut', { + duration: 60, + display: 'auto' + }); + + mapElements.velocity({ + translateY: [ -20, 0 ], + opacity: [ 0, 1 ] + }, { + duration: 150, + easing: 'easeOut', + complete: function(){ + resolve({ + action: 'visualizeMap', + data: false + }); + } + }); + } + }; + + return new Promise(visualizeMapExecutor); + }; + + /** + * set default map Options ( + * -> HINT: This function triggers Events! Promise is resolved before trigger completed + * @param mapElement + * @param mapConfig + * @returns {Promise} + */ + let setMapDefaultOptions = (mapElement, mapConfig) => { + + let setMapDefaultOptionsExecutor = (resolve, reject) => { + // update main menu options based on the active map ----------------------------------------------- + $(document).trigger('pf:updateMenuOptions', { + mapConfig: mapConfig + }); + + // init compact system layout --------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapCompact', + toggle: false + }); + + // init magnetizer -------------------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapMagnetizer', + toggle: false + }); + + // init grid snap --------------------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapSnapToGrid', + toggle: false + }); + + // init endpoint overlay -------------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapEndpoint', + toggle: false + }); + + resolve({ + action: 'setMapDefaultOptions', + data: false + }); + }; + + return new Promise(setMapDefaultOptionsExecutor); + }; + + /** + * scroll map to default (stored) x/y coordinates + * @param mapElement + * @returns {Promise} + */ + let scrollToDefaultPosition = (mapElement) => { + + let scrollToDefaultPositionExecutor = (resolve, reject) => { + let mapWrapper = mapElement.parents('.' + config.mapWrapperClass); + + // auto scroll map to previous stored position + let promiseStore = getLocaleData('map', mapElement.data('id')); + promiseStore.then(data => { + // This code runs once the value has been loaded from offline storage + if(data && data.scrollOffset){ + mapWrapper.scrollToPosition([data.scrollOffset.y, data.scrollOffset.x]); + } + + resolve({ + action: 'scrollToDefaultPosition', + data: false + }); + }); + }; + + return new Promise(scrollToDefaultPositionExecutor); + }; + /** * delete local map configuration by key (IndexedDB) * @param type @@ -1440,6 +1603,9 @@ define([ getLocaleData: getLocaleData, storeLocalData: storeLocalData, deleteLocalData: deleteLocalData, + visualizeMap: visualizeMap, + setMapDefaultOptions: setMapDefaultOptions, + scrollToDefaultPosition: scrollToDefaultPosition, getSystemId: getSystemId, checkRight: checkRight, getMapDeeplinkUrl: getMapDeeplinkUrl, diff --git a/js/app/mappage.js b/js/app/mappage.js index 66839ced..12c65528 100644 --- a/js/app/mappage.js +++ b/js/app/mappage.js @@ -257,12 +257,12 @@ define([ onGet: (MsgWorkerMessage) => { switch(MsgWorkerMessage.task()){ case 'mapUpdate': - Util.updateCurrentMapData( MsgWorkerMessage.data() ); + Util.updateCurrentMapData(MsgWorkerMessage.data()); ModuleMap.updateMapModule(mapModule); break; case 'mapAccess': case 'mapDeleted': - Util.deleteCurrentMapData( MsgWorkerMessage.data() ); + Util.deleteCurrentMapData(MsgWorkerMessage.data()); ModuleMap.updateMapModule(mapModule); break; case 'mapSubscriptions': diff --git a/js/app/module_map.js b/js/app/module_map.js index 38517d05..72bb7639 100644 --- a/js/app/module_map.js +++ b/js/app/module_map.js @@ -682,7 +682,7 @@ define([ mapElement.data('frozen', true); // hide current map with animation - mapElement.visualizeMap('hide', function(){ + MapUtil.visualizeMap(mapElement, 'hide').then(payload => { // un-block map tabs mapTabChangeBlocked = switchTabCallback(mapElement, tabLinkElement); }); diff --git a/js/app/page.js b/js/app/page.js index 2275ff9c..a9b50868 100644 --- a/js/app/page.js +++ b/js/app/page.js @@ -455,11 +455,7 @@ define([ * @param event * @param data */ - $.fn.triggerMenuEvent = function(event, data){ - if(data === undefined){ - data = {}; - } - + $.fn.triggerMenuEvent = function(event, data = {}){ $(this).trigger('pf:menu' + event, [data]); }; diff --git a/public/js/v1.3.6/app/map/map.js b/public/js/v1.3.6/app/map/map.js index 18db09ef..2b005d4e 100644 --- a/public/js/v1.3.6/app/map/map.js +++ b/public/js/v1.3.6/app/map/map.js @@ -1223,81 +1223,6 @@ define([ return connection; }; - /** - * make all systems appear visual on the map with its connections - * @param show - * @param callback - */ - $.fn.visualizeMap = function(show, callback){ - let mapElement = $(this); - - // start map update counter -> prevent map updates during animations - mapElement.getMapOverlay('timer').startMapUpdateCounter(); - - let systemElements = mapElement.find('.' + config.systemClass); - let endpointElements = mapElement.find('.jsplumb-endpoint:visible'); - let connectorElements = mapElement.find('.jsplumb-connector:visible'); - let overlayElements = mapElement.find('.jsplumb-overlay:visible, .tooltip'); - - let hideElements = (elements) => { - if(elements.length > 0){ - // disable transition for next opacity change - elements.addClass('pf-notransition'); - // hide elements - elements.css('opacity', 0); - // Trigger a reflow, flushing the CSS changes - // -> http://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily - elements[0].offsetHeight; // jshint ignore:line - elements.removeClass('pf-notransition'); - } - - return elements; - }; - - let mapElements = systemElements.add(endpointElements).add(connectorElements); - - // show nice animation - if(show === 'show'){ - hideElements(systemElements); - hideElements(endpointElements); - hideElements(connectorElements); - hideElements(overlayElements); - - overlayElements.velocity('transition.fadeIn', { - duration: 60, - display: 'auto' - }); - - mapElements.velocity({ - translateY: [ 0, -20], - opacity: [ 1, 0 ] - }, { - duration: 150, - easing: 'easeOut', - complete: function(){ - callback(); - } - }); - }else if(show === 'hide'){ - - overlayElements.velocity('transition.fadeOut', { - duration: 60, - display: 'auto' - }); - - mapElements.velocity({ - translateY: [ -20, 0 ], - opacity: [ 0, 1 ] - }, { - duration: 150, - easing: 'easeOut', - complete: function(){ - callback(); - } - }); - } - }; - /** * mark a system as source * @param map @@ -3315,11 +3240,6 @@ define([ */ let initMapOptions = (mapConfig, options) => { - /** - * init map options promise - * @param resolve - * @param reject - */ let initMapOptionsExecutor = (resolve, reject) => { let payload = { action: 'initMapOptions', @@ -3329,63 +3249,18 @@ define([ }; if(options.showAnimation){ - /** - * callback after visualizeMap is done - * @param mapName - * @param mapContainer - */ - let switchTabCallback = (mapContainer, mapConfig) => { - Util.showNotify({title: 'Map initialized', text: mapConfig.name + ' - loaded', type: 'success'}); - - let mapWrapper = mapContainer.parents('.' + config.mapWrapperClass); - - // auto scroll map to previous position ----------------------------------------------------------- - let promiseStore = MapUtil.getLocaleData('map', mapContainer.data('id') ); - promiseStore.then(data => { - // This code runs once the value has been loaded from offline storage - if(data && data.scrollOffset){ - mapWrapper.scrollToPosition([data.scrollOffset.y, data.scrollOffset.x]); - } - }); - - // update main menu options based on the active map ----------------------------------------------- - $(document).trigger('pf:updateMenuOptions', { - mapConfig: mapConfig - }); - - // init magnetizer -------------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapMagnetizer', - toggle: false - }); - - // init grid snap --------------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapSnapToGrid', - toggle: false - }); - - // init endpoint overlay -------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapEndpoint', - toggle: false - }); - - // init compact system UI -------------------------------------------------------------------------- - mapContainer.triggerMenuEvent('MapOption', { - option: 'mapCompact', - toggle: false - }); - }; - - // show nice visualization effect --------------------------------------------------------------------- - let mapContainer = $(mapConfig.map.getContainer()); - mapContainer.visualizeMap('show', function(){ - switchTabCallback(mapContainer, mapConfig.config); - }); + let mapElement = $(mapConfig.map.getContainer()); + MapUtil.setMapDefaultOptions(mapElement, mapConfig.config) + .then(payload => MapUtil.visualizeMap(mapElement, 'show')) + .then(payload => MapUtil.scrollToDefaultPosition(mapElement)) + .then(payload => { + Util.showNotify({title: 'Map initialized', text: mapConfig.config.name + ' - loaded', type: 'success'}); + }) + .then(() => resolve(payload)); + }else{ + // nothing to do here... + resolve(payload); } - - resolve(payload); }; return new Promise(initMapOptionsExecutor); diff --git a/public/js/v1.3.6/app/map/util.js b/public/js/v1.3.6/app/map/util.js index c4098429..18b07ecf 100644 --- a/public/js/v1.3.6/app/map/util.js +++ b/public/js/v1.3.6/app/map/util.js @@ -19,6 +19,8 @@ define([ mapLocalStoragePrefix: 'map_', // prefix for map data local storage key mapTabContentClass: 'pf-map-tab-content', // Tab-Content element (parent element) + mapWrapperClass: 'pf-map-wrapper', // wrapper div (scrollable) + mapClass: 'pf-map', // class for all maps mapGridClass: 'pf-grid-small', // class for map grid snapping mapCompactClass: 'pf-compact', // class for map compact system UI @@ -961,6 +963,167 @@ define([ } }; + /** + * show map animations when a new map gets visual + * @param mapElement + * @param show + * @returns {Promise} + */ + let visualizeMap = (mapElement, show) => { + + let visualizeMapExecutor = (resolve, reject) => { + // start map update counter -> prevent map updates during animations + mapElement.getMapOverlay('timer').startMapUpdateCounter(); + + let systemElements = mapElement.find('.' + config.systemClass); + let endpointElements = mapElement.find('.jsplumb-endpoint:visible'); + let connectorElements = mapElement.find('.jsplumb-connector:visible'); + let overlayElements = mapElement.find('.jsplumb-overlay:visible, .tooltip'); + + let hideElements = (elements) => { + if(elements.length > 0){ + // disable transition for next opacity change + elements.addClass('pf-notransition'); + // hide elements + elements.css('opacity', 0); + // Trigger a reflow, flushing the CSS changes + // -> http://stackoverflow.com/questions/11131875/what-is-the-cleanest-way-to-disable-css-transition-effects-temporarily + elements[0].offsetHeight; // jshint ignore:line + elements.removeClass('pf-notransition'); + } + + return elements; + }; + + let mapElements = systemElements.add(endpointElements).add(connectorElements); + + // show nice animation + if(show === 'show'){ + hideElements(systemElements); + hideElements(endpointElements); + hideElements(connectorElements); + hideElements(overlayElements); + + overlayElements.velocity('transition.fadeIn', { + duration: 60, + display: 'auto' + }); + + mapElements.velocity({ + translateY: [ 0, -20], + opacity: [ 1, 0 ] + }, { + duration: 150, + easing: 'easeOut', + complete: function(){ + resolve({ + action: 'visualizeMap', + data: false + }); + } + }); + }else if(show === 'hide'){ + + overlayElements.velocity('transition.fadeOut', { + duration: 60, + display: 'auto' + }); + + mapElements.velocity({ + translateY: [ -20, 0 ], + opacity: [ 0, 1 ] + }, { + duration: 150, + easing: 'easeOut', + complete: function(){ + resolve({ + action: 'visualizeMap', + data: false + }); + } + }); + } + }; + + return new Promise(visualizeMapExecutor); + }; + + /** + * set default map Options ( + * -> HINT: This function triggers Events! Promise is resolved before trigger completed + * @param mapElement + * @param mapConfig + * @returns {Promise} + */ + let setMapDefaultOptions = (mapElement, mapConfig) => { + + let setMapDefaultOptionsExecutor = (resolve, reject) => { + // update main menu options based on the active map ----------------------------------------------- + $(document).trigger('pf:updateMenuOptions', { + mapConfig: mapConfig + }); + + // init compact system layout --------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapCompact', + toggle: false + }); + + // init magnetizer -------------------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapMagnetizer', + toggle: false + }); + + // init grid snap --------------------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapSnapToGrid', + toggle: false + }); + + // init endpoint overlay -------------------------------------------------------------------------- + mapElement.triggerMenuEvent('MapOption', { + option: 'mapEndpoint', + toggle: false + }); + + resolve({ + action: 'setMapDefaultOptions', + data: false + }); + }; + + return new Promise(setMapDefaultOptionsExecutor); + }; + + /** + * scroll map to default (stored) x/y coordinates + * @param mapElement + * @returns {Promise} + */ + let scrollToDefaultPosition = (mapElement) => { + + let scrollToDefaultPositionExecutor = (resolve, reject) => { + let mapWrapper = mapElement.parents('.' + config.mapWrapperClass); + + // auto scroll map to previous stored position + let promiseStore = getLocaleData('map', mapElement.data('id')); + promiseStore.then(data => { + // This code runs once the value has been loaded from offline storage + if(data && data.scrollOffset){ + mapWrapper.scrollToPosition([data.scrollOffset.y, data.scrollOffset.x]); + } + + resolve({ + action: 'scrollToDefaultPosition', + data: false + }); + }); + }; + + return new Promise(scrollToDefaultPositionExecutor); + }; + /** * delete local map configuration by key (IndexedDB) * @param type @@ -1440,6 +1603,9 @@ define([ getLocaleData: getLocaleData, storeLocalData: storeLocalData, deleteLocalData: deleteLocalData, + visualizeMap: visualizeMap, + setMapDefaultOptions: setMapDefaultOptions, + scrollToDefaultPosition: scrollToDefaultPosition, getSystemId: getSystemId, checkRight: checkRight, getMapDeeplinkUrl: getMapDeeplinkUrl, diff --git a/public/js/v1.3.6/app/mappage.js b/public/js/v1.3.6/app/mappage.js index 66839ced..12c65528 100644 --- a/public/js/v1.3.6/app/mappage.js +++ b/public/js/v1.3.6/app/mappage.js @@ -257,12 +257,12 @@ define([ onGet: (MsgWorkerMessage) => { switch(MsgWorkerMessage.task()){ case 'mapUpdate': - Util.updateCurrentMapData( MsgWorkerMessage.data() ); + Util.updateCurrentMapData(MsgWorkerMessage.data()); ModuleMap.updateMapModule(mapModule); break; case 'mapAccess': case 'mapDeleted': - Util.deleteCurrentMapData( MsgWorkerMessage.data() ); + Util.deleteCurrentMapData(MsgWorkerMessage.data()); ModuleMap.updateMapModule(mapModule); break; case 'mapSubscriptions': diff --git a/public/js/v1.3.6/app/module_map.js b/public/js/v1.3.6/app/module_map.js index 38517d05..72bb7639 100644 --- a/public/js/v1.3.6/app/module_map.js +++ b/public/js/v1.3.6/app/module_map.js @@ -682,7 +682,7 @@ define([ mapElement.data('frozen', true); // hide current map with animation - mapElement.visualizeMap('hide', function(){ + MapUtil.visualizeMap(mapElement, 'hide').then(payload => { // un-block map tabs mapTabChangeBlocked = switchTabCallback(mapElement, tabLinkElement); }); diff --git a/public/js/v1.3.6/app/page.js b/public/js/v1.3.6/app/page.js index 2275ff9c..a9b50868 100644 --- a/public/js/v1.3.6/app/page.js +++ b/public/js/v1.3.6/app/page.js @@ -455,11 +455,7 @@ define([ * @param event * @param data */ - $.fn.triggerMenuEvent = function(event, data){ - if(data === undefined){ - data = {}; - } - + $.fn.triggerMenuEvent = function(event, data = {}){ $(this).trigger('pf:menu' + event, [data]); };