Switch calendar owner to web component

This commit is contained in:
nathan 2022-07-05 10:18:12 -06:00
parent 5dc1c74167
commit c6ef3b3a8d
11 changed files with 179 additions and 268 deletions

View File

@ -148,9 +148,11 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
Api\Json\Response::get()->data($label);
return $label;
}
else if($id && is_array($id))
else
{
$labels = Array();
if($id && is_array($id))
{
$labels = array();
foreach($id as $index => $_id)
{
$labels[$_id] = self::format_owner($_id, self::get_owner_label($_id));
@ -158,15 +160,19 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
Api\Json\Response::get()->data($labels);
return $labels;
}
}
}
public static function ajax_search($search_text, $search_options = [])
{
$bo = new calendar_bo();
$query = $_REQUEST['query'];
// Arbitrarily limited to 50 / resource
$options = array('start' => 0, 'num_rows' => 50,
// Filter accounts out of addressbook
'filter' => array('account_id' => null)) +
array_diff_key($_REQUEST, array_flip(array('menuaction','query')));
$search_options;
$results = array();
// Contacts matching accounts the user does not have permission for cause
@ -187,7 +193,7 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
$owngroup_options = $options+array('account_type'=>'owngroups');
$own_groups = Api\Accounts::link_query('',$owngroup_options);
$account_options = $options + array('account_type' => 'both');
$_results += $remove_contacts = Api\Accounts::link_query($query, $account_options);
$_results += $remove_contacts = Api\Accounts::link_query($search_text, $account_options);
if (!empty($_REQUEST['checkgrants']))
{
$grants = (array)$GLOBALS['egw']->acl->get_grants('calendar') + $own_groups;
@ -197,12 +203,12 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
// App provides a custom search function
else if ($data['app'] && $data['search'])
{
$_results = call_user_func_array($data['search'], array($query, $options));
$_results = call_user_func_array($data['search'], array($search_text, $options));
}
// Use standard link registry
else if ($data['app'] && Link::get_registry($data['app'], 'query'))
{
$_results = Link::query($data['app'], $query,$options);
$_results = Link::query($data['app'], $search_text, $options);
}
// There are always special cases
@ -212,8 +218,9 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
// Include mailing lists, but not account groups
$lists = array_filter(
$contacts_obj->get_lists(Api\Acl::READ),
function($element, $index) use($query) {
return $index > 0 && (stripos($element, $query) !== false);
function ($element, $index) use ($search_text)
{
return $index > 0 && (stripos($element, $search_text) !== false);
},
ARRAY_FILTER_USE_BOTH
);
@ -244,12 +251,7 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
}
}
// switch regular JSON response handling off
Api\Json\Request::isJSONRequest(false);
header('Content-Type: application/json; charset=utf-8');
echo json_encode($results);
exit();
Api\Json\Response::get()->data($results);
}
/**
@ -280,10 +282,8 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
}
$type = $data['type'];
// Magicsuggest uses id, not value.
$value = array(
'id' => $type.$id,
'value'=> $type.$id,
'value' => $id,
'label' => $title,
'app' => lang($data['app'])
);
@ -343,3 +343,5 @@ class calendar_owner_etemplate_widget extends Etemplate\Widget\Taglist
return $label;
}
}
Etemplate\Widget::registerWidget(__NAMESPACE__ . '\\calendar_owner_etemplate_widget', array('calendar-owner'));

View File

@ -0,0 +1,92 @@
/*
* Calendar owner widget
*
* @license https://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package calendar
* @subpackage etemplate
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {Et2Select} from "../../api/js/etemplate/Et2Select/Et2Select";
import {css} from "@lion/core";
/**
* Select widget customised for calendar owner, which can be a user
* account or group, or an entry from almost any app, or an email address
*
*/
export class CalendarOwner extends Et2Select
{
static get styles()
{
return [
...super.styles,
css`
/* Larger maximum height before scroll*/
.select__tags {
max-height: 10em;
}
`
];
}
constructor(...args : any[])
{
super(...args);
this.searchUrl = "calendar_owner_etemplate_widget::ajax_search";
this.multiple = true;
}
/**
* Override parent to handle our special additional data types (c#,r#,etc.) when they
* are not available client side.
*
* @param {string|string[]} _value array of selected owners, which can be a number,
* or a number prefixed with one character indicating the resource type.
*/
set_value(_value)
{
super.set_value(_value);
// If parent didn't find a label, label will be the same as ID so we
// can find them that way
let missing_labels = [];
for(var i = 0; i < this.value.length; i++)
{
if(!this.menuItems.find(o => o.value == this.value[i]))
{
missing_labels.push(this.value[i]);
}
}
if(Object.keys(missing_labels).length > 0)
{
// Proper label was not found by parent - ask directly
this.egw().json('calendar_owner_etemplate_widget::ajax_owner', [missing_labels], function(data)
{
for(let owner in data)
{
if(!owner || typeof owner == "undefined")
{
continue;
}
// Put it in the list of options
let index = this.select_options.findIndex(o => o.value == owner);
if(index !== -1)
{
this.select_options[index] = data[owner];
}
else
{
this.select_options.push(data[owner]);
}
}
this.requestUpdate("select_options");
this.updateComplete.then(() => {this.syncItemsFromValue();});
}, this, true, this).sendRequest();
}
}
}
customElements.define("calendar-owner", CalendarOwner);

