/** * 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'=>'', * '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_. The favorite favorite used for clicking on * the filter button is stored in nextmatch--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); } 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); // If a pre-selected filter was passed from server if(typeof this.options.value != "undefined") { this.set_value(this.options.value); } var self = this; // Initialize sidebox this._init_sidebox(); // 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(".ui-icon-heart") .replaceWith(""); $j("li[data-id='"+self.preferred+"'] img",self.sidebox_target) .replaceWith("
"); } // 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); }); }); // Add a listener on the delete to remove this.menu.on("click","div.ui-icon-trash", this, this.delete_favorite) // 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("");}) .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); } }, destroy: function() { if(this.popup != null) { if(this.popup.group) { this.popup.group.free(); delete this.popup.group; } this.popup.dialog("destroy"); this.popup = null; } if(this.sidebox_target.length) { this.sidebox_target .off() .empty(); } this._super.apply(this, arguments); }, /** * 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"), filters: {}, } }; // Load saved favorites var preferences = egw.preference("*",app); for(var pref_name in preferences) { if(pref_name.indexOf(this.favorite_prefix) == 0) { var name = pref_name.substr(this.favorite_prefix.length); stored_filters[name] = preferences[pref_name]; } } if(typeof stored_filters == "undefined" || !stored_filters) { stored_filters = {}; } return stored_filters; }, /** * Delete a favorite from the list and update preferences * Registered as a handler on the delete icons */ delete_favorite: function(event) { // Don't do the menu event.stopImmediatePropagation(); var header = event.data; var name = $j(this).parentsUntil("li").parent().attr("data-id"); var trash = this; // Make sure first var do_delete = function(button_id) { if(button_id != et2_dialog.YES_BUTTON) return; // Hide the trash $j(trash).hide(); // Delete preference server side var request = egw.json("etemplate_widget_nextmatch::ajax_set_favorite::etemplate", [header.app, name, "delete", header.stored_filters[name].group ? header.stored_filters[name].group : '', ''], function(result) { if(result) { // Remove line from list this.slideUp("slow", function() { header.menu.hide();}); delete header.stored_filters[name]; header.init_filters(header); } }, $j(trash).parentsUntil("li").parent(), true, $j(trash).parentsUntil("li").parent() ); request.sendRequest(); } et2_dialog.show_dialog(do_delete, (header.egw().lang("Delete") + " " +header.stored_filters[name].name +"?"), "Delete", et2_dialog.YES_NO, et2_dialog.QUESTION_MESSAGE); }, // 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] = ""+ (filters[name].name != undefined ? filters[name].name : name) + (filters[name].group != false ? " ♦" :"") + (filters[name].group != false && !this.is_admin || name == 'blank' ? "" : "
"); } // Only add 'Add current' if we have a nextmatch if(this.nextmatch) { options.add = "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); // Clone for sidebox if(this.sidebox_target.length) { this.sidebox_target.empty(); var sidebox_clone = widget.menu.clone(); sidebox_clone .appendTo(this.sidebox_target) .removeAttr('style') .menu() .removeClass("ui-widget") .show() .find("input:checked").replaceWith("
"); sidebox_clone .find("input").replaceWith(""); } }, /** * Find sidebox, if not found yet, and register handlers */ _init_sidebox: function() { // Sometimes the sidebox is not loaded when the template is created (jdots) if(this.options && (!this.sidebox_target || this.sidebox_target.length == 0)) { 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); } if(this.sidebox_target.length == 0) { // Still no sidebox - might be loaded via ajax later, so we'll do this on first mouse over $j('body').on('mouseover','#'+this.options.sidebox_target, jQuery.proxy(function(e) { // Set up handlers & such this._init_sidebox(); // It will still have the plain HTML, so re-create the contents this.init_filters(this); $j('body').off(e); },this) ); } } if(this.sidebox_target.length) { var self = this; this.sidebox_target .off() .on("mouseenter","div.ui-icon-trash", function() {$j(this).wrap("");}) .on("mouseleave","div.ui-icon-trash", function() {$j(this).unwrap();}) .on("click","div.ui-icon-trash", this, this.delete_favorite) .addClass("ui-helper-clearfix"); this.sidebox_target.on("click","li",function() { self.set_value($j(this).attr("data-id")); self.change(this); }); } }, set_nm_filters: function(filters) { if(this.nextmatch) { this.nextmatch.activeFilters = filters; this.nextmatch.applyFilters(); } 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]) { this.set_nm_filters(jQuery.extend({},this.stored_filters[this.preferred].filter)); } 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 this.popup.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 this.popup.current_filters == 'undefined') { this.popup.current_filters[extra] = this.nextmatch.options.settings[extra]; } } // Skip columns for now delete this.popup.current_filters.selcolumns; // Add in application's settings if(this.filters != true) { for(var i = 0; i < this.filters.length; i++) { this.popup.current_filters[this.options.filters[i]] = this.nextmatch.options.settings[this.options.filters[i]]; } } // Remove some internal values delete this.popup.current_filters[this.id]; if(this.popup.group != undefined) { delete this.popup.current_filters[this.popup.group.id]; } // Make sure it's an object - deep copy to prevent references in sub-objects (col_filters) this.popup.current_filters = jQuery.extend(true,{},this.popup.current_filters); // Update popup with current set filters (more for debug than user) var filter_list = []; var add_to_popup = function(arr) { filter_list.push("
    "); jQuery.each(arr, function(index, filter) { filter_list.push("
  • "+index+"" + (typeof filter != "object" ? ""+filter+"": "") ); if(typeof filter == "object" && filter != null) add_to_popup(filter); filter_list.push("
  • "); }); filter_list.push("
