diff --git a/calendar/js/app.js b/calendar/js/app.js index 8a7d9eff02..fbcde75cbc 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -1283,6 +1283,12 @@ app.classes.calendar = AppJS.extend( jQuery.extend(state, this._super.apply(this, arguments)); // call default implementation } + // Make sure date is consitantly a string, in case it needs to be passed to server + if(state.date.toJSON) + { + state.state = state.date.toJSON(); + } + // Don't store current user in state to allow admins to create favourites for all // Should make no difference for normal users. if(state.owner == egw.user('account_id')) @@ -1291,6 +1297,10 @@ app.classes.calendar = AppJS.extend( // it will work for other users too. state.owner = 0; } + // Don't store first and last + delete state.first; + delete state.last; + return state; }, diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index 1bba2edac8..571926e067 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -412,6 +412,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin */ applyFilters: function(_set) { var changed = false; + var keep_selection = false; // Avoid loops cause by change events if(this.update_in_progress) return; @@ -464,6 +465,17 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin } } } + else if (s === 'selected') + { + changed = true; + keep_selection = true; + this.controller._selectionMgr.resetSelection(); + for(var i in _set.selected) + { + this.controller._selectionMgr.setSelected(_set.selected[i].indexOf('::') > 0 ? _set.selected[i] : this.controller.dataStorePrefix + '::'+_set.selected[i],true); + } + delete _set.selected; + } else if (this.activeFilters[s] !== _set[s]) { this.activeFilters[s] = _set[s]; @@ -475,7 +487,7 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin this.egw().debug("info", "Changing nextmatch filters to ", this.activeFilters); // Keep the selection after applying filters, but only if unchanged - if(!changed) + if(!changed || keep_selection) { this.controller.keepSelection(); } @@ -509,6 +521,19 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrin // Trigger an update this.controller.update(true); + if(changed) + { + // Highlight matching favorite in sidebox + if(this.getInstanceManager().app) + { + var app = this.getInstanceManager().app; + if(window.app[app] && window.app[app].highlight_favorite) + { + window.app[app].highlight_favorite(); + } + } + } + this.update_in_progress = false; }, diff --git a/phpgwapi/js/jsapi/app_base.js b/phpgwapi/js/jsapi/app_base.js index aa176a3d49..6fc5c656da 100644 --- a/phpgwapi/js/jsapi/app_base.js +++ b/phpgwapi/js/jsapi/app_base.js @@ -142,6 +142,9 @@ var AppJS = Class.extend( this.et2 = et2.widgetContainer; this._fix_iFrameScrolling(); if (this.egw.is_popup()) this._set_Window_title(); + + // Highlights the favorite based on initial list state + this.highlight_favorite(); }, /** @@ -363,9 +366,21 @@ var AppJS = Class.extend( sidebox .off() // removed .on("mouse(enter|leave)" (wrapping trash icon), as it stalls delete in IE11 - .on("click","div.ui-icon-trash", this, this.delete_favorite) + .on("click.sidebox","div.ui-icon-trash", this, this.delete_favorite) // need to install a favorite handler, as we switch original one off with .off() - .on('click','li[data-id]', this, function(event) { + .on('click.sidebox','li[data-id]', this, function(event) { + var li = $j(this); + li.siblings().removeClass('ui-state-highlight'); + + // Wait an arbitrary 50ms to avoid having the class removed again + // by the change handler. + if(li.attr('data-id') !== 'blank') + { + window.setTimeout(function() { + li.addClass('ui-state-highlight'); + },50); + } + var href = jQuery('a[href^="javascript:"]', this).prop('href'); var matches = href ? href.match(/^javascript:([^\(]+)\((.*)?\);?$/) : null; if (matches && matches.length > 1 && matches[2] !== undefined) @@ -401,6 +416,18 @@ var AppJS = Class.extend( self._refresh_fav_nm(); } }); + + // Bind favorite de-select + var egw_fw = egw_getFramework(); + if(egw_fw && egw_fw.applications[this.appname] && egw_fw.applications[this.appname].browser + && egw_fw.applications[this.appname].browser.baseDiv) + { + $j(egw_fw.applications[this.appname].browser.baseDiv) + .off('.sidebox') + .on('change.sidebox', function() { + self.highlight_favorite(); + }); + } return true; } return false; @@ -721,6 +748,102 @@ var AppJS = Class.extend( return false; }, + /** + * Mark the favorite closest matching the current state + * + * Closest matching takes into account not set values, so we pick the favorite + * with the most matching values without a value that differs. + */ + highlight_favorite: function() { + if(!this.sidebox) return; + + var state = this.getState(); + var best_match = false; + var best_count = 0; + + $j('li[data-id]',this.sidebox).removeClass('ui-state-highlight'); + + $j('li[data-id] a[href^="javascript:"]',this.sidebox).each(function(i,href) { + + var matches = href.href ? href.href.match(/^javascript:([^\(]+)\((.*)?\);?$/) : null; + var favorite = {} + if (matches && matches.length > 1 && matches[2] !== undefined) + { + favorite = JSON.parse(decodeURI(matches[2])); + } + if(!favorite || jQuery.isEmptyObject(favorite)) return; + + var match_count = 0; + for(var state_key in state) + { + if(state[state_key] == favorite.state[state_key] || !state[state_key] && !favorite.state[state_key]) + { + match_count++; + } + else if (state[state_key] && typeof state[state_key] === 'object' && favorite.state[state_key] && typeof favorite.state[state_key] === 'object') + { + if((typeof state[state_key].length !== 'undefined' || typeof state[state_key].length !== 'undefined') + && (state[state_key].length || Object.keys(state[state_key]).length) != (favorite.state[state_key].length || Object.keys(favorite.state[state_key]).length )) + { + // State or favorite has a length, but the other does not + if((state[state_key].length === 0 || Object.keys(state[state_key]).length === 0) && + (favorite.state[state_key].length == 0 || Object.keys(favorite.state[state_key]).length === 0)) + { + // Just missing, or one is an array and the other is an object + continue; + } + // One has a value and the other doesn't, no match + debugger; + return; + } + // Consider sub-objects (column filters) individually + for(var sub_key in state[state_key]) + { + if(state[state_key][sub_key] == favorite.state[state_key][sub_key] || !state[state_key][sub_key] && !favorite.state[state_key][sub_key]) + { + match_count++; + } + else if (state[state_key][sub_key] && favorite.state[state_key][sub_key] && + typeof state[state_key][sub_key] === 'object' && typeof favorite.state[state_key][sub_key] === 'object') + { + // Too deep to keep going, just string compare for perfect match + if(JSON.stringify(state[state_key][sub_key]) === JSON.stringify(favorite.state[state_key][sub_key])) + { + match_count++; + } + } + else if(state[state_key][sub_key] && state[state_key][sub_key] != favorite.state[state_key][sub_key]) + { + // Different values, do not match + debugger; + return; + } + + } + } + else if (state_key == 'selectcols') + { + // Skip, might be set, might not + } + else if (typeof state[state_key] !== 'undefined' && state[state_key] != favorite.state[state_key]) + { + // Different values, do not match + debugger; + return; + } + } + if(match_count > best_count) + { + best_match = href.parentNode.dataset.id; + best_count = match_count; + } + }); + if(best_match) + { + $j('li[data-id="'+best_match+'"]',this.sidebox).addClass('ui-state-highlight'); + } + }, + /** * Fix scrolling iframe browsed by iPhone/iPod/iPad touch devices */