View File

@ -23,7 +23,6 @@ import {EgwApp, PushData} from "../../api/js/jsapi/egw_app";
import {etemplate2} from "../../api/js/etemplate/etemplate2";
import {et2_container} from "../../api/js/etemplate/et2_core_baseWidget";
import {et2_date} from "../../api/js/etemplate/et2_widget_date";
import {et2_calendar_owner} from "./et2_widget_owner";
import {day, day4, listview, month, planner, week, weekN} from "./View";
import {et2_calendar_view} from "./et2_widget_view";
import {et2_calendar_timegrid} from "./et2_widget_timegrid";
@ -56,6 +55,7 @@ import {nm_action} from "../../api/js/etemplate/et2_extension_nextmatch_actions"
import flatpickr from "flatpickr";
import Sortable from 'sortablejs/modular/sortable.complete.esm.js';
import {tapAndSwipe} from "../../api/js/tapandswipe";
import {CalendarOwner} from "./CalendarOwner";
/**
* UI for calendar
@ -263,7 +263,7 @@ export class CalendarApp extends EgwApp
if(sidebox.length == 0 && egw_getFramework() != null)
{
// Force rollup to load owner widget, it leaves it out otherwise
new et2_calendar_owner(_et2.widgetContainer, {});
new CalendarOwner();
// Force rollup to load planner widget, it leaves it out otherwise
new et2_calendar_planner(_et2.widgetContainer, {});
@ -1482,17 +1482,25 @@ export class CalendarApp extends EgwApp
*/
edit_update_participant(input, widget?)
{
if(typeof widget === 'undefined') widget = input;
if(typeof widget === 'undefined')
{
widget = input;
}
var content = widget.getInstanceManager().getValues(widget.getRoot());
var participant = widget.getRoot().getWidgetById('participant');
if(!participant) return;
var participant = <CalendarOwner>widget.getRoot().getWidgetById('participant');
if(!participant)
{
return;
}
participant.set_autocomplete_params({exec:{
participant.searchOptions = {
exec: {
start: content.start,
end: content.end,
duration: content.duration,
whole_day: content.whole_day,
}});
}
};
}
/**
@ -1620,10 +1628,10 @@ export class CalendarApp extends EgwApp
{
var add = <et2_button>this.et2.getWidgetById('add');
var quantity = <et2_number>this.et2.getWidgetById('quantity');
var participant = <et2_calendar_owner> this.et2.getWidgetById('participant');
var participant = <Et2CalendarOwner>this.et2.getWidgetById('participant');
// array of participants
var value = participant.get_value();
let value = participant.value;
add.set_readonly(value.length <= 0);
@ -3700,27 +3708,27 @@ export class CalendarApp extends EgwApp
{
var found = false;
var option = data.rows.sel_options[field][i];
for(var j in widget.options.select_options)
for(var j in widget.select_options)
{
if(option.value == widget.options.select_options[j].value)
if(option.value == widget.select_options[j].value)
{
widget.options.select_options[j].label = option.label;
widget.select_options[j].label = option.label;
found = true;
break;
}
}
if(!found)
{
if(!widget.options.select_options.push)
if(!widget.select_options.push)
{
widget.options.select_options = [];
widget.select_options = [];
}
widget.options.select_options.push(option);
widget.select_options.push(option);
}
}
var in_progress = app.calendar.state_update_in_progress;
app.calendar.state_update_in_progress = true;
widget.set_select_options(widget.options.select_options);
widget.set_select_options(widget.select_options);
widget.set_value(widget.getValue());
app.calendar.state_update_in_progress = in_progress;
@ -4173,17 +4181,6 @@ export class CalendarApp extends EgwApp
button.parent().css('margin-right',button.outerWidth(true)+2);
button.parent().parent().css('white-space','nowrap');
}
jQuery(window).on('resize.calendar-owner', function() {
var preferred_width = jQuery('#calendar-et2_target').children().first().outerWidth()||0;
if(app.calendar && app.calendar.sidebox_et2)
{
var owner = app.calendar.sidebox_et2.getWidgetById('owner');
if(preferred_width && owner.input.hasClass("chzn-done"))
{
owner.input.next().css('width',preferred_width);
}
}
});
}
/**

View File

@ -961,13 +961,13 @@ export class et2_calendar_event extends et2_valueWidget implements et2_IDetached
let options: any = null;
if (app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner'))
{
options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection();
options = app.calendar.sidebox_et2.getWidgetById('owner').select_options
}
if ((isNaN(parseInt(owner)) || parseInt(owner) < 0) && options && typeof options.find == "function")
{
let resource = options.find(function (element)
{
return element.id == owner;
return element.value == owner;
}) || {};
let matching_participant = typeof resource.resources == "undefined" ?
resource :
@ -1201,7 +1201,7 @@ export class et2_calendar_event extends et2_valueWidget implements et2_IDetached
let options: any = null;
if (app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner'))
{
options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection();
options = app.calendar.sidebox_et2.getWidgetById('owner').select_options;
}
else
{
@ -1227,7 +1227,7 @@ export class et2_calendar_event extends et2_valueWidget implements et2_IDetached
{
var resource = options.find(function(element)
{
return element.id == parent_owner[i];
return element.value == parent_owner[i];
}) || {};
if(resource && resource.resources)
{
@ -1249,7 +1249,7 @@ export class et2_calendar_event extends et2_valueWidget implements et2_IDetached
var resource;
if (options && options.find && (resource = options.find(function (element)
{
return element.id === id;
return element.value === id;
})) && resource.resources)
{
participants = participants.concat(resource.resources);

View File

@ -1,152 +0,0 @@
/*
* Egroupware
*
* @license https://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package calendar
* @subpackage etemplate
* @link https://www.egroupware.org
* @author Nathan Gray
*/
import {et2_register_widget} from "../../api/js/etemplate/et2_core_widget.ts";
import {et2_taglist_email} from "../../api/js/etemplate/et2_widget_taglist";
/**
* Tag list widget customised for calendar owner, which can be a user
* account or group, or an entry from almost any app, or an email address
*
* A cross between auto complete, selectbox and chosen multiselect
*
* Uses MagicSuggest library
* @see http://nicolasbize.github.io/magicsuggest/
* @augments et2_taglist_email
*/
export class et2_calendar_owner extends et2_taglist_email
{
static readonly _attributes = {
"autocomplete_url": {
"default": "calendar_owner_etemplate_widget::ajax_owner"
},
"autocomplete_params": {
"name": "Autocomplete parameters",
"type": "any",
"default": {},
"description": "Extra parameters passed to autocomplete URL. It should be a stringified JSON object."
},
allowFreeEntries: {
"default": false,
ignore: true
},
select_options: {
"type": "any",
"name": "Select options",
// Set to empty object to use selectbox's option finding
"default": {},
"description": "Internally used to hold the select options."
}
};
// Allows sub-widgets to override options to the library
lib_options = {
autoSelect: false,
groupBy: 'app',
minChars: 2,
selectFirst: true,
// This option will also expand when the selection is changed
// via code, which we do not want
//expandOnFocus: true
toggleOnClick: true
};
doLoadingFinished()
{
super.doLoadingFinished();
var widget = this;
// onChange fired when losing focus, which is different from normal
this._oldValue = this.taglist.getValue();
return true;
}
selectionRenderer(item)
{
if(this && this.options && this.options.allowFreeEntries)
{
return super.selectionRenderer(item);
}
else
{
var label = jQuery('<span>').text(item.label);
if (item.class) label.addClass(item.class);
if (typeof item.title != 'undefined') label.attr('title', item.title);
if (typeof item.data != 'undefined') label.attr('data', item.data);
if (typeof item.icon != 'undefined')
{
var wrapper = jQuery('<div>').addClass('et2_taglist_tags_icon_wrapper');
jQuery('<span/>')
.addClass('et2_taglist_tags_icon')
.css({"background-image": "url("+(item.icon.match(/^(http|https|\/)/) ? item.icon : egw.image(item.icon, item.app))+")"})
.appendTo(wrapper);
label.appendTo(wrapper);
return wrapper;
}
return label;
}
}
getValue()
{
if(this.taglist == null) return null;
return this.taglist.getValue();
}
/**
* Override parent to handle our special additional data types (c#,r#,etc.) when they
* are not available client side.
*
* @param {string|string[]} _value array of selected owners, which can be a number,
* or a number prefixed with one character indicating the resource type.
*/
set_value(_value)
{
super.set_value(_value);
// If parent didn't find a label, label will be the same as ID so we
// can find them that way
let missing_labels = [];
for(var i = 0; i < this.options.value.length; i++)
{
var value = this.options.value[i];
if(value.id == value.label)
{
missing_labels.push(value.id);
}
}
if(Object.keys(missing_labels).length > 0)
{
// Proper label was not found by parent - ask directly
egw.json('calendar_owner_etemplate_widget::ajax_owner',[missing_labels],function(data) {
for(let owner in data)
{
if(!owner || typeof owner == "undefined") continue;
let idx = this.options.value.find(element => element.id == owner);
if(idx)
{
idx = jQuery.extend(idx, data[owner]);
}
// Put it in the list of options for next time
this.options.select_options.push(data[owner]);
}
this.set_value(this.options.value);
}, this,true,this).sendRequest();
}
if(this.taglist)
{
this.taglist.clear(true);
this.taglist.addToSelection(this.options.value,true);
}
}
}
et2_register_widget(et2_calendar_owner, ["calendar-owner"]);

