diff --git a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php
index 331104e1b8..f8170d6cbd 100644
--- a/etemplate/inc/class.etemplate_widget_nextmatch.inc.php
+++ b/etemplate/inc/class.etemplate_widget_nextmatch.inc.php
@@ -114,8 +114,49 @@ class etemplate_widget_nextmatch extends etemplate_widget
$value['rows'] = array();
$send_value = $value;
+
+ // Check for a favorite in URL
+ if($_GET['favorite'] && $value['favorites'])
+ {
+ list($app) = explode('.',$value['get_rows']);
+ $safe_name = preg_replace('/[^A-Za-z0-9-_]/','_',strip_tags($_GET['favorite']));
+ $pref_name = "favorite_" .$safe_name;
+
+ // Do some easy applying of filters server side
+ $favorite = $GLOBALS['egw_info']['user']['preferences'][$app][$pref_name];
+ if(!$favorite && $_GET['favorite'] == 'blank')
+ {
+ // Have to go through each of these
+ foreach(array('search','cat_id','filter','filter2') as $filter)
+ {
+ $send_value[$filter] = '';
+ }
+ unset($send_value['col_filter']);
+ }
+ if($favorite && $favorite['filter'])
+ {
+ $send_value = array_merge($value, $favorite['filter']);
+
+ // Ajax call can handle the saved sort here, but this can't
+ if($favorite['filter']['sort'])
+ {
+ unset($send_value['sort']);
+ $send_value['order'] = $favorite['filter']['sort']['id'];
+ $send_value['sort'] = $favorite['filter']['sort']['asc'] ? 'ASC' : 'DESC';
+ }
+ }
+ }
+ // Make sure it's not set
+ unset($send_value['favorite']);
+
$total = self::call_get_rows($send_value, $send_value['rows'], self::$request->readonlys);
$value =& self::get_array(self::$request->content, $form_name, true);
+
+ // Add favorite here so app doesn't save it in the session
+ if($_GET['favorite'])
+ {
+ $send_value['favorite'] = $safe_name;
+ }
$value = $send_value;
$value['total'] = $total;
@@ -725,6 +766,7 @@ class etemplate_widget_nextmatch extends etemplate_widget
return $cat_actions;
}
+
/**
* Validate input
*
@@ -746,6 +788,9 @@ class etemplate_widget_nextmatch extends etemplate_widget
$value = self::get_array($content, $form_name);
list($app) = explode('.',$this->attrs['template']);
+ unset($value['favorite']);
+_debug_array($value);
+
// On client, rows does not get its own namespace, but all apps are expecting it
$value['rows'] = $value;
@@ -756,6 +801,7 @@ class etemplate_widget_nextmatch extends etemplate_widget
$validated += $content[$value[$preserve['action_var']].'_popup'];
}
+
// Save current column settings as default (admins only)
if($value['as_default'])
{
@@ -780,6 +826,61 @@ class etemplate_widget_nextmatch extends etemplate_widget
}
+ /**
+ * Include favorites when generating the page server-side
+ *
+ * Use this function in your sidebox (or anywhere else, I suppose) to
+ * get the favorite list when a nextmatch is _not_ on the page. If
+ * a nextmatch is on the page, it will update / replace this list.
+ *
+ * @param $app String Current application, needed to find preferences
+ * @param $default String Preference name for default favorite
+ *
+ * @return String HTML fragment for the list
+ */
+ public static function favorite_list($app, $default)
+ {
+ if(!$app) return '';
+ $target = 'favorite_sidebox';
+ $pref_prefix = 'favorite_';
+ $filters = array(
+ 'blank' => array(
+ 'name' => lang('Blank'),
+ 'filters' => array()
+ )
+ );
+ $default_filter = $GLOBALS['egw_info']['user']['preferences'][$app][$default];
+ if(!$default_filter) $default_filter = "blank";
+
+ $html = "';
+ return $html;
+ }
+
/**
* Create or delete a favorite for multiple users
*
diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js
index 443d1c2b5b..bb20e73d7e 100644
--- a/etemplate/js/et2_extension_nextmatch.js
+++ b/etemplate/js/et2_extension_nextmatch.js
@@ -32,6 +32,7 @@
et2_widget_template;
et2_widget_grid;
et2_widget_selectbox;
+ et2_widget_selectAccount;
et2_extension_customfields;
// Include all nextmatch subclasses
@@ -122,6 +123,10 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], {
// Create the outer grid container
this.dataview = new et2_dataview(this.innerDiv, this.egw());
+ // Blank placeholder
+ this.blank = $j(document.createElement("div"))
+ .appendTo(this.dataview.table);
+
// We cannot create the grid controller now, as this depends on the grid
// instance, which can first be created once we have the columns
this.controller = null;
@@ -265,6 +270,9 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput], {
// Update any column filters
this.iterateOver(function(column) {
+ // Skip favorites - it implements et2_INextmatchHeader, but we don't want it in the filter
+ if(typeof column.id != "undefined" && column.id.indexOf('favorite') == 0) return;
+
if(typeof column.set_value != "undefined" && column.id)
{
column.set_value(typeof this[column.id] == "undefined" || this[column.id] == null ? "" : this[column.id]);
@@ -1085,9 +1093,6 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, {
destroy: function() {
this.nextmatch = null;
this.div = null;
- this.favorites = null;
- this.favorites_popup.dialog("destroy");
- this.favorites_popup = null;
},
setNextmatch: function(nextmatch) {
@@ -1362,27 +1367,6 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, {
/**
* Set up the favorites UI control
*
- * The 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:
- * 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.
- *
* @param filters Array|boolean The nextmatch setting for favorites. Either true, or a list of
* additional fields/settings to add in to the favorite.
*/
@@ -1393,366 +1377,16 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, {
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)
- {
- if(pref_name.indexOf(favorite_prefix) == 0)
- {
- var name = pref_name.substr(favorite_prefix.length);
- stored_filters[name] = preferences[pref_name];
- }
- }
- if(typeof stored_filters == "undefined" || !stored_filters)
- {
- 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());
-
+ var widget_options = {
+ default_pref: "nextmatch-" + this.nextmatch.options.settings.columnselection_pref + "-favorite",
+ app: list[0],
+ filters: filters
};
-
- // Create & set filter options for dropdown menu
- var init_filters = function(widget)
- {
- var options = {};
- for(var name in stored_filters)
- {
- options[name] = ""+
- (stored_filters[name].name != undefined ? stored_filters[name].name : name) +
- (stored_filters[name].group != false ? " ♦" :"") +
- (stored_filters[name].group != false && !is_admin ? "" :
- "");
- }
- options.add = "Add current";
- widget.set_select_options(options);
-
- // 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
- this.favorites = et2_createWidget("dropdown_button", {
- id: "favorite[button]",
- label: "",
- label_updates: false,
- image: "etemplate/fav_filter",
- 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
- if(preferred && stored_filters[preferred])
- {
- nextmatch.activeFilters = jQuery.extend({},stored_filters[preferred].filter);
- nextmatch.applyFilters();
- }
- else
- {
- alert(this.egw().lang("No default set"));
- }
- };
-
- 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")
- {
- // Get current filters
- header.favorites_popup.current_filters = $j.extend({},nextmatch.activeFilters);
-
- // Add in application's settings
- if(filters != true)
- {
- for(var i = 0; i < filters.length; i++)
- {
- header.favorites_popup.current_filters[filters[i]] = nextmatch.options.settings[filters[i]];
- }
- }
-
- // Remove some internal values
- delete header.favorites_popup.current_filters[header.favorites.id];
- if(header.favorites_popup.group != undefined)
- {
- delete header.favorites_popup.current_filters[header.favorites_popup.group.id];
- }
-
- // Make sure it's an object - deep copy to prevent references in sub-objects (col_filters)
- header.favorites_popup.current_filters = jQuery.extend(true,{},header.favorites_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(header.favorites_popup.current_filters);
- $j("#nm_favorites_popup_filters",header.favorites_popup)
- .replaceWith(
- $j(filter_list.join("")).attr("id","nm_favorites_popup_filters")
- );
- $j("#nm_favorites_popup_filters",header.favorites_popup)
- .hide()
- .siblings(".ui-icon-circle-plus")
- .removeClass("ui-icon-circle-minus");
-
- // Popup
- header.favorites_popup.dialog("open");
-
- // Reset value
- this.set_value(preferred && stored_filters[preferred] ? preferred : "");
- }
- else if(stored_filters[this.get_value()])
- {
- // Apply selected filter - make sure it's an object, and not a reference
- nextmatch.activeFilters = jQuery.extend(true, {},stored_filters[this.get_value()].filter);
- nextmatch.applyFilters();
- }
- };
-
-
- // Add a listener on the delete to remove
- $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
- // 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();});
+ this.favorites = et2_createWidget('favorites', widget_options, this);
// 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('');
- $j(".ui-icon-circle-plus",this.favorites_popup).prev().andSelf().click(function() {
- var details = $j("#nm_favorites_popup_filters",header.favorites_popup)
- .slideToggle()
- .siblings(".ui-icon-circle-plus")
- .toggleClass("ui-icon-circle-minus");
- });
- this.favorites_popup.name = et2_createWidget("text",{id:"favorite[name]"},this);
- $j("name",this.favorites_popup).replaceWith(this.favorites_popup.name.getDOMNode());
-
- // Add some controls if user is an admin
- if(apps['admin'])
- {
- this.favorites_popup.group = et2_createWidget("select-account",{
- id: "favorite[group]",
- account_type: "groups",
- empty_label: "Groups",
- no_lang: true
- },this);
- $j("#nm_favorites_popup_admin",this.favorites_popup)
- .append(this.favorites_popup.group.getDOMNode(this.favorites_popup.group));
-
- }
- 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,"_");
- 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+safe_name;
-
- // Save to preferences
- if(typeof header.favorites_popup.group != "undefined")
- {
- // Admin stuff - save preference server side
- var request = new egw_json_request("etemplate_widget_nextmatch::ajax_set_favorite::etemplate",
- [
- app,
- name.val(),
- "add",
- header.favorites_popup.group.get_value(),
- header.favorites_popup.current_filters
- ],
- header
- );
- request.sendRequest(true, function(result) {
- if(result)
- {
- // Do something nice and confirmy if result is true - not really needed
- }
- }, header);
- header.favorites_popup.group.set_value("");
- }
- else
- {
- // Normal user - just save to preferences client side
- header.egw().set_preference(app,favorite_pref,{
- name: name.val(),
- group: false,
- filter:header.favorites_popup.current_filters
- });
- }
- delete header.favorites_popup.current_filters;
- }
- // Reset form
- name.val("");
- $j("#filters",header.favorites_popup).empty();
-
- $j(this).dialog("close");
- };
- buttons[this.egw().lang("cancel")] = function() {
- $j(this).dialog("close");
- };
-
- this.favorites_popup.dialog({
- autoOpen: false,
- modal: true,
- buttons: buttons,
- close: function() {
- }
- });
},
/**
@@ -1765,6 +1399,9 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, {
*/
setFilters: function(filters) {
this.iterateOver(function(child) {
+ // Skip favorites, don't want them in the filter
+ if(typeof child.id != "undefined" && child.id.indexOf("favorite") == 0) return;
+
if(typeof child.set_value != "undefined" && child.id)
{
child.set_value(typeof this[child.id] == "undefined" || this[child.id] == null ? "" : this[child.id]);
@@ -1784,13 +1421,6 @@ var et2_nextmatch_header_bar = et2_DOMWidget.extend(et2_INextmatchHeader, {
// Set activeFilters to current value
filters.searchletter = $j("td.lettersearch_active").attr("id")
}
-
- // Remove some internal values
- if(typeof this.favorites != "undefined")
- {
- delete filters[this.favorites.id];
- if(this.favorites_popup.group != undefined) delete filters[this.favorites_popup.group.id];
- }
},
/**
diff --git a/etemplate/js/et2_widget_favorites.js b/etemplate/js/et2_widget_favorites.js
new file mode 100644
index 0000000000..5677fc28d3
--- /dev/null
+++ b/etemplate/js/et2_widget_favorites.js
@@ -0,0 +1,551 @@
+/**
+ * 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.
+ */
+
+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"},
+ tooltip: {"default": "Favorite queries"}
+ },
+
+ // Some convenient variables, used in closures / event handlers
+ header: null,
+ nextmatch: null,
+ favorite_prefix: "favorite_",
+
+ // If filter was set server side, we need to remember it until nm is created
+ nm_filter: false,
+
+ 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("#favorite_sidebox",egw_fw.sidemenuDiv);
+ }
+
+ var apps = egw().user('apps');
+ this.is_admin = (typeof apps['admin'] != "undefined");
+
+ this.preferred = this.egw().preference(this.options.default_pref,this.options.app);
+ if(!this.preferred || typeof this.stored_filters[this.preferred] == "undefined")
+ {
+ this.preferred = "blank";
+ }
+
+ this.stored_filters = this.load_favorites(this.options.app);
+
+ 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
+ if(this.sidebox_target.length)
+ {
+ this.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", this, this.delete_favorite)
+ .addClass("ui-helper-clearfix");
+ this.sidebox_target.on("click","li",function() {
+ self.set_value($j(this).attr("id"));
+ self.change(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.favorite_preference,$j(this).val());
+ preferred = $j(this).val();
+
+ // Update sidebox, if there
+ if(self.sidebox_target.length)
+ {
+ self.sidebox_target.find(".ui-icon-heart")
+ .replaceWith("");
+ $j("li#"+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() {
+ this.popup.dialog("destroy");
+ this.popup = null;
+ 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("Blank"),
+ filters: {},
+ }
+ };
+
+ // Load saved favorites
+ var preferences = this.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("id");
+
+ // Make sure first
+ if(!confirm(header.egw().lang("Delete") + " " +header.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",
+ [header.app, name, "delete", header.stored_filters[name].group ? header.stored_filters[name].group : '', ''],
+ header
+ );
+ request.sendRequest(true, 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(this).parentsUntil("li").parent());
+
+ },
+
+ // 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(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)
+ .menu()
+ .removeClass("ui-widget")
+ .show()
+ .find("input:checked").replaceWith("");
+ sidebox_clone
+ .find("input").replaceWith("");
+
+ }
+ },
+
+ 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("id");
+ if(this.value == "add" && this.nextmatch)
+ {
+ // Get current filters
+ this.popup.current_filters = $j.extend({},this.nextmatch.activeFilters);
+
+ // 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;
+
+ // Create popup
+ this.popup = $j(''
+ ).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: 'nm_favorites_popup_admin'
+ },this);
+/*
+ $j("#nm_favorites_popup_admin",this.popup)
+ .append(this.popup.group.getDOMNode(this.popup.group));
+*/
+
+ }
+
+ 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")
+ {
+ // Admin stuff - save preference server side
+ var request = new egw_json_request("etemplate_widget_nextmatch::ajax_set_favorite::etemplate",
+ [
+ self.options.app,
+ name.val(),
+ "add",
+ self.popup.group.get_value(),
+ self.popup.current_filters
+ ],
+ self
+ );
+ request.sendRequest(true, function(result) {
+ if(result)
+ {
+ // Do something nice and confirmy if result is true - not really needed
+ }
+ }, self);
+ self.popup.group.set_value(null);
+ }
+ 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"]);
diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js
index fb9674b38b..69412345d6 100644
--- a/etemplate/js/etemplate2.js
+++ b/etemplate/js/etemplate2.js
@@ -31,6 +31,7 @@
et2_widget_diff;
et2_widget_dropdown_button;
et2_widget_styles;
+ et2_widget_favorites;
et2_widget_html;
et2_widget_htmlarea;
et2_widget_tabs;