From ce418affe841f8e178ef76bd21165a2757d58d7a Mon Sep 17 00:00:00 2001 From: ralf Date: Tue, 12 Jul 2022 09:57:24 +0200 Subject: [PATCH] drop old select/taglist/link incl. Choosen and MagicSuggest --- api/js/etemplate/et2_core_arrayMgr.ts | 5 + api/js/etemplate/et2_widget_link.ts | 1457 +------------- api/js/etemplate/et2_widget_selectAccount.ts | 734 +------ api/js/etemplate/et2_widget_selectbox.ts | 1851 +----------------- api/js/etemplate/et2_widget_taglist.ts | 1537 +-------------- api/js/jquery/chosen/LICENSE.md | 24 - api/js/jquery/chosen/README.md | 50 - api/js/jquery/chosen/chosen-sprite.png | Bin 646 -> 0 bytes api/js/jquery/chosen/chosen-sprite@2x.png | Bin 872 -> 0 bytes api/js/jquery/chosen/chosen.css | 464 ----- api/js/jquery/chosen/chosen.jquery.js | 1214 ------------ api/js/jquery/chosen/example.jquery.html | 1333 ------------- api/js/jquery/chosen/package.json | 18 - composer.json | 3 +- composer.lock | 145 +- 15 files changed, 44 insertions(+), 8791 deletions(-) delete mode 100644 api/js/jquery/chosen/LICENSE.md delete mode 100644 api/js/jquery/chosen/README.md delete mode 100644 api/js/jquery/chosen/chosen-sprite.png delete mode 100755 api/js/jquery/chosen/chosen-sprite@2x.png delete mode 100644 api/js/jquery/chosen/chosen.css delete mode 100644 api/js/jquery/chosen/chosen.jquery.js delete mode 100644 api/js/jquery/chosen/example.jquery.html delete mode 100644 api/js/jquery/chosen/package.json diff --git a/api/js/etemplate/et2_core_arrayMgr.ts b/api/js/etemplate/et2_core_arrayMgr.ts index a01a1ecd47..46a9c22236 100644 --- a/api/js/etemplate/et2_core_arrayMgr.ts +++ b/api/js/etemplate/et2_core_arrayMgr.ts @@ -351,6 +351,11 @@ export class et2_arrayMgr parseBoolExpression(_expression : string) { + if (typeof _expression === "undefined" || _expression === null) + { + return false; + } + // Check whether "$" occurs in the given identifier, don't parse rows if we're not in a row // This saves booleans in repeating rows from being parsed too early - we'll parse again when repeating if(_expression.indexOf('$') >= 0 && this.perspectiveData.row == null && _expression.match(/\$\{?row\}?/)) diff --git a/api/js/etemplate/et2_widget_link.ts b/api/js/etemplate/et2_widget_link.ts index 9c72485e28..bab61ef37e 100644 --- a/api/js/etemplate/et2_widget_link.ts +++ b/api/js/etemplate/et2_widget_link.ts @@ -22,1231 +22,29 @@ import {et2_createWidget, et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; import {ClassWithAttributes} from "./et2_core_inheritance"; -import {et2_valueWidget} from "./et2_core_valueWidget"; import {et2_inputWidget} from "./et2_core_inputWidget"; -import {et2_selectbox} from "./et2_widget_selectbox"; import {et2_button} from "./et2_widget_button"; -import {et2_file} from "./et2_widget_file"; -import {et2_vfsSelect} from "./et2_widget_vfs"; -import {egw} from "../jsapi/egw_global"; -import {et2_tabbox} from "./et2_widget_tabs"; -import {et2_csvSplit, et2_no_init} from "./et2_core_common"; -import {et2_IDetachedDOM} from "./et2_core_interfaces"; -import {et2_DOMWidget} from "./et2_core_DOMWidget"; import {Et2LinkList} from "./Et2Link/Et2LinkList"; import type {Et2LinkString} from "./Et2Link/Et2LinkString"; import {Et2Link} from "./Et2Link/Et2Link"; +import type {Et2LinkTo} from "./Et2Link/Et2LinkTo"; +import type {Et2LinkAppSelect} from "./Et2Link/Et2LinkAppSelect"; +import type {Et2LinkEntry, Et2LinkEntryReadonly} from "./Et2Link/Et2LinkEntry"; /** - * UI widgets for Egroupware linking system + * @deprecated use Et2LinkTo */ -export class et2_link_to extends et2_inputWidget -{ - static readonly _attributes: any = { - "only_app": { - "name": "Application", - "type": "string", - "default": "", - "description": "Limit to just this one application - hides app selection" - }, - "application_list": { - "name": "Application list", - "type": "any", - "default": "", - "description": "Limit to the listed application or applications (comma seperated)" - }, - "blur": { - "name": "Placeholder", - "type": "string", - "default": "", - "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text.", - translate: true - }, - "no_files": { - "name": "No files", - "type": "boolean", - "default": false, - "description": "Suppress attach-files" - }, - "search_label": { - "name": "Search label", - "type": "string", - "default": "", - "description": "Label to use for search" - }, - "link_label": { - "name": "Link label", - "type": "string", - "default": "Link", - "description": "Label for the link button" - }, - "value": { - // Could be string or int if application is provided, or an Object - "type": "any" - } - }; - - private div: JQuery; - private link_button: JQuery; - private link_div: JQuery; - private filemanager_button: JQuery; - private file_div: JQuery; - private status_span: JQuery; - private link_entry: et2_link_entry; - private vfs_select: et2_vfsSelect; - private file_upload: et2_file; - - - /** - * Constructor - * - * @memberOf et2_link_to - */ - constructor(_parent: et2_widget, _attrs?: WidgetConfig, _child?: object) - { - super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_to._attributes, _child || {})); - - this.div = jQuery(document.createElement("div")).addClass("et2_link_to et2_toolbar"); - - this.link_button = null; - this.status_span = null; - - this.link_entry = null; - this.file_upload = null; - - this.createInputWidget(); - } - - destroy() - { - this.link_button = null; - this.status_span = null; - - if (this.link_entry) - { - this.link_entry.destroy(); - this.link_entry = null; - } - if (this.file_upload) - { - this.file_upload.destroy(); - this.file_upload = null; - } - this.div = null; - - super.destroy.apply(this, arguments); - } - - /** - * Override to provide proper node for sub widgets to go in - * - * @param {Object} _sender - */ - getDOMNode(_sender) - { - if (_sender == this) - { - return this.div[0]; - } - else if (_sender._type == 'link-entry') - { - return this.link_div[0]; - } - else if (_sender._type == 'file') - { - return this.file_div[0]; - } - else if (_sender._type == 'vfs-select') - { - return this.filemanager_button[0]; - } - } - - createInputWidget() - { - - // Need a div for file upload widget - this.file_div = jQuery(document.createElement("div")).css({display: 'inline-block'}).appendTo(this.div); - - // Filemanager link popup - this.filemanager_button = jQuery(document.createElement("div")).css({display: 'inline-block'}).appendTo(this.div); - - // Need a div for link-to widget - this.link_div = jQuery(document.createElement("div")) - .addClass('div_link') - // Leave room for link button - .appendTo(this.div); - - if (!this.options.readonly) - { - // One common link button - this.link_button = jQuery(document.createElement("button")) - .text(this.egw().lang(this.options.link_label)) - .appendTo(this.div).hide() - .addClass('link') - .click(this, this.createLink); - - // Span for indicating status - this.status_span = jQuery(document.createElement("span")) - .appendTo(this.div).addClass("status").hide(); - } - - this.setDOMNode(this.div[0]); - } - - doLoadingFinished() - { - super.doLoadingFinished.apply(this, arguments); - - var self = this; - if (this.link_entry && this.vfs_select && this.file_upload) - { - // Already done - return false; - } - - // Link-to - var link_entry_attrs = { - id: this.id + '_link_entry', - only_app: this.options.only_app, - application_list: this.options.application_list, - blur: this.options.search_label ? this.options.search_label : this.egw().lang('Search...'), - query: function () - { - self.link_button.hide(); - return true; - }, - select: function () - { - self.link_button.show(); - return true; - }, - readonly: this.options.readonly - }; - this.link_entry = et2_createWidget("link-entry", link_entry_attrs, this); - - // Filemanager select - var select_attrs: any = { - button_label: egw.lang('Link'), - button_caption: '', - button_icon: 'link', - readonly: this.options.readonly, - dialog_title: egw.lang('Link'), - extra_buttons: [{text: egw.lang("copy"), id: "copy", image: "copy"}, - {text: egw.lang("move"), id: "move", image: "move"}], - onchange: function () - { - var values = true; - // If entry not yet saved, store for linking on server - if (!self.options.value.to_id || typeof self.options.value.to_id == 'object') - { - values = self.options.value.to_id || {}; - var files = self.vfs_select.getValue(); - if (typeof files !== 'undefined') - { - for (var i = 0; i < files.length; i++) - { - values['link:' + files[i]] = { - app: 'link', - id: files[i], - type: 'unknown', - icon: 'link', - remark: '', - title: files[i] - }; - } - } - } - self._link_result(values); - } - }; - // only set server-side callback, if we have a real application-id (not null or array) - // otherwise it only gives an error on server-side - if (self.options.value && self.options.value.to_id && typeof self.options.value.to_id != 'object') - { - select_attrs.method = 'EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_existing'; - select_attrs.method_id = self.options.value.to_app + ':' + self.options.value.to_id; - } - this.vfs_select = et2_createWidget("vfs-select", select_attrs, this); - this.vfs_select.set_readonly(this.options.readonly); - - // File upload - var file_attrs = { - multiple: true, - id: this.id + '_file', - label: '', - // Make the whole template a drop target - drop_target: this.getInstanceManager().DOMContainer.getAttribute("id"), - readonly: this.options.readonly, - - // Change to this tab when they drop - onStart: function (event, file_count) - { - // Find the tab widget, if there is one - var tabs: et2_widget = self; - do - { - tabs = tabs.getParent(); - } - while (tabs != self.getRoot() && tabs.getType() != 'tabbox'); - if (tabs != self.getRoot()) - { - (tabs).activateTab(self); - } - return true; - }, - onFinish: function (event, file_count) - { - event.data = self; - self.filesUploaded(event); - - // Auto-link uploaded files - self.createLink(event); - } - }; - - this.file_upload = et2_createWidget("file", file_attrs, this); - this.file_upload.set_readonly(this.options.readonly); - return true; - } - - getValue() - { - return this.options.value; - } - - filesUploaded(event) - { - var self = this; - - this.link_button.show(); - } - - /** - * Create a link using the current internal values - * - * @param {Object} event - */ - createLink(event) - { - // Disable link button - event.data.link_button.attr("disabled", true); - - var values = event.data.options.value; - var self = event.data; - - var links = []; - - // Links to other entries - event.data = self.link_entry; - self.link_entry.createLink(event, links); - - // Files - if (!self.options.no_files) - { - for (var file in self.file_upload.options.value) - { - - links.push({ - app: 'file', - id: file, - name: self.file_upload.options.value[file].name, - type: self.file_upload.options.value[file].type, - remark: jQuery("li[file='" + self.file_upload.options.value[file].name.replace(/'/g, '"') + "'] > input", self.file_upload.progress) - .filter(function () - { - return jQuery(this).attr("placeholder") != jQuery(this).val(); - }).val() - }); - } - } - if (links.length == 0) - { - return; - } - - var request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", - [values.to_app, values.to_id, links], - self._link_result, - self, - true, - self - ); - request.sendRequest(); - } - - /** - * Sent some links, server has a result - * - * @param {Object} success - */ - _link_result(success) - { - if (success) - { - this.link_button.hide().attr("disabled", false); - this.status_span.removeClass("error").addClass("success"); - this.status_span.fadeIn().delay(1000).fadeOut(); - delete this.options.value.app; - delete this.options.value.id; - for (var file in this.file_upload.options.value) - { - delete this.file_upload.options.value[file]; - } - this.file_upload.progress.empty(); - - // Server says it's OK, but didn't store - we'll send this again on submit - // This happens if you link to something before it's saved to the DB - if (typeof success == "object") - { - // Save as appropriate in value - if (typeof this.options.value != "object") - { - this.options.value = {}; - } - this.options.value.to_id = success; - for (let link in success) - { - // Icon should be in registry - if (typeof success[link].icon == 'undefined') - { - success[link].icon = egw.link_get_registry(success[link].app, 'icon'); - // No icon, try by mime type - different place for un-saved entries - if (success[link].icon == false && success[link].id.type) - { - // Triggers icon by mime type, not thumbnail or app - success[link].type = success[link].id.type; - success[link].icon = true; - } - } - // Special handling for file - if not existing, we can't ask for title - if (success[link].app == 'file' && typeof success[link].title == 'undefined') - { - success[link].title = success[link].id.name || ''; - } - } - } - - // Look for a link-list with the same ID, refresh it - var self = this; - var list_widget = null; - this.getRoot().iterateOver( - function (widget) - { - if (widget.id == self.id) - { - list_widget = widget; - if (success === true) - { - widget._get_links(); - } - } - }, - this, et2_link_list - ); - - // If there's an array of data (entry is not yet saved), updating the list will - // not work, so add them in explicitly. - if (list_widget && success) - { - // Clear list - list_widget.set_value(null); - - // Add temp links in - for (var link_id in success) - { - let link = success[link_id]; - if (typeof link.title == 'undefined') - { - // Callback to server for title - egw.link_title(link.app, link.id, true).then(title => - { - link.title = title; - list_widget._add_link(link); - }); - } - else - { - // Add direct - list_widget._add_link(link); - } - } - } - // Update any neighbouring link lists - ((this.getParent()).getDOMNode().querySelector('et2-link-list'))?.get_links(Object.values(success)); - } - else - { - this.status_span.removeClass("success").addClass("error") - .fadeIn(); - } - this.div.trigger('link.et2_link_to', success); - } - - set_no_files(no_files) - { - if (this.options.readonly) return; - if (no_files) - { - this.file_div.hide(); - this.filemanager_button.hide(); - } - else - { - this.file_div.show(); - this.filemanager_button.show(); - } - this.options.no_files = no_files; - } -} - -et2_register_widget(et2_link_to, ["link-to"]); +export type et2_link_to = Et2LinkTo; /** - * List of applications that support link + * @deprecated use Et2LinkAppSelect */ -export class et2_link_apps extends et2_selectbox -{ - static readonly _attributes: any = { - "only_app": { - "name": "Application", - "type": "string", - "default": "", - "description": "Limit to just this one application - hides app selection" - }, - "application_list": { - "name": "Application list", - "type": "any", - "default": "", - "description": "Limit to the listed application or applications (comma seperated)" - } - }; - - /** - * Constructor - * - */ - constructor(_parent: et2_widget, _attrs?: WidgetConfig, _child?: object) - { - super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_apps._attributes, _child || {})); - - - if (this.options.select_options != null) - { - // Preset to last application - if (!this.options.value) - { - this.set_value(egw.preference('link_app', this.egw().getAppName())); - } - // Register to update preference - var self = this; - this.input.bind("click", function () - { - if (typeof self.options.value != 'undefined') var appname = self.options.value.to_app; - egw.set_preference(appname || self.egw().getAppName(), 'link_app', self.getValue()); - }); - } - } - - /** - * We get some minor speedups by overriding parent searching and directly setting select options - * - * @param {Array} _attrs an array of attributes - */ - transformAttributes(_attrs) - { - var select_options = {}; - - // Limit to one app - if (_attrs.only_app) - { - select_options[_attrs.only_app] = this.egw().lang(_attrs.only_app); - } - else if (_attrs.application_list) - { - select_options = _attrs.application_list; - } - else - { - select_options = egw.link_app_list('query'); - if (typeof select_options['addressbook-email'] !== 'undefined') - { - delete select_options['addressbook-email']; - } - } - _attrs.select_options = select_options; - super.transformAttributes(_attrs); - } -} - -et2_register_widget(et2_link_apps, ["link-apps"]); +export type et2_link_apps = Et2LinkAppSelect; /** - * Search and select an entry for linking + * @deprecated use Et2LinkEntry */ -export class et2_link_entry extends et2_inputWidget -{ - static readonly _attributes: any = { - "value": { - "type": "any", - "default": {} - }, - "only_app": { - "name": "Application", - "type": "string", - "default": "", - "description": "Limit to just this one application - hides app selection" - }, - "application_list": { - "name": "Application list", - "type": "any", - "default": "", - "description": "Limit to the listed applications (comma seperated)" - }, - "app_icons": { - "name": "Application icons", - "type": "boolean", - "default": false, - "description": "Show application icons instead of names" - }, - "blur": { - "name": "Placeholder", - "type": "string", - "default": et2_no_init, - "description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text.", - translate: true - }, - "query": { - "name": "Query callback", - "type": "js", - "default": et2_no_init, - "description": "Callback before query to server. It will be passed the request & et2_link_entry objects. Must return true, or false to abort query." - }, - "select": { - "name": "Select callback", - "type": "js", - "default": et2_no_init, - "description": "Callback when user selects an option. Must return true, or false to abort normal action." - } - }; - - public static readonly legacyOptions = ["only_app", "application_list"]; - protected static readonly search_timeout = 500; //ms after change to send query - protected static readonly minimum_characters = 4; // Don't send query unless there's at least this many chars - - private cache: any = {}; - - protected div: JQuery; - protected app_select: JQuery; - protected search: JQuery; - protected clear: JQuery; - protected link_button: JQuery; - private request: any; - private last_search: string; - processing: boolean = false; - - /** - * Constructor - * - * @memberOf et2_link_entry - */ - constructor(_parent: et2_widget, _attrs?: WidgetConfig, _child?: object) - { - super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_entry._attributes, _child || {})); - - this.search = null; - this.clear = null; - this.app_select = null; - this._oldValue = { - id: null, - app: this.options.value && this.options.value.app ? this.options.value.app : this.options.only_app - }; - - if (typeof this.options.value == 'undefined' || this.options.value == null) - { - this.options.value = {}; - } - this.cache = {}; - this.request = null; - - this.createInputWidget(); - var self = this; - - jQuery(this.getInstanceManager().DOMContainer).on('clear', function () - { - // We need to unbind events to prevent a second triggerd event handler - // (eg. setting a project in infolog edit dialog) when the widget gets cleared. - jQuery(self.getDOMNode()).off(); - }); - } - - destroy() - { - super.destroy.apply(this, arguments); - - this.div = null; - if (this.search.data("ui-autocomplete")) - { - this.search.autocomplete("destroy"); - } - this.search = null; - this.clear = null; - this.app_select = null; - this.request = null; - } - - createInputWidget() - { - var self = this; - this.div = jQuery(document.createElement("div")).addClass("et2_link_entry"); - - this.app_select = jQuery(document.createElement("et2-link-apps")).appendTo(this.div) - .on("change", function(e) - { - // Clear cache when app changes - self.cache = {}; - - // Update preference with new value - egw.set_preference(self.options.value.to_app || self.egw().getAppName(), 'link_app', self.app_select.val()); - - if(typeof self.options.value != 'object') - { - self.options.value = {}; - } - self.options.value.app = self.app_select.val(); - }) - .attr("aria-label", egw.lang("linkapps")); - if (this.options.only_app) - { - this.app_select.val(this.options.only_app); - this.app_select.hide(); - this.div.addClass("no_app"); - } - else - { - // Now that options are in, set to last used app - this.app_select.val(this.options.value.app || ''); - } - - // Search input - this.search = jQuery(document.createElement("input")) - // .attr("type", "search") // Fake it for all browsers below - .focus(function () - { - if (!self.options.only_app) - { - // Adjust width, leave room for app select & link button - self.div.removeClass("no_app"); - self.app_select.show(); - } - }) - .blur(function (e) - { - if (self.div.has(e.relatedTarget).length) return; - if (self.options.app_icons) - { - // Adjust width, leave room for app select & link button - self.div.addClass("no_app"); - } - else if (self.search.val()) - { - if (self.options.only_app) - { - // Adjust width, leave room for app select & link button - self.div.addClass("no_app"); - } - } - }) - .attr("aria-label", egw.lang("Link search")) - .attr("role", "searchbox") - .appendTo(this.div); - - this.set_blur(this.options.blur ? this.options.blur : this.egw().lang("search"), this.search); - - // Autocomplete - /* - this.search.autocomplete({ - source: function (request, response) - { - return self.query(request, response); - }, - select: function (event, item) - { - event.data = self; - // Correct changed value from server - if (item.item.value) - { - item.item.value = ("" + item.item.value).trim(); - } - self.select(event, item); - return false; - }, - focus: function (event, item) - { - event.stopPropagation(); - self.search.val(item.item.label); - return false; - }, - minLength: et2_link_entry.minimum_characters, - delay: et2_link_entry.search_timeout, - disabled: self.options.disabled, - appendTo: self.div - }); -*/ - // Custom display (colors) - /* - this.search.data("uiAutocomplete")._renderItem = function (ul, item) - { - var li = jQuery(document.createElement('li')) - .data("item.autocomplete", item); - var extra: any = {}; - - // Extra stuff - if (typeof item.label == 'object') - { - extra = item.label; - item.label = extra.label ? extra.label : extra; - if (extra['style.backgroundColor'] || extra.color) - { - li.css({'border-left': '5px solid ' + (extra.color ? extra.color : extra['style.backgroundColor'])}); - } - // Careful with this, some browsers may have trouble loading all at once, which can slow display - if (extra.icon) - { - var img = self.egw().image(extra.icon); - if (img) - { - jQuery(document.createElement("img")) - .attr("src", img) - .css("float", "right") - .appendTo(li); - } - } - } - - // Normal stuff - li.append(jQuery("").text(item.label)) - .appendTo(ul); - window.setTimeout(function () - { - ul.css('max-width', jQuery('.et2_container').width() - ul.offset().left); - }, 300); - return li; - }; - - */ - - // Bind to enter key to start search early - this.search.keydown(function(e) - { - var keycode = (e.keyCode ? e.keyCode : e.which); - if(keycode == 13 && !self.processing) - { - self.search.autocomplete("option", "minLength", 0); - self.search.autocomplete("search"); - self.search.autocomplete("option", "minLength", self.minimum_characters); - return false; - } - }); - - // Clear / last button - this.clear = jQuery(document.createElement("span")) - .addClass("ui-icon ui-icon-close") - .click(function (e) - { - if (!self.search) return; // only gives an error, we should never get into that situation - // No way to tell if the results is open, so if they click the button while open, it clears - if (self.last_search && self.last_search != self.search.val()) - { - // Repeat last search (should be cached) - self.search.val(self.last_search); - self.last_search = ""; - self.search.autocomplete("search"); - } - else - { - // Clear - self.search.autocomplete("close"); - self.set_value(null); - self.search.val(""); - // call trigger, after finishing this handler, not in the middle of it - window.setTimeout(function () - { - self.search.trigger("change"); - }, 0); - } - self.search.focus(); - }) - .appendTo(this.div) - .hide(); - - this.setDOMNode(this.div[0]); - } - - getDOMNode() - { - return this.div ? this.div[0] : null; - } - - transformAttributes(_attrs) - { - super.transformAttributes.apply(this, arguments); - - - _attrs["select_options"] = {}; - if (_attrs["application_list"]) - { - var apps = (typeof _attrs["application_list"] == "string") ? et2_csvSplit(_attrs["application_list"], null, ",") : _attrs["application_list"]; - for (var i = 0; i < apps.length; i++) - { - _attrs["select_options"][apps[i]] = this.egw().lang(apps[i]); - } - } - else - { - _attrs["select_options"] = this.egw().link_app_list('query'); - if (typeof _attrs["select_options"]["addressbook-email"] != 'undefined') delete _attrs["select_options"]["addressbook-email"]; - } - - // Check whether the options entry was found, if not read it from the - // content array. - if (_attrs["select_options"] == null) - { - _attrs["select_options"] = this.getArrayMgr('content') - .getEntry("options-" + this.id); - } - - // Default to an empty object - if (_attrs["select_options"] == null) - { - _attrs["select_options"] = {}; - } - } - - doLoadingFinished() - { - if (typeof this.options.value == 'object' && !this.options.value.app) - { - this.options.value.app = egw.preference('link_app', this.options.value.to_app || this.egw().getAppName()); - // If there's no value set for app, then take the first one from the selectbox - if (typeof this.options.value.app == 'undefined' || !this.options.value.app) - { - this.options.value.app = Object.keys(this.options.select_options)[0]; - } - this.app_select.val(this.options.value.app); - } - - return super.doLoadingFinished.apply(this, arguments); - } - - getValue() - { - var value = this.options && this.options.only_app ? this.options.value.id : this.options ? this.options.value : null; - if (this.options && !this.options.only_app && this.search) - { - value.search = this.search.val(); - } - return value; - } - - set_value(_value) - { - if (typeof _value == 'string' || typeof _value == 'number') - { - if (typeof _value == 'string' && _value.indexOf(",") > 0) _value = _value.replace(",", ":"); - if (typeof _value == 'string' && _value.indexOf(":") >= 0) - { - var split = _value.split(":"); - - _value = { - app: split.shift(), - id: split.length == 1 ? split[0] : split - }; - } - else if (_value && this.options.only_app) - { - _value = { - app: this.options.only_app, - id: _value - }; - } - } - // display a search query, not a selected entry - else if (_value !== null && typeof _value === 'object' && typeof _value.query === 'string') - { - this.options.value = { app: _value.app || this.options.only_app, id: null }; - this.search.val(_value.query); - return; - } - this._oldValue = this.options.value; - if (!_value || _value.length == 0 || _value == null || jQuery.isEmptyObject(_value)) - { - this.search.val(""); - this.clear.hide(); - this.options.value = _value = {'id': null}; - } - if (!_value.app) _value.app = this.options.only_app || this.app_select.val(); - - if (_value.id) - { - // Remove specific display and revert to CSS file - // show() would use inline, should be inline-block - this.clear.css('display', ''); - } - else - { - this.clear.hide(); - return; - } - if (typeof _value != 'object' || (!_value.app && !_value.id)) - { - console.warn("Bad value for link widget. Need an object with keys 'app', 'id', and optionally 'title'", _value); - return; - } - if (!_value.title) - { - var title = this.egw().link_title(_value.app, _value.id, false); - if (title != null) - { - _value.title = title; - } - else - { - // Title will be fetched from server and then set - var title = this.egw().link_title(_value.app, _value.id, true).then(title => - { - this.search.removeClass("loading").val(title + ""); - // Remove specific display and revert to CSS file - // show() would use inline, should be inline-block - this.clear.css('display', ''); - }); - this.search.addClass("loading"); - } - } - if (_value.title) - { - this.search.val(_value.title + ""); - } - this.options.value = _value; - - jQuery("option[value='" + _value.app + "']", this.app_select).prop("selected", true); - this.app_select.hide(); - - this.div.addClass("no_app"); - } - - set_blur(_value, input) - { - - if (typeof input == 'undefined') input = this.search; - - if (_value) - { - input.attr("placeholder", _value); // HTML5 - if (!input[0].placeholder) - { - // Not HTML5 - if (input.val() == "") input.val(_value); - input.focus(input, function (e) - { - var placeholder = _value; - if (e.data.val() == placeholder) e.data.val(""); - }).blur(input, function (e) - { - var placeholder = _value; - if (e.data.val() == "") e.data.val(placeholder); - }); - if (input.val() == "") - { - input.val(_value); - } - } - } - else - { - this.search.removeAttr("placeholder"); - } - } - - /** - * Set the query callback - * - * @param {function} f - */ - set_query(f) - { - this.options.query = f; - } - - /** - * Set the select callback - * - * @param {function} f - */ - set_select(f) - { - this.options.select = f; - } - - /** - * Ask server for entries matching selected app/type and filtered by search string - * - * @param {Object} request - * @param {Object} response - */ - query(request, response) - { - // If there is a pending request, abort it - if (this.request) - { - this.request.abort(); - this.request = null; - } - - // Remember last search - this.last_search = this.search.val(); - - // Allow hook / tie in - if (this.options.query && typeof this.options.query == 'function') - { - if (!this.options.query(request, this)) return false; - } - - if ((typeof request.no_cache == 'undefined' && !request.no_cache) && request.term in this.cache) - { - return response(this.cache[request.term]); - } - - this.search.addClass("loading"); - // Remove specific display and revert to CSS file - // show() would use inline, should be inline-block - this.clear.css('display', ''); - - this.request = egw.request("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_search", - [this.app_select.val(), '', request.term, request.options]); - - this.request.then((data) => - { - if (this.request) - { - this.request = null; - } - this.search.removeClass("loading"); - let result = []; - for (var id in data) - { - result.push({"value": id, "label": data[id]}); - } - this.cache[this.search.val()] = result; - response(result); - }); - } - - /** - * User selected a value - * - * @param {Object} event - * @param {Object} selected - * - */ - select(event, selected) - { - if (selected.item.value !== null && typeof selected.item.value == "string") - { - // Correct changed value from server - selected.item.value = selected.item.value.trim(); - } - if (this.options.select && typeof this.options.select == 'function') - { - if (!this.options.select(event, selected)) return false; - } - if (typeof event.data.options.value != 'object' || event.data.options.value == null) - { - event.data.options.value = {}; - } - event.data.options.value.id = selected.item.value; - - // Set a processing flag to filter some events - event.data.processing = true; - - // Remove specific display and revert to CSS file - // show() would use inline, should be inline-block - this.clear.css('display', ''); - event.data.search.val(selected.item.label); - - // Fire change event - this.search.change(); - - // Turn off processing flag when done - window.setTimeout(jQuery.proxy(function () - { - delete this.processing; - }, event.data)); - } - - /** - * Create a link using the current internal values - * - * @param {Object} event - * @param {Object} _links - */ - createLink(event, _links) - { - - var values = event.data.options.value; - var self = event.data; - var links = []; - - if (typeof _links == 'undefined') - { - links = []; - } - else - { - links = _links; - } - - // Links to other entries - if (values.id) - { - links.push({ - app: values.app, - id: values.id - }); - self.search.val(""); - } - - // If a link array was passed in, don't make the ajax call - if (typeof _links == 'undefined') - { - var request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", - [values.to_app, values.to_id, links], - self._link_result, - this, - true - ); - request.sendRequest(); - } - } - - /** - * Sent some links, server has a result - * - * @param {Object} success - * - */ - _link_result(success) - { - if (success) - { - this.status_span.fadeIn().delay(1000).fadeOut(); - delete this.options.value.app; - delete this.options.value.id; - } - } -} - -et2_register_widget(et2_link_entry, ["link-entry"]); +export type et2_link_entry = Et2LinkEntry; /** * @deprecated use Et2Link @@ -1254,245 +52,16 @@ et2_register_widget(et2_link_entry, ["link-entry"]); export type et2_link = Et2Link; /** - * UI widget for a single (read-only) link - * + * @deprecated use Et2LinkEntryReadonly */ -export class et2_link_entry_ro extends et2_valueWidget implements et2_IDetachedDOM -{ - static readonly _attributes: any = { - "only_app": { - "name": "Application", - "type": "string", - "default": "", - "description": "Use the given application, so you can pass just the ID for value" - }, - "value": { - description: "Array with keys app, id, and optionally title", - type: "any" - }, - "needed": { - "ignore": true - }, - "link_hook": { - "name": "Type", - "type": "string", - "default": "view", - "description": "Hook used for displaying link (view/edit/add)" - }, - "target_app": { - "name": "Target application", - "type": "string", - "default": "", - "description": "Optional parameter to be passed to egw().open in order to open links in specified application" - }, - "extra_link_target": { - "name": "Link target", - "type": "string", - "default": null, - "description": "Optional parameter to be passed to egw().open in order to open links in specified target eg. _blank" - }, - "break_title": { - "name": "break title", - "type": "string", - "default": null, - "description": "Breaks title into multiple lines based on selected delimiter by replacing it with '\r\n'" - } - }; - public static readonly legacyOptions = ["only_app"]; - - private label_span: JQuery; - private link: JQuery; - - /** - * Constructor - * - * @memberOf et2_link - */ - constructor(_parent: et2_widget, _attrs?: WidgetConfig, _child?: object) - { - super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_valueWidget._attributes, _child || {})); - - - this.label_span = jQuery(document.createElement("label")) - .addClass("et2_label"); - this.link = jQuery(document.createElement("span")) - .addClass("et2_link") - .appendTo(this.label_span); - - if (this.options['class']) this.label_span.addClass(this.options['class']); - this.setDOMNode(this.label_span[0]); - } - - destroy() - { - if (this.link) this.link.unbind(); - this.link = null; - super.destroy.apply(this, arguments); - } - - set_label(label) - { - // Remove current label - this.label_span.contents() - .filter(function () - { - return this.nodeType == 3; - }).remove(); - - var parts = et2_csvSplit(label, 2, "%s"); - this.label_span.prepend(parts[0]); - this.label_span.append(parts[1]); - this.label = label; - - // add class if label is empty - this.label_span.toggleClass('et2_label_empty', !label || !parts[0]); - } - - set_value(_value) - { - if (typeof _value != 'object' && _value && !this.options.only_app) - { - if (_value.indexOf(':') >= 0) - { - var app = _value.split(':', 1); - var id = _value.substr(app[0].length + 1); - _value = {'app': app[0], 'id': id}; - } - else - { - console.warn("Bad value for link widget. Need an object with keys 'app', 'id', and optionally 'title'", _value); - return; - } - } - // Application set, just passed ID - else if (typeof _value != "object") - { - _value = { - app: this.options.only_app, - id: _value - }; - } - if (!_value || jQuery.isEmptyObject(_value)) - { - this.link.text("").unbind(); - return; - } - var self = this; - this.link.unbind(); - if (_value.id && _value.app) - { - this.link.addClass("et2_link"); - this.link.click(function (e) - { - // try to fetch value.title if it wasn't fetched during initiation. - if (!_value.title) _value.title = self.egw().link_title(_value.app, _value.id, false); - if (!self.options.target_app) - { - self.options.target_app = _value.app; - } - const target = self.options.extra_link_target || _value.app; - self.egw().open(_value, "", self.options.link_hook, _value.extra_args, target, self.options.target_app); - e.stopImmediatePropagation(); - }); - } - else - { - this.link.removeClass("et2_link"); - } - if (!_value.title) - { - const node = this.link[0]; - if (_value.app && _value.id) - { - this.egw().link_title(_value.app, _value.id, true).then(title => - { - this.set_title(node, title); - }); - // Title will be fetched from server and then set - return; - } - _value.title = ""; - } - this.set_title(this.link, _value.title); - } - - /** - * Sets the text to be displayed. - * Used as a callback, so node is provided to make sure we get the right one - * - * @param {Object} node - * @param {String} _value description - */ - set_title(node, _value) - { - if (_value === false || _value === null) _value = ""; - - if (this.options.break_title) - { - // Set up title to optionally break on the provided character - replace all space with nbsp, add a - // zero-width space after the break string - _value = _value - .replace(this.options.break_title, this.options.break_title.trimEnd() + "\u200B") - .replace(/ /g, '\u00a0'); - } - jQuery(node).text(_value + ""); - } - - /** - * Creates a list of attributes which can be set when working in the - * "detached" mode. The result is stored in the _attrs array which is provided - * by the calling code. - * - * @param {Array} _attrs an array of attributes - */ - getDetachedAttributes(_attrs) - { - _attrs.push("label", "value"); - } - - /** - * Returns an array of DOM nodes. The (relatively) same DOM-Nodes have to be - * passed to the "setDetachedAttributes" function in the same order. - */ - getDetachedNodes() - { - return [this.node, this.link[0]]; - } - - /** - * Sets the given associative attribute->value array and applies the - * attributes to the given DOM-Node. - * - * @param _nodes is an array of nodes which have to be in the same order as - * the nodes returned by "getDetachedNodes" - * @param _values is an associative array which contains a subset of attributes - * returned by the "getDetachedAttributes" function and sets them to the - * given values. - */ - setDetachedAttributes(_nodes, _values) - { - this.node = _nodes[0]; - this.label_span = jQuery(_nodes[0]); - this.link = jQuery(_nodes[1]); - if (typeof _values["id"] !== "undefined") this.set_id(_values['id']); - if (typeof _values["label"] !== "undefined") this.set_label(_values['label']); - if (typeof _values["value"] !== "undefined") this.set_value(_values["value"]); - } - -} - -et2_register_widget(et2_link_entry_ro, ["link-entry_ro"]); +export type et2_link_entry_ro = Et2LinkEntryReadonly; /** - * UI widget for one or more links, comma separated - * * @deprecated use Et2LinkString */ export type et2_link_string = Et2LinkString; /** - * UI widget for one or more links in a list (table) - * * @deprecated use Et2LinkList */ // can't just define as type, as tracker/app.ts uses it with iterateOver()! @@ -1550,7 +119,7 @@ export class et2_link_add extends et2_inputWidget value: this.options.application ? this.options.application : this.options.value && this.options.value.add_app ? this.options.value.add_app : null, application_list: this.options.application ? this.options.application : null }), this); - this.div.append(this.app_select.getDOMNode()); + this.div.append(this.app_select); this.button = et2_createWidget("button", { id: this.options.id + "_add", label: this.egw().lang("add") @@ -1559,7 +128,7 @@ export class et2_link_add extends et2_inputWidget var self = this; this.button.click = function () { - self.egw().open(self.options.value.to_app + ":" + self.options.value.to_id, self.app_select.get_value(), 'add'); + self.egw().open(self.options.value.to_app + ":" + self.options.value.to_id, self.app_select.value, 'add'); return false; }; this.div.append(this.button.getDOMNode()); diff --git a/api/js/etemplate/et2_widget_selectAccount.ts b/api/js/etemplate/et2_widget_selectAccount.ts index 8f7237a038..0569ea3c20 100644 --- a/api/js/etemplate/et2_widget_selectAccount.ts +++ b/api/js/etemplate/et2_widget_selectAccount.ts @@ -12,741 +12,13 @@ * @copyright Nathan Gray 2012 */ -/*egw:uses - et2_widget_link; -*/ - -import {et2_selectbox} from "./et2_widget_selectbox"; -import {et2_createWidget, et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; -import {ClassWithAttributes} from "./et2_core_inheritance"; -import {et2_link_entry, et2_link_string} from "./et2_widget_link"; -import {et2_dialog} from "./et2_widget_dialog"; -import {egw} from "../jsapi/egw_global"; import type {Et2SelectAccountReadonly} from "./Et2Select/Et2SelectReadonly"; +import type {Et2SelectAccount} from "./Et2Select/Et2SelectAccount"; /** - * Account selection widget - * Changes according to the user's account_selection preference - * - 'none' => Server-side: the read-only widget is used, and no values are sent or displayed - * - 'groupmembers' => Non admins can only select groupmembers (Server side - normal selectbox) - * - 'selectbox' => Selectbox with all accounts and groups (Server side - normal selectbox) - * - 'primary_group' => Selectbox with primary group and search - * - * Only primary_group and popup need anything different from a normal selectbox - * + * @deprecated use Et2SelectAccount */ -export class et2_selectAccount extends et2_selectbox -{ - static readonly _attributes : any = { - 'account_type': { - 'name': 'Account type', - 'default': 'accounts', - 'type': 'string', - 'description': 'Limit type of accounts. One of {accounts,groups,both,owngroups}.' - } - }; - - public static readonly legacyOptions = ['empty_label','account_type']; - - public static readonly account_types = ['accounts','groups','both','owngroups']; - private search: JQuery; - private dialog: et2_dialog; - private widgets: any; - private search_widget: et2_link_entry; - - /** - * Constructor - * - */ - constructor(_parent : et2_widget, _attrs? : WidgetConfig, _child? : object) - { - super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_selectAccount._attributes, _child || {})); - - // Type in rows or somewhere else? - if(et2_selectAccount.account_types.indexOf(this.options.empty_label) >= 0 && ( - et2_selectAccount.account_types.indexOf(this.options.account_type) < 0 || - this.options.account_type == et2_selectAccount._attributes.account_type.default) - ) - { - this.options.account_type = _attrs['empty_label']; - this.options.empty_label = ''; - } - if(jQuery.inArray(_attrs['account_type'], et2_selectAccount.account_types) < 0) - { - this.egw().debug("warn", "Invalid account_type: %s Valid options:",_attrs['account_type'], et2_selectAccount.account_types); - } - - // Holder for search jQuery nodes - this.search = null; - - // Reference to dialog - this.dialog = null; - - // Reference to widget within dialog - this.widgets = null; - - if(!this.options.empty_label && !this.options.readonly && this.options.multiple) - { - this.options.empty_label = this.egw().lang('Select user or group'); - } - - // Allow certain widgets inside this one - this.supportedWidgetClasses = [et2_link_entry]; - } - - destroy( ) - { - super.destroy.apply(this, arguments); - } - - /** - * Single selection - override to add search button - */ - createInputWidget() - { - var type = this.egw().preference('account_selection', 'common'); - - switch(type) - { - case 'none': - if(typeof egw.user('apps').admin == 'undefined') - { - this.options.select_options = {}; - break; - } - case 'selectbox': - case 'groupmembers': - default: - this.options.select_options = this._get_accounts(); - break; - } - - super.createInputWidget(); - - // Add search button - if(type == 'primary_group') - { - var button = jQuery(document.createElement("span")) - .addClass("et2_clickable") - .click(this, jQuery.proxy(function(e) { - // Auto-expand - if(this.options.expand_multiple_rows && !this.options.multiple) - { - this.set_multiple(true, this.options.expand_multiple_rows); - } - - if(this.options.multiple) - { - this._open_multi_search(e); - } - else - { - this._open_search(e); - } - },this)) - .attr("title", egw.lang("popup with search")) - .append(''); - - this.getSurroundings().insertDOMNode(button[0]); - } - } - - /** - * Multiple selection - override to add search button - */ - createMultiSelect( ) - { - - var type = this.egw().preference('account_selection', 'common'); - if(type == 'none' && typeof egw.user('apps').admin == 'undefined') return; - - super.createMultiSelect(); - - this.options.select_options = this._get_accounts(); - - if(type == 'primary_group') - { - // Allow search 'inside' this widget - this.supportedWidgetClasses = [et2_link_entry]; - - // Add quick search - turn off multiple to get normal result list - this.options.multiple = false; - this._create_search(); - - // Clear search box after select - var old_select = this.search_widget.select; - var self = this; - // @ts-ignore - this.search_widget.select = function(e, selected) { - var current = self.getValue(); - - // Fix ID as sent from server - must be numeric - selected.item.value = parseInt(selected.item.value); - - // This one is important, it makes sure the option is there - old_select.apply(this, arguments); - - // Add quick search selection into current selection - current.push(selected.item.value); - - // Clear search - this.search.val(''); - - self.set_value(current); - }; - - // Put search results as a DOM sibling of the options, for proper display - this.search_widget.search.on("autocompleteopen", jQuery.proxy(function() { - this.search_widget.search.data("ui-autocomplete").menu.element - .appendTo(this.node) - .position({my: 'left top', at: 'left bottom', of: this.multiOptions.prev()}); - },this)); - this.search = jQuery(document.createElement("li")) - .appendTo(this.multiOptions.prev().find('ul')); - this.options.multiple = true; - - // Add search button - var button = jQuery(document.createElement("li")) - .addClass("et2_clickable") - .click(this, this._open_multi_search) - .attr("title", egw.lang("popup with search")) - .append(''); - var type = this.egw().preference('account_selection', 'common'); - - // Put it last so check/uncheck doesn't move around - this.multiOptions.prev().find('ul') - .append(button); - } - } - - /** - * Override parent to make sure accounts are there as options. - * - * Depending on the widget's attributes and the user's preferences, not all selected - * accounts may be in the cache as options, so we fetch the extras to make sure - * we don't lose any. - * - * As fetching them might only work asynchron (if they are not yet loaded), - * we have to call set_value again, once all labels have arrived from server. - * - * @param {string|array} _value - */ - set_value(_value) - { - if(typeof _value == "string" && this.options.multiple && _value.match(this._is_multiple_regexp) !== null) - { - _value = _value.split(','); - } - - if(_value) - { - var search = _value; - if (!jQuery.isArray(search)) - { - search = [_value]; - } - var update_options = false; - var num_calls = 0; - var current_call = 0; - for(var j = 0; j < search.length; j++) - { - var found = false; - - // Not having a value to look up causes an infinite loop - if(!search[j] || search[j] === "0") continue; - - // Options are not indexed, so we must look - for(var i = 0; !found && i < this.options.select_options.length; i++) - { - if (typeof this.options.select_options[i] != 'object') - { - egw.debug('warn',this.id + ' wrong option '+i+' this.options.select_options=', this.options.select_options); - continue; - } - if(this.options.select_options[i].value == search[j]) found = true; - } - // We only look for numeric IDs, non-numeric IDs cause an exception - if(!found && !isNaN(search[j])) - { - // Add it in - var name = this.egw().link_title('api-accounts', search[j], false); - if (name) // was already cached on client-side - { - update_options = true; - this.options.select_options.push({value: search[j], label:name}); - } - else // not available: need to call set_value again, after all arrived from server - { - ++num_calls; - // Add immediately with value as label, we'll replace later - this._appendOptionElement(search[j],search[j]); - this.egw().link_title('api-accounts', search[j], function(name) - { - if (++current_call >= num_calls) // only run last callback - { - // Update the label - // Options are not indexed, so we must look - for(var i = 0; i < this.widget.options.select_options.length; i++) - { - var opt = this.widget.options.select_options[i]; - if(opt && opt.value && opt.value == this.unknown && opt.label == this.unknown) - { - opt.label = name; - this.widget.set_select_options(this.widget.options.select_options); - break; - } - } - this.widget.set_value(_value); - } - }, {widget: this, unknown: search[j]}); - } - } - } - if(update_options) - { - this.set_select_options(this.options.select_options); - } - } - super.set_value(_value); - } - - /** - * Get account info for select options from common client-side account cache - * - * @return {Array} select options - */ - _get_accounts() - { - if (!jQuery.isArray(this.options.select_options)) - { - var options = jQuery.extend({}, this.options.select_options); - this.options.select_options = []; - for(var key in options) - { - if (typeof options[key] == 'object') - { - if (typeof(options[key].key) == 'undefined') - { - options[key].value = key; - } - this.options.select_options.push(options[key]); - } - else - { - this.options.select_options.push({value: key, label: options[key]}); - } - } - } - var type = this.egw().preference('account_selection', 'common'); - var accounts = []; - // for primary_group we only display owngroups == own memberships, not other groups - if (type == 'primary_group' && this.options.account_type != 'accounts') - { - if (this.options.account_type == 'both') - { - accounts = this.egw().accounts('accounts'); - } - accounts = accounts.concat(this.egw().accounts('owngroups')); - } - else - { - accounts = this.egw().accounts(this.options.account_type); - } - return this.options.select_options.concat(accounts); - } - - /** - * Create & display a way to search & select a single account / group - * Single selection is just link widget - * - * @param e event - */ - _open_search( e) - { - var widget = e.data; - var search = widget._create_search(); - - // Selecting a single user closes the dialog, this only used if user cleared - var ok_click = function() { - widget.set_value([]); - // Fire change event - if(widget.input) widget.input.trigger("change"); - jQuery(this).dialog("close"); - }; - widget._create_dialog(search, ok_click); - } - - /** - * Create & display a way to search & select multiple accounts / groups - * - * @param e event - */ - _open_multi_search( e) - { - var widget = e && e.data ? e.data : this; - var table = widget.search = jQuery('
'); - table.css("width", "100%").css("height", "100%"); - var search_col = jQuery('#search_col',table); - var select_col = jQuery('#selection_col',table); - - // Search / Selection - search_col.append(widget._create_search()); - - // Currently selected - select_col.append(widget._create_selected()); - - var ok_click = function() { - jQuery(this).dialog("close"); - // Update widget with selected - var ids = []; - var data = {}; - jQuery('#'+widget.getInstanceManager().uniqueId + '_selected li',select_col).each(function() { - var id = jQuery(this).attr("data-id"); - // Add to list - ids.push(id); - - // Make sure option is there - if(widget.options.multiple && jQuery('input[id$="_opt_'+id+'"]',widget.multiOptions).length == 0) - { - widget._appendMultiOption(id,jQuery('label',this).text()); - } - else if (!widget.options.multiple && jQuery('option[value="'+id+'"]',widget.node).length == 0) - { - widget._appendOptionElement(id,jQuery('label',this).text()); - } - }); - - widget.set_value(ids); - - // Fire change event - if(widget.input) widget.input.trigger("change"); - }; - - var container = jQuery(document.createElement("div")).append(table); - return widget._create_dialog(container, ok_click); - } - - /** - * Create / display popup with search / selection widgets - * - * @param {et2_dialog} widgets - * @param {function} update_function - */ - _create_dialog( widgets, update_function) - { - this.widgets = widgets; - this.dialog = et2_dialog.show_dialog(undefined, - '', - this.options.label ? this.options.label : this.egw().lang('Select'), - {}, - [{ - text: this.egw().lang("ok"), - image: 'check', - click: update_function - },{ - text: this.egw().lang("cancel"), - image: 'cancel' - }] - ); - this.dialog.set_dialog_type(''); - // Static size for easier layout - this.dialog.div.dialog({width: "500", height: "370"}); - - this.dialog.div.append(widgets.width('100%')); - return widgets; - } - - /** - * Search is a link-entry widget, with some special display for multi-select - */ - _create_search( ) - { - var self = this; - var search = this.search = jQuery(document.createElement("div")); - - var search_widget = this.search_widget = et2_createWidget('link-entry', { - 'only_app': 'api-accounts', - 'query'( request, response) - { - // Clear previous search results for multi-select - if(!request.options) - { - search.find('#search_results').empty(); - } - // Restrict to specified account type - if(!request.options || !request.options.filter) - { - request.options = {account_type: self.options.account_type}; - } - return true; - }, - 'select'( e, selected) - { - // Make sure option is there - var already_there = false; - var last_key = null; - for(last_key in self.options.select_options) - { - var option = self.options.select_options[last_key]; - already_there = already_there || (typeof option.value != 'undefined' && option.value == selected.item.value); - } - if(!already_there) - { - self.options.select_options[parseInt(last_key)+1] = selected.item; - self._appendOptionElement(selected.item.value, selected.item.label); - } - self.set_value(selected.item.value); - if(self.dialog && self.dialog.div) - { - self.dialog.div.dialog("close"); - } - // Fire change event - if(self.input) self.input.trigger("change"); - return true; - } - }, this); - // add it where we want it - search.append(search_widget.getDOMNode()); - - if(!this.options.multiple) return search; - - // Multiple is more complicated. It uses a custom display for results to - // allow choosing multiples from a match - var results = jQuery(document.createElement("ul")) - .attr("id", "search_results") - .css("height", "230px") - .addClass("ui-multiselect-checkboxes ui-helper-reset"); - jQuery(document.createElement("div")) - .addClass("et2_selectbox") - .css("height", "100%") - .append(results) - .appendTo(search); - - // Override link-entry auto-complete for custom display - // Don't show normal drop-down - search_widget.search.data("ui-autocomplete")._suggest = function(items) { - jQuery.each(items, function (index, item) { - // Make sure value is numeric - item.value = parseInt(item.value); - self._add_search_result(results, item); - }); - }; - - return search; - } - - /** - * Add the selected result to the list of search results - * - * @param list - * @param item - */ - _add_search_result( list, item) - { - - var node = null; - var self = this; - - // Make sure value is numeric - if(item.value) item.value = parseInt(item.value); - - // (containter of) Currently selected users / groups - var selected = jQuery('#'+this.getInstanceManager().uniqueId + "_selected", this.widgets); - - // Group - if(item.value && item.value < 0) - { - node = jQuery(document.createElement('ul')); - // Add button to show users - if(this.options.account_type != 'groups') - { - jQuery('') - .css("float", "left") - .appendTo(node) - .click(function() { - if(jQuery(this).hasClass("ui-icon-circlesmall-plus")) - { - jQuery(this).removeClass("ui-icon-circlesmall-plus") - .addClass("ui-icon-circlesmall-minus"); - - var group = jQuery(this).parent() - .addClass("expanded"); - - if(group.children("li").length == 0) - { - // Fetch group members - self.search_widget.query({ - term:"", - options: {filter:{group: item.value}}, - no_cache:true - }, function(items) { - jQuery(items).each(function(index,item) { - self._add_search_result(node, item); - }); - }); - } - else - { - group.children("li") - // Only show children that are not selected - .each(function(index, item) { - var j = jQuery(item); - if(jQuery('[data-id="'+j.attr("data-id")+'"]',selected).length == 0) - { - j.show(); - } - }); - } - } - else - { - jQuery(this).addClass("ui-icon-circlesmall-plus") - .removeClass("ui-icon-circlesmall-minus"); - - var group = jQuery(this).parent().children("li").hide(); - } - }); - } - - } - // User - else if (item.value) - { - node = jQuery(document.createElement('li')); - } - node.attr("data-id", item.value); - - jQuery('') - .css("float", "right") - .appendTo(node) - .click(function() { - var button = jQuery(this); - self._add_selected(selected, button.parent().attr("data-id")); - // Hide user, but only hide button for group - if(button.parent().is('li')) - { - button.parent().hide(); - } - else - { - button.hide(); - } - }); - - // If already in list, hide it - if(jQuery('[data-id="'+item.value+'"]',selected).length != 0) - { - node.hide(); - } - - var label = jQuery(document.createElement('label')) - .addClass("loading") - .appendTo(node); - - this.egw().link_title('api-accounts', item.value, function(name) { - label.text(name).removeClass("loading"); - }, label); - - node.appendTo(list); - } - - _create_selected( ) - { - var node = jQuery(document.createElement("div")) - .addClass("et2_selectbox"); - - var header = jQuery(document.createElement("div")) - .addClass("ui-widget-header ui-helper-clearfix") - .appendTo(node); - - var selected = jQuery(document.createElement("ul")) - .addClass("ui-multiselect-checkboxes ui-helper-reset") - .attr("id", this.getInstanceManager().uniqueId + "_selected") - .css("height", "230px") - .appendTo(node); - - jQuery(document.createElement("span")) - .text(this.egw().lang("Selection")) - .addClass("ui-multiselect-header") - .appendTo(header); - - var controls = jQuery(document.createElement("ul")) - .addClass('ui-helper-reset') - .appendTo(header); - - jQuery(document.createElement("li")) - .addClass("et2_clickable") - .click(selected, function(e) {jQuery("li",e.data).remove();}) - .append('') - .appendTo(controls); - - // Add in currently selected - if(this.getValue()) - { - var value = this.getValue(); - for(var i = 0; i < value.length; i++) { - this._add_selected(selected, value[i]); - } - } - return node; - } - - /** - * Add an option to the list of selected accounts - * value is the account / group ID - * - * @param list - * @param value - */ - _add_selected( list, value) - { - - // Each option only once - var there = jQuery('[data-id="' + value + '"]',list); - if(there.length) - { - there.show(); - return; - } - - var option = jQuery(document.createElement('li')) - .attr("data-id",value) - .appendTo(list); - jQuery('
') - .css("float", "right") - .appendTo(option) - .click(function() { - var id = jQuery(this).parent().attr("data-id"); - jQuery(this).parent().remove(); - // Add 'add' button back, if in results list - list.parents("tr").find("[data-id='"+id+"']").show() - // Show button(s) for group - .children('span').show(); - }); - - var label = jQuery(document.createElement('label')) - .addClass("loading") - .appendTo(option); - this.egw().link_title('api-accounts', value, function(name) {this.text(name).removeClass("loading");}, label); - } - - /** - * Overwritten attachToDOM method to modify attachToDOM - */ - attachToDOM() - { - let result = super.attachToDOM(); - //Chosen needs to be set after widget dettached from DOM (eg. validation_error), because chosen is not part of the widget node - if (this.egw().preference('account_selection', 'common') == 'primary_group') - { - jQuery(this.node).removeClass('chzn-done'); - this.set_tags(this.options.tags, this.options.width); - } - - return result; - } -} -et2_register_widget(et2_selectAccount, ["select-account"]); +export type et2_selectAccount = Et2SelectAccount; /** * @deprecated use Et2SelectAccountReadonly diff --git a/api/js/etemplate/et2_widget_selectbox.ts b/api/js/etemplate/et2_widget_selectbox.ts index 7addc5ea16..af18e8066f 100644 --- a/api/js/etemplate/et2_widget_selectbox.ts +++ b/api/js/etemplate/et2_widget_selectbox.ts @@ -10,1854 +10,9 @@ * @copyright Nathan Gray 2011 */ -/*egw:uses - /vendor/bower-asset/jquery/dist/jquery.js; - /api/js/jquery/chosen/chosen.jquery.js; - et2_core_xml; - et2_core_DOMWidget; - et2_core_inputWidget; -*/ - -import "../../../vendor/bower-asset/jquery/dist/jquery.min.js"; -import "../jquery/chosen/chosen.jquery.js"; -import {et2_no_init} from "./et2_core_common"; -import {ClassWithAttributes} from "./et2_core_inheritance"; -import {et2_register_widget, et2_widget, WidgetConfig} from "./et2_core_widget"; -import {et2_inputWidget} from './et2_core_inputWidget' -import {et2_DOMWidget} from "./et2_core_DOMWidget"; -import {et2_directChildrenByTagName, et2_readAttrWithDefault} from "./et2_core_xml"; -import {egw} from "../jsapi/egw_global"; -import {et2_IDetachedDOM} from "./et2_core_interfaces"; -import {sprintf} from "../egw_action/egw_action_common.js"; - -// all calls to Chosen jQuery plugin as jQuery.(un)chosen() give errors which are currently suppressed with @ts-ignore -// adding npm package @types/chosen-js did NOT help :( +import {Et2Select} from "./Et2Select/Et2Select"; /** - * et2 select(box) widget + * @deprecated use Et2Select */ -export class et2_selectbox extends et2_inputWidget -{ - static readonly _attributes : any = { - // todo fully implement attr[multiple] === "dynamic" to render widget with a button to switch to multiple - // as it is used in account_id selection in admin >> mailaccount (app.admin.edit_multiple method client-side) - "multiple": { - "name": "multiple", - "type": "boolean", - "default": false, - "description": "Allow selecting multiple options" - }, - "expand_multiple_rows": { - "name": "Expand multiple", - "type": "integer", - "default": et2_no_init, - "description": "Shows single select widget, with a button. If the "+ - "user clicks the button, the input will toggle to a multiselect,"+ - "with this many rows. " - }, - "rows": { - "name": "Rows", - "type": "any", // Old options put either rows or empty_label in first space - "default": 1, - "description": "Number of rows to display" - }, - "empty_label": { - "name": "Empty label", - "type": "string", - "default": "", - "description": "Textual label for first row, eg: 'All' or 'None'. ID will be ''", - translate:true - }, - "select_options": { - "type": "any", - "name": "Select options", - "default": {}, - "description": "Internaly used to hold the select options." - }, - "selected_first": { - "name": "Selected options first", - "type": "boolean", - "default": true, - "description": "For multi-selects, put the selected options at the top of the list when first loaded" - }, - - // Chosen options - "search": { - "name": "Search", - "type": "boolean", - "default": false, - "description": "For single selects, add a search box to the drop-down list" - }, - "tags": { - "name": "Tag style", - "type": "boolean", - "default": false, - "description": "For multi-selects, displays selected as a list of tags instead of a big list" - }, - "allow_single_deselect": { - "name": "Allow Single Deselect", - "type": "boolean", - "default": true, - "description": "Allow user to unset current selected value" - }, - - // Value can be string or integer - "value": { - "type": "any" - }, - // Type specific legacy options. Avoid using. - "other": { - "ignore": true, - "type": "any" - }, - value_class: { - name: "Value class", - type: "string", - default: "", - description: "Allow to set a custom css class combined with selected value. (e.g. cat_23)" - } - }; - - public static readonly legacyOptions: string[] = ["rows","other"]; // Other is sub-type specific - input: JQuery = null; - value: string | string[] = ''; - expand_button: JQuery; - multiOptions: JQuery; - selected_first: boolean = true; - - /** - * Constructor - */ - constructor(_parent, _attrs? : WidgetConfig, _child? : object) - { - // Call the inherited constructor - super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_selectbox._attributes, _child || {})); - - this.input = null; - // Start at '' to avoid infinite loops while setting value/select options - this.value = ''; - - // Allow no other widgets inside this one - this.supportedWidgetClasses = []; - - // Legacy options could have row count or empty label in first slot - if(typeof this.options.rows == "string") - { - if(isNaN(this.options.rows)) - { - this.options.empty_label = this.egw().lang(this.options.rows); - this.options.rows = 1; - } - else - { - this.options.rows = parseInt(this.options.rows); - } - } - - if(this.options.rows > 1) - { - this.options.multiple = true; - if(this.options.tags) - { - this.createInputWidget(); - } - else - { - this.createMultiSelect(); - } - } - else - { - this.createInputWidget(); - } - if(!this.options.empty_label && !this.options.readonly && this.options.multiple) - { - this.options.empty_label = this.egw().lang('Select some options'); - } - } - - destroy() - { - if(this.input != null) - { - // @ts-ignore - this.input.unchosen(); - } - if(this.expand_button) - { - this.expand_button.off(); - this.expand_button.remove(); - this.expand_button = null; - } - super.destroy(); - - this.input = null; - } - - transformAttributes(_attrs) - { - super.transformAttributes(_attrs); - - // If select_options are already known, skip the rest - if(this.options && this.options.select_options && !jQuery.isEmptyObject(this.options.select_options) || - _attrs.select_options && !jQuery.isEmptyObject(_attrs.select_options) || - // Allow children to skip select_options - check to make sure default got set to something (should be {}) - typeof _attrs.select_options == 'undefined' || _attrs.select_options === null - ) - { - // do not return inside nextmatch, as get_rows data might have changed select_options - // for performance reasons we only do it for first row, which should have id "0[...]" - if (this.getParent() && this.getParent().getType() != 'rowWidget' || !_attrs.id || _attrs.id[0] != '0') return; - } - - var sel_options = et2_selectbox.find_select_options(this, _attrs['select_options'], _attrs); - if(!jQuery.isEmptyObject(sel_options)) - { - _attrs['select_options'] = sel_options; - } - } - - /** - * Switch instanciated widget to multi-selection and back, optionally enabeling tags too - * - * If you want to switch tags on too, you need to do so after switching to multiple! - * - * @param {boolean} _multiple - * @param {integer} _size default=3 - */ - set_multiple(_multiple, _size) - { - this.options.multiple = _multiple; - - if (this.input) - { - if (_multiple) - { - this.input.attr('size', _size || 3); - this.input.prop('multiple', true); - this.input.attr('name', this.id + '[]'); - - if ((this.input[0]).options.length && (this.input[0]).options[0].value === '') - { - (this.input[0]).options[0] = null; - } - } - else - { - this.input.prop('multiple', false); - this.input.removeAttr('size'); - this.input.attr('name', this.id); - - if (this.options.empty_label && (this.input[0]).options[0].value !== '') - { - this._appendOptionElement('', this.options.empty_label); - } - } - if(this.expand_button) - { - if(_multiple) - { - this.expand_button.addClass('ui-icon-minus').removeClass('ui-icon-plus'); - } - else - { - this.expand_button.removeClass('ui-icon-minus').addClass('ui-icon-plus'); - } - } - } - } - - change(_node, _widget, _value) - { - var valid = super.change.apply(this, arguments); - if (!this.input) return valid; - var selected = this.input.siblings().find('a.chzn-single'); - var val = _value && _value.selected ? _value.selected : this.input.val(); - switch (this.getType()) - { - case 'select-country': - if (selected && selected.length == 1 && val) - { - selected.removeClass (function (index, className) { - return (className.match (/(^|\s)flag-\S+/g) || []).join(' '); - }); - selected.find('span.img').remove(); - selected.prepend(''); - selected.addClass('et2_country-select flag-'+ val.toLowerCase()); - } - else if(selected) - { - selected.removeClass('et2_country-select'); - } - break; - } - return valid; - } - - /** - * Overridden from parent to make sure tooltip handler is bound to the correct element - * if tags is on. - */ - getTooltipElement(): HTMLElement - { - if(this.input && (this.options.tags || this.options.search)) - { - return jQuery(this.input.siblings()).get(0); - } - return this.getDOMNode(this); - } - - - /** - * Add an option to regular drop-down select - * - * @param {string} _value value attribute of option - * @param {string} _label label of option - * @param {string} _title title attribute of option - * @param {node} dom_element parent of new option - * @param {string} _class specify classes of option - */ - _appendOptionElement(_value, _label, _title?, dom_element?, _class?) - { - if(_value == "" && (_label == null || _label == "")) { - return; // empty_label is added in set_select_options anyway, ignoring it here to not add it twice - } - - if(this.input == null) - { - return this._appendMultiOption(_value, _label, _title, dom_element); - } - - var option = jQuery(document.createElement("option")) - .attr("value", _value) - .text(_label+""); - option.addClass(_class); - if (this.options.tags) - { - switch (this.getType()) - { - case 'select-cat': - option.addClass('cat_'+_value); - break; - case 'select-country': - // jQuery(document.createElement("span")).addClass('et2_country-select').appenTo(option); - option.addClass('et2_country-select flag-'+_value.toLowerCase()); - break; - } - if (this.options.value_class != '') option.addClass(this.options.value_class+_value); - } - if (typeof _title != "undefined" && _title) - { - option.attr("title", _title); - } - if(_label == this.options.empty_label || this.options.empty_label == "" && _value === "") - { - // Make sure empty / all option is first - option.prependTo(this.input); - } - else - { - option.appendTo(dom_element || this.input); - } - } - - /** - * Append a value to multi-select - * - * @param {string} _value value attribute of option - * @param {string} _label label of option - * @param {string} _title title attribute of option - * @param {node} dom_element parent of new option - */ - _appendMultiOption(_value, _label, _title, dom_element?) - { - var option_data = null; - if(typeof _label == "object") - { - option_data = _label; - _label = option_data.label; - } - - // Already in header - if(_label == this.options.empty_label) return; - - var opt_id = this.dom_id + "_opt_" + _value; - var label = jQuery(document.createElement("label")) - .attr("for", opt_id) - .hover( - function() {jQuery(this).addClass("ui-state-hover");}, - function() {jQuery(this).removeClass("ui-state-hover");} - ); - var option = jQuery(document.createElement("input")) - .attr("type", "checkbox") - .attr("id",opt_id) - .attr("value", _value) - .appendTo(label); - if(typeof _title !== "undefined") - { - option.attr("title",_title); - } - - // Some special stuff for categories - if(option_data ) - { - if(option_data.icon) - { - var img = this.egw().image(option_data.icon); - jQuery(document.createElement(img ? "img" : "div")) - .attr("src", img) - .addClass('cat_icon cat_' + _value) - .appendTo(label); - } - if(option_data.color) - { - label.css("background-color",option_data.color) - .addClass('cat_' + _value); - } - } - //added tooltip to multiselect - if(typeof _title == "undefined") - { - _title = _label; - } - label.append(jQuery(""+_label+"")); - var li = jQuery(document.createElement("li")).append(label); - if (this.options.value_class !='') li.addClass(this.options.value_class+_value); - li.appendTo(dom_element || this.multiOptions); - } - - /** - * Create a regular drop-down select box - */ - createInputWidget() { - // Create the base input widget - this.input = jQuery(document.createElement("select")) - .addClass("et2_selectbox") - .attr("size", this.options.rows); - - this.setDOMNode(this.input[0]); - - // Add the empty label - if(this.options.empty_label) - { - this._appendOptionElement("", this.options.empty_label); - } - - // Set multiple - if(this.options.multiple) - { - this.input.attr("multiple", "multiple"); - } - } - - /** - * Create a list of checkboxes - */ - createMultiSelect() { - var node = jQuery(document.createElement("div")) - .addClass("et2_selectbox"); - - var header = jQuery(document.createElement("div")) - .addClass("ui-widget-header ui-helper-clearfix") - .appendTo(node); - var controls = jQuery(document.createElement("ul")) - .addClass('ui-helper-reset') - .appendTo(header); - - jQuery(document.createElement("span")) - .text(this.options.empty_label) - .addClass("ui-multiselect-header") - .appendTo(header); - - - // Set up for options to be added later - var options = this.multiOptions = jQuery(document.createElement("ul")); - this.multiOptions.addClass("ui-multiselect-checkboxes ui-helper-reset") - .css("height", 1.9*this.options.rows + "em") - .appendTo(node); - - if(this.options.rows >= 5) - { - // Check / uncheck all - var header_controls = { - check: { - icon_class: 'ui-icon-check', - label: this.egw().lang('Check all'), - click(e) { - var all_off = false; - jQuery("input[type='checkbox']",e.data).each(function() { - if(!jQuery(this).prop("checked")) all_off = true; - }); - jQuery("input[type='checkbox']",e.data).prop("checked", all_off); - } - } - }; - for(var key in header_controls) - { - jQuery(document.createElement("li")) - .addClass("et2_clickable") - .click(options, header_controls[key].click) - .attr("title", header_controls[key].label) - .append('') - .appendTo(controls); - } - - } - - this.setDOMNode(node[0]); - } - - doLoadingFinished() - { - super.doLoadingFinished(); - - this.set_tags(this.options.tags, this.options.width); - - // Reset dirty again here. super.doLoadingFinished() does it too, but set_tags() & others - // change things. Moving set_tags() before super.doLoadingFinished() breaks tag widgets - this.resetDirty(); - - return true; - } - - loadFromXML(_node) { - // Handle special case where legacy option for empty label is used (conflicts with rows), and rows is set as an attribute - var legacy = _node.getAttribute("options"); - if(legacy) - { - var legacy = legacy.split(","); - if(legacy.length && isNaN(legacy[0])) - { - this.options.empty_label = legacy[0]; - } - } - - // Read the option-tags - var options = et2_directChildrenByTagName(_node, "option"); - if(options.length) - { - // Break reference to content manager, we don't want to add to it - this.options.select_options = jQuery.extend([], this.options.select_options); - } - var egw = this.egw(); - for (var i = 0; i < options.length; i++) - { - this.options.select_options.push({ - value: et2_readAttrWithDefault(options[i], "value", options[i].textContent), - // allow options to contain multiple translated sub-strings eg: {Firstname}.{Lastname} - "label": options[i].textContent.replace(/{([^}]+)}/g, function(str,p1) - { - return egw.lang(p1); - }), - "title": et2_readAttrWithDefault(options[i], "title", "") - }); - } - - this.set_select_options(this.options.select_options); - } - - /** - * Regular expression, to check string-value contains multiple comma-separated values - */ - readonly _is_multiple_regexp = /^[,0-9A-Za-z/_ -]+$/; - - /** - * Regular expression and replace value for escaping values in jQuery selectors used to find options - */ - readonly _escape_value_replace = /\\/g; - readonly _escape_value_with = '\\\\'; - - /** - * Find an option by it's value - * - * Taking care of escaping values correctly eg. EGroupware\Api\Mail\Smtp using above regular expression - * - * @param {string} _value - * @return {array} - */ - find_option(_value) - { - return jQuery("option[value='"+(typeof _value === 'string' ? _value.replace(this._escape_value_replace, this._escape_value_with) : _value)+"']", this.input); - } - - /** - * Set value - * - * @param {string|number} _value - * @param {boolean} _dont_try_set_options true: if _value is not in options, use "" instead of calling set_select_options - * (which would go into an infinit loop) - */ - // @ts-ignore for 2nd parameter - set_value(_value, _dont_try_set_options?) - { - if (typeof _value == "number") _value = ""+_value; // convert to string for consitent matching - if(typeof _value == "string" && (this.options.multiple || this.options.expand_multiple_rows) && _value.match(this._is_multiple_regexp) !== null) - { - _value = _value.split(','); - } - if(this.input !== null && this.options.select_options && ( - !jQuery.isEmptyObject(this.options.select_options) || this.options.select_options.length > 0 - ) && this.input.children().length == 0) - { - // No options set yet - this.set_select_options(this.options.select_options); - } - // select-cat set/unset right cat_ color for selected value - if ((this.getType() == 'select-cat' || this.options.value_class) && this.options.tags) { - var chosen = this.input.next(); - var prefix_c = this.options.value_class ? this.options.value_class : 'cat_'; - this.input.removeClass(prefix_c+this._oldValue); - this.input.addClass(prefix_c+this.value); - if (chosen.length > 0) { - chosen.removeClass(prefix_c+this._oldValue); - chosen.addClass(prefix_c+this.value); - } - } - if (this.getType() == 'select-country' && this.options.tags) - { - var selected = this.input.siblings().find('a.chzn-single'); - if (selected && selected.length == 1 && _value) - { - selected.removeClass (function (index, className) { - return (className.match (/(^|\s)flag-\S+/g) || []).join(' '); - }); - selected.find('span.img').remove(); - selected.prepend(''); - selected.addClass('et2_country-select flag-'+ _value.toLowerCase()); - } - } - if(this.getType() == "select-bitwise" && _value && !isNaN(_value) && this.options.select_options) - { - var new_value = []; - for(var index in this.options.select_options) - { - var right = this.options.select_options[index].value; - if(!!(_value & right)) - { - new_value.push(right); - } - } - _value = new_value; - } - this._oldValue = this.value; - if(this.input !== null && (this.options.tags || this.options.search)) - { - // Value must be a real Array, not an object - this.input.val(typeof _value == 'object' && _value != null ? jQuery.map(_value,function(value,index){return [value];}) : _value); - this.input.trigger("liszt:updated"); - var self = this; - if (this.getType() == 'listbox' && this.options.value_class != '') - { - var chosen = this.input.next(); - chosen.find('.search-choice-close').each(function(i,v){ - // @ts-ignore - jQuery(v).parent().addClass(self.options.value_class + self.options.select_options[v.rel]['value']); - }); - } - this.value = _value; - return; - } - if(this.input == null) - { - return this.set_multi_value(_value); - } - // Auto-expand multiple if not yet turned on, and value has multiple - if(this.options.expand_multiple_rows && !this.options.multiple && jQuery.isArray(_value) && _value.length > 1) - { - this.set_multiple(true, this.options.expand_multiple_rows); - } - - jQuery("option",this.input).prop("selected", false); - if (typeof _value == "object") - { - for(var i in _value) - { - this.find_option(_value[i]).prop("selected", true); - } - } - else - { - if(_value && this.find_option(_value).prop("selected", true).length == 0) - { - if(this.options.select_options[_value] || - this.options.select_options.filter && - this.options.select_options.filter(function(value) {return value == _value;}) && - !_dont_try_set_options) - { - // Options not set yet? Do that now, which will try again. - return this.set_select_options(this.options.select_options); - } - else if (_dont_try_set_options) - { - this.value = ""; - } - else if (jQuery.isEmptyObject(this.options.select_options)) - { - this.egw().debug("warn", "Can't set value to '%s', widget has no options set",_value, this); - this.value = null; - } - else - { - var debug_value = _value; - if(debug_value === null) debug_value == 'NULL'; - this.egw().debug("warn", "Tried to set value '%s' that isn't an option", debug_value, this); - } - return; - } - } - this.value = _value; - if(this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value) - { - this.input.change(); - } - } - - /** - * Find an option by it's value - * - * Taking care of escaping values correctly eg. EGroupware\Api\Mail\Smtp - * - * @param {string} _value - * @return {array} - */ - find_multi_option(_value) - { - return jQuery("input[value='"+ - (typeof _value === 'string' ? _value.replace(this._escape_value_replace, this._escape_value_with) : _value)+ - "']", this.multiOptions - ); - } - - set_multi_value(_value) - { - jQuery("input",this.multiOptions).prop("checked", false); - if (typeof _value == "object") - { - for(var i in _value) - { - this.find_multi_option(_value[i]).prop("checked", true); - } - } - else - { - if(this.find_multi_option(_value).prop("checked", true).length == 0) - { - var debug_value = _value; - if(debug_value === null) debug_value == 'NULL'; - this.egw().debug("warn", "Tried to set value '%s' that isn't an option", debug_value, this); - } - } - - // Sort selected to the top - if(this.selected_first) - { - this.multiOptions.find("li:has(input:checked)").prependTo(this.multiOptions); - } - this.value = _value; - } - - /** - * Method to check all options of a multi-select, if not all are selected, or none if all where selected - * - * @todo: add an attribute to automatic add a button calling this method - */ - select_all_toggle() - { - var all = jQuery("input",this.multiOptions); - all.prop("checked", jQuery("input:checked",this.multiOptions).length == all.length ? false : true); - } - - /** - * Add a button to toggle between single select and multi select. - * - * @param {number} _rows How many rows for multi-select - */ - set_expand_multiple_rows(_rows) - { - this.options.expand_multiple_rows = _rows; - - var surroundings = this.getSurroundings(); - if(_rows <= 1 && this.expand_button ) - { - // Remove - surroundings.removeDOMNode(this.expand_button.get(0)); - } - else - { - if (!this.expand_button) - { - var button_id = this.getInstanceManager().uniqueId+'_'+this.id.replace(/\./g, '-') + "_expand"; - this.expand_button = jQuery("