View File

@ -408,11 +408,11 @@ export class et2_calendar_planner extends et2_calendar_view implements et2_IDeta
row_labels: function() {
var labels = [];
var already_added = [];
var options = false;
var options = [];
var resource = null;
if(app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner'))
{
options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection();
options = app.calendar.sidebox_et2.getWidgetById('owner').select_options;
}
else
{
@ -424,7 +424,7 @@ export class et2_calendar_planner extends et2_calendar_view implements et2_IDeta
// Handle grouped resources like mailing lists - pull it from sidebox owner
// and expand to their contents
if(options && options.find &&
((resource = options.find(function(element) {return element.id == user;}) || {}) || isNaN(user)))
((resource = options.find(function(element) {return element.value == user;}) || {}) || isNaN(user)))
{
if(resource && resource.resources)
{
@ -2128,7 +2128,7 @@ export class et2_calendar_planner extends et2_calendar_view implements et2_IDeta
if(app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner'))
{
options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection();
options = app.calendar.sidebox_et2.getWidgetById('owner').select_options;
}
else
{
@ -2143,7 +2143,7 @@ export class et2_calendar_planner extends et2_calendar_view implements et2_IDeta
if(options.find &&
((resource = options.find(function (element)
{
return element.id == user;
return element.value == user;
}))))
{
// Members found

View File

@ -440,7 +440,7 @@ export class et2_calendar_view extends et2_valueWidget
let options = false;
if(app.calendar && app.calendar.sidebox_et2 && app.calendar.sidebox_et2.getWidgetById('owner'))
{
options = app.calendar.sidebox_et2.getWidgetById('owner').taglist.getSelection();
options = app.calendar.sidebox_et2.getWidgetById('owner').select_options;
}
else
{
@ -448,7 +448,7 @@ export class et2_calendar_view extends et2_valueWidget
}
if(options && options.find)
{
var found = options.find(function(element) {return element.id == user;}) || {};
var found = options.find(function(element) {return element.value == user;}) || {};
if(found && found.label && found.label !== user)
{
label = found.label;

View File

@ -70,13 +70,6 @@
#calendar-et2_target > div {
position: relative;
}
#calendar-sidebox_owner {
width: 90%;
}
#calendar-sidebox_owner ~ * {
vertical-align: top;
display: inline-block;
}
#calendar-sidebox_cat_id {
margin-bottom: 10px;
}
@ -120,10 +113,6 @@
#calendar-sidebox_date .calendar_calHoliday {
background-color: rgba(103, 159, 210, 0.5);
}
#calendar-sidebox_owner .ms-helper {
padding: 2px;
background-color: white;
}
#calendar-sidebox_date .flatpickr-months {
gap: 0;

View File

@ -57,11 +57,16 @@
<select label="Notify externals" id="notify_externals" span="all" align="right"/>
</row>
<row disabled="@no_add" height="60" class="et2_toolbar">
<calendar-owner id="participant" allowFreeEntries="true" span="4" empty_label="Add new participants or resource" onchange="app.calendar.participantOnChange"/>
<calendar-owner id="participant" allowFreeEntries="true" span="4"
empty_label="Add new participants or resource"
onchange="app.calendar.participantOnChange"/>
<hbox width="100%">
<textbox type="integer" id="quantity" min="1" size="3" statustext="Number of resources to be booked"/>
<textbox type="integer" id="quantity" min="1" size="3"
statustext="Number of resources to be booked"/>
<select class="selectRole" id="role"/>
<button id="add" align="right" image="add" novalidate="1" statustext="Please first select participants on the left and then use plus button to add them" background_image="1" readonly="true" ro_image="add"/>
<button id="add" align="right" image="add" novalidate="1"
statustext="Please first select participants on the left and then use plus button to add them"
background_image="1" readonly="true" ro_image="add"/>
</hbox>
</row>
<row class="th thb">

View File

@ -91,13 +91,6 @@
#calendar-et2_target > div {
position: relative;
}
#calendar-sidebox_owner {
width: 90%;
}
#calendar-sidebox_owner ~ * {
vertical-align: top;
display: inline-block;
}
#calendar-sidebox_cat_id {
margin-bottom: 10px;
}
@ -140,10 +133,6 @@
#calendar-sidebox_date .calendar_calHoliday {
background-color: rgba(103, 159, 210, 0.5);
}
#calendar-sidebox_owner .ms-helper {
padding: 2px;
background-color: white;
}
#calendar-sidebox_date .flatpickr-months {
gap: 0;
background-color: initial;

View File

@ -79,13 +79,6 @@
#calendar-et2_target > div {
position: relative;
}
#calendar-sidebox_owner {
width: 90%;
}
#calendar-sidebox_owner ~ * {
vertical-align: top;
display: inline-block;
}
#calendar-sidebox_cat_id {
margin-bottom: 10px;
}
@ -128,10 +121,6 @@
#calendar-sidebox_date .calendar_calHoliday {
background-color: rgba(103, 159, 210, 0.5);
}
#calendar-sidebox_owner .ms-helper {
padding: 2px;
background-color: white;
}
#calendar-sidebox_date .flatpickr-months {
gap: 0;
background-color: initial;