diff --git a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php index 6161a2ea4d..331104e1b8 100644 --- a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php +++ b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php @@ -795,7 +795,10 @@ class etemplate_widget_nextmatch extends etemplate_widget */ public static function ajax_set_favorite($app, $name, $action, $group, $filters = array()) { - $pref_name = "favorite_".$name; + // Only use alphanumeric for preference name, so it can be used directly as DOM ID + $name = strip_tags($name); + $pref_name = "favorite_".preg_replace('/[^A-Za-z0-9-_]/','_',$name); + if($group && $GLOBALS['egw_info']['apps']['admin']) { $prefs = new preferences(is_numeric($group) ? $group: $GLOBALS['egw_info']['user']['account_id']); @@ -809,7 +812,9 @@ class etemplate_widget_nextmatch extends etemplate_widget if($action == "add") { $filters = array( - 'group' => $group, + // This is the name as user entered it, minus tags + 'name' => $name, + 'group' => $group ? $group : false, 'filter' => $filters ); $result = $prefs->add($app,$pref_name,$filters,$type); diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index 53cf8897b6..443d1c2b5b 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -1367,6 +1367,18 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { * 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: + * 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. @@ -1375,28 +1387,34 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { * additional fields/settings to add in to the favorite. */ _setup_favorites: function(filters) { - // Some convenient variables, used in closures / event handlers - var header = this; - var nextmatch = this.nextmatch; - var nm_div = this.nextmatch.div; - var favorite_prefix = "favorite_"; - var favorite_preference = "nextmatch-" + nextmatch.options.settings.columnselection_pref + "-favorite"; - - var apps = this.egw().user('apps'); - var is_admin = (typeof apps['admin'] != "undefined"); - if(typeof filters == "undefined") { // No favorites configured return; } + // Some convenient variables, used in closures / event handlers + var header = this; + var nextmatch = this.nextmatch; + var nm_div = this.nextmatch.div; + var favorite_prefix = "favorite_"; + var favorite_preference = "nextmatch-" + nextmatch.options.settings.columnselection_pref + "-favorite"; + var sidebox_target = $j("#favorite_sidebox"); + if(sidebox_target.length == 0 && egw_getFramework() != null) + { + var egw_fw = egw_getFramework(); + sidebox_target = $j("#favorite_sidebox",egw_fw.sidemenuDiv); + } + + var apps = this.egw().user('apps'); + var is_admin = (typeof apps['admin'] != "undefined"); var list = et2_csvSplit(this.options.get_rows, 2, "."); var app = list[0]; // Load saved favorites var stored_filters = {}; + var preferred = this.egw().preference(favorite_preference, app); var preferences = this.egw().preference("*",app); for(var pref_name in preferences) { @@ -1411,6 +1429,40 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { stored_filters = {}; } + /** + * Delete a favorite from the list and update preferences + * Registered as a handler on the delete icons + */ + var delete_favorite = function(event) + { + // Don't do the menu + event.stopImmediatePropagation(); + + var name = $j(this).parentsUntil("li").parent().attr("id"); + + // Make sure first + if(!confirm(header.egw().lang("Delete") + " " +stored_filters[name].name +"?")) return; + + // Hide the trash + $j(this).hide(); + + // Delete preference server side + var request = new egw_json_request("etemplate_widget_nextmatch::ajax_set_favorite::etemplate", + [app, name, "delete", stored_filters[name].group ? stored_filters[name].group : '', ''], + header + ); + request.sendRequest(true, function(result) { + if(result) + { + // Remove line from list + this.slideUp("slow", function() { header.favorites.menu.hide();}); + delete stored_filters[name]; + init_filters(header.favorites); + } + }, $j(this).parentsUntil("li").parent()); + + }; + // Create & set filter options for dropdown menu var init_filters = function(widget) { @@ -1418,7 +1470,8 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { for(var name in stored_filters) { options[name] = ""+ - name + (stored_filters[name].group != false ? " ♦" :"") + + (stored_filters[name].name != undefined ? stored_filters[name].name : name) + + (stored_filters[name].group != false ? " ♦" :"") + (stored_filters[name].group != false && !is_admin ? "" : "
"); } @@ -1427,6 +1480,21 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { // Set radio to current value $j("input[value='"+ preferred +"']:radio", header.favorites.menu).attr("checked",true); + + // Clone for sidebox + if(sidebox_target.length) + { + sidebox_target.empty(); + var sidebox_clone = widget.menu.clone(); + sidebox_clone + .appendTo(sidebox_target) + .menu() + .show() + .find("input:checked").replaceWith("
"); + sidebox_clone + .find("input").replaceWith(""); + + } }; // Favorite dropdown button @@ -1438,12 +1506,12 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { tooltip: "Favorite queries" }, this); + this.favorites.menu.addClass("favorites"); this.favorites.onclick= function(node) { // Apply preferred filter - make sure it's an object, and not a reference - var favorite = header.egw().preference(favorite_preference,app); - if(favorite && stored_filters[favorite]) + if(preferred && stored_filters[preferred]) { - nextmatch.activeFilters = jQuery.extend({},stored_filters[favorite].filter); + nextmatch.activeFilters = jQuery.extend({},stored_filters[preferred].filter); nextmatch.applyFilters(); } else @@ -1452,10 +1520,53 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { } }; - var preferred = this.egw().preference(favorite_preference, app); this.favorites.set_value(preferred && stored_filters[preferred] ? preferred : ""); init_filters(this.favorites); + // Initialize sidebox + if(sidebox_target.length) + { + sidebox_target + .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", delete_favorite); + if(header.favorites) + { + sidebox_target.on("click","li",function() { + header.favorites.set_value($j(this).attr("id")); + header.favorites.change(this); + }); + } + } + + // Add a listener on the radio buttons to set default filter + $j(this.favorites.menu).on("click","input:radio",function(event){ + // Don't do the menu + event.stopImmediatePropagation(); + + // Save as default favorite - used when you click the button + header.egw().set_preference(app,favorite_preference,$j(this).val()); + preferred = $j(this).val(); + + // Update sidebox, if there + if(sidebox_target.length) + { + sidebox_target.find(".ui-icon-heart") + .replaceWith(""); + $j("li#"+preferred+" img",sidebox_target) + .replaceWith("
"); + + } + + // Close the menu + header.favorites.menu.hide(); + + // Some user feedback + header.favorites.button.addClass("ui-state-active", 500,"swing",function(){ + header.favorites.button.removeClass("ui-state-active",2000); + }); + }); + // Apply the favorite when you pick from the list this.favorites.change = function(selected_node) { if(this.get_value() == "add") @@ -1507,6 +1618,9 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { // Popup header.favorites_popup.dialog("open"); + + // Reset value + this.set_value(preferred && stored_filters[preferred] ? preferred : ""); } else if(stored_filters[this.get_value()]) { @@ -1516,58 +1630,24 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { } }; - - // Add a listener on the radio buttons to set default filter - $j(this.favorites.menu).on("click","input:radio",function(event){ - // Don't do the menu - event.stopImmediatePropagation(); - - // Save as default favorite - used when you click the button - header.egw().set_preference(app,favorite_preference,$j(this).val()); - - // Close the menu - header.favorites.menu.hide(); - - // Some user feedback - header.favorites.button.addClass("ui-state-active", 500,"swing",function(){ - header.favorites.button.removeClass("ui-state-active",2000); - }); - }); // Add a listener on the delete to remove - $j(this.favorites.menu).on("click","div.ui-icon-trash", function(event) { - // Don't do the menu - event.stopImmediatePropagation(); - - // Hide the trash - $j(this).hide(); - - var name = $j(this).parentsUntil("li").parent().attr("id"); - - // Delete preference server side - var request = new egw_json_request("etemplate_widget_nextmatch::ajax_set_favorite::etemplate", - [app, name, "delete", stored_filters[name].group ? stored_filters[name].group : '', ''], - header - ); - request.sendRequest(true, function(result) { - if(result) - { - // Remove line from list - this.slideUp("slow", function() {header.favorites.menu.hide();}); - delete stored_filters[name]; - init_filters(header.favorites); - } - }, $j(this).parentsUntil("li").parent()); - - }) + $j(this.favorites.menu).on("click","div.ui-icon-trash", delete_favorite) // Wrap and unwrap because jQueryUI styles use a parent, and we don't want to change the state of the menu item - // Use a span instead of a div because div gets a border + // 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();}); // Add into header $j(this.favorites.getDOMNode(this.favorites)).insertAfter(this.count).css("float","right"); + // Trigger refresh of menu options now that events are registered + // to update sidebox + if(sidebox_target.length > 0) + { + init_filters(this.favorites); + } + // Create popup this.favorites_popup = $j('
\
\ @@ -1610,15 +1690,18 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { if(name.val()) { - // Add to the list - stored_filters[name.val()] = { - group: (typeof header.favorites_popup.group != "undefined" ? header.favorites_popup.group.get_value() != "": false), + name.val(name.val().replace(/(<([^>]+)>)/ig,"")); + var safe_name = name.val().replace(/[^A-Za-z0-9-_]/g,"_"); + stored_filters[safe_name] = { + name: name.val(), + group: (typeof header.favorites_popup.group != "undefined" && + header.favorites_popup.group.get_value() ? header.favorites_popup.group.get_value() : false), filter: header.favorites_popup.current_filters }; init_filters(header.favorites); - var favorite_pref = favorite_prefix+name.val(); + var favorite_pref = favorite_prefix+safe_name; // Save to preferences if(typeof header.favorites_popup.group != "undefined") @@ -1645,7 +1728,11 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, { else { // Normal user - just save to preferences client side - header.egw().set_preference(app,favorite_pref,{filter:header.favorites_popup.current_filters}); + header.egw().set_preference(app,favorite_pref,{ + name: name.val(), + group: false, + filter:header.favorites_popup.current_filters + }); } delete header.favorites_popup.current_filters; } diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index c1da15b7ef..74494fbd4c 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -768,17 +768,27 @@ label input, label span, label div, label select, label textarea { width: 45%; display: inline-block; } -#favorite\[button\]_menu li a { +.favorites li a { } -#favorite\[button\]_menu input, #favorite\[button\]_menu img { +.favorites input, .favorites img { margin-right: 2ex; } -#favorite\[button\]_menu div.ui-icon { +.favorites div.ui-icon-trash { float:right; display:none; } -#favorite\[button\]_menu li:hover div.ui-icon { - display:block; +.favorites li:hover div.ui-icon { + display:inline-block; +} +#favorite_sidebox ul { + width: 99%; + padding: 0px; + border: none; + background: white; +} +#favorite_sidebox div.ui-icon-heart{ + float: left; + display:inline-block; } .nextmatch_sortheader {