diff --git a/api/js/etemplate/et2_widget_dropdown_button.js b/api/js/etemplate/et2_widget_dropdown_button.js
index e72c219211..f08c16eec3 100644
--- a/api/js/etemplate/et2_widget_dropdown_button.js
+++ b/api/js/etemplate/et2_widget_dropdown_button.js
@@ -377,5 +377,6 @@ var et2_dropdown_button = /** @class */ (function (_super) {
};
return et2_dropdown_button;
}(et2_core_inputWidget_1.et2_inputWidget));
+exports.et2_dropdown_button = et2_dropdown_button;
et2_core_widget_1.et2_register_widget(et2_dropdown_button, ["dropdown_button"]);
//# sourceMappingURL=et2_widget_dropdown_button.js.map
\ No newline at end of file
diff --git a/api/js/etemplate/et2_widget_dropdown_button.ts b/api/js/etemplate/et2_widget_dropdown_button.ts
index c21efdaa89..150851f387 100644
--- a/api/js/etemplate/et2_widget_dropdown_button.ts
+++ b/api/js/etemplate/et2_widget_dropdown_button.ts
@@ -34,7 +34,7 @@ import {ClassWithAttributes} from "./et2_core_inheritance";
*
* @augments et2_inputWidget
*/
-class et2_dropdown_button extends et2_inputWidget
+export class et2_dropdown_button extends et2_inputWidget
{
static readonly attributes : any = {
"label": {
diff --git a/api/js/etemplate/et2_widget_favorites.js b/api/js/etemplate/et2_widget_favorites.js
index c455e1d847..962fbfe0a3 100644
--- a/api/js/etemplate/et2_widget_favorites.js
+++ b/api/js/etemplate/et2_widget_favorites.js
@@ -1,3 +1,4 @@
+"use strict";
/**
* EGroupware eTemplate2 - JS Favorite widget
*
@@ -7,14 +8,28 @@
* @link http://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
- * @version $Id$
*/
-
+var __extends = (this && this.__extends) || (function () {
+ var extendStatics = function (d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+ };
+ return function (d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+ };
+})();
+Object.defineProperty(exports, "__esModule", { value: true });
/*egw:uses
- et2_dropdown_button;
- et2_extension_nextmatch;
+ et2_dropdown_button;
+ et2_extension_nextmatch;
*/
-
+var et2_core_widget_1 = require("./et2_core_widget");
+var et2_widget_dropdown_button_1 = require("./et2_widget_dropdown_button");
+var et2_core_inheritance_1 = require("./et2_core_inheritance");
/**
* Favorites widget, designed for use with a nextmatch widget
*
@@ -42,369 +57,287 @@
*
* @augments et2_dropdown_button
*/
-var et2_favorites = (function(){ "use strict"; return 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": this.egw().image('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 = jQuery("#"+this.options.sidebox_target);
- if(this.sidebox_target.length == 0 && egw_getFramework() != null)
- {
- var egw_fw = egw_getFramework();
- this.sidebox_target = jQuery("#"+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
- jQuery(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,jQuery(this).val());
- self.preferred = jQuery(this).val();
-
- // Update sidebox, if there
- if(self.sidebox_target.length)
- {
- self.sidebox_target.find("div.ui-icon-heart")
- .replaceWith("
");
- jQuery("li[data-id='"+self.preferred+"'] div.sideboxstar",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);
- });
- });
-
- //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
- jQuery(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() {jQuery(this).wrap("");})
- .on("mouseleave","div.ui-icon-trash", function() {jQuery(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] = ""+
- (filters[name].name != undefined ? filters[name].name : name) +
- (filters[name].group != false && !this.is_admin || name == 'blank' ? "" :
- "");
- }
-
- // Only add 'Add current' if we have a nextmatch
- if(this.nextmatch)
- {
- options.add = "
"+this.egw().lang('Add current');
- }
- widget.set_select_options.call(widget,options);
-
- // Set radio to current value
- jQuery("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 = jQuery(selected_node).attr("data-id");
- if(this.value == "add" && this.nextmatch)
- {
- // Get current filters
- var current_filters = jQuery.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);
- }
- else if (this.value == 'blank')
- {
- // Reset filters when select no filters
- this.set_nm_filters({});
- }
- },
-
- 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);
- }
-});}).call(this);
-et2_register_widget(et2_favorites, ["favorites"]);
+var et2_favorites = /** @class */ (function (_super) {
+ __extends(et2_favorites, _super);
+ /**
+ * Constructor
+ *
+ * @memberOf et2_favorites
+ */
+ function et2_favorites(_parent, _attrs, _child) {
+ var _this = _super.call(this, _parent, _attrs, et2_core_inheritance_1.ClassWithAttributes.extendAttributes(et2_favorites._attributes, _child || {})) || this;
+ // Some convenient variables, used in closures / event handlers
+ _this.header = null;
+ _this.nextmatch = null;
+ _this.favSortedList = null;
+ _this.sidebox_target = null;
+ // If filter was set server side, we need to remember it until nm is created
+ _this.nm_filter = false;
+ _this.sidebox_target = jQuery("#" + _this.options.sidebox_target);
+ if (_this.sidebox_target.length == 0 && egw_getFramework() != null) {
+ var egw_fw = egw_getFramework();
+ _this.sidebox_target = jQuery("#" + _this.options.sidebox_target, egw_fw.sidemenuDiv);
+ }
+ // Store array of sorted items
+ _this.favSortedList = ['blank'];
+ var apps = egw().user('apps');
+ et2_favorites.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
+ jQuery(_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, jQuery(this).val());
+ self.preferred = jQuery(this).val();
+ // Update sidebox, if there
+ if (self.sidebox_target.length) {
+ self.sidebox_target.find("div.ui-icon-heart")
+ .replaceWith("");
+ jQuery("li[data-id='" + self.preferred + "'] div.sideboxstar", 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);
+ });
+ });
+ //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
+ jQuery(_this.menu).sortable({
+ items: 'li:not([data-id$="add"])',
+ placeholder: 'ui-fav-sortable-placeholder',
+ delay: 250,
+ update: function () {
+ 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 () { jQuery(this).wrap(""); })
+ .on("mouseleave", "div.ui-icon-trash", function () { jQuery(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);
+ }
+ return _this;
+ }
+ /**
+ * Load favorites from preferences
+ *
+ * @param app String Load favorites from this application
+ */
+ et2_favorites.prototype.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_1 = pref_name.substr(this.favorite_prefix.length);
+ stored_filters[name_1] = 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_2 in stored_filters) {
+ if (this.favSortedList.indexOf(name_2) < 0) {
+ this.favSortedList.push(name_2);
+ }
+ }
+ 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
+ et2_favorites.prototype.init_filters = function (widget, filters) {
+ if (typeof filters == "undefined") {
+ filters = this.stored_filters;
+ }
+ var options = {};
+ for (var name_3 in filters) {
+ options[name_3] = "" +
+ (filters[name_3].name != undefined ? filters[name_3].name : name_3) +
+ (filters[name_3].group != false && !et2_favorites.is_admin || name_3 == 'blank' ? "" :
+ "");
+ }
+ // Only add 'Add current' if we have a nextmatch
+ if (this.nextmatch) {
+ options["add"] = "
" + this.egw().lang('Add current');
+ }
+ widget.set_select_options.call(widget, options);
+ // Set radio to current value
+ jQuery("input[value='" + this.preferred + "']:radio", this.menu).attr("checked", 1);
+ };
+ et2_favorites.prototype.set_nm_filters = function (filters) {
+ if (this.nextmatch) {
+ this.nextmatch.applyFilters(filters);
+ }
+ else {
+ console.log(filters);
+ }
+ };
+ et2_favorites.prototype.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
+ et2_favorites.prototype.change = function (selected_node) {
+ this.value = jQuery(selected_node).attr("data-id");
+ if (this.value == "add" && this.nextmatch) {
+ // Get current filters
+ var current_filters = jQuery.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);
+ }
+ else if (this.value == 'blank') {
+ // Reset filters when select no filters
+ this.set_nm_filters({});
+ }
+ };
+ et2_favorites.prototype.set_value = function (filter_name, parent) {
+ if (parent) {
+ return _super.prototype.set_value.call(this, filter_name);
+ }
+ if (filter_name == 'add')
+ return false;
+ app[this.options.app].setState(this.stored_filters[filter_name]);
+ return false;
+ };
+ et2_favorites.prototype.getValue = function () {
+ return null;
+ };
+ /**
+ * Set the nextmatch to filter
+ * From et2_INextmatchHeader interface
+ *
+ * @param {et2_nextmatch} nextmatch
+ */
+ et2_favorites.prototype.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_favorites._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": egw().image('fav_filter') },
+ statustext: { "default": "Favorite queries", "type": "string" }
+ };
+ return et2_favorites;
+}(et2_widget_dropdown_button_1.et2_dropdown_button));
+et2_core_widget_1.et2_register_widget(et2_favorites, ["favorites"]);
+//# sourceMappingURL=et2_widget_favorites.js.map
\ No newline at end of file
diff --git a/api/js/etemplate/et2_widget_favorites.ts b/api/js/etemplate/et2_widget_favorites.ts
new file mode 100644
index 0000000000..0144e52f7f
--- /dev/null
+++ b/api/js/etemplate/et2_widget_favorites.ts
@@ -0,0 +1,425 @@
+/**
+ * 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
+ */
+
+/*egw:uses
+ et2_dropdown_button;
+ et2_extension_nextmatch;
+*/
+
+import {et2_register_widget, WidgetConfig} from "./et2_core_widget";
+import {et2_INextmatchHeader} from "./et2_extension_nextmatch";
+import {et2_dropdown_button} from "./et2_widget_dropdown_button";
+import {ClassWithAttributes} from "./et2_core_inheritance";
+
+/**
+ * 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
+ */
+class et2_favorites extends et2_dropdown_button implements et2_INextmatchHeader
+{
+ static readonly _attributes : any = {
+ "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": egw().image('fav_filter')},
+ statustext: {"default": "Favorite queries", "type": "string"}
+ };
+
+ // Some convenient variables, used in closures / event handlers
+ header = null;
+ nextmatch = null;
+ favorite_prefix: "favorite_";
+ private stored_filters: {};
+ private favSortedList : any = null;
+ private sidebox_target : JQuery = null;
+ private preferred;
+ static is_admin : boolean;
+ private filters : any;
+
+ // If filter was set server side, we need to remember it until nm is created
+ nm_filter = false;
+
+ /**
+ * Constructor
+ *
+ * @memberOf et2_favorites
+ */
+ constructor(_parent?, _attrs? : WidgetConfig, _child? : object)
+ {
+ super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_favorites._attributes, _child || {}));
+ this.sidebox_target = jQuery("#"+this.options.sidebox_target);
+ if(this.sidebox_target.length == 0 && egw_getFramework() != null)
+ {
+ let egw_fw = egw_getFramework();
+ this.sidebox_target = jQuery("#"+this.options.sidebox_target,egw_fw.sidemenuDiv);
+ }
+ // Store array of sorted items
+ this.favSortedList = ['blank'];
+
+ let apps = egw().user('apps');
+ et2_favorites.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);
+
+ let self = this;
+
+ // Add a listener on the radio buttons to set default filter
+ jQuery(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,jQuery(this).val());
+ self.preferred = jQuery(this).val();
+
+ // Update sidebox, if there
+ if(self.sidebox_target.length)
+ {
+ self.sidebox_target.find("div.ui-icon-heart")
+ .replaceWith("");
+ jQuery("li[data-id='"+self.preferred+"'] div.sideboxstar",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);
+ });
+ });
+
+ //Sort DomNodes of sidebox fav. menu
+ let sideBoxDOMNodeSort = function (_favSList) {
+ let favS = jQuery.isArray(_favSList)?_favSList.slice(0).reverse():[];
+
+ for (let 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
+ jQuery(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 ()
+ {
+ 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() {jQuery(this).wrap("");})
+ .on("mouseleave","div.ui-icon-trash", function() {jQuery(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(app)
+ {
+
+ // Default blank filter
+ let stored_filters : any = {
+ 'blank': {
+ name: this.egw().lang("No filters"),
+ state: {}
+ }
+ };
+
+ // Load saved favorites
+ let preferences : any = egw.preference("*",app);
+ for(let pref_name in preferences)
+ {
+ if(pref_name.indexOf(this.favorite_prefix) == 0 && typeof preferences[pref_name] == 'object')
+ {
+ let 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(let 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)
+ {
+ let sortedListObj = {};
+
+ for (let 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(widget, filters?)
+ {
+ if(typeof filters == "undefined")
+ {
+ filters = this.stored_filters;
+ }
+
+ let options = {};
+ for(let name in filters)
+ {
+ options[name] = ""+
+ (filters[name].name != undefined ? filters[name].name : name) +
+ (filters[name].group != false && !et2_favorites.is_admin || name == 'blank' ? "" :
+ "");
+ }
+
+ // Only add 'Add current' if we have a nextmatch
+ if(this.nextmatch)
+ {
+ options["add"] = "
"+this.egw().lang('Add current');
+ }
+ widget.set_select_options.call(widget,options);
+
+ // Set radio to current value
+ jQuery("input[value='"+ this.preferred +"']:radio", this.menu).attr("checked",1);
+ }
+
+ set_nm_filters(filters)
+ {
+ if(this.nextmatch)
+ {
+ this.nextmatch.applyFilters(filters);
+ }
+ else
+ {
+ console.log(filters);
+ }
+ }
+
+ onclick(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(selected_node)
+ {
+ this.value = jQuery(selected_node).attr("data-id");
+ if(this.value == "add" && this.nextmatch)
+ {
+ // Get current filters
+ let current_filters = jQuery.extend({},this.nextmatch.activeFilters);
+
+ // Add in extras
+ for(let 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(let 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);
+ }
+ else if (this.value == 'blank')
+ {
+ // Reset filters when select no filters
+ this.set_nm_filters({});
+ }
+ }
+
+ set_value(filter_name, parent? : boolean) : void | boolean
+ {
+ if(parent)
+ {
+ return super.set_value(filter_name);
+ }
+
+ if(filter_name == 'add') return false;
+
+ app[this.options.app].setState(this.stored_filters[filter_name]);
+ return false;
+ }
+
+ getValue()
+ {
+ return null;
+ }
+
+ /**
+ * Set the nextmatch to filter
+ * From et2_INextmatchHeader interface
+ *
+ * @param {et2_nextmatch} nextmatch
+ */
+ setNextmatch(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"]);
+