/** * EGroupware eTemplate2 - JS Favorite widget * * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License * @package etemplate * @subpackage api * @link http://www.egroupware.org * @author Nathan Gray * @copyright Nathan Gray 2013 * @version $Id$ */ "use strict"; /*egw:uses et2_dropdown_button; et2_extension_nextmatch; */ /** * Favorites widget, designed for use with a nextmatch widget * * The primary control is a split/dropdown button. Clicking on the left side of the button filters the * nextmatch list by the user's default filter. The right side of the button gives a list of * saved filters, pulled from preferences. Clicking a filter from the dropdown list sets the * filters as saved. * * Favorites can also automatically be shown in the sidebox, using the special ID favorite_sidebox. * Use the following code to generate the sidebox section: * display_sidebox($appname,lang('Favorites'),array( * array( * 'no_lang' => true, * 'text'=>'<span id="favorite_sidebox"/>', * 'link'=>false, * 'icon' => false * ) * )); * This sidebox list will be automatically generated and kept up to date. * * * Favorites are implemented by saving the values for [column] filters. Filters are stored * in preferences, with the name favorite_<name>. The favorite favorite used for clicking on * the filter button is stored in nextmatch-<columnselection_pref>-favorite. * * @augments et2_dropdown_button */ var et2_favorites = et2_dropdown_button.extend([et2_INextmatchHeader], { attributes: { "default_pref": { "name": "Default preference key", "type": "string", "description": "The preference key where default favorite is stored (not the value)" }, "sidebox_target": { "name": "Sidebox target", "type": "string", "description": "ID of element to insert favorite list into", "default": "favorite_sidebox" }, "app": { "name": "Application", "type": "string", "description": "Application to show favorites for" }, "filters": { "name": "Extra filters", "type": "any", "description": "Array of extra filters to include in the saved favorite" }, // These are particular to favorites id: {"default": "favorite"}, label: {"default": ""}, label_updates: { "default": false}, image: {"default": "etemplate/fav_filter"}, statustext: {"default": "Favorite queries", "type": "string"} }, // Some convenient variables, used in closures / event handlers header: null, nextmatch: null, favorite_prefix: "favorite_", stored_filters: {}, // If filter was set server side, we need to remember it until nm is created nm_filter: false, /** * Constructor * * @memberOf et2_favorites */ init: function() { this._super.apply(this, arguments); this.sidebox_target = $j("#"+this.options.sidebox_target); if(this.sidebox_target.length == 0 && egw_getFramework() != null) { var egw_fw = egw_getFramework(); this.sidebox_target = $j("#"+this.options.sidebox_target,egw_fw.sidemenuDiv); } // Store array of sorted items this.favSortedList = ['blank']; var apps = egw().user('apps'); this.is_admin = (typeof apps['admin'] != "undefined"); this.stored_filters = this.load_favorites(this.options.app); this.preferred = egw.preference(this.options.default_pref,this.options.app); if(!this.preferred || typeof this.stored_filters[this.preferred] == "undefined") { this.preferred = "blank"; } // It helps to have the ID properly set before we get too far this.set_id(this.id); this.init_filters(this); this.menu.addClass("favorites"); // Set the default (button) value this.set_value(this.preferred,true); var self = this; // Add a listener on the radio buttons to set default filter $j(this.menu).on("click","input:radio", function(event){ // Don't do the menu event.stopImmediatePropagation(); // Save as default favorite - used when you click the button self.egw().set_preference(self.options.app,self.options.default_pref,$j(this).val()); self.preferred = $j(this).val(); // Update sidebox, if there if(self.sidebox_target.length) { self.sidebox_target.find("div.ui-icon-heart") .replaceWith("<div class='sideboxstar'/>"); $j("li[data-id='"+self.preferred+"'] div.sideboxstar",self.sidebox_target) .replaceWith("<div class='ui-icon ui-icon-heart'/>"); } // Close the menu self.menu.hide(); // Some user feedback self.button.addClass("ui-state-active", 500,"swing",function(){ self.button.removeClass("ui-state-active",2000); }); }); //Sort DomNodes of sidebox fav. menu var sideBoxDOMNodeSort = function (_favSList) { var favS = jQuery.isArray(_favSList)?_favSList.slice(0).reverse():[]; for (var i=0; i < favS.length;i++) { self.sidebox_target.children().find('[data-id$="' + favS[i] + '"]').prependTo(self.sidebox_target.children()); } }; //Add Sortable handler to nm fav. menu $j(this.menu).sortable({ items:'li:not([data-id$="add"])', placeholder:'ui-fav-sortable-placeholder', delay: 250, //(millisecond) delay before the sorting should start update: function (event, ui) { self.favSortedList = jQuery(this).sortable('toArray', {attribute:'data-id'}); self.egw().set_preference(self.options.app,'fav_sort_pref',self.favSortedList); sideBoxDOMNodeSort(self.favSortedList); } }); // Add a listener on the delete to remove this.menu.on("click","div.ui-icon-trash", app[self.options.app], function() { // App instance might not be ready yet, so don't bind directly app[self.options.app].delete_favorite.apply(this,arguments); }) // Wrap and unwrap because jQueryUI styles use a parent, and we don't want to change the state of the menu item // Wrap in a span instead of a div because div gets a border .on("mouseenter","div.ui-icon-trash", function() {$j(this).wrap("<span class='ui-state-active'/>");}) .on("mouseleave","div.ui-icon-trash", function() {$j(this).unwrap();}); // Trigger refresh of menu options now that events are registered // to update sidebox if(this.sidebox_target.length > 0) { this.init_filters(this); } }, /** * Load favorites from preferences * * @param app String Load favorites from this application */ load_favorites: function(app) { // Default blank filter var stored_filters = { 'blank': { name: this.egw().lang("No filters"), state: {} } }; // Load saved favorites var preferences = egw.preference("*",app); for(var pref_name in preferences) { if(pref_name.indexOf(this.favorite_prefix) == 0 && typeof preferences[pref_name] == 'object') { var name = pref_name.substr(this.favorite_prefix.length); stored_filters[name] = preferences[pref_name]; // Keep older favorites working - they used to store nm filters in 'filters',not state if(preferences[pref_name].filters) { stored_filters[pref_name].state = preferences[pref_name].filters; } } if (pref_name == 'fav_sort_pref') { this.favSortedList = preferences[pref_name]; //Make sure sorted list is always an array, seems some old fav are not array if (!jQuery.isArray(this.favSortedList)) this.favSortedList = this.favSortedList.split(','); } } if(typeof stored_filters == "undefined" || !stored_filters) { stored_filters = {}; } else { for(var name in stored_filters) { if (this.favSortedList.indexOf(name) < 0) { this.favSortedList.push(name); } } this.egw().set_preference (this.options.app,'fav_sort_pref',this.favSortedList); if (this.favSortedList.length > 0) { var sortedListObj = {}; for (var i=0;i < this.favSortedList.length;i++) { if (typeof stored_filters[this.favSortedList[i]] != 'undefined') { sortedListObj[this.favSortedList[i]] = stored_filters[this.favSortedList[i]]; } else { this.favSortedList.splice(i,1); this.egw().set_preference (this.options.app,'fav_sort_pref',this.favSortedList); } } stored_filters = jQuery.extend(sortedListObj,stored_filters); } } return stored_filters; }, // Create & set filter options for dropdown menu init_filters: function(widget, filters) { if(typeof filters == "undefined") { filters = this.stored_filters; } var options = {}; for(var name in filters) { options[name] = "<input type='radio' name='"+this.internal_ids.menu+"[button][favorite]' value='"+name+"' title='" + this.egw().lang('Set as default') + "'/>"+ (filters[name].name != undefined ? filters[name].name : name) + (filters[name].group != false && !this.is_admin || name == 'blank' ? "" : "<div class='ui-icon ui-icon-trash' title='" + this.egw().lang('Delete') + "'/>"); } // Only add 'Add current' if we have a nextmatch if(this.nextmatch) { options.add = "<img src='"+this.egw().image("new") +"'/>"+this.egw().lang('Add current'); } widget.set_select_options.call(widget,options); // Set radio to current value $j("input[value='"+ this.preferred +"']:radio", this.menu).attr("checked",true); }, set_nm_filters: function(filters) { if(this.nextmatch) { this.nextmatch.applyFilters(filters); } else { console.log(filters); } }, onclick: function(node) { // Apply preferred filter - make sure it's an object, and not a reference if(this.preferred && this.stored_filters[this.preferred]) { // use app[appname].setState if available to allow app to overwrite it (eg. change to non-listview in calendar) if (typeof app[this.options.app] != 'undefined') { app[this.options.app].setState(this.stored_filters[this.preferred]); } else { this.set_nm_filters(jQuery.extend({},this.stored_filters[this.preferred].state)); } } else { alert(this.egw().lang("No default set")); } }, // Apply the favorite when you pick from the list change: function(selected_node) { this.value = $j(selected_node).attr("data-id"); if(this.value == "add" && this.nextmatch) { // Get current filters var current_filters = $j.extend({},this.nextmatch.activeFilters); // Add in extras for(var extra in this.options.filters) { // Don't overwrite what nm has, chances are nm has more up-to-date value if(typeof current_filters == 'undefined') { current_filters[extra] = this.nextmatch.options.settings[extra]; } } // Skip columns for now delete current_filters.selcolumns; // Add in application's settings if(this.filters != true) { for(var i = 0; i < this.filters.length; i++) { current_filters[this.options.filters[i]] = this.nextmatch.options.settings[this.options.filters[i]]; } } // Call framework app[this.options.app].add_favorite(current_filters); // Reset value this.set_value(this.preferred,true); } }, set_value: function(filter_name, parent) { if(parent) { return this._super.call(this, filter_name); } if(filter_name == 'add') return false; app[this.options.app].setState(this.stored_filters[filter_name]); return false; }, getValue: function() { return null; }, /** * Set the nextmatch to filter * From et2_INextmatchHeader interface * * @param {et2_nextmatch} nextmatch */ setNextmatch: function(nextmatch) { this.nextmatch = nextmatch; if(this.nm_filter) { this.set_value(this.nm_filter); this.nm_filter = false; } // Re-generate filter list so we can add 'Add current' this.init_filters(this); } }); et2_register_widget(et2_favorites, ["favorites"]);