"); } add_to_popup(this.popup.current_filters); $j("#nm_favorites_popup_filters",this.popup) .replaceWith( $j(filter_list.join("")).attr("id","nm_favorites_popup_filters") ); $j("#nm_favorites_popup_filters",this.popup) .hide() .siblings(".ui-icon-circle-plus") .removeClass("ui-icon-circle-minus"); // Popup this.popup.dialog("open"); // Reset value this.set_value(this.preferred,true); } }, /** * Create the "Add new" popup dialog */ create_popup: function() { var self = this; // Clear old, if existing if(this.popup && this.popup.group) { this.popup.group.free(); delete this.popup; } // Create popup this.popup = $j('
\
\ ' + '\
\ '+ this.egw().lang("Details") + '\
    \ \
' ).appendTo(this.div); $j(".ui-icon-circle-plus",this.popup).prev().andSelf().click(function() { var details = $j("#nm_favorites_popup_filters",this.popup) .slideToggle() .siblings(".ui-icon-circle-plus") .toggleClass("ui-icon-circle-minus"); }); // Add some controls if user is an admin if(this.is_admin) { this.popup.group = et2_createWidget("select-account",{ id: "favorite[group]", account_type: "groups", empty_label: "Groups", no_lang: true, parent_node: this.dom_id+'nm_favorites_popup_admin' },this); } var buttons = {}; buttons[this.egw().lang("save")] = function() { // Add a new favorite var name = $j("#name",this); if(name.val()) { // Add to the list name.val(name.val().replace(/(<([^>]+)>)/ig,"")); var safe_name = name.val().replace(/[^A-Za-z0-9-_]/g,"_"); self.stored_filters[safe_name] = { name: name.val(), group: (typeof self.popup.group != "undefined" && self.popup.group.get_value() ? self.popup.group.get_value() : false), filter: self.popup.current_filters }; self.init_filters(self); var favorite_pref = self.favorite_prefix+safe_name; // Save to preferences if(typeof self.popup.group != "undefined" && self.popup.group.getValue() != '') { // Admin stuff - save preference server side var request = egw.json("etemplate_widget_nextmatch::ajax_set_favorite::etemplate", [ self.options.app, name.val(), "add", self.popup.group.get_value(), self.popup.current_filters ] ); request.sendRequest(); self.popup.group.set_value(''); } else { // Normal user - just save to preferences client side self.egw().set_preference(self.options.app,favorite_pref,{ name: name.val(), group: false, filter:self.popup.current_filters }); } delete self.popup.current_filters; } // Reset form name.val(""); $j("#filters",self.popup).empty(); $j(this).dialog("close"); }; buttons[this.egw().lang("cancel")] = function() { self.popup.group.set_value(null); $j(this).dialog("close"); }; this.popup.dialog({ autoOpen: false, modal: true, buttons: buttons, close: function() { } }); }, set_value: function(filter_name, parent) { if(parent) { return this._super.apply(filter_name); } if(this.nextmatch) { if(this.stored_filters[filter_name]) { // Apply selected filter - make sure it's an object, and not a reference this.set_nm_filters(jQuery.extend(true, {},this.stored_filters[filter_name].filter)); } } else { // Too soon - nm doesn't exist yet this.nm_filter = filter_name; } }, getValue: function() { return null; }, /** * Set the nextmatch to filter * From et2_INextmatchHeader interface */ 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); // With no Add current, this is only needed when there's a nm this.create_popup(); } }); et2_register_widget(et2_favorites, ["favorites"]);