From 50f4b5cd472319c4482100967fc505696aec5b39 Mon Sep 17 00:00:00 2001 From: nathan Date: Tue, 7 Dec 2021 13:35:25 -0700 Subject: [PATCH] Fix missing calendar participant + button set_readonly() was missing, getWindowTitle() was using .options.value instead of get_value() --- .../Et2InputWidget/Et2InputWidget.ts | 7 + api/js/jsapi/egw_app.ts | 1100 +++++++++-------- 2 files changed, 610 insertions(+), 497 deletions(-) diff --git a/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts b/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts index 35e8d30db0..ad3f5f71a0 100644 --- a/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts +++ b/api/js/etemplate/Et2InputWidget/Et2InputWidget.ts @@ -71,11 +71,13 @@ const Et2InputWidgetMixin = (superclass) => super(...args); } + connectedCallback() { super.connectedCallback(); this.node = this.getInputNode(); } + set_value(new_value) { this.value = new_value; @@ -86,6 +88,11 @@ const Et2InputWidgetMixin = (superclass) => return this.getValue(); } + set_readonly(new_value) + { + this.readonly = this.readOnly = new_value; + } + getValue() { return typeof this.serializedValue !== "undefined" ? this.serializedValue : this.modalValue; diff --git a/api/js/jsapi/egw_app.ts b/api/js/jsapi/egw_app.ts index eadedb8631..8610112f24 100644 --- a/api/js/jsapi/egw_app.ts +++ b/api/js/jsapi/egw_app.ts @@ -18,18 +18,19 @@ import {et2_createWidget} from "../etemplate/et2_core_widget"; import {et2_favorites} from "../etemplate/et2_widget_favorites"; import type {IegwAppLocal} from "./egw_global"; import Sortable from 'sortablejs/modular/sortable.complete.esm.js'; +import {et2_valueWidget} from "../etemplate/et2_core_valueWidget"; /** * Type for push-message */ export interface PushData { - type: "add"|"edit"|"update"|"delete"|"unknown"; - app: string; // app-name, can include a subtype eg. "projectmanager-element" - id: string | number; - acl?: any; // app-specific acl data, eg. the owner, or array of participants - account_id: number; // user that caused the change - [propName:string]: any; // arbitrary more parameters + type : "add" | "edit" | "update" | "delete" | "unknown"; + app : string; // app-name, can include a subtype eg. "projectmanager-element" + id : string | number; + acl? : any; // app-specific acl data, eg. the owner, or array of participants + account_id : number; // user that caused the change + [propName : string] : any; // arbitrary more parameters } /** @@ -67,7 +68,7 @@ export abstract class EgwApp /** * Internal application name - pass this in constructor */ - readonly appname: string; + readonly appname : string; /** * Internal reference to the most recently loaded etemplate2 widget tree @@ -102,23 +103,23 @@ export abstract class EgwApp * * @var {et2_container} */ - et2: et2_container; + et2 : et2_container; /** * Internal reference to egw client-side api object for current app and window * * @var {egw} */ - egw: IegwAppLocal; + egw : IegwAppLocal; - sidebox: JQuery; + sidebox : JQuery; - viewContainer: JQuery; - viewTemplate: JQuery; - et2_view: any; + viewContainer : JQuery; + viewTemplate : JQuery; + et2_view : any; favorite_popup : JQuery | any; - tutorial_initialised: boolean; + tutorial_initialised : boolean; dom_id : string; @@ -129,7 +130,7 @@ export abstract class EgwApp * using the global. We want to be able to access them for observer() & push(), so * we track all instances. */ - static _instances: EgwApp[] = []; + static _instances : EgwApp[] = []; /** * If pushData.acl has fields that can help filter based on ACL grants, list them @@ -137,7 +138,7 @@ export abstract class EgwApp * * @protected */ - protected push_grant_fields: string[]; + protected push_grant_fields : string[]; /** * If pushData.acl has fields that can help filter based on current nextmatch filters, @@ -145,13 +146,13 @@ export abstract class EgwApp * * @protected */ - protected push_filter_fields: string[]; + protected push_filter_fields : string[]; /** * Initialization and setup goes here, but the etemplate2 object * is not yet ready. */ - constructor(appname: string) + constructor(appname : string) { this.appname = appname; this.egw = egw(this.appname, window); @@ -164,7 +165,7 @@ export abstract class EgwApp if(sidebox.length == 0 && egw_getFramework() != null) { var egw_fw = egw_getFramework(); - sidebox= jQuery('#favorite_sidebox_'+this.appname,egw_fw.sidemenuDiv); + sidebox = jQuery('#favorite_sidebox_' + this.appname, egw_fw.sidemenuDiv); } // Make sure we're running in the top window when we init sidebox //@ts-ignore @@ -191,14 +192,14 @@ export abstract class EgwApp destroy(_app) { delete this.et2; - if (this.sidebox) + if(this.sidebox) this.sidebox.off(); delete this.sidebox; if (!_app) delete app[this.appname]; let index = -1; if((index = EgwApp._instances.indexOf(this)) >= 0) { - EgwApp._instances.splice(index,1); + EgwApp._instances.splice(index, 1); } } @@ -219,7 +220,7 @@ export abstract class EgwApp } this.et2 = et2.widgetContainer; this._fix_iFrameScrolling(); - if (this.egw && this.egw.is_popup()) + if(this.egw && this.egw.is_popup()) { this._set_Window_title(); } @@ -273,10 +274,13 @@ export abstract class EgwApp push(pushData : PushData) { // don't care about other apps data, reimplement if your app does care eg. calendar - if (pushData.app !== this.appname) return; + if(pushData.app !== this.appname) + { + return; + } // handle delete, for simple case of uid === "$app::$id" - if (pushData.type === 'delete' && egw.dataHasUID(this.uid(pushData))) + if(pushData.type === 'delete' && egw.dataHasUID(this.uid(pushData))) { egw.refresh('', pushData.app, pushData.id, 'delete'); return; @@ -285,7 +289,7 @@ export abstract class EgwApp // If we know about it and it's an update, just update. // This must be before all ACL checks, as responsible might have changed and entry need to be removed // (server responds then with null / no entry causing the entry to disappear) - if (pushData.type !== "add" && this.egw.dataHasUID(this.uid(pushData)) && this.et2) + if(pushData.type !== "add" && this.egw.dataHasUID(this.uid(pushData)) && this.et2) { return this.et2.getInstanceManager().refresh("", pushData.app, pushData.id, pushData.type); } @@ -333,18 +337,22 @@ export abstract class EgwApp let grants = egw.grants(appname || this.appname); // No grants known - if(!grants) return true; + if(!grants) + { + return true; + } // check user has a grant from owner or something for(let i = 0; i < grant_fields.length; i++) { let grant_field = pushData.acl[grant_fields[i]]; - if(["number","string"].indexOf(typeof grant_field) >=0 && grants[grant_field] !== 'undefined') + if(["number", "string"].indexOf(typeof grant_field) >= 0 && grants[grant_field] !== 'undefined') { // ACL access return true; } - else if(!Object.keys(grants).filter(function(grant_account) { + else if(!Object.keys(grants).filter(function(grant_account) + { return grant_field.indexOf(grant_account) >= 0 || grant_field.indexOf(parseInt(grant_account)).length })) @@ -364,7 +372,7 @@ export abstract class EgwApp * @param filter_fields List of filter field names eg: [owner, cat_id] * @return boolean True if the nextmatch filters might include the entry, false if not */ - _push_field_filter(pushData : PushData, nm : et2_nextmatch, filter_fields: string[]) : boolean + _push_field_filter(pushData : PushData, nm : et2_nextmatch, filter_fields : string[]) : boolean { let filters = {}; for(let i = 0; i < filter_fields.length; i++) @@ -388,7 +396,7 @@ export abstract class EgwApp { field_filter.filter_values.push(val); } - else if (val && typeof val == "object" && !jQuery.isEmptyObject(val)) + else if(val && typeof val == "object" && !jQuery.isEmptyObject(val)) { field_filter.filter_values = field_filter.filter_values.concat(Object.values(val)) } @@ -401,9 +409,9 @@ export abstract class EgwApp if (field_filter.filter_values.length == 0) continue; // acl value is a scalar (not array) --> check contained in filter - if (pushData.acl && typeof pushData.acl[field_filter.col] !== 'object') + if(pushData.acl && typeof pushData.acl[field_filter.col] !== 'object') { - if (field_filter.filter_values.indexOf(pushData.acl[field_filter.col]) < 0) + if(field_filter.filter_values.indexOf(pushData.acl[field_filter.col]) < 0) { return false; } @@ -437,7 +445,8 @@ export abstract class EgwApp * @param _action * @param _senders */ - open(_action, _senders) { + open(_action, _senders) + { var id_app = _senders[0].id.split('::'); egw.open(id_app[1], this.appname); } @@ -462,22 +471,22 @@ export abstract class EgwApp // let user confirm select-all var select_all = _action.getManager().getActionById("select_all"); var confirm_msg = (_elems.length > 1 || select_all && select_all.checked) && - typeof _action.data.confirm_multiple != 'undefined' ? - _action.data.confirm_multiple : _action.data.confirm; + typeof _action.data.confirm_multiple != 'undefined' ? + _action.data.confirm_multiple : _action.data.confirm; - if (typeof confirm_msg != 'undefined') + if(typeof confirm_msg != 'undefined') { var that = this; var action_id = _action.id; - et2_dialog.show_dialog(function(button_id,value) + et2_dialog.show_dialog(function(button_id, value) { - if (button_id != et2_dialog.NO_BUTTON) + if(button_id != et2_dialog.NO_BUTTON) { that._do_action(action_id, _elems); } - }, confirm_msg, egw.lang('Confirmation required'),null, et2_dialog.BUTTONS_YES_NO, et2_dialog.QUESTION_MESSAGE); + }, confirm_msg, egw.lang('Confirmation required'), null, et2_dialog.BUTTONS_YES_NO, et2_dialog.QUESTION_MESSAGE); } - else if (typeof this._do_action == 'function') + else if(typeof this._do_action == 'function') { this._do_action(_action.id, _elems); } @@ -515,19 +524,19 @@ export abstract class EgwApp * @param {string} template template name to check, instead of trying all templates of current app * @return {boolean} false - Returns false to stop event propagation */ - setState(state, template? : string) : string|false|void + setState(state, template? : string) : string | false | void { // State should be an object, not a string, but we'll parse if(typeof state == "string") { - if(state.indexOf('{') != -1 || state =='null') + if(state.indexOf('{') != -1 || state == 'null') { state = JSON.parse(state); } } if(typeof state != "object") { - egw.debug('error', 'Unable to set state to %o, needs to be an object',state); + egw.debug('error', 'Unable to set state to %o, needs to be an object', state); return; } if(state == null) @@ -538,7 +547,7 @@ export abstract class EgwApp // Check for egw.open() parameters if(state.state && state.state.id && state.state.app) { - return egw.open(state.state,undefined,undefined,{},'_self'); + return egw.open(state.state, undefined, undefined, {}, '_self'); } // Try and find a nextmatch widget, and set its filters @@ -546,24 +555,25 @@ export abstract class EgwApp var et2 = template ? etemplate2.getByTemplate(template) : etemplate2.getByApplication(this.appname); for(var i = 0; i < et2.length; i++) { - et2[i].widgetContainer.iterateOver(function(_widget) { + et2[i].widgetContainer.iterateOver(function(_widget) + { // Firefox has trouble with spaces in search if(state.state && state.state.search) state.state.search = unescape(state.state.search); // Apply if(state.state && state.state.sort && state.state.sort.id) { - _widget.sortBy(state.state.sort.id, state.state.sort.asc,false); + _widget.sortBy(state.state.sort.id, state.state.sort.asc, false); } else { // Not using resetSort() to avoid the extra applyFilters() call - _widget.sortBy(undefined,undefined,false); + _widget.sortBy(undefined, undefined, false); } if(state.state && state.state.selectcols) { // Make sure it's a real array, not an object, then set cols - _widget.set_columns(jQuery.extend([],state.state.selectcols)); + _widget.set_columns(jQuery.extend([], state.state.selectcols)); } _widget.applyFilters(state.state || state.filter || {}); nextmatched = true; @@ -572,18 +582,18 @@ export abstract class EgwApp } // 'blank' is the special name for no filters, send that instead of the nice translated name - var safe_name = jQuery.isEmptyObject(state) || jQuery.isEmptyObject(state.state||state.filter) ? 'blank' : state.name.replace(/[^A-Za-z0-9-_]/g, '_'); - var url = '/'+this.appname+'/index.php'; + var safe_name = jQuery.isEmptyObject(state) || jQuery.isEmptyObject(state.state || state.filter) ? 'blank' : state.name.replace(/[^A-Za-z0-9-_]/g, '_'); + var url = '/' + this.appname + '/index.php'; // Try a redirect to list, if app defines a "list" value in registry - if (egw.link_get_registry(this.appname, 'list')) + if(egw.link_get_registry(this.appname, 'list')) { url = egw.link('/index.php', jQuery.extend({'favorite': safe_name}, egw.link_get_registry(this.appname, 'list'))); } // if no list try index value from application - else if (egw.app(this.appname)?.index) + else if(egw.app(this.appname)?.index) { - url = egw.link('/index.php', 'menuaction='+egw.app(this.appname).index+'&favorite='+safe_name); + url = egw.link('/index.php', 'menuaction=' + egw.app(this.appname).index + '&favorite=' + safe_name); } egw.open_link(url, undefined, undefined, this.appname); return false; @@ -601,7 +611,7 @@ export abstract class EgwApp * * @return {object} Application specific map representing the current state */ - getState() : {[propName:string]: any} + getState() : { [propName : string] : any } { var state = {}; @@ -609,7 +619,8 @@ export abstract class EgwApp var et2 = etemplate2.getByApplication(this.appname); for(var i = 0; i < et2.length; i++) { - et2[i].widgetContainer.iterateOver(function(_widget) { + et2[i].widgetContainer.iterateOver(function(_widget) + { state = _widget.getValue(); }, this, et2_nextmatch); } @@ -634,11 +645,12 @@ export abstract class EgwApp // nm row id var rowID = ''; // content to feed to etemplate2 - var content:any = {}; + var content : any = {}; var self = this; - if (id){ + if(id) + { var parts = id.split('::'); rowID = parts[1]; content = egw.dataGetUIDdata(id); @@ -646,16 +658,17 @@ export abstract class EgwApp } // create a new app object with just constructors for our new etemplate2 object - var app = { classes: window.app.classes }; + var app = {classes: window.app.classes}; /* destroy generated etemplate for view mode in DOM*/ - var destroy = function(){ + var destroy = function() + { self.viewContainer.remove(); delete self.viewTemplate; delete self.viewContainer; delete self.et2_view; // we need to reference back into parent context this - for (var v in self) + for(var v in self) { this[v] = self[v]; } @@ -664,55 +677,57 @@ export abstract class EgwApp // view container this.viewContainer = jQuery(document.createElement('div')) - .addClass('et2_mobile_view') - .css({ - "z-index":102, - width:"100%", - height:"100%", - background:"white", - display:'block', - position: 'absolute', - left:0, - bottom:0, - right:0, - overflow:'auto' - }) - .attr('id','popupMainDiv') - .appendTo('body'); + .addClass('et2_mobile_view') + .css({ + "z-index": 102, + width: "100%", + height: "100%", + background: "white", + display: 'block', + position: 'absolute', + left: 0, + bottom: 0, + right: 0, + overflow: 'auto' + }) + .attr('id', 'popupMainDiv') + .appendTo('body'); // close button var close = jQuery(document.createElement('span')) - .addClass('egw_fw_mobile_popup_close loaded') - .click(function(){ - destroy.call(app[self.appname]); - //disable selected actions after close - egw_globalObjectManager.setAllSelected(false); - }) - .appendTo(this.viewContainer); - if (!noEdit) + .addClass('egw_fw_mobile_popup_close loaded') + .click(function() + { + destroy.call(app[self.appname]); + //disable selected actions after close + egw_globalObjectManager.setAllSelected(false); + }) + .appendTo(this.viewContainer); + if(!noEdit) { // edit button var edit = jQuery(document.createElement('span')) - .addClass('mobile-view-editBtn') - .click(function(){ - egw.open(rowID, self.appname); - }) - .appendTo(this.viewContainer); + .addClass('mobile-view-editBtn') + .click(function() + { + egw.open(rowID, self.appname); + }) + .appendTo(this.viewContainer); } // view template main container (content) this.viewTemplate = jQuery(document.createElement('div')) - .attr('id', this.appname+'-view') - .addClass('et2_mobile-view-container popupMainDiv') - .appendTo(this.viewContainer); + .attr('id', this.appname + '-view') + .addClass('et2_mobile-view-container popupMainDiv') + .appendTo(this.viewContainer); - var mobileViewTemplate = (_action.data.mobileViewTemplate ||'edit').split('?'); + var mobileViewTemplate = (_action.data.mobileViewTemplate || 'edit').split('?'); var templateName = mobileViewTemplate[0]; var templateTimestamp = mobileViewTemplate[1]; - var templateURL = egw.webserverUrl+ '/' + this.appname + '/templates/mobile/'+templateName+'.xet'+'?'+templateTimestamp; + var templateURL = egw.webserverUrl + '/' + this.appname + '/templates/mobile/' + templateName + '.xet' + '?' + templateTimestamp; var data = { 'content': content, - 'readonlys': {'__ALL__':true,'link_to':false}, + 'readonlys': {'__ALL__': true, 'link_to': false}, 'currentapp': this.appname, 'langRequire': this.et2.getArrayMgr('langRequire').data, 'sel_options': this.et2.getArrayMgr('sel_options').data, @@ -721,7 +736,7 @@ export abstract class EgwApp }; // etemplate2 object for view - this.et2_view = new etemplate2 (this.viewTemplate[0], ''); + this.et2_view = new etemplate2(this.viewTemplate[0], ''); framework.pushState('view'); if(templateName) { @@ -807,9 +822,10 @@ export abstract class EgwApp sidebox .off() // removed .on("mouse(enter|leave)" (wrapping trash icon), as it stalls delete in IE11 - .on("click.sidebox","div.ui-icon-trash", this, this.delete_favorite) + .on("click.sidebox", "div.ui-icon-trash", this, this.delete_favorite) // need to install a favorite handler, as we switch original one off with .off() - .on('click.sidebox','li[data-id]', this, function(event) { + .on('click.sidebox', 'li[data-id]', this, function(event) + { var li = jQuery(this); li.siblings().removeClass('ui-state-highlight'); @@ -829,17 +845,18 @@ export abstract class EgwApp }) .addClass("ui-helper-clearfix"); - let el = document.getElementById('favorite_sidebox_'+this.appname)?.getElementsByTagName('ul')[0]; - if (el && el instanceof HTMLElement) + let el = document.getElementById('favorite_sidebox_' + this.appname)?.getElementsByTagName('ul')[0]; + if(el && el instanceof HTMLElement) { let sortablejs = Sortable.create(el, { ghostClass: 'ui-fav-sortable-placeholder', draggable: 'li:not([data-id$="add"])', delay: 25, - dataIdAttr:'data-id', - onSort: function(event){ + dataIdAttr: 'data-id', + onSort: function(event) + { let favSortedList = sortablejs.toArray(); - self.egw.set_preference(self.appname,'fav_sort_pref',favSortedList); + self.egw.set_preference(self.appname, 'fav_sort_pref', favSortedList); self._refresh_fav_nm(); } }); @@ -852,7 +869,8 @@ export abstract class EgwApp { jQuery(egw_fw.applications[this.appname].browser.baseDiv) .off('.sidebox') - .on('change.sidebox', function() { + .on('change.sidebox', function() + { self.highlight_favorite(); }); } @@ -874,42 +892,44 @@ export abstract class EgwApp { if(typeof this.favorite_popup == "undefined" || // Create popup if it's not defined yet (this.favorite_popup && typeof this.favorite_popup.group != "undefined" - && !this.favorite_popup.group.isAttached())) // recreate the favorite popup if the group selectbox is not attached (eg. after et2 submit) + && !this.favorite_popup.group.isAttached())) // recreate the favorite popup if the group selectbox is not attached (eg. after et2 submit) { this._create_favorite_popup(); } // Get current state - this.favorite_popup.state = jQuery.extend({}, this.getState(), state||{}); -/* - // Add in extras - for(var extra in this.options.filters) - { - // Don't overwrite what nm has, chances are nm has more up-to-date value - if(typeof this.popup.current_filters == 'undefined') - { - this.popup.current_filters[extra] = this.nextmatch.options.settings[extra]; - } - } + this.favorite_popup.state = jQuery.extend({}, this.getState(), state || {}); + /* + // Add in extras + for(var extra in this.options.filters) + { + // Don't overwrite what nm has, chances are nm has more up-to-date value + if(typeof this.popup.current_filters == 'undefined') + { + this.popup.current_filters[extra] = this.nextmatch.options.settings[extra]; + } + } - // Add in application's settings - if(this.filters != true) - { - for(var i = 0; i < this.filters.length; i++) - { - this.popup.current_filters[this.options.filters[i]] = this.nextmatch.options.settings[this.options.filters[i]]; - } - } -*/ + // Add in application's settings + if(this.filters != true) + { + for(var i = 0; i < this.filters.length; i++) + { + this.popup.current_filters[this.options.filters[i]] = this.nextmatch.options.settings[this.options.filters[i]]; + } + } + */ // Make sure it's an object - deep copy to prevent references in sub-objects (col_filters) - this.favorite_popup.state = jQuery.extend(true,{},this.favorite_popup.state); + this.favorite_popup.state = jQuery.extend(true, {}, this.favorite_popup.state); // Update popup with current set filters (more for debug than user) var filter_list = []; - var add_to_popup = function(arr) { + var add_to_popup = function(arr) + { filter_list.push(""); }; add_to_popup(this.favorite_popup.state); - jQuery("#"+this.appname+"_favorites_popup_state",this.favorite_popup) + jQuery("#" + this.appname + "_favorites_popup_state", this.favorite_popup) .replaceWith( - jQuery(filter_list.join("")).attr("id",this.appname+"_favorites_popup_state") + jQuery(filter_list.join("")).attr("id", this.appname + "_favorites_popup_state") ); - jQuery("#"+this.appname+"_favorites_popup_state",this.favorite_popup) + jQuery("#" + this.appname + "_favorites_popup_state", this.favorite_popup) .hide() .siblings(".ui-icon-circle-plus") .removeClass("ui-icon-circle-minus"); @@ -938,7 +958,7 @@ export abstract class EgwApp * Update favorite items in nm fav. menu * */ - _refresh_fav_nm () + _refresh_fav_nm() { var self = this; @@ -947,7 +967,8 @@ export abstract class EgwApp var et2 = etemplate2.getByApplication(self.appname); for(var i = 0; i < et2.length; i++) { - et2[i].widgetContainer.iterateOver(function(_widget) { + et2[i].widgetContainer.iterateOver(function(_widget) + { _widget.stored_filters = _widget.load_favorites(self.appname); _widget.init_filters(_widget); }, self, et2_favorites); @@ -955,7 +976,7 @@ export abstract class EgwApp } else { - throw new Error ("_refresh_fav_nm():Either et2 is not ready/ not there yet. Make sure that etemplate2 is ready before call this method."); + throw new Error("_refresh_fav_nm():Either et2 is not ready/ not there yet. Make sure that etemplate2 is ready before call this method."); } } @@ -975,23 +996,24 @@ export abstract class EgwApp } // Create popup - this.favorite_popup = jQuery('
\ + this.favorite_popup = jQuery('
\
\ -