remove generated .js files from git

This commit is contained in:
Ralf Becker 2021-07-01 08:38:08 +02:00
parent 2a02ce0b46
commit 05405b955a
98 changed files with 0 additions and 51371 deletions

View File

@ -1,218 +0,0 @@
/**
* EGroupware - Addressbook - Javascript UI
*
* @link: https://www.egroupware.org
* @package addressbook
* @author Hadi Nategh <hn-AT-stylite.de>
* @author Ralf Becker <rb-AT-egroupware.org>
* @copyright (c) 2008-21 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
/*egw:uses
/api/js/jsapi/egw_app.js
*/
import { EgwApp } from '../../api/js/jsapi/egw_app';
import { et2_nextmatch } from "../../api/js/etemplate/et2_extension_nextmatch";
import { egw } from "../../api/js/jsapi/egw_global.js";
/**
* UI for Addressbook CRM view
*
*/
export class CRMView extends EgwApp {
/**
* Constructor
*
* CRM is part of addressbook
*/
constructor() {
// call parent
super('addressbook');
// List ID
this.list_id = "";
// Reference to the list
this.nm = null;
// Which addressbook contact id(s) we are showing entries for
this.contact_ids = [];
// Private js for the list
this.app_obj = null;
// Push data key(s) to check for our contact ID in the entry's ACL data
this.push_contact_ids = ["contact_id"];
}
/**
* Destructor
*/
destroy(_app) {
this.nm = null;
if (this.app_obj != null) {
this.app_obj.destroy(_app);
}
// call parent
super.destroy(_app);
}
/**
* A template from an app is ready, looks like it might be a CRM view.
* Check it, get CRM ready, and bind accordingly
*
* @param et2
* @param appname
*/
static view_ready(et2, app_obj) {
// Check to see if the template is for a CRM view
if (et2.app == app_obj.appname) {
return CRMView.reconnect(app_obj);
}
// Make sure object is there, etemplate2 will pick it up and call our et2_ready
let crm = undefined;
// @ts-ignore
if (typeof et2.app_obj.crm == "undefined" && app.classes.crm) {
// @ts-ignore
crm = et2.app_obj.crm = new app.classes.crm();
}
if (typeof crm == "undefined") {
egw.debug("error", "CRMView object is missing");
return false;
}
// We can set this now
crm.set_view_obj(app_obj);
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. The associated app [is supposed to have] already called its own et2_ready(),
* so any changes done here will override the app.
*
* @param {etemplate2} et2 newly ready object
* @param {string} name Template name
*/
et2_ready(et2, name) {
// call parent
super.et2_ready(et2, name);
}
/**
* Our CRM has become disconnected from its list, probably because something submitted.
* Find it, and get things working again.
*
* @param app_obj
*/
static reconnect(app_obj) {
var _a;
// Check
let contact_ids = app_obj.et2.getArrayMgr("content").getEntry("action_id") || "";
if (!contact_ids)
return;
for (let existing_app of EgwApp._instances) {
if (existing_app instanceof CRMView && existing_app.list_id == app_obj.et2.getInstanceManager().uniqueId) {
// List was reloaded. Rebind.
existing_app.app_obj.destroy(existing_app.app_obj.appname);
if (!((_a = existing_app.nm) === null || _a === void 0 ? void 0 : _a.getParent())) {
try {
// This will probably not die cleanly, we had a reference when it was destroyed
existing_app.nm.destroy();
}
catch (e) { }
}
return existing_app.set_view_obj(app_obj);
}
}
}
/**
* Set the associated private app JS
* We try and pull the needed info here
*/
set_view_obj(app_obj) {
this.app_obj = app_obj;
// Make sure object is there, etemplate2 will pick it up and call our et2_ready
app_obj.et2.getInstanceManager().app_obj.crm = this;
// Make _sure_ we get notified if the list is removed (actions, refresh) - this is not always a full
// destruction
jQuery(app_obj.et2.getDOMNode()).on('clear', function () {
this.nm = null;
}.bind(this));
// For easy reference later
this.list_id = app_obj.et2.getInstanceManager().uniqueId;
this.nm = app_obj.et2.getDOMWidgetById('nm');
let contact_ids = app_obj.et2.getArrayMgr("content").getEntry("action_id") || "";
if (typeof contact_ids == "string") {
contact_ids = contact_ids.split(",");
}
this.set_contact_ids(contact_ids);
// Override the push handler
this._override_push(app_obj);
}
/**
* Set or change which contact IDs we are showing entries for
*/
set_contact_ids(ids) {
this.contact_ids = ids;
let filter = { action_id: this.contact_ids };
if (this.nm !== null) {
this.nm.applyFilters(filter);
}
}
/**
* Handle a push notification about entry changes from the websocket
*
* @param pushData
* @param {string} pushData.app application name
* @param {(string|number)} pushData.id id of entry to refresh or null
* @param {string} pushData.type either 'update', 'edit', 'delete', 'add' or null
* - update: request just modified data from given rows. Sorting is not considered,
* so if the sort field is changed, the row will not be moved.
* - edit: rows changed, but sorting may be affected. Requires full reload.
* - delete: just delete the given rows clientside (no server interaction neccessary)
* - add: ask server for data, add in intelligently
* @param {object|null} pushData.acl Extra data for determining relevance. eg: owner or responsible to decide if update is necessary
* @param {number} pushData.account_id User that caused the notification
*/
push(pushData) {
if (pushData.app !== this.app_obj.appname || !this.nm)
return;
// If we know about it and it's an update, just update.
// This must be before all ACL checks, as contact might have changed and entry needs 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))) {
// Check to see if it's in OUR nextmatch
let uid = this.uid(pushData);
let known = Object.values(this.nm.controller._indexMap).filter(function (row) { return row.uid == uid; });
let type = pushData.type;
if (known && known.length > 0) {
if (!this.id_check(pushData.acl)) {
// Was ours, not anymore, and we know this now - no server needed. Just remove from nm.
type = et2_nextmatch.DELETE;
}
return this.nm.refresh(pushData.id, type);
}
}
if (this.id_check(pushData.acl)) {
return this._app_obj_push(pushData);
}
}
/**
* Check to see if the given entry is "ours"
*
* @param entry
*/
id_check(entry) {
// Check if it's for one of our contacts
for (let field of this.push_contact_ids) {
if (entry && entry[field]) {
let val = typeof entry[field] == "string" ? [entry[field]] : entry[field];
if (val.filter(v => this.contact_ids.indexOf(v) >= 0).length > 0) {
return true;
}
}
}
return false;
}
/**
* Override the list's push handler to do nothing, we'll call it if we want it.
*
* @param app_obj
* @private
*/
_override_push(app_obj) {
this._app_obj_push = app_obj.push.bind(app_obj);
app_obj.push = function (pushData) { return false; };
}
}
app.classes.crm = CRMView;
//# sourceMappingURL=CRM.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,751 +0,0 @@
/**
* EGroupware eTemplate2 - JS DOM Widget class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
et2_core_interfaces;
et2_core_widget;
/api/js/egw_action/egw_action.js;
*/
import { ClassWithAttributes } from './et2_core_inheritance';
import { et2_IDOMNode } from "./et2_core_interfaces";
import { et2_hasChild, et2_no_init } from "./et2_core_common";
import { et2_widget } from "./et2_core_widget";
import { egw_getActionManager, egwActionObject, egwActionObjectInterface, egw_getAppObjectManager, egw_getObjectManager } from '../egw_action/egw_action.js';
import { EGW_AI_DRAG_OVER, EGW_AI_DRAG_OUT } from '../egw_action/egw_action_constants.js';
import { egw } from "../jsapi/egw_global";
/**
* Abstract widget class which can be inserted into the DOM. All widget classes
* deriving from this class have to care about implementing the "getDOMNode"
* function which has to return the DOM-Node.
*
* @augments et2_widget
*/
export class et2_DOMWidget extends et2_widget {
/**
* When the DOMWidget is initialized, it grabs the DOM-Node of the parent
* object (if available) and passes it to its own "createDOMNode" function
*
* @memberOf et2_DOMWidget
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {}));
this.parentNode = null;
this.disabled = false;
this._attachSet = {
"node": null,
"parent": null
};
this._surroundingsMgr = null;
}
/**
* Detatches the node from the DOM and clears all references to the parent
* node or the dom node of this widget.
*/
destroy() {
this.detachFromDOM();
this.parentNode = null;
this._attachSet = {};
if (this._actionManager) {
var app_om = egw_getObjectManager(this.egw().getAppName(), false, 1);
if (app_om) {
var om = app_om.getObjectById(this.id);
if (om)
om.remove();
}
this._actionManager.remove();
this._actionManager = null;
}
if (this._surroundingsMgr) {
this._surroundingsMgr.destroy();
this._surroundingsMgr = null;
}
super.destroy();
}
/**
* Attaches the container node of this widget to the DOM-Tree
*/
doLoadingFinished() {
// Check whether the parent implements the et2_IDOMNode interface. If
// yes, grab the DOM node and create our own.
if (this.getParent() && this.getParent().implements(et2_IDOMNode)) {
if (this.options.parent_node) {
this.set_parent_node(this.options.parent_node);
}
else {
this.setParentDOMNode(this.getParent().getDOMNode(this));
}
}
return true;
}
/**
* Detaches the widget from the DOM tree, if it had been attached to the
* DOM-Tree using the attachToDOM method.
*/
detachFromDOM() {
if (this._attachSet && this._attachSet.node && this._attachSet.parent) {
// Remove the current node from the parent node
try {
this._attachSet.parent.removeChild(this._attachSet.node);
}
catch (e) {
// Don't throw a DOM error if the node wasn't in the parent
}
// Reset the "attachSet"
this._attachSet = {
"node": null,
"parent": null
};
return true;
}
return false;
}
/**
* Attaches the widget to the DOM tree. Fails if the widget is already
* attached to the tree or no parent node or no node for this widget is
* defined.
*/
attachToDOM() {
// Attach the DOM node of this widget (if existing) to the new parent
var node = this.getDOMNode(this);
if (node && this.parentNode &&
(!this._attachSet || this._attachSet && node != this._attachSet.node ||
this.parentNode != this._attachSet.parent)) {
// If the surroundings manager exists, surround the DOM-Node of this
// widget with the DOM-Nodes inside the surroundings manager.
if (this._surroundingsMgr) {
node = this._surroundingsMgr.getDOMNode(node);
}
// Append this node at its index
var idx = this.getDOMIndex();
if (idx < 0 || idx >= this.parentNode.childNodes.length - 1) {
this.parentNode.appendChild(node);
}
else {
this.parentNode.insertBefore(node, this.parentNode.childNodes[idx]);
}
// Store the currently attached nodes
this._attachSet = {
"node": node,
"parent": this.parentNode
};
return true;
}
return false;
}
/**
* Inserts a child at the given index.
*
* @param _node is the node which should be added. It has to be an instance
* of et2_widget
* @param _idx is the position at which the element should be added.
*/
insertChild(_node, _idx) {
super.insertChild(_node, _idx);
if (_node.instanceOf && _node.instanceOf(et2_DOMWidget) && typeof _node.hasOwnProperty('parentNode') && this.getDOMNode(this)) {
try {
_node.setParentDOMNode(this.getDOMNode(_node));
}
catch (_a) {
// Not ready to be added, usually due to construction order,
// will probably try again in doLoadingFinished()
}
}
// _node is actually a Web Component
else if (_node instanceof Element) {
this.getDOMNode().append(_node);
}
}
isAttached() {
return this.parentNode != null;
}
getSurroundings() {
if (!this._surroundingsMgr) {
this._surroundingsMgr = new et2_surroundingsMgr(this);
}
return this._surroundingsMgr;
}
/**
* Get data for the tab this widget is on.
*
* Will return null if the widget is not on a tab or tab data containing
* - id
* - label
* - widget (top level widget)
* - contentDiv (jQuery object for the div the tab content is in)
*
* @returns {Object|null} Data for tab the widget is on
*/
get_tab_info() {
var parent = this;
do {
parent = parent.getParent();
} while (parent !== this.getRoot() && parent.getType() !== 'tabbox');
// No tab
if (parent === this.getRoot()) {
return null;
}
let tabbox = parent;
// Find the tab index
for (var i = 0; i < tabbox.tabData.length; i++) {
// Find the tab by DOM heritage
// @ts-ignore
if (tabbox.tabData[i].contentDiv.has(this.div).length) {
return tabbox.tabData[i];
}
}
// On a tab, but we couldn't find it by DOM nodes Maybe tab template is
// not loaded yet. Try checking IDs.
var template = this;
do {
template = template.getParent();
// @ts-ignore
} while (template !== tabbox && template.getType() !== 'template');
for (var i = tabbox.tabData.length - 1; i >= 0; i--) {
if (template && template.id && template.id === tabbox.tabData[i].id) {
return tabbox.tabData[i];
}
}
// Fallback
let fallback = this.getParent();
if (typeof fallback.get_tab_info === 'function') {
return fallback.get_tab_info();
}
return null;
}
/**
* Set the parent DOM node of this element. Takes a wider variety of types
* than setParentDOMNode(), and matches the set_<attribute> naming convention.
*
* @param _node String|DOMNode DOM node to contain the widget, or the ID of the DOM node.
*/
set_parent_node(_node) {
if (typeof _node == "string") {
var parent = jQuery('#' + _node);
if (parent.length === 0 && window.parent) {
// Could not find it, try again with wider context
// (in case there's an iframe in admin, for example)
parent = jQuery('#' + _node, window.parent.document);
}
if (parent.length === 0) {
this.egw().debug('warn', 'Unable to find DOM parent node with ID "%s" for widget %o.', _node, this);
}
else {
this.setParentDOMNode(parent.get(0));
}
}
else {
this.setParentDOMNode(_node);
}
}
/**
* Set the parent DOM node of this element. If another parent node is already
* set, this widget removes itself from the DOM tree
*
* @param _node
*/
setParentDOMNode(_node) {
if (_node != this.parentNode) {
// Detatch this element from the DOM tree
this.detachFromDOM();
this.parentNode = _node;
// And attatch the element to the DOM tree
this.attachToDOM();
}
}
/**
* Returns the parent node.
*/
getParentDOMNode() {
return this.parentNode;
}
/**
* Returns the index of this element in the DOM tree
*/
getDOMIndex() {
if (this.getParent()) {
var idx = 0;
var children = this.getParent().getChildren();
if (children && children.indexOf)
return children.indexOf(this);
egw.debug('warn', 'No Array.indexOf(), falling back to looping. ');
for (var i = 0; i < children.length; i++) {
if (children[i] == this) {
return idx;
}
else if (children[i].isInTree()) {
idx++;
}
}
}
return -1;
}
/**
* Sets the id of the DOM-Node.
*
* DOM id's have dots "." replaced with dashes "-"
*
* @param {string} _value id to set
*/
set_id(_value) {
this.id = _value;
this.dom_id = _value ? this.getInstanceManager().uniqueId + '_' + _value.replace(/\./g, '-') : _value;
var node = this.getDOMNode(this);
if (node) {
if (_value != "") {
node.setAttribute("id", this.dom_id);
}
else {
node.removeAttribute("id");
}
}
}
set_disabled(_value) {
var node = this._surroundingsMgr != null ? this._surroundingsMgr.getDOMNode(this.getDOMNode(this)) : this.getDOMNode(this);
if (node && this.disabled != _value) {
this.disabled = _value;
if (_value) {
jQuery(node).hide();
}
else {
jQuery(node).show();
}
}
}
set_width(_value) {
this.width = _value;
var node = this.getDOMNode(this);
if (node) {
jQuery(node).css("width", _value);
}
}
set_height(_value) {
this.height = _value;
var node = this.getDOMNode(this);
if (node) {
jQuery(node).css("height", _value);
}
}
set_class(_value) {
var node = this.getDOMNode(this);
if (node) {
if (this["class"]) {
jQuery(node).removeClass(this["class"]);
}
jQuery(node).addClass(_value);
}
this["class"] = _value;
}
set_overflow(_value) {
this.overflow = _value;
var node = this.getDOMNode(this);
if (node) {
jQuery(node).css("overflow", _value);
}
}
set_data(_value) {
var node = this.getDOMNode(this);
if (node && _value) {
var pairs = _value.split(/,/g);
for (var i = 0; i < pairs.length; ++i) {
var name_value = pairs[i].split(':');
jQuery(node).attr('data-' + name_value[0], name_value[1]);
}
}
}
set_background(_value) {
var node = this.getDOMNode(this);
var values = '';
if (_value && node) {
values = _value.split(',');
jQuery(node).css({
"background-image": 'url("' + values[0] + '")',
"background-position-x": values[1],
"background-position-y": values[2],
"background-scale": values[3]
});
}
}
/**
* Set Actions on the widget
*
* Each action is defined as an object:
*
* move: {
* type: "drop",
* acceptedTypes: "mail",
* icon: "move",
* caption: "Move to"
* onExecute: javascript:mail_move"
* }
*
* This will turn the widget into a drop target for "mail" drag types. When "mail" drag types are dropped,
* the global function mail_move(egwAction action, egwActionObject sender) will be called. The ID of the
* dragged "mail" will be in sender.id, some information about the sender will be in sender.context. The
* etemplate2 widget involved can typically be found in action.parent.data.widget, so your handler
* can operate in the widget context easily. The location varies depending on your action though. It
* might be action.parent.parent.data.widget
*
* To customise how the actions are handled for a particular widget, override _link_actions(). It handles
* the more widget-specific parts.
*
* @param {object} actions {ID: {attributes..}+} map of egw action information
* @see api/src/Etemplate/Widget/Nextmatch.php egw_actions() method
*/
set_actions(actions) {
if (this.id == "" || typeof this.id == "undefined") {
this.egw().debug("warn", "Widget should have an ID if you want actions", this);
return;
}
// Initialize the action manager and add some actions to it
// Only look 1 level deep
var gam = egw_getActionManager(this.egw().appName, true, 1);
if (typeof this._actionManager != "object") {
if (gam.getActionById(this.getInstanceManager().uniqueId, 1) !== null) {
gam = gam.getActionById(this.getInstanceManager().uniqueId, 1);
}
if (gam.getActionById(this.id, 1) != null) {
this._actionManager = gam.getActionById(this.id, 1);
}
else {
this._actionManager = gam.addAction("actionManager", this.id);
}
}
this._actionManager.updateActions(actions, this.egw().appName);
if (this.options.default_execute)
this._actionManager.setDefaultExecute(this.options.default_execute);
// Put a reference to the widget into the action stuff, so we can
// easily get back to widget context from the action handler
this._actionManager.data = { widget: this };
// Link the actions to the DOM
this._link_actions(actions);
}
set_default_execute(_default_execute) {
this.options.default_execute = _default_execute;
if (this._actionManager)
this._actionManager.setDefaultExecute(null, _default_execute);
}
/**
* Get all action-links / id's of 1.-level actions from a given action object
*
* This can be overwritten to not allow all actions, by not returning them here.
*
* @param actions
* @returns {Array}
*/
_get_action_links(actions) {
var action_links = [];
for (var i in actions) {
var action = actions[i];
action_links.push(typeof action.id != 'undefined' ? action.id : i);
}
return action_links;
}
/**
* Link the actions to the DOM nodes / widget bits.
*
* @param {object} actions {ID: {attributes..}+} map of egw action information
*/
_link_actions(actions) {
// Get the top level element for the tree
var objectManager = egw_getAppObjectManager(true);
var widget_object = objectManager.getObjectById(this.id);
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = objectManager.insertObject(false, new egwActionObject(this.id, objectManager, (new et2_action_object_impl(this)).getAOI(), this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager));
}
else {
widget_object.setAOI((new et2_action_object_impl(this, this.getDOMNode())).getAOI());
}
// Delete all old objects
widget_object.clear();
widget_object.unregisterActions();
// Go over the widget & add links - this is where we decide which actions are
// 'allowed' for this widget at this time
var action_links = this._get_action_links(actions);
widget_object.updateActionLinks(action_links);
}
}
et2_DOMWidget._attributes = {
"disabled": {
"name": "Disabled",
"type": "boolean",
"description": "Defines whether this widget is visible. Not to be confused with an input widget's HTML attribute 'disabled'.",
"default": false
},
"width": {
"name": "Width",
"type": "dimension",
"default": et2_no_init,
"description": "Width of the element in pixels, percentage or 'auto'"
},
"height": {
"name": "Height",
"type": "dimension",
"default": et2_no_init,
"description": "Height of the element in pixels, percentage or 'auto'"
},
"class": {
"name": "CSS Class",
"type": "string",
"default": et2_no_init,
"description": "CSS Class which is applied to the dom element of this node"
},
"overflow": {
"name": "Overflow",
"type": "string",
"default": et2_no_init,
"description": "If set, the css-overflow attribute is set to that value"
},
"parent_node": {
"name": "DOM parent",
"type": "string",
"default": et2_no_init,
"description": "Insert into the target DOM node instead of the normal location"
},
"actions": {
"name": "Actions list",
"type": "any",
"default": et2_no_init,
"description": "List of egw actions that can be done on the widget. This includes context menu, drag and drop. TODO: Link to action documentation"
},
default_execute: {
name: "Default onExecute for actions",
type: "js",
default: et2_no_init,
description: "Set default onExecute javascript method for action not specifying their own"
},
resize_ratio: {
name: "Resize height of the widget on callback resize",
type: "string",
default: '',
description: "Allow Resize height of the widget based on exess height and given ratio"
},
data: {
name: "comma-separated name:value pairs set as data attributes on DOM node",
type: "string",
default: '',
description: 'data="mime:${row}[mime]" would generate data-mime="..." in DOM, eg. to use it in CSS on a parent'
},
background: {
name: "Add background image",
type: "string",
default: '',
description: "Sets background image, left, right and scale on DOM",
}
};
/**
* The surroundings manager class allows to append or prepend elements around
* an widget node.
*/
export class et2_surroundingsMgr extends ClassWithAttributes {
/**
* Constructor
*
* @memberOf et2_surroundingsMgr
* @param _widget
*/
constructor(_widget) {
super();
this._widgetContainer = null;
this._widgetSurroundings = [];
this._widgetPlaceholder = null;
this._widgetNode = null;
this._ownPlaceholder = true;
this._surroundingsUpdated = false;
this.widget = _widget;
}
destroy() {
this._widgetContainer = null;
this._widgetSurroundings = null;
this._widgetPlaceholder = null;
this._widgetNode = null;
}
prependDOMNode(_node) {
this._widgetSurroundings.unshift(_node);
this._surroundingsUpdated = true;
}
appendDOMNode(_node) {
// Append an placeholder first if none is existing yet
if (this._ownPlaceholder && this._widgetPlaceholder == null) {
this._widgetPlaceholder = document.createElement("span");
this._widgetSurroundings.push(this._widgetPlaceholder);
}
// Append the given node
this._widgetSurroundings.push(_node);
this._surroundingsUpdated = true;
}
insertDOMNode(_node) {
if (!this._ownPlaceholder || this._widgetPlaceholder == null) {
this.appendDOMNode(_node);
return;
}
// Get the index of the widget placeholder and delete it, insert the
// given node instead
var idx = this._widgetSurroundings.indexOf(this._widgetPlaceholder);
this._widgetSurroundings.splice(idx, 1, _node);
// Delete the reference to the own placeholder
this._widgetPlaceholder = null;
this._ownPlaceholder = false;
}
removeDOMNode(_node) {
for (var i = 0; this._widgetSurroundings && i < this._widgetSurroundings.length; i++) {
if (this._widgetSurroundings[i] == _node) {
this._widgetSurroundings.splice(i, 1);
this._surroundingsUpdated = true;
break;
}
}
}
setWidgetPlaceholder(_node) {
if (_node != this._widgetPlaceholder) {
if (_node != null && this._ownPlaceholder && this._widgetPlaceholder != null) {
// Delete the current placeholder which was created by the
// widget itself
var idx = this._widgetSurroundings.indexOf(this._widgetPlaceholder);
this._widgetSurroundings.splice(idx, 1);
// Delete any reference to the own placeholder and set the
// _ownPlaceholder flag to false
this._widgetPlaceholder = null;
this._ownPlaceholder = false;
}
this._ownPlaceholder = (_node == null);
this._widgetPlaceholder = _node;
this._surroundingsUpdated = true;
}
}
_rebuildContainer() {
// Return if there has been no change in the "surroundings-data"
if (!this._surroundingsUpdated) {
return false;
}
// Build the widget container
if (this._widgetSurroundings.length > 0) {
// Check whether the widgetPlaceholder is really inside the DOM-Tree
var hasPlaceholder = et2_hasChild(this._widgetSurroundings, this._widgetPlaceholder);
// If not, append another widget placeholder
if (!hasPlaceholder) {
this._widgetPlaceholder = document.createElement("span");
this._widgetSurroundings.push(this._widgetPlaceholder);
this._ownPlaceholder = true;
}
// If the surroundings array only contains one element, set this one
// as the widget container
if (this._widgetSurroundings.length == 1) {
if (this._widgetSurroundings[0] == this._widgetPlaceholder) {
this._widgetContainer = null;
}
else {
this._widgetContainer = this._widgetSurroundings[0];
}
}
else {
// Create an outer "span" as widgetContainer
this._widgetContainer = document.createElement("span");
// Append the children inside the widgetSurroundings array to
// the widget container
for (var i = 0; i < this._widgetSurroundings.length; i++) {
this._widgetContainer.appendChild(this._widgetSurroundings[i]);
}
}
}
else {
this._widgetContainer = null;
this._widgetPlaceholder = null;
}
this._surroundingsUpdated = false;
return true;
}
update() {
if (this._surroundingsUpdated) {
var attached = this.widget ? this.widget.isAttached() : false;
// Reattach the widget - this will call the "getDOMNode" function
// and trigger the _rebuildContainer function.
if (attached && this.widget) {
this.widget.detachFromDOM();
this.widget.attachToDOM();
}
}
}
getDOMNode(_widgetNode) {
// Update the whole widgetContainer if this is not the first time this
// function has been called but the widget node has changed.
if (this._widgetNode != null && this._widgetNode != _widgetNode) {
this._surroundingsUpdated = true;
}
// Copy a reference to the given node
this._widgetNode = _widgetNode;
// Build the container if it didn't exist yet.
var updated = this._rebuildContainer();
// Return the widget node itself if there are no surroundings arround
// it
if (this._widgetContainer == null) {
return _widgetNode;
}
// Replace the widgetPlaceholder with the given widget node if the
// widgetContainer has been updated
if (updated) {
this._widgetPlaceholder.parentNode.replaceChild(_widgetNode, this._widgetPlaceholder);
if (!this._ownPlaceholder) {
this._widgetPlaceholder = _widgetNode;
}
}
// Return the widget container
return this._widgetContainer;
}
getWidgetSurroundings() {
return this._widgetSurroundings;
}
}
/**
* The egw_action system requires an egwActionObjectInterface Interface implementation
* to tie actions to DOM nodes. This one can be used by any widget.
*
* The class extension is different than the widgets
*
* @param {et2_DOMWidget} widget
* @param {Object} node
*
*/
export class et2_action_object_impl {
constructor(_widget, _node) {
var widget = _widget;
var objectNode = _node;
this.aoi = new egwActionObjectInterface();
this.aoi.getWidget = function () {
return widget;
};
this.aoi.doGetDOMNode = function () {
return objectNode ? objectNode : widget.getDOMNode();
};
// _outerCall may be used to determine, whether the state change has been
// evoked from the outside and the stateChangeCallback has to be called
// or not.
this.aoi.doSetState = function (_state, _outerCall) {
};
// The doTiggerEvent function may be overritten by the aoi if it wants to
// support certain action implementation specific events like EGW_AI_DRAG_OVER
// or EGW_AI_DRAG_OUT
this.aoi.doTriggerEvent = function (_event, _data) {
switch (_event) {
case EGW_AI_DRAG_OVER:
jQuery(this.node).addClass("ui-state-active");
break;
case EGW_AI_DRAG_OUT:
jQuery(this.node).removeClass("ui-state-active");
break;
}
};
}
getAOI() {
return this.aoi;
}
}
//# sourceMappingURL=et2_core_DOMWidget.js.map

View File

@ -1,406 +0,0 @@
/**
* EGroupware eTemplate2 - JS content array manager
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
et2_core_common;
egw_inheritance;
et2_core_phpExpressionCompiler;
*/
import { et2_evalBool } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
import { et2_compilePHPExpression } from "./et2_core_phpExpressionCompiler";
/**
* Manage access to various template customisation arrays passed to etemplate->exec().
*
* This manages access to content, modifications and readonlys arrays
*/
export class et2_arrayMgr {
/**
* Constructor
*
* @memberOf et2_arrayMgr
* @param _data
* @param _parentMgr
*/
constructor(_data = {}, _parentMgr) {
this.splitIds = true;
// Holds information about the current perspective
this.perspectiveData = {
"owner": null,
"key": null,
"row": null
};
this.readOnly = false;
if (typeof _parentMgr == "undefined") {
_parentMgr = null;
}
// Copy the parent manager which is needed to access relative data when
// being in a relative perspective of the manager
this._parentMgr = _parentMgr;
// Hold a reference to the data
if (typeof _data == "undefined" || !_data) {
egw.debug("log", "No data passed to content array manager. Probably a mismatch between template namespaces and data.");
_data = {};
}
// Expand sub-arrays that have been shmushed together, so further perspectives work
// Shmushed keys look like: ${row}[info_cat]
// Expanded: ${row}: Object{info_cat: ..value}
if (this.splitIds) {
// For each index, we need a key: {..} sub array
for (let key in _data) {
// Split up indexes
const indexes = key.replace(/&#x5B;/g, "[").split('[');
// Put data in the proper place
if (indexes.length > 1) {
const value = _data[key];
let target = _data;
for (let i = 0; i < indexes.length; i++) {
indexes[i] = indexes[i].replace(/&#x5D;/g, '').replace(']', '');
if (typeof target[indexes[i]] == "undefined" || target[indexes[i]] === null) {
target[indexes[i]] = i == indexes.length - 1 ? value : {};
}
target = target[indexes[i]];
}
delete _data[key];
}
}
}
this.data = _data;
}
/**
* Returns the root content array manager object
*/
getRoot() {
if (this._parentMgr != null) {
return this._parentMgr.getRoot();
}
return this;
}
getParentMgr() {
return this._parentMgr;
}
getPerspectiveData() {
return this.perspectiveData;
}
setPerspectiveData(new_perspective) {
this.perspectiveData = new_perspective;
}
setRow(new_row) {
this.perspectiveData.row = new_row;
}
/**
* Explodes compound keys (eg IDs) into a list of namespaces
* This uses no internal values, just expands
*
* eg:
* a[b][c] => [a,b,c]
* col_filter[tr_tracker] => [col_filter, tr_tracker]
*
* @param {string} _key
*
* @return {string[]}
*/
explodeKey(_key) {
if (!_key || typeof _key == 'string' && _key.trim() === "")
return [];
// Parse the given key by removing the "]"-chars and splitting at "["
let indexes = [_key];
if (typeof _key === "string") {
_key = _key.replace(/&#x5B;/g, "[").replace(/&#x5D;/g, "]");
indexes = _key.split('[');
}
if (indexes.length > 1) {
indexes = [indexes.shift(), indexes.join('[')];
indexes[1] = indexes[1].substring(0, indexes[1].length - 1);
const children = indexes[1].split('][');
if (children.length) {
indexes = jQuery.merge([indexes[0]], children);
}
}
return indexes;
}
/**
* Returns the path to this content array manager perspective as an array
* containing the key values
*
* @param _path is used internally, do not supply it manually.
*/
getPath(_path) {
if (typeof _path == "undefined") {
_path = [];
}
if (this.perspectiveData.key != null) {
// prepend components of this.perspectiveData.key to path, can be more then one eg. "nm[rows]"
_path = this.perspectiveData.key.replace(/]/g, '').split('[').concat(_path);
}
if (this._parentMgr != null) {
_path = this._parentMgr.getPath(_path);
}
return _path;
}
/**
* Get array entry is the equivalent to the boetemplate get_array function.
* It returns a reference to the (sub) array with the given key. This also works
* for keys using the ETemplate referencing scheme like a[b][c]
*
* @param _key is the string index, may contain sub-indices like a[b]
* @param _referenceInto if true none-existing sub-arrays/-indices get created
* to be returned as reference, else false is returned. Defaults to false
* @param _skipEmpty returns null if _key is not present in this content array.
* Defaults to false.
*/
getEntry(_key, _referenceInto, _skipEmpty) {
if (typeof _referenceInto == "undefined") {
_referenceInto = false;
}
if (typeof _skipEmpty == "undefined") {
_skipEmpty = false;
}
// Parse the given key by removing the "]"-chars and splitting at "["
const indexes = this.explodeKey(_key);
if (indexes.length == 0 && _skipEmpty)
return null;
let entry = this.data;
for (let i = 0; i < indexes.length; i++) {
// Abort if the current entry is not an object (associative array) and
// we should descend further into it.
const isObject = typeof entry === 'object';
if (!isObject && !_referenceInto || entry == null || jQuery.isEmptyObject(entry)) {
return null;
}
// Check whether the entry actually exists
const idx = indexes[i];
if (_skipEmpty && (!isObject || typeof entry[idx] == "undefined")) {
return null;
}
entry = entry[idx];
}
return entry;
}
/**
* Equivalent to the boetemplate::expand_name function.
*
* Expands variables inside the given identifier to their values inside the
* content array.
*
* @param {string} _ident Key used to reference into managed array
* @return {*}
*/
expandName(_ident) {
// Check whether the identifier refers to an index in the content array
const is_index_in_content = _ident.charAt(0) == '@';
// Check whether "$" occurs in the given identifier
const pos_var = _ident.indexOf('$');
if (pos_var >= 0 && (this.perspectiveData.row != null || !_ident.match(/\$\{?row\}?/))
// Avoid messing with regex in validators
&& pos_var !== _ident.indexOf("$/")) {
// Get the content array for the current row
const row = typeof this.perspectiveData.row == 'number' ? this.perspectiveData.row : '';
const row_cont = this.data[row] || {};
// $cont is NOT root but current name-space in old eTemplate
const cont = this.data; //getRoot().data;
const _cont = this.data; // according to a grep only used in ImportExport just twice
// Check whether the expression has already been compiled - if not,
// try to compile it first. If an error occurs, the identifier
// function is set to null
if (typeof et2_arrayMgr.compiledExpressions[_ident] == "undefined") {
try {
if (this.perspectiveData.row == null) {
// No row, compile for only top level content
// @ts-ignore
et2_arrayMgr.compiledExpressions[_ident] = et2_compilePHPExpression(_ident, ["cont", "_cont"]);
}
else {
// @ts-ignore
et2_arrayMgr.compiledExpressions[_ident] = et2_compilePHPExpression(_ident, ["row", "cont", "row_cont", "_cont"]);
}
}
catch (e) {
et2_arrayMgr.compiledExpressions[_ident] = null;
egw.debug("error", "Error while compiling PHP->JS ", e);
}
}
// Execute the previously compiled expression, if it is not "null"
// because compilation failed. The parameters have to be in the same
// order as defined during compilation.
if (et2_arrayMgr.compiledExpressions[_ident]) {
try {
if (this.perspectiveData.row == null) {
// No row, exec with only top level content
_ident = et2_arrayMgr.compiledExpressions[_ident](cont, _cont);
}
else {
_ident = et2_arrayMgr.compiledExpressions[_ident](row, cont, row_cont, _cont);
}
}
catch (e) {
// only log error, as they are no real errors but missing data
egw.debug("log", typeof e == 'object' ? e.message : e);
_ident = null;
}
}
}
if (is_index_in_content && _ident) {
// If an additional "@" is specified, this means that we have to return
// the entry from the root element
if (_ident.charAt(1) == '@') {
return this.getRoot().getEntry(_ident.substr(2));
}
else {
return this.getEntry(_ident.substr(1));
}
}
return _ident;
}
parseBoolExpression(_expression) {
// If the first char of the expression is a '!' this means, that the value
// is to be negated.
if (_expression.charAt(0) == '!') {
return !this.parseBoolExpression(_expression.substr(1));
}
// Split the expression at a possible "="
const parts = _expression.split('=');
// Expand the first value
let val = this.expandName(parts[0]);
val = (typeof val == "undefined" || val === null) ? '' : '' + val;
// If a second expression existed, test that one
if (typeof parts[1] != "undefined") {
// Expand the second value
const checkVal = '' + this.expandName(parts[1]);
// Values starting with / are treated as regular expression. It is
// checked whether the first value matches the regular expression
if (checkVal.charAt(0) == '/') {
return (new RegExp(checkVal.substr(1, checkVal.length - 2)))
.test(val);
}
// Otherwise check for simple equality
return val == checkVal;
}
return et2_evalBool(val);
}
/**
* ?
*
* @param {object} _owner owner object
* @param {(string|null|object)} _root string with key, null for whole data or object with data
* @param {number?} _row key for into the _root for the desired row
*/
openPerspective(_owner, _root, _row) {
// Get the root node
let root = typeof _root == "string" ? this.data[_root] :
(_root == null ? this.data : _root);
if (typeof root == "undefined" && typeof _root == "string")
root = this.getEntry(_root);
// Create a new content array manager with the given root
const constructor = this.readOnly ? et2_readonlysArrayMgr : et2_arrayMgr;
const mgr = new constructor(root, this);
// Set the owner
mgr.perspectiveData.owner = _owner;
// Set the root key
if (typeof _root == "string") {
mgr.perspectiveData.key = _root;
}
// Set _row parameter
if (typeof _row != "undefined") {
mgr.perspectiveData.row = _row;
}
return mgr;
}
}
et2_arrayMgr.compiledExpressions = {};
/**
* @augments et2_arrayMgr
*/
export class et2_readonlysArrayMgr extends et2_arrayMgr {
constructor() {
super(...arguments);
this.readOnly = true;
}
/**
* Find out if the given ID is readonly, according to the array data
*
* @memberOf et2_readonlysArrayMgr
* @param _id
* @param _attr
* @param _parent
* @returns
*/
isReadOnly(_id, _attr, _parent) {
let entry = null;
if (_id != null) {
if (_id.indexOf('$') >= 0 || _id.indexOf('@') >= 0) {
_id = this.expandName(_id);
}
// readonlys was not namespaced in old eTemplate, therefore if we dont find data
// under current namespace, we look into parent
// (if there is anything namespaced, we will NOT look for parent!)
let mgr = this;
while (mgr.getParentMgr() && jQuery.isEmptyObject(mgr.data)) {
mgr = mgr.getParentMgr();
}
entry = mgr.getEntry(_id);
}
// Let the array entry override the read only attribute entry
if (typeof entry != "undefined" && !(typeof entry === 'object')) {
return entry;
}
// If the attribute is set, return that
if (typeof _attr != "undefined" && _attr !== null) {
// Accept 'editable', but otherwise boolean
return this.expandName(_attr) === 'editable' ? 'editable' : et2_evalBool(_attr);
}
// Otherwise take into accounf whether the parent is readonly
if (typeof _parent != "undefined" && _parent) {
return true;
}
// Otherwise return the default value
entry = this.getEntry("__ALL__");
return entry !== null && (typeof entry != "undefined");
}
/**
* Override parent to handle cont and row_cont.
*
* Normally these should refer to the readonlys data, but that's not
* useful, so we use the owner inside perspective data to expand using content.
*
* @param {string} ident Key for searching into the array.
* @returns {*}
*/
expandName(ident) {
return this.perspectiveData.owner.getArrayMgr('content').expandName(ident);
}
}
/**
* Creates a new set of array managers
*
* @param _owner is the owner object of the array managers - this object (a widget)
* will free the array manager
* @param _mgrs is the original set of array managers, the array managers are
* inside an associative array as recived from et2_widget::getArrayMgrs()
* @param _data is an associative array of new data which will be merged into the
* existing array managers.
* @param _row is the row for which the array managers will be opened.
*/
export function et2_arrayMgrs_expand(_owner, _mgrs, _data, _row) {
// Create a copy of the given _mgrs associative array
let result = {};
// Merge the given data associative array into the existing array managers
for (let key in _mgrs) {
result[key] = _mgrs[key];
if (typeof _data[key] != "undefined") {
// Open a perspective for the given data row
let rowData = {};
rowData[_row] = _data[key];
result[key] = _mgrs[key].openPerspective(_owner, rowData, _row);
}
}
// Return the resulting managers object
return result;
}
//# sourceMappingURL=et2_core_arrayMgr.js.map

View File

@ -1,416 +0,0 @@
/**
* EGroupware eTemplate2 - JS Widget base class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
import { et2_DOMWidget } from './et2_core_DOMWidget';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
import { et2_no_init } from "./et2_core_common";
import { egwIsMobile } from "../egw_action/egw_action_common.js";
/**
* Class which manages the DOM node itself. The simpleWidget class is derrived
* from et2_DOMWidget and implements the getDOMNode function. A setDOMNode
* function is provided, which attatches the given node to the DOM if possible.
*
* @augments et2_DOMWidget
*/
export class et2_baseWidget extends et2_DOMWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_baseWidget._attributes, _child || {}));
this.align = 'left';
this.node = null;
this.statustext = '';
this._messageDiv = null;
this._tooltipElem = null;
}
destroy() {
super.destroy();
this.node = null;
this._messageDiv = null;
}
/**
* The setMessage function can be used to attach a small message box to the
* widget. This is e.g. used to display validation errors or success messages
*
* @param _text is the text which should be displayed as a message
* @param _type is an css class which is attached to the message box.
* Currently available are "hint", "success" and "validation_error", defaults
* to "hint"
* @param _floating if true, the object will be in one row with the element,
* defaults to true
* @param _prepend if set, the message is displayed behind the widget node
* instead of before. Defaults to false.
*/
showMessage(_text, _type, _floating, _prepend) {
// Preset the parameters
if (typeof _type == "undefined") {
_type = "hint";
}
if (typeof _floating == "undefined") {
_floating = true;
}
if (typeof _prepend == "undefined") {
_prepend = false;
}
var surr = this.getSurroundings();
// Remove the message div from the surroundings before creating a new
// one
this.hideMessage(false, true);
// Create the message div and add it to the "surroundings" manager
this._messageDiv = jQuery(document.createElement("div"))
.addClass("message")
.addClass(_type)
.addClass(_floating ? "floating" : "")
.text(_text.valueOf() + "");
// Decide whether to prepend or append the div
if (_prepend) {
surr.prependDOMNode(this._messageDiv[0]);
}
else {
surr.appendDOMNode(this._messageDiv[0]);
}
surr.update();
}
/**
* The hideMessage function can be used to hide a previously shown message.
*
* @param _fade if true, the message div will fade out, otherwise the message
* div is removed immediately. Defaults to true.
* @param _noUpdate is used internally to prevent an update of the surroundings
* manager.
*/
hideMessage(_fade, _noUpdate) {
if (typeof _fade == "undefined") {
_fade = true;
}
if (typeof _noUpdate == "undefined") {
_noUpdate = false;
}
// Remove the message from the surroundings manager and remove the
// reference to it
if (this._messageDiv != null) {
var surr = this.getSurroundings();
var self = this;
var messageDiv = this._messageDiv;
self._messageDiv = null;
var _done = function () {
surr.removeDOMNode(messageDiv[0]);
// Update the surroundings manager
if (!_noUpdate) {
surr.update();
}
};
// Either fade out or directly call the function which removes the div
if (_fade) {
messageDiv.fadeOut("fast", _done);
}
else {
_done();
}
}
}
detachFromDOM() {
// Detach this node from the tooltip node
if (this._tooltipElem) {
this.egw().tooltipUnbind(this._tooltipElem);
this._tooltipElem = null;
}
// Remove the binding to the click handler
if (this.node) {
jQuery(this.node).unbind("click.et2_baseWidget");
}
return super.detachFromDOM();
}
attachToDOM() {
let ret = super.attachToDOM();
// Add the binding for the click handler
if (this.node) {
jQuery(this.node).bind("click.et2_baseWidget", this, function (e) {
return e.data.click.call(e.data, e, this);
});
if (typeof this.onclick == 'function')
jQuery(this.node).addClass('et2_clickable');
}
// Update the statustext
this.set_statustext(this.statustext);
return ret;
}
setDOMNode(_node) {
if (_node != this.node) {
// Deatch the old node from the DOM
this.detachFromDOM();
// Set the new DOM-Node
this.node = _node;
// Attatch the DOM-Node to the tree
return this.attachToDOM();
}
return false;
}
getDOMNode(_sender) {
return this.node;
}
getTooltipElement() {
return this.getDOMNode(this);
}
/**
* Click handler calling custom handler set via onclick attribute to this.onclick
*
* @param _ev
* @returns
*/
click(_ev) {
if (typeof this.onclick == 'function') {
// Make sure function gets a reference to the widget, splice it in as 2. argument if not
var args = Array.prototype.slice.call(arguments);
if (args.indexOf(this) == -1)
args.splice(1, 0, this);
return this.onclick.apply(this, args);
}
return true;
}
set_statustext(_value) {
// Tooltip should not be shown in mobile view
if (egwIsMobile())
return;
// Don't execute the code below, if no tooltip will be attached/detached
if (_value == "" && !this._tooltipElem) {
return;
}
this.statustext = _value;
//Get the domnode the tooltip should be attached to
var elem = jQuery(this.getTooltipElement());
if (elem) {
// Make readable by screenreader
elem.attr("aria-description", this.statustext);
//If a tooltip is already attached to the element, remove it first
if (this._tooltipElem) {
this.egw().tooltipUnbind(this._tooltipElem);
this._tooltipElem = null;
}
if (_value && _value != '') {
this.egw().tooltipBind(elem, _value, this.options.statustext_html);
this._tooltipElem = elem;
}
}
}
set_align(_value) {
this.align = _value;
}
get_align() {
return this.align;
}
}
et2_baseWidget._attributes = {
"statustext": {
"name": "Tooltip",
"type": "string",
"description": "Tooltip which is shown for this element",
"translate": true
},
"statustext_html": {
"name": "Tooltip is html",
"type": "boolean",
"description": "Flag to allow html content in tooltip",
"default": false
},
"align": {
"name": "Align",
"type": "string",
"default": "left",
"description": "Position of this element in the parent hbox"
},
"onclick": {
"name": "onclick",
"type": "js",
"default": et2_no_init,
"description": "JS code which is executed when the element is clicked."
}
};
/**
* Simple container object
*
* There is no tag to put this in a template. By convention we only make one of these per etemplate,
* and it's the top level object.
*/
export class et2_container extends et2_baseWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_container._attributes, _child || {}));
this.setDOMNode(document.createElement("div"));
}
/**
* The destroy function destroys all children of the widget, removes itself
* from the parents children list.
* Overriden to not try to remove self from parent, as that's not possible.
*/
destroy() {
// Call the destructor of all children
for (var i = this._children.length - 1; i >= 0; i--) {
this._children[i].destroy();
}
// Free the array managers if they belong to this widget
for (var key in this._mgrs) {
if (this._mgrs[key] && this._mgrs[key].owner == this) {
this._mgrs[key].destroy();
}
}
}
/**
* Searches for a DOM widget by id in the tree, descending into the child levels.
*
* @param _id is the id you're searching for
*/
getDOMWidgetById(_id) {
let widget = this.getWidgetById(_id);
if (widget && widget.instanceOf(et2_DOMWidget)) {
return widget;
}
return null;
}
/**
* Searches for a Value widget by id in the tree, descending into the child levels.
*
* @param _id is the id you're searching for
*/
getInputWidgetById(_id) {
let widget = this.getWidgetById(_id);
// instead of checking widget to be instance of valueWidget (which would create a circular dependency)
// we check for the interface/methods of valueWidget
if (widget && typeof widget.get_value === 'function' && typeof widget.set_value === 'function') {
return widget;
}
return null;
}
/**
* Set the value for a child widget, specified by the given ID
*
* @param id string The ID you're searching for
* @param value Value for the widget
*
* @return Returns the result of widget's set_value(), though this is usually undefined
*
* @throws Error If the widget cannot be found or it does not have a set_value() function
*/
setValueById(id, value) {
let widget = this.getWidgetById(id);
if (!widget)
throw 'Could not find widget ' + id;
// Don't care about what class it is, just that it has the function
// @ts-ignore
if (typeof widget.set_value !== 'function') {
throw 'Widget ' + id + ' does not have a set_value() function';
}
// @ts-ignore
return widget.set_value(value);
}
/**
* Get the current value of a child widget, specified by the given ID
*
* This is the current value of the widget, which may be different from the original value given in content
*
* @param id string The ID you're searching for
* @throws Error If the widget cannot be found or it does not have a set_value() function
*/
getValueById(id) {
let widget = this.getWidgetById(id);
if (!widget)
throw 'Could not find widget ' + id;
// Don't care about what class it is, just that it has the function
// @ts-ignore
if (typeof widget.get_value !== 'function') {
throw 'Widget ' + id + ' does not have a get_value() function';
}
// @ts-ignore
return widget.get_value();
}
/**
* Set the value for a child widget, specified by the given ID
*
* @param id string The ID you're searching for
* @throws Error If the widget cannot be found or it does not have a set_value() function
*/
setDisabledById(id, value) {
let widget = this.getWidgetById(id);
if (!widget)
throw 'Could not find widget ' + id;
// Don't care about what class it is, just that it has the function
// @ts-ignore
if (typeof widget.set_disabled !== 'function') {
throw 'Widget ' + id + ' does not have a set_disabled() function';
}
// @ts-ignore
return widget.set_disabled(value);
}
}
// Register widget for attributes, but not for any xml tags
et2_register_widget(et2_container, []);
/**
* Container object for not-yet supported widgets
*
* @augments et2_baseWidget
*/
export class et2_placeholder extends et2_baseWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_placeholder._attributes, _child || {}));
this.visible = false;
this.attrNodes = {};
// Create the placeholder div
this.placeDiv = jQuery(document.createElement("span"))
.addClass("et2_placeholder");
var headerNode = jQuery(document.createElement("span"))
.text(this.getType() || "")
.addClass("et2_caption")
.appendTo(this.placeDiv);
var attrsCntr = jQuery(document.createElement("span"))
.appendTo(this.placeDiv)
.hide();
headerNode.click(this, function (e) {
e.data.visible = !e.data.visible;
if (e.data.visible) {
attrsCntr.show();
}
else {
attrsCntr.hide();
}
});
for (var key in this.options) {
if (typeof this.options[key] != "undefined") {
if (typeof this.attrNodes[key] == "undefined") {
this.attrNodes[key] = jQuery(document.createElement("span"))
.addClass("et2_attr");
attrsCntr.append(this.attrNodes[key]);
}
this.attrNodes[key].text(key + "=" + this.options[key]);
}
}
this.setDOMNode(this.placeDiv[0]);
}
getDetachedAttributes(_attrs) {
_attrs.push("value");
}
getDetachedNodes() {
return [this.placeDiv[0]];
}
setDetachedAttributes(_nodes, _values) {
this.placeDiv = jQuery(_nodes[0]);
}
}
// Register widget, but no tags
et2_register_widget(et2_placeholder, ['placeholder', 'placeholder_ro']);
//# sourceMappingURL=et2_core_baseWidget.js.map

View File

@ -1,582 +0,0 @@
/**
* EGroupware eTemplate2 - JS Widget base class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
import { egw } from "../jsapi/egw_global";
/**
* IE Fix for array.indexOf
*/
if (typeof Array.prototype.indexOf == "undefined") {
Array.prototype.indexOf = function (_elem) {
for (var i = 0; i < this.length; i++) {
if (this[i] === _elem)
return i;
}
return -1;
};
}
/**
* Array with all types supported by the et2_checkType function.
*/
export var et2_validTypes = ["boolean", "string", "rawstring", "html", "float", "integer", "any", "js", "dimension"];
/**
* Object whith default values for the above types. Do not specify array or
* objects inside the et2_typeDefaults object, as this instance will be shared
* between all users of it.
*/
export var et2_typeDefaults = {
"boolean": false,
"string": "",
"rawstring": "",
"html": "",
"js": null,
"float": 0.0,
"integer": 0,
"any": null,
"dimension": "auto"
};
export function et2_evalBool(_val) {
if (typeof _val == "string") {
if (_val == "false" || _val == "0") {
return false;
}
}
return _val ? true : false;
}
/**
* Concat et2 name together, eg. et2_concat("namespace","test[something]") == "namespace[test][something]"
* @param variable number of arguments to contact
* @returns string
*/
export function et2_form_name(_cname, _name) {
var parts = [];
for (var i = 0; i < arguments.length; ++i) {
var name = arguments[i];
if (typeof name == 'string' && name.length > 0) // et2_namespace("","test") === "test" === et2_namespace(null,"test")
{
parts = parts.concat(name.replace(/]/g, '').split('['));
}
}
var name = parts.shift();
return parts.length ? name + '[' + parts.join('][') + ']' : name;
}
/**
* Checks whether the given value is of the given type. Strings are converted
* into the corresponding type. The (converted) value is returned. All supported
* types are listed in the et2_validTypes array.
*
* @param mixed _val value
* @param string _type a valid type eg. "string" or "js"
* @param string _attr attribute name
* @param object _widget
*/
export function et2_checkType(_val, _type, _attr, _widget) {
if (typeof _attr == "undefined") {
_attr = null;
}
function _err() {
var res = et2_typeDefaults[_type];
if (typeof _val != "undefined" && _val) {
egw.debug("warn", "Widget %o: '" + _val + "' was not of specified _type '" +
_type + (_attr != null ? "' for attribute '" + _attr + "' " : "") +
"and is now '" + res + "'", _widget);
}
return res;
}
// If the type is "any" simply return the value again
if (_type == "any") {
return _val;
}
// we dont check default-value any further, that also fixes type="js" does NOT accept null,
// which happens on expanded values
if (_val === et2_typeDefaults[_type]) {
return _val;
}
// If the type is boolean, check whether the given value is exactly true or
// false. Otherwise check whether the value is the string "true" or "false".
if (_type == "boolean") {
if (_val === true || _val === false) {
return _val;
}
if (typeof _val == "string") {
var lcv = _val.toLowerCase();
if (lcv === "true" || lcv === "false" || lcv === "") {
return _val === "true";
}
if (lcv === "0" || lcv === "1") {
return _val === "1";
}
}
else if (typeof _val == "number") {
return _val != 0;
}
return _err();
}
// Check whether the given value is of the type "string"
if (_type == "string" || _type == "html" || _type == "rawstring") {
if (typeof _val == "number") // as php is a bit vague here, silently convert to a string
{
return _val.toString();
}
if (typeof _val == "string") {
return _type == "string" ? html_entity_decode(_val) : _val;
}
// Handle some less common possibilities
// Maybe a split on an empty string
if (typeof _val == "object" && jQuery.isEmptyObject(_val))
return "";
return _err();
}
// Check whether the value is already a number, otherwise try to convert it
// to one.
if (_type == "float") {
if (typeof _val == "number") {
return _val;
}
if (!isNaN(_val)) {
return parseFloat(_val);
}
return _err();
}
// Check whether the value is an integer by comparing the result of
// parseInt(_val) to the value itself.
if (_type == "integer") {
if (parseInt(_val) == _val) {
return parseInt(_val);
}
return _err();
}
// Parse the given dimension value
if (_type == "dimension") {
// Case 1: The value is "auto"
if (_val == "auto") {
return _val;
}
// Case 2: The value is simply a number, attach "px"
if (!isNaN(_val)) {
return parseFloat(_val) + "px";
}
// Case 3: The value is already a valid css pixel value or a percentage
if (typeof _val == "string" &&
((_val.indexOf("px") == _val.length - 2 && !isNaN(_val.split("px")[0])) ||
(_val.indexOf("%") == _val.length - 1 && !isNaN(_val.split("%")[0])))) {
return _val;
}
return _err();
}
// Javascript
if (_type == "js") {
if (typeof _val == "function" || typeof _val == "undefined") {
return _val;
}
if (_val)
_val = _val.replace(/window\.close\(\)/g, 'egw(window).close()');
// Check to see if it's a string in app.appname.function format, and wrap it in
// a closure to make sure context is preserved
if (typeof _val == "string" && _val.substr(0, 4) == "app." && app) {
var parts = _val.split('.');
var func = parts.pop();
var parent = window;
for (var i = 0; i < parts.length && typeof parent[parts[i]] != 'undefined'; ++i) {
parent = parent[parts[i]];
}
if (typeof parent[func] == 'function') {
try {
return jQuery.proxy(parent[func], parent);
}
catch (e) {
egw.debug('error', 'Function', _val);
return _err();
}
}
}
if (!_val || typeof _val == "string") {
return _val; // get compiled later in widgets own initAttributes, as widget is not yet initialised
}
}
// We should never come here
throw ("Invalid type identifier '" + _attr + "': '" + _type + "'");
}
/**
* If et2_no_init is set as default value, the initAttributes function will not
* try to initialize the attribute with the default value.
*/
export const et2_no_init = new Object();
/**
* Validates the given attribute with the given id. The validation checks for
* the existance of a human name, a description, a type and a default value.
* If the human name defaults to the given id, the description defaults to an
* empty string, the type defaults to any and the default to the corresponding
* type default.
*/
export function et2_validateAttrib(_id, _attrib) {
// Default ignore to false.
if (typeof _attrib["ignore"] == "undefined") {
_attrib["ignore"] = false;
}
// Break if "ignore" is set to true.
if (_attrib.ignore) {
return;
}
if (typeof _attrib["name"] == "undefined") {
_attrib["name"] = _id;
egw.debug("log", "Human name ('name'-Field) for attribute '" +
_id + "' has not been supplied, set to '" + _id + "'");
}
if (typeof _attrib["description"] == "undefined") {
_attrib["description"] = "";
egw.debug("log", "Description for attribute '" +
_id + "' has not been supplied");
}
if (typeof _attrib["type"] == "undefined") {
_attrib["type"] = "any";
}
else {
if (et2_validTypes.indexOf(_attrib["type"]) < 0) {
egw.debug("error", "Invalid type '" + _attrib["type"] + "' for attribute '" + _id +
"' supplied. Valid types are ", et2_validTypes);
}
}
// Set the defaults
if (typeof _attrib["default"] == "undefined") {
_attrib["default"] = et2_typeDefaults[_attrib["type"]];
}
}
/**
* Equivalent to the PHP array_values function
*/
export function et2_arrayValues(_arr) {
var result = [];
for (var key in _arr) {
// @ts-ignore we check key is an integer
if (parseInt(key) == key) {
result.push(_arr[key]);
}
}
return result;
}
/**
* Equivalent to the PHP array_keys function
*/
export function et2_arrayKeys(_arr) {
var result = [];
for (var key in _arr) {
result.push(key);
}
return result;
}
export function et2_arrayIntKeys(_arr) {
var result = [];
for (var key in _arr) {
result.push(parseInt(key));
}
return result;
}
/**
* Equivalent to the PHP substr function, partly take from phpjs, licensed under
* the GPL.
*/
export function et2_substr(str, start, len) {
var end = str.length;
if (start < 0) {
start += end;
}
end = typeof len === 'undefined' ? end : (len < 0 ? len + end : len + start);
return start >= str.length || start < 0 || start > end ? "" : str.slice(start, end);
}
/**
* Split a $delimiter-separated options string, which can contain parts with
* delimiters enclosed in $enclosure. Ported from class.boetemplate.inc.php
*
* Examples:
* - et2_csvSplit('"1,2,3",2,3') === array('1,2,3','2','3')
* - et2_csvSplit('1,2,3',2) === array('1','2,3')
* - et2_csvSplit('"1,2,3",2,3',2) === array('1,2,3','2,3')
* - et2_csvSplit('"a""b,c",d') === array('a"b,c','d') // to escape enclosures double them!
*
* @param string _str
* @param int _num=null in how many parts to split maximal, parts over this
* number end up (unseparated) in the last part
* @param string _delimiter=','
* @param string _enclosure='"'
* @return array
*/
export function et2_csvSplit(_str, _num, _delimiter, _enclosure) {
// Default the parameters
if (typeof _str == "undefined" || _str == null) {
_str = "";
}
if (typeof _num == "undefined") {
_num = null;
}
if (typeof _delimiter == "undefined") {
_delimiter = ",";
}
if (typeof _enclosure == "undefined") {
_enclosure = '"';
}
// If the _enclosure string does not occur in the string, simply use the
// split function
if (_str.indexOf(_enclosure) == -1) {
return _num === null ? _str.split(_delimiter) :
_str.split(_delimiter, _num);
}
// Split the string at the delimiter and join it again, when a enclosure is
// found at the beginning/end of a part
var parts = _str.split(_delimiter);
for (var n = 0; typeof parts[n] != "undefined"; n++) {
var part = parts[n];
if (part.charAt(0) === _enclosure) {
var m = n;
while (typeof parts[m + 1] != "undefined" && parts[n].substr(-1) !== _enclosure) {
parts[n] += _delimiter + parts[++m];
delete (parts[m]);
}
parts[n] = et2_substr(parts[n].replace(new RegExp(_enclosure + _enclosure, 'g'), _enclosure), 1, -1);
n = m;
}
}
// Rebuild the array index
parts = et2_arrayValues(parts);
// Limit the parts to the given number
if (_num !== null && _num > 0 && _num < parts.length && parts.length > 0) {
parts[_num - 1] = parts.slice(_num - 1, parts.length).join(_delimiter);
parts = parts.slice(0, _num);
}
return parts;
}
/**
* Parses the given string and returns an array marking parts which are URLs
*/
export function et2_activateLinks(_content) {
var _match = false;
var arr = [];
function _splitPush(_matches, _proc) {
if (_matches) {
// We had a match
_match = true;
// Replace "undefined" with ""
for (var i = 1; i < _matches.length; i++) {
if (typeof _matches[i] == "undefined") {
_matches[i] = "";
}
}
// Split the content string at the given position(s)
// but we only handle the first occurence
var splitted = _content.split(_matches[0]);
// Push the not-matched part
var left = splitted.shift();
if (left) {
// activate the links of the left string
arr = arr.concat(et2_activateLinks(left));
}
// Call the callback function which converts the matches into an object
// and appends it to the string
_proc(_matches);
// Set the new working string to the right part
_content = splitted.join(_matches[0]);
}
}
var mail_regExp = /(mailto:)?([a-z0-9._-]+)@([a-z0-9_-]+)\.([a-z0-9._-]+)/i;
// First match things beginning with http:// (or other protocols)
var protocol = '(http:\\/\\/|(ftp:\\/\\/|https:\\/\\/))'; // only http:// gets removed, other protocolls are shown
var domain = '([\\w-]+\\.[\\w-.]+)';
var subdir = '([\\w\\-\\.,@?^=%&;:\\/~\\+#]*[\\w\\-\\@?^=%&\\/~\\+#])?';
var http_regExp = new RegExp(protocol + domain + subdir, 'i');
// Now match things beginning with www.
var domain = 'www(\\.[\\w-.]+)';
var subdir = '([\\w\\-\\.,@?^=%&:\\/~\\+#]*[\\w\\-\\@?^=%&\\/~\\+#])?';
var www_regExp = new RegExp(domain + subdir, 'i');
do {
_match = false;
// Abort if the remaining length of _content is smaller than 20 for
// performance reasons
if (!_content) {
break;
}
// No need make emailaddress spam-save, as it gets dynamically created
_splitPush(_content.match(mail_regExp), function (_matches) {
arr.push({
"href": (_matches[1] ? '' : 'mailto:') + _matches[0],
"text": _matches[2] + "@" + _matches[3] + "." + _matches[4]
});
});
// Create hrefs for links starting with "http://"
_splitPush(_content.match(http_regExp), function (_matches) {
arr.push({
"href": _matches[0],
"text": _matches[2] + _matches[3] + _matches[4]
});
});
// Create hrefs for links starting with "www."
_splitPush(_content.match(www_regExp), function (_matches) {
arr.push({
"href": "http://" + _matches[0],
"text": _matches[0]
});
});
} while (_match);
arr.push(_content);
return arr;
}
/**
* Inserts the structure generated by et2_activateLinks into the given DOM-Node
*/
export function et2_insertLinkText(_text, _node, _target) {
if (!_node) {
egw.debug("warn", "et2_insertLinkText called without node", _text, _node, _target);
return;
}
// Clear the node
for (var i = _node.childNodes.length - 1; i >= 0; i--) {
_node.removeChild(_node.childNodes[i]);
}
for (var i = 0; i < _text.length; i++) {
var s = _text[i];
if (typeof s == "string" || typeof s == "number") {
// Include line breaks
var lines = typeof s !== "number" && s.split ? s.split('\n') : [s + ""];
// Insert the lines
for (var j = 0; j < lines.length; j++) {
_node.appendChild(document.createTextNode(lines[j]));
if (j < lines.length - 1) {
_node.appendChild(document.createElement("br"));
}
}
}
else if (s === null || s === void 0 ? void 0 : s.text) {
if (!s.href) {
egw.debug("warn", "et2_activateLinks gave bad data", s, _node, _target);
s.href = "";
}
var a = jQuery(document.createElement("a"))
.attr("href", s.href)
.text(s.text);
if (typeof _target != "undefined" && _target && _target != "_self" && s.href.substr(0, 7) != "mailto:") {
a.attr("target", _target);
}
// open mailto links depending on preferences in mail app
if (s.href.substr(0, 7) == "mailto:" &&
(egw.user('apps').mail || egw.user('apps').felamimail) &&
egw.preference('force_mailto', 'addressbook') != '1') {
a.click(function (event) {
egw.open_link(this.href);
return false;
});
}
a.appendTo(_node);
}
}
}
/**
* Creates a copy of the given object (non recursive)
*/
export function et2_cloneObject(_obj) {
var result = {};
for (var key in _obj) {
result[key] = _obj[key];
}
return result;
}
/**
* Returns true if the given array of nodes or their children contains the given
* child node.
*/
export function et2_hasChild(_nodes, _child) {
for (var i = 0; i < _nodes.length; i++) {
if (_nodes[i] == _child) {
return true;
}
else if (_nodes[i].childNodes) {
var res = et2_hasChild(_nodes[i].childNodes, _child);
if (res) {
return true;
}
}
}
return false;
}
/**
* Functions to work with ranges and range intersection (used in the dataview)
*/
/**
* Common functions used in most view classes
*/
/**
* Returns an "range" object with the given top position and height
*/
export function et2_range(_top, _height) {
return {
"top": _top,
"bottom": _top + _height
};
}
/**
* Returns an "area" object with the given top- and bottom position
*/
export function et2_bounds(_top, _bottom) {
return {
"top": _top,
"bottom": _bottom
};
}
/**
* Returns whether two range objects intersect each other
*/
export function et2_rangeIntersect(_ar1, _ar2) {
return !(_ar1.bottom < _ar2.top || _ar1.top > _ar2.bottom);
}
/**
* Returns whether two ranges intersect (result = 0) or their relative position
* to each other (used to do a binary search inside a list of sorted range objects).
*/
export function et2_rangeIntersectDir(_ar1, _ar2) {
if (_ar1.bottom < _ar2.top) {
return -1;
}
if (_ar1.top > _ar2.bottom) {
return 1;
}
return 0;
}
/**
* Returns whether two ranges are equal.
*/
export function et2_rangeEqual(_ar1, _ar2) {
return _ar1.top === _ar2.top && _ar1.bottom === _ar2.bottom;
}
/**
* Substracts _ar2 from _ar1, returns an array of new ranges.
*/
export function et2_rangeSubstract(_ar1, _ar2) {
// Per default return the complete _ar1 range
var res = [_ar1];
// Check whether there is an intersection between the given ranges
if (et2_rangeIntersect(_ar1, _ar2)) {
res = [et2_bounds(_ar1.top, _ar2.top),
et2_bounds(_ar2.bottom, _ar1.bottom)];
}
// Remove all zero-length ranges from the result
for (var i = res.length - 1; i >= 0; i--) {
if (res[i].bottom - res[i].top <= 0) {
res.splice(i, 1);
}
}
return res;
}
/**
* Decode html entities so they can be added via .text(_str), eg. html_entity_decode('&amp;') === '&'
*
* @param {string} _str
* @returns {string}
*/
export function html_entity_decode(_str) {
return _str && _str.indexOf('&') != -1 ? jQuery('<span>' + _str + '</span>').text() : _str;
}
//# sourceMappingURL=et2_core_common.js.map

View File

@ -1,177 +0,0 @@
/**
* EGroupware eTemplate2 - JS Widget base class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
/*egw:uses
et2_core_inputWidget;
*/
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
/**
* et2_editableWidget derives from et2_inputWidget and adds the ability to start
* readonly, then turn editable on double-click. If we decide to do this with
* more widgets, it should just be merged with et2_inputWidget.
*
* @augments et2_inputWidget
*/
export class et2_editableWidget extends et2_inputWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// 'Editable' really should be boolean for everything else to work
if (_attrs.readonly && typeof _attrs.readonly === 'string') {
_attrs.readonly = true;
var toggle_readonly = _attrs.toggle_readonly;
}
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_editableWidget._attributes, _child || {}));
if (typeof toggle_readonly != 'undefined')
this._toggle_readonly = toggle_readonly;
}
destroy() {
let node = this.getInputNode();
if (node) {
jQuery(node).off('.et2_editableWidget');
}
super.destroy();
}
/**
* Load the validation errors from the server
*
* @param {object} _attrs
*/
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
}
attachToDOM() {
let res = super.attachToDOM();
let node = this.getDOMNode();
if (node && this._toggle_readonly) {
jQuery(node)
.off('.et2_editableWidget')
.on("dblclick.et2_editableWidget", this, function (e) {
e.data.dblclick.call(e.data, this);
})
.addClass('et2_clickable et2_editable');
}
else {
jQuery(node).addClass('et2_editable_readonly');
}
return res;
}
detatchFromDOM() {
super.detatchFromDOM();
}
/**
* Handle double click
*
* Turn widget editable
*
* @param {DOMNode} _node
*/
dblclick(_node) {
// Turn off readonly
this.set_readonly(false);
jQuery('body').on("click.et2_editableWidget", this, function (e) {
// Make sure click comes from body, not a popup
if (jQuery.contains(this, e.target) && e.target.type != 'textarea') {
jQuery(this).off("click.et2_editableWidget");
e.data.focusout.call(e.data, this);
}
});
}
/**
* User clicked somewhere else, save and turn back to readonly
*
* @param {DOMNode} _node Body node
* @returns {et2_core_editableWidgetet2_editableWidget.et2_core_editableWidgetAnonym$0@call;getInstanceManager@call;submit}
*/
focusout(_node) {
var value = this.get_value();
var oldValue = this._oldValue;
// Change back to readonly
this.set_readonly(true);
// No change, do nothing
if (value == oldValue)
return;
// Submit
if (this.options.save_callback) {
var params = [value];
if (this.options.save_callback_params) {
params = params.concat(this.options.save_callback_params.split(','));
}
egw.json(this.options.save_callback, params, function () {
}, this, true, this).sendRequest();
}
else {
this.set_value(value);
return this.getInstanceManager().submit();
}
}
/**
* Called whenever the template gets submitted.
* If we have a save_callback, we call that before the submit (no check on
* the result)
*
* @param _values contains the values which will be sent to the server.
* Listeners may change these values before they get submitted.
*/
submit(_values) {
if (this.options.readonly) {
// Not currently editing, just continue on
return true;
}
// Change back to readonly
this.set_readonly(true);
var params = [this.get_value()];
if (this.options.save_callback_params) {
params = params.concat(this.options.save_callback_params.split(','));
}
if (this.options.save_callback) {
egw.json(this.options.save_callback, params, function () {
}, this, true, this).sendRequest();
}
return true;
}
}
et2_editableWidget._attributes = {
readonly: {
name: "readonly",
type: "string",
default: false,
description: "If set to 'editable' will start readonly, double clicking will make it editable and clicking out will save",
ignore: true // todo: not sure why this used to be ignored before migration by default but not anymore
},
toggle_readonly: {
name: "toggle_readonly",
type: "boolean",
default: true,
description: "Double clicking makes widget editable. If off, must be made editable in some other way."
},
save_callback: {
name: "save_callback",
type: "string",
default: et2_no_init,
description: "Ajax callback to save changed value when readonly is 'editable'. If not provided, a regular submit is done."
},
save_callback_params: {
name: "readonly",
type: "string",
default: et2_no_init,
description: "Additional parameters passed to save_callback"
},
editable_height: {
name: "Editable height",
description: "Set height for widget while in edit mode",
type: "string"
}
};
//# sourceMappingURL=et2_core_editableWidget.js.map

View File

@ -1,252 +0,0 @@
/**
* EGroupware eTemplate2 - JS code for implementing inheritance with attributes
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link: https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
et2_core_common;
*/
import { egw } from "../jsapi/egw_global";
import { et2_checkType, et2_no_init, et2_validateAttrib } from "./et2_core_common";
import { et2_implements_registry } from "./et2_core_interfaces";
// Needed for mixin
export function mix(superclass) {
return new MixinBuilder(superclass);
}
export class MixinBuilder {
constructor(superclass) {
this.superclass = superclass;
}
with(...mixins) {
return mixins.reduce(this.applyMixins, this.superclass);
}
applyMixins(derivedConstructor, baseConstructor) {
Object.getOwnPropertyNames(baseConstructor.prototype)
.forEach(name => {
Object.defineProperty(derivedConstructor.prototype, name, Object.
getOwnPropertyDescriptor(baseConstructor.prototype, name));
});
}
copyProperties(target, source) {
for (let key of Reflect.ownKeys(source)) {
if (key !== "constructor" && key !== "prototype" && key !== "name") {
let desc = Object.getOwnPropertyDescriptor(source, key);
Object.defineProperty(target, key, desc);
}
}
}
}
// This one from Typescript docs
export function applyMixins(derivedCtor, constructors) {
constructors.forEach((baseCtor) => {
Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
Object.defineProperty(derivedCtor.prototype, name, Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
Object.create(null));
});
});
}
/*
Experiments in using mixins to combine et2_widget & LitElement
Note that this "works", in that it mixes the code properly.
It does not work in that the resulting class does not work with et2's inheritance & class checking stuff
// This one to make TypeScript happy?
interface et2_textbox extends et2_textbox, LitElement {}
// This one to make the inheritance magic happen
applyMixins(et2_textbox, [et2_textbox,LitElement]);
// Make it a real WebComponent
customElements.define("et2-textbox",et2_textbox);
*/
export class ClassWithInterfaces {
/**
* The implements function can be used to check whether the object
* implements the given interface.
*
* As TypeScript can not (yet) check if an objects implements an interface on runtime,
* we currently implements with each interface a function called 'implements_'+interfacename
* to be able to check here.
*
* @param _iface name of interface to check
*/
implements(_iface_name) {
if (typeof et2_implements_registry[_iface_name] === 'function' &&
et2_implements_registry[_iface_name](this)) {
return true;
}
return false;
}
/**
* Check if object is an instance of a class or implements an interface (specified by the interfaces name)
*
* @param _class_or_interfacename class(-name) or string with name of interface
*/
instanceOf(_class_or_interfacename) {
if (typeof _class_or_interfacename === 'string') {
return this.implements(_class_or_interfacename);
}
return this instanceof _class_or_interfacename;
}
}
export class ClassWithAttributes extends ClassWithInterfaces {
/**
* Returns the value of the given attribute. If the property does not
* exist, an error message is issued.
*
* @param {string} _name
* @return {*}
*/
getAttribute(_name) {
if (typeof this.attributes[_name] != "undefined" &&
!this.attributes[_name].ignore) {
if (typeof this["get_" + _name] == "function") {
return this["get_" + _name]();
}
else {
return this[_name];
}
}
else {
egw.debug("error", this, "Attribute '" + _name + "' does not exist!");
}
}
/**
* The setAttribute function sets the attribute with the given name to
* the given value. _override defines, whether this[_name] will be set,
* if this key already exists. _override defaults to true. A warning
* is issued if the attribute does not exist.
*
* @param {string} _name
* @param {*} _value
* @param {boolean} _override
*/
setAttribute(_name, _value, _override) {
if (typeof this.attributes[_name] != "undefined") {
if (!this.attributes[_name].ignore) {
if (typeof _override == "undefined") {
_override = true;
}
var val = et2_checkType(_value, this.attributes[_name].type, _name, this);
if (typeof this["set_" + _name] == "function") {
this["set_" + _name](val);
}
else if (_override || typeof this[_name] == "undefined") {
this[_name] = val;
}
}
}
else {
egw.debug("warn", this, "Attribute '" + _name + "' does not exist!");
}
}
/**
* generateAttributeSet sanitizes the given associative array of attributes
* (by passing each entry to "et2_checkType" and checking for existance of
* the attribute) and adds the default values to the associative array.
*
* @param {object} _attrs is the associative array containing the attributes.
*/
static generateAttributeSet(widget, _attrs) {
// Sanity check and validation
for (var key in _attrs) {
if (typeof widget[key] != "undefined") {
if (!widget[key].ignore) {
_attrs[key] = et2_checkType(_attrs[key], widget[key].type, key, this);
}
}
else {
// Key does not exist - delete it and issue a warning
delete (_attrs[key]);
egw.debug("warn", this, "Attribute '" + key +
"' does not exist in " + _attrs.type + "!");
}
}
// Include default values or already set values for this attribute
for (var key in widget) {
if (typeof _attrs[key] == "undefined") {
var _default = widget[key]["default"];
if (_default == et2_no_init) {
_default = undefined;
}
_attrs[key] = _default;
}
}
return _attrs;
}
/**
* The initAttributes function sets the attributes to their default
* values. The attributes are not overwritten, which means, that the
* default is only set, if either a setter exists or this[propName] does
* not exist yet.
*
* @param {object} _attrs is the associative array containing the attributes.
*/
initAttributes(_attrs) {
for (var key in _attrs) {
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined)) {
this.setAttribute(key, _attrs[key], false);
}
}
}
static buildAttributes(class_prototype) {
let class_tree = [];
let attributes = {};
let n = 0;
do {
n++;
class_tree.push(class_prototype);
class_prototype = Object.getPrototypeOf(class_prototype);
} while (class_prototype !== ClassWithAttributes && n < 50);
for (let i = class_tree.length - 1; i >= 0; i--) {
attributes = ClassWithAttributes.extendAttributes(attributes, class_tree[i]._attributes);
}
return attributes;
}
/**
* Extend current _attributes with the one from the parent class
*
* This gives inheritance from the parent plus the ability to override in the current class.
*
* @param _attributes
* @param _parent
*/
static extendAttributes(_parent, _attributes) {
function _copyMerge(_new, _old) {
var result = {};
// Copy the new object
if (typeof _new != "undefined") {
for (var key in _new) {
result[key] = _new[key];
}
}
// Merge the old object
for (var key in _old) {
if (typeof result[key] == "undefined") {
result[key] = _old[key];
}
}
return result;
}
var attributes = {};
// Copy the old attributes
for (var key in _attributes) {
attributes[key] = _copyMerge({}, _attributes[key]);
}
// Add the old attributes to the new ones. If the attributes already
// exist, they are merged.
for (var key in _parent) {
var _old = _parent[key];
attributes[key] = _copyMerge(attributes[key], _old);
}
// Validate the attributes
for (var key in attributes) {
et2_validateAttrib(key, attributes[key]);
}
return attributes;
}
}
//# sourceMappingURL=et2_core_inheritance.js.map

View File

@ -1,294 +0,0 @@
/**
* EGroupware eTemplate2 - JS Widget base class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_interfaces;
et2_core_valueWidget;
*/
import { et2_no_init } from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from './et2_core_valueWidget';
import { et2_compileLegacyJS } from "./et2_core_legacyJSFunctions";
/**
* et2_inputWidget derrives from et2_simpleWidget and implements the IInput
* interface. When derriving from this class, call setDOMNode with an input
* DOMNode.
*/
export class et2_inputWidget extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_inputWidget._attributes, _child || {}));
// mark value as not initialised, so set_value can determine if it is necessary to trigger change event
this._oldValue = et2_no_init;
this._labelContainer = null;
}
destroy() {
var node = this.getInputNode();
if (node) {
jQuery(node).unbind("change.et2_inputWidget");
jQuery(node).unbind("focus");
}
super.destroy();
this._labelContainer = null;
}
/**
* Make sure dirty flag is properly set
*/
doLoadingFinished() {
let result = super.doLoadingFinished();
this.resetDirty();
return result;
}
/**
* Load the validation errors from the server
*
* @param {object} _attrs
*/
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
// Check whether an validation error entry exists
if (this.id && this.getArrayMgr("validation_errors")) {
var val = this.getArrayMgr("validation_errors").getEntry(this.id);
if (val) {
_attrs["validation_error"] = val;
}
}
}
attachToDOM() {
var node = this.getInputNode();
if (node) {
jQuery(node)
.off('.et2_inputWidget')
.bind("change.et2_inputWidget", this, function (e) {
e.data.change.call(e.data, this);
})
.bind("focus.et2_inputWidget", this, function (e) {
e.data.focus.call(e.data, this);
});
}
return super.attachToDOM();
// jQuery(this.getInputNode()).attr("novalidate","novalidate"); // Stop browser from getting involved
// jQuery(this.getInputNode()).validator();
}
detatchFromDOM() {
// if(this.getInputNode()) {
// jQuery(this.getInputNode()).data("validator").destroy();
// }
super.detachFromDOM();
}
change(_node, _widget, _value) {
var messages = [];
var valid = this.isValid(messages);
// Passing false will clear any set messages
this.set_validation_error(valid ? false : messages);
if (valid && this.onchange) {
if (typeof this.onchange == 'function') {
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if (args.indexOf(this) == -1)
args.push(this);
return this.onchange.apply(this, args);
}
else {
return (et2_compileLegacyJS(this.options.onchange, this, _node))();
}
}
return valid;
}
focus(_node) {
if (typeof this.options.onfocus == 'function') {
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if (args.indexOf(this) == -1)
args.push(this);
return this.options.onfocus.apply(this, args);
}
}
/**
* Set value of widget and trigger for real changes a change event
*
* First initialisation (_oldValue === et2_no_init) is NOT considered a change!
*
* @param {string} _value value to set
*/
set_value(_value) {
var node = this.getInputNode();
if (node) {
jQuery(node).val(_value);
if (this.isAttached() && this._oldValue !== et2_no_init && this._oldValue !== _value) {
jQuery(node).change();
}
}
this._oldValue = _value;
}
set_id(_value) {
this.id = _value;
this.dom_id = _value && this.getInstanceManager() ? this.getInstanceManager().uniqueId + '_' + this.id : _value;
// Set the id of the _input_ node (in contrast to the default
// implementation, which sets the base node)
var node = this.getInputNode();
if (node) {
// Unique ID to prevent DOM collisions across multiple templates
if (_value != "") {
node.setAttribute("id", this.dom_id);
node.setAttribute("name", _value);
}
else {
node.removeAttribute("id");
node.removeAttribute("name");
}
}
}
set_needed(_value) {
var node = this.getInputNode();
if (node) {
if (_value && !this.options.readonly) {
jQuery(node).attr("required", "required");
}
else {
node.removeAttribute("required");
}
}
}
set_validation_error(_value) {
var node = this.getInputNode();
if (node) {
if (_value === false) {
this.hideMessage();
jQuery(node).removeClass("invalid");
}
else {
this.showMessage(_value, "validation_error");
jQuery(node).addClass("invalid");
// If on a tab, switch to that tab so user can see it
let widget = this;
while (widget.getParent() && widget.getType() != 'tabbox') {
widget = widget.getParent();
}
if (widget.getType() == 'tabbox')
widget.activateTab(this);
}
}
}
/**
* Set tab index
*
* @param {number} index
*/
set_tabindex(index) {
jQuery(this.getInputNode()).attr("tabindex", index);
}
getInputNode() {
return this.node;
}
get_value() {
return this.getValue();
}
getValue() {
var node = this.getInputNode();
if (node) {
var val = jQuery(node).val();
return val;
}
return this._oldValue;
}
isDirty() {
let value = this.getValue();
if (typeof value !== typeof this._oldValue) {
return true;
}
if (this._oldValue === value) {
return false;
}
switch (typeof this._oldValue) {
case "object":
if (typeof this._oldValue.length !== "undefined" &&
this._oldValue.length !== value.length) {
return true;
}
for (let key in this._oldValue) {
if (this._oldValue[key] !== value[key])
return true;
}
return false;
default:
return this._oldValue != value;
}
}
resetDirty() {
this._oldValue = this.getValue();
}
isValid(messages) {
var ok = true;
// Check for required
if (this.options && this.options.needed && !this.options.readonly && !this.disabled &&
(this.getValue() == null || this.getValue().valueOf() == '')) {
messages.push(this.egw().lang('Field must not be empty !!!'));
ok = false;
}
return ok;
}
/**
* Called whenever the template gets submitted. We return false if the widget
* is not valid, which cancels the submission.
*
* @param _values contains the values which will be sent to the server.
* Listeners may change these values before they get submitted.
*/
submit(_values) {
var messages = [];
var valid = this.isValid(messages);
// Passing false will clear any set messages
this.set_validation_error(valid ? false : messages);
return valid;
}
}
et2_inputWidget._attributes = {
"needed": {
"name": "Required",
"default": false,
"type": "boolean",
"description": "If required, the user must enter a value before the form can be submitted"
},
"onchange": {
"name": "onchange",
"type": "js",
"default": et2_no_init,
"description": "JS code which is executed when the value changes."
},
"onfocus": {
"name": "onfocus",
"type": "js",
"default": et2_no_init,
"description": "JS code which get executed when wiget receives focus."
},
"validation_error": {
"name": "Validation Error",
"type": "string",
"default": et2_no_init,
"description": "Used internally to store the validation error that came from the server."
},
"tabindex": {
"name": "Tab index",
"type": "integer",
"default": et2_no_init,
"description": "Specifies the tab order of a widget when the 'tab' button is used for navigating."
},
readonly: {
name: "readonly",
type: "boolean",
"default": false,
description: "Does NOT allow user to enter data, just displays existing data"
}
};
//# sourceMappingURL=et2_core_inputWidget.js.map

View File

@ -1,61 +0,0 @@
/**
* EGroupware eTemplate2 - File which contains all interfaces
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
export var et2_implements_registry = {};
/**
* Checks if an object / et2_widget implements given methods
*
* @param obj
* @param methods
*/
export function implements_methods(obj, methods) {
for (let i = 0; i < methods.length; ++i) {
if (typeof obj[methods[i]] !== 'function') {
return false;
}
}
return true;
}
export const et2_IDOMNode = "et2_IDOMNode";
et2_implements_registry.et2_IDOMNode = function (obj) {
return implements_methods(obj, ["getDOMNode"]);
};
export const et2_IInputNode = "et2_IInputNode";
et2_implements_registry.et2_IInputNode = function (obj) {
return implements_methods(obj, ["getInputNode"]);
};
export const et2_IInput = "et2_IInput";
et2_implements_registry.et2_IInput = function (obj) {
return implements_methods(obj, ["getValue", "isDirty", "resetDirty", "isValid"]);
};
export const et2_IResizeable = "et2_IResizeable";
et2_implements_registry.et2_IResizeable = function (obj) {
return implements_methods(obj, ["resize"]);
};
export const et2_IAligned = "et2_IAligned";
et2_implements_registry.et2_IAligned = function (obj) {
return implements_methods(obj, ["get_align"]);
};
export const et2_ISubmitListener = "et2_ISubmitListener";
et2_implements_registry.et2_ISubmitListener = function (obj) {
return implements_methods(obj, ["submit"]);
};
export const et2_IDetachedDOM = "et2_IDetachedDOM";
et2_implements_registry.et2_IDetachedDOM = function (obj) {
return implements_methods(obj, ["getDetachedAttributes", "getDetachedNodes", "setDetachedAttributes"]);
};
export const et2_IPrint = "et2_IPrint";
et2_implements_registry.et2_IPrint = function (obj) {
return implements_methods(obj, ["beforePrint", "afterPrint"]);
};
export const et2_IExposable = "et2_IExposable";
et2_implements_registry.et2_IExposable = function (obj) {
return implements_methods(obj, ["getMedia"]);
};
//# sourceMappingURL=et2_core_interfaces.js.map

View File

@ -1,149 +0,0 @@
/**
* EGroupware eTemplate2 - Execution layer for legacy event code
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-21
*/
/*egw:uses
et2_interfaces;
et2_core_common;
*/
import { egw } from "../jsapi/egw_global";
import { et2_IDOMNode } from "./et2_core_interfaces";
export function et2_compileLegacyJS(_code, _widget, _context) {
// Replace the javascript pseudo-functions
_code = js_pseudo_funcs(_code, _widget);
// Check whether _code is simply "1" -- if yes replace it accordingly
if (_code === '1') {
_code = 'widget.getInstanceManager().submit(); return false;';
}
// Check whether some pseudo-variables still reside inside of the code,
// if yes, replace them.
if (_code.indexOf("$") >= 0 || _code.indexOf("@") >= 0) {
// Get the content array manager for the widget
var mgr = _widget.getArrayMgr("content");
if (mgr) {
_code = mgr.expandName(_code);
}
}
// Context is the context in which the function will run. Set context to
// null as a default, so that it's possible to find bugs where "this" is
// accessed in the code, but not properly set.
var context = _context ? _context : null;
// Check whether the given widget implements the "et2_IDOMNode"
// interface
if (!context && _widget.implements(et2_IDOMNode)) {
context = _widget.getDOMNode();
}
// Check to see if it's referring to an existing function with no arguments specified.
// If so, bind context & use it directly
if (_code.indexOf("(") === -1) {
var parts = _code.split(".");
var existing_func = parts.pop();
var parent = _widget.egw().window;
for (var i = 0; i < parts.length; ++i) {
if (typeof parent[parts[i]] !== "undefined") {
parent = parent[parts[i]];
}
// Nope
else {
break;
}
}
if (typeof parent[existing_func] === "function") {
return parent[existing_func];
}
}
// Generate the function itself, if it fails, log the error message and
// return a function which always returns false
try {
// Code is app.appname.function, add the arguments so it can be executed
if (typeof _code == 'string' && _code.indexOf('app') == 0 && _code.split('.').length >= 3 && _code.indexOf('(') == -1) {
const parts = _code.split('.');
const app = _widget.getInstanceManager().app_obj;
// check if we need to load the object
if (parts.length === 3 && typeof app[parts[1]] === 'undefined') {
return function (ev, widget) {
return egw.applyFunc(_code, [ev, widget]);
};
}
// Code is app.appname.function, add the arguments so it can be executed
_code += '(ev,widget)';
}
// use app object from etemplate2, which might be private and not just window.app
_code = _code.replace(/(window\.)?app\./, 'widget.getInstanceManager().app_obj.');
var func = new Function('ev', 'widget', _code);
}
catch (e) {
_widget.egw().debug('error', 'Error while compiling JS code ', _code);
return (function () { return false; });
}
// Execute the code and return its results, pass the egw instance and
// the widget
return function (ev) {
// Dump the executed code for debugging
egw.debug('log', 'Executing legacy JS code: ', _code);
if (arguments && arguments.length > 2) {
egw.debug('warn', 'Legacy JS code only supports 2 arguments (event and widget)', _code, arguments);
}
// Return the result of the called function
return func.call(context, ev, _widget);
};
}
/**
* Resolve javascript pseudo functions in onclick or onchange:
* - egw::link('$l','$p') calls egw.link($l,$p)
* - form::name('name') returns expanded name/id taking into account the name at that point of the template hierarchy
* - egw::lang('Message ...') translate the message, calls egw.lang()
* - confirm('message') translates 'message' and adds a '?' if not present
* - window.open() replaces it with egw_openWindowCentered2()
* - xajax_doXMLHTTP('etemplate. replace ajax calls in widgets with special handler not requiring etemplate run rights
*
* @param {string} _val onclick, onchange, ... action
* @param {et2_widget} widget
* @ToDo replace xajax_doXMLHTTP with egw.json()
* @ToDo replace (common) cases of confirm with new dialog, idea: calling function supplys function to call after confirm
* @ToDo template::styles(name) inserts the styles of a named template
* @return string
*/
function js_pseudo_funcs(_val, widget) {
if (_val.indexOf('egw::link(') != -1) {
_val = _val.replace(/egw::link\(/g, 'egw.link(');
}
if (_val.indexOf('form::name(') != -1) {
// et2_form_name doesn't care about ][, just [
var _cname = widget.getPath() ? widget.getPath().join("[") : false;
_val = _val.replace(/form::name\(/g, "'" + widget.getRoot()._inst.uniqueId + "_'+" + (_cname ? "et2_form_name('" + _cname + "'," : '('));
}
if (_val.indexOf('egw::lang(') != -1) {
_val = _val.replace(/egw::lang\(/g, 'egw.lang(');
}
// ToDo: inserts the styles of a named template
/*if (preg_match('/template::styles\(["\']{1}(.*)["\']{1}\)/U',$on,$matches))
{
$tpl = $matches[1] == $this->name ? $this : new etemplate($matches[1]);
$on = str_replace($matches[0],"'<style>".str_replace(array("\n","\r"),'',$tpl->style)."</style>'",$on);
}*/
// translate messages in confirm()
if (_val.indexOf('confirm(') != -1) {
_val = _val.replace(/confirm\((['"])(.*?)(\?)?['"]\)/, "confirm(egw.lang($1$2$1)+'$3')"); // add ? if not there, saves extra phrase
}
// replace window.open() with EGw's egw_openWindowCentered2()
if (_val.indexOf('window.open(') != -1) {
_val = _val.replace(/window.open\('(.*)','(.*)','dependent=yes,width=([^,]*),height=([^,]*),scrollbars=yes,status=(.*)'\)/, "egw_openWindowCentered2('$1', '$2', $3, $4, '$5')");
}
// replace xajax calls to code in widgets, with the "etemplate" handler,
// this allows to call widgets with the current app, otherwise everyone would need etemplate run rights
if (_val.indexOf("xajax_doXMLHTTP('etemplate.") != -1) {
_val = _val.replace(/^xajax_doXMLHTTP\('etemplate\.([a-z]+_widget\.[a-zA-Z0-9_]+)\'/, "xajax_doXMLHTTP('" + egw.getAppName() + ".$1.etemplate'");
}
if (_val.indexOf('this.form.submit()') != -1) {
_val = _val.replace('this.form.submit()', 'widget.getInstanceManager().submit()');
}
return _val;
}
//# sourceMappingURL=et2_core_legacyJSFunctions.js.map

View File

@ -1,353 +0,0 @@
/**
* EGroupware eTemplate2 - A simple PHP expression parser written in JS
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
et2_core_common;
*/
import { egw } from "../jsapi/egw_global";
/**
* Function which compiles the given PHP string to a JS function which can be
* easily executed.
*
* @param _expr is the PHP string expression
* @param _vars is an array with variable names (without the PHP $).
* The parameters have to be passed to the resulting JS function in the same
* order.
*/
export function et2_compilePHPExpression(_expr, _vars) {
if (typeof _vars == "undefined") {
_vars = [];
}
try {
// Initialize the parser object and create the syntax tree for the given
// expression
var parser = _php_parser(_expr);
var syntaxTree = [];
// Parse the given expression as if it was a double quoted string
_php_parseDoubleQuoteString(parser, syntaxTree);
// Transform the generated syntaxTree into a JS string
var js = _php_compileJSCode(_vars, syntaxTree);
// Log the successfull compiling
egw.debug("log", "Compiled PHP " + _expr + " --> " + js);
}
catch (e) {
// if expression does NOT compile use it literally and log a warning, but not stop execution
egw.debug("warn", "Error compiling PHP " + _expr + " --> using it literally (" +
(typeof e == 'string' ? e : e.message) + ")!");
return function () { return _expr; };
}
// Prepate the attributes for the function constuctor
var attrs = [];
for (var i = 0; i < _vars.length; i++) {
attrs.push("_" + _vars[i]);
}
attrs.push(js);
// Create the function and return it
return (Function.apply(Function, attrs));
}
const STATE_DEFAULT = 0;
const STATE_ESCAPED = 1;
const STATE_CURLY_BRACE_OPEN = 2;
const STATE_EXPECT_CURLY_BRACE_CLOSE = 3;
const STATE_EXPECT_RECT_BRACE_CLOSE = 4;
const STATE_EXPR_BEGIN = 5;
const STATE_EXPR_END = 6;
function _throwParserErr(_p, _err) {
throw ("Syntax error while parsing '" + _p.expr + "' at " +
_p.pos + ", " + _err);
}
function _php_parseDoubleQuoteString(_p, _tree) {
// Extract all PHP variables from the string
var state = STATE_DEFAULT;
var str = "";
while (_p.pos < _p.expr.length) {
// Read the current char and then increment the parser position by
// one
var c = _p.expr.charAt(_p.pos++);
switch (state) {
case STATE_DEFAULT:
case STATE_CURLY_BRACE_OPEN:
switch (c) {
case '\\':
state = STATE_ESCAPED;
break;
case '$':
// check for '$$' as used in placeholder syntax, it is NOT expanded and returned as is
if (_p.expr.charAt(_p.pos) == "$" && state == STATE_DEFAULT) {
_p.pos++;
str += '$$';
break;
}
// check for '$' as last char, as in PHP "test$" === 'test$', $ as last char is NOT expanded
if (_p.pos == _p.expr.length) {
str += '$';
break;
}
// check for regular expression "/ $/"
if (_p.expr.charAt(_p.pos) == '/' && _p.expr.charAt(0) == '/') {
str += '$';
break;
}
if (str) {
_tree.push(str);
str = "";
}
// Support for the ${[expr] sytax
if (_p.expr.charAt(_p.pos) == "{" && state != STATE_CURLY_BRACE_OPEN) {
state = STATE_CURLY_BRACE_OPEN;
_p.pos++;
}
if (state == STATE_CURLY_BRACE_OPEN) {
_tree.push(_php_parseVariable(_p));
state = STATE_EXPECT_CURLY_BRACE_CLOSE;
}
else {
_tree.push(_php_parseVariable(_p));
}
break;
case '{':
state = STATE_CURLY_BRACE_OPEN;
break;
default:
if (state == STATE_CURLY_BRACE_OPEN) {
str += '{';
state = STATE_DEFAULT;
}
str += c;
}
break;
case STATE_ESCAPED:
str += c;
break;
case STATE_EXPECT_CURLY_BRACE_CLOSE:
// When returning from the variableEx parser,
// the current char must be a "}"
if (c != "}") {
_throwParserErr(_p, "expected '}', but got " + c);
}
state = STATE_DEFAULT;
break;
}
}
// Throw an error when reaching the end of the string but expecting
// "}"
if (state == STATE_EXPECT_CURLY_BRACE_CLOSE) {
_throwParserErr(_p, "unexpected end of string, expected '}'");
}
// Push the last part of the string onto the syntax tree
if (state == STATE_CURLY_BRACE_OPEN) {
str += "{";
}
if (str) {
_tree.push(str);
}
}
// Regular expression which matches on PHP variable identifiers (without the $)
var PHP_VAR_PREG = /^([A-Za-z0-9_]+)/;
function _php_parseVariableName(_p) {
// Extract the variable name form the expression
var vname = PHP_VAR_PREG.exec(_p.expr.substr(_p.pos));
if (vname) {
// Increment the parser position by the length of vname
_p.pos += vname[0].length;
return { "variable": vname[0], "accessExpressions": [] };
}
_throwParserErr(_p, "expected variable identifier.");
}
function _php_parseVariable(_p) {
// Parse the first variable
var variable = _php_parseVariableName(_p);
// Parse all following variable access identifiers
var state = STATE_DEFAULT;
while (_p.pos < _p.expr.length) {
var c = _p.expr.charAt(_p.pos++);
switch (state) {
case STATE_DEFAULT:
switch (c) {
case "[":
// Parse the expression inside the rect brace
variable.accessExpressions.push(_php_parseExpression(_p));
state = STATE_EXPECT_RECT_BRACE_CLOSE;
break;
default:
_p.pos--;
return variable;
}
break;
case STATE_EXPECT_RECT_BRACE_CLOSE:
if (c != "]") {
_throwParserErr(_p, " expected ']', but got " + c);
}
state = STATE_DEFAULT;
break;
}
}
return variable;
}
/**
* Reads a string delimited by the char _delim or the regExp _delim from the
* current parser context and returns it.
*
* @param {object} _p parser contect
* @param {string} _delim delimiter
* @return {string} string read (or throws an exception)
*/
function _php_readString(_p, _delim) {
var state = STATE_DEFAULT;
var str = "";
while (_p.pos < _p.expr.length) {
var c = _p.expr.charAt(_p.pos++);
switch (state) {
case STATE_DEFAULT:
if (c == "\\") {
state = STATE_ESCAPED;
}
else if (c === _delim || (typeof _delim != "string" && _delim.test(c))) {
return str;
}
else {
str += c;
}
break;
case STATE_ESCAPED:
str += c;
state = STATE_DEFAULT;
break;
}
}
_throwParserErr(_p, "unexpected end of string while parsing string!");
}
function _php_parseExpression(_p) {
var state = STATE_EXPR_BEGIN;
var result = null;
while (_p.pos < _p.expr.length) {
var c = _p.expr.charAt(_p.pos++);
switch (state) {
case STATE_EXPR_BEGIN:
switch (c) {
// Skip whitespace
case " ":
case "\n":
case "\r":
case "\t":
break;
case "\"":
result = [];
var p = _php_parser(_php_readString(_p, "\""));
_php_parseDoubleQuoteString(p, result);
state = STATE_EXPR_END;
break;
case "\'":
result = _php_readString(_p, "'");
state = STATE_EXPR_END;
break;
case "$":
result = _php_parseVariable(_p);
state = STATE_EXPR_END;
break;
default:
_p.pos--;
result = _php_readString(_p, /[^A-Za-z0-9_#]/);
if (!result) {
_throwParserErr(_p, "unexpected char " + c);
}
_p.pos--;
state = STATE_EXPR_END;
break;
}
break;
case STATE_EXPR_END:
switch (c) {
// Skip whitespace
case " ":
case "\n":
case "\r":
case "\t":
break;
default:
_p.pos--;
return result;
}
}
}
_throwParserErr(_p, "unexpected end of string while parsing access expressions!");
}
function _php_parser(_expr) {
return {
expr: _expr,
pos: 0
};
}
function _throwCompilerErr(_err) {
throw ("PHP to JS compiler error, " + _err);
}
function _php_compileVariable(_vars, _variable) {
if (_vars.indexOf(_variable.variable) >= 0) {
// Attach a "_" to the variable name as PHP variable names may start
// with numeric values
var result = "_" + _variable.variable;
// Create the access functions
for (var i = 0; i < _variable.accessExpressions.length; i++) {
result += "[" +
_php_compileString(_vars, _variable.accessExpressions[i]) +
"]";
}
return '(typeof _' + _variable.variable + ' != "undefined" && typeof ' + result + '!="undefined" && ' + result + ' != null ? ' + result + ':"")';
}
_throwCompilerErr("Variable $" + _variable.variable + " is not defined.");
}
function _php_compileString(_vars, _string) {
if (!(_string instanceof Array)) {
_string = [_string];
}
var parts = [];
var hasString = false;
for (var i = 0; i < _string.length; i++) {
var part = _string[i];
if (typeof part == "string") {
hasString = true;
// Escape all "'" and "\" chars and add the string to the parts array
parts.push("'" + part.replace(/\\/g, "\\\\").replace(/'/g, "\\'") + "'");
}
else {
parts.push(_php_compileVariable(_vars, part));
}
}
if (!hasString) // Force the result to be of the type string
{
parts.push('""');
}
return parts.join(" + ");
}
function _php_compileJSCode(_vars, _tree) {
// Each tree starts with a "string"
return "return " + _php_compileString(_vars, _tree) + ";";
}
// Include this code in in order to test the above code
/*(function () {
var row = 10;
var row_cont = {"title": "Hello World!"};
var cont = {10: row_cont};
function test(_php, _res)
{
console.log(
et2_compilePHPExpression(_php, ["row", "row_cont", "cont"])
(row, row_cont, cont) === _res);
}
test("${row}[title]", "10[title]");
test("{$row_cont[title]}", "Hello World!");
test('{$cont["$row"][\'title\']}', "Hello World!");
test("$row_cont[${row}[title]]");
test("\\\\", "\\");
test("", "");
})();*/
//# sourceMappingURL=et2_core_phpExpressionCompiler.js.map

View File

@ -1,124 +0,0 @@
/**
* EGroupware eTemplate2 - JS widget class with value attribute and auto loading
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
*/
import { et2_baseWidget } from './et2_core_baseWidget';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_csvSplit, et2_no_init } from "./et2_core_common";
/**
* et2_valueWidget is the base class for et2_inputWidget - valueWidget introduces
* the "value" attribute and automatically loads it from the "content" array
* after loading from XML.
*/
export class et2_valueWidget extends et2_baseWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_valueWidget._attributes, _child || {}));
this.label = '';
this._labelContainer = null;
}
/**
*
* @param _attrs
*/
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
if (this.id) {
// Set the value for this element
var contentMgr = this.getArrayMgr("content");
if (contentMgr != null) {
let val = contentMgr.getEntry(this.id, false, true);
if (val !== null) {
_attrs["value"] = val;
}
}
// Check for already inside namespace
if (this._createNamespace() && this.getArrayMgr("content").perspectiveData.owner == this) {
_attrs["value"] = this.getArrayMgr("content").data;
}
}
}
set_label(_value) {
// Abort if there was no change in the label
if (_value == this.label) {
return;
}
if (_value) {
// Create the label container if it didn't exist yet
if (this._labelContainer == null) {
this._labelContainer = jQuery(document.createElement("label"))
.addClass("et2_label");
this.getSurroundings().insertDOMNode(this._labelContainer[0]);
}
// Clear the label container.
this._labelContainer.empty();
// Create the placeholder element and set it
var ph = document.createElement("span");
this.getSurroundings().setWidgetPlaceholder(ph);
// Split the label at the "%s"
var parts = et2_csvSplit(_value, 2, "%s");
// Update the content of the label container
for (var i = 0; i < parts.length; i++) {
if (parts[i]) {
this._labelContainer.append(document.createTextNode(parts[i]));
}
if (i == 0) {
this._labelContainer.append(ph);
}
}
// add class if label is empty
this._labelContainer.toggleClass('et2_label_empty', !_value || !parts[0]);
}
else {
// Delete the labelContainer from the surroundings object
if (this._labelContainer) {
this.getSurroundings().removeDOMNode(this._labelContainer[0]);
}
this._labelContainer = null;
}
// Update the surroundings in order to reflect the change in the label
this.getSurroundings().update();
// Copy the given value
this.label = _value;
}
get_value() {
return this.value;
}
/**
* Set value of widget
*
* @param {string} _value value to set
*/
set_value(_value) {
this.value = _value;
}
}
et2_valueWidget._attributes = {
"label": {
"name": "Label",
"default": "",
"type": "string",
"description": "The label is displayed by default in front (for radiobuttons behind) each widget (if not empty). If you want to specify a different position, use a '%s' in the label, which gets replaced by the widget itself. Eg. '%s Name' to have the label Name behind a checkbox. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
"translate": true
},
"value": {
"name": "Value",
"description": "The value of the widget",
"type": "rawstring",
"default": et2_no_init
}
};
//# sourceMappingURL=et2_core_valueWidget.js.map

View File

@ -1,910 +0,0 @@
/**
* EGroupware eTemplate2 - JS Widget base class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
jsapi.egw;
et2_core_xml;
et2_core_common;
et2_core_inheritance;
et2_core_arrayMgr;
*/
import { ClassWithAttributes } from './et2_core_inheritance';
import { et2_arrayMgr } from "./et2_core_arrayMgr";
import { egw } from "../jsapi/egw_global";
import { et2_cloneObject, et2_csvSplit } from "./et2_core_common";
import { et2_compileLegacyJS } from "./et2_core_legacyJSFunctions";
import { et2_IDOMNode, et2_IInputNode } from "./et2_core_interfaces";
/**
* The registry contains all XML tag names and the corresponding widget
* constructor.
*/
export var et2_registry = {};
export var et2_attribute_registry = {};
/**
* Registers the widget class defined by the given constructor, registers all its class attributes, and associates it
* with the types in the _types array.
*
* @param {function} _constructor constructor
* @param {array} _types widget types _constructor wants to register for
*/
export function et2_register_widget(_constructor, _types) {
"use strict";
et2_attribute_registry[_constructor.name] = ClassWithAttributes.buildAttributes(_constructor);
// Iterate over all given types and register those
for (var i = 0; i < _types.length; i++) {
var type = _types[i].toLowerCase();
// Check whether a widget has already been registered for one of the
// types.
if (et2_registry[type]) {
egw.debug("warn", "Widget class registered for " + type +
" will be overwritten.");
}
et2_registry[type] = _constructor;
}
}
/**
* Creates a widget registered for the given tag-name. If "readonly" is listed
* inside the attributes, et2_createWidget will try to use the "_ro" type of the
* widget.
*
* @param _name is the name of the widget with which it is registered. If the
* widget is not found, an et2_placeholder will be created.
* @param _attrs is an associative array with attributes. If not passed, it will
* default to an empty object.
* @param _parent is the parent to which the element will be attached. If _parent
* is not passed, it will default to null. Then you have to attach the element
* to a parent using the addChild or insertChild method.
*/
export function et2_createWidget(_name, _attrs, _parent) {
"use strict";
if (typeof _attrs == "undefined") {
_attrs = {};
}
if (typeof _attrs != "object") {
_attrs = {};
}
if (typeof _parent == "undefined") {
_parent = null;
}
// Parse the "readonly" and "type" flag for this element here, as they
// determine which constructor is used
var nodeName = _attrs["type"] = _name;
var readonly = _attrs["readonly"] =
typeof _attrs["readonly"] == "undefined" ? false : _attrs["readonly"];
// Get the constructor - if the widget is readonly, use the special "_ro"
// constructor if it is available
let constructor = et2_registry[typeof et2_registry[nodeName] == "undefined" ? 'placeholder' : nodeName];
if (readonly && typeof et2_registry[nodeName + "_ro"] != "undefined") {
constructor = et2_registry[nodeName + "_ro"];
}
// Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], _attrs);
// Create the new widget and return it
return new constructor(_parent, _attrs);
}
/**
* The et2 widget base class.
*
* @augments ClassWithAttributes
*/
export class et2_widget extends ClassWithAttributes {
/**
* Widget constructor
*
* To implement the attributes inheritance and overriding each extending class/widget needs to call:
*
* super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_DOMWidget._attributes, _child || {}));
*
* @param _parent is the parent object from the XML tree which contains this
* object. The default constructor always adds the new instance to the
* children list of the given parent object. _parent may be NULL.
* @param _attrs is an associative array of attributes.
* @param _child attributes object from the child
*/
constructor(_parent, _attrs, _child) {
super(); // because we in the top of the widget hierarchy
this._children = [];
this._mgrs = {};
/**
* This is used and therefore it we can not (yet) make it private
*
* @deprecated use this.getInstanceMgr()
*/
this._inst = null;
this.attributes = ClassWithAttributes.extendAttributes(et2_widget._attributes, _child || {});
// Check whether all attributes are available
if (typeof _parent == "undefined") {
_parent = null;
}
if (typeof _attrs == "undefined") {
_attrs = {};
}
if (_attrs.attributes) {
jQuery.extend(_attrs, _attrs.attributes);
}
// Initialize all important parameters
this._mgrs = {};
this._inst = null;
this._children = [];
this._type = _attrs["type"];
this.id = _attrs["id"];
// Add this widget to the given parent widget
if (_parent != null) {
_parent.addChild(this);
}
// The supported widget classes array defines a whitelist for all widget
// classes or interfaces child widgets have to support.
this.supportedWidgetClasses = [et2_widget];
if (_attrs["id"]) {
// Create a namespace for this object
if (this._createNamespace()) {
this.checkCreateNamespace(_attrs);
}
}
if (this.id) {
//this.id = this.id.replace(/\[/g,'&#x5B;').replace(/]/g,'&#x5D;');
}
// Add all attributes hidden in the content arrays to the attributes
// parameter
this.transformAttributes(_attrs);
// Create a local copy of the options object
this.options = et2_cloneObject(_attrs);
}
/**
* The destroy function destroys all children of the widget, removes itself
* from the parents children list.
* In all classes derrived from et2_widget ALWAYS override the destroy
* function and remove ALL references to other objects. Also remember to
* unbind ANY event this widget created and to remove all DOM-Nodes it
* created.
*/
destroy() {
// Call the destructor of all children
for (var i = this._children.length - 1; i >= 0; i--) {
this._children[i].destroy();
}
// Remove this element from the parent, if it exists
if (typeof this._parent != "undefined" && this._parent !== null) {
this._parent.removeChild(this);
}
// Free the array managers if they belong to this widget
for (var key in this._mgrs) {
if (this._mgrs[key] && this._mgrs[key].owner == this) {
this._mgrs[key].destroy();
}
}
}
getType() {
return this._type;
}
setType(_type) {
this._type = _type;
}
/**
* Creates a copy of this widget. The parameters given are passed to the
* constructor of the copied object. If the parameters are omitted, _parent
* is defaulted to null
*
* @param {et2_widget} _parent parent to set for clone, default null
*/
clone(_parent) {
// Default _parent to null
if (typeof _parent == "undefined") {
_parent = null;
}
// Create the copy
var copy = new this.constructor(_parent, this.options);
// Assign this element to the copy
copy.assign(this);
return copy;
}
assign(_obj) {
if (typeof _obj._children == "undefined") {
this.egw().debug("log", "Foo!");
}
// Create a clone of all child elements of the given object
for (var i = 0; i < _obj._children.length; i++) {
_obj._children[i].clone(this);
}
// Copy a reference to the content array manager
this.setArrayMgrs(_obj.mgrs);
}
/**
* Returns the parent widget of this widget
*/
getParent() {
return this._parent;
}
/**
* Returns the list of children of this widget.
*/
getChildren() {
return this._children;
}
/**
* Returns the base widget
*/
getRoot() {
if (this._parent != null) {
return this._parent.getRoot();
}
else {
return this;
}
}
/**
* Inserts an child at the end of the list.
*
* @param _node is the node which should be added. It has to be an instance
* of et2_widget
*/
addChild(_node) {
this.insertChild(_node, this._children.length);
}
/**
* Inserts a child at the given index.
*
* @param _node is the node which should be added. It has to be an instance
* of et2_widget
* @param _idx is the position at which the element should be added.
*/
insertChild(_node, _idx) {
// Check whether the node is one of the supported widget classes.
if (this.isOfSupportedWidgetClass(_node)) {
// Remove the node from its original parent
if (_node._parent) {
_node._parent.removeChild(_node);
}
_node._parent = this;
this._children.splice(_idx, 0, _node);
}
else {
this.egw().debug("error", "Widget " + _node._type + " is not supported by this widget class", this);
// throw("Widget is not supported by this widget class!");
}
}
/**
* Removes the child but does not destroy it.
*
* @param {et2_widget} _node child to remove
*/
removeChild(_node) {
// Retrieve the child from the child list
var idx = this._children.indexOf(_node);
if (idx >= 0) {
// This element is no longer parent of the child
_node._parent = null;
this._children.splice(idx, 1);
}
}
/**
* Searches an element by id in the tree, descending into the child levels.
*
* @param _id is the id you're searching for
*/
getWidgetById(_id) {
if (this.id == _id) {
return this;
}
if (!this._children)
return null;
for (var i = 0; i < this._children.length; i++) {
var elem = this._children[i].getWidgetById(_id);
if (elem != null) {
return elem;
}
}
if (this.id && _id.indexOf('[') > -1 && this._children.length) {
var ids = (new et2_arrayMgr()).explodeKey(_id);
var widget = this;
for (var i = 0; i < ids.length && widget !== null; i++) {
widget = widget.getWidgetById(ids[i]);
}
return widget;
}
return null;
}
/**
* Function which allows iterating over the complete widget tree.
*
* @param _callback is the function which should be called for each widget
* @param _context is the context in which the function should be executed
* @param _type is an optional parameter which specifies a class/interface
* the elements have to be instanceOf.
*/
iterateOver(_callback, _context, _type) {
if (typeof _type == "undefined") {
_type = et2_widget;
}
if (this.isInTree() && this.instanceOf(_type)) {
_callback.call(_context, this);
}
for (var i = 0; i < this._children.length; i++) {
this._children[i].iterateOver(_callback, _context, _type);
}
}
/**
* Returns true if the widget currently resides in the visible part of the
* widget tree. E.g. Templates which have been cloned are not in the visible
* part of the widget tree.
*
* @param _sender
* @param {boolean} _vis can be used by widgets overwriting this function - simply
* write
* return this._super(inTree);
* when calling this function the _vis parameter does not have to be supplied.
*/
isInTree(_sender, _vis) {
if (typeof _vis == "undefined") {
_vis = true;
}
if (this._parent) {
return _vis && this._parent.isInTree(this);
}
return _vis;
}
isOfSupportedWidgetClass(_obj) {
for (var i = 0; i < this.supportedWidgetClasses.length; i++) {
if (_obj instanceof this.supportedWidgetClasses[i]) {
return true;
}
}
return false;
}
/**
* The parseXMLAttrs function takes an XML DOM attributes object
* and adds the given attributes to the _target associative array. This
* function also parses the legacyOptions.
*
* @param _attrsObj is the XML DOM attributes object
* @param {object} _target is the object to which the attributes should be written.
* @param {et2_widget} _proto prototype with attributes and legacyOptions attribute
*/
parseXMLAttrs(_attrsObj, _target, _proto) {
// Check whether the attributes object is really existing, if not abort
if (typeof _attrsObj == "undefined") {
return;
}
// Iterate over the given attributes and parse them
var mgr = this.getArrayMgr("content");
for (var i = 0; i < _attrsObj.length; i++) {
var attrName = _attrsObj[i].name;
var attrValue = _attrsObj[i].value;
// Special handling for the legacy options
if (attrName == "options" && _proto.constructor.legacyOptions && _proto.constructor.legacyOptions.length > 0) {
let legacy = _proto.constructor.legacyOptions || [];
let attrs = et2_attribute_registry[Object.getPrototypeOf(_proto).constructor.name] || {};
// Check for modifications on legacy options here. Normal modifications
// are handled in widget constructor, but it's too late for legacy options then
if (_target.id && this.getArrayMgr("modifications").getEntry(_target.id)) {
var mod = this.getArrayMgr("modifications").getEntry(_target.id);
if (typeof mod.options != "undefined")
attrValue = _attrsObj[i].value = mod.options;
}
// expand legacyOptions with content
if (attrValue.charAt(0) == '@' || attrValue.indexOf('$') != -1) {
attrValue = mgr.expandName(attrValue);
}
// Parse the legacy options (as a string, other types not allowed)
var splitted = et2_csvSplit(attrValue + "");
for (var j = 0; j < splitted.length && j < legacy.length; j++) {
// Blank = not set, unless there's more legacy options provided after
if (splitted[j].trim().length === 0 && legacy.length >= splitted.length)
continue;
// Check to make sure we don't overwrite a current option with a legacy option
if (typeof _target[legacy[j]] === "undefined") {
attrValue = splitted[j];
/**
If more legacy options than expected, stuff them all in the last legacy option
Some legacy options take a comma separated list.
*/
if (j == legacy.length - 1 && splitted.length > legacy.length) {
attrValue = splitted.slice(j);
}
var attr = et2_attribute_registry[_proto.constructor.name][legacy[j]] || {};
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type == "boolean") {
attrValue = mgr.parseBoolExpression(attrValue);
}
else if (typeof attrValue != "object") {
attrValue = mgr.expandName(attrValue);
}
_target[legacy[j]] = attrValue;
}
}
}
else if (attrName == "readonly" && typeof _target[attrName] != "undefined") {
// do NOT overwrite already evaluated readonly attribute
}
else {
let attrs = et2_attribute_registry[_proto.constructor.name] || {};
if (mgr != null && typeof attrs[attrName] != "undefined") {
var attr = attrs[attrName];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type == "boolean") {
attrValue = mgr.parseBoolExpression(attrValue);
}
else {
attrValue = mgr.expandName(attrValue);
}
}
// Set the attribute
_target[attrName] = attrValue;
}
}
}
/**
* Apply the "modifications" to the element and translate attributes marked
* with "translate: true"
*
* @param {object} _attrs
*/
transformAttributes(_attrs) {
// Apply the content of the modifications array
if (this.id) {
if (typeof this.id != "string") {
console.log(this.id);
}
if (this.getArrayMgr("modifications")) {
var data = this.getArrayMgr("modifications").getEntry(this.id);
// Check for already inside namespace
if (this._createNamespace() && this.getArrayMgr("modifications").perspectiveData.owner == this) {
data = this.getArrayMgr("modifications").data;
}
if (typeof data === 'object') {
for (var key in data) {
_attrs[key] = data[key];
}
}
}
}
// Translate the attributes
for (var key in _attrs) {
if (_attrs[key] && typeof this.attributes[key] != "undefined") {
if (this.attributes[key].translate === true ||
(this.attributes[key].translate === "!no_lang" && !_attrs["no_lang"])) {
let value = _attrs[key];
// allow statustext to contain multiple translated sub-strings eg: {Firstname}.{Lastname}
if (value.indexOf('{') !== -1) {
const egw = this.egw();
_attrs[key] = value.replace(/{([^}]+)}/g, function (str, p1) {
return egw.lang(p1);
});
}
else {
_attrs[key] = this.egw().lang(value);
}
}
}
}
}
/**
* Create a et2_widget from an XML node.
*
* First the type and attributes are read from the node. Then the readonly & modifications
* arrays are checked for changes specific to the loaded data. Then the appropriate
* constructor is called. After the constructor returns, the widget has a chance to
* further initialize itself from the XML node when the widget's loadFromXML() method
* is called with the node.
*
* @param _node XML node to read
* @param _name XML node name
*
* @return et2_widget
*/
createElementFromNode(_node, _name) {
var attributes = {};
// Parse the "readonly" and "type" flag for this element here, as they
// determine which constructor is used
var _nodeName = attributes["type"] = _node.getAttribute("type") ?
_node.getAttribute("type") : _node.nodeName.toLowerCase();
var readonly = attributes["readonly"] = this.getArrayMgr("readonlys") ?
this.getArrayMgr("readonlys").isReadOnly(_node.getAttribute("id"), _node.getAttribute("readonly"), typeof this.readonly !== 'undefined' ? this.readonly : this.options.readonly) : false;
// Check to see if modifications change type
var modifications = this.getArrayMgr("modifications");
if (modifications && _node.getAttribute("id")) {
var entry = modifications.getEntry(_node.getAttribute("id"));
if (entry == null) {
// Try again, but skip the fancy stuff
// TODO: Figure out why the getEntry() call doesn't always work
var entry = modifications.data[_node.getAttribute("id")];
if (entry) {
this.egw().debug("warn", "getEntry(" + _node.getAttribute("id") + ") failed, but the data is there.", modifications, entry);
}
else {
// Try the root, in case a namespace got missed
entry = modifications.getRoot().getEntry(_node.getAttribute("id"));
}
}
if (entry && entry.type && typeof entry.type === 'string') {
_nodeName = attributes["type"] = entry.type;
}
entry = null;
}
// if _nodeName / type-attribute contains something to expand (eg. type="@${row}[type]"),
// we need to expand it now as it defines the constructor and by that attributes parsed via parseXMLAttrs!
if (_nodeName.charAt(0) == '@' || _nodeName.indexOf('$') >= 0) {
_nodeName = attributes["type"] = this.getArrayMgr('content').expandName(_nodeName);
}
// Get the constructor - if the widget is readonly, use the special "_ro"
// constructor if it is available
var constructor = et2_registry[typeof et2_registry[_nodeName] == "undefined" ? 'placeholder' : _nodeName];
if (readonly === true && typeof et2_registry[_nodeName + "_ro"] != "undefined") {
constructor = et2_registry[_nodeName + "_ro"];
}
// Parse the attributes from the given XML attributes object
this.parseXMLAttrs(_node.attributes, attributes, constructor.prototype);
// Do an sanity check for the attributes
ClassWithAttributes.generateAttributeSet(et2_attribute_registry[constructor.name], attributes);
if (undefined == window.customElements.get(_nodeName)) {
// Creates the new widget, passes this widget as an instance and
// passes the widgetType. Then it goes on loading the XML for it.
var widget = new constructor(this, attributes);
// Load the widget itself from XML
widget.loadFromXML(_node);
}
else {
widget = this.loadWebComponent(_nodeName, _node, attributes);
if (this.addChild) {
// webcomponent going into old et2_widget
this.addChild(widget);
}
}
return widget;
}
/**
* Load a Web Component
* @param _nodeName
* @param _node
*/
loadWebComponent(_nodeName, _node, attributes) {
let widget = document.createElement(_nodeName);
widget.textContent = _node.textContent;
// Apply any set attributes
_node.getAttributeNames().forEach(attribute => widget.setAttribute(attribute, attributes[attribute]));
return widget;
}
/**
* Loads the widget tree from an XML node
*
* @param _node xml node
*/
loadFromXML(_node) {
// Load the child nodes.
for (var i = 0; i < _node.childNodes.length; i++) {
var node = _node.childNodes[i];
var widgetType = node.nodeName.toLowerCase();
if (widgetType == "#comment") {
continue;
}
if (widgetType == "#text") {
if (node.data.replace(/^\s+|\s+$/g, '')) {
this.loadContent(node.data);
}
continue;
}
// Create the new element
this.createElementFromNode(node);
}
}
/**
* Called whenever textNodes are loaded from the XML tree
*
* @param _content
*/
loadContent(_content) {
}
/**
* Called when loading the widget (sub-tree) is finished. First when this
* function is called, the DOM-Tree is created. loadingFinished is
* recursively called for all child elements. Do not directly override this
* function but the doLoadingFinished function which is executed before
* descending deeper into the DOM-Tree.
*
* Some widgets (template) do not load immediately because they request
* additional resources via AJAX. They will return a Deferred Promise object.
* If you call loadingFinished(promises) after creating such a widget
* programmatically, you might need to wait for it to fully complete its
* loading before proceeding. In that case use:
* <code>
* var promises = [];
* widget.loadingFinished(promises);
* jQuery.when.apply(null, promises).done( doneCallback );
* </code>
* @see {@link http://api.jquery.com/category/deferred-object/|jQuery Deferred}
*
* @param {Promise[]} promises List of promises from widgets that are not done. Pass an empty array, it will be filled if needed.
*/
loadingFinished(promises) {
// Call all availble setters
this.initAttributes(this.options);
// Make sure promises is defined to avoid errors.
// We'll warn (below) if programmer should have passed it.
if (typeof promises == "undefined") {
promises = [];
var warn_if_deferred = true;
}
var loadChildren = function () {
// Descend recursively into the tree
for (var i = 0; i < this._children.length; i++) {
try {
this._children[i].loadingFinished(promises);
}
catch (e) {
egw.debug("error", "There was an error with a widget:\nError:%o\nProblem widget:%o", e.valueOf(), this._children[i], e.stack);
}
}
};
var result = this.doLoadingFinished();
if (typeof result == "boolean" && result) {
// Simple widget finishes nicely
loadChildren.apply(this, arguments);
}
else if (typeof result == "object" && result.done) {
// Warn if list was not provided
if (warn_if_deferred) {
// Might not be a problem, but if you need the widget to be really loaded, it could be
egw.debug("warn", "Loading was deferred for widget %o, but creator is not checking. Pass a list to loadingFinished().", this);
}
// Widget is waiting. Add to the list
promises.push(result);
// Fihish loading when it's finished
result.done(jQuery.proxy(loadChildren, this));
}
}
/**
* The initAttributes function sets the attributes to their default
* values. The attributes are not overwritten, which means, that the
* default is only set, if either a setter exists or this[propName] does
* not exist yet.
*
* Overwritten here to compile legacy JS code in attributes of type "js"
*
* @param {object} _attrs
*/
initAttributes(_attrs) {
for (var key in _attrs) {
if (typeof this.attributes[key] != "undefined" && !this.attributes[key].ignore && !(_attrs[key] == undefined)) {
var val = _attrs[key];
// compile string values of attribute type "js" to functions
if (this.attributes[key].type == 'js' && typeof _attrs[key] == 'string') {
val = et2_compileLegacyJS(val, this, this.implements(et2_IInputNode) ? this.getInputNode() :
(this.implements(et2_IDOMNode) ? this.getDOMNode() : null));
}
this.setAttribute(key, val, false);
}
}
}
/**
* Does specific post-processing after the widget is loaded. Most widgets should not
* need to do anything here, it should all be done before.
*
* @return {boolean|Promise} True if the widget is fully loaded, false to avoid procesing children,
* or a Promise if loading is not actually finished (eg. waiting for AJAX)
*
* @see {@link http://api.jquery.com/deferred.promise/|jQuery Promise}
*/
doLoadingFinished() {
return true;
}
/**
* The egw function returns the instance of the client side api belonging
* to this widget tree. The api instance can be set in the "container"
* widget using the setApiInstance function.
*/
egw() {
// The _egw property is not set
if (typeof this._egw === 'undefined') {
if (this._parent != null) {
return this._parent.egw();
}
// Get the window this object belongs to
var wnd = null;
if (this.implements(et2_IDOMNode)) {
var node = this.getDOMNode();
if (node && node.ownerDocument) {
wnd = node.ownerDocument.parentNode || node.ownerDocument.defaultView;
}
}
// If we're the root object, return the phpgwapi API instance
return egw('phpgwapi', wnd);
}
return this._egw;
}
/**
* Sets the client side api instance. It can be retrieved by the widget tree
* by using the "egw()" function.
*
* @param {IegwAppLocal} _egw egw object to set
*/
setApiInstance(_egw) {
this._egw = _egw;
}
/**
* Sets all array manager objects - this function can be used to set the
* root array managers of the container object.
*
* @param {object} _mgrs
*/
setArrayMgrs(_mgrs) {
this._mgrs = et2_cloneObject(_mgrs);
}
/**
* Returns an associative array containing the top-most array managers.
*
* @param _mgrs is used internally and should not be supplied.
*/
getArrayMgrs(_mgrs) {
if (typeof _mgrs == "undefined") {
_mgrs = {};
}
// Add all managers of this object to the result, if they have not already
// been set in the result
for (var key in this._mgrs) {
if (typeof _mgrs[key] == "undefined") {
_mgrs[key] = this._mgrs[key];
}
}
// Recursively applies this function to the parent widget
if (this._parent) {
this._parent.getArrayMgrs(_mgrs);
}
return _mgrs;
}
/**
* Sets the array manager for the given part
*
* @param {string} _part which array mgr to set
* @param {object} _mgr
*/
setArrayMgr(_part, _mgr) {
this._mgrs[_part] = _mgr;
}
/**
* Returns the array manager object for the given part
*
* @param {string} managed_array_type name of array mgr to return
*/
getArrayMgr(managed_array_type) {
if (this._mgrs && typeof this._mgrs[managed_array_type] != "undefined") {
return this._mgrs[managed_array_type];
}
else if (this._parent) {
return this._parent.getArrayMgr(managed_array_type);
}
return null;
}
/**
* Checks whether a namespace exists for this element in the content array.
* If yes, an own perspective of the content array is created. If not, the
* parent content manager is used.
*
* Constructor attributes are passed in case a child needs to make decisions
*/
checkCreateNamespace(_attrs) {
// Get the content manager
var mgrs = this.getArrayMgrs();
for (var key in mgrs) {
var mgr = mgrs[key];
// Get the original content manager if we have already created a
// perspective for this node
if (typeof this._mgrs[key] != "undefined" && mgr.perspectiveData.owner == this) {
mgr = mgr.parentMgr;
}
// Check whether the manager has a namespace for the id of this object
var entry = mgr.getEntry(this.id);
if (typeof entry === 'object' && entry !== null || this.id) {
// The content manager has an own node for this object, so
// create an own perspective.
this._mgrs[key] = mgr.openPerspective(this, this.id);
}
else {
// The current content manager does not have an own namespace for
// this element, so use the content manager of the parent.
delete (this._mgrs[key]);
}
}
}
/**
* Widgets that do support a namespace should override and return true.
*
* Since a private attribute doesn't get instanciated properly before it's needed,
* we use a method so we can get what we need while still in the constructor.
*
* @private
*/
_createNamespace() {
return false;
}
/**
* Sets the instance manager object (of type etemplate2, see etemplate2.js)
*
* @param {etemplate2} _inst
*/
setInstanceManager(_inst) {
this._inst = _inst;
}
/**
* Returns the instance manager
*
* @return {etemplate2}
*/
getInstanceManager() {
if (this._inst != null) {
return this._inst;
}
else if (this._parent) {
return this._parent.getInstanceManager();
}
return null;
}
/**
* Returns the path into the data array. By default, array manager takes care of
* this, but some extensions need to override this
*/
getPath() {
var path = this.getArrayMgr("content").getPath();
// Prevent namespaced widgets with value from going an extra layer deep
if (this.id && this._createNamespace() && path[path.length - 1] == this.id)
path.pop();
return path;
}
}
et2_widget._attributes = {
"id": {
"name": "ID",
"type": "string",
"description": "Unique identifier of the widget"
},
"no_lang": {
"name": "No translation",
"type": "boolean",
"default": false,
"description": "If true, no translations are made for this widget"
},
/**
* Ignore the "span" property by default - it is read by the grid and
* other widgets.
*/
"span": {
"ignore": true
},
/**
* Ignore the "type" tag - it is read by the "createElementFromNode"
* function and passed as second parameter of the widget constructor
*/
"type": {
"name": "Widget type",
"type": "string",
"ignore": true,
"description": "What kind of widget this is"
},
/**
* Ignore the readonly tag by default - its also read by the
* "createElementFromNode" function.
*/
"readonly": {
"ignore": true
},
/**
* Widget's attributes
*/
attributes: {
"name": "Widget attributes",
"type": "any",
"ignore": true,
"description": "Object of widget attributes"
}
};
// Set the legacyOptions array to the names of the properties the "options"
// attribute defines.
et2_widget.legacyOptions = [];
//# sourceMappingURL=et2_core_widget.js.map

View File

@ -1,88 +0,0 @@
/**
* EGroupware eTemplate2 - JS XML Code
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
import "../../../vendor/bower-asset/jquery/dist/jquery.min.js";
import "../../../vendor/bower-asset/jquery-ui/jquery-ui.js";
import "../jquery/jquery.noconflict.js";
import { egw } from "../jsapi/egw_global.js";
/**
* Loads the given URL asynchronously from the server
*
* We make the Ajax call through main-windows jQuery object, to ensure cached copy
* in main-windows etemplate2 prototype works in IE too!
*
* @param {string} _url
* @param {function} _callback function(_xml)
* @param {object} _context for _callback
* @param {function} _fail_callback function(_xml)
* @return Promise
*/
export function et2_loadXMLFromURL(_url, _callback, _context, _fail_callback) {
if (typeof _context == "undefined") {
_context = null;
}
// use window object from main window with same algorithm as for the template cache
let win;
try {
if (opener && opener.etemplate2) {
win = opener;
}
}
catch (e) {
// catch security exception if opener is from a different domain
}
if (typeof win == "undefined") {
win = egw.top;
}
return win.jQuery.ajax({
// we add the full url (protocol and domain) as sometimes just the path
// gives a CSP error interpreting it as file:///path
// (if there are a enough 404 errors in html content ...)
url: (_url[0] == '/' ? location.protocol + '//' + location.host : '') + _url,
context: _context,
type: 'GET',
dataType: 'xml',
success: function (_data, _status, _xmlhttp) {
if (typeof _callback === 'function') {
_callback.call(_context, _data.documentElement);
}
},
error: function (_xmlhttp, _err) {
egw().debug('error', 'Loading eTemplate from ' + _url + ' failed! ' + _xmlhttp.status + ' ' + _xmlhttp.statusText);
if (typeof _fail_callback === 'function') {
_fail_callback.call(_context, _err);
}
}
});
}
export function et2_directChildrenByTagName(_node, _tagName) {
// Normalize the tag name
_tagName = _tagName.toLowerCase();
let result = [];
for (let i = 0; i < _node.childNodes.length; i++) {
if (_tagName == _node.childNodes[i].nodeName.toLowerCase()) {
result.push(_node.childNodes[i]);
}
}
return result;
}
export function et2_filteredNodeIterator(_node, _callback, _context) {
for (let i = 0; i < _node.childNodes.length; i++) {
let node = _node.childNodes[i];
let nodeName = node.nodeName.toLowerCase();
if (nodeName.charAt(0) != "#") {
_callback.call(_context, node, nodeName);
}
}
}
export function et2_readAttrWithDefault(_node, _name, _default) {
let val = _node.getAttribute(_name);
return (val === null) ? _default : val;
}
//# sourceMappingURL=et2_core_xml.js.map

View File

@ -1,479 +0,0 @@
/**
* EGroupware eTemplate2 - dataview code
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_common;
et2_dataview_model_columns;
et2_dataview_view_grid;
et2_dataview_view_rowProvider;
et2_dataview_view_resizeable;
*/
import { et2_dataview_column, et2_dataview_columns } from './et2_dataview_model_columns';
import { et2_dataview_view_resizable } from "./et2_dataview_view_resizeable";
import { et2_dataview_grid } from "./et2_dataview_view_grid";
import { et2_dataview_rowProvider } from "./et2_dataview_view_rowProvider";
import { egw } from "../jsapi/egw_global";
/**
* The et2_dataview class is the main class for displaying a dataview. The
* dataview class manages the creation of the outer html nodes (like the table,
* header, etc.) and contains the root container: an instance of
* et2_dataview_view_grid, which can be accessed using the "grid" property of
* this object.
*
* @augments Class
*/
export class et2_dataview {
/**
* Constructor for the grid container
*
* @param {DOMElement} _parentNode is the DOM-Node into which the grid view will be inserted
* @param {egw} _egw
* @memberOf et2_dataview
*/
constructor(_parentNode, _egw) {
// Copy the arguments
this.parentNode = jQuery(_parentNode);
this.egw = _egw;
// Initialize some variables
this.columnNodes = []; // Array with the header containers
this.columns = [];
this.columnMgr = null;
this.rowProvider = null;
this.width = 0;
this.height = 0;
this.uniqueId = "gridCont_" + this.egw.uid();
// Build the base nodes
this._createElements();
// Read the browser dependant variables
this._getDepVars();
}
/**
* Destroys the object, removes all dom nodes and clears all references.
*/
destroy() {
// Clear the columns
this._clearHeader();
// Free the grid
if (this.grid) {
this.grid.destroy();
}
// Free the row provider
if (this.rowProvider) {
this.rowProvider.destroy();
}
// Detatch the outer element
this.table.remove();
}
/**
* Clears all data rows and reloads them
*/
clear() {
if (this.grid) {
this.grid.clear();
}
}
/**
* Returns the column container node for the given column index
*
* @param _columnIdx the integer column index
*/
getHeaderContainerNode(_columnIdx) {
if (typeof this.columnNodes[_columnIdx] != "undefined") {
return this.columnNodes[_columnIdx].container[0];
}
return null;
}
/**
* Sets the column descriptors and creates the column header according to it.
* The inner grid will be emptied if it has already been built.
*/
setColumns(_columnData) {
// Free all column objects which have been created till this moment
this._clearHeader();
// Copy the given column data
this.columnMgr = new et2_dataview_columns(_columnData);
// Create the stylesheets
this.updateColumns();
// Build the header row
this._buildHeader();
// Build the grid
this._buildGrid();
}
/**
* Resizes the grid
*/
resize(_w, _h) {
// Not fully initialized yet...
if (!this.columnMgr)
return;
if (this.width != _w) {
this.width = _w;
// Take grid border width into account
_w -= (this.table.outerWidth(true) - this.table.innerWidth());
// Take grid header border's width into account. eg. category colors may add extra pixel into width
_w = _w - (this.thead.find('tr').outerWidth() - this.thead.find('tr').innerWidth());
// Rebuild the column stylesheets
this.columnMgr.setTotalWidth(_w - this.scrollbarWidth);
this._updateColumns();
}
if (this.height != _h) {
this.height = _h;
// Set the height of the grid.
if (this.grid) {
this.grid.setScrollHeight(this.height -
this.headTr.outerHeight(true));
}
}
}
/**
* Returns the column manager object. You can use it to set the visibility
* of columns etc. Call "updateHeader" if you did any changes.
*/
getColumnMgr() {
return this.columnMgr;
}
/**
* Recalculates the stylesheets which determine the column visibility and
* width.
*
* @param setDefault boolean Allow admins to save current settings as default for all users
*/
updateColumns(setDefault = false) {
if (this.columnMgr) {
this._updateColumns();
}
// Ability to notify parent / someone else
if (this.onUpdateColumns) {
this.onUpdateColumns(setDefault);
}
}
/* --- PRIVATE FUNCTIONS --- */
/* --- Code for building the grid container DOM-Tree elements ---- */
/**
* Builds the base DOM-Tree elements
*/
_createElements() {
/*
Structure:
<table class="egwGridView_outer">
<thead>
<tr> [HEAD] </tr>
</thead>
<tbody>
<tr> [GRID CONTAINER] </tr>
</tbody>
</table>
*/
this.containerTr = jQuery(document.createElement("tr"));
this.headTr = jQuery(document.createElement("tr"));
this.thead = jQuery(document.createElement("thead"))
.append(this.headTr);
this.tbody = jQuery(document.createElement("tbody"))
.append(this.containerTr);
this.table = jQuery(document.createElement("table"))
.addClass("egwGridView_outer")
.append(this.thead, this.tbody)
.appendTo(this.parentNode);
}
/* --- Code for building the header row --- */
/**
* Clears the header row
*/
_clearHeader() {
if (this.columnMgr) {
this.columnMgr.destroy();
this.columnMgr = null;
}
// Remove dynamic CSS,
for (var i = 0; i < this.columns.length; i++) {
if (this.columns[i].tdClass) {
this.egw.css('.' + this.columns[i].tdClass);
}
if (this.columns[i].divClass) {
this.egw.css('.' + this.columns[i].divClass);
this.egw.css(".egwGridView_outer ." + this.columns[i].divClass);
this.egw.css(".egwGridView_grid ." + this.columns[i].divClass);
}
}
this.egw.css(".egwGridView_grid ." + this.uniqueId + "_div_fullRow");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_td_fullRow");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_spacer_fullRow");
// Reset the headerColumns array and empty the table row
this.columnNodes = [];
this.columns = [];
this.headTr.empty();
}
/**
* Sets the column data which is retrieved by calling egwGridColumns.getColumnData.
* The columns will be updated.
*/
_updateColumns() {
// Copy the columns data
this.columns = this.columnMgr.getColumnData();
// Count the visible rows
var total_cnt = 0;
for (var i = 0; i < this.columns.length; i++) {
if (this.columns[i].visible) {
total_cnt++;
}
}
// Set the grid column styles
var first = true;
var vis_col = this.visibleColumnCount = 0;
var totalWidth = 0;
for (var i = 0; i < this.columns.length; i++) {
var col = this.columns[i];
col.tdClass = this.uniqueId + "_td_" + col.id;
col.divClass = this.uniqueId + "_div_" + col.id;
if (col.visible) {
vis_col++;
this.visibleColumnCount++;
// Update the visibility of the column
this.egw.css("." + col.tdClass, "display: table-cell; " +
"!important;");
// Ugly browser dependant code - each browser seems to treat the
// right (collapsed) border of the row differently
var subBorder = 0;
var subHBorder = 0;
/*
if (jQuery.browser.mozilla)
{
var maj = jQuery.browser.version.split(".")[0];
if (maj < 2) {
subBorder = 1; // Versions <= FF 3.6
}
}
if (jQuery.browser.webkit)
{
if (!first)
{
subBorder = 1;
}
subHBorder = 1;
}
if ((jQuery.browser.msie || jQuery.browser.opera) && first)
{
subBorder = -1;
}
*/
// Make the last columns one pixel smaller, to prevent a horizontal
// scrollbar from showing up
if (vis_col == total_cnt) {
subBorder += 1;
}
// Write the width of the header columns
var headerWidth = Math.max(0, (col.width - this.headerBorderWidth - subHBorder));
this.egw.css(".egwGridView_outer ." + col.divClass, "width: " + headerWidth + "px;");
// Write the width of the body-columns
var columnWidth = Math.max(0, (col.width - this.columnBorderWidth - subBorder));
this.egw.css(".egwGridView_grid ." + col.divClass, "width: " + columnWidth + "px;");
totalWidth += col.width;
first = false;
}
else {
this.egw.css("." + col.tdClass, "display: none;");
}
}
// Add the full row and spacer class
this.egw.css(".egwGridView_grid ." + this.uniqueId + "_div_fullRow", "width: " + (totalWidth - this.columnBorderWidth - 2) + "px; border-right-width: 0 !important;");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_td_fullRow", "border-right-width: 0 !important;");
this.egw.css(".egwGridView_outer ." + this.uniqueId + "_spacer_fullRow", "width: " + (totalWidth - 1) + "px; border-right-width: 0 !important;");
}
/**
* Builds the containers for the header row
*/
_buildHeader() {
var self = this;
var handler = function (event) {
};
for (var i = 0; i < this.columns.length; i++) {
var col = this.columns[i];
// Create the column header and the container element
var cont = jQuery(document.createElement("div"))
.addClass("innerContainer")
.addClass(col.divClass);
var column = jQuery(document.createElement("th"))
.addClass(col.tdClass)
.attr("align", "left")
.append(cont)
.appendTo(this.headTr);
if (this.columnMgr && this.columnMgr.getColumnById(i)) {
column.addClass(this.columnMgr.getColumnById(i).fixedWidth ? 'fixedWidth' : 'relativeWidth');
if (this.columnMgr.getColumnById(i).visibility === et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) {
column.addClass('noResize');
}
}
// make column resizable
var enc_column = self.columnMgr.getColumnById(col.id);
if (enc_column.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) {
et2_dataview_view_resizable.makeResizeable(column, function (_w) {
// User wants the column to stay where they put it, even for relative
// width columns, so set it explicitly first and adjust other relative
// columns to match.
if (this.relativeWidth) {
// Set to selected width
this.set_width(_w + "px");
self.columnMgr.updated();
// Just triggers recalculation
self.columnMgr.getColumnWidth(0);
// Set relative widths to match
var relative = self.columnMgr.totalWidth - self.columnMgr.totalFixed + _w;
this.set_width(_w / relative);
for (var i = 0; i < self.columnMgr.columnCount(); i++) {
var col = self.columnMgr.getColumnById('col_' + i);
if (!col || col == this || col.fixedWidth)
continue;
col.set_width(self.columnMgr.getColumnWidth(i) / relative);
}
// Triggers column change callback, which saves
self.updateColumns();
}
else {
this.set_width(this.relativeWidth ? (_w / self.columnMgr.totalWidth) : _w + "px");
self.columnMgr.updated();
self.updateColumns();
}
}, enc_column);
}
// Store both nodes in the columnNodes array
this.columnNodes.push({
"column": column,
"container": cont
});
}
this._buildSelectCol();
}
/**
* Builds the select cols column
*/
_buildSelectCol() {
// Build the "select columns" icon
this.selectColIcon = jQuery(document.createElement("span"))
.addClass("selectcols")
.css('display', 'inline-block'); // otherwise jQuery('span.selectcols',this.dataview.headTr).show() set it to "inline" causing it to not show up because 0 height
// Build the option column
this.selectCol = jQuery(document.createElement("th"))
.addClass("optcol")
.append(this.selectColIcon)
// Toggle display of option popup
.click(this, function (e) { if (e.data.selectColumnsClick)
e.data.selectColumnsClick(e); })
.appendTo(this.headTr);
this.selectCol.css("width", this.scrollbarWidth - this.selectCol.outerWidth()
+ this.selectCol.width() + 1);
}
/**
* Builds the inner grid class
*/
_buildGrid() {
// Create the collection of column ids
var colIds = [];
for (var i = 0; i < this.columns.length; i++) {
if (this.columns[i].visible) {
colIds[i] = this.columns[i].id;
}
}
// Create the row provider
if (this.rowProvider) {
this.rowProvider.destroy();
}
this.rowProvider = new et2_dataview_rowProvider(this.uniqueId, colIds);
// Create the grid class and pass "19" as the starting average row height
this.grid = new et2_dataview_grid(null, null, this.egw, this.rowProvider, 19);
// Insert the grid into the DOM-Tree
var tr = jQuery(this.grid.getFirstNode());
this.containerTr.replaceWith(tr);
this.containerTr = tr;
}
/* --- Code for calculating the browser/css depending widths --- */
/**
* Reads the browser dependant variables
*/
_getDepVars() {
if (typeof this.scrollbarWidth === 'undefined') {
// Clone the table and attach it to the outer body tag
var clone = this.table.clone();
jQuery(egw.top.document.getElementsByTagName("body")[0])
.append(clone);
// Read the scrollbar width
this.scrollbarWidth = this.constructor.prototype.scrollbarWidth =
this._getScrollbarWidth(clone);
// Read the header border width
this.headerBorderWidth = this.constructor.prototype.headerBorderWidth =
this._getHeaderBorderWidth(clone);
// Read the column border width
this.columnBorderWidth = this.constructor.prototype.columnBorderWidth =
this._getColumnBorderWidth(clone);
// Remove the cloned DOM-Node again from the outer body
clone.remove();
}
}
/**
* Reads the scrollbar width
*/
_getScrollbarWidth(_table) {
// Create a temporary td and two divs, which are inserted into the
// DOM-Tree. The outer div has a fixed size and "overflow" set to auto.
// When the second div is inserted, it will be forced to display a scrollbar.
var div_inner = jQuery(document.createElement("div"))
.css("height", "1000px");
var div_outer = jQuery(document.createElement("div"))
.css("height", "100px")
.css("width", "100px")
.css("overflow", "auto")
.append(div_inner);
var td = jQuery(document.createElement("td"))
.append(div_outer);
// Store the scrollbar width statically.
jQuery("tbody tr", _table).append(td);
var width = Math.max(10, div_outer.outerWidth() - div_inner.outerWidth());
// Remove the elements again
div_outer.remove();
return width;
}
/**
* Calculates the total width of the header column border
*/
_getHeaderBorderWidth(_table) {
// Create a temporary th which is appended to the outer thead row
var cont = jQuery(document.createElement("div"))
.addClass("innerContainer");
var th = jQuery(document.createElement("th"))
.append(cont);
// Insert the th into the document tree
jQuery("thead tr", _table).append(th);
// Calculate the total border width
var width = th.outerWidth(true) - cont.width();
// Remove the appended element again
th.remove();
return width;
}
/**
* Calculates the total width of the column border
*/
_getColumnBorderWidth(_table) {
// Create a temporary th which is appended to the outer thead row
var cont = jQuery(document.createElement("div"))
.addClass("innerContainer");
var td = jQuery(document.createElement("td"))
.append(cont);
// Insert the th into the document tree
jQuery("tbody tr", _table).append(td);
// Calculate the total border width
_table.addClass("egwGridView_grid");
var width = td.outerWidth(true) - cont.width();
// Remove the appended element again
td.remove();
return width;
}
}
//# sourceMappingURL=et2_dataview.js.map

View File

@ -1,834 +0,0 @@
/**
* EGroupware eTemplate2
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
import { et2_dataview_selectionManager } from "./et2_dataview_controller_selection";
import { et2_dataview_row } from "./et2_dataview_view_row";
import { et2_arrayIntKeys, et2_bounds } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
import { egwBitIsSet } from "../egw_action/egw_action_common.js";
import { EGW_AO_STATE_NORMAL, EGW_AO_STATE_SELECTED } from "../egw_action/egw_action_constants.js";
/**
* The fetch timeout specifies the time during which the controller tries to
* consolidate requests for rows.
*/
export const ET2_DATAVIEW_FETCH_TIMEOUT = 50;
export const ET2_DATAVIEW_STEPSIZE = 50;
/**
* The et2_dataview_controller class is the intermediate layer between a grid
* instance and the corresponding data source. It manages updating the grid,
* as well as inserting and deleting rows.
*/
export class et2_dataview_controller {
/**
* Constructor of the et2_dataview_controller, connects to the grid
* callback.
*
* @param _grid is the grid the controller should controll.
* @param _rowCallback is the callback function that gets called when a row
* is requested.
* @param _linkCallback is the callback function that gets called for
* requesting action links for a row. The row data, the index of the row and
* the uid are passed as parameters to the function.
* uid is passed to the function.
* @param _actionObjectManager is the object that manages the action
* objects.
*/
constructor(_parentController, _grid) {
this._indexMap = {};
// Copy the given arguments
this._parentController = _parentController;
this._grid = _grid;
// Initialize list of child controllers
this._children = [];
// Initialize the "index map" which contains all currently displayed
// containers hashed by the "index"
this._indexMap = {};
// Timer used for queing fetch requests
this._queueTimer = null;
// Array which contains all currently queued row indices in the form of
// an associative array
this._queue = {};
// Current concurrent requests we have
this._request_queue = [];
// Register the dataFetch callback
this._grid.setDataCallback(this._gridCallback, this);
// Record the child
if (this._parentController != null) {
this._parentController._children.push(this);
}
}
destroy() {
// Destroy the selection manager
this._selectionMgr.destroy();
// Clear the selection timeout
this._clearTimer();
// Remove the child from the child list
if (this._parentController != null) {
var idx = this._parentController._children.indexOf(this);
if (idx >= 0) {
// This element is no longer parent of the child
this._parentController._children.splice(idx, 1);
this._parentController = null;
}
}
this._grid = null;
}
/**
* @param value is an object implementing the et2_IDataProvider
* interface
*/
setDataProvider(value) {
this._dataProvider = value;
}
setRowCallback(value) {
this._rowCallback = value;
}
setLinkCallback(value) {
this._linkCallback = value;
}
/**
* @param value is the context in which the _rowCallback and the
* _linkCallback are called.
*/
setContext(value) {
this._context = value;
}
setActionObjectManager(_actionObjectManager) {
if (this._selectionMgr) {
this._selectionMgr.destroy();
}
// Create the selection manager
this._selectionMgr = new et2_dataview_selectionManager(this._parentController ? this._parentController._selectionMgr : null, this._indexMap, _actionObjectManager, this._selectionFetchRange, this._makeIndexVisible, this);
}
/**
* The update function queries the server for changes in the currently
* managed index range -- those changes are then merged into the current
* view without a complete rebuild of every row.
*
* @param {boolean} clear Skip the fancy stuff, dump everything and start again.
* Completely clears the grid and selection.
*/
update(clear) {
// Avoid update after destroy
// Happens sometimes if AJAX response comes after etemplate unload
if (!this._grid)
return;
// ---------
// TODO: Actually stuff here should be done if the server responds that
// there at all were some changes (needs implementation of "refresh")
// Tell the grid not to try and update itself while we do this
this._grid.doInvalidate = false;
if (clear) {
// Scroll to top
this._grid.makeIndexVisible(0);
this._grid.clear();
// Free selection manager
this._selectionMgr.clear();
// Clear object manager
this._objectManager.clear();
// Clear the map
this._indexMap = {};
// Update selection manager, it uses this by reference
this._selectionMgr.setIndexMap(this._indexMap);
// Clear the queue
this._queue = {};
// Invalidate the change detection, re-fetches any known rows
this._lastModification = 0;
}
// Remove all rows which are outside the view range
this._grid.cleanup();
// Get the currently visible range from the grid
var range = this._grid.getIndexRange();
// Force range.top and range.bottom to contain an integer
if (range.top === false) {
range.top = range.bottom = 0;
}
this._request_queue = [];
// Require that range from the server
this._queueFetch(et2_bounds(range.top, clear ? 0 : range.bottom + 1), 0, true);
}
/**
* Rebuilds the complete grid.
*/
reset() {
// Throw away all internal mappings and reset the timestamp
this._indexMap = {};
// Update selection manager, it uses this by reference
this._selectionMgr.setIndexMap(this._indexMap);
// Clear the grid
this._grid.clear();
// Clear the row queue
this._queue = {};
// Reset the request queue
this._request_queue = [];
// Update the data
this.update();
}
/**
* Loads the initial order. Do not call multiple times.
*/
loadInitialOrder(order) {
for (var i = 0; i < order.length; i++) {
this._getIndexEntry(i).uid = order[i];
}
}
/**
* Load initial data
*
* @param {string} uid_key Name of the unique row identifier field
* @param {Object} data Key / Value mapping of initial data.
*/
loadInitialData(uid_prefix, uid_key, data) {
var idx = 0;
for (var key in data) {
// Skip any extra keys
if (typeof data[key] != "object" || data[key] == null || typeof data[key][uid_key] == "undefined")
continue;
// Add to row / uid map
var entry = this._getIndexEntry(idx++);
entry.uid = data[key][uid_key] + "";
if (entry.uid.indexOf(uid_prefix) < 0) {
entry.uid = uid_prefix + "::" + entry.uid;
}
// Add to data cache so grid will find it
egw.dataStoreUID(entry.uid, data[key]);
// Don't try to insert the rows, grid will do that automatically
}
if (idx == 0) {
// No rows, start with an empty
this._selectionMgr.clear();
this._emptyRow(this._grid._total == 0);
}
}
/**
* Returns the depth of the controller instance.
*/
getDepth() {
if (this._parentController) {
return this._parentController.getDepth() + 1;
}
return 0;
}
/**
* Set the data cache prefix
* The default is to use appname, but if you need to set it explicitly to
* something else to avoid conflicts. Use the same prefix everywhere for
* each type of data. eg. infolog for infolog entries, even if accessed via addressbook
*/
setPrefix(prefix) {
this.dataStorePrefix = prefix;
}
/**
* Returns the row information of the passed node, or null if not available
*
* @param {DOMNode} node
* @return {string|false} UID, or false if not found
*/
getRowByNode(node) {
// Whatever the node, find a TR
var row_node = jQuery(node).closest('tr');
var row = null;
// Check index map - simple case
var indexed = this._getIndexEntry(row_node.index());
if (indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) {
row = indexed;
}
else {
// Check whole index map
for (var index in this._indexMap) {
indexed = this._indexMap[index];
if (indexed && indexed.row && indexed.row.getDOMNode() == row_node[0]) {
row = indexed;
break;
}
}
}
// Check children
for (var i = 0; !row && i < this._children.length; i++) {
var child_row = this._children[i].getRowByNode(node);
if (child_row !== false)
row = child_row;
}
if (row && !row.controller) {
row.controller = this;
}
return row;
}
/**
* Returns the current "total" count.
*/
getTotalCount() {
return this._grid.getTotalCount();
}
/* -- PRIVATE FUNCTIONS -- */
_getIndexEntry(_idx) {
// Create an entry in the index map if it does not exist yet
if (typeof this._indexMap[_idx] === "undefined") {
this._indexMap[_idx] = {
"row": null,
"uid": null
};
}
// Always update the index of the entries before returning them. This is
// neccessary, as when we remove the uid from an entry without row, its
// index does not get updated any further
this._indexMap[_idx]["idx"] = _idx;
return this._indexMap[_idx];
}
/**
* Inserts a new data row into the grid. index and uid are derived from the
* given management entry. If the data for the given uid does not exist yet,
* a "loading" placeholder will be shown instead. The function will do
* nothing if there already is a row associated to the entry. This function
* will not re-insert a row if the entry already had a row.
*
* @param _entry is the management entry for the index the row will be
* displayed at.
* @param _update specifies whether the row should be updated if _entry.row
* already exists.
* @return true, if all data for the row has been available, false
* otherwise.
*/
_insertDataRow(_entry, _update) {
// Abort if the entry already has a row but the _insert flag is not set
if (_entry.row && !_update) {
return true;
}
// Context used for the callback functions
var ctx = { "self": this, "entry": _entry };
// Create a new row instance, if it does not exist yet
var createdRow = false;
if (!_entry.row) {
createdRow = true;
_entry.row = this._createRow(ctx);
_entry.row.setDestroyCallback(this._destroyCallback, ctx);
}
// Load the row data if we have a uid for the entry
this.hasData = false; // Gets updated by the _dataCallback
if (_entry.uid) {
// Register the callback / immediately load the data
this._dataProvider.dataRegisterUID(_entry.uid, this._dataCallback, ctx);
}
// Display the loading "row prototype" if we don't have data for the row
if (!this.hasData) {
// Get the average height, the "-5" derives from the td padding
var avg = Math.round(this._grid.getAverageHeight() - 5) + "px";
var prototype = this._grid.getRowProvider().getPrototype("loading");
jQuery("div", prototype).css("height", avg);
var node = _entry.row.getJNode();
node.empty();
node.append(prototype.children());
}
// Insert the row into the table -- the same row must never be inserted
// twice into the grid, so this function only executes the following
// code only if it is a newly created row.
if (createdRow && _entry.row) {
this._grid.insertRow(_entry.idx, _entry.row);
}
// Remove 'No matches found' row
var row = jQuery(".egwGridView_empty", this._grid.innerTbody).remove();
if (row.length) {
this._selectionMgr.unregisterRow("", 0);
}
// Update index map only for push (autorefresh disabled)
if (this._indexMap[_entry.idx] && this._indexMap[_entry.idx].uid !== _entry.uid) {
let max = parseInt(Object.keys(this._indexMap).reduce((a, b) => this._indexMap[a] > this._indexMap[b] ? a : b));
for (let idx = max; idx >= _entry.idx; idx--) {
let entry = this._indexMap[idx];
this._indexMap[idx].idx = idx + 1;
this._indexMap[this._indexMap[idx].idx] = this._indexMap[idx];
if (this._selectionMgr && this._selectionMgr._registeredRows[entry.uid]) {
this._selectionMgr._registeredRows[entry.uid].idx = entry.idx;
}
}
}
this._indexMap[_entry.idx] = _entry;
return this.hasData;
}
/**
* Create a new row.
*
* @param {type} ctx
* @returns {et2_dataview_container}
*/
_createRow(ctx) {
return new et2_dataview_row(this._grid);
}
/**
* Function which gets called by the grid when data is requested.
*
* @param _idxStart is the index of the first row for which data is
* requested.
* @param _idxEnd is the index of the last requested row.
*/
_gridCallback(_idxStart, _idxEnd) {
var needsData = false;
// Iterate over all elements the dataview requested and create a row
// which indicates that we are currently loading data
for (var i = _idxStart; i <= _idxEnd; i++) {
var entry = this._getIndexEntry(i);
// Insert the row for the entry -- do not update rows which are
// already existing, as we do not have new data for those.
if (!this._insertDataRow(entry, false) && needsData === false) {
needsData = i;
}
}
// Queue fetching that data range
if (needsData !== false) {
this._queueFetch(et2_bounds(needsData, _idxEnd + 1), needsData == _idxStart ? 0 : needsData > _idxStart ? 1 : -1, false);
}
}
/**
* The _queueFetch function is used to queue a fetch request.
* TODO: Refresh is currently not used
*/
_queueFetch(_range, _direction, _isUpdate) {
// Force immediate to be false
_isUpdate = _isUpdate ? _isUpdate : false;
// Push the requests onto the request queue
var start = Math.max(0, _range.top);
var end = Math.min(this._grid.getTotalCount(), _range.bottom);
for (var i = start; i < end; i++) {
if (typeof this._queue[i] === "undefined") {
this._queue[i] = _direction; // Stage 1 - queue for after current, -1 -- queue for before current
}
}
// Start the queue timer, if this has not already been done
if (this._queueTimer === null && !_isUpdate) {
var self = this;
egw.debug('log', 'Dataview queue: ', _range);
this._queueTimer = window.setTimeout(function () {
self._flushQueue(false);
}, ET2_DATAVIEW_FETCH_TIMEOUT);
}
if (_isUpdate) {
this._flushQueue(true);
}
}
/**
* Flushes the queue.
*/
_flushQueue(_isUpdate) {
// Clear any still existing timer
this._clearTimer();
// Mark all elements in a radius of ET2_DATAVIEW_STEPSIZE
var marked = {};
var r = _isUpdate ? 0 : Math.floor(ET2_DATAVIEW_STEPSIZE / 2);
var total = this._grid.getTotalCount();
for (var key in this._queue) {
if (this._queue[key] > 1)
continue;
key = parseInt(key);
var b = Math.max(0, key - r + (r * this._queue[key]));
var t = Math.min(key + r + (r * this._queue[key]), total - 1);
var c = 0;
for (var i = b; i <= t && c < ET2_DATAVIEW_STEPSIZE; i++) {
if (typeof this._queue[i] == "undefined"
|| this._queue[i] <= 1) {
this._queue[i] = 2; // Stage 2 -- pending or available
marked[i] = true;
c++;
}
}
}
// Create a list with start indices and counts
var fetchList = [];
var entry = null;
var last = 0;
// Get the int keys and sort the array numeric
var arr = et2_arrayIntKeys(marked).sort(function (a, b) { return a > b ? 1 : (a == b ? 0 : -1); });
for (var i = 0; i < arr.length; i++) {
if (i == 0 || arr[i] - last > 1) {
if (entry) {
fetchList.push(entry);
}
entry = {
"start": arr[i],
"count": 1
};
}
else {
entry.count++;
}
last = arr[i];
}
if (entry) {
fetchList.push(entry);
}
// Special case: If there are no entries in the fetch list and this is
// an update, create an dummy entry, so that we'll get the current count
if (fetchList.length === 0 && _isUpdate) {
fetchList.push({
"start": 0, "count": 0
});
// Disable grid invalidate, or it might request again before we're done
this._grid.doInvalidate = false;
}
egw.debug("log", "Dataview flush", fetchList);
// Execute all queries
for (var i = 0; i < fetchList.length; i++) {
// Build the query
var query = {
"start": fetchList[i].start,
"num_rows": fetchList[i].count,
"refresh": false
};
// Context used in the callback function
var ctx = {
"self": this,
"start": query.start,
"count": query.num_rows,
"lastModification": this._lastModification,
prefix: undefined
};
if (this.dataStorePrefix) {
ctx.prefix = this.dataStorePrefix;
}
this._queueRequest(query, ctx);
}
}
/**
* Queue a request for data
* @param {Object} query
* @param {Object} ctx
*/
_queueRequest(query, ctx) {
this._request_queue.push({
query: query,
context: ctx,
// Start pending, set to 1 when request sent
status: 0
});
this._fetchQueuedRequest();
}
/**
* Fetch data for a queued request, subject to rate limit
*/
_fetchQueuedRequest() {
// Check to see if there's room
var count = 0;
for (var i = 0; i < this._request_queue.length; i++) {
if (this._request_queue[i].status > 0)
count++;
}
// Too many requests, will try again after response is received
if (count >= et2_dataview_controller.CONCURRENT_REQUESTS || this._request_queue.length === 0) {
return;
}
// Keep at least 1 previous pending
var keep = 1;
// The most recent is the one the user's most interested in
var request = null;
for (var i = this._request_queue.length - 1; i >= 0; i--) {
// Only interested in pending requests (status 0)
if (this._request_queue[i].status != 0) {
continue;
}
if (request == null) {
request = this._request_queue[i];
}
else if (keep > 0) {
keep--;
}
else if (keep <= 0) {
// Cancel pending, they've probably scrolled past.
this._request_queue.splice(i, 1);
}
}
if (request == null)
return;
// Request being sent
request.status = 1;
// Call the callback
this._dataProvider.dataFetch(request.query, this._fetchCallback, request.context);
}
_clearTimer() {
// Reset the queue timer upon destruction
if (this._queueTimer) {
window.clearTimeout(this._queueTimer);
this._queueTimer = null;
}
}
/**
* Called by the data source when the data changes
*
* @param _data Object|null New data, or null. Null will remove the row.
*/
_dataCallback(_data) {
// Set the "hasData" flag
this.self.hasData = true;
// Call the row callback with the new data -- the row callback then
// generates the row DOM nodes that will be inserted into the grid
if (this.self._rowCallback) {
// Remove everything from the current row
this.entry.row.clear();
// If there's no data, stop
if (typeof _data == "undefined" || _data == null) {
this.self._destroyCallback.call(this, this.entry.row);
return;
}
// Fill the row DOM Node with data
this.self._rowCallback.call(this.self._context, _data, this.entry.row, this.entry.idx, this.entry);
// Attach the "subgrid" tag to the row, if the depth of this
// controller is larger than zero
var tr = this.entry.row.getDOMNode();
var d = this.self.getDepth();
if (d > 0) {
jQuery(tr).addClass("subentry");
jQuery("td:first", tr).children("div").last().addClass("level_" + d + " indentation");
if (this.entry.idx == 0) {
// Set the CSS for the level - required so columns line up
var indent = jQuery("<span class='indentation'/>").appendTo('body');
egw.css(".subentry td div.innerContainer.level_" + d, "margin-right:" + (parseInt(indent.css("margin-right")) * d) + "px");
indent.remove();
}
}
var links = null;
// Look for a flag in the row to avoid actions. Use for sums or extra header rows.
if (!_data.no_actions) {
// Get the action links if the links callback is set
if (this.self._linkCallback) {
links = this.self._linkCallback.call(this.self._context, _data, this.entry.idx, this.entry.uid);
}
// Register the row in the selection manager
this.self._selectionMgr.registerRow(this.entry.uid, this.entry.idx, tr, links);
}
else {
// Remember that
this.entry.no_actions = true;
}
// Invalidate the current row entry
this.entry.row.invalidate();
}
}
/**
*
*/
_destroyCallback(_row) {
// Unregister the row from the selection manager, if not selected
// If it is selected, leave it there - allows selecting rows and scrolling
var selection = this.self._selectionMgr._getRegisteredRowsEntry(this.entry.uid);
if (this.entry.row && selection && !egwBitIsSet(selection.state, EGW_AO_STATE_SELECTED)) {
var tr = this.entry.row.getDOMNode();
this.self._selectionMgr._updateState(this.entry.uid, EGW_AO_STATE_NORMAL);
this.self._selectionMgr.unregisterRow(this.entry.uid, tr);
}
// There is no further row connected to the entry
this.entry.row = null;
// Unregister the data callback
this.self._dataProvider.dataUnregisterUID(this.entry.uid, this.self._dataCallback, this);
}
/**
* Returns an array containing "_count" index mapping entries starting from
* the index given in "_start".
*/
_getIndexMapping(_start, _count) {
var result = [];
for (var i = _start; i < _start + _count; i++) {
result.push(this._getIndexEntry(i));
}
return result;
}
/**
* Updates the grid according to the new order. The function simply does the
* following: It iterates along the new order (given in _order) and the old
* order given in _idxMap. Iteration variables used are
* a) i -- points to the current entry in _order
* b) idx -- points to the current grid row that will be effected by
* this operation.
* c) mapIdx -- points to the current entry in _indexMap
* The following cases may occur:
* a) The current entry in the old order has no uid or no row -- in that
* case the row at the current position is simply updated,
* the old pointer will be incremented.
* b) The two uids differ -- insert a new row with the new uid, do not
* increment the old pointer.
* c) The two uids are the same -- increment the old pointer.
* In a last step all rows that are left in the old order are deleted. All
* newly created index entries are returned. This function does not update
* the internal mapping in _idxMap.
*/
_updateOrder(_start, _count, _idxMap, _order) {
// The result contains the newly created index map entries which have to
// be merged with the result
var result = [];
// Iterate over the new order
var mapIdx = 0;
var idx = _start;
for (var i = 0; i < _order.length; i++, idx++) {
var current = _idxMap[mapIdx];
if (!current.row || !current.uid) {
// If there is no row yet at the current position or the uid
// of that entry is unknown, simply update the entry.
current.uid = _order[i];
current.idx = idx;
// Only update the row, if it is displayed (e.g. has a "loading"
// row displayed) -- this is needed for prefetching
if (current.row) {
this._insertDataRow(current, true);
}
mapIdx++;
}
else if (current.uid !== _order[i]) {
// Insert a new row at the new position
var entry = {
"idx": idx,
"uid": _order[i],
"row": null
};
this._insertDataRow(entry, true);
// Remember the new entry
result.push(entry);
}
else {
// Do nothing, the uids do not differ, just update the index of
// the element
current.idx = idx;
mapIdx++;
}
}
// Delete as many rows as we have left, invalidate the corresponding
// index entry
for (var i = mapIdx; i < _idxMap.length; i++) {
if (typeof _idxMap[i] != 'undefined') {
_idxMap[i].uid = null;
}
}
return result;
}
_mergeResult(_newEntries, _invalidStartIdx, _diff, _total) {
if (_newEntries.length > 0 || _diff > 0) {
// Create a new index map
var newMap = {};
// Insert all new entries into the new index map
for (var i = 0; i < _newEntries.length; i++) {
newMap[_newEntries[i].idx] = _newEntries[i];
}
// Merge the old map with all old entries
for (var key in this._indexMap) {
// Get the corresponding index entry
var entry = this._indexMap[key];
// Calculate the new index -- if rows were deleted, we'll
// have to adjust the index
var newIdx = entry.idx >= _invalidStartIdx
? entry.idx - _diff : entry.idx;
if (newIdx >= 0 && newIdx < _total
&& typeof newMap[newIdx] === "undefined") {
entry.idx = newIdx;
newMap[newIdx] = entry;
}
else if (newMap[newIdx] !== this._indexMap[key]) {
// Make sure the old entry gets invalidated
entry.idx = null;
entry.row = null;
}
}
// Make the new index map the current index map
this._indexMap = newMap;
this._selectionMgr.setIndexMap(newMap);
}
}
_fetchCallback(_response) {
// Remove answered request from queue
var request = null;
for (var i = 0; i < this.self._request_queue.length; i++) {
if (this.self._request_queue[i].context == this) {
request = this.self._request_queue[i];
this.self._request_queue.splice(i, 1);
break;
}
}
this.self._lastModification = _response.lastModification;
// Do nothing if _response.order evaluates to false
if (!_response.order) {
return;
}
// Make sure _response.order.length is not longer than the requested
// count, if a specific count was requested
var order = this.count != 0 ? _response.order.splice(0, this.count) : _response.order;
// Remove from queue, or it will not be fetched again
if (_response.total < this.count) {
// Less rows than we expected
// Clear the queue, or the remnants will never be loaded again
this.self._queue = {};
}
else {
for (var i = this.start; i < this.start + order.length; i++)
delete this.self._queue[i];
}
// Get the current index map for the updated region
var idxMap = this.self._getIndexMapping(this.start, order.length);
// Update the grid using the new order. The _updateOrder function does
// not update the internal mapping while inserting and deleting rows, as
// this would move us to another asymptotic runtime level.
var res = this.self._updateOrder(this.start, this.count, idxMap, order);
// Merge the new indices, update all indices with rows that were not
// affected and invalidate all indices if there were changes
this.self._mergeResult(res, this.start + order.length, idxMap.length - order.length, _response.total);
if (_response.total == 0) {
this.self._emptyRow(true);
}
else {
var row = jQuery(".egwGridView_empty", this.self._grid.innerTbody).remove();
this.self._selectionMgr.unregisterRow("", 0, row.get(0));
}
// Now it's OK to invalidate, if it wasn't before
this.self._grid.doInvalidate = true;
// Update the total element count in the grid
this.self._grid.setTotalCount(_response.total);
this.self._selectionMgr.setTotalCount(_response.total);
// Schedule an invalidate, in case total is the same
this.self._grid.invalidate();
// Check if requests are waiting
this.self._fetchQueuedRequest();
}
/**
* Insert an empty / placeholder row when there is no data to display
*/
_emptyRow(_noRows) {
var noRows = !_noRows ? false : true;
jQuery(".egwGridView_empty", this._grid.innerTbody).remove();
if (typeof this._grid._rowProvider != "undefined" && this._grid._rowProvider.getPrototype("empty")) {
var placeholder = this._grid._rowProvider.getPrototype("empty");
if (jQuery("td", placeholder).length == 1) {
jQuery("td", placeholder).css("width", this._grid.outerCell.width() + "px");
}
placeholder.appendTo(this._grid.innerTbody);
// Register placeholder action only if no rows
if (noRows) {
// Get the action links if the links callback is set
var links = null;
if (this._linkCallback) {
links = this._linkCallback.call(this._context, {}, 0, "");
}
this._selectionMgr.registerRow("", 0, placeholder.get(0), links);
}
}
}
/**
* Callback function used by the selection manager to translate the selected
* range to uids.
*/
_selectionFetchRange(_range, _callback, _context) {
this._dataProvider.dataFetch({ "start": _range.top, "num_rows": _range.bottom - _range.top + 1,
"no_data": true }, function (_response) {
_callback.call(_context, _response.order);
}, _context);
}
/**
* Tells the grid to make the given index visible.
*/
_makeIndexVisible(_idx) {
this._grid.makeIndexVisible(_idx);
}
}
// Maximum concurrent data requests. Additional ones are held in the queue.
et2_dataview_controller.CONCURRENT_REQUESTS = 5;
//# sourceMappingURL=et2_dataview_controller.js.map

View File

@ -1,525 +0,0 @@
/**
* EGroupware eTemplate2
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
et2_dataview_view_aoi;
egw_action.egw_keymanager;
*/
import { egw } from "../jsapi/egw_global";
import { et2_bounds } from "./et2_core_common";
import { et2_dialog } from "./et2_widget_dialog";
import { et2_createWidget } from "./et2_core_widget";
import { et2_dataview_rowAOI } from "./et2_dataview_view_aoi";
import { egwActionObjectInterface } from "../egw_action/egw_action.js";
import { EGW_AO_SHIFT_STATE_BLOCK, EGW_AO_SHIFT_STATE_MULTI, EGW_AO_STATE_FOCUSED, EGW_AO_STATE_NORMAL, EGW_AO_STATE_SELECTED } from "../egw_action/egw_action_constants.js";
import { egwBitIsSet, egwSetBit } from "../egw_action/egw_action_common.js";
/**
* The selectioManager is internally used by the et2_dataview_controller class
* to manage the row selection.
* As the action system does not allow selection of entries which are currently
* not in the dom tree, we have to manage this in this class. The idea is to
* manage an external action object interface for each visible row and proxy all
* state changes between an dummy action object, that does no selection handling,
* and the external action object interface.
*
* @augments Class
*/
export class et2_dataview_selectionManager {
/**
* Constructor
*
* @param _parent
* @param _indexMap
* @param _actionObjectManager
* @param _queryRangeCallback
* @param _makeVisibleCallback
* @param _context
* @memberOf et2_dataview_selectionManager
*/
constructor(_parent, _indexMap, _actionObjectManager, _queryRangeCallback, _makeVisibleCallback, _context) {
// Copy the arguments
this._parent = _parent;
this._indexMap = _indexMap;
this._actionObjectManager = _actionObjectManager;
this._queryRangeCallback = _queryRangeCallback;
this._makeVisibleCallback = _makeVisibleCallback;
this._context = _context;
// Attach this manager to the parent manager if one is given
if (this._parent) {
this._parent._children.push(this);
}
// Use our selection instead of object manager's to handle not-loaded rows
if (_actionObjectManager) {
this._actionObjectManager.getAllSelected = jQuery.proxy(this.getAllSelected, this);
}
// Internal map which contains all curently selected uids and their
// state
this._registeredRows = {};
this._focusedEntry = null;
this._invertSelection = false;
this._selectAll = false;
this._inUpdate = false;
this._total = 0;
this._children = [];
// Callback for when the selection changes
this.select_callback = null;
}
destroy() {
// If we have a parent, unregister from that
if (this._parent) {
var idx = this._parent._children.indexOf(this);
this._parent._children.splice(idx, 1);
}
// Destroy all children
for (var i = this._children.length - 1; i >= 0; i--) {
this._children[i].destroy();
}
// Delete all still registered rows
for (var key in this._registeredRows) {
this.unregisterRow(key, this._registeredRows[key].tr);
}
this.select_callback = null;
}
clear() {
for (var key in this._registeredRows) {
this.unregisterRow(key, this._registeredRows[key].tr);
delete this._registeredRows[key];
}
if (this._actionObjectManager) {
this._actionObjectManager.clear();
}
for (key in this._indexMap) {
delete this._indexMap[key];
}
this._total = 0;
this._focusedEntry = null;
this._invertSelection = false;
this._selectAll = false;
this._inUpdate = false;
}
setIndexMap(_indexMap) {
this._indexMap = _indexMap;
}
setTotalCount(_total) {
this._total = _total;
}
registerRow(_uid, _idx, _tr, _links) {
// Get the corresponding entry from the registered rows array
var entry = this._getRegisteredRowsEntry(_uid);
// If the row has changed unregister the old one and do not delete
// entry from the entry map
if (entry.tr && entry.tr !== _tr) {
this.unregisterRow(_uid, entry.tr, true);
}
// Create the AOI for the tr
if (!entry.tr && _links) {
this._attachActionObjectInterface(entry, _tr, _uid);
this._attachActionObject(entry, _tr, _uid, _links, _idx);
}
// Update the entry
if (entry.ao)
entry.ao._index;
entry.idx = _idx;
entry.tr = _tr;
// Update the visible state of the _tr
this._updateEntryState(entry, entry.state);
}
unregisterRow(_uid, _tr, _noDelete) {
// _noDelete defaults to false
_noDelete = _noDelete ? true : false;
if (typeof this._registeredRows[_uid] !== "undefined"
&& this._registeredRows[_uid].tr === _tr) {
this._inUpdate = true;
// Don't leave focusedEntry
// @ts-ignore
if (this._focusedEntry !== null && this._focusedEntry.uid == _uid) {
this.setFocused(_uid, false);
}
this._registeredRows[_uid].tr = null;
this._registeredRows[_uid].aoi = null;
// Remove the action object from its container
if (this._registeredRows[_uid].ao) {
this._registeredRows[_uid].ao.remove();
this._registeredRows[_uid].ao = null;
}
if (!_noDelete
&& this._registeredRows[_uid].state === EGW_AO_STATE_NORMAL) {
delete this._registeredRows[_uid];
}
this._inUpdate = false;
}
}
resetSelection() {
this._invertSelection = false;
this._selectAll = false;
this._actionObjectManager.setAllSelected(false);
for (var key in this._registeredRows) {
this.setSelected(key, false);
}
for (var i = 0; i < this._children.length; i++) {
this._children[i].resetSelection();
}
}
setSelected(_uid, _selected) {
this._selectAll = false;
var entry = this._getRegisteredRowsEntry(_uid);
this._updateEntryState(entry, egwSetBit(entry.state, EGW_AO_STATE_SELECTED, _selected));
}
getAllSelected() {
var selected = this.getSelected();
return selected.all || (selected.ids.length === this._total);
}
setFocused(_uid, _focused) {
// Reset the state of the currently focused entry
if (this._focusedEntry) {
this._updateEntryState(this._focusedEntry, egwSetBit(this._focusedEntry.state, EGW_AO_STATE_FOCUSED, false));
this._focusedEntry = null;
}
// Mark the new given uid as focused
if (_focused) {
//console.log('et2_dataview_controller_selection::setFocused -> UID:'+_uid+' is focused by:'+this._actionObjectManager.name);
var entry = this._focusedEntry = this._getRegisteredRowsEntry(_uid);
this._updateEntryState(entry, egwSetBit(entry.state, EGW_AO_STATE_FOCUSED, true));
}
}
selectAll() {
// Reset the selection
this.resetSelection();
this._selectAll = true;
// Run as a range if there's less then the max
if (egw.dataKnownUIDs(this._context._dataProvider.dataStorePrefix).length !== this._total &&
this._total <= et2_dataview_selectionManager.MAX_SELECTION) {
this._selectRange(0, this._total);
}
// Tell action manager to do all
this._actionObjectManager.setAllSelected(true);
// Update the selection
for (var key in this._registeredRows) {
var entry = this._registeredRows[key];
this._updateEntryState(entry, entry.state);
}
this._selectAll = true;
}
getSelected() {
// Collect all currently selected ids
var ids = [];
for (var key in this._registeredRows) {
if (egwBitIsSet(this._registeredRows[key].state, EGW_AO_STATE_SELECTED)) {
ids.push(key);
}
}
// Push all events of the child managers onto the list
for (var i = 0; i < this._children.length; i++) {
ids = ids.concat(this._children[i].getSelected().ids);
}
// Return an array containing those ids
// RB: we are currently NOT using "inverted"
return {
//"inverted": this._invertSelection,
"all": this._selectAll,
"ids": ids
};
}
/** -- PRIVATE FUNCTIONS -- **/
_attachActionObjectInterface(_entry, _tr, _uid) {
// Create the AOI which is used internally in the selection manager
// this AOI is not connected to the AO, as the selection manager
// cares about selection etc.
_entry.aoi = new et2_dataview_rowAOI(_tr);
_entry.aoi.setStateChangeCallback(function (_newState, _changedBit, _shiftState) {
if (_changedBit === EGW_AO_STATE_SELECTED) {
// Call the select handler
this._handleSelect(_uid, _entry, egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_BLOCK), egwBitIsSet(_shiftState, EGW_AO_SHIFT_STATE_MULTI));
}
}, this);
}
_getDummyAOI(_entry, _tr, _uid, _idx) {
// Create AOI
var dummyAOI = new egwActionObjectInterface();
var self = this;
// Handling for Action Implementations updating the state
dummyAOI.doSetState = function (_state) {
if (!self._inUpdate) {
// Update the "focused" flag
self.setFocused(_uid, egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
// Generally update the state
self._updateState(_uid, _state);
}
};
// Handle the "make visible" event, pass the request to the parent
// controller
dummyAOI.doMakeVisible = function () {
self._makeVisibleCallback.call(self._context, _idx);
};
// Connect the the two AOIs
dummyAOI.doTriggerEvent = _entry.aoi.doTriggerEvent;
// Implementation of the getDOMNode function, so that the event
// handlers can be properly bound
dummyAOI.getDOMNode = function () { return _tr; };
return dummyAOI;
}
_attachActionObject(_entry, _tr, _uid, _links, _idx) {
// Get the dummyAOI which connects the action object to the tr but
// does no selection handling
var dummyAOI = this._getDummyAOI(_entry, _tr, _uid, _idx);
// Create an action object for the tr and connect it to a dummy AOI
if (this._actionObjectManager) {
if (this._actionObjectManager.getObjectById(_uid)) {
var state = _entry.state;
this._actionObjectManager.getObjectById(_uid).remove();
_entry.state = state;
}
_entry.ao = this._actionObjectManager.addObject(_uid, dummyAOI);
}
// Force context (actual widget) in here, it's the last place it's available
_entry.ao._context = this._context;
_entry.ao.updateActionLinks(_links);
_entry.ao._index = _idx;
// Overwrite some functions like "traversePath", "getNext" and
// "getPrevious"
var self = this;
function getIndexAO(_idx) {
// Check whether the index is in the index map
if (typeof self._indexMap[_idx] !== "undefined"
&& self._indexMap[_idx].uid) {
return self._getRegisteredRowsEntry(self._indexMap[_idx].uid).ao;
}
return null;
}
function getElementRelatively(_step) {
var total = self._total || Object.keys(self._indexMap).length;
var max_index = Math.max.apply(Math, Object.keys(self._indexMap));
// Get a reasonable number of iterations - not all
var count = Math.max(1, Math.min(self._total, 50));
var element = null;
var idx = _entry.idx;
while (element == null && count > 0 && max_index > 0) {
count--;
element = getIndexAO(Math.max(0, Math.min(max_index, idx += _step)));
}
return element;
}
_entry.ao.getPrevious = function (_step) {
return getElementRelatively(-_step);
};
_entry.ao.getNext = function (_step) {
return getElementRelatively(_step);
};
_entry.ao.traversePath = function (_obj) {
// Get the start and the stop index
var s = Math.min(this._index, _obj._index);
var e = Math.max(this._index, _obj._index);
var result = [];
for (var i = s; i < e; i++) {
var ao = getIndexAO(i);
if (ao) {
result.push(ao);
}
}
return result;
};
}
_updateState(_uid, _state) {
var entry = this._getRegisteredRowsEntry(_uid);
this._updateEntryState(entry, _state);
return entry;
}
_updateEntryState(_entry, _state) {
if (this._selectAll) {
_state |= EGW_AO_STATE_SELECTED;
}
else if (this._invertSelection) {
_state ^= EGW_AO_STATE_SELECTED;
}
// Attach ao if not there, happens for rows loaded for selection, but
// not displayed yet
if (!_entry.ao && _entry.uid && this._actionObjectManager) {
var _links = [];
for (var key in this._registeredRows) {
if (this._registeredRows[key].ao && this._registeredRows[key].ao.actionLinks) {
_links = this._registeredRows[key].ao.actionLinks;
break;
}
}
if (_links.length) {
this._attachActionObjectInterface(_entry, null, _entry.uid);
this._attachActionObject(_entry, null, _entry.uid, _links, _entry.idx);
}
}
// Update the state if it has changed
if ((_entry.aoi && _entry.aoi.getState() !== _state) || _entry.state != _state) {
this._inUpdate = true; // Recursion prevention
// Update the state of the action object
if (_entry.ao) {
_entry.ao.setSelected(egwBitIsSet(_state, EGW_AO_STATE_SELECTED));
_entry.ao.setFocused(egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
}
this._inUpdate = false;
// Delete the element if state was set to "NORMAL" and there is
// no tr
if (_state === EGW_AO_STATE_NORMAL && !_entry.tr) {
delete this._registeredRows[_entry.uid];
}
}
// Update the visual state
if (_entry.aoi && _entry.aoi.doSetState) {
_entry.aoi.doSetState(_state);
}
// Update the state of the entry
_entry.state = _state;
}
_getRegisteredRowsEntry(_uid) {
if (typeof this._registeredRows[_uid] === "undefined") {
this._registeredRows[_uid] = {
"uid": _uid,
"idx": null,
"state": EGW_AO_STATE_NORMAL,
"tr": null,
"aoi": null,
"ao": null
};
}
return this._registeredRows[_uid];
}
_handleSelect(_uid, _entry, _shift, _ctrl) {
// If not "_ctrl" is set, reset the selection
if (!_ctrl) {
var top = this;
while (top._parent !== null) {
top = top._parent;
}
top.resetSelection();
this._actionObjectManager.setAllSelected(false); // needed for hirachical stuff
}
// Mark the element that was clicked as selected
var entry = this._getRegisteredRowsEntry(_uid);
this.setSelected(_uid, !_ctrl || !egwBitIsSet(entry.state, EGW_AO_STATE_SELECTED));
// Focus the element if shift is not pressed
if (!_shift) {
this.setFocused(_uid, true);
}
else if (this._focusedEntry) {
this._selectRange(this._focusedEntry.idx, _entry.idx);
}
if (this.select_callback && typeof this.select_callback == "function") {
this.select_callback.apply(this._context, arguments);
}
}
_selectRange(_start, _stop) {
// Contains ranges that are not currently in the index map and that have
// to be queried
var queryRanges = [];
// Iterate over the given range and select the elements in the range
// from _start to _stop
var naStart = false;
var s = Math.min(_start, _stop);
var e = Math.max(_stop, _start);
var RANGE_MAX = 50;
var range_break = s + RANGE_MAX;
for (var i = s; i <= e; i++) {
if (typeof this._indexMap[i] !== "undefined" &&
this._indexMap[i].uid && egw.dataGetUIDdata(this._indexMap[i].uid)) {
// Add the range to the "queryRanges"
if (naStart !== false) {
queryRanges.push(et2_bounds(naStart, i - 1));
naStart = false;
range_break += RANGE_MAX;
}
// Select the element, unless flagged for exclusion
// Check for no_actions flag via data
var data = egw.dataGetUIDdata(this._indexMap[i].uid);
if (data && data.data && !data.data.no_actions) {
this.setSelected(this._indexMap[i].uid, true);
}
}
else if (naStart === false) {
naStart = i;
range_break = naStart + RANGE_MAX;
}
else if (i >= range_break) {
queryRanges.push(et2_bounds(naStart ? naStart : s, i - 1));
naStart = i;
range_break += RANGE_MAX;
}
}
// Add the last range to the "queryRanges"
if (naStart !== false) {
queryRanges.push(et2_bounds(naStart, i - 1));
naStart = false;
}
// Query all unknown ranges from the server
if (queryRanges.length > 0) {
this._query_ranges(queryRanges);
}
}
_query_ranges(queryRanges) {
var that = this;
var record_count = 0;
var range_index = 0;
var range_count = queryRanges.length;
var cont = true;
var fetchPromise = new Promise(function (resolve) {
resolve();
});
// Found after dialog loads
var progressbar;
var parent = et2_dialog._create_parent();
var dialog = et2_createWidget("dialog", {
callback:
// Abort the long task if they canceled the data load
function () { cont = false; },
template: egw.webserverUrl + '/api/templates/default/long_task.xet',
message: egw.lang('Loading'),
title: egw.lang('please wait...'),
buttons: [{ "button_id": et2_dialog.CANCEL_BUTTON, "text": egw.lang('cancel'), id: 'dialog[cancel]', image: 'cancel' }],
width: 300
}, parent);
jQuery(dialog.template.DOMContainer).on('load', function () {
// Get access to template widgets
progressbar = dialog.template.widgetContainer.getWidgetById('progressbar');
});
for (var i = 0; i < queryRanges.length; i++) {
if (record_count + (queryRanges[i].bottom - queryRanges[i].top + 1) > that.MAX_SELECTION) {
egw.message(egw.lang('Too many rows selected.<br />Select all, or less than %1 rows', that.MAX_SELECTION));
break;
}
else {
record_count += (queryRanges[i].bottom - queryRanges[i].top + 1);
fetchPromise = fetchPromise.then((function () {
// Check for abort
if (!cont)
return;
return new Promise(function (resolve) {
that._queryRangeCallback.call(that._context, this, function (_order) {
for (var j = 0; j < _order.length; j++) {
// Check for no_actions flag via data since entry isn't there/available
var data = egw.dataGetUIDdata(_order[j]);
if (!data || data && data.data && !data.data.no_actions) {
var entry = this._getRegisteredRowsEntry(_order[j]);
this._updateEntryState(entry, egwSetBit(entry.state, EGW_AO_STATE_SELECTED, true));
}
}
progressbar.set_value(100 * (++range_index / range_count));
resolve();
}, that);
}.bind(this));
}).bind(queryRanges[i]));
}
}
fetchPromise.finally(function () {
dialog.destroy();
});
}
}
// Maximum number of rows we can safely fetch for selection
// Actual selection may have more rows if we already have some
et2_dataview_selectionManager.MAX_SELECTION = 1000;
//# sourceMappingURL=et2_dataview_controller_selection.js.map

View File

@ -1,27 +0,0 @@
/**
* EGroupware eTemplate2 - Contains interfaces used inside the dataview
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
et2_core_inheritance;
*/
import { implements_methods, et2_implements_registry } from "./et2_core_interfaces";
export const et2_dataviewIInvalidatable = "et2_dataview_IInvalidatable";
et2_implements_registry.et2_dataview_IInvalidatable = function (obj) {
return implements_methods(obj, ["invalidate"]);
};
export const et2_dataview_IViewRange = "et2_dataview_IViewRange";
et2_implements_registry.et2_dataview_IViewRange = function (obj) {
return implements_methods(obj, ["setViewRange"]);
};
export const et2_IDataProvider = "et2_IDataProvider";
et2_implements_registry.et2_IDataProvider = function (obj) {
return implements_methods(obj, ["dataFetch", "dataRegisterUID", "dataUnregisterUID"]);
};
//# sourceMappingURL=et2_dataview_interfaces.js.map

View File

@ -1,416 +0,0 @@
/**
* EGroupware eTemplate2 - Class which contains a the columns model
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
et2_core_inheritance;
et2_inheritance;
*/
import { egw } from "../jsapi/egw_global";
/**
* Class which stores the data of a single column.
*
* @augments Class
*/
export class et2_dataview_column {
/**
* Constructor
*/
constructor(_attrs) {
/**
* Defines the visibility state of this column.
*/
this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE;
this.caption = '';
/**
* Column type - Type of the column
*
* One of ET2_COL_TYPE_DEFAULT or ET2_COL_TYPE_NAME_ICON_FIXED
*/
this.type = et2_dataview_column.ET2_COL_TYPE_DEFAULT;
/**
* Width of the column
*/
this.width = 80;
/**
* Maximum width of the column
*/
this.maxWidth = 0;
/**
* Minimum width of the column, in pixels. Values below this are rejected.
*/
this.minWidth = 20;
this.id = _attrs.id;
if (typeof _attrs.visibility !== "undefined") {
this.visibility = _attrs.visibility;
}
this.caption = _attrs.caption;
if (typeof _attrs.type !== "undefined") {
this.type = _attrs.type;
}
if (typeof _attrs.width !== "undefined") {
this.set_width(_attrs.width);
}
if (typeof _attrs.maxWidth !== "undefined") {
this.maxWidth = _attrs.maxWidth;
}
if (typeof _attrs.minWidth !== "undefined") {
this.minWidth = _attrs.minWidth;
}
}
/**
* Set the column width
*
* Posible value types are:
* 1. "100" => fixedWidth 100px
* 2. "100px" => fixedWidth 100px
* 3. "50%" => relativeWidth 50%
* 4. 0.5 => relativeWidth 50%
*
* @param {float|string} _value
*/
set_width(_value) {
// Parse the width parameter.
this.relativeWidth = false;
this.fixedWidth = false;
var w = _value;
if (typeof w == 'number') {
this.relativeWidth = parseFloat(w.toFixed(3));
}
else if (w.charAt(w.length - 1) == "%" && !isNaN(w.substr(0, w.length - 1))) {
this.relativeWidth = parseInt(w.substr(0, w.length - 1)) / 100;
// Relative widths with more than 100% are not allowed!
if (this.relativeWidth > 1) {
this.relativeWidth = false;
}
}
else if (w.substr(w.length - 2, 2) == "px" && !isNaN(w.substr(0, w.length - 2))) {
this.fixedWidth = parseInt(w.substr(0, w.length - 2));
}
else if (typeof w == 'string' && !isNaN(parseFloat(w))) {
this.fixedWidth = parseInt(w);
}
}
set_visibility(_value) {
// If visibility is always, don't turn it off
if (this.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS || this.visibility == et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT)
return;
if (_value === true) {
this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE;
}
else if (_value === false) {
this.visibility = et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE;
}
else if (typeof _value == "number") {
this.visibility = _value;
}
else {
egw().debug("warn", "Invalid visibility option for column: ", _value);
}
}
}
et2_dataview_column.ET2_COL_TYPE_DEFAULT = 0;
et2_dataview_column.ET2_COL_TYPE_NAME_ICON_FIXED = 1;
et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS = 0;
et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE = 1;
et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE = 2;
et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT = 3;
et2_dataview_column.ET2_COL_VISIBILITY_DISABLED = 4;
et2_dataview_column._attributes = {
"id": {
"name": "ID",
"type": "string",
"description": "Unique identifier for this column. It is used to " +
"store changed column widths or visibilities."
},
"visibility": {
"name": "Visibility",
"type": "integer",
"default": et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE,
"description": "Defines the visibility state of this column."
},
"caption": {
"name": "Caption",
"type": "string",
"description": "Caption of the column as it is displayed in the " +
"select columns popup."
},
"type": {
"name": "Column type",
"type": "integer",
"default": et2_dataview_column.ET2_COL_TYPE_DEFAULT,
"description": "Type of the column"
},
"width": {
"name": "Width",
"type": "dimension",
"default": "80px",
"description": "Width of the column."
},
"minWidth": {
"name": "Minimum width",
"type": "integer",
"default": 20,
"description": "Minimum width of the column, in pixels. Values below this are rejected."
},
"maxWidth": {
"name": "Maximum width",
"type": "integer",
"default": 0,
"description": "Maximum width of the column"
}
};
/**
* Contains logic for the columns class. The columns class represents the unique set
* of columns a grid view owns. The parameters of the columns (except for visibility)
* do normaly not change.
*/
export class et2_dataview_columns {
constructor(_columnData) {
// Initialize some variables
this._totalWidth = 0;
this._totalFixed = 0;
this.columnWidths = [];
// Create the columns object
this.columns = new Array(_columnData.length);
for (var i = 0; i < _columnData.length; i++) {
this.columns[i] = new et2_dataview_column(_columnData[i]);
}
this._updated = true;
}
destroy() {
// Free all column objects
for (var i = 0; i < this.columns.length; i++) {
this.columns[i] = null;
}
}
updated() {
this._updated = true;
}
columnCount() {
return this.columns.length;
}
get totalWidth() {
return this._totalWidth;
}
get totalFixed() {
return this._totalFixed ? this._totalFixed : 0;
}
/**
* Set the total width of the header row
*
* @param {(string|number)} _width
*/
setTotalWidth(_width) {
if (_width != this._totalWidth && _width > 0) {
this._totalWidth = _width;
this._updated = true;
}
}
/**
* Returns the index of the colum with the given id
*
* @param {string} _id
*/
getColumnIndexById(_id) {
for (var i = 0; i < this.columns.length; i++) {
if (this.columns[i].id == _id) {
return i;
}
}
return -1;
}
/**
* Returns the column with the given id
*
* @param {string} _id
*/
getColumnById(_id) {
var idx = this.getColumnIndexById(_id);
return (idx == -1) ? null : this.columns[idx];
}
/**
* Returns the width of the column with the given index
*
* @param {number} _idx
*/
getColumnWidth(_idx) {
if (this._totalWidth > 0 && _idx >= 0 && _idx < this.columns.length) {
// Recalculate the column widths if something has changed.
if (this._updated) {
this._calculateWidths();
this._updated = false;
}
// Return the calculated width for the column with the given index.
return this.columnWidths[_idx];
}
return 0;
}
/**
* Returns an array containing the width of the column and its visibility
* state.
*/
getColumnData() {
var result = [];
for (var i = 0; i < this.columns.length; i++) {
result.push({
"id": this.columns[i].id,
"width": this.getColumnWidth(i),
"visible": this.columns[i].visibility !== et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE &&
this.columns[i].visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED
});
}
return result;
}
/**
* Returns an associative array which contains data about the visibility
* state of the columns.
*/
getColumnVisibilitySet() {
var result = {};
for (var i = 0; i < this.columns.length; i++) {
if (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS_NOSELECT) {
result[this.columns[i].id] = {
"caption": this.columns[i].caption,
"enabled": (this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_ALWAYS) &&
(this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) &&
(this.columns[i].type != et2_dataview_column.ET2_COL_TYPE_NAME_ICON_FIXED),
"visible": this.columns[i].visibility != et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE
};
}
}
return result;
}
/**
* Sets a column visiblity set
*
* @param {object} _set
*/
setColumnVisibilitySet(_set) {
for (var k in _set) {
var col = this.getColumnById(k);
if (col) {
col.set_visibility(_set[k].visible ? et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE :
et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE);
}
}
this._updated = true;
}
/* ---- PRIVATE FUNCTIONS ---- */
/**
* Calculates the absolute column width depending on the previously set
* "totalWidth" value. The calculated values are stored in the columnWidths
* array.
*/
_calculateWidths() {
// Reset some values which are used during the calculation
let _larger = Array(this.columns.length);
for (var i = 0; i < this.columns.length; i++) {
_larger[i] = false;
}
// Remove the spacing between the columns from the total width
var tw = this._totalWidth;
// Calculate how many space is - relatively - not occupied with columns with
// relative or fixed width
var totalRelative = 0;
var fixedCount = 0;
this._totalFixed = 0;
for (var i = 0; i < this.columns.length; i++) {
var col = this.columns[i];
if (col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE &&
col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) {
// Some bounds sanity checking
if (col.fixedWidth > tw || col.fixedWidth < 0) {
col.fixedWidth = false;
}
else if (col.relativeWidth > 1 || col.relativeWidth < 0) {
col.relativeWidth = false;
}
if (col.relativeWidth) {
totalRelative += col.relativeWidth;
}
else if (col.fixedWidth) {
this._totalFixed += col.fixedWidth;
fixedCount++;
}
}
}
// Now calculate the absolute width of the columns in pixels
var usedTotal = 0;
this.columnWidths = [];
for (var i = 0; i < this.columns.length; i++) {
var w = 0;
var col = this.columns[i];
if (col.visibility != et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE &&
col.visibility !== et2_dataview_column.ET2_COL_VISIBILITY_DISABLED) {
if (_larger[i]) {
w = col.maxWidth;
}
else if (col.fixedWidth) {
w = col.fixedWidth;
}
else if (col.relativeWidth) {
// Reset relative to an actual percentage (of 1.00) or
// resizing eventually sends them to 0
col.relativeWidth = col.relativeWidth / totalRelative;
w = Math.round((tw - this._totalFixed) * col.relativeWidth);
}
if (w > tw || (col.maxWidth && w > col.maxWidth)) {
w = Math.min(tw - usedTotal, col.maxWidth);
}
if (w < 0 || w < col.minWidth) {
w = Math.max(0, col.minWidth);
}
}
this.columnWidths.push(w);
usedTotal += w;
}
// Deal with any accumulated rounding errors
if (usedTotal != tw) {
var column, columnIndex;
var remaining_width = (usedTotal - tw);
// Pick the first relative column and use it
for (columnIndex = 0; columnIndex < this.columns.length; columnIndex++) {
if (this.columns[columnIndex].visibility === et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE ||
this.columns[columnIndex].visibility === et2_dataview_column.ET2_COL_VISIBILITY_DISABLED ||
this.columnWidths[columnIndex] <= 0 ||
remaining_width > 0 && this.columnWidths[columnIndex] <= this.columns[columnIndex].minWidth) {
continue;
}
var col = this.columns[columnIndex];
if (col.relativeWidth || !col.fixedWidth) {
column = col;
break;
}
else if (!col.fixedWidth) {
column = col;
}
}
if (!column) {
// Distribute shortage over all fixed width columns
var diff = Math.round(remaining_width / fixedCount);
for (var i = 0; i < this.columns.length; i++) {
var col = this.columns[i];
var col_diff = (diff < 0 ?
Math.max(remaining_width, diff) :
Math.min(remaining_width, diff));
if (!col.fixedWidth)
continue;
var new_width = this.columnWidths[i] - col_diff;
remaining_width -= col_diff;
this.columnWidths[i] = Math.max(0, Math.min(new_width, tw));
}
}
else {
this.columnWidths[columnIndex] = Math.max(column.minWidth, this.columnWidths[columnIndex] - remaining_width);
}
}
}
}
//# sourceMappingURL=et2_dataview_model_columns.js.map

View File

@ -1,121 +0,0 @@
/**
* EGroupware eTemplate2 - Contains interfaces used inside the dataview
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
egw_action.egw_action_common;
egw_action.egw_action;
/vendor/bower-asset/jquery-touchswipe/jquery.touchSwipe.js;
*/
import { egwActionObjectInterface } from "../egw_action/egw_action.js";
import { EGW_AO_SHIFT_STATE_MULTI, EGW_AO_STATE_FOCUSED, EGW_AO_STATE_SELECTED } from '../egw_action/egw_action_constants.js';
import { egwBitIsSet, egwGetShiftState, egwPreventSelect, egwSetBit, egwUnfocus, egwIsMobile } from "../egw_action/egw_action_common.js";
import { _egw_active_menu } from "../egw_action/egw_menu.js";
/**
* Contains the action object interface implementation for the nextmatch widget
* row.
*/
export const EGW_SELECTMODE_DEFAULT = 0;
export const EGW_SELECTMODE_TOGGLE = 1;
/**
* An action object interface for each nextmatch widget row - "inherits" from
* egwActionObjectInterface
*
* @param {DOMNode} _node
*/
export function et2_dataview_rowAOI(_node) {
"use strict";
var aoi = new egwActionObjectInterface();
aoi.node = _node;
aoi.selectMode = EGW_SELECTMODE_DEFAULT;
aoi.checkBox = null; //(jQuery(":checkbox", aoi.node))[0];
// Rows without a checkbox OR an id set are unselectable
aoi.doGetDOMNode = function () {
return aoi.node;
};
// Prevent the browser from selecting the content of the element, when
// a special key is pressed.
jQuery(_node).mousedown(egwPreventSelect);
/**
* Now append some action code to the node
*
* @memberOf et2_dataview_rowAOI
* @param {DOMEvent} e
* @param {object} _params
*/
var selectHandler = function (e, _params) {
// Reset the focus so that keyboard navigation will work properly
// after the element has been clicked
egwUnfocus();
// Reset the prevent selection code (in order to allow wanted
// selection of text)
_node.onselectstart = null;
if (e.target != aoi.checkBox) {
var selected = egwBitIsSet(aoi.getState(), EGW_AO_STATE_SELECTED);
var state = egwGetShiftState(e);
if (_params) {
if (egwIsMobile()) {
switch (_params.swip) {
case "left":
case "right":
state = 1;
// Hide context menu on swip actions
if (_egw_active_menu)
_egw_active_menu.hide();
break;
case "up":
case "down":
return;
}
}
}
switch (aoi.selectMode) {
case EGW_SELECTMODE_DEFAULT:
aoi.updateState(EGW_AO_STATE_SELECTED, !egwBitIsSet(state, EGW_AO_SHIFT_STATE_MULTI) || !selected, state);
break;
case EGW_SELECTMODE_TOGGLE:
aoi.updateState(EGW_AO_STATE_SELECTED, !selected, egwSetBit(state, EGW_AO_SHIFT_STATE_MULTI, true));
break;
}
}
};
if (egwIsMobile()) {
jQuery(_node).swipe({
allowPageScroll: "vertical",
longTapThreshold: 10,
swipe: function (event, direction, distance) {
if (distance > 100)
selectHandler(event, { swip: direction });
},
tap: function (event, duration) {
selectHandler(event);
},
// stop scrolling touch being confused from tap
longTap: function (event) {
return;
}
});
}
else {
jQuery(_node).click(selectHandler);
}
jQuery(aoi.checkBox).change(function () {
aoi.updateState(EGW_AO_STATE_SELECTED, this.checked, EGW_AO_SHIFT_STATE_MULTI);
});
aoi.doSetState = function (_state) {
var selected = egwBitIsSet(_state, EGW_AO_STATE_SELECTED);
if (this.checkBox) {
this.checkBox.checked = selected;
}
jQuery(this.node).toggleClass('focused', egwBitIsSet(_state, EGW_AO_STATE_FOCUSED));
jQuery(this.node).toggleClass('selected', selected);
};
return aoi;
}
//# sourceMappingURL=et2_dataview_view_aoi.js.map

View File

@ -1,312 +0,0 @@
/**
* EGroupware eTemplate2 - dataview code
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
* @version $Id$
*/
import { et2_bounds } from "./et2_core_common";
import { ClassWithInterfaces } from "./et2_core_inheritance";
/**
* The et2_dataview_container class is the main object each dataview consits of.
* Each row, spacer as well as the grid itself are containers. A container is
* described by its parent element and a certain height. On the DOM-Level a
* container may consist of multiple "tr" nodes, which are treated as a unit.
* Some containers (like grid containers) are capable of managing a set of child
* containers. Each container can indicate, that it thinks that it's height
* might have changed. In that case it informs its parent element about that.
* The only requirement for the parent element is, that it implements the
* et2_dataview_IInvalidatable interface.
* A container does not know where it resides inside the grid, or whether it is
* currently visible or not -- this information is efficiently managed by the
* et2_dataview_grid container.
*
* @augments Class
*/
export class et2_dataview_container extends ClassWithInterfaces {
/**
* Initializes the container object.
*
* @param _parent is an object which implements the IInvalidatable
* interface. _parent may not be null.
* @memberOf et2_dataview_container
*/
constructor(_parent) {
super();
// Copy the given invalidation element
this._parent = _parent;
this._nodes = [];
this._inTree = false;
this._attachData = { "node": null, "prepend": false };
this._destroyCallback = null;
this._destroyContext = null;
this._height = -1;
this._index = 0;
this._top = 0;
}
/**
* Destroys this container. Classes deriving from et2_dataview_container
* should override this method and take care of unregistering all event
* handlers etc.
*/
destroy() {
// Remove the nodes from the tree
this.removeFromTree();
// Call the callback function (if one is registered)
if (this._destroyCallback) {
this._destroyCallback.call(this._destroyContext, this);
}
}
/**
* Sets the "destroyCallback" -- the given function gets called whenever
* the container is destroyed. This instance is passed as an parameter to
* the callback.
*
* @param {function} _callback
* @param {object} _context
*/
setDestroyCallback(_callback, _context) {
this._destroyCallback = _callback;
this._destroyContext = _context;
}
/**
* Inserts all container nodes into the DOM tree after or before the given
* element.
*
* @param _node is the node after/before which the container "tr"s should
* get inserted. _node should be a simple DOM node, not a jQuery object.
* @param _prepend specifies whether the container should be inserted before
* or after the given node. Inserting before is needed for inserting the
* first element in front of an spacer.
*/
insertIntoTree(_node, _prepend) {
if (!this._inTree && _node != null && this._nodes.length > 0) {
// Store the parent node and indicate that this element is now in
// the tree.
this._attachData = { node: _node, prepend: _prepend };
this._inTree = true;
for (let i = 0; i < this._nodes.length; i++) {
if (i == 0) {
if (_prepend) {
_node.before(this._nodes[0]);
}
else {
_node.after(this._nodes[0]);
}
}
else {
// Insert all following nodes after the previous node
this._nodes[i - 1].after(this._nodes[i]);
}
}
// Invalidate this element in order to update the height of the
// parent
this.invalidate();
}
}
/**
* Removes all container nodes from the tree.
*/
removeFromTree() {
if (this._inTree) {
// Call the jQuery remove function to remove all nodes from the tree
// again.
for (let i = 0; i < this._nodes.length; i++) {
this._nodes[i].remove();
}
// Reset the "attachData"
this._inTree = false;
this._attachData = { "node": null, "prepend": false };
}
}
/**
* Appends a node to the container.
*
* @param _node is the DOM-Node which should be appended.
*/
appendNode(_node) {
// Add the given node to the "nodes" array
this._nodes.push(_node);
// If the container is already in the tree, attach the given node to the
// tree.
if (this._inTree) {
if (this._nodes.length === 1) {
if (this._attachData.prepend) {
this._attachData.node.before(_node);
}
else {
this._attachData.node.after(_node);
}
}
else {
this._nodes[this._nodes.length - 2].after(_node);
}
this.invalidate();
}
}
/**
* Removes a certain node from the container
*
* @param {HTMLElement} _node
*/
removeNode(_node) {
// Get the index of the node in the nodes array
const idx = this._nodes.indexOf(_node);
if (idx >= 0) {
// Remove the node if the container is currently attached
if (this._inTree) {
_node.parentNode.removeChild(_node);
}
// Remove the node from the nodes array
this._nodes.splice(idx, 1);
}
}
/**
* Returns the last node of the container - new nodes have to be appended
* after it.
*/
getLastNode() {
if (this._nodes.length > 0) {
return this._nodes[this._nodes.length - 1];
}
return null;
}
/**
* Returns the first node of the container.
*/
getFirstNode() {
return this._nodes.length > 0 ? this._nodes[0] : null;
}
/**
* Returns the accumulated height of all container nodes. Only visible nodes
* (without "display: none" etc.) are taken into account.
*/
getHeight() {
if (this._height === -1 && this._inTree) {
this._height = 0;
// Setting this before measuring height helps with issues getting the
// wrong height due to margins & collapsed borders
this.tr.css('display', 'block');
// Increment the height value for each visible container node
for (let i = 0; i < this._nodes.length; i++) {
if (et2_dataview_container._isVisible(this._nodes[i][0])) {
this._height += et2_dataview_container._nodeHeight(this._nodes[i][0]);
}
}
this.tr.css('display', '');
}
return (this._height === -1) ? 0 : this._height;
}
/**
* Returns a datastructure containing information used for calculating the
* average row height of a grid.
* The datastructure has the
* {
* avgHeight: <the calculated average height of this element>,
* avgCount: <the element count this calculation was based on>
* }
*/
getAvgHeightData() {
return {
"avgHeight": this.getHeight(),
"avgCount": 1
};
}
/**
* Returns the previously set "pixel top" of the container.
*/
getTop() {
return this._top;
}
/**
* Returns the "pixel bottom" of the container.
*/
getBottom() {
return this._top + this.getHeight();
}
/**
* Returns the range of the element.
*/
getRange() {
return et2_bounds(this.getTop(), this.getBottom());
}
/**
* Returns the index of the element.
*/
getIndex() {
return this._index;
}
/**
* Returns how many elements this container represents.
*/
getCount() {
return 1;
}
/**
* Sets the top of the element.
*
* @param {number} _value
*/
setTop(_value) {
this._top = _value;
}
/**
* Sets the index of the element.
*
* @param {number} _value
*/
setIndex(_value) {
this._index = _value;
}
/* -- et2_dataview_IInvalidatable -- */
/**
* Broadcasts an invalidation through the container tree. Marks the own
* height as invalid.
*/
invalidate() {
// Abort if this element is already marked as invalid.
if (this._height !== -1) {
// Delete the own, probably computed height
this._height = -1;
// Broadcast the invalidation to the parent element
this._parent.invalidate();
}
}
/* -- PRIVATE FUNCTIONS -- */
/**
* Used to check whether an element is visible or not (non recursive).
*
* @param _obj is the element which should be checked for visibility, it is
* only checked whether some stylesheet makes the element invisible, not if
* the given object is actually inside the DOM.
*/
static _isVisible(_obj) {
// Check whether the element is localy invisible
if (_obj.style && (_obj.style.display === "none"
|| _obj.style.visibility === "none")) {
return false;
}
// Get the computed style of the element
const style = window.getComputedStyle ? window.getComputedStyle(_obj, null)
// @ts-ignore
: _obj.currentStyle;
if (style.display === "none" || style.visibility === "none") {
return false;
}
return true;
}
/**
* Returns the height of a node in pixels and zero if the element is not
* visible. The height is clamped to positive values.
*
* @param {HTMLElement} _node
*/
static _nodeHeight(_node) {
return _node.offsetHeight;
}
}
//# sourceMappingURL=et2_dataview_view_container.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,177 +0,0 @@
/**
* EGroupware eTemplate2 - Functions which allow resizing of table headers
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
import { egw } from "../jsapi/egw_global";
/**
* This set of functions is currently only supporting resizing in ew-direction
*/
export class et2_dataview_view_resizable {
// In resize region returns whether the mouse is currently in the
// "resizeRegion"
static inResizeRegion(_x, _elem) {
var ol = _x - _elem.offset().left;
return (ol > (_elem.outerWidth(true) - et2_dataview_view_resizable.RESIZE_BORDER));
}
static startResize(_outerElem, _elem, _callback, _column) {
if (this.overlay == null || this.helper == null) {
// Prevent text selection
// FireFox handles highlight prevention (text selection) different than other browsers
if (typeof _elem[0].style.MozUserSelect != "undefined") {
_elem[0].style.MozUserSelect = "none";
}
else {
_elem[0].onselectstart = function () {
return false;
};
}
// Indicate resizing is in progress
jQuery(_outerElem).addClass('egwResizing');
// Reset the "didResize" flag
this.didResize = false;
// Create the resize helper
var left = _elem.offset().left;
this.helper = jQuery(document.createElement("div"))
.addClass("egwResizeHelper")
.appendTo("body")
.css("top", _elem.offset().top + "px")
.css("left", left + "px")
.css("height", _outerElem.outerHeight(true) + "px");
// Create the overlay which will be catching the mouse movements
this.overlay = jQuery(document.createElement("div"))
.addClass("egwResizeOverlay")
.bind("mousemove", function (e) {
this.didResize = true;
this.resizeWidth = Math.max(e.pageX - left + et2_dataview_view_resizable.RESIZE_ADD, _column && _column.minWidth ? _column.minWidth : et2_dataview_view_resizable.RESIZE_MIN_WIDTH);
this.helper.css("width", this.resizeWidth + "px");
}.bind(this))
.bind("mouseup", function () {
this.stopResize(_outerElem);
// Reset text selection
_elem[0].onselectstart = null;
// Call the callback if the user actually performed a resize
if (this.didResize) {
_callback(this.resizeWidth);
}
}.bind(this))
.appendTo("body");
}
}
static stopResize(_outerElem) {
jQuery(_outerElem).removeClass('egwResizing');
if (this.helper != null) {
this.helper.remove();
this.helper = null;
}
if (this.overlay != null) {
this.overlay.remove();
this.overlay = null;
}
}
}
// Define some constants
et2_dataview_view_resizable.RESIZE_BORDER = 12;
et2_dataview_view_resizable.RESIZE_MIN_WIDTH = 25;
et2_dataview_view_resizable.RESIZE_ADD = 2; // Used to ensure mouse is under the resize element after resizing has finished
et2_dataview_view_resizable.helper = null;
et2_dataview_view_resizable.overlay = null;
et2_dataview_view_resizable.didResize = false;
et2_dataview_view_resizable.resizeWidth = 0;
et2_dataview_view_resizable.makeResizeable = function (_elem, _callback, _context) {
// Get the table surrounding the given element - this element is used to
// align the helper properly
var outerTable = _elem.closest("table");
// Bind the "mousemove" event in the "resize" namespace
_elem.bind("mousemove.resize", function (e) {
var stopResize = false;
// Stop switch to resize cursor if the mouse position
// is more intended for scrollbar not the resize edge
// 8pixel is an arbitary number for scrolbar area
if (e.target.clientHeight < e.target.scrollHeight && e.target.offsetWidth - e.offsetX <= 8) {
stopResize = true;
}
_elem.css("cursor", et2_dataview_view_resizable.inResizeRegion(e.pageX, _elem) && !stopResize ? "ew-resize" : "auto");
});
// Bind the "mousedown" event in the "resize" namespace
_elem.bind("mousedown.resize", function (e) {
var stopResize = false;
// Stop resize if the mouse position is more intended
// for scrollbar not the resize edge
// 8pixel is an arbitary number for scrolbar area
if (e.target.clientHeight < e.target.scrollHeight && e.target.offsetWidth - e.offsetX <= 8) {
stopResize = true;
}
// Do not triger startResize if clicked element is select-tag, as it may causes conflict in some browsers
if (et2_dataview_view_resizable.inResizeRegion(e.pageX, _elem) && e.target.tagName != 'SELECT' && !stopResize) {
// Start the resizing
et2_dataview_view_resizable.startResize(outerTable, _elem, function (_w) {
_callback.call(_context, _w);
}, _context);
}
});
// Bind double click for auto-size
_elem.dblclick(function (e) {
// Just show message for relative width columns
if (_context && _context.relativeWidth) {
return egw.message(egw.lang('You tried to automatically size a flex column, which always takes the rest of the space', 'info'));
}
// Find column class - it's usually the first one
var col_class = '';
for (var i = 0; i < this.classList.length; i++) {
if (this.classList[i].indexOf('gridCont') === 0) {
col_class = this.classList[i];
break;
}
}
// Find widest part, including header
var column = jQuery(this);
column.children().css('width', 'auto');
var max_width = column.children().children().innerWidth();
var padding = column.outerWidth(true) - max_width;
var resize = jQuery(this).closest('.egwGridView_outer')
.find('tbody td.' + col_class + '> div:first-child')
.add(column.children())
// Set column width to auto to allow space for everything to flow
.css('width', 'auto');
resize.children()
.css({ 'white-space': 'nowrap' })
.each(function () {
var col = jQuery(this);
// Find visible (text) children and force them to not wrap
var children = col.find('span:visible, time:visible, label:visible')
.css({ 'white-space': 'nowrap' });
this.offsetWidth;
children.each(function () {
var child = jQuery(this);
this.offsetWidth;
if (child.outerWidth() > max_width) {
max_width = child.outerWidth();
}
window.getComputedStyle(this).width;
});
this.offsetWidth;
if (col.innerWidth() > max_width) {
max_width = col.innerWidth();
}
// Reset children
children.css('white-space', '');
children.css('display', '');
})
.css({ 'white-space': '' });
// Reset column
column.children().css('width', '');
resize.css('width', '');
_callback.call(_context, max_width + padding);
});
};
et2_dataview_view_resizable.et2_dataview_resetResizeable = function (_elem) {
// Remove all events in the ".resize" namespace from the element
_elem.unbind(".resize");
};
//# sourceMappingURL=et2_dataview_view_resizeable.js.map

View File

@ -1,140 +0,0 @@
/**
* EGroupware eTemplate2 - dataview
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
egw_action.egw_action;
et2_dataview_view_container;
*/
import { et2_dataview_IViewRange } from "./et2_dataview_interfaces";
import { et2_dataview_container } from "./et2_dataview_view_container";
export class et2_dataview_row extends et2_dataview_container {
/**
* Creates the row container. Use the "setRow" function to load the actual
* row content.
*
* @param _parent is the row parent container.
*/
constructor(_parent) {
// Call the inherited constructor
super(_parent);
// Create the outer "tr" tag and append it to the container
this.tr = jQuery(document.createElement("tr"));
this.appendNode(this.tr);
// Grid row which gets expanded when clicking on the corresponding
// button
this.expansionContainer = null;
this.expansionVisible = false;
// Toggle button which is used to show and hide the expansionContainer
this.expansionButton = null;
}
destroy() {
if (this.expansionContainer != null) {
this.expansionContainer.destroy();
}
super.destroy();
}
clear() {
this.tr.empty();
}
makeExpandable(_expandable, _callback, _context) {
if (_expandable) {
// Create the tr and the button if this has not been done yet
if (!this.expansionButton) {
this.expansionButton = jQuery(document.createElement("span"));
this.expansionButton.addClass("arrow closed");
}
// Update context
var self = this;
this.expansionButton.off("click").on("click", function (e) {
self._handleExpansionButtonClick(_callback, _context);
e.stopImmediatePropagation();
});
jQuery("td:first", this.tr).prepend(this.expansionButton);
}
else {
// If the row is made non-expandable, remove the created DOM-Nodes
if (this.expansionButton) {
this.expansionButton.remove();
}
if (this.expansionContainer) {
this.expansionContainer.destroy();
}
this.expansionButton = null;
this.expansionContainer = null;
}
}
removeFromTree() {
if (this.expansionContainer) {
this.expansionContainer.removeFromTree();
}
this.expansionContainer = null;
this.expansionButton = null;
super.removeFromTree();
}
getDOMNode() {
return this.tr[0];
}
getJNode() {
return this.tr;
}
getHeight() {
var h = super.getHeight();
if (this.expansionContainer && this.expansionVisible) {
h += this.expansionContainer.getHeight();
}
return h;
}
getAvgHeightData() {
// Only take the height of the own tr into account
//var oldVisible = this.expansionVisible;
this.expansionVisible = false;
var res = {
"avgHeight": this.getHeight(),
"avgCount": 1
};
this.expansionVisible = true;
return res;
}
/** -- PRIVATE FUNCTIONS -- **/
_handleExpansionButtonClick(_callback, _context) {
// Create the "expansionContainer" if it does not exist yet
if (!this.expansionContainer) {
this.expansionContainer = _callback.call(_context);
this.expansionContainer.insertIntoTree(this.tr);
this.expansionVisible = false;
}
// Toggle the visibility of the expansion tr
this.expansionVisible = !this.expansionVisible;
jQuery(this.expansionContainer._nodes[0]).toggle(this.expansionVisible);
// Set the class of the arrow
if (this.expansionVisible) {
this.expansionButton.addClass("opened");
this.expansionButton.removeClass("closed");
}
else {
this.expansionButton.addClass("closed");
this.expansionButton.removeClass("opened");
}
this.invalidate();
}
/** -- Implementation of et2_dataview_IViewRange -- **/
setViewRange(_range) {
if (this.expansionContainer && this.expansionVisible
&& this.expansionContainer.implements(et2_dataview_IViewRange)) {
// Substract the height of the own row from the container
var oh = jQuery(this._nodes[0]).height();
_range.top -= oh;
// Proxy the setViewRange call to the expansion container
this.expansionContainer.setViewRange(_range);
}
}
}
//# sourceMappingURL=et2_dataview_view_row.js.map

View File

@ -1,109 +0,0 @@
/**
* EGroupware eTemplate2 - Class which contains a factory method for rows
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inheritance;
et2_core_interfaces;
et2_core_arrayMgr;
et2_core_widget;
*/
/**
* The row provider contains prototypes (full clonable dom-trees)
* for all registered row types.
*/
export class et2_dataview_rowProvider {
/**
*
* @param _outerId
* @param _columnIds
*/
constructor(_outerId, _columnIds) {
// Copy the given parameters
this._outerId = _outerId;
this._columnIds = _columnIds;
this._prototypes = {};
this._template = null;
this._mgrs = null;
this._rootWidget = null;
// Create the default row "prototypes"
this._createFullRowPrototype();
this._createDefaultPrototype();
this._createEmptyPrototype();
this._createLoadingPrototype();
}
destroy() {
this._template = null;
this._mgrs = null;
this._rootWidget = null;
this._prototypes = {};
this._columnIds = [];
}
getColumnCount() {
return this._columnIds.length;
}
/**
* Returns a clone of the prototype with the given name. If the generator
* callback function is given, this function is called if the prototype
* does not yet registered.
*
* @param {string} _name
* @param {function} _generator
* @param {object} _context
*/
getPrototype(_name, _generator, _context) {
if (typeof this._prototypes[_name] == "undefined") {
if (typeof _generator != "undefined") {
this._prototypes[_name] = _generator.call(_context, this._outerId, this._columnIds);
}
else {
return null;
}
}
return this._prototypes[_name].clone();
}
/* ---- PRIVATE FUNCTIONS ---- */
_createFullRowPrototype() {
var tr = jQuery(document.createElement("tr"));
var td = jQuery(document.createElement("td"))
.addClass(this._outerId + "_td_fullRow")
.attr("colspan", this._columnIds.length)
.appendTo(tr);
var div = jQuery(document.createElement("div"))
.addClass(this._outerId + "_div_fullRow")
.appendTo(td);
this._prototypes["fullRow"] = tr;
}
_createDefaultPrototype() {
var tr = jQuery(document.createElement("tr"));
// Append a td for each column
for (var column of this._columnIds) {
if (!column)
continue;
var td = jQuery(document.createElement("td"))
.addClass(this._outerId + "_td_" + column)
.appendTo(tr);
var div = jQuery(document.createElement("div"))
.addClass(this._outerId + "_div_" + column)
.addClass("innerContainer")
.appendTo(td);
}
this._prototypes["default"] = tr;
}
_createEmptyPrototype() {
this._prototypes["empty"] = jQuery(document.createElement("tr"));
}
_createLoadingPrototype() {
var fullRow = this.getPrototype("fullRow");
jQuery("div", fullRow).addClass("loading");
this._prototypes["loading"] = fullRow;
}
}
//# sourceMappingURL=et2_dataview_view_rowProvider.js.map

View File

@ -1,83 +0,0 @@
/**
* EGroupware eTemplate2 - Class which contains the spacer container
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_dataview_view_container;
*/
import { et2_dataview_container } from "./et2_dataview_view_container";
/**
* @augments et2_dataview_container
*/
export class et2_dataview_spacer extends et2_dataview_container {
/**
* Constructor
*
* @param _parent
* @param _rowProvider
* @memberOf et2_dataview_spacer
*/
constructor(_parent, _rowProvider) {
// Call the inherited container constructor
super(_parent);
// Initialize the row count and the row height
this._count = 0;
this._rowHeight = 19;
this._avgSum = 0;
this._avgCount = 0;
// Get the spacer row and append it to the container
this.spacerNode = _rowProvider.getPrototype("spacer", this._createSpacerPrototype, this);
this._phDiv = jQuery("td", this.spacerNode);
this.appendNode(this.spacerNode);
}
setCount(_count, _rowHeight) {
// Set the new count and _rowHeight if given
this._count = _count;
if (typeof _rowHeight !== "undefined") {
this._rowHeight = _rowHeight;
}
// Update the element height
this._phDiv.height(this._count * this._rowHeight);
// Call the invalidate function
this.invalidate();
}
getCount() {
return this._count;
}
getHeight() {
// Set the calculated height, so that "invalidate" will work correctly
this._height = this._count * this._rowHeight;
return this._height;
}
getAvgHeightData() {
if (this._avgCount > 0) {
return {
"avgHeight": this._avgSum / this._avgCount,
"avgCount": this._avgCount
};
}
return null;
}
addAvgHeight(_height) {
this._avgSum += _height;
this._avgCount++;
}
/* ---- PRIVATE FUNCTIONS ---- */
_createSpacerPrototype(_outerId, _columnIds) {
var tr = jQuery(document.createElement("tr"));
var td = jQuery(document.createElement("td"))
.addClass("egwGridView_spacer")
.addClass(_outerId + "_spacer_fullRow")
.attr("colspan", _columnIds.length)
.appendTo(tr);
return tr;
}
}
//# sourceMappingURL=et2_dataview_view_spacer.js.map

View File

@ -1,89 +0,0 @@
/**
* EGroupware eTemplate2 - dataview code
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage dataview
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2014
* @version $Id: et2_dataview_view_container_1.js 46338 2014-03-20 09:40:37Z ralfbecker $
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_dataview_interfaces;
*/
import { et2_dataview_row } from "./et2_dataview_view_row";
/**
* Displays tiles or thumbnails (squares) instead of full rows.
*
* It's important that the template specifies a fixed width and height (via CSS)
* so that the rows and columns work out properly.
*
*/
export class et2_dataview_tile extends et2_dataview_row {
/**
* Creates the row container. Use the "setRow" function to load the actual
* row content.
*
* @param _parent is the row parent container.
* @memberOf et2_dataview_row
*/
constructor(_parent) {
// Call the inherited constructor
super(_parent);
this.columns = 4;
// Make sure the needed class is there to get the CSS
this.tr.addClass('tile');
}
makeExpandable(_expandable, _callback, _context) {
// Nope. It mostly works, it's just weird.
}
getAvgHeightData() {
var res = {
"avgHeight": this.getHeight() / this.columns,
"avgCount": this.columns
};
return res;
}
/**
* Returns the height for the tile.
*
* This is where we do the magic. If a new row should start, we return the proper
* height. If this should be another tile in the same row, we say it has 0 height.
* @returns {Number}
*/
getHeight() {
if (this._index % this.columns == 0) {
return super.getHeight();
}
else {
return 0;
}
}
/**
* Broadcasts an invalidation through the container tree. Marks the own
* height as invalid.
*/
invalidate() {
if (this._inTree && this.tr) {
var template_width = jQuery('.innerContainer', this.tr).children().outerWidth(true);
if (template_width) {
this.tr.css('width', template_width + (this.tr.outerWidth(true) - this.tr.width()));
}
}
this._recalculate_columns();
super.invalidate();
}
/**
* Recalculate how many columns we can fit in a row.
* While browser takes care of the actual layout, we need this for proper
* pagination.
*/
_recalculate_columns() {
if (this._inTree && this.tr && this.tr.parent()) {
this.columns = Math.max(1, parseInt(this.tr.parent().innerWidth() / this.tr.outerWidth(true)));
}
}
}
//# sourceMappingURL=et2_dataview_view_tile.js.map

View File

@ -1,635 +0,0 @@
/**
* EGroupware eTemplate2 - JS Custom fields object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
*/
/*egw:uses
lib/tooltip;
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_xml;
et2_core_DOMWidget;
et2_core_inputWidget;
*/
import { et2_createWidget, et2_register_widget, et2_registry } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_cloneObject, et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
export class et2_customfields_list extends et2_valueWidget {
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_customfields_list._attributes, _child || {}));
this.rows = {};
this.widgets = {};
this.detachedNodes = [];
// Some apps (infolog edit) don't give ID, so assign one to get settings
if (!this.id) {
this.id = _attrs.id = et2_customfields_list.DEFAULT_ID;
// Add all attributes hidden in the content arrays to the attributes
// parameter
this.transformAttributes(_attrs);
// Create a local copy of the options object
this.options = et2_cloneObject(_attrs);
}
// Create the table body and the table
this.tbody = jQuery(document.createElement("tbody"));
this.table = jQuery(document.createElement("table"))
.addClass("et2_grid et2_customfield_list");
this.table.append(this.tbody);
if (!this.options.fields)
this.options.fields = {};
if (typeof this.options.fields === 'string') {
const fields = this.options.fields.split(',');
this.options.fields = {};
for (var i = 0; i < fields.length; i++) {
this.options.fields[fields[i]] = true;
}
}
if (this.options.type_filter && typeof this.options.type_filter == "string") {
this.options.type_filter = this.options.type_filter.split(",");
}
if (this.options.type_filter) {
const already_filtered = !jQuery.isEmptyObject(this.options.fields);
for (let field_name in this.options.customfields) {
// Already excluded?
if (already_filtered && !this.options.fields[field_name])
continue;
if (!this.options.customfields[field_name].type2 || this.options.customfields[field_name].type2.length == 0 ||
this.options.customfields[field_name].type2 == '0') {
// No restrictions
this.options.fields[field_name] = true;
continue;
}
const types = typeof this.options.customfields[field_name].type2 == 'string' ? this.options.customfields[field_name].type2.split(",") : this.options.customfields[field_name].type2;
this.options.fields[field_name] = false;
for (var i = 0; i < types.length; i++) {
if (jQuery.inArray(types[i], this.options.type_filter) > -1) {
this.options.fields[field_name] = true;
}
}
}
}
this.setDOMNode(this.table[0]);
}
destroy() {
super.destroy();
this.rows = {};
this.widgets = {};
this.detachedNodes = [];
this.tbody = null;
}
/**
* What does this do? I don't know, but when everything is done the second
* time, this makes it work. Otherwise, all custom fields are lost.
*/
assign(_obj) {
this.loadFields();
}
getDOMNode(_sender) {
// Check whether the _sender object exists inside the management array
if (this.rows && _sender.id && this.rows[_sender.id]) {
return this.rows[_sender.id];
}
if (this.rows && _sender.id && _sender.id.indexOf("_label") && this.rows[_sender.id.replace("_label", "")]) {
return jQuery(this.rows[_sender.id.replace("_label", "")]).prev("td")[0] || null;
}
return super.getDOMNode(_sender);
}
/**
* Initialize widgets for custom fields
*/
loadFields() {
if (!this.options || !this.options.customfields)
return;
// Already set up - avoid duplicates in nextmatch
if (this.getType() == 'customfields-list' && !this.isInTree() && Object.keys(this.widgets).length > 0)
return;
if (!jQuery.isEmptyObject(this.widgets))
return;
// Check for global setting changes (visibility)
const global_data = this.getArrayMgr("modifications").getRoot().getEntry('~custom_fields~');
if (global_data && global_data.fields && !this.options.fields)
this.options.fields = global_data.fields;
// For checking app entries
const apps = this.egw().link_app_list();
// Create the table rows
for (let field_name in this.options.customfields) {
// Skip fields if we're filtering
if (this.getType() != 'customfields-list' && !jQuery.isEmptyObject(this.options.fields) && !this.options.fields[field_name])
continue;
const field = this.options.customfields[field_name];
let id = this.options.prefix + field_name;
// Need curlies around ID for nm row expansion
if (this.id == '$row') {
id = "{" + this.id + "}" + "[" + this.options.prefix + field_name + "]";
}
else if (this.id != et2_customfields_list.DEFAULT_ID) {
// Prefix this ID to avoid potential ID collisions
id = this.id + "[" + id + "]";
}
// Avoid creating field twice
if (!this.rows[id]) {
const row = jQuery(document.createElement("tr"))
.appendTo(this.tbody)
.addClass(this.id + '_' + id);
let cf = jQuery(document.createElement("td"))
.appendTo(row);
if (!field.type)
field.type = 'text";';
const setup_function = '_setup_' + (apps[field.type] ? 'link_entry' : field.type.replace("-", "_"));
const attrs = jQuery.extend({}, this.options[field_name] ? this.options[field_name] : {}, {
'id': id,
'statustext': field.help,
'needed': field.needed,
'readonly': this.getArrayMgr("readonlys").isReadOnly(id, "" + this.options.readonly),
'value': this.options.value[this.options.prefix + field_name]
});
// Can't have a required readonly, it will warn & be removed later, so avoid the warning
if (attrs.readonly === true)
delete attrs.needed;
if (this.options.onchange) {
attrs.onchange = this.options.onchange;
}
if (this[setup_function]) {
const no_skip = this[setup_function].call(this, field_name, field, attrs);
if (!no_skip)
continue;
}
this.rows[id] = cf[0];
if (this.getType() == 'customfields-list') {
// No label, cust widget
attrs.readonly = true;
// Widget tooltips don't work in nextmatch because of the creation / binding separation
// Set title to field label so browser will show something
// Add field label & help as data attribute to row, so it can be stylied with CSS (title should be disabled)
row.attr('title', field.label);
row.attr('data-label', field.label);
row.attr('data-field', field_name);
row.attr('data-help', field.help);
this.detachedNodes.push(row[0]);
}
else {
// Label in first column, widget in 2nd
jQuery(document.createElement("td"))
.prependTo(row);
et2_createWidget("label", { id: id + "_label", value: field.label, for: id }, this);
}
// Set any additional attributes set in options, but not for widgets that pass actual options
if (['select', 'radio', 'radiogroup', 'checkbox', 'button'].indexOf(field.type) == -1 && !jQuery.isEmptyObject(field.values)) {
const w = et2_registry[attrs.type ? attrs.type : field.type];
for (let attr_name in field.values) {
if (typeof w._attributes[attr_name] != "undefined") {
attrs[attr_name] = field.values[attr_name];
}
}
}
// Create widget
const widget = this.widgets[field_name] = et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
}
// Field is not to be shown
if (!this.options.fields || jQuery.isEmptyObject(this.options.fields) || this.options.fields[field_name] == true) {
jQuery(this.rows[field_name]).show();
}
else {
jQuery(this.rows[field_name]).hide();
}
}
}
/**
* Read needed info on available custom fields from various places it's stored.
*/
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
// Add in settings that are objects
// Customized settings for this widget (unlikely)
const data = this.getArrayMgr("modifications").getEntry(this.id);
// Check for global settings
const global_data = this.getArrayMgr("modifications").getRoot().getEntry('~custom_fields~', true);
if (global_data) {
for (let key in data) {
// Don't overwrite fields with global values
if (global_data[key] && key !== 'fields') {
data[key] = jQuery.extend(true, {}, data[key], global_data[key]);
}
}
}
for (var key in data) {
_attrs[key] = data[key];
}
for (let key in global_data) {
if (typeof global_data[key] != 'undefined' && !_attrs[key])
_attrs[key] = global_data[key];
}
if (this.id) {
// Set the value for this element
const contentMgr = this.getArrayMgr("content");
if (contentMgr != null) {
const val = contentMgr.getEntry(this.id);
_attrs["value"] = {};
let prefix = _attrs["prefix"] || et2_customfields_list.PREFIX;
if (val !== null) {
if (this.id.indexOf(prefix) === 0 && typeof data.fields != 'undefined' && data.fields[this.id.replace(prefix, '')] === true) {
_attrs['value'][this.id] = val;
}
else {
// Only set the values that match desired custom fields
for (let key in val) {
if (key.indexOf(prefix) === 0) {
_attrs["value"][key] = val[key];
}
}
}
//_attrs["value"] = val;
}
else {
// Check for custom fields directly in record
for (var key in _attrs.customfields) {
_attrs["value"][prefix + key] = contentMgr.getEntry(prefix + key);
}
}
}
}
}
loadFromXML(_node) {
this.loadFields();
// Load the nodes as usual
super.loadFromXML(_node);
}
set_value(_value) {
if (!this.options.customfields)
return;
for (let field_name in this.options.customfields) {
// Skip fields if we're filtering
if (!jQuery.isEmptyObject(this.options.fields) && !this.options.fields[field_name])
continue;
// Make sure widget is created, and has the needed function
if (!this.widgets[field_name] || !this.widgets[field_name].set_value)
continue;
let value = _value[this.options.prefix + field_name] ? _value[this.options.prefix + field_name] : null;
// Check if ID was missing
if (value == null && this.id == et2_customfields_list.DEFAULT_ID && this.getArrayMgr("content").getEntry(this.options.prefix + field_name)) {
value = this.getArrayMgr("content").getEntry(this.options.prefix + field_name);
}
switch (this.options.customfields[field_name].type) {
case 'date':
// Date custom fields are always in Y-m-d, which seldom matches user's preference
// which fails when sent to date widget. This is only used for nm rows, when possible
// this is fixed server side
if (value && isNaN(value)) {
value = jQuery.datepicker.parseDate("yy-mm-dd", value);
}
break;
}
this.widgets[field_name].set_value(value);
}
}
/**
* et2_IInput so the custom field can be it's own widget.
*/
getValue() {
// Not using an ID means we have to grab all the widget values, and put them where server knows to look
if (this.id != et2_customfields_list.DEFAULT_ID) {
return null;
}
const value = {};
for (let field_name in this.widgets) {
if (this.widgets[field_name].getValue && !this.widgets[field_name].options.readonly) {
value[this.options.prefix + field_name] = this.widgets[field_name].getValue();
}
}
return value;
}
isDirty() {
let dirty = false;
for (let field_name in this.widgets) {
if (this.widgets[field_name].isDirty) {
dirty = dirty || this.widgets[field_name].isDirty();
}
}
return dirty;
}
resetDirty() {
for (let field_name in this.widgets) {
if (this.widgets[field_name].resetDirty) {
this.widgets[field_name].resetDirty();
}
}
}
isValid() {
// Individual customfields will handle themselves
return true;
}
/**
* Adapt provided attributes to match options for widget
*
* rows > 1 --> textarea, with rows=rows and cols=len
* !rows --> input, with size=len
* rows = 1 --> input, with size=len, maxlength=len
*/
_setup_text(field_name, field, attrs) {
// No label on the widget itself
delete (attrs.label);
field.type = 'textbox';
attrs.rows = field.rows > 1 ? field.rows : null;
if (field.len) {
attrs.size = field.len;
if (field.rows == 1)
attrs.maxlength = field.len;
}
return true;
}
_setup_passwd(field_name, field, attrs) {
// No label on the widget itself
delete (attrs.label);
let defaults = {
viewable: true,
plaintext: false,
suggest: 16
};
for (let key of Object.keys(defaults)) {
attrs[key] = (field.values && typeof field.values[key] !== "undefined") ? field.values[key] : defaults[key];
}
return true;
}
_setup_ajax_select(field_name, field, attrs) {
const attributes = ['get_rows', 'get_title', 'id_field', 'template'];
if (field.values) {
for (let i = 0; i < attributes.length; i++) {
if (typeof field.values[attributes[i]] !== 'undefined') {
attrs[attributes[i]] = field.values[attributes[i]];
}
}
}
return true;
}
_setup_float(field_name, field, attrs) {
// No label on the widget itself
delete (attrs.label);
field.type = 'float';
if (field.len) {
attrs.size = field.len;
}
return true;
}
_setup_select(field_name, field, attrs) {
// No label on the widget itself
delete (attrs.label);
attrs.rows = field.rows;
// select_options are now send from server-side incl. ones defined via a file in EGroupware root
attrs.tags = field.tags;
return true;
}
_setup_select_account(field_name, field, attrs) {
attrs.empty_label = egw.lang('Select');
if (field.account_type) {
attrs.account_type = field.account_type;
}
return this._setup_select(field_name, field, attrs);
}
_setup_date(field_name, field, attrs) {
attrs.data_format = field.values && field.values.format ? field.values.format : 'Y-m-d';
return true;
}
_setup_date_time(field_name, field, attrs) {
attrs.data_format = field.values && field.values.format ? field.values.format : 'Y-m-d H:i:s';
return true;
}
_setup_htmlarea(field_name, field, attrs) {
attrs.config = field.config ? field.config : {};
attrs.config.toolbarStartupExpanded = false;
if (field.len) {
attrs.config.width = field.len + 'px';
}
attrs.config.height = (((field.rows > 0 && field.rows != 'undefined') ? field.rows : 5) * 16) + 'px';
// We have to push the config modifications into the modifications array, or they'll
// be overwritten by the site config from the server
const data = this.getArrayMgr("modifications").getEntry(this.options.prefix + field_name);
if (data)
jQuery.extend(data.config, attrs.config);
return true;
}
_setup_radio(field_name, field, attrs) {
// 'Empty' label will be first
delete (attrs.label);
if (field.values && field.values['']) {
attrs.label = field.values[''];
delete field.values[''];
}
field.type = 'radiogroup';
attrs.options = field.values;
return true;
}
_setup_checkbox(field_name, field, attrs) {
// Read-only checkbox is just text
if (attrs.readonly && this.getType() !== "customfields") {
attrs.ro_true = field.label;
}
else if (field.hasOwnProperty('ro_true')) {
attrs.ro_true = field.ro_true;
}
if (field.hasOwnProperty('ro_false')) {
attrs.ro_false = field.ro_false;
}
return true;
}
/**
* People set button attributes as
* label: javascript
*/
_setup_button(field_name, field, attrs) {
// No label on the widget itself
delete (attrs.label);
attrs.label = field.label;
if (this.getType() == 'customfields-list') {
// No buttons in a list, it causes problems with detached nodes
return false;
}
// Simple case, one widget for a custom field
if (!field.values || typeof field.values != 'object' || Object.keys(field.values).length == 1) {
for (let key in field.values) {
attrs.label = key;
attrs.onclick = field.values[key];
}
if (!attrs.label) {
attrs.label = 'No "label=onclick" in values!';
attrs.onclick = function () { return false; };
}
return !attrs.readonly;
}
else {
// Complicated case, a single custom field you get multiple widgets
// Handle it all here, since this is the exception
const row = jQuery('tr', this.tbody).last();
let cf = jQuery('td', row);
// Label in first column, widget in 2nd
cf.text(field.label + "");
cf = jQuery(document.createElement("td"))
.appendTo(row);
for (var key in field.values) {
const button_attrs = jQuery.extend({}, attrs);
button_attrs.label = key;
button_attrs.onclick = field.values[key];
button_attrs.id = attrs.id + '_' + key;
// This controls where the button is placed in the DOM
this.rows[button_attrs.id] = cf[0];
// Do not store in the widgets list, one name for multiple widgets would cause problems
/*this.widgets[field_name] = */ et2_createWidget(attrs.type ? attrs.type : field.type, button_attrs, this);
}
return false;
}
}
_setup_link_entry(field_name, field, attrs) {
if (field.type === 'filemanager') {
return this._setup_filemanager(field_name, field, attrs);
}
// No label on the widget itself
delete (attrs.label);
attrs.type = "link-entry";
attrs.only_app = typeof field.only_app == "undefined" ? field.type : field.only_app;
return true;
}
_setup_filemanager(field_name, field, attrs) {
attrs.type = 'vfs-upload';
delete (attrs.label);
if (this.getType() == 'customfields-list') {
// No special UI needed?
return true;
}
else {
// Complicated case, a single custom field you get multiple widgets
// Handle it all here, since this is the exception
const row = jQuery('tr', this.tbody).last();
let cf = jQuery('td', row);
// Label in first column, widget in 2nd
cf.text(field.label + "");
cf = jQuery(document.createElement("td"))
.appendTo(row);
// Create upload widget
let widget = this.widgets[field_name] = et2_createWidget(attrs.type ? attrs.type : field.type, attrs, this);
// This controls where the widget is placed in the DOM
this.rows[attrs.id] = cf[0];
jQuery(widget.getDOMNode(widget)).css('vertical-align', 'top');
// Add a link to existing VFS file
const select_attrs = jQuery.extend({}, attrs,
// Filemanager select
{
label: '',
mode: widget.options.multiple ? 'open-multiple' : 'open',
method: 'EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_existing',
method_id: attrs.path,
button_label: egw.lang('Link')
}, { type: 'vfs-select' });
select_attrs.id = attrs.id + '_vfs_select';
// This controls where the button is placed in the DOM
this.rows[select_attrs.id] = cf[0];
// Do not store in the widgets list, one name for multiple widgets would cause problems
widget = et2_createWidget(select_attrs.type, select_attrs, this);
jQuery(widget.getDOMNode(widget)).css('vertical-align', 'top').prependTo(cf);
}
return false;
}
/**
* Display links in list as CF name
* @param field_name
* @param field
* @param attrs
*/
_setup_url(field_name, field, attrs) {
if (this.getType() == 'customfields-list') {
attrs.label = field.label;
}
return true;
}
/**
* Set which fields are visible, by name
*
* Note: no # prefix on the name
*
*/
set_visible(_fields) {
for (let name in _fields) {
if (this.rows[this.options.prefix + name]) {
if (_fields[name]) {
jQuery(this.rows[this.options.prefix + name]).show();
}
else {
jQuery(this.rows[this.options.prefix + name]).hide();
}
}
this.options.fields[name] = _fields[name];
}
}
/**
* Code for implementing et2_IDetachedDOM
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "class");
}
getDetachedNodes() {
return this.detachedNodes ? this.detachedNodes : [];
}
setDetachedAttributes(_nodes, _values) {
// Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
// Show the row if there's a value, hide it if there is no value
for (let i = 0; i < _nodes.length; i++) {
// toggle() needs a boolean to do what we want
const key = _nodes[i].getAttribute('data-field');
jQuery(_nodes[i]).toggle(_values.fields[key] && _values.value[this.options.prefix + key] ? true : false);
}
}
}
et2_customfields_list._attributes = {
'customfields': {
'name': 'Custom fields',
'description': 'Auto filled',
'type': 'any'
},
'fields': {
'name': 'Custom fields',
'description': 'Auto filled',
'type': 'any'
},
'value': {
'name': 'Custom fields',
'description': 'Auto filled',
'type': "any"
},
'type_filter': {
'name': 'Field filter',
"default": "",
"type": "any",
"description": "Filter displayed custom fields by their 'type2' attribute"
},
'private': {
ignore: true,
type: 'boolean'
},
'sub_app': {
'name': 'sub app name',
'type': "string",
'description': "Name of sub application"
},
// Allow onchange so you can put handlers on the sub-widgets
'onchange': {
"name": "onchange",
"type": "string",
"default": et2_no_init,
"description": "JS code which is executed when the value changes."
},
// Allow changing the field prefix. Normally it's the constant but importexport filter changes it.
"prefix": {
name: "prefix",
type: "string",
default: "#",
description: "Custom prefix for custom fields. Default #"
}
};
et2_customfields_list.legacyOptions = ["type_filter", "private", "fields"]; // Field restriction & private done server-side
et2_customfields_list.PREFIX = '#';
et2_customfields_list.DEFAULT_ID = "custom_fields";
et2_register_widget(et2_customfields_list, ["customfields", "customfields-list"]);
//# sourceMappingURL=et2_extension_customfields.js.map

View File

@ -1,25 +0,0 @@
/**
* EGroupware eTemplate2 - JS Itempicker object
* derived from et2_link_entry widget @copyright 2011 Nathan Gray
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Christian Binder
* @author Nathan Gray
* @copyright 2012 Christian Binder
* @copyright 2011 Nathan Gray
*/
function itempickerDocumentAction(context, data) {
"use strict";
let formid = "itempicker_action_form";
let form = "<form id='" + formid + "' action='index.php?menuaction=" + data.app + "." + data.app + "_merge.download_by_request' method='POST'>"
+ "<input type='hidden' name='data_document_name' value='" + data.value.name + "' />"
+ "<input type='hidden' name='data_document_dir' value='" + data.value.dir + "' />"
+ "<input type='hidden' name='data_checked' value='" + data.checked.join(',') + "' />"
+ "</form>";
jQuery("body").append(form);
jQuery("#" + formid).submit().remove();
}
//# sourceMappingURL=et2_extension_itempicker_actions.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,601 +0,0 @@
/**
* EGroupware eTemplate2 - Class which contains a the data model for nextmatch widgets
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
import { et2_dataview_row } from "./et2_dataview_view_row";
import { et2_dataview_tile } from "./et2_dataview_view_tile";
import { et2_dataview_controller } from "./et2_dataview_controller";
import { et2_dataview_column } from "./et2_dataview_model_columns";
import { framework, egw } from "../jsapi/egw_global";
import { egw_getActionManager, egw_getObjectManager, egwActionObjectManager, egwActionObject } from "../egw_action/egw_action.js";
import { EGW_AO_FLAG_DEFAULT_FOCUS, EGW_AO_EXEC_SELECTED, EGW_AO_FLAG_IS_CONTAINER } from "../egw_action/egw_action_constants.js";
import { nm_action } from "./et2_extension_nextmatch_actions.js";
import { egwIsMobile } from "../egw_action/egw_action_common.js";
/**
* @augments et2_dataview_controller
*/
export class et2_nextmatch_controller extends et2_dataview_controller {
/**
* Initializes the nextmatch controller.
*
* @param _parentController is the parent nextmatch controller instance
* @param _egw is the api instance
* @param _execId is the execId of the etemplate
* @param _widget is the nextmatch-widget we are fetching data for.
* @param _grid is the grid the grid controller will be controlling
* @param _rowProvider is the nextmatch row provider instance.
* @param _objectManager is the parent object manager (if null, the object
* manager) will be created using
* @param _actionLinks contains the action links
* @param _actions contains the actions, may be null if an object manager
* is given.
* @memberOf et2_nextmatch_controller
*/
constructor(_parentController, _egw, _execId, _widget, _parentId, _grid, _rowProvider, _actionLinks, _objectManager, _actions) {
// Call the parent et2_dataview_controller constructor
super(_parentController, _grid);
this.setDataProvider(this);
this.setRowCallback(this._rowCallback);
this.setLinkCallback(this._linkCallback);
this.setContext(this);
// Copy the egw reference
this.egw = _egw;
// Keep a reference to the widget
this._widget = _widget;
// Copy the given parameters
this._actionLinks = _actionLinks;
this._execId = _execId;
// Get full widget ID, including path
var id = _widget.getArrayMgr('content').getPath();
if (typeof id == 'string') {
this._widgetId = id;
}
else if (id.length === 1) {
this._widgetId = id[0];
}
else {
this._widgetId = id.shift() + '[' + id.join('][') + ']';
}
this._parentId = _parentId;
this._rowProvider = _rowProvider;
// Initialize the action and the object manager
// _initActions calls _init_link_dnd, which uses this._actionLinks,
// so this must happen after the above parameter copying
if (!_objectManager) {
this._initActions(_actions);
}
else {
this._actionManager = null;
this._objectManager = _objectManager;
}
this.setActionObjectManager(this._objectManager);
// Add our selection callback to selection manager
var self = this;
this._objectManager.setSelectedCallback = function () { self._selectCallback.apply(self, [this, arguments]); };
// We start with no filters
this._filters = {};
// Keep selection across filter changes
this.kept_selection = null;
this.kept_focus = null;
this.kept_expansion = [];
// Directly use the API-Implementation of dataRegisterUID and
// dataUnregisterUID
this.dataUnregisterUID = _egw.dataUnregisterUID;
// Default to rows
this._view = et2_nextmatch_controller.VIEW_ROW;
}
destroy() {
// If the actionManager variable is set, the object- and actionManager
// were created by this instance -- clear them
if (this._actionManager) {
this._objectManager.remove();
this._actionManager.remove();
}
this._widget = null;
super.destroy();
}
/**
* Updates the filter instance.
*/
setFilters(_filters) {
// Update the filters
this._filters = _filters;
}
/**
* Keep the selection, if possible, across a data fetch and restore it
* after
*/
keepSelection() {
this.kept_selection = this._selectionMgr ? this._selectionMgr.getSelected() : null;
this.kept_focus = this._selectionMgr && this._selectionMgr._focusedEntry ?
this._selectionMgr._focusedEntry.uid || null : null;
// Find expanded rows
var nm = this._widget;
var controller = this;
jQuery('.arrow.opened', this._widget.getDOMNode(this._widget)).each(function () {
var entry = controller.getRowByNode(this);
if (entry && entry.uid) {
controller.kept_expansion.push(entry.uid);
}
});
}
getObjectManager() {
return this._objectManager;
}
/**
* Deletes a row from the grid
*
* @param {string} uid
*/
deleteRow(uid) {
var entry = Object.values(this._indexMap).find(entry => entry.uid == uid);
// Unselect
this._selectionMgr.setSelected(uid, false);
if (entry && entry.idx !== null) {
this._selectionMgr.unregisterRow(uid, entry.tr);
// This will remove the row, but add an empty to the end.
// That's OK, because it will be removed when we update the row count
this._grid.deleteRow(entry.idx);
// Remove from internal map
delete this._indexMap[entry.idx];
// Update the indices of all elements after the current one
for (var mapIndex = entry.idx + 1; typeof this._indexMap[mapIndex] !== 'undefined'; mapIndex++) {
var entry = this._indexMap[mapIndex];
entry.idx = mapIndex - 1;
this._indexMap[mapIndex - 1] = entry;
// Update selection mgr too
if (entry.uid && typeof this._selectionMgr._registeredRows[entry.uid] !== 'undefined') {
var reg = this._selectionMgr._getRegisteredRowsEntry(entry.uid);
reg.idx = entry.idx;
if (reg.ao && reg.ao._index)
reg.ao._index = entry.idx;
this._selectionMgr._registeredRows[entry.uid].idx = reg.idx;
}
}
// Remove last one, it was moved to mapIndex-1 before increment
delete this._indexMap[mapIndex - 1];
// Not needed, they share by reference
// this._selectionMgr.setIndexMap(this._indexMap);
}
for (let child of this._children) {
child.deleteRow(uid);
}
}
/** -- PRIVATE FUNCTIONS -- **/
/**
* Create a new row, either normal or tiled
*
* @param {type} ctx
* @returns {et2_dataview_container}
*/
_createRow(ctx) {
switch (this._view) {
case et2_nextmatch_controller.VIEW_TILE:
var row = new et2_dataview_tile(this._grid);
// Try to overcome chrome rendering issue where float is not
// applied properly, leading to incomplete rows
window.setTimeout(function () {
if (!row.tr)
return;
row.tr.css('float', 'none');
window.setTimeout(function () {
if (!row.tr)
return;
row.tr.css('float', 'left');
}, 50);
}, 100);
return row;
case et2_nextmatch_controller.VIEW_ROW:
default:
return new et2_dataview_row(this._grid);
}
}
/**
* Initializes the action and the object manager.
*/
_initActions(_actions) {
// Generate a uid for the action and object manager
var uid = this._widget.id || this.egw.uid();
if (_actions == null)
_actions = [];
// Initialize the action manager and add some actions to it
// Only look 1 level deep
var gam = egw_getActionManager(this.egw.appName, true, 1);
if (this._actionManager == null) {
this._actionManager = gam.addAction("actionManager", uid);
}
var data = this._actionManager.data;
if (data == 'undefined' || !data) {
data = {};
}
data.nextmatch = this._widget;
data.context = this._widget.getInstanceManager().app_obj;
this._actionManager.set_data(data);
this._actionManager.updateActions(_actions, this.egw.appName);
// Set the default execute handler
var self = this;
this._actionManager.setDefaultExecute(function (_action, _senders, _target) {
// Get the selected ids descriptor object
var ids = self._selectionMgr.getSelected();
// Pass a reference to the actual widget
if (typeof _action.data == 'undefined' || !_action.data)
_action.data = {};
_action.data.nextmatch = self._widget;
// Call the nm_action function with the ids
nm_action(_action, _senders, _target, ids);
});
// Set the 'Select All' handler
var select_all = this._actionManager.getActionById('select_all');
if (select_all) {
select_all.set_onExecute(jQuery.proxy(function (action, selected) {
this._selectionMgr.selectAll();
}, this));
}
// Initialize the object manager - look for application
// object manager 1 level deep
var gom = egw_getObjectManager(this.egw.appName, true, 1);
if (this._objectManager == null) {
this._objectManager = gom.addObject(new egwActionObjectManager(uid, this._actionManager));
this._objectManager.handleKeyPress = function (_keyCode, _shift, _ctrl, _alt) {
for (var i = 0; i < self._actionManager.children.length; i++) {
if (typeof self._actionManager.children[i].shortcut === 'object' &&
self._actionManager.children[i].shortcut &&
_keyCode == self._actionManager.children[i].shortcut.keyCode) {
return this.executeActionImplementation({
"keyEvent": {
"keyCode": _keyCode,
"shift": _shift,
"ctrl": _ctrl,
"alt": _alt
}
}, "popup", EGW_AO_EXEC_SELECTED);
}
}
return egwActionObject.prototype.handleKeyPress.call(this, _keyCode, _shift, _ctrl, _alt);
};
}
this._objectManager.flags = this._objectManager.flags
| EGW_AO_FLAG_DEFAULT_FOCUS | EGW_AO_FLAG_IS_CONTAINER;
this._init_links_dnd();
if (this._selectionMgr) {
// Need to update the action links for every registered row too
for (var uid in this._selectionMgr._registeredRows) {
// Get the corresponding entry from the registered rows array
var entry = this._selectionMgr._getRegisteredRowsEntry(uid);
if (entry.ao) {
entry.ao.updateActionLinks(this._actionLinks);
}
}
}
}
/**
* Automatically add dnd support for linking
*/
_init_links_dnd() {
var mgr = this._actionManager;
var self = this;
var drop_action = mgr.getActionById('egw_link_drop');
var drag_action = mgr.getActionById('egw_link_drag');
var drop_cancel = mgr.getActionById('egw_cancel_drop');
if (!this._actionLinks) {
this._actionLinks = [];
}
if (!drop_cancel) {
// Create a generic cancel action in order to cancel drop action
// applied for all apps plus file and link action.
drop_cancel = mgr.addAction('drop', 'egw_cancel_drop', this.egw.lang('Cancel'), egw.image('cancel'), function () { }, true);
drop_cancel.set_group('99');
drop_cancel.acceptedTypes = drop_cancel.acceptedTypes.concat(Object.keys(egw.user('apps')).concat(['link', 'file']));
this._actionLinks.push(drop_cancel.id);
}
// Check if this app supports linking
if (!egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'query') ||
egw.link_get_registry(this.dataStorePrefix || this.egw.appName, 'title')) {
if (drop_action) {
drop_action.remove();
if (this._actionLinks.indexOf(drop_action.id) >= 0) {
this._actionLinks.splice(this._actionLinks.indexOf(drop_action.id), 1);
}
}
if (drag_action) {
drag_action.remove();
if (this._actionLinks.indexOf(drag_action.id) >= 0) {
this._actionLinks.splice(this._actionLinks.indexOf(drag_action.id), 1);
}
}
return;
}
// Don't re-add
if (drop_action == null) {
// Create the drop action that links entries
drop_action = mgr.addAction('drop', 'egw_link_drop', this.egw.lang('Create link'), egw.image('link'), function (action, source, dropped) {
// Extract link IDs
var links = [];
var id = '';
for (var i = 0; i < source.length; i++) {
if (!source[i].id)
continue;
id = source[i].id.split('::');
links.push({ app: id[0] == 'filemanager' ? 'link' : id[0], id: id[1] });
}
if (!links.length) {
return;
}
// Link the entries
self.egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", dropped.id.split('::').concat([links]), function (result) {
if (result) {
for (var i = 0; i < this._objectManager.selectedChildren.length; i++) {
this._widget.refresh(this._objectManager.selectedChildren[i].id, 'update');
}
this._widget.egw().message('Linked');
// Update the target to show the liks
this._widget.refresh(dropped.id, 'update');
}
}, self, true, self).sendRequest();
}, true);
}
if (this._actionLinks.indexOf(drop_action.id) < 0) {
this._actionLinks.push(drop_action.id);
}
// Accept other links, and files dragged from the filemanager
// This does not handle files dragged from the desktop. They are
// handled by et2_nextmatch, since it needs DOM stuff
if (drop_action.acceptedTypes.indexOf('link') == -1) {
drop_action.acceptedTypes.push('link');
}
// Don't re-add
if (drag_action == null) {
// Create drag action that allows linking
drag_action = mgr.addAction('drag', 'egw_link_drag', this.egw.lang('link'), 'link', function (action, selected) {
// Drag helper - list titles. Arbitrarily limited to 10.
var helper = jQuery(document.createElement("div"));
for (var i = 0; i < selected.length && i < 10; i++) {
var id = selected[i].id.split('::');
var span = jQuery(document.createElement('span')).appendTo(helper);
self.egw.link_title(id[0], id[1], function (title) {
this.append(title);
this.append('<br />');
}, span);
}
// As we wanted to have a general defaul helper interface, we return null here and not using customize helper for links
// TODO: Need to decide if we need to create a customized helper interface for links anyway
//return helper;
return null;
}, true);
}
if (this._actionLinks.indexOf(drag_action.id) < 0) {
this._actionLinks.push(drag_action.id);
}
drag_action.set_dragType('link');
}
/**
* Set the data cache prefix
* Overridden from the parent to re-check automatically the added link dnd
* since the prefix is used in support detection.
*/
setPrefix(prefix) {
super.setPrefix(prefix);
this._init_links_dnd();
}
/**
* Overwrites the inherited _destroyCallback function in order to be able
* to free the "rowWidget".
*/
_destroyCallback(_row) {
// Destroy any widget associated to the row
if (this.entry.widget) {
this.entry.widget.destroy();
this.entry.widget = null;
}
// Call the inherited function
super._destroyCallback(_row);
}
/**
* Creates the actual data row.
*
* @param _data is an array containing the row data
* @param _tr is the tr into which the data will be inserted
* @param _idx is the index of the row
* @param _entry is the internal row datastructure of the controller, in
* this special case used to store the rowWidget reference, so that it can
* be properly freed.
*/
_rowCallback(_data, _tr, _idx, _entry) {
// Let the row provider fill in the data row -- store the returned
// rowWidget inside the _entry
_entry.widget = this._rowProvider.getDataRow({ "content": _data }, _tr, _idx, this);
}
/**
* Returns the names of action links for a given data row -- currently these are
* always the same links, as we controll enabled/disabled over the row
* classes, unless the row UID is "", then it's an 'empty' row.
*
* The empty row placeholder can still have actions, but nothing that requires
* an actual UID.
*
* @TODO: Currently empty row is just add, need to actually filter somehow. Here
* might not be the right place.
*
* @param _data Object The data for the row
* @param _idx int The row index
* @param _uid String The row's ID
*
* @return Array List of action names that valid for the row
*/
_linkCallback(_data, _idx, _uid) {
if (_uid.trim() != "") {
return this._actionLinks;
}
// No UID, so return a filtered list of actions that doesn't need a UID
var links = [];
try {
links = typeof this._widget.options.settings.placeholder_actions != 'undefined' ?
this._widget.options.settings.placeholder_actions : (this._widget.options.add ? ["add"] : []);
// Make sure that placeholder actions are defined and existed in client-side,
// otherwise do not set them as placeholder. for instance actions with
// attribute hideOnMobile do not get sent to client-side.
var action_search = function (current) {
if (typeof this._widget.options.actions[current] !== 'undefined')
return true;
// Check children
for (var action in this._widget.options.actions) {
action = this._widget.options.actions[action];
if (action.children && action.children[current])
return true;
}
return false;
};
links = links.filter(action_search, this);
}
catch (e) {
}
return links;
}
/**
* Overridden from the parent to also process any additional data that
* the data source adds, such as readonlys and additonal content.
* For example, non-numeric IDs in rows are added to the content manager
*/
_fetchCallback(_response) {
var nm = this.self._widget;
if (!nm) {
// Nextmatch either not connected, or it tried to destroy this
// but the server returned something
return;
}
// Readonlys
// Other stuff
for (var i in _response.rows) {
if (jQuery.isNumeric(i))
continue;
if (i == 'sel_options') {
var mgr = nm.getArrayMgr(i);
for (var id in _response.rows.sel_options) {
mgr.data[id] = _response.rows.sel_options[id];
var select = nm.getWidgetById(id);
if (select && select.set_select_options) {
select.set_select_options(_response.rows.sel_options[id]);
}
// Clear rowProvider internal cache so it uses new values
if (id == 'cat_id') {
this.self._rowProvider.categories = null;
}
// update array mgr so select widgets in row also get refreshed options
nm.getParent().getArrayMgr('sel_options').data[id] = _response.rows.sel_options[id];
}
}
else if (i === "order" && _response.rows[i] !== nm.activeFilters.order) {
nm.sortBy(_response.rows[i], undefined, false);
}
else {
var mgr = nm.getArrayMgr('content');
mgr.data[i] = _response.rows[i];
// It's not enough to just update the data, the widgets need to
// be updated too, if there are matching widgets.
var widget = nm.getWidgetById(i);
if (widget && widget.set_value) {
widget.set_value(mgr.getEntry(i));
}
}
}
// Might be trying to disable a column
var col_refresh = false;
for (var column_index = 0; column_index < nm.columns.length; column_index++) {
if (typeof nm.columns[column_index].disabled === 'string' &&
nm.columns[column_index].disabled[0] === '@') {
col_refresh = true;
nm.dataview.columnMgr.getColumnById('col_' + column_index)
.set_visibility(nm.getArrayMgr('content').parseBoolExpression(nm.columns[column_index].disabled) ?
et2_dataview_column.ET2_COL_VISIBILITY_DISABLED :
nm.columns[column_index].visible);
}
}
if (col_refresh) {
nm.dataview.columnMgr.updated();
nm.dataview._updateColumns();
}
// If we're doing an autorefresh and the count decreases, preserve the
// selection or it will be lost when the grid rows are shuffled. Increases
// are fine though.
if (this.self && this.self.kept_selection == null &&
!this.refresh && this.self._grid.getTotalCount() > _response.total) {
this.self.keepSelection();
}
// Call the inherited function
super._fetchCallback.apply(this, arguments);
// Restore selection, if passed
if (this.self && this.self.kept_selection && this.self._selectionMgr) {
if (this.self.kept_selection.all) {
this.self._selectionMgr.selectAll();
}
for (var i = (this.self.kept_selection.ids.length || 1) - 1; i >= 0; i--) {
// Only keep the selected if they came back in the fetch
if (_response.order.indexOf(this.self.kept_selection.ids[i]) >= 0) {
this.self._selectionMgr.setSelected(this.self.kept_selection.ids[i], true);
this.self.kept_selection.ids.splice(i, 1);
}
else {
this.self.kept_selection.ids.splice(i, 1);
}
}
if (this.self.kept_focus && _response.order.indexOf(this.self.kept_focus) >= 0) {
this.self._selectionMgr.setFocused(this.self.kept_focus, true);
}
// Re-expanding rows handled in et2_extension_nextmatch_rowProvider
// Expansions might still be valid, so we don't clear them
if (this.self.kept_selection != null && typeof this.self.kept_selection.ids != 'undefined' && this.self.kept_selection.ids.length == 0) {
this.self.kept_selection = null;
}
this.self.kept_focus = null;
}
}
/**
* Execute the select callback when the row selection changes
*/
_selectCallback(action, senders) {
if (typeof senders == "undefined") {
senders = [];
}
if (!this._widget)
return;
// inform mobile framework about nm selections, need to update status of header objects on selection
if (egwIsMobile())
framework.nm_onselect_ctrl(this._widget, action, senders);
this._widget.onselect.call(this._widget, action, senders);
}
/** -- Implementation of et2_IDataProvider -- **/
dataFetch(_queriedRange, _callback, _context) {
// Merge the parent id into the _queriedRange if it is set
if (this._parentId !== null) {
_queriedRange["parent_id"] = this._parentId;
}
// sub-levels dont have there own _filters object, need to use the one from parent (or it's parents parent)
var obj = this;
while ((typeof obj._filters == 'undefined' || jQuery.isEmptyObject(obj._filters)) && obj._parentController) {
obj = obj._parentController;
}
// Pass the fetch call to the API, multiplex the data about the
// nextmatch instance into the call.
this.egw.dataFetch(this._widget.getInstanceManager().etemplate_exec_id || this._execId, _queriedRange, obj._filters, this._widgetId, _callback, _context);
}
dataRegisterUID(_uid, _callback, _context) {
// Make sure we use correct prefix when data comes back
if (this._widget && this._widget._get_appname() != this.egw.getAppName()) {
_context.prefix = _uid.split('::')[0];
}
this.egw.dataRegisterUID(_uid, _callback, _context, this._widget.getInstanceManager().etemplate_exec_id || this._execId, this._widgetId);
}
dataUnregisterUID() {
// Overwritten in the constructor
}
}
// Display constants
et2_nextmatch_controller.VIEW_ROW = 'row';
et2_nextmatch_controller.VIEW_TILE = 'tile';
//# sourceMappingURL=et2_extension_nextmatch_controller.js.map

View File

@ -1,554 +0,0 @@
/**
* EGroupware eTemplate2 - Class which contains a factory method for rows
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inheritance;
et2_core_interfaces;
et2_core_arrayMgr;
et2_core_widget;
et2_dataview_view_rowProvider;
*/
import { et2_widget } from "./et2_core_widget";
import { et2_arrayMgrs_expand } from "./et2_core_arrayMgr";
import { et2_dataview_grid } from "./et2_dataview_view_grid";
import { egw } from "../jsapi/egw_global";
import { et2_IDetachedDOM, et2_IDOMNode } from "./et2_core_interfaces";
/**
* The row provider contains prototypes (full clonable dom-trees)
* for all registered row types.
*
*/
export class et2_nextmatch_rowProvider {
/**
* Creates the nextmatch row provider.
*
* @param {et2_nextmatch_rowProvider} _rowProvider
* @param {function} _subgridCallback
* @param {object} _context
* @memberOf et2_nextmatch_rowProvider
*/
constructor(_rowProvider, _subgridCallback, _context) {
/**
* Match category-ids from class attribute eg. "cat_15" or "123,456,789 "
*
* Make sure to not match numbers inside other class-names.
*
* We can NOT use something like /(^| |,|cat_)([0-9]+)( |,|$)/g as it wont find all cats in "123,456,789 "!
*/
this.cat_regexp = /(^| |,|cat_)([0-9]+)/g;
/**
* Regular expression used to filter out non-nummerical chars from above matches
*/
this.cat_cleanup = /[^0-9]/g;
// Copy the arguments
this._rowProvider = _rowProvider;
this._subgridCallback = _subgridCallback;
this._context = _context;
this._createEmptyPrototype();
}
destroy() {
this._rowProvider.destroy();
this._subgridCallback = null;
this._context = null;
this._dataRow = null;
}
/**
* Creates the data row prototype.
*
* @param _widgets is an array containing the root widget for each column.
* @param _rowData contains the properties of the root "tr" (like its class)
* @param _rootWidget is the parent widget of the data rows (i.e.
* the nextmatch)
*/
setDataRowTemplate(_widgets, _rowData, _rootWidget) {
// Copy the root widget
this._rootWidget = _rootWidget;
// Create the base row
var row = this._rowProvider.getPrototype("default");
// Copy the row template
var rowTemplate = {
"row": row[0],
"rowData": _rowData,
"widgets": _widgets,
"root": _rootWidget,
"seperated": null,
"mgrs": _rootWidget.getArrayMgrs()
};
// Create the row widget and insert the given widgets into the row
var rowWidget = new et2_nextmatch_rowWidget(rowTemplate.mgrs, row[0]);
rowWidget._parent = _rootWidget;
rowWidget.createWidgets(_widgets);
// Get the set containing all variable attributes
var variableAttributes = this._getVariableAttributeSet(rowWidget);
// Filter out all widgets which do not implement the et2_IDetachedDOM
// interface or do not support all attributes listed in the et2_IDetachedDOM
// interface. A warning is issued for all those widgets as they heavily
// degrade the performance of the dataview
var seperated = rowTemplate.seperated =
this._seperateWidgets(variableAttributes);
// Remove all DOM-Nodes of all widgets inside the "remaining" slot from
// the row-template, then build the access functions for the detachable
// widgets
this._stripTemplateRow(rowTemplate);
this._buildNodeAccessFuncs(rowTemplate);
// Create the DOM row template
var tmpl = document.createDocumentFragment();
row.children().each(function () { tmpl.appendChild(this); });
this._dataRow = tmpl;
this._template = rowTemplate;
}
getDataRow(_data, _row, _idx, _controller) {
// Clone the row template
var row = this._dataRow.cloneNode(true);
// Create array managers with the given data merged in
var mgrs = et2_arrayMgrs_expand(rowWidget, this._template.mgrs, _data, _idx);
// Insert the widgets into the row which do not provide the functions
// to set the _data directly
var rowWidget = null;
if (this._template.seperated.remaining.length > 0) {
// Transform the variable attributes
for (var i = 0; i < this._template.seperated.remaining.length; i++) {
var entry = this._template.seperated.remaining[i];
for (var j = 0; j < entry.data.length; j++) {
var set = entry.data[j];
entry.widget.options[set.attribute] = mgrs["content"].expandName(set.expression);
}
}
// Create the row widget
var rowWidget = new et2_nextmatch_rowTemplateWidget(this._rootWidget, row);
// Let the row widget create the widgets
rowWidget.createWidgets(mgrs, this._template.placeholders);
}
// Update the content of all other widgets
for (var i = 0; i < this._template.seperated.detachable.length; i++) {
var entry = this._template.seperated.detachable[i];
// Parse the attribute expressions
var data = {};
for (var j = 0; j < entry.data.length; j++) {
var set = entry.data[j];
data[set.attribute] = mgrs["content"].expandName(set.expression);
}
// Retrieve all DOM-Nodes
var nodes = new Array(entry.nodeFuncs.length);
for (var j = 0; j < nodes.length; j++) {
// Use the previously compiled node function to get the node
// from the entry
try {
nodes[j] = entry.nodeFuncs[j](row);
}
catch (e) {
debugger;
continue;
}
}
// Set the array managers first
entry.widget._mgrs = mgrs;
if (typeof data.id != "undefined") {
entry.widget.id = data.id;
}
// Adjust data for that row
entry.widget.transformAttributes.call(entry.widget, data);
// Call the setDetachedAttributes function
entry.widget.setDetachedAttributes(nodes, data, _data);
}
// Insert the row into the tr
var tr = _row.getDOMNode();
tr.appendChild(row);
// Make the row expandable
if (typeof _data.content["is_parent"] !== "undefined"
&& _data.content["is_parent"]) {
_row.makeExpandable(true, function () {
return this._subgridCallback.call(this._context, _row, _data, _controller);
}, this);
// Check for kept expansion, and set the row up to be re-expanded
// Only the top controller tracks expanded, including sub-grids
var top_controller = _controller;
while (top_controller._parentController != null) {
top_controller = top_controller._parentController;
}
var expansion_index = top_controller.kept_expansion.indexOf(top_controller.dataStorePrefix + '::' + _data.content[this._context.settings.row_id]);
if (top_controller.kept_expansion && expansion_index >= 0) {
top_controller.kept_expansion.splice(expansion_index, 1);
// Use a timeout since the DOM nodes might not be finished yet
window.setTimeout(function () {
_row.expansionButton.trigger('click');
}, et2_dataview_grid.ET2_GRID_INVALIDATE_TIMEOUT);
}
}
// Set the row data
this._setRowData(this._template.rowData, tr, mgrs);
return rowWidget;
}
/**
* Placeholder for empty row
*
* The empty row placeholder is used when there are no results to display.
* This allows the user to still have a drop target, or use actions that
* do not require a row ID, such as 'Add new'.
*/
_createEmptyPrototype() {
var label = this._context && this._context.options && this._context.options.settings.placeholder;
var placeholder = jQuery(document.createElement("td"))
.attr("colspan", this._rowProvider.getColumnCount())
.css("height", "19px")
.text(typeof label != "undefined" && label ? label : egw().lang("No matches found"));
this._rowProvider._prototypes["empty"] = jQuery(document.createElement("tr"))
.addClass("egwGridView_empty")
.append(placeholder);
}
/** -- PRIVATE FUNCTIONS -- **/
/**
* Returns an array containing objects which have variable attributes
*
* @param {et2_widget} _widget
*/
_getVariableAttributeSet(_widget) {
let variableAttributes = [];
const process = function (_widget) {
// Create the attribtues
var hasAttr = false;
var widgetData = {
"widget": _widget,
"data": []
};
// Get all attribute values
for (const key in _widget.attributes) {
if (!_widget.attributes[key].ignore &&
typeof _widget.options[key] != "undefined") {
const val = _widget.options[key];
// TODO: Improve detection
if (typeof val == "string" && val.indexOf("$") >= 0) {
hasAttr = true;
widgetData.data.push({
"attribute": key,
"expression": val
});
}
}
}
// Add the entry if there is any data in it
if (hasAttr) {
variableAttributes.push(widgetData);
}
};
// Check each column
const columns = _widget._widgets;
for (var i = 0; i < columns.length; i++) {
// If column is hidden, don't process it
if (typeof columns[i] === 'undefined' || this._context && this._context.columns && this._context.columns[i] && !this._context.columns[i].visible) {
continue;
}
columns[i].iterateOver(process, this);
}
return variableAttributes;
}
_seperateWidgets(_varAttrs) {
// The detachable array contains all widgets which implement the
// et2_IDetachedDOM interface for all needed attributes
var detachable = [];
// The remaining array creates all widgets which have to be completely
// cloned when the widget tree is created
var remaining = [];
// Iterate over the widgets
for (var i = 0; i < _varAttrs.length; i++) {
var widget = _varAttrs[i].widget;
// Check whether the widget parents are not allready in the "remaining"
// slot - if this is the case do not include the widget at all.
var insertWidget = true;
var checkWidget = function (_widget) {
if (_widget.parent != null) {
for (var i = 0; i < remaining.length; i++) {
if (remaining[i].widget == _widget.parent) {
insertWidget = false;
return;
}
}
checkWidget(_widget.parent);
}
};
checkWidget(widget);
// Handle the next widget if this one should not be included.
if (!insertWidget) {
continue;
}
// Check whether the widget implements the et2_IDetachedDOM interface
var isDetachable = false;
if (widget.implements(et2_IDetachedDOM)) {
// Get all attributes the widgets supports to be set in the
// "detached" mode
var supportedAttrs = [];
widget.getDetachedAttributes(supportedAttrs);
supportedAttrs.push("id");
isDetachable = true;
for (var j = 0; j < _varAttrs[i].data.length /* && isDetachable*/; j++) {
var data = _varAttrs[i].data[j];
var supportsAttr = supportedAttrs.indexOf(data.attribute) != -1;
if (!supportsAttr) {
egw.debug("warn", "et2_IDetachedDOM widget " +
widget._type + " does not support " + data.attribute);
}
isDetachable = isDetachable && supportsAttr;
}
}
// Insert the widget into the correct slot
if (isDetachable) {
detachable.push(_varAttrs[i]);
}
else {
remaining.push(_varAttrs[i]);
}
}
return {
"detachable": detachable,
"remaining": remaining
};
}
/**
* Removes to DOM code for all widgets in the "remaining" slot
*
* @param {object} _rowTemplate
*/
_stripTemplateRow(_rowTemplate) {
_rowTemplate.placeholders = [];
for (var i = 0; i < _rowTemplate.seperated.remaining.length; i++) {
var entry = _rowTemplate.seperated.remaining[i];
// Issue a warning - widgets which do not implement et2_IDOMNode
// are very slow
egw.debug("warn", "Non-clonable widget '" + entry.widget._type + "' in dataview row - this " +
"might be slow", entry);
// Set the placeholder for the entry to null
entry.placeholder = null;
// Get the outer DOM-Node of the widget
if (entry.widget.implements(et2_IDOMNode)) {
var node = entry.widget.getDOMNode(entry.widget);
if (node && node.parentNode) {
// Get the parent node and replace the node with a placeholder
entry.placeholder = document.createElement("span");
node.parentNode.replaceChild(entry.placeholder, node);
_rowTemplate.placeholders.push({
"widget": entry.widget,
"func": this._compileDOMAccessFunc(_rowTemplate.row, entry.placeholder)
});
}
}
}
}
_nodeIndex(_node) {
if (_node.parentNode == null) {
return 0;
}
for (var i = 0; i < _node.parentNode.childNodes.length; i++) {
if (_node.parentNode.childNodes[i] == _node) {
return i;
}
}
return -1;
}
/**
* Returns a function which does a relative access on the given DOM-Node
*
* @param {DOMElement} _root
* @param {DOMElement} _target
*/
_compileDOMAccessFunc(_root, _target) {
function recordPath(_root, _target, _path) {
if (typeof _path == "undefined") {
_path = [];
}
if (_root != _target && _target) {
// Get the index of _target in its parent node
var idx = this._nodeIndex(_target);
if (idx >= 0) {
// Add the access selector
_path.unshift("childNodes[" + idx + "]");
// Record the remaining path
return recordPath.call(this, _root, _target.parentNode, _path);
}
throw ("Internal error while compiling DOM access function.");
}
else {
_path.unshift("_node");
return "return " + _path.join(".") + ";";
}
}
return new Function("_node", recordPath.call(this, _root, _target));
}
/**
* Builds relative paths to the DOM-Nodes and compiles fast-access functions
*
* @param {object} _rowTemplate
*/
_buildNodeAccessFuncs(_rowTemplate) {
for (var i = 0; i < _rowTemplate.seperated.detachable.length; i++) {
var entry = _rowTemplate.seperated.detachable[i];
// Get all needed nodes from the widget
var nodes = entry.widget.getDetachedNodes();
var nodeFuncs = entry.nodeFuncs = new Array(nodes.length);
// Record the path to each DOM-Node
for (var j = 0; j < nodes.length; j++) {
nodeFuncs[j] = this._compileDOMAccessFunc(_rowTemplate.row, nodes[j]);
}
}
}
/**
* Applies additional row data (like the class) to the tr
*
* @param {object} _data
* @param {DOMElement} _tr
* @param {object} _mgrs
*/
_setRowData(_data, _tr, _mgrs) {
// TODO: Implement other fields than "class"
if (_data["class"]) {
var classes = _mgrs["content"].expandName(_data["class"]);
// Get fancy with categories
var cats = [];
// Assume any numeric class is a category
if (_data["class"].indexOf("cat") !== -1 || classes.match(/[0-9]+/)) {
// Accept either cat, cat_id or category as ID, and look there for category settings
var category_location = _data["class"].match(/(cat(_id|egory)?)/);
if (category_location)
category_location = category_location[0];
cats = classes.match(this.cat_regexp) || [];
classes = classes.replace(this.cat_regexp, '');
// Set category class
for (var i = 0; i < cats.length; i++) {
// Need cat_, classes can't start with a number
var cat_id = cats[i].replace(this.cat_cleanup, '');
var cat_class = 'cat_' + cat_id;
classes += ' ' + cat_class;
}
classes += " row_category";
}
classes += " row";
_tr.setAttribute("class", classes);
}
if (_data['valign']) {
var align = _mgrs["content"].expandName(_data["valign"]);
_tr.setAttribute("valign", align);
}
}
}
/**
* @augments et2_widget
*/
export class et2_nextmatch_rowWidget extends et2_widget {
/**
* Constructor
*
* @param _mgrs
* @param _row
* @memberOf et2_nextmatch_rowWidget
*/
constructor(_mgrs, _row) {
// Call the parent constructor with some dummy attributes
super(null, { "id": "", "type": "rowWidget" });
// Initialize some variables
this._widgets = [];
// Copy the given DOM node and the content arrays
this._mgrs = _mgrs;
this._row = _row;
}
/**
* Copies the given array manager and clones the given widgets and inserts
* them into the row which has been passed in the constructor.
*
* @param {array} _widgets
*/
createWidgets(_widgets) {
// Clone the given the widgets with this element as parent
this._widgets = [];
let row_id = 0;
for (var i = 0; i < _widgets.length; i++) {
// Disabled columns might be missing widget - skip it
if (!_widgets[i])
continue;
this._widgets[i] = _widgets[i].clone(this);
this._widgets[i].loadingFinished();
// Set column alignment from widget
if (this._widgets[i].align && this._row.childNodes[row_id]) {
this._row.childNodes[row_id].align = this._widgets[i].align;
}
row_id++;
}
}
/**
* Returns the column node for the given sender
*
* @param {et2_widget} _sender
* @return {DOMElement}
*/
getDOMNode(_sender) {
var row_id = 0;
for (var i = 0; i < this._widgets.length; i++) {
// Disabled columns might be missing widget - skip it
if (!this._widgets[i])
continue;
if (this._widgets[i] == _sender && this._row.childNodes[row_id]) {
return this._row.childNodes[row_id].childNodes[0]; // Return the i-th td tag
}
row_id++;
}
return null;
}
}
/**
* @augments et2_widget
*/
export class et2_nextmatch_rowTemplateWidget extends et2_widget {
/**
* Constructor
*
* @param _root
* @param _row
* @memberOf et2_nextmatch_rowTemplateWidget
*/
constructor(_root, _row) {
// Call the parent constructor with some dummy attributes
super(null, { "id": "", "type": "rowTemplateWidget" });
this._root = _root;
this._mgrs = {};
this._row = _row;
// Set parent to root widget, so sub-widget calls still work
this._parent = _root;
// Clone the widgets inside the placeholders array
this._widgets = [];
}
createWidgets(_mgrs, _widgets) {
// Set the array managers - don't use setArrayMgrs here as this creates
// an unnecessary copy of the object
this._mgrs = _mgrs;
this._widgets = new Array(_widgets.length);
for (var i = 0; i < _widgets.length; i++) {
this._row.childNodes[0].childNodes[0];
this._widgets[i] = {
"widget": _widgets[i].widget.clone(this),
"node": _widgets[i].func(this._row)
};
this._widgets[i].widget.loadingFinished();
}
}
/**
* Returns the column node for the given sender
*
* @param {et2_widget} _sender
* @return {DOMElement}
*/
getDOMNode(_sender) {
for (var i = 0; i < this._widgets.length; i++) {
if (this._widgets[i].widget == _sender) {
return this._widgets[i].node;
}
}
return null;
}
}
//# sourceMappingURL=et2_extension_nextmatch_rowProvider.js.map

View File

@ -1,225 +0,0 @@
/**
* EGroupware eTemplate2 - JS Ajax select / auto complete object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2012
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/vendor/bower-asset/jquery-ui/jquery-ui.js;
et2_core_inputWidget;
et2_core_valueWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_selectbox } from "./et2_widget_selectbox";
/**
* Using AJAX, this widget allows a type-ahead find similar to a ComboBox, where as the user enters information,
* a drop-down box is populated with the n closest matches. If the user clicks on an item in the drop-down, that
* value is selected.
* n is the maximum number of results set in the user's preferences.
* The user is restricted to selecting values in the list.
* This widget can get data from any function that can provide data to a nextmatch widget.
* @augments et2_inputWidget
*/
export class et2_ajaxSelect extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_ajaxSelect
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_ajaxSelect._attributes, _child || {}));
this.input = null;
if (typeof _attrs.get_rows == 'string') {
_attrs.get_rows = this.egw().link('/index.php', {
menuaction: this.options.get_rows
});
}
this.createInputWidget();
this.input = null;
this.createInputWidget();
}
createInputWidget() {
this.input = jQuery(document.createElement("input"));
this.input.addClass("et2_textbox");
this.setDOMNode(this.input[0]);
let widget = this;
this.input.autocomplete({
delay: 100,
source: this.options.get_rows ?
this.options.get_rows :
et2_selectbox.find_select_options(this, this.options.values),
select: function (event, ui) {
widget.value = ui.item[widget.options.id_field];
if (widget.options.get_title) {
if (typeof widget.options.get_title == 'function') {
widget.input.val(widget.options.get_title.call(widget.value));
}
else if (typeof widget.options.get_title == 'string') {
// TODO: Server side callback
}
}
else {
widget.input.val(ui.item.label);
}
// Prevent default action of setting field to the value
return false;
}
});
}
getValue() {
if (this.options.blur && this.input.val() == this.options.blur)
return "";
return this.value;
}
set_value(_value) {
this.value = _value;
if (this.input.autocomplete('instance')) {
let source = this.input.autocomplete('option', 'source');
if (typeof source == 'object') {
for (let i in source) {
if (typeof source[i].value != 'undefined' && typeof source[i].label != 'undefined' && source[i].value === _value) {
this.input.val(source[i].label);
}
else if (typeof source[i] == 'string') {
this.input.val(source[_value]);
break;
}
}
}
else if (typeof source == 'function') {
// TODO
}
}
}
set_blur(_value) {
if (_value) {
this.input.attr("placeholder", _value + ""); // HTML5
if (!this.input[0]["placeholder"]) {
// Not HTML5
if (this.input.val() == "")
this.input.val(this.options.blur);
this.input.focus(this, function (e) {
if (e.data.input.val() == e.data.options.blur)
e.data.input.val("");
}).blur(this, function (e) {
if (e.data.input.val() == "")
e.data.input.val(e.data.options.blur);
});
}
}
else {
this.input.removeAttr("placeholder");
}
}
}
et2_ajaxSelect._attributes = {
'get_rows': {
"name": "Data source",
"type": "any",
"default": "",
"description": "Function to get search results, either a javascript function or server-side."
},
'get_title': {
"name": "Title function",
"type": "any",
"default": "",
"description": "Function to get title for selected entry. Used when closed, and if no template is given."
},
'id_field': {
"name": "Result ID field",
"type": "string",
"default": "value",
"description": "Which key in result sub-array to look for row ID. If omitted, the key for the row will be used."
},
'template': {
"name": "Row template",
"type": "string",
"default": "",
"description": "ID of the template to use to display rows. If omitted, title will be shown for each result."
},
'filter': {
"name": "Filter",
"type": "string",
"default": "",
"description": "Apply filter to search results. Same as nextmatch."
},
'filter2': {
"name": "Filter 2",
"type": "string",
"default": "",
"description": "Apply filter to search results. Same as nextmatch."
},
'link': {
"name": "Read only link",
"type": "boolean",
"default": "true",
"description": "If readonly, widget will be text. If link is set, widget will be a link."
},
// Pass by code only
'values': {
"name": "Values",
"type": "any",
"default": {},
"description": "Specify the available options. Use this, or Data source."
}
};
et2_register_widget(et2_ajaxSelect, ["ajax_select"]);
/**
* et2_textbox_ro is the dummy readonly implementation of the textbox.
* @augments et2_valueWidget
*/
export class et2_ajaxSelect_ro extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_ajaxSelect_ro
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_ajaxSelect_ro._attributes, _child || {}));
this.value = "";
this.span = jQuery(document.createElement("span"));
this.setDOMNode(this.span[0]);
}
set_value(_value) {
this.value = _value;
if (!_value)
_value = "";
this.span.text(_value);
}
/**
* Code for implementing et2_IDetachedDOM
*/
getDetachedAttributes(_attrs) {
_attrs.push("value");
}
getDetachedNodes() {
return [this.span[0]];
}
setDetachedAttributes(_nodes, _values) {
this.span = jQuery(_nodes[0]);
if (typeof _values["value"] != 'undefined') {
this.set_value(_values["value"]);
}
}
}
/**
* Ignore all more advanced attributes.
*/
et2_ajaxSelect_ro._attributes = {
"multiline": {
"ignore": true
}
};
et2_register_widget(et2_ajaxSelect_ro, ["ajax_select_ro"]);
//# sourceMappingURL=et2_widget_ajaxSelect.js.map

View File

@ -1,151 +0,0 @@
/**
* EGroupware eTemplate2 - JS Audio tag
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh <hn[at]egroupware.org>
* @copyright EGroupware GmbH
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_interfaces;
et2_core_baseWidget;
*/
import { et2_baseWidget } from './et2_core_baseWidget';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
/**
* This widget represents the HTML5 Audio tag with all its optional attributes
*
* The widget can be created in the following ways:
* <code>
* var audioTag = et2_createWidget("audio", {
* audio_src: "../../test.mp3",
* src_type: "audio/mpeg",
* muted: true,
* autoplay: true,
* controls: true,
* loop: true,
* height: 100,
* width: 200,
* });
* </code>
* Or by adding XET-tag in your template (.xet) file:
* <code>
* <audio [attributes...]/>
* </code>
*/
/**
* Class which implements the "audio" XET-Tag
*
* @augments et2_baseWidget
*/
export class et2_audio extends et2_baseWidget {
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_audio._attributes, _child || {}));
this.audio = null;
this.container = null;
//Create Audio tag
this.audio = new Audio();
// Container
this.container = document.createElement('div');
this.container.append(this.audio);
this.container.classList.add('et2_audio');
if (this.options.autohide)
this.container.classList.add('et2_audio_autohide');
if (this.options.controls)
this.audio.setAttribute("controls", '1');
if (this.options.autoplay)
this.audio.setAttribute("autoplay", '1');
if (this.options.muted)
this.audio.setAttribute("muted", '1');
if (this.options.loop)
this.audio.setAttribute("loop", '1');
if (this.options.preload)
this.audio.setAttribute('preload', this.options.preload);
this.setDOMNode(this.container);
}
/**
* Set audio source
*
* @param {string} _value url
*/
set_src(_value) {
if (_value) {
this.audio.setAttribute('src', _value);
if (this.options.src_type) {
this.audio.setAttribute('type', this.options.src_type);
}
//preload the audio after changing the source/ only if preload is allowed
if (this.options.preload != "none")
this.audio.load();
}
}
/**
* @return Promise
*/
play() {
return this.audio.play();
}
pause() {
this.audio.pause();
}
currentTime() {
return this.audio.currentTime;
}
seek(_time) {
this.audio.currentTime = _time;
}
}
et2_audio._attributes = {
"src": {
"name": "Audio",
"type": "string",
"description": "Source of audio to play"
},
"src_type": {
"name": "Source type",
"type": "string",
"description": "Defines the type the stream source provided"
},
"muted": {
"name": "Audio control",
"type": "boolean",
"default": false,
"description": "Defines that the audio output should be muted"
},
"autoplay": {
"name": "Autoplay",
"type": "boolean",
"default": false,
"description": "Defines if audio will start playing as soon as it is ready"
},
"controls": {
"name": "Control buttons",
"type": "boolean",
"default": true,
"description": "Defines if audio controls, play/pause buttons should be displayed"
},
"loop": {
"name": "Audio loop",
"type": "boolean",
"default": false,
"description": "Defines if the audio should be played repeatedly"
},
"autohide": {
"name": "Auto hide",
"type": "boolean",
"default": false,
"description": "Auto hides audio control bars and only shows a play button, hovering for few seconds will show the whole controlbar."
},
"preload": {
"name": "preload",
"type": "string",
"default": 'auto',
"description": "preloads audio source based on given option. none(do not preload), auto(preload), metadata(preload metadata only)."
}
};
et2_register_widget(et2_audio, ["audio"]);
//# sourceMappingURL=et2_widget_audio.js.map

View File

@ -1,138 +0,0 @@
/**
* EGroupware eTemplate2 - JS barcode widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh <hn[at]stylite.de>
* @copyright EGroupware GmbH
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/api/js/jquery/barcode/jquery-barcode.min.js;
et2_core_interfaces;
et2_core_baseWidget;
*/
/**
* This widget creates barcode out of a given text
*
* The widget can be created in the following ways:
* <code>
* var barcodeTag = et2_createWidget("barcode", {
* code_type:et2_barcode.TYPE_CSS,
* bgColor:"#FFFFFF",
* barColor:"#000000",
* format:et2_barcode.FORMAT_SVG,
* barWidth:"1",
* barHeight:"50"
* });
* </code>
* Or by adding XET-tag in your template (.xet) file:
* <code>
* <barcode [attributes...]/>
* </code>
*
* Further information about types and formats are defined in static part of the class at the end
*/
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from "./et2_core_valueWidget";
/**
* Class which implements the "barcode" XET-Tag
*
*/
export class et2_barcode extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_barcode._attributes, _child || {}));
this.div = jQuery(document.createElement('div')).attr({ class: 'et2_barcode' });
// Set domid
this.set_id(this.id);
this.setDOMNode(this.div[0]);
this.createWidget();
}
createWidget() {
this.settings = {
output: this.options.format,
bgColor: this.options.bgColor,
color: this.options.barColor,
barWidth: this.options.barWidth,
barHeight: this.options.barHeight,
};
if (this.get_value()) {
// @ts-ignore
this.div.barcode(this.get_value(), this.options.code_type, this.settings);
}
}
set_value(_val) {
if (typeof _val !== 'undefined') {
this.value = _val;
this.createWidget();
}
}
}
// Class Constants
/*
* type const
*/
et2_barcode.TYPE_CODEBAR = "codebar";
et2_barcode.TYPE_CODE11 = "code11"; //(code 11)
et2_barcode.TYPE_CODE39 = "code39"; //(code 39)
et2_barcode.TYPE_CODE128 = "code128"; //(code 128)
et2_barcode.TYPE_EAN8 = "ean8"; //(ean 8) - http://barcode-coder.com/en/ean-8-specification-101.html
et2_barcode.TYPE_EAN13 = "ean13"; //(ean 13) - http://barcode-coder.com/en/ean-13-specification-102.html
et2_barcode.TYPE_STD25 = "std25"; //(standard 2 of 5 - industrial 2 of 5) - http://barcode-coder.com/en/standard-2-of-5-specification-103.html
et2_barcode.TYPE_INT25 = "int25"; //(interleaved 2 of 5)
et2_barcode.TYPE_MSI = "msi";
et2_barcode.TYPE_DATAMATRIX = "datamatrix"; //(ASCII + extended) - http://barcode-coder.com/en/datamatrix-specification-104.html
/**
* Formats consts
*/
et2_barcode.FORMAT_CSS = "css";
et2_barcode.FORMAT_SVG = "svg";
et2_barcode.FORMAT_bmp = "bmp";
et2_barcode.FORMAT_CANVAS = "canvas";
et2_barcode._attributes = {
"code_type": {
"name": "code type",
"type": "string",
"default": et2_barcode.TYPE_DATAMATRIX,
"description": "Barcode type to be generated, default is QR barcode"
},
bgColor: {
"name": "bgColor",
"type": "string",
"default": '#FFFFFF',
"description": "Defines backgorund color of barcode container"
},
barColor: {
"name": "barColor",
"type": "string",
"default": '#000000',
"description": "Defines color of the bars in barcode."
},
format: {
"name": "format",
"type": "string",
"default": 'css',
"description": "Defines in which format the barcode should be rendered. Default is SVG."
},
barWidth: {
"name": "bar width",
"type": "string",
"default": '1',
"description": "Defines width of each bar in the barcode."
},
barHeight: {
"name": "bar height",
"type": "string",
"default": '50',
"description": "Defines heigh of each bar in the barcode."
},
};
et2_register_widget(et2_barcode, ["barcode"]);
//# sourceMappingURL=et2_widget_barcode.js.map

View File

@ -1,205 +0,0 @@
/**
* EGroupware eTemplate2 - JS Box object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_baseWidget } from "./et2_core_baseWidget";
import { et2_readAttrWithDefault } from "./et2_core_xml";
/**
* Class which implements box and vbox tag
*
* Auto-repeat: In order to get box auto repeat to work we need to have another
* box as a wrapper with an id set.
*
* @augments et2_baseWidget
*/
export class et2_box extends et2_baseWidget {
/**
* Constructor
*
* @memberOf et2_box
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, _child);
this.div = jQuery(document.createElement("div"))
.addClass("et2_" + this.getType())
.addClass("et2_box_widget");
this.setDOMNode(this.div[0]);
}
_createNamespace() {
return true;
}
/**
* Overriden so we can check for autorepeating children. We only check for
* $ in the immediate children & grandchildren of this node.
*
* @param {object} _node
*/
loadFromXML(_node) {
if (this.getType() != "box") {
return super.loadFromXML(_node);
}
// Load the child nodes.
var childIndex = 0;
var repeatNode = null;
for (var i = 0; i < _node.childNodes.length; i++) {
var node = _node.childNodes[i];
var widgetType = node.nodeName.toLowerCase();
if (widgetType == "#comment") {
continue;
}
if (widgetType == "#text") {
if (node.data.replace(/^\s+|\s+$/g, '')) {
this.loadContent(node.data);
}
continue;
}
// Create the new element, if no expansion needed
var id = et2_readAttrWithDefault(node, "id", "");
if (id.indexOf('$') < 0 || ['box', 'grid'].indexOf(widgetType) == -1) {
this.createElementFromNode(node);
childIndex++;
}
else {
repeatNode = node;
}
}
// Only the last child repeats(?)
if (repeatNode != null) {
var currentPerspective = this.getArrayMgr("content").perspectiveData;
// Extra content
for (childIndex; typeof this.getArrayMgr("content").data[childIndex] != "undefined" && this.getArrayMgr("content").data[childIndex]; childIndex++) {
// Adjust for the row
var mgrs = this.getArrayMgrs();
for (var name in mgrs) {
if (this.getArrayMgr(name).getEntry(childIndex)) {
this.getArrayMgr(name).setRow(childIndex);
}
}
this.createElementFromNode(repeatNode);
}
// Reset
for (var name in this.getArrayMgrs()) {
this.getArrayMgr(name).setPerspectiveData(currentPerspective);
}
}
}
/**
* Code for implementing et2_IDetachedDOM
* This doesn't need to be implemented.
* Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
*
* @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
_attrs.push('data');
}
getDetachedNodes() {
return [this.getDOMNode()];
}
setDetachedAttributes(_nodes, _values) {
if (_values.data) {
var pairs = _values.data.split(/,/g);
for (var i = 0; i < pairs.length; ++i) {
var name_value = pairs[i].split(':');
jQuery(_nodes[0]).attr('data-' + name_value[0], name_value[1]);
}
}
}
}
et2_box._attributes = {
// Not needed
"rows": { "ignore": true },
"cols": { "ignore": true }
};
et2_register_widget(et2_box, ["vbox", "box"]);
/**
* Details widget implementation
* widget name is "details" and can be use as a wrapping container
* in order to make its children collapsible.
*
* Note: details widget does not represent html5 "details" tag in DOM
*
* <details>
* <widgets>
* ....
* <details/>
*
*/
export class et2_details extends et2_box {
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, _child);
this.div = jQuery(document.createElement('div')).addClass('et2_details');
this.title = jQuery(document.createElement('span'))
.addClass('et2_label et2_details_title')
.appendTo(this.div);
this.span = jQuery(document.createElement('span'))
.addClass('et2_details_toggle')
.appendTo(this.div);
this.wrapper = jQuery(document.createElement('div'))
.addClass('et2_details_wrapper')
.appendTo(this.div);
this._createWidget();
}
/**
* Function happens on toggle action
*/
_toggle() {
this.div.toggleClass('et2_details_expanded');
}
/**
* Create widget, set contents, and binds handlers
*/
_createWidget() {
const self = this;
this.span.on('click', function (e) {
self._toggle();
});
//Set header title
if (this.options.title) {
this.title
.click(function () {
self._toggle();
})
.text(this.options.title);
}
// Align toggle button left/right
if (this.options.toggle_align === "left")
this.span.css({ float: 'left' });
}
getDOMNode(_sender) {
if (!_sender || _sender === this) {
return this.div[0];
}
else {
return this.wrapper[0];
}
}
}
et2_details._attributes = {
"toggle_align": {
name: "Toggle button alignment",
description: " Defines where to align the toggle button, default is right alignment",
type: "string",
default: "right"
},
title: {
name: "title",
description: "Set a header title for box and shows it next to toggle button, default is no title",
type: "string",
default: "",
translate: true
}
};
et2_register_widget(et2_details, ["details"]);
//# sourceMappingURL=et2_widget_box.js.map

View File

@ -1,378 +0,0 @@
/**
* EGroupware eTemplate2 - JS Button object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_interfaces;
et2_core_baseWidget;
*/
import { et2_no_init } from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_baseWidget } from './et2_core_baseWidget';
/**
* Class which implements the "button" XET-Tag
*/
export class et2_button extends et2_baseWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_button._attributes, _child || {}));
this.label = "";
this.clicked = false;
this.btn = null;
this.image = null;
if (!this.options.background_image && (this.options.image || this.options.ro_image)) {
this.image = jQuery(document.createElement("img"))
.addClass("et2_button et2_button_icon");
if (!this.options.readonly)
this.image.addClass("et2_clickable");
this.setDOMNode(this.image[0]);
return;
}
if (!this.options.readonly || this.options.ro_image) {
this.btn = jQuery(document.createElement("button"))
.addClass("et2_button")
.attr({ type: "button" });
this.setDOMNode(this.btn[0]);
}
if (this.options.image)
this.set_image(this.options.image);
}
/**
* Apply the "modifications" to the element and translate attributes marked
* with "translate: true"
*
* Reimplemented here to assign default background-images to buttons
*
* @param {object} _attrs
*/
transformAttributes(_attrs) {
if (this.id && typeof _attrs.background_image == 'undefined' && !_attrs.image) {
for (var image in et2_button.default_background_images) {
if (this.id.match(et2_button.default_background_images[image])) {
_attrs.image = image;
_attrs.background_image = true;
break;
}
}
}
for (var name in et2_button.default_classes) {
if (this.id.match(et2_button.default_classes[name])) {
_attrs.class = (typeof _attrs.class == 'undefined' ? '' : _attrs.class + ' ') + name;
break;
}
}
super.transformAttributes(_attrs);
}
set_accesskey(key) {
jQuery(this.node).attr("accesskey", key);
}
/**
* Set image and update current image
*
* @param _image
*/
set_image(_image) {
this.options.image = _image;
this.update_image();
}
/**
* Set readonly image and update current image
*
* @param _image
*/
set_ro_image(_image) {
this.options.ro_image = _image;
this.update_image();
}
/**
* Set current image (dont update options.image)
*
* @param _image
*/
update_image(_image) {
if (!this.isInTree() || !this.options.background_image && this.image == null)
return;
if (typeof _image == 'undefined')
_image = this.options.readonly ? (this.options.ro_image || this.options.image) : this.options.image;
// Silently blank for percentages instead of warning about missing image - use a progress widget
if (_image.match(/^[0-9]+\%$/)) {
_image = "";
//this.egw().debug("warn", "Use a progress widget instead of percentage images", this);
}
var found_image = false;
if (_image != "") {
var src = this.egw().image(_image);
if (src) {
found_image = true;
}
else if (_image[0] == '/' || _image.substr(0, 4) == 'http') {
src = _image;
found_image = true;
}
if (found_image) {
if (this.image != null) {
this.image.attr("src", src);
}
else if (this.options.background_image && this.btn) {
this.btn.css("background-image", "url(" + src + ")");
this.btn.addClass('et2_button_with_image');
}
}
}
if (!found_image) {
this.set_label(this.label);
if (this.btn) {
this.btn.css("background-image", "");
this.btn.removeClass('et2_button_with_image');
}
}
}
/**
* Set options.readonly and update image
*
* @param {boolean} _ro
*/
set_readonly(_ro) {
if (_ro != this.options.readonly) {
this.options.readonly = _ro;
if (this.options.image || this.options.ro_image) {
this.update_image();
}
// dont show readonly buttons as clickable
if (this.btn || this.image) {
(this.btn || this.image)
.toggleClass('et2_clickable', !_ro)
.toggleClass('et2_button_ro', _ro)
.css('cursor', _ro ? 'default' : 'pointer'); // temp. 'til it is removed from et2_button
}
}
}
attachToDOM() {
let ret = super.attachToDOM();
if (this.options.readonly && (this.btn || this.image)) {
(this.btn || this.image)
.removeClass('et2_clickable')
.addClass('et2_button_ro')
.css('cursor', 'default'); // temp. 'til it is removed from et2_button
}
return ret;
}
getDOMNode() {
return this.btn ? this.btn[0] : (this.image ? this.image[0] : null);
}
/**
* Overwritten to maintain an internal clicked attribute
*
* @param _ev
* @returns {Boolean}
*/
click(_ev) {
var _a, _b;
// ignore click on readonly button
if (this.options.readonly)
return false;
this.clicked = true;
// Cancel buttons don't trigger the close confirmation prompt
if ((_a = this.btn) === null || _a === void 0 ? void 0 : _a.hasClass("et2_button_cancel")) {
this.getInstanceManager().skip_close_prompt();
}
if (!super.click.apply(this, arguments)) {
this.clicked = false;
return false;
}
// Submit the form
if (this.getType() != "buttononly") {
this.getInstanceManager().submit(this, false, this.options.novalidate); //TODO: this only needs to be passed if it's in a datagrid
}
this.clicked = false;
(_b = this.getInstanceManager()) === null || _b === void 0 ? void 0 : _b.skip_close_prompt(false);
return true;
}
set_label(_value) {
if (this.btn) {
this.label = _value;
this.btn.text(_value);
if (_value && !this.image)
this.btn.addClass('et2_button_text');
else
this.btn.removeClass('et2_button_text');
}
if (this.image) {
this.image.attr("alt", _value);
// Don't set title if there's a tooltip, browser may show both
if (!this.options.statustext) {
this.image.attr("title", _value);
}
}
}
/**
* Set tab index
*
* @param {number} index
*/
set_tabindex(index) {
jQuery(this.btn).attr("tabindex", index);
}
/**
* Implementation of the et2_IInput interface
*/
/**
* Always return false as a button is never dirty
*/
isDirty() {
return false;
}
resetDirty() {
}
getValue() {
if (this.clicked) {
return true;
}
// If "null" is returned, the result is not added to the submitted
// array.
return null;
}
isValid() {
return true;
}
/**
* et2_IDetachedDOM
*
* @param {array} _attrs
*/
getDetachedAttributes(_attrs) {
_attrs.push("label", "value", "class", "image", "ro_image", "onclick", "background_image");
}
getDetachedNodes() {
return [
this.btn != null ? this.btn[0] : null,
this.image != null ? this.image[0] : null
];
}
setDetachedAttributes(_nodes, _values) {
// Datagrid puts in the row for null
this.btn = _nodes[0].nodeName[0] != '#' ? jQuery(_nodes[0]) : null;
this.image = 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") {
}
if (typeof _values["image"] != "undefined") {
this.set_image(_values["image"]);
}
if (typeof _values["ro_image"] != "undefined") {
this.set_ro_image(_values["ro_image"]);
}
if (typeof _values["class"] != "undefined") {
this.set_class(_values["class"]);
}
if (typeof _values["onclick"] != "undefined") {
this.options.onclick = _values["onclick"];
}
var type = this.getType();
var attrs = jQuery.extend(_values, this.options);
var parent = this.getParent();
jQuery(this.getDOMNode()).bind("click.et2_baseWidget", this, function (e) {
var widget = et2_createWidget(type, attrs, parent);
e.data = widget;
e.data.set_id(_values["id"]);
return e.data.click.call(e.data, e);
});
}
}
et2_button._attributes = {
"label": {
"name": "caption",
"type": "string",
"description": "Label of the button",
"translate": true
},
"image": {
"name": "Icon",
"type": "string",
"description": "Use an icon instead of label (when available)"
},
"ro_image": {
"name": "Read-only Icon",
"type": "string",
"description": "Use this icon instead of hiding for read-only"
},
"onclick": {
"description": "JS code which gets executed when the button is clicked",
"type": "js"
},
"accesskey": {
"name": "Access Key",
"type": "string",
"default": et2_no_init,
"description": "Alt + <key> activates widget"
},
"tabindex": {
"name": "Tab index",
"type": "integer",
"default": et2_no_init,
"description": "Specifies the tab order of a widget when the 'tab' button is used for navigating."
},
background_image: {
name: "Add image in front of text",
type: "boolean",
description: "Adds image in front of text instead of just using an image with text as tooltip",
default: et2_no_init // to leave it undefined, if not defined, so background-image is assigned by default
},
novalidate: {
name: "Do NOT validate form",
type: "boolean",
description: "Do NOT validate form before submitting it",
default: false
},
// No such thing as a required button
"needed": {
"ignore": true
}
};
et2_button.legacyOptions = ["image", "ro_image"];
/**
* images to be used as background-image, if none is explicitly applied and id matches given regular expression
*/
et2_button.default_background_images = {
save: /save(&|\]|$)/,
apply: /apply(&|\]|$)/,
cancel: /cancel(&|\]|$)/,
delete: /delete(&|\]|$)/,
discard: /discard(&|\]|$)/,
edit: /edit(&|\[\]|$)/,
next: /(next|continue)(&|\]|$)/,
finish: /finish(&|\]|$)/,
back: /(back|previous)(&|\]|$)/,
copy: /copy(&|\]|$)/,
more: /more(&|\]|$)/,
check: /(yes|check)(&|\]|$)/,
cancelled: /no(&|\]|$)/,
ok: /ok(&|\]|$)/,
close: /close(&|\]|$)/,
add: /(add(&|\]|$)|create)/ // customfields use create*
};
/**
* Classnames added automatic to buttons to set certain hover background colors
*/
et2_button.default_classes = {
et2_button_cancel: /cancel(&|\]|$)/,
et2_button_question: /(yes|no)(&|\]|$)/,
et2_button_delete: /delete(&|\]|$)/ // red
};
et2_register_widget(et2_button, ["button", "buttononly"]);
//# sourceMappingURL=et2_widget_button.js.map

View File

@ -1,254 +0,0 @@
/**
* EGroupware eTemplate2 - JS Checkbox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
et2_core_valueWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
/**
* Class which implements the "checkbox" XET-Tag
*
* @augments et2_inputWidget
*/
export class et2_checkbox extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_checkbox
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_checkbox._attributes, _child || {}));
this.input = null;
this.toggle = null;
this.input = null;
this.createInputWidget();
}
createInputWidget() {
this.input = jQuery(document.createElement("input")).attr("type", "checkbox");
this.input.addClass("et2_checkbox");
if (this.options.toggle_on || this.options.toggle_off) {
let self = this;
// checkbox container
this.toggle = jQuery(document.createElement('span'))
.addClass('et2_checkbox_slideSwitch')
.append(this.input);
// update switch status on change
this.input.change(function () {
self.getValue();
return true;
});
// switch container
let area = jQuery(document.createElement('span')).addClass('slideSwitch_container').appendTo(this.toggle);
// on span tag
let on = jQuery(document.createElement('span')).addClass('on').appendTo(area);
// off span tag
let off = jQuery(document.createElement('span')).addClass('off').appendTo(area);
on.text(this.options.toggle_on);
off.text(this.options.toggle_off);
// handle a tag
jQuery(document.createElement('a')).appendTo(area);
this.setDOMNode(this.toggle[0]);
}
else {
this.setDOMNode(this.input[0]);
}
}
/**
* Override default to place checkbox before label, if there is no %s in the label
*
* @param {string} label
*/
set_label(label) {
if (label.length && label.indexOf('%s') < 0) {
label = '%s' + label;
}
super.set_label(label);
jQuery(this.getSurroundings().getWidgetSurroundings()).addClass('et2_checkbox_label');
}
/**
* Override default to match against set/unset value
*
* @param {string|boolean} _value
*/
set_value(_value) {
// in php, our database storage and et2_checkType(): "0" == false
if (_value === "0" && this.options.selected_value != "0") {
_value = false;
}
if (_value != this.value) {
if (_value == this.options.selected_value ||
_value && this.options.selected_value == this.attributes["selected_value"]["default"] &&
_value != this.options.unselected_value) {
if (this.options.toggle_on || this.options.toggle_off)
this.toggle.addClass('switchOn');
this.input.prop("checked", true);
}
else {
this.input.prop("checked", false);
if (this.options.toggle_on || this.options.toggle_off)
this.toggle.removeClass('switchOn');
}
}
}
/**
* Disable checkbox on runtime
*
* @param {boolean} _ro
*/
set_readonly(_ro) {
jQuery(this.getDOMNode()).attr('disabled', _ro);
this.input.prop('disabled', _ro);
}
/**
* Override default to return unchecked value
*/
getValue() {
if (this.input.prop("checked")) {
if (this.options.toggle_on || this.options.toggle_off)
this.toggle.addClass('switchOn');
return this.options.selected_value;
}
else {
if (this.options.toggle_on || this.options.toggle_off)
this.toggle.removeClass('switchOn');
return this.options.unselected_value;
}
}
set_disabled(_value) {
let parentNode = jQuery(this.getDOMNode()).parent();
if (parentNode[0] && parentNode[0].nodeName == "label" && parentNode.hasClass('.et2_checkbox_label')) {
if (_value) {
parentNode.hide();
}
else {
parentNode.show();
}
}
super.set_disabled(_value);
}
}
et2_checkbox._attributes = {
"selected_value": {
"name": "Set value",
"type": "string",
"default": "true",
"description": "Value when checked"
},
"unselected_value": {
"name": "Unset value",
"type": "string",
"default": "",
"description": "Value when not checked"
},
"ro_true": {
"name": "Read only selected",
"type": "string",
"default": "X ",
"description": "What should be displayed when readonly and selected"
},
"ro_false": {
"name": "Read only unselected",
"type": "string",
"default": "",
"description": "What should be displayed when readonly and not selected"
},
"value": {
// Stop framework from messing with value
"type": "any"
},
"toggle_on": {
"name": "Toggle on caption",
"type": "string",
"default": "",
"description": "String caption to show for ON status",
"translate": true
},
"toggle_off": {
"name": "Toggle off caption",
"type": "string",
"default": "",
"description": "String caption to show OFF status",
"translate": true
}
};
et2_checkbox.legacyOptions = ["selected_value", "unselected_value", "ro_true", "ro_false"];
et2_register_widget(et2_checkbox, ["checkbox"]);
/**
* et2_checkbox_ro is the dummy readonly implementation of the checkbox
* @augments et2_checkbox
*/
export class et2_checkbox_ro extends et2_checkbox {
/**
* Constructor
*
* @memberOf et2_checkbox_ro
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_checkbox_ro._attributes, _child || {}));
this.span = null;
this.value = "";
this.span = jQuery(document.createElement("span"))
.addClass("et2_checkbox_ro");
this.setDOMNode(this.span[0]);
}
/**
* note: checkbox is checked if even there is a value but not only if the _value is only "true"
* it's an exceptional validation for cases that we pass non boolean values as checkbox _value
*
* @param {string|boolean} _value
*/
set_value(_value) {
if (_value == this.options.selected_value || _value && this.options.selected_value == this.attributes["selected_value"]["default"] &&
_value != this.options.unselected_value) {
this.span.text(this.options.ro_true);
this.value = _value;
}
else {
this.span.text(this.options.ro_false);
}
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "class");
}
getDetachedNodes() {
return [this.span[0]];
}
setDetachedAttributes(_nodes, _values) {
// Update the properties
if (typeof _values["value"] != "undefined") {
this.span = jQuery(_nodes[0]);
this.set_value(_values["value"]);
}
if (typeof _values["class"] != "undefined") {
_nodes[0].setAttribute("class", _values["class"]);
}
}
}
/**
* Ignore unset value
*/
et2_checkbox_ro._attributes = {
"unselected_value": {
"ignore": true
}
};
et2_register_widget(et2_checkbox_ro, ["checkbox_ro"]);
//# sourceMappingURL=et2_widget_checkbox.js.map

View File

@ -1,115 +0,0 @@
/**
* EGroupware eTemplate2 - JS Color picker object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2012
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
et2_core_valueWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
/**
* Class which implements the "colorpicker" XET-Tag
*
*/
export class et2_color extends et2_inputWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_color._attributes, _child || {}));
this.cleared = true;
// included via etemplate2.css
//this.egw().includeCSS("phpgwapi/js/jquery/jpicker/css/jPicker-1.1.6.min.css");
this.span = jQuery("<span class='et2_color'/>");
this.image = jQuery("<img src='" + this.egw().image("non_loaded_bg") + "'/>")
.appendTo(this.span)
.on("click", function () {
this.input.trigger('click');
}.bind(this));
this.input = jQuery("<input type='color'/>").appendTo(this.span)
.on('change', function () {
this.cleared = false;
this.image.hide();
}.bind(this));
if (!this.options.readonly && !this.options.needed) {
this.clear = jQuery("<span class='ui-icon clear'/>")
.appendTo(this.span)
.on("click", function () {
this.set_value('');
return false;
}.bind(this));
}
this.setDOMNode(this.span[0]);
}
getValue() {
var value = this.input.val();
if (this.cleared || value === '#FFFFFF' || value === '#ffffff') {
return '';
}
return value;
}
set_value(color) {
if (!color) {
color = '';
}
this.cleared = !color;
this.image.toggle(!color);
this.input.val(color);
}
}
et2_register_widget(et2_color, ["colorpicker"]);
/**
* et2_textbox_ro is the dummy readonly implementation of the textbox.
* @augments et2_valueWidget
*/
export class et2_color_ro extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_color_ro
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, _child || {});
this.value = "";
this.$node = jQuery(document.createElement("div"))
.addClass("et2_color");
this.setDOMNode(this.$node[0]);
}
set_value(_value) {
this.value = _value;
if (!_value)
_value = "inherit";
this.$node.css("background-color", _value);
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
_attrs.push("value");
}
getDetachedNodes() {
return [this.node];
}
setDetachedAttributes(_nodes, _values) {
this.$node = jQuery(_nodes[0]);
if (typeof _values["value"] != 'undefined') {
this.set_value(_values["value"]);
}
}
}
et2_register_widget(et2_color_ro, ["colorpicker_ro"]);
//# sourceMappingURL=et2_widget_color.js.map

View File

@ -1,157 +0,0 @@
/**
* EGroupware eTemplate2 - Countdown timer widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
*/
import { et2_no_init } from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "countdown" XET-Tag
*
* Value for countdown is an integer duration in seconds or a server-side to a duration converted expiry datetime.
*
* The duration has the benefit, that it does not depend on the correct set time and timezone of the browser / computer of the user.
*/
export class et2_countdown extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_countdown._attributes, _child || {}));
this.timer = null;
this.container = null;
this.days = null;
this.hours = null;
this.minutes = null;
this.seconds = null;
// Build countdown dom container
this.container = jQuery(document.createElement("div"))
.addClass("et2_countdown");
this.days = jQuery(document.createElement("span"))
.addClass("et2_countdown_days").appendTo(this.container);
this.hours = jQuery(document.createElement("span"))
.addClass("et2_countdown_hours").appendTo(this.container);
this.minutes = jQuery(document.createElement("span"))
.addClass("et2_countdown_minutes").appendTo(this.container);
this.seconds = jQuery(document.createElement("span"))
.addClass("et2_countdown_seconds").appendTo(this.container);
this.setDOMNode(this.container[0]);
}
set_value(_time) {
if (isNaN(_time))
return;
super.set_value(_time);
this.time = new Date();
this.time.setSeconds(this.time.getSeconds() + parseInt(_time));
let self = this;
this.timer = setInterval(function () {
if (self._updateTimer() <= 0) {
clearInterval(self.timer);
if (typeof self.onFinish == "function")
self.onFinish();
}
}, 1000);
}
_updateTimer() {
let now = new Date();
let distance = this.time.getTime() - now.getTime();
if (distance < 0)
return 0;
if (this.options.alarm > 0 && this.options.alarm == distance / 1000 && typeof this.onAlarm == 'function') {
this.onAlarm();
}
let values = {
days: Math.floor(distance / (1000 * 60 * 60 * 24)),
hours: Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)),
minutes: Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)),
secounds: Math.floor((distance % (1000 * 60)) / 1000)
};
this.days.text(values.days + this._getIndicator("days"));
this.hours.text(values.hours + this._getIndicator("hours"));
this.minutes.text(values.minutes + this._getIndicator("minutes"));
this.seconds.text(values.secounds + this._getIndicator("seconds"));
if (this.options.hideEmpties) {
if (values.days == 0) {
this.days.hide();
if (values.hours == 0) {
this.hours.hide();
if (values.minutes == 0) {
this.minutes.hide();
if (values.secounds == 0)
this.seconds.hide();
}
}
}
}
if (this.options.precision) {
const units = ['days', 'hours', 'minutes', 'seconds'];
for (let u = 0; u < 4; ++u) {
if (values[units[u]]) {
for (let n = u + this.options.precision; n < 4; n++) {
this[units[n]].hide();
}
break;
}
else {
this[units[u]].hide();
}
}
}
return distance;
}
_getIndicator(_v) {
return this.options.format == 's' ? egw.lang(_v).substr(0, 1) : egw.lang(_v);
}
}
et2_countdown._attributes = {
format: {
name: "display format",
type: "string",
default: "s",
description: "Defines display format; s (Initial letter) or l (Complete word) display, default is s."
},
onFinish: {
name: "on finish countdown",
type: "js",
default: et2_no_init,
description: "Callback function to call when the countdown is finished."
},
hideEmpties: {
name: "hide empties",
type: "string",
default: true,
description: "Only displays none empty values."
},
precision: {
name: "how many counters to show",
type: "integer",
default: 0,
description: "Limit number of counters, eg. 2 does not show minutes and seconds, if days are displayed"
},
alarm: {
name: "alarm",
type: "any",
default: "",
description: "Defines an alarm set before the countdown is finished, it should be in seconds"
},
onAlarm: {
name: "alarm callback",
type: "js",
default: "",
description: "Defines a callback to gets called at alarm - timer. This only will work if there's an alarm set."
}
};
et2_register_widget(et2_countdown, ["countdown"]);
//# sourceMappingURL=et2_widget_countdown.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,353 +0,0 @@
/**
* EGroupware eTemplate2 - JS Description object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
var _a;
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
expose;
*/
import { et2_activateLinks, et2_csvSplit, et2_insertLinkText, et2_no_init } from "./et2_core_common";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
import { et2_baseWidget } from './et2_core_baseWidget';
import { et2_inputWidget } from "./et2_core_inputWidget";
import { expose } from "./expose";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "description" XET-Tag
*/
export class et2_description extends expose((_a = class et2_description extends et2_baseWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_description._attributes, _child || {}));
this._labelContainer = null;
// Create the span/label tag which contains the label text
this.span = jQuery(document.createElement(this.options["for"] ? "label" : "span"))
.addClass("et2_label");
et2_insertLinkText(this._parseText(this.options.value), this.span[0], this.options.href ? this.options.extra_link_target : '_blank');
this.setDOMNode(this.span[0]);
}
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
if (this.id) {
var val = this.getArrayMgr("content").getEntry(this.id);
if (val) {
_attrs["value"] = val;
}
}
}
doLoadingFinished() {
super.doLoadingFinished();
// Get the real id of the 'for' widget
var for_widget = null;
let for_id = "";
if (this.options["for"] && ((for_widget = this.getParent().getWidgetById(this.options.for)) ||
(for_widget = this.getRoot().getWidgetById(this.options.for))) && for_widget && for_widget.id) {
if (for_widget.dom_id) {
for_id = for_widget.dom_id;
if (for_widget.instanceOf(et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== for_widget.getInputNode().id) {
for_id = for_widget.getInputNode().id;
}
this.span.attr("for", for_id);
}
else {
// Target widget is not done yet, need to wait
var tab_deferred = jQuery.Deferred();
window.setTimeout(function () {
var _a;
for_id = for_widget.dom_id;
if (for_widget.instanceOf(et2_inputWidget) && for_widget.getInputNode() && for_widget.dom_id !== ((_a = for_widget.getInputNode()) === null || _a === void 0 ? void 0 : _a.id)) {
for_id = for_widget.getInputNode().id;
}
this.span.attr("for", for_id);
tab_deferred.resolve();
}.bind(this), 0);
return tab_deferred.promise();
}
}
return true;
}
set_label(_value) {
// Abort if ther was no change in the label
if (_value == this.label) {
return;
}
if (_value) {
// Create the label container if it didn't exist yet
if (this._labelContainer == null) {
this._labelContainer = jQuery(document.createElement("label"))
.addClass("et2_label");
this.getSurroundings().insertDOMNode(this._labelContainer[0]);
}
// Clear the label container.
this._labelContainer.empty();
// Create the placeholder element and set it
var ph = document.createElement("span");
this.getSurroundings().setWidgetPlaceholder(ph);
// Split the label at the "%s"
var parts = et2_csvSplit(_value, 2, "%s");
// Update the content of the label container
for (var i = 0; i < parts.length; i++) {
if (parts[i]) {
this._labelContainer.append(document.createTextNode(parts[i]));
}
if (i == 0) {
this._labelContainer.append(ph);
}
}
// add class if label is empty
this._labelContainer.toggleClass('et2_label_empty', !_value || !parts[0]);
}
else {
// Delete the labelContainer from the surroundings object
if (this._labelContainer) {
this.getSurroundings().removeDOMNode(this._labelContainer[0]);
}
this._labelContainer = null;
}
// Update the surroundings in order to reflect the change in the label
this.getSurroundings().update();
// Copy the given value
this.label = _value;
}
/**
* Function to get media content to feed the expose
* @param {type} _value
* @returns {Array|Array.getMedia.mediaContent}
*/
getMedia(_value) {
let base_url = egw.webserverUrl.match(new RegExp(/^\//, 'ig')) ? egw(window).window.location.origin : '';
let mediaContent = [];
if (_value) {
mediaContent = [{
title: this.options.label,
href: base_url + _value,
type: this.options.type + "/*",
thumbnail: base_url + _value
}];
if (_value.match(/\/webdav.php/, 'ig'))
mediaContent[0]["download_href"] = base_url + _value + '?download';
}
return mediaContent;
}
set_value(_value) {
if (!_value)
_value = "";
if (!this.options.no_lang)
_value = this.egw().lang(_value);
if (this.options.value && (this.options.value + "").indexOf('%s') != -1) {
_value = this.options.value.replace(/%s/g, _value);
}
et2_insertLinkText(this._parseText(_value), this.span[0], this.options.href ? this.options.extra_link_target : '_blank');
// Add hover action button (Edit)
if (this.options.hover_action) {
this._build_hover_action();
}
if (this.options.extra_link_popup || this.options.mime) {
var href = this.options.href;
var mime_data = this.options.mime_data;
var self = this;
var $span = this.options.mime_data ? jQuery(this.span) : jQuery('a', this.span);
$span.click(function (e) {
if (self.options.expose_view && typeof self.options.mime != 'undefined' && self.options.mime.match(self.mime_regexp, 'ig')) {
self._init_blueimp_gallery(e, href);
}
else {
egw(window).open_link(mime_data || href, self.options.extra_link_target, self.options.extra_link_popup, null, null, self.options.mime);
}
e.preventDefault();
return false;
});
}
}
_parseText(_value) {
if (this.options.href) {
var href = this.options.href;
if (href.indexOf('/') == -1 && href.split('.').length >= 3 &&
!(href.indexOf('mailto:') != -1 || href.indexOf('://') != -1 || href.indexOf('javascript:') != -1)) {
href = "/index.php?menuaction=" + href;
}
if (href.charAt(0) == '/') // link relative to eGW
{
href = egw.link(href);
}
return [{
"href": href,
"text": _value
}];
}
else if (this.options.activate_links) {
return et2_activateLinks(_value);
}
else {
return [_value];
}
}
set_font_style(_value) {
this.font_style = _value;
this.span.toggleClass("et2_bold", _value.indexOf("b") >= 0);
this.span.toggleClass("et2_italic", _value.indexOf("i") >= 0);
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "class", "href");
}
getDetachedNodes() {
return [this.span[0]];
}
setDetachedAttributes(_nodes, _values, _data) {
// Update the properties
var updateLink = false;
if (typeof _values["href"] != "undefined") {
updateLink = true;
this.options.href = _values["href"];
}
if (typeof _values["value"] != "undefined" || (updateLink && (_values["value"] || this.options.value))) {
this.span = jQuery(_nodes[0]);
this.set_value(_values["value"]);
}
if (typeof _values["class"] != "undefined") {
_nodes[0].setAttribute("class", _values["class"]);
}
// Add hover action button (Edit), _data is nm's row data
if (this.options.hover_action) {
this._build_hover_action(_data);
}
}
/**
* Builds button for hover action
* @param {object} _data
*/
_build_hover_action(_data) {
var content = _data && _data.content ? _data.content : undefined;
var widget = this;
this.span.off().on('mouseenter', jQuery.proxy(function (event) {
event.stopImmediatePropagation();
var self = this;
this.span.tooltip({
items: 'span.et2_label',
position: { my: "right top", at: "left top", collision: "flipfit" },
tooltipClass: "et2_email_popup",
content() {
return jQuery('<a href="#" class= "et2_url_email_contactPlus" title="' + widget.egw().lang(widget.options.hover_action_title) + '"><img src="'
+ egw.image("edit") + '"/></a>')
.on('click', function () {
widget.options.hover_action.call(self, self.widget, content);
});
},
close(event, ui) {
ui.tooltip.hover(function () {
jQuery(this).stop(true).fadeTo(400, 1);
}, function () {
jQuery(this).fadeOut("400", function () { jQuery(this).remove(); });
});
}
})
.tooltip("open");
}, { widget: this, span: this.span }));
}
},
_a._attributes = {
"label": {
"name": "Label",
"default": "",
"type": "string",
"description": "The label is displayed by default in front (for radiobuttons behind) each widget (if not empty). If you want to specify a different position, use a '%s' in the label, which gets replaced by the widget itself. Eg. '%s Name' to have the label Name behind a checkbox. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
"translate": true
},
"value": {
"name": "Value",
"type": "string",
"description": "Displayed text",
"translate": "!no_lang",
"default": ""
},
/**
* Options converted from the "options"-attribute.
*/
"font_style": {
"name": "Font Style",
"type": "string",
"description": "Style may be a compositum of \"b\" and \"i\" which " +
" renders the text bold and/or italic."
},
"href": {
"name": "Link URL",
"type": "string",
"description": "Link URL, empty if you don't wan't to display a link."
},
"activate_links": {
"name": "Replace URLs",
"type": "boolean",
"default": false,
"description": "If set, URLs in the text are automatically replaced " +
"by links"
},
"for": {
"name": "Label for widget",
"type": "string",
"description": "Marks the text as label for the given widget."
},
"extra_link_target": {
"name": "Link target",
"type": "string",
"default": "_browser",
"description": "Link target for href attribute"
},
"extra_link_popup": {
"name": "Popup",
"type": "string",
"description": "widthxheight, if popup should be used, eg. 640x480"
},
"expose_view": {
name: "Expose view",
type: "boolean",
default: false,
description: "Clicking on description with href value would popup an expose view, and will show content referenced by href."
},
mime: {
name: "Mime type",
type: "string",
default: '',
description: "Mime type of the registered link"
},
mime_data: {
name: "Mime data",
type: "string",
default: '',
description: "hash for data stored on service-side with egw_link::(get|set)_data()"
},
hover_action: {
"name": "hover action",
"type": "js",
"default": et2_no_init,
"description": "JS code which is executed when clicking on action button. This action is explicitly for attached nodes, like in nm."
},
hover_action_title: {
"name": "hover action title",
"type": "string",
"default": "Edit",
"description": "Text to show as tooltip of defined action"
}
},
_a.legacyOptions = ["font_style", "href", "activate_links", "for",
"extra_link_target", "extra_link_popup", "statustext"],
_a)) {
}
;
et2_register_widget(et2_description, ["description", "label"]);
//# sourceMappingURL=et2_widget_description.js.map

View File

@ -1,846 +0,0 @@
/**
* EGroupware eTemplate2 - JS Dialog Widget class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
*/
/*egw:uses
et2_core_widget;
/vendor/bower-asset/jquery-ui/jquery-ui.js;
*/
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_widget } from "./et2_core_widget";
import { et2_button } from "./et2_widget_button";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { etemplate2 } from "./etemplate2";
import { egw } from "../jsapi/egw_global";
import { et2_no_init } from "./et2_core_common";
/**
* A common dialog widget that makes it easy to imform users or prompt for information.
*
* It is possible to have a custom dialog by using a template, but you can also use
* the static method et2_dialog.show_dialog(). At its simplest, you can just use:
* <code>
* et2_dialog.show_dialog(false, "Operation completed");
* </code>
* Or a more complete example:
* <code>
* var callback = function (button_id)
* {
* if(button_id == et2_dialog.YES_BUTTON)
* {
* // Do stuff
* }
* else if (button_id == et2_dialog.NO_BUTTON)
* {
* // Other stuff
* }
* else if (button_id == et2_dialog.CANCEL_BUTTON)
* {
* // Abort
* }
* }.
* var dialog = et2_dialog.show_dialog(
* callback, "Erase the entire database?","Break things", {} // value
* et2_dialog.BUTTONS_YES_NO_CANCEL, et2_dialog.WARNING_MESSAGE
* );
* </code>
*
*
* The parameters for the above are all optional, except callback and message:
* callback - function called when the dialog closes, or false/null.
* The ID of the button will be passed. Button ID will be one of the et2_dialog.*_BUTTON constants.
* The callback is _not_ called if the user closes the dialog with the X in the corner, or presses ESC.
* message - (plain) text to display
* title - Dialog title
* value (for prompt)
* buttons - et2_dialog BUTTONS_* constant, or an array of button settings
* dialog_type - et2_dialog *_MESSAGE constant
* icon - URL of icon
*
* Note that these methods will _not_ block program flow while waiting for user input.
* The user's input will be provided to the callback.
*
* You can also use the standard et2_createWidget() to create a custom dialog using an etemplate, even setting all
* the buttons yourself.
* <code>
* var dialog = et2_createWidget("dialog",{
* // If you use a template, the second parameter will be the value of the template, as if it were submitted.
* callback: function(button_id, value) {...}, // return false to prevent dialog closing
* buttons: [
* // These ones will use the callback, just like normal
* {text: egw.lang("OK"),id:"OK", class="ui-priority-primary", default: true},
* {text: egw.lang("Yes"),id:"Yes"},
* {text: egw.lang("Sure"),id:"Sure"},
* {text: egw.lang("Maybe"),click: function() {
* // If you override, 'this' will be the dialog DOMNode.
* // Things get more complicated.
* // Do what you like, but don't forget this line:
* jQuery(this).dialog("close")
* }, class="ui-state-error"},
*
* ],
* title: 'Why would you want to do this?',
* template:"/egroupware/addressbook/templates/default/edit.xet",
* value: { content: {...default values}, sel_options: {...}...}
* });
* </code>
* @augments et2_widget
* @see http://api.jqueryui.com/dialog/
*/
export class et2_dialog extends et2_widget {
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_dialog._attributes, _child || {}));
/**
* Details for dialog type options
*/
this._dialog_types = [
//PLAIN_MESSAGE: 0
"",
//INFORMATION_MESSAGE: 1,
"dialog_info",
//QUESTION_MESSAGE: 2,
"dialog_help",
//WARNING_MESSAGE: 3,
"dialog_warning",
//ERROR_MESSAGE: 4,
"dialog_error"
];
this._buttons = [
/*
Pre-defined Button combos
- button ids copied from et2_dialog static, since the constants are not defined yet
- image get replaced by 'style="background-image: url('+egw.image(image)+')' for an image prefixing text
*/
//BUTTONS_OK: 0,
[{ "button_id": 1, "text": 'ok', id: 'dialog[ok]', image: 'check', "default": true }],
//BUTTONS_OK_CANCEL: 1,
[
{ "button_id": 1, "text": 'ok', id: 'dialog[ok]', image: 'check', "default": true },
{ "button_id": 0, "text": 'cancel', id: 'dialog[cancel]', image: 'cancel' }
],
//BUTTONS_YES_NO: 2,
[
{ "button_id": 2, "text": 'yes', id: 'dialog[yes]', image: 'check', "default": true },
{ "button_id": 3, "text": 'no', id: 'dialog[no]', image: 'cancelled' }
],
//BUTTONS_YES_NO_CANCEL: 3,
[
{ "button_id": 2, "text": 'yes', id: 'dialog[yes]', image: 'check', "default": true },
{ "button_id": 3, "text": 'no', id: 'dialog[no]', image: 'cancelled' },
{ "button_id": 0, "text": 'cancel', id: 'dialog[cancel]', image: 'cancel' }
]
];
this.div = null;
this.template = null;
// Define this as null to avoid breaking any hierarchies (eg: destroy())
if (this.getParent() != null)
this.getParent().removeChild(this);
// Button callbacks need a reference to this
let self = this;
for (let i = 0; i < this._buttons.length; i++) {
for (let j = 0; j < this._buttons[i].length; j++) {
this._buttons[i][j].click = (function (id) {
return function (event) {
self.click(event.target, id);
};
})(this._buttons[i][j].button_id);
// translate button texts, as translations are not available before
this._buttons[i][j].text = egw.lang(this._buttons[i][j].text);
}
}
this.div = jQuery(document.createElement("div"));
this._createDialog();
}
/**
* Clean up dialog
*/
destroy() {
if (this.div != null) {
// Un-dialog the dialog
this.div.dialog("destroy");
if (this.template) {
this.template.clear(true);
this.template = null;
}
this.div = null;
}
// Call the inherited constructor
super.destroy();
}
/**
* Internal callback registered on all standard buttons.
* The provided callback is called after the dialog is closed.
*
* @param target DOMNode The clicked button
* @param button_id integer The ID of the clicked button
*/
click(target, button_id) {
if (this.options.callback) {
if (this.options.callback.call(this, button_id, this.get_value()) === false)
return;
}
// Triggers destroy too
this.div.dialog("close");
}
/**
* Returns the values of any widgets in the dialog. This does not include
* the buttons, which are only supplied for the callback.
*/
get_value() {
var value = this.options.value;
if (this.template) {
value = this.template.getValues(this.template.widgetContainer);
}
return value;
}
/**
* Set the displayed prompt message
*
* @param {string} message New message for the dialog
*/
set_message(message) {
this.options.message = message;
this.div.empty()
.append("<img class='dialog_icon' />")
.append(jQuery('<div/>').text(message));
}
/**
* Set the dialog type to a pre-defined type
*
* @param {integer} type constant from et2_dialog
*/
set_dialog_type(type) {
if (this.options.dialog_type != type && typeof this._dialog_types[type] == "string") {
this.options.dialog_type = type;
}
this.set_icon(this._dialog_types[type] ? egw.image(this._dialog_types[type]) : "");
}
/**
* Set the icon for the dialog
*
* @param {string} icon_url
*/
set_icon(icon_url) {
if (icon_url == "") {
jQuery("img.dialog_icon", this.div).hide();
}
else {
jQuery("img.dialog_icon", this.div).show().attr("src", icon_url);
}
}
/**
* Set the dialog buttons
*
* Use either the pre-defined options in et2_dialog, or an array
* @see http://api.jqueryui.com/dialog/#option-buttons
* @param {array} buttons
*/
set_buttons(buttons) {
this.options.buttons = buttons;
if (buttons instanceof Array) {
for (var i = 0; i < buttons.length; i++) {
var button = buttons[i];
if (!button.click) {
button.click = jQuery.proxy(this.click, this, null, button.id);
}
// set a default background image and css class based on buttons id
if (button.id && typeof button.class == 'undefined') {
for (var name in et2_button.default_classes) {
if (button.id.match(et2_button.default_classes[name])) {
button.class = (typeof button.class == 'undefined' ? '' : button.class + ' ') + name;
break;
}
}
}
if (button.id && typeof button.image == 'undefined' && typeof button.style == 'undefined') {
for (var name in et2_button.default_background_images) {
if (button.id.match(et2_button.default_background_images[name])) {
button.image = name;
break;
}
}
}
if (button.image) {
button.style = 'background-image: url(' +
(button.image.match('^http|\/') ?
button.image : this.egw().image(button.image, 'api'))
+ ')';
delete button.image;
}
}
}
// If dialog already created, update buttons
if (this.div.data('ui-dialog')) {
this.div.dialog("option", "buttons", buttons);
// Focus default button so enter works
jQuery('.ui-dialog-buttonpane button[default]', this.div.parent()).focus();
}
}
/**
* Set the dialog title
*
* @param {string} title New title for the dialog
*/
set_title(title) {
this.options.title = title;
this.div.dialog("option", "title", title);
}
/**
* Block interaction with the page behind the dialog
*
* @param {boolean} modal Block page behind dialog
*/
set_modal(modal) {
this.options.modal = modal;
this.div.dialog("option", "modal", modal);
}
/**
* Load an etemplate into the dialog
*
* @param template String etemplate file name
*/
set_template(template) {
if (this.template && this.options.template != template) {
this.template.clear();
}
this.template = new etemplate2(this.div[0]);
if (template.indexOf('.xet') > 0) {
// File name provided, fetch from server
this.template.load("", template, this.options.value || { content: {} }, jQuery.proxy(function () {
// Set focus to the first input
jQuery('input', this.div).first().focus();
}, this));
}
else {
// Just template name, it better be loaded already
this.template.load(template, '', this.options.value || {},
// true: do NOT call et2_ready, as it would overwrite this.et2 in app.js
undefined, undefined, true);
}
// Don't let dialog closing destroy the parent session
if (this.template.etemplate_exec_id && this.template.app) {
for (let et of etemplate2.getByApplication(this.template.app)) {
if (et !== this.template && et.etemplate_exec_id === this.template.etemplate_exec_id) {
// Found another template using that exec_id, don't destroy when dialog closes.
this.template.unbind_unload();
break;
}
}
}
// set template-name as id, to allow to style dialogs
this.div.children().attr('id', template.replace(/^(.*\/)?([^/]+)(\.xet)?$/, '$2').replace(/\./g, '-'));
}
/**
* Actually create and display the dialog
*/
_createDialog() {
if (this.options.template) {
this.set_template(this.options.template);
}
else {
this.set_message(this.options.message);
this.set_dialog_type(this.options.dialog_type);
}
this.set_buttons(typeof this.options.buttons == "number" ? this._buttons[this.options.buttons] : this.options.buttons);
let position_my, position_at = '';
if (this.options.position) {
let positions = this.options.position.split(',');
position_my = positions[0] ? positions[0].trim() : 'center';
position_at = positions[1] ? positions[1].trim() : position_my;
}
let options = {
// Pass the internal object, not the option
buttons: this.options.buttons,
modal: this.options.modal,
resizable: this.options.resizable,
minWidth: this.options.minWidth,
minHeight: this.options.minHeight,
maxWidth: 640,
height: this.options.height,
title: this.options.title,
open: function () {
// Focus default button so enter works
jQuery(this).parents('.ui-dialog-buttonpane button[default]').focus();
window.setTimeout(function () {
jQuery(this).dialog('option', 'position', {
my: position_my,
at: position_at,
of: window
});
}.bind(this), 0);
},
close: jQuery.proxy(function () {
this.destroy();
}, this),
beforeClose: this.options.beforeClose,
closeText: this.egw().lang('close'),
position: { my: "center", at: "center", of: window },
appendTo: this.options.appendTo,
draggable: this.options.draggable,
closeOnEscape: this.options.closeOnEscape,
dialogClass: this.options.dialogClass,
};
// Leaving width unset lets it size itself according to contents
if (this.options.width) {
options['width'] = this.options.width;
}
this.div.dialog(options);
// Make sure dialog is wide enough for the title
// Arbitrary numbers that seem to work nicely.
let title_width = 20 + 10 * this.options.title.length;
if (this.div.width() < title_width && this.options.title.trim()) {
// Auto-sizing chopped the title
this.div.dialog('option', 'width', title_width);
}
}
/**
* Create a parent to inject application specific egw object with loaded translations into et2_dialog
*
* @param {string|egw} _egw_or_appname egw object with already loaded translations or application name to load translations for
*/
static _create_parent(_egw_or_appname) {
if (typeof _egw_or_appname == 'undefined') {
// @ts-ignore
_egw_or_appname = egw_appName;
}
// create a dummy parent with a correct reference to an application specific egw object
let parent = new et2_widget();
// if egw object is passed in because called from et2, just use it
if (typeof _egw_or_appname != 'string') {
parent.setApiInstance(_egw_or_appname);
}
// otherwise use given appname to create app-specific egw instance and load default translations
else {
parent.setApiInstance(egw(_egw_or_appname));
parent.egw().langRequireApp(parent.egw().window, _egw_or_appname);
}
return parent;
}
/**
* Show a confirmation dialog
*
* @param {function} _callback Function called when the user clicks a button. The context will be the et2_dialog widget, and the button constant is passed in.
* @param {string} _message Message to be place in the dialog.
* @param {string} _title Text in the top bar of the dialog.
* @param _value passed unchanged to callback as 2. parameter
* @param {integer|array} _buttons One of the BUTTONS_ constants defining the set of buttons at the bottom of the box
* @param {integer} _type One of the message constants. This defines the style of the message.
* @param {string} _icon URL of an icon to display. If not provided, a type-specific icon will be used.
* @param {string|egw} _egw_or_appname egw object with already laoded translations or application name to load translations for
*/
static show_dialog(_callback, _message, _title, _value, _buttons, _type, _icon, _egw_or_appname) {
let parent = et2_dialog._create_parent(_egw_or_appname);
// Just pass them along, widget handles defaults & missing
return et2_createWidget("dialog", {
callback: _callback || function () {
},
message: _message,
title: _title || parent.egw().lang('Confirmation required'),
buttons: typeof _buttons != 'undefined' ? _buttons : et2_dialog.BUTTONS_YES_NO,
dialog_type: typeof _type != 'undefined' ? _type : et2_dialog.QUESTION_MESSAGE,
icon: _icon,
value: _value,
width: 'auto'
}, parent);
}
;
/**
* Show an alert message with OK button
*
* @param {string} _message Message to be place in the dialog.
* @param {string} _title Text in the top bar of the dialog.
* @param {integer} _type One of the message constants. This defines the style of the message.
*/
static alert(_message, _title, _type) {
let parent = et2_dialog._create_parent(et2_dialog._create_parent().egw());
et2_createWidget("dialog", {
callback: function () {
},
message: _message,
title: _title,
buttons: et2_dialog.BUTTONS_OK,
dialog_type: _type || et2_dialog.INFORMATION_MESSAGE
}, parent);
}
/**
* Show a prompt dialog
*
* @param {function} _callback Function called when the user clicks a button. The context will be the et2_dialog widget, and the button constant is passed in.
* @param {string} _message Message to be place in the dialog.
* @param {string} _title Text in the top bar of the dialog.
* @param {string} _value for prompt, passed to callback as 2. parameter
* @param {integer|array} _buttons One of the BUTTONS_ constants defining the set of buttons at the bottom of the box
* @param {string|egw} _egw_or_appname egw object with already laoded translations or application name to load translations for
*/
static show_prompt(_callback, _message, _title, _value, _buttons, _egw_or_appname) {
var callback = _callback;
// Just pass them along, widget handles defaults & missing
return et2_createWidget("dialog", {
callback: function (_button_id, _value) {
if (typeof callback == "function") {
callback.call(this, _button_id, _value.value);
}
},
title: _title || egw.lang('Input required'),
buttons: _buttons || et2_dialog.BUTTONS_OK_CANCEL,
value: {
content: {
value: _value,
message: _message
}
},
template: egw.webserverUrl + '/api/templates/default/prompt.xet',
class: "et2_prompt"
}, et2_dialog._create_parent(_egw_or_appname));
}
/**
* Method to build a confirmation dialog only with
* YES OR NO buttons and submit content back to server
*
* @param {widget} _senders widget that has been clicked
* @param {String} _dialogMsg message shows in dialog box
* @param {String} _titleMsg message shows as a title of the dialog box
* @param {Bool} _postSubmit true: use postSubmit instead of submit
*
* @description submit the form contents including the button that has been pressed
*/
static confirm(_senders, _dialogMsg, _titleMsg, _postSubmit) {
var senders = _senders;
var buttonId = _senders.id;
var dialogMsg = (typeof _dialogMsg != "undefined") ? _dialogMsg : '';
var titleMsg = (typeof _titleMsg != "undefined") ? _titleMsg : '';
var egw = _senders instanceof et2_widget ? _senders.egw() : et2_dialog._create_parent().egw();
var callbackDialog = function (button_id) {
if (button_id == et2_dialog.YES_BUTTON) {
if (_postSubmit) {
senders.getRoot().getInstanceManager().postSubmit(buttonId);
}
else if (senders.instanceOf(et2_button) && senders.getType() !== "buttononly") {
senders.clicked = true;
senders.getInstanceManager().submit(senders, false, senders.options.novalidate);
senders.clicked = false;
}
else {
senders.getRoot().getInstanceManager().submit(buttonId);
}
}
};
et2_dialog.show_dialog(callbackDialog, egw.lang(dialogMsg), egw.lang(titleMsg), {}, et2_dialog.BUTTONS_YES_NO, et2_dialog.WARNING_MESSAGE, undefined, egw);
}
;
/**
* Show a dialog for a long-running, multi-part task
*
* Given a server url and a list of parameters, this will open a dialog with
* a progress bar, asynchronously call the url with each parameter, and update
* the progress bar.
* Any output from the server will be displayed in a box.
*
* When all tasks are done, the callback will be called with boolean true. It will
* also be called if the user clicks a button (OK or CANCEL), so be sure to
* check to avoid executing more than intended.
*
* @param {function} _callback Function called when the user clicks a button,
* or when the list is done processing. The context will be the et2_dialog
* widget, and the button constant is passed in.
* @param {string} _message Message to be place in the dialog. Usually just
* text, but DOM nodes will work too.
* @param {string} _title Text in the top bar of the dialog.
* @param {string} _menuaction the menuaction function which should be called and
* which handles the actual request. If the menuaction is a full featured
* url, this one will be used instead.
* @param {Array[]} _list - List of parameters, one for each call to the
* address. Multiple parameters are allowed, in an array.
* @param {string|egw} _egw_or_appname egw object with already laoded translations or application name to load translations for
*
* @return {et2_dialog}
*/
static long_task(_callback, _message, _title, _menuaction, _list, _egw_or_appname) {
let parent = et2_dialog._create_parent(_egw_or_appname);
let egw = parent.egw();
// Special action for cancel
let buttons = [
{ "button_id": et2_dialog.OK_BUTTON, "text": egw.lang('ok'), "default": true, "disabled": true },
{
"button_id": et2_dialog.CANCEL_BUTTON, "text": egw.lang('cancel'), click: function () {
// Cancel run
cancel = true;
jQuery("button[button_id=" + et2_dialog.CANCEL_BUTTON + "]", dialog.div.parent()).button("disable");
update.call(_list.length, '');
}
}
];
let dialog = et2_createWidget("dialog", {
template: egw.webserverUrl + '/api/templates/default/long_task.xet',
value: {
content: {
message: _message
}
},
callback: function (_button_id, _value) {
if (_button_id == et2_dialog.CANCEL_BUTTON) {
cancel = true;
}
if (typeof _callback == "function") {
_callback.call(this, _button_id, _value.value);
}
},
title: _title || egw.lang('please wait...'),
buttons: buttons
}, parent);
// OK starts disabled
jQuery("button[button_id=" + et2_dialog.OK_BUTTON + "]", dialog.div.parent()).button("disable");
let log = null;
let progressbar = null;
let cancel = false;
let totals = {
success: 0,
skipped: 0,
failed: 0,
widget: null
};
// Updates progressbar & log, calls next step
let update = function (response) {
// context is index
let index = this || 0;
progressbar.set_value(100 * (index / _list.length));
progressbar.set_label(index + ' / ' + _list.length);
// Display response information
switch (response.type) {
case 'error':
jQuery("<div class='message error'></div>")
.text(response.data)
.appendTo(log);
totals.failed++;
// Ask to retry / ignore / abort
et2_createWidget("dialog", {
callback: function (button) {
switch (button) {
case 'dialog[cancel]':
cancel = true;
return update.call(index, '');
case 'dialog[skip]':
// Continue with next index
totals.skipped++;
return update.call(index, '');
default:
// Try again with previous index
return update.call(index - 1, '');
}
},
message: response.data,
title: '',
buttons: [
// These ones will use the callback, just like normal
{ text: egw.lang("Abort"), id: 'dialog[cancel]' },
{ text: egw.lang("Retry"), id: 'dialog[retry]' },
{ text: egw.lang("Skip"), id: 'dialog[skip]', class: "ui-priority-primary", default: true }
],
dialog_type: et2_dialog.ERROR_MESSAGE
}, parent);
// Early exit
return;
default:
if (response && typeof response === "string") {
totals.success++;
jQuery("<div class='message'></div>")
.text(response)
.appendTo(log);
}
else {
jQuery("<div class='message error'></div>")
.text(JSON.stringify(response))
.appendTo(log);
}
}
// Scroll to bottom
let height = log[0].scrollHeight;
log.scrollTop(height);
// Update totals
totals.widget.set_value(egw.lang("Total: %1 Successful: %2 Failed: %3 Skipped: %4", _list.length, totals.success, totals.failed, totals.skipped));
// Fire next step
if (!cancel && index < _list.length) {
var parameters = _list[index];
if (typeof parameters != 'object')
parameters = [parameters];
// Async request, we'll take the next step in the callback
// We can't pass index = 0, it looks like false and causes issues
egw.json(_menuaction, parameters, update, index + 1, true, index + 1).sendRequest();
}
else {
// All done
if (!cancel)
progressbar.set_value(100);
jQuery("button[button_id=" + et2_dialog.CANCEL_BUTTON + "]", dialog.div.parent()).button("disable");
jQuery("button[button_id=" + et2_dialog.OK_BUTTON + "]", dialog.div.parent()).button("enable");
if (!cancel && typeof _callback == "function") {
_callback.call(dialog, true, response);
}
}
};
jQuery(dialog.template.DOMContainer).on('load', function () {
// Get access to template widgets
log = jQuery(dialog.template.widgetContainer.getWidgetById('log').getDOMNode());
progressbar = dialog.template.widgetContainer.getWidgetById('progressbar');
progressbar.set_label('0 / ' + _list.length);
totals.widget = dialog.template.widgetContainer.getWidgetById('totals');
// Start
window.setTimeout(function () {
update.call(0, '');
}, 0);
});
return dialog;
}
}
et2_dialog._attributes = {
callback: {
name: "Callback",
type: "js",
description: "Callback function is called with the value when the dialog is closed",
"default": function (button_id) {
egw.debug("log", "Button ID: %d", button_id);
}
},
beforeClose: {
name: "before close callback",
type: "js",
description: "Callback function before dialog is closed, return false to prevent that",
"default": function () {
}
},
message: {
name: "Message",
type: "string",
description: "Dialog message (plain text, no html)",
"default": "Somebody forgot to set this..."
},
dialog_type: {
name: "Dialog type",
type: "integer",
description: "To use a pre-defined dialog style, use et2_dialog.ERROR_MESSAGE, INFORMATION_MESSAGE,WARNING_MESSAGE,QUESTION_MESSAGE,PLAIN_MESSAGE constants. Default is et2_dialog.PLAIN_MESSAGE",
"default": 0 //this.PLAIN_MESSAGE
},
buttons: {
name: "Buttons",
type: "any",
"default": 0,
description: "Buttons that appear at the bottom of the dialog. You can use the constants et2_dialog.BUTTONS_OK, BUTTONS_YES_NO, BUTTONS_YES_NO_CANCEL, BUTTONS_OK_CANCEL, or pass in an array for full control"
},
icon: {
name: "Icon",
type: "string",
description: "URL of an icon for the dialog. If omitted, an icon based on dialog_type will be used.",
"default": ""
},
title: {
name: "Title",
type: "string",
description: "Title for the dialog box (plain text, no html)",
"default": ""
},
modal: {
name: "Modal",
type: "boolean",
description: "Prevent the user from interacting with the page",
"default": true
},
resizable: {
name: "Resizable",
type: "boolean",
description: "Allow the user to resize the dialog",
"default": true
},
value: {
"name": "Value",
"description": "The (default) value of the dialog. Use with template.",
"type": "any",
"default": et2_no_init
},
template: {
"name": "Template",
"description": "Instead of displaying a simple message, a full template can be loaded instead. Set defaults with value.",
"type": "string",
"default": et2_no_init
},
minWidth: {
name: "minimum width",
type: "integer",
description: "Define minimum width of dialog",
"default": 0
},
minHeight: {
name: "minimum height",
type: "integer",
description: "Define minimum height of dialog",
"default": 0
},
width: {
name: "width",
type: "string",
description: "Define width of dialog, the default is auto",
"default": et2_no_init
},
height: {
name: "height",
type: "string",
description: "Define width of dialog, the default is auto",
"default": 'auto'
},
position: {
name: "position",
type: "string",
description: "Define position of dialog in the main window",
default: "center"
},
appendTo: {
name: "appendTo",
type: "string",
description: "Defines the dialog parent context",
default: ''
},
draggable: {
name: "Draggable",
type: "boolean",
description: "Allow the user to drag the dialog",
default: true
},
closeOnEscape: {
name: "close on escape",
type: "boolean",
description: "Allow the user to close the dialog by hiting escape",
default: true
},
dialogClass: {
name: "dialog class",
type: "string",
description: "Add css classed into dialog container",
default: ''
}
};
/**
* Types
* @constant
*/
et2_dialog.PLAIN_MESSAGE = 0;
et2_dialog.INFORMATION_MESSAGE = 1;
et2_dialog.QUESTION_MESSAGE = 2;
et2_dialog.WARNING_MESSAGE = 3;
et2_dialog.ERROR_MESSAGE = 4;
/* Pre-defined Button combos */
et2_dialog.BUTTONS_OK = 0;
et2_dialog.BUTTONS_OK_CANCEL = 1;
et2_dialog.BUTTONS_YES_NO = 2;
et2_dialog.BUTTONS_YES_NO_CANCEL = 3;
/* Button constants */
et2_dialog.CANCEL_BUTTON = 0;
et2_dialog.OK_BUTTON = 1;
et2_dialog.YES_BUTTON = 2;
et2_dialog.NO_BUTTON = 3;
// make et2_dialog publicly available as we need to call it from templates
window['et2_dialog'] = et2_dialog;
et2_register_widget(et2_dialog, ["dialog"]);
//# sourceMappingURL=et2_widget_dialog.js.map

View File

@ -1,168 +0,0 @@
/**
* EGroupware eTemplate2 - JS Diff object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2012
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/vendor/bower-asset/jquery-ui/jquery-ui.js;
/vendor/bower-asset/diff2html/dist/diff2html.min.js;
et2_core_valueWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from "./et2_core_valueWidget";
/**
* Class that displays the diff between two [text] values
*
* @augments et2_valueWidget
*/
export class et2_diff extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_diff._attributes, _child || {}));
this.mini = true;
// included via etemplate2.css
//this.egw().includeCSS('../../../vendor/bower-asset/dist/dist2html.css');
this.div = document.createElement("div");
jQuery(this.div).addClass('et2_diff');
}
set_value(value) {
jQuery(this.div).empty();
if (typeof value == 'string') {
// Diff2Html likes to have files, we don't have them
if (value.indexOf('---') !== 0) {
value = "--- diff\n+++ diff\n" + value;
}
// @ts-ignore
var diff = Diff2Html.getPrettyHtml(value, this.diff_options);
// var ui = new Diff2HtmlUI({diff: diff});
// ui.draw(jQuery(this.div), this.diff_options);
jQuery(this.div).append(diff);
}
else if (typeof value != 'object') {
jQuery(this.div).append(value);
}
this.check_mini();
}
check_mini() {
if (!this.mini) {
return false;
}
var view = jQuery(this.div).children();
this.minify(view);
var self = this;
jQuery('<span class="ui-icon ui-icon-circle-plus">&nbsp;</span>')
.appendTo(self.div)
.css("cursor", "pointer")
.click({ diff: view, div: self.div, label: self.options.label }, function (e) {
var diff = e.data.diff;
var div = e.data.div;
self.un_minify(diff);
var dialog_div = jQuery('<div>')
.append(diff);
dialog_div.dialog({
title: e.data.label,
width: 'auto',
modal: true,
buttons: [{ text: self.egw().lang('ok'), click: function () { jQuery(this).dialog("close"); } }],
open() {
if (jQuery(this).parent().height() > jQuery(window).height()) {
jQuery(this).height(jQuery(window).height() * 0.7);
}
jQuery(this).addClass('et2_diff').dialog({ position: "center" });
},
close(event, ui) {
// Need to destroy the dialog, etemplate widget needs divs back where they were
dialog_div.dialog("destroy");
self.minify(this);
// Put it back where it came from, or et2 will error when clear() is called
diff.prependTo(div);
}
});
});
}
set_label(_label) {
this.options.label = _label;
}
/**
* Make the diff into a mini-diff
*
* @param {DOMNode|String} view
*/
minify(view) {
view = jQuery(view)
.addClass('mini')
// Dialog changes these, if resized
.width('100%').css('height', 'inherit')
.show();
jQuery('th', view).hide();
jQuery('td.equal', view).hide()
.prevAll().hide();
}
/**
* Expand mini-diff
*
* @param {DOMNode|String} view
*/
un_minify(view) {
jQuery(view).removeClass('mini').show();
jQuery('th', view).show();
jQuery('td.equal', view).show();
}
/**
* Code for implementing et2_IDetachedDOM
* Fast-clonable read-only widget that only deals with DOM nodes, not the widget tree
*/
/**
* Build a list of attributes which can be set when working in the
* "detached" mode in the _attrs array which is provided
* by the calling code.
*
* @param {object} _attrs
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "label");
}
/**
* Returns an array of DOM nodes. The (relativly) same DOM-Nodes have to be
* passed to the "setDetachedAttributes" function in the same order.
*/
getDetachedNodes() {
return [this.div];
}
/**
* Sets the given associative attribute->value array and applies the
* attributes to the given DOM-Node.
*
* @param _nodes is an array of nodes which has 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.div = _nodes[0];
if (typeof _values['label'] != 'undefined') {
this.set_label(_values['label']);
}
if (typeof _values['value'] != 'undefined') {
this.set_value(_values['value']);
}
}
}
et2_diff._attributes = {
"value": {
"type": "any"
}
};
et2_register_widget(et2_diff, ["diff"]);
//# sourceMappingURL=et2_widget_diff.js.map

View File

@ -1,364 +0,0 @@
/**
* EGroupware eTemplate2 - JS Dropdown Button object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/vendor/bower-asset/jquery-ui/jquery-ui.js;
et2_baseWidget;
*/
import { et2_inputWidget } from './et2_core_inputWidget';
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
/**
* A split button - a button with a dropdown list
*
* There are several parts to the button UI:
* - Container: This is what is percieved as the dropdown button, the whole package together
* - Button: The part on the left that can be clicked
* - Arrow: The button to display the choices
* - Menu: The list of choices
*
* Menu options are passed via the select_options. They are normally ID => Title pairs,
* as for a select box, but the title can also be full HTML if needed.
*
* @augments et2_inputWidget
*/
export class et2_dropdown_button extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_dropdown_button
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_dropdown_button._attributes, _child || {}));
this.internal_ids = {
div: "",
button: "",
menu: ""
};
this.div = null;
this.buttons = null;
this.button = null;
this.arrow = null;
this.menu = null;
this.image = null;
this.clicked = false;
this.label_updates = true;
this.value = null;
/**
* Default menu, so there is something for the widget browser / editor to show
*/
this.default_menu = '<ul> \
<li data-id="opt_1.1"><a href="#">Option-1.1</a></li>\
<li data-id="opt_1.2"><a href="#">Option-1.2</a></li>\
<li data-id="opt_1.3"><a href="#">Option-1.3</a></li>\
<li data-id="opt_1.4"><a href="#">Option-1.4<br>\
<small>with second line</small>\
</a></li>\
<li data-id="opt_1.5"><a href="#">Option-1.5</a></li>\
</ul>';
this.clicked = false;
let self = this;
// Create the individual UI elements
// Menu is a UL
this.menu = jQuery(this.default_menu).attr("id", this.internal_ids.menu)
.hide()
.menu({
select: function (event, ui) {
self.onselect.call(self, event, ui.item);
}
});
this.buttons = jQuery(document.createElement("div"))
.addClass("et2_dropdown");
// Main "wrapper" div
this.div = jQuery(document.createElement("div"))
.attr("id", this.internal_ids.div)
.append(this.buttons)
.append(this.menu);
// Left side - activates click action
this.button = jQuery(document.createElement("button"))
.attr("id", this.internal_ids.button)
.attr("type", "button")
.addClass("ui-widget ui-corner-left").removeClass("ui-corner-all")
.appendTo(this.buttons);
// Right side - shows dropdown
this.arrow = jQuery(document.createElement("button"))
.addClass("ui-widget ui-corner-right").removeClass("ui-corner-all")
.attr("type", "button")
.click(function () {
// ignore click on readonly button
if (self.options.readonly)
return false;
// Clicking it again hides menu
if (self.menu.is(":visible")) {
self.menu.hide();
return false;
}
// Show menu dropdown
var menu = self.menu.show().position({
my: "left top",
at: "left bottom",
of: self.buttons
});
// Hide menu if clicked elsewhere
jQuery(document).one("click", function () {
menu.hide();
});
return false;
})
// This is the actual down arrow icon
.append("<div class='ui-icon ui-icon-triangle-1-s'/>")
.appendTo(this.buttons);
// Common button UI
this.buttons.children("button")
.addClass("ui-state-default")
.hover(function () { jQuery(this).addClass("ui-state-hover"); }, function () { jQuery(this).removeClass("ui-state-hover"); });
// Icon
this.image = jQuery(document.createElement("img"));
this.setDOMNode(this.div[0]);
}
destroy() {
// Destroy widget
if (this.menu && this.menu.data('ui-menu'))
this.menu.menu("destroy");
// Null children
this.image = null;
this.button = null;
this.arrow = null;
this.buttons = null;
this.menu = null;
// Remove
this.div.empty().remove();
}
set_id(_id) {
super.set_id(_id);
// Update internal IDs - not really needed since we refer by internal
// javascript reference, but good to keep up to date
this.internal_ids = {
div: this.dom_id + "_wrapper",
button: this.dom_id,
menu: this.dom_id + "_menu"
};
for (let key in this.internal_ids) {
if (this[key] == null)
continue;
this[key].attr("id", this.internal_ids[key]);
}
}
/**
* Set if the button label changes to match the selected option
*
* @param updates boolean Turn updating on or off
*/
set_label_updates(updates) {
this.label_updates = updates;
}
set_accesskey(key) {
jQuery(this.node).attr("accesskey", key);
}
set_ro_image(_image) {
if (this.options.readonly) {
this.set_image(_image);
}
}
set_image(_image) {
if (!this.isInTree() || this.image == null)
return;
if (!_image.trim()) {
this.image.hide();
}
else {
this.image.show();
}
let src = this.egw().image(_image);
if (src) {
this.image.attr("src", src);
}
// allow url's too
else if (_image[0] == '/' || _image.substr(0, 4) == 'http') {
this.image.attr('src', _image);
}
else {
this.image.hide();
}
}
/**
* Overwritten to maintain an internal clicked attribute
*
* @param _ev
* @returns {Boolean}
*/
click(_ev) {
// ignore click on readonly button
if (this.options.readonly)
return false;
this.clicked = true;
if (!super.click(_ev)) {
this.clicked = false;
return false;
}
this.clicked = false;
return true;
}
onselect(event, selected_node) {
this.set_value(selected_node.attr("data-id"));
this.change(selected_node);
}
attachToDOM() {
let res = super.attachToDOM();
// Move the parent's handler to the button, or we can't tell the difference between the clicks
jQuery(this.node).unbind("click.et2_baseWidget");
this.button.off().bind("click.et2_baseWidget", this, function (e) {
return e.data.click.call(e.data, this);
});
return res;
}
set_label(_value) {
if (this.button) {
this.label = _value;
this.button.text(_value)
.prepend(this.image);
}
}
/**
* Set the options for the dropdown
*
* @param options Object ID => Label pairs
*/
set_select_options(options) {
this.menu.first().empty();
// Allow more complicated content, if passed
if (typeof options == "string") {
this.menu.append(options);
}
else {
let add_complex = function (node, options) {
for (let key in options) {
let item;
if (typeof options[key] == "string") {
item = jQuery("<li data-id='" + key + "'><a href='#'>" + options[key] + "</a></li>");
}
else if (options[key]["label"]) {
item = jQuery("<li data-id='" + key + "'><a href='#'>" + options[key]["label"] + "</a></li>");
}
// Optgroup
else {
item = jQuery("<li><a href='#'>" + key + "</a></li>");
add_complex(node.append("<ul>"), options[key]);
}
node.append(item);
if (item && options[key].icon) {
// we supply a applicable class for item images
jQuery('a', item).prepend('<img class="et2_button_icon" src="' + (options[key].icon.match(/^(http|https|\/)/) ? options[key].icon : egw.image(options[key].icon)) + '"/>');
}
}
};
add_complex(this.menu.first(), options);
}
this.menu.menu("refresh");
}
/**
* Set tab index
*/
set_tabindex(index) {
jQuery(this.button).attr("tabindex", index);
}
set_value(new_value) {
let menu_item = jQuery("[data-id='" + new_value + "']", this.menu);
if (menu_item.length) {
this.value = new_value;
if (this.label_updates) {
this.set_label(menu_item.text());
}
}
else {
this.value = null;
if (this.label_updates) {
this.set_label(this.options.label);
}
}
}
getValue() {
return this.value;
}
/**
* Set options.readonly
*
* @param {boolean} _ro
*/
set_readonly(_ro) {
if (_ro != this.options.readonly) {
this.options.readonly = _ro;
// don't make readonly dropdown buttons clickable
if (this.buttons) {
this.buttons.find('button')
.toggleClass('et2_clickable', !_ro)
.toggleClass('et2_button_ro', _ro)
.css('cursor', _ro ? 'default' : 'pointer');
}
}
}
}
et2_dropdown_button.attributes = {
"label": {
"name": "caption",
"type": "string",
"description": "Label of the button",
"translate": true,
"default": "Select..."
},
"label_updates": {
"name": "Label updates",
"type": "boolean",
"description": "Button label updates when an option is selected from the menu",
"default": true
},
"image": {
"name": "Icon",
"type": "string",
"description": "Add an icon"
},
"ro_image": {
"name": "Read-only Icon",
"type": "string",
"description": "Use this icon instead of hiding for read-only"
},
"onclick": {
"description": "JS code which gets executed when the button is clicked"
},
"select_options": {
"type": "any",
"name": "Select options",
"default": {},
"description": "Select options for dropdown. Can be a simple key => value list, or value can be full HTML",
// Skip normal initialization for this one
"ignore": true
},
"accesskey": {
"name": "Access Key",
"type": "string",
"default": et2_no_init,
"description": "Alt + <key> activates widget"
},
"tabindex": {
"name": "Tab index",
"type": "integer",
"default": et2_no_init,
"description": "Specifies the tab order of a widget when the 'tab' button is used for navigating."
},
// No such thing as a required button
"required": {
"ignore": true
}
};
et2_register_widget(et2_dropdown_button, ["dropdown_button"]);
//# sourceMappingURL=et2_widget_dropdown_button.js.map

View File

@ -1,153 +0,0 @@
/**
* EGroupware eTemplate2 - JS Dynheight object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:use
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inheritance;
*/
import { egw } from "../jsapi/egw_global";
/**
* Object which resizes an inner node to the maximum extend of an outer node
* (without creating a scrollbar) - it achieves that by performing some very
* nasty and time consuming calculations.
*/
export class et2_dynheight {
constructor(_outerNode, _innerNode, _minHeight) {
this.initialized = false;
this.minHeight = 0;
this.bottomNodes = [];
this.innerMargin = 0;
this.outerMargin = 0;
this.outerNode = jQuery(_outerNode);
this.innerNode = jQuery(_innerNode);
this.minHeight = _minHeight;
}
destroy() {
this.outerNode = null;
this.innerNode = null;
this.bottomNodes = [];
}
/**
* Resizes the inner node. When this is done, the callback function is
* called.
*
* @param {function} _callback
* @param {object} _context
*/
update(_callback, _context) {
// Check whether the inner node is actually visible - if not, don't
// trigger the callback function
if (this.innerNode.is(":visible")) {
// Initialize the height calculation
this._initialize();
// Get the outer container height and offset, if available
const oh = this.outerNode.height();
const ot = this.outerNode.offset() ? this.outerNode.offset().top : 0;
// Get top and height of the inner node
const it = this.innerNode.offset().top;
// Calculate the height of the "bottomNodes"
let bminTop = this.bottomNodes.length ? Infinity : 0;
let bmaxBot = 0;
for (let i = 0; i < this.bottomNodes.length; i++) {
// Ignore hidden popups
if (this.bottomNodes[i].find('.action_popup').length) {
egw.debug('warn', "Had to skip a hidden popup - it should be removed", this.bottomNodes[i].find('.action_popup'));
continue;
}
// Ignore other hidden nodes
if (!this.bottomNodes[i].is(':visible'))
continue;
// Get height, top and bottom and calculate the maximum/minimum
let bh = this.bottomNodes[i].outerHeight(true);
let bt = this.bottomNodes[i].offset().top;
const bb = bh + bt;
if (i == 0 || bminTop > bt) {
bminTop = bt;
}
if (i == 0 || bmaxBot < bb) {
bmaxBot = bb;
}
}
// Get the height of the bottom container
const bh = Math.max(0, bmaxBot - bminTop);
// Calculate the new height of the inner container
const h = Math.max(this.minHeight, oh + ot - it - bh -
this.innerMargin - this.outerMargin);
this.innerNode.height(h);
// Update the width
// Some checking to make sure it doesn't overflow the width when user
// resizes the window
let w = this.outerNode.width();
if (w > jQuery(window).width()) {
// 50px border, totally arbitrary, but we just need to make sure it's inside
w = jQuery(window).width() - 50;
}
if (w != this.innerNode.outerWidth()) {
this.innerNode.width(w);
}
// Call the callback function
if (typeof _callback != "undefined") {
_callback.call(_context, w, h);
}
}
}
/**
* Function used internally which collects all DOM-Nodes which are located
* below this element.
*
* @param {HTMLElement} _node
* @param {number} _bottom
*/
_collectBottomNodes(_node, _bottom) {
// Calculate the bottom position of the inner node
if (typeof _bottom == "undefined") {
_bottom = this.innerNode.offset().top + this.innerNode.height();
}
if (_node) {
// Accumulate the outer margin of the parent elements
const node = jQuery(_node);
const ooh = node.outerHeight(true);
const oh = node.height();
this.outerMargin += (ooh - oh) / 2; // Divide by 2 as the value contains margin-top and -bottom
// Iterate over the children of the given node and do the same
// recursively to the parent nodes until the _outerNode or body is
// reached.
const self = this;
jQuery(_node).children().each(function () {
const $this = jQuery(this);
const top = $this.offset().top;
if (this != self.innerNode[0] && top >= _bottom) {
self.bottomNodes.push($this);
}
});
if (_node != this.outerNode[0] && _node != jQuery("body")[0]) {
this._collectBottomNodes(_node.parentNode, _bottom);
}
}
}
/**
* Used internally to calculate some information which will not change over
* the time.
*/
_initialize() {
if (!this.initialized) {
// Collect all bottomNodes and calculates the outer margin
this.bottomNodes = [];
this.outerMargin = 0;
this._collectBottomNodes(this.innerNode[0].parentNode);
// Calculate the inner margin
const ioh = this.innerNode.outerHeight(true);
const ih = this.innerNode.height();
this.innerMargin = ioh - ih;
this.initialized = true;
}
}
}
//# sourceMappingURL=et2_widget_dynheight.js.map

View File

@ -1,152 +0,0 @@
/*
* Egroupware etemplate2 JS Entry widget
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
/*egw:uses
et2_core_valueWidget;
*/
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
/**
* A widget to display a value from an entry
*
* Since we have Etemplate\Widget\Transformer, this client side widget exists
* mostly to resolve the problem where the ID for the entry widget is the same
* as the widget where you actually set the value, which prevents transformer
* from working.
*
* Server side will find the associated entry, and load it into ~<entry_id> to
* avoid overwriting the widget with id="entry_id". This widget will reverse
* that, and the modifications from transformer will be applied.
*
* @augments et2_valueWidget
*/
export class et2_entry extends et2_valueWidget {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_entry._attributes, _child || {}));
this.widget = null;
// Often the ID conflicts, so check prefix
if (_attrs.id && _attrs.id.indexOf(et2_entry.prefix) < 0) {
_attrs.id = et2_entry.prefix + _attrs.id;
}
let value = _attrs.value;
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_entry._attributes, _child || {}));
// Save value from parsing, but only if set
if (value) {
this.options.value = value;
}
this.widget = null;
this.setDOMNode(document.createElement('span'));
}
loadFromXML(_node) {
// Load the nodes as usual
super.loadFromXML(_node);
// Do the magic
this.loadField();
}
/**
* Initialize widget for entry field
*/
loadField() {
// Create widget of correct type
let attrs = {
id: this.id + (this.options.field ? '[' + this.options.field + ']' : ''),
type: 'label',
readonly: this.options.readonly
};
let modifications = this.getArrayMgr("modifications");
if (modifications && this.options.field) {
jQuery.extend(attrs, modifications.getEntry(attrs.id));
}
// Supress labels on templates
if (attrs.type == 'template' && this.options.label) {
this.egw().debug('log', "Surpressed label on <" + this.getType() + ' label="' + this.options.label + '" id="' + this.id + '"...>');
this.options.label = '';
}
let widget = et2_createWidget(attrs.type, attrs, this);
// If value is not set, etemplate takes care of everything
// If value was set, find the record explicitly.
if (typeof this.options.value == 'string') {
widget.options.value = this.getArrayMgr('content').getEntry(this.id + '[' + this.options.field + ']') ||
this.getRoot().getArrayMgr('content').getEntry(et2_entry.prefix + this.options.value + '[' + this.options.field + ']');
}
else if (this.options.field && this.options.value && this.options.value[this.options.field]) {
widget.options.value = this.options.value[this.options.field];
}
if (this.options.compare) {
widget.options.value = widget.options.value == this.options.compare ? 'X' : '';
}
if (this.options.alternate_fields) {
let sum = 0;
let fields = this.options.alternate_fields.split(':');
for (let i = 0; i < fields.length; i++) {
let negate = (fields[i][0] == "-");
let value = this.getArrayMgr('content').getEntry(fields[i].replace('-', ''));
sum += typeof value === 'undefined' ? 0 : (parseFloat(value) * (negate ? -1 : 1));
if (value && this.options.field !== 'sum') {
widget.options.value = value;
break;
}
}
if (this.options.field == 'sum') {
if (this.options.precision && jQuery.isNumeric(sum))
sum = parseFloat(sum).toFixed(this.options.precision);
widget.options.value = sum;
}
}
}
}
et2_entry._attributes = {
field: {
'name': 'Fields',
'description': 'Which entry field to display, or "sum" to add up the alternate_fields',
'type': 'string'
},
compare: {
name: 'Compare',
description: 'if given, the selected field is compared with its value and an X is printed on equality, nothing otherwise',
default: et2_no_init,
type: 'string'
},
alternate_fields: {
name: 'Alternate fields',
description: 'colon (:) separated list of alternative fields. The first non-empty one is used if the selected field is empty, (-) used for subtraction',
type: 'string',
default: et2_no_init
},
precision: {
name: 'Decimals to be shown',
description: 'Specifies the number of decimals for sum of alternates, the default is 2',
type: 'string',
default: '2'
},
regex: {
name: 'Regular expression pattern',
description: 'Only used server-side in a preg_replace with regex_replace to modify the value',
default: et2_no_init,
type: 'string'
},
regex_replace: {
name: 'Regular expression replacement pattern',
description: 'Only used server-side in a preg_replace with regex to modify the value',
default: et2_no_init,
type: 'string'
},
value: {
type: 'any'
},
readonly: {
default: true
}
};
et2_entry.legacyOptions = ["field", "compare", "alternate_fields"];
et2_entry.prefix = '~';
et2_register_widget(et2_entry, ["entry", 'contact-value', 'contact-account', 'contact-template', 'infolog-value', 'tracker-value', 'records-value']);
//# sourceMappingURL=et2_widget_entry.js.map

View File

@ -1,331 +0,0 @@
/**
* EGroupware eTemplate2 - JS Favorite widget
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
*/
/*egw:uses
et2_dropdown_button;
et2_extension_nextmatch;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_dropdown_button } from "./et2_widget_dropdown_button";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { egw, egw_getFramework } from "../jsapi/egw_global";
/**
* Favorites widget, designed for use with a nextmatch widget
*
* The primary control is a split/dropdown button. Clicking on the left side of the button filters the
* nextmatch list by the user's default filter. The right side of the button gives a list of
* saved filters, pulled from preferences. Clicking a filter from the dropdown list sets the
* filters as saved.
*
* Favorites can also automatically be shown in the sidebox, using the special ID favorite_sidebox.
* Use the following code to generate the sidebox section:
* display_sidebox($appname,lang('Favorites'),array(
* array(
* 'no_lang' => true,
* 'text'=>'<span id="favorite_sidebox"/>',
* 'link'=>false,
* 'icon' => false
* )
* ));
* This sidebox list will be automatically generated and kept up to date.
*
*
* Favorites are implemented by saving the values for [column] filters. Filters are stored
* in preferences, with the name favorite_<name>. The favorite favorite used for clicking on
* the filter button is stored in nextmatch-<columnselection_pref>-favorite.
*
* @augments et2_dropdown_button
*/
export class et2_favorites extends et2_dropdown_button {
/**
* Constructor
*
* @memberOf et2_favorites
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_favorites._attributes, _child || {}));
// Some convenient variables, used in closures / event handlers
this.header = null;
this.nextmatch = null;
this.favSortedList = null;
this.sidebox_target = null;
// If filter was set server side, we need to remember it until nm is created
this.nm_filter = false;
this.sidebox_target = jQuery("#" + this.options.sidebox_target);
if (this.sidebox_target.length == 0 && egw_getFramework() != null) {
let egw_fw = egw_getFramework();
this.sidebox_target = jQuery("#" + this.options.sidebox_target, egw_fw.sidemenuDiv);
}
// Store array of sorted items
this.favSortedList = ['blank'];
let apps = egw().user('apps');
et2_favorites.is_admin = (typeof apps['admin'] != "undefined");
// Make sure we have an app
if (!this.options.app) {
this.options.app = this.getInstanceManager().app;
}
this.stored_filters = this.load_favorites(this.options.app);
this.preferred = egw.preference(this.options.default_pref, this.options.app);
if (!this.preferred || typeof this.stored_filters[this.preferred] == "undefined") {
this.preferred = "blank";
}
// It helps to have the ID properly set before we get too far
this.set_id(this.id);
this.init_filters(this);
this.menu.addClass("favorites");
// Set the default (button) value
this.set_value(this.preferred, true);
let self = this;
// Add a listener on the radio buttons to set default filter
jQuery(this.menu).on("click", "input:radio", function (event) {
// Don't do the menu
event.stopImmediatePropagation();
// Save as default favorite - used when you click the button
self.egw().set_preference(self.options.app, self.options.default_pref, jQuery(this).val());
self.preferred = jQuery(this).val();
// Update sidebox, if there
if (self.sidebox_target.length) {
jQuery("div.ui-icon-heart", self.sidebox_target)
.replaceWith("<div class='sideboxstar'/>");
jQuery("li[data-id='" + self.preferred + "'] div.sideboxstar", self.sidebox_target)
.replaceWith("<div class='ui-icon ui-icon-heart'/>");
}
// Close the menu
self.menu.hide();
// Some user feedback
self.button.addClass("ui-state-active", 500, "swing", function () {
self.button.removeClass("ui-state-active", 2000);
});
});
//Sort DomNodes of sidebox fav. menu
let sideBoxDOMNodeSort = function (_favSList) {
let favS = jQuery.isArray(_favSList) ? _favSList.slice(0).reverse() : [];
for (let i = 0; i < favS.length; i++) {
self.sidebox_target.children().find('[data-id$="' + favS[i] + '"]').prependTo(self.sidebox_target.children());
}
};
//Add Sortable handler to nm fav. menu
jQuery(this.menu).sortable({
items: 'li:not([data-id$="add"])',
placeholder: 'ui-fav-sortable-placeholder',
delay: 250,
update: function () {
self.favSortedList = jQuery(this).sortable('toArray', { attribute: 'data-id' });
self.egw().set_preference(self.options.app, 'fav_sort_pref', self.favSortedList);
sideBoxDOMNodeSort(self.favSortedList);
}
});
// Add a listener on the delete to remove
this.menu.on("click", "div.ui-icon-trash", app[self.options.app], function () {
// App instance might not be ready yet, so don't bind directly
app[self.options.app].delete_favorite.apply(this, arguments);
})
// Wrap and unwrap because jQueryUI styles use a parent, and we don't want to change the state of the menu item
// Wrap in a span instead of a div because div gets a border
.on("mouseenter", "div.ui-icon-trash", function () { jQuery(this).wrap("<span class='ui-state-active'/>"); })
.on("mouseleave", "div.ui-icon-trash", function () { jQuery(this).unwrap(); });
// Trigger refresh of menu options now that events are registered
// to update sidebox
if (this.sidebox_target.length > 0) {
this.init_filters(this);
}
}
/**
* Load favorites from preferences
*
* @param app String Load favorites from this application
*/
load_favorites(app) {
// Default blank filter
let stored_filters = {
'blank': {
name: this.egw().lang("No filters"),
state: {}
}
};
// Load saved favorites
let preferences = egw.preference("*", app);
for (let pref_name in preferences) {
if (pref_name.indexOf(et2_favorites.PREFIX) == 0 && typeof preferences[pref_name] == 'object') {
let name = pref_name.substr(et2_favorites.PREFIX.length);
stored_filters[name] = preferences[pref_name];
// Keep older favorites working - they used to store nm filters in 'filters',not state
if (preferences[pref_name]["filters"]) {
stored_filters[pref_name]["state"] = preferences[pref_name]["filters"];
}
}
if (pref_name == 'fav_sort_pref') {
this.favSortedList = preferences[pref_name];
//Make sure sorted list is always an array, seems some old fav are not array
if (!jQuery.isArray(this.favSortedList))
this.favSortedList = this.favSortedList.split(',');
}
}
if (typeof stored_filters == "undefined" || !stored_filters) {
stored_filters = {};
}
else {
for (let name in stored_filters) {
if (this.favSortedList.indexOf(name) < 0) {
this.favSortedList.push(name);
}
}
this.egw().set_preference(this.options.app, 'fav_sort_pref', this.favSortedList);
if (this.favSortedList.length > 0) {
let sortedListObj = {};
for (let i = 0; i < this.favSortedList.length; i++) {
if (typeof stored_filters[this.favSortedList[i]] != 'undefined') {
sortedListObj[this.favSortedList[i]] = stored_filters[this.favSortedList[i]];
}
else {
this.favSortedList.splice(i, 1);
this.egw().set_preference(this.options.app, 'fav_sort_pref', this.favSortedList);
}
}
stored_filters = jQuery.extend(sortedListObj, stored_filters);
}
}
return stored_filters;
}
// Create & set filter options for dropdown menu
init_filters(widget, filters) {
if (typeof filters == "undefined") {
filters = this.stored_filters;
}
let options = {};
for (let name in filters) {
options[name] = "<input type='radio' name='" + this.internal_ids.menu + "[button][favorite]' value='" + name + "' title='" +
this.egw().lang('Set as default') + "'/>" +
(filters[name].name != undefined ? filters[name].name : name) +
(filters[name].group != false && !et2_favorites.is_admin || name == 'blank' ? "" :
"<div class='ui-icon ui-icon-trash' title='" + this.egw().lang('Delete') + "'/>");
}
// Only add 'Add current' if we have a nextmatch
if (this.nextmatch) {
options["add"] = "<img src='" + this.egw().image("new") + "'/>" + this.egw().lang('Add current');
}
widget.set_select_options.call(widget, options);
// Set radio to current value
jQuery("input[value='" + this.preferred + "']:radio", this.menu).attr("checked", 1);
}
set_nm_filters(filters) {
if (this.nextmatch) {
this.nextmatch.applyFilters(filters);
}
else {
console.log(filters);
}
}
onclick(node) {
// Apply preferred filter - make sure it's an object, and not a reference
if (this.preferred && this.stored_filters[this.preferred]) {
// use app[appname].setState if available to allow app to overwrite it (eg. change to non-listview in calendar)
if (typeof app[this.options.app] != 'undefined') {
app[this.options.app].setState(this.stored_filters[this.preferred]);
}
else {
this.set_nm_filters(jQuery.extend({}, this.stored_filters[this.preferred].state));
}
}
else {
alert(this.egw().lang("No default set"));
}
}
// Apply the favorite when you pick from the list
change(selected_node) {
this.value = jQuery(selected_node).attr("data-id");
if (this.value == "add" && this.nextmatch) {
// Get current filters
let current_filters = jQuery.extend({}, this.nextmatch.activeFilters);
// Add in extras
for (let extra in this.options.filters) {
// Don't overwrite what nm has, chances are nm has more up-to-date value
if (typeof current_filters == 'undefined') {
current_filters[extra] = this.nextmatch.options.settings[extra];
}
}
// Skip columns for now
delete current_filters.selcolumns;
// Add in application's settings
if (this.filters != true) {
for (let i = 0; i < this.filters.length; i++) {
current_filters[this.options.filters[i]] = this.nextmatch.options.settings[this.options.filters[i]];
}
}
// Call framework
app[this.options.app].add_favorite(current_filters);
// Reset value
this.set_value(this.preferred, true);
}
else if (this.value == 'blank') {
// Reset filters when select no filters
this.set_nm_filters({});
}
}
set_value(filter_name, parent) {
if (parent) {
return super.set_value(filter_name);
}
if (filter_name == 'add')
return false;
app[this.options.app].setState(this.stored_filters[filter_name]);
return false;
}
getValue() {
return null;
}
/**
* Set the nextmatch to filter
* From et2_INextmatchHeader interface
*
* @param {et2_nextmatch} nextmatch
*/
setNextmatch(nextmatch) {
this.nextmatch = nextmatch;
if (this.nm_filter) {
this.set_value(this.nm_filter);
this.nm_filter = false;
}
// Re-generate filter list so we can add 'Add current'
this.init_filters(this);
}
}
et2_favorites._attributes = {
"default_pref": {
"name": "Default preference key",
"type": "string",
"description": "The preference key where default favorite is stored (not the value)"
},
"sidebox_target": {
"name": "Sidebox target",
"type": "string",
"description": "ID of element to insert favorite list into",
"default": "favorite_sidebox"
},
"app": {
"name": "Application",
"type": "string",
"description": "Application to show favorites for"
},
"filters": {
"name": "Extra filters",
"type": "any",
"description": "Array of extra filters to include in the saved favorite"
},
// These are particular to favorites
id: { "default": "favorite" },
label: { "default": "" },
label_updates: { "default": false },
image: { "default": egw().image('fav_filter') },
statustext: { "default": "Favorite queries", "type": "string" }
};
et2_favorites.PREFIX = "favorite_";
et2_register_widget(et2_favorites, ["favorites"]);
//# sourceMappingURL=et2_widget_favorites.js.map

View File

@ -1,621 +0,0 @@
/**
* EGroupware eTemplate2 - JS Number object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
*/
/*egw:uses
et2_core_inputWidget;
api.Resumable.resumable;
*/
import "../Resumable/resumable.js";
import { et2_inputWidget } from "./et2_core_inputWidget";
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
import { et2_vfsSize } from "./et2_widget_vfs";
/**
* Class which implements file upload
*
* @augments et2_inputWidget
*/
export class et2_file extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_file
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_file._attributes, _child || {}));
this.asyncOptions = {};
this.input = null;
this.progress = null;
this.span = null;
this.node = null;
this.input = null;
this.progress = null;
this.span = null;
// Contains all submit buttons need to be disabled during upload process
this.disabled_buttons = jQuery("input[type='submit'], button");
// Make sure it's an object, not an array, or values get lost when sent to server
this.options.value = jQuery.extend({}, this.options.value);
if (!this.options.id) {
console.warn("File widget needs an ID. Used 'file_widget'.");
this.options.id = "file_widget";
}
// Legacy - id ending in [] means multiple
if (this.options.id.substr(-2) == "[]") {
this.options.multiple = true;
}
// If ID ends in /, it's a directory - allow multiple
else if (this.options.id.substr(-1) === "/") {
this.options.multiple = true;
_attrs.multiple = true;
}
// Set up the URL to have the request ID & the widget ID
var instance = this.getInstanceManager();
let self = this;
this.asyncOptions = jQuery.extend({}, this.getAsyncOptions(this));
this.asyncOptions.fieldName = this.options.id;
this.createInputWidget();
this.set_readonly(this.options.readonly);
}
destroy() {
super.destroy();
this.set_drop_target(null);
this.node = null;
this.input = null;
this.span = null;
this.progress = null;
}
createInputWidget() {
this.node = jQuery(document.createElement("div")).addClass("et2_file");
this.span = jQuery(document.createElement("span"))
.addClass('et2_file_span et2_button')
.appendTo(this.node);
if (this.options.label != '')
this.span.addClass('et2_button_text');
let span = this.span;
this.input = jQuery(document.createElement("input"))
.attr("type", "file").attr("placeholder", this.options.blur)
.addClass("et2_file_upload")
.appendTo(this.node)
.hover(function () {
jQuery(span)
.toggleClass('et2_file_spanHover');
})
.on({
mousedown: function () {
jQuery(span).addClass('et2_file_spanActive');
},
mouseup: function () {
jQuery(span).removeClass('et2_file_spanActive');
}
});
if (this.options.accept)
this.input.attr('accept', this.options.accept);
let self = this;
// trigger native input upload file
if (!this.options.readonly)
this.span.click(function () { self.input.click(); });
// Check for File interface, should fall back to normal form submit if missing
if (typeof File != "undefined" && typeof (new XMLHttpRequest()).upload != "undefined") {
this.resumable = new Resumable(this.asyncOptions);
this.resumable.assignBrowse(this.input);
this.resumable.on('fileAdded', jQuery.proxy(this._fileAdded, this));
this.resumable.on('fileProgress', jQuery.proxy(this._fileProgress, this));
this.resumable.on('fileSuccess', jQuery.proxy(this.finishUpload, this));
this.resumable.on('complete', jQuery.proxy(this.onFinish, this));
}
else {
// This may be a problem submitting via ajax
}
if (this.options.progress) {
let widget = this.getRoot().getWidgetById(this.options.progress);
if (widget) {
//may be not available at createInputWidget time
this.progress = jQuery(widget.getDOMNode());
}
}
if (!this.progress) {
this.progress = jQuery(document.createElement("div")).appendTo(this.node);
}
this.progress.addClass("progress");
if (this.options.multiple) {
this.input.attr("multiple", "multiple");
}
this.setDOMNode(this.node[0]);
// set drop target to widget dom node if no target option is specified
if (!this.options.drop_target)
this.resumable.assignDrop([this.getDOMNode()]);
}
/**
* Get any specific async upload options
*/
getAsyncOptions(self) {
return {
// Callbacks
onStart: function (event, file_count) {
return self.onStart(event, file_count);
},
onFinish: function (event, file_count) {
self.onFinish.apply(self, [event, file_count]);
},
onStartOne: function (event, file_name, index, file_count) {
},
onFinishOne: function (event, response, name, number, total) { return self.finishUpload(event, response, name, number, total); },
onProgress: function (event, progress, name, number, total) { return self.onProgress(event, progress, name, number, total); },
onError: function (event, name, error) { return self.onError(event, name, error); },
beforeSend: function (form) { return self.beforeSend(form); },
chunkSize: this.options.chunk_size || 1024 * 1024,
target: egw.ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\File::ajax_upload"),
query: function (file) { return self.beforeSend(file); },
// Disable checking for already uploaded chunks
testChunks: false
};
}
/**
* Set a widget or DOM node as a HTML5 file drop target
*
* @param {string} new_target widget ID or DOM node ID to be used as a new target
*/
set_drop_target(new_target) {
// Cancel old drop target
if (this.options.drop_target) {
let widget = this.getRoot().getWidgetById(this.options.drop_target);
let drop_target = widget && widget.getDOMNode() || document.getElementById(this.options.drop_target);
if (drop_target) {
this.resumable.unAssignDrop(drop_target);
}
}
this.options.drop_target = new_target;
if (!this.options.drop_target)
return;
// Set up new drop target
let widget = this.getRoot().getWidgetById(this.options.drop_target);
let drop_target = widget && widget.getDOMNode() || document.getElementById(this.options.drop_target);
if (drop_target) {
this.resumable.assignDrop([drop_target]);
}
else {
this.egw().debug("warn", "Did not find file drop target %s", this.options.drop_target);
}
}
attachToDOM() {
let res = super.attachToDOM();
// Override parent's change, file widget will fire change when finished uploading
this.input.unbind("change.et2_inputWidget");
return res;
}
getValue() {
return this.options.value ? this.options.value : this.input.val();
}
/**
* Set the value of the file widget.
*
* If you pass a FileList or list of files, it will trigger the async upload
*
* @param {FileList|File[]|false} value List of files to be uploaded, or false to reset.
* @param {Event} event Most browsers require the user to initiate file transfers in some way.
* Pass the event in, if you have it.
*/
set_value(value, event) {
if (!value || typeof value == "undefined") {
value = {};
}
if (jQuery.isEmptyObject(value)) {
this.options.value = {};
if (this.resumable.progress() == 1)
this.progress.empty();
// Reset the HTML element
this.input.wrap('<form>').closest('form').get(0).reset();
this.input.unwrap();
return;
}
let addFile = jQuery.proxy(function (i, file) {
this.resumable.addFile(file, event);
}, this);
if (typeof value == 'object' && value.length && typeof value[0] == 'object' && value[0].name) {
try {
this.input[0].files = value;
jQuery.each(value, addFile);
}
catch (e) {
var self = this;
var args = arguments;
jQuery.each(value, addFile);
}
}
}
/**
* Set the value for label
* The label is used as caption for span tag which customize the HTML file upload styling
*
* @param {string} value text value of label
*/
set_label(value) {
if (this.span != null && value != null) {
this.span.text(value);
}
}
getInputNode() {
if (typeof this.input == 'undefined')
return false;
return this.input[0];
}
set_mime(mime) {
if (!mime) {
this.options.mime = null;
}
if (mime.indexOf("/") != 0) {
// Lower case it now, if it's not a regex
this.options.mime = mime.toLowerCase();
}
else {
this.options.mime = mime;
}
}
set_multiple(_multiple) {
this.options.multiple = _multiple;
if (_multiple) {
return this.input.attr("multiple", "multiple");
}
return this.input.removeAttr("multiple");
}
/**
* Check to see if the provided file's mimetype matches
*
* @param f File object
* @return boolean
*/
checkMime(f) {
if (!this.options.mime)
return true;
let mime = '';
if (this.options.mime.indexOf("/") != 0) {
// Lower case it now, if it's not a regex
mime = this.options.mime.toLowerCase();
}
else {
// Convert into a js regex
var parts = this.options.mime.substr(1).match(/(.*)\/([igm]?)$/);
mime = new RegExp(parts[1], parts.length > 2 ? parts[2] : "");
}
// If missing, let the server handle it
if (!mime || !f.type)
return true;
var is_preg = (typeof mime == "object");
if (!is_preg && f.type.toLowerCase() == mime || is_preg && mime.test(f.type)) {
return true;
}
// Not right mime
return false;
}
_fileAdded(file, event) {
// Manual additions have no event
if (typeof event == 'undefined') {
event = {};
}
// Trigger start of uploading, calls callback
if (!this.resumable.isUploading()) {
if (!(this.onStart(event, this.resumable.files.length)))
return;
}
// Here 'this' is the input
if (this.checkMime(file.file)) {
if (this.createStatus(event, file)) {
// Disable buttons
this.disabled_buttons
.not("[disabled]")
.attr("disabled", true)
.addClass('et2_button_ro')
.removeClass('et2_clickable')
.css('cursor', 'default');
// Actually start uploading
this.resumable.upload();
}
}
else {
// Wrong mime type - show in the list of files
return this.createStatus(this.egw().lang("File is of wrong type (%1 != %2)!", file.file.type, this.options.mime), file);
}
}
/**
* Add in the request id
*/
beforeSend(form) {
var instance = this.getInstanceManager();
return {
request_id: instance.etemplate_exec_id,
widget_id: this.id
};
}
/**
* Disables submit buttons while uploading
*/
onStart(event, file_count) {
// Hide any previous errors
this.hideMessage();
event.data = this;
//Add dropdown_progress
if (this.options.progress_dropdownlist) {
this._build_progressDropDownList();
}
// Callback
if (this.options.onStart)
return et2_call(this.options.onStart, event, file_count);
return true;
}
/**
* Re-enables submit buttons when done
*/
onFinish() {
this.disabled_buttons.removeAttr("disabled").css('cursor', 'pointer').removeClass('et2_button_ro');
var file_count = this.resumable.files.length;
// Remove files from list
while (this.resumable.files.length > 0) {
this.resumable.removeFile(this.resumable.files[this.resumable.files.length - 1]);
}
var event = jQuery.Event('upload');
event.data = this;
var result = false;
//Remove progress_dropDown_fileList class and unbind the click handler from body
if (this.options.progress_dropdownlist) {
this.progress.removeClass("progress_dropDown_fileList");
jQuery(this.node).find('span').removeClass('totalProgress_loader');
jQuery('body').off('click');
}
if (this.options.onFinish && !jQuery.isEmptyObject(this.getValue())) {
result = et2_call(this.options.onFinish, event, file_count);
}
else {
result = (file_count == 0 || !jQuery.isEmptyObject(this.getValue()));
}
if (result) {
// Fire legacy change action when done
this.change(this.input);
}
}
/**
* Build up dropdown progress with total count indicator
*
* @todo Implement totalProgress bar instead of ajax-loader, in order to show how much percent of uploading is completed
*/
_build_progressDropDownList() {
this.progress.addClass("progress_dropDown_fileList");
//Add uploading indicator and bind hover handler on it
jQuery(this.node).find('span').addClass('totalProgress_loader');
jQuery(this.node).find('span.et2_file_span').hover(function () {
jQuery('.progress_dropDown_fileList').show();
});
//Bind click handler to dismiss the dropdown while uploading
jQuery('body').on('click', function (event) {
if (event.target.className != 'remove') {
jQuery('.progress_dropDown_fileList').hide();
}
});
}
/**
* Creates the elements used for displaying the file, and it's upload status, and
* attaches them to the DOM
*
* @param _event Either the event, or an error message
*/
createStatus(_event, file) {
var error = (typeof _event == "object" ? "" : _event);
if (this.options.max_file_size && file.size > this.options.max_file_size) {
error = this.egw().lang("File too large. Maximum %1", et2_vfsSize.prototype.human_size(this.options.max_file_size));
}
if (this.options.progress) {
var widget = this.getRoot().getWidgetById(this.options.progress);
if (widget) {
this.progress = jQuery(widget.getDOMNode());
this.progress.addClass("progress");
}
}
if (this.progress) {
var fileName = file.fileName || 'file';
var status = jQuery("<li data-file='" + fileName.replace(/'/g, '&quot') + "'>" + fileName
+ "<div class='remove'/><span class='progressBar'><p/></span></li>")
.appendTo(this.progress);
jQuery("div.remove", status).on('click', file, jQuery.proxy(this.cancel, this));
if (error != "") {
status.addClass("message ui-state-error");
status.append("<div>" + error + "</diff>");
jQuery(".progressBar", status).css("display", "none");
}
}
return error == "";
}
_fileProgress(file) {
if (this.progress) {
jQuery("li[data-file='" + file.fileName.replace(/'/g, '&quot') + "'] > span.progressBar > p").css("width", Math.ceil(file.progress() * 100) + "%");
}
return true;
}
onError(event, name, error) {
console.warn(event, name, error);
}
/**
* A file upload is finished, update the UI
*/
finishUpload(file, response) {
var name = file.fileName || 'file';
if (typeof response == 'string')
response = jQuery.parseJSON(response);
if (response.response[0] && typeof response.response[0].data.length == 'undefined') {
if (typeof this.options.value !== 'object' || !this.options.multiple) {
this.set_value({});
}
for (var key in response.response[0].data) {
if (typeof response.response[0].data[key] == "string") {
// Message from server - probably error
jQuery("[data-file='" + name.replace(/'/g, '&quot') + "']", this.progress)
.addClass("error")
.css("display", "block")
.text(response.response[0].data[key]);
}
else {
this.options.value[key] = response.response[0].data[key];
// If not multiple, we already destroyed the status, so re-create it
if (!this.options.multiple) {
this.createStatus({}, file);
}
if (this.progress) {
jQuery("[data-file='" + name.replace(/'/g, '&quot') + "']", this.progress).addClass("message success");
}
}
}
}
else if (this.progress) {
jQuery("[data-file='" + name.replace(/'/g, '&quot') + "']", this.progress)
.addClass("ui-state-error")
.css("display", "block")
.text(this.egw().lang("Server error"));
}
var event = jQuery.Event('upload');
event.data = this;
// Callback
if (typeof this.onFinishOne == 'function') {
this.onFinishOne(event, response, name);
}
return true;
}
/**
* Remove a file from the list of values
*
* @param {File|string} File object, or file name, to remove
*/
remove_file(file) {
//console.info(filename);
if (typeof file == 'string') {
file = { fileName: file };
}
for (var key in this.options.value) {
if (this.options.value[key].name == file.fileName) {
delete this.options.value[key];
jQuery('[data-file="' + file.fileName.replace(/'/g, '&quot') + '"]', this.node).remove();
return;
}
}
if (file.isComplete && !file.isComplete() && file.cancel)
file.cancel();
}
/**
* Cancel a file - event callback
*/
cancel(e) {
e.preventDefault();
// Look for file name in list
var target = jQuery(e.target).parents("li");
this.remove_file(e.data);
// In case it didn't make it to the list (error)
target.remove();
jQuery(e.target).remove();
}
/**
* Set readonly
*
* @param {boolean} _ro boolean readonly state, true means readonly
*/
set_readonly(_ro) {
if (typeof _ro != "undefined") {
this.options.readonly = _ro;
this.span.toggleClass('et2_file_ro', _ro);
if (this.options.readonly) {
this.span.unbind('click');
}
else {
var self = this;
this.span.off().bind('click', function () { self.input.click(); });
}
}
}
}
et2_file._attributes = {
"multiple": {
"name": "Multiple files",
"type": "boolean",
"default": false,
"description": "Allow the user to select more than one file to upload at a time. Subject to browser support."
},
"max_file_size": {
"name": "Maximum file size",
"type": "integer",
"default": 0,
"description": "Largest file accepted, in bytes. Subject to server limitations. 8MB = 8388608"
},
"mime": {
"name": "Allowed file types",
"type": "string",
"default": et2_no_init,
"description": "Mime type (eg: image/png) or regex (eg: /^text\//i) for allowed file types"
},
"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."
},
"progress": {
"name": "Progress node",
"type": "string",
"default": et2_no_init,
"description": "The ID of an alternate node (div) to display progress and results. The Node is fetched with et2 getWidgetById so you MUST use the id assigned in XET-File (it may not be available at creation time, so we (re)check on createStatus time)"
},
"onStart": {
"name": "Start event handler",
"type": "any",
"default": et2_no_init,
"description": "A (js) function called when an upload starts. Return true to continue with upload, false to cancel."
},
"onFinish": {
"name": "Finish event handler",
"type": "any",
"default": et2_no_init,
"description": "A (js) function called when all files to be uploaded are finished."
},
drop_target: {
"name": "Optional, additional drop target for HTML5 uploads",
"type": "string",
"default": et2_no_init,
"description": "The ID of an additional drop target for HTML5 drag-n-drop file uploads"
},
label: {
"name": "Label of file upload",
"type": "string",
"default": "Choose file...",
"description": "String caption to be displayed on file upload span"
},
progress_dropdownlist: {
"name": "List on files in progress like dropdown",
"type": "boolean",
"default": false,
"description": "Style list of files in uploading progress like dropdown list with a total upload progress indicator"
},
onFinishOne: {
"name": "Finish event handler for each one",
"type": "js",
"default": et2_no_init,
"description": "A (js) function called when a file to be uploaded is finished."
},
accept: {
"name": "Acceptable extensions",
"type": "string",
"default": '',
"description": "Define types of files that the server accepts. Multiple types can be seperated by comma and the default is to accept everything."
},
chunk_size: {
"name": "Chunk size",
"type": "integer",
"default": 1024 * 1024,
"description": "Max chunk size, gets set from server-side PHP (max_upload_size-1M)/2" // last chunk can be up to 2*chunk_size!
}
};
et2_register_widget(et2_file, ["file"]);
//# sourceMappingURL=et2_widget_file.js.map

View File

@ -1,910 +0,0 @@
/**
* EGroupware eTemplate2 - JS Grid object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_DOMWidget;
et2_core_xml;
*/
import { et2_no_init } from "./et2_core_common";
import { et2_register_widget, et2_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_action_object_impl, et2_DOMWidget } from "./et2_core_DOMWidget";
import { egw_getAppObjectManager, egwActionObject } from '../egw_action/egw_action.js';
import { et2_directChildrenByTagName, et2_filteredNodeIterator, et2_readAttrWithDefault } from "./et2_core_xml";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "grid" XET-Tag
*
* This also includes repeating the last row in the grid and filling
* it with content data
*
* @augments et2_DOMWidget
*/
export class et2_grid extends et2_DOMWidget {
/**
* Constructor
*
* @memberOf et2_grid
*/
constructor(_parent, _attrs, _child) {
// Call the parent constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_grid._attributes, _child || {}));
// Counters for rows and columns
this.rowCount = 0;
this.columnCount = 0;
// 2D-Array which holds references to the DOM td tags
this.cells = [];
this.rowData = [];
this.colData = [];
this.managementArray = [];
// Keep the template node for later regeneration
this.template_node = null;
// Wrapper div for height & overflow, if needed
this.wrapper = null;
// Create the table body and the table
this.table = jQuery(document.createElement("table"))
.addClass("et2_grid");
this.thead = jQuery(document.createElement("thead"))
.appendTo(this.table);
this.tfoot = jQuery(document.createElement("tfoot"))
.appendTo(this.table);
this.tbody = jQuery(document.createElement("tbody"))
.appendTo(this.table);
}
_initCells(_colData, _rowData) {
// Copy the width and height
const w = _colData.length;
const h = _rowData.length;
// Create the 2D-Cells array
const cells = new Array(h);
for (let y = 0; y < h; y++) {
cells[y] = new Array(w);
// Initialize the cell description objects
for (let x = 0; x < w; x++) {
// Some columns (nm) we do not parse into a boolean
const col_disabled = _colData[x].disabled;
cells[y][x] = {
"td": null,
"widget": null,
"colData": _colData[x],
"rowData": _rowData[y],
"disabled": col_disabled || _rowData[y].disabled,
"class": _colData[x]["class"],
"colSpan": 1,
"autoColSpan": false,
"rowSpan": 1,
"autoRowSpan": false,
"width": _colData[x].width,
"x": x,
"y": y
};
}
}
return cells;
}
_getColDataEntry() {
return {
width: "auto",
class: "",
align: "",
span: "1",
disabled: false
};
}
_getRowDataEntry() {
return {
height: "auto",
class: "",
valign: "top",
span: "1",
disabled: false
};
}
_getCell(_cells, _x, _y) {
if ((0 <= _y) && (_y < _cells.length)) {
const row = _cells[_y];
if ((0 <= _x) && (_x < row.length)) {
return row[_x];
}
}
throw ("Error while accessing grid cells, invalid element count or span value!");
}
_forceNumber(_val) {
if (isNaN(_val)) {
throw (_val + " is not a number!");
}
return parseInt(_val);
}
_fetchRowColData(columns, rows, colData, rowData) {
// Some things cannot be done inside a nextmatch - nm will do the expansion later
var nm = false;
let widget = this;
while (!nm && widget != this.getRoot()) {
nm = (widget.getType() == 'nextmatch');
widget = widget.getParent();
}
// Parse the columns tag
et2_filteredNodeIterator(columns, function (node, nodeName) {
const colDataEntry = this._getColDataEntry();
// This cannot be done inside a nm, it will expand it later
colDataEntry["disabled"] = nm ?
et2_readAttrWithDefault(node, "disabled", "") :
this.getArrayMgr("content")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
if (nodeName == "column") {
colDataEntry["width"] = et2_readAttrWithDefault(node, "width", "auto");
colDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
colDataEntry["align"] = et2_readAttrWithDefault(node, "align", "");
colDataEntry["span"] = et2_readAttrWithDefault(node, "span", "1");
// Keep any others attributes set, there's no 'column' widget
for (let i in node.attributes) {
const attr = node.attributes[i];
if (attr.nodeType == 2 && typeof colDataEntry[attr.nodeName] == 'undefined') {
colDataEntry[attr.nodeName] = attr.value;
}
}
}
else {
colDataEntry["span"] = "all";
}
colData.push(colDataEntry);
}, this);
// Parse the rows tag
et2_filteredNodeIterator(rows, function (node, nodeName) {
const rowDataEntry = this._getRowDataEntry();
rowDataEntry["disabled"] = this.getArrayMgr("content")
.parseBoolExpression(et2_readAttrWithDefault(node, "disabled", ""));
if (nodeName == "row") {
// Remember this row for auto-repeat - it'll eventually be the last one
this.lastRowNode = node;
rowDataEntry["height"] = et2_readAttrWithDefault(node, "height", "auto");
rowDataEntry["class"] = et2_readAttrWithDefault(node, "class", "");
rowDataEntry["valign"] = et2_readAttrWithDefault(node, "valign", "");
rowDataEntry["span"] = et2_readAttrWithDefault(node, "span", "1");
rowDataEntry["part"] = et2_readAttrWithDefault(node, "part", "body");
const id = et2_readAttrWithDefault(node, "id", "");
if (id) {
rowDataEntry["id"] = id;
}
}
else {
rowDataEntry["span"] = "all";
}
rowData.push(rowDataEntry);
}, this);
// Add in repeated rows
// TODO: It would be nice if we could skip header (thead) & footer (tfoot) or treat them separately
let rowIndex = Infinity;
if (this.getArrayMgr("content")) {
const content = this.getArrayMgr("content");
var rowDataEntry = rowData[rowData.length - 1];
rowIndex = rowData.length - 1;
// Find out if we have any content rows, and how many
let cont = true;
while (cont) {
if (content.data[rowIndex]) {
rowData[rowIndex] = jQuery.extend({}, rowDataEntry);
rowIndex++;
}
else if (this.lastRowNode != null) {
// Have to look through actual widgets to support const[$row]
// style names - should be avoided so we can remove this extra check
// Old etemplate checked first two widgets, or first two box children
// This cannot be done inside a nextmatch - nm will do the expansion later
var nm = false;
if (nm) {
// No further checks for repeated rows
break;
}
// Not in a nextmatch, so we can expand with abandon
const currentPerspective = jQuery.extend({}, content.perspectiveData);
const check = function (node, nodeName) {
if (nodeName == 'box' || nodeName == 'hbox' || nodeName == 'vbox') {
return et2_filteredNodeIterator(node, check, this);
}
content.perspectiveData.row = rowIndex;
for (let attr in node.attributes) {
const value = et2_readAttrWithDefault(node, node.attributes[attr].name, "");
// Don't include first char, those should be handled by normal means
// and it would break nextmatch
if (value.indexOf('@') > 0 || value.indexOf('$') > 0) {
// Ok, we found something. How many? Check for values.
let ident = content.expandName(value);
// expandName() handles index into content (@), but we have to look up
// regular values
if (value[0] != '@') {
// Returns null if there isn't an actual value
ident = content.getEntry(ident, false, true);
}
while (ident != null && rowIndex < 1000) {
rowData[rowIndex] = jQuery.extend({}, rowDataEntry);
content.perspectiveData.row = ++rowIndex;
ident = content.expandName(value);
if (value[0] != '@') {
// Returns null if there isn't an actual value
ident = content.getEntry(ident, false, true);
}
}
if (rowIndex >= 1000) {
egw.debug("error", "Problem in autorepeat fallback: too many rows for '%s'. Use a nextmatch, or start debugging.", value);
}
return;
}
}
};
et2_filteredNodeIterator(this.lastRowNode, check, this);
cont = false;
content.perspectiveData = currentPerspective;
}
else {
// No more rows, stop
break;
}
}
}
if (rowIndex <= rowData.length - 1) {
// No auto-repeat
this.lastRowNode = null;
}
}
_fillCells(cells, columns, rows) {
const h = cells.length;
const w = (h > 0) ? cells[0].length : 0;
const currentPerspective = jQuery.extend({}, this.getArrayMgr("content").perspectiveData);
// Read the elements inside the columns
let x = 0;
et2_filteredNodeIterator(columns, function (node, nodeName) {
function _readColNode(node, nodeName) {
if (y >= h) {
this.egw().debug("warn", "Skipped grid cell in column, '" +
nodeName + "'");
return;
}
const cell = this._getCell(cells, x, y);
// Read the span value of the element
if (node.getAttribute("span")) {
cell.rowSpan = node.getAttribute("span");
}
else {
cell.rowSpan = cell.colData["span"];
cell.autoRowSpan = true;
}
if (cell.rowSpan == "all") {
cell.rowSpan = cells.length;
}
const span = cell.rowSpan = this._forceNumber(cell.rowSpan);
// Create the widget
const widget = this.createElementFromNode(node, nodeName);
// Fill all cells the widget is spanning
for (let i = 0; i < span && y < cells.length; i++, y++) {
this._getCell(cells, x, y).widget = widget;
}
}
// If the node is a column, create the widgets which belong into
// the column
var y = 0;
if (nodeName == "column") {
et2_filteredNodeIterator(node, _readColNode, this);
}
else {
_readColNode.call(this, node, nodeName);
}
x++;
}, this);
// Read the elements inside the rows
var y = 0;
x = 0;
let readRowNode;
let nm = false;
var widget = this;
while (!nm && widget != this.getRoot()) {
nm = (widget.getType() == 'nextmatch');
widget = widget.getParent();
}
et2_filteredNodeIterator(rows, function (node, nodeName) {
readRowNode = function _readRowNode(node, nodeName) {
if (x >= w) {
if (nodeName != "description") {
// Only notify it skipping other than description,
// description used to pad
this.egw().debug("warn", "Skipped grid cell in row, '" +
nodeName + "'");
}
return;
}
let cell = this._getCell(cells, x, y);
// Read the span value of the element
if (node.getAttribute("span")) {
cell.colSpan = node.getAttribute("span");
}
else {
cell.colSpan = cell.rowData["span"];
cell.autoColSpan = true;
}
if (cell.colSpan == "all") {
cell.colSpan = cells[y].length;
}
const span = cell.colSpan = this._forceNumber(cell.colSpan);
// Read the align value of the element
if (node.getAttribute("align")) {
cell.align = node.getAttribute("align");
}
// store id of nextmatch-*headers, so it is available for disabled widgets, which get not instanciated
if (nodeName.substr(0, 10) == 'nextmatch-') {
cell.nm_id = node.getAttribute('id');
}
// Apply widget's class to td, for backward compatability
if (node.getAttribute("class")) {
cell.class += (cell.class ? " " : "") + node.getAttribute("class");
}
// Create the element
if (!cell.disabled || cell.disabled && typeof cell.disabled === 'string') {
//Skip if it is a nextmatch while the nextmatch handles row adjustment by itself
if (!nm) {
// Adjust for the row
const mgrs = this.getArrayMgrs();
for (let name in mgrs) {
this.getArrayMgr(name).perspectiveData.row = y;
}
if (this._getCell(cells, x, y).rowData.id) {
this._getCell(cells, x, y).rowData.id = this.getArrayMgr("content").expandName(this._getCell(cells, x, y).rowData.id);
}
if (this._getCell(cells, x, y).rowData.class) {
this._getCell(cells, x, y).rowData.class = this.getArrayMgr("content").expandName(this._getCell(cells, x, y).rowData.class);
}
}
if (!nm && typeof cell.disabled === 'string') {
cell.disabled = this.getArrayMgr("content").parseBoolExpression(cell.disabled);
}
if (nm || !cell.disabled) {
var widget = this.createElementFromNode(node, nodeName);
}
}
// Fill all cells the widget is spanning
for (let i = 0; i < span && x < cells[y].length; i++, x++) {
cell = this._getCell(cells, x, y);
if (cell.widget == null) {
cell.widget = widget;
}
else {
throw ("Grid cell collision, two elements " +
"defined for cell (" + x + "," + y + ")!");
}
}
};
// If the node is a row, create the widgets which belong into
// the row
x = 0;
if (this.lastRowNode && node == this.lastRowNode) {
return;
}
if (nodeName == "row") {
// Adjust for the row
for (var name in this.getArrayMgrs()) {
//this.getArrayMgr(name).perspectiveData.row = y;
}
let cell = this._getCell(cells, x, y);
if (cell.rowData.id) {
this.getArrayMgr("content").expandName(cell.rowData.id);
}
// If row disabled, just skip it
let disabled = false;
if (node.getAttribute("disabled") == "1") {
disabled = true;
}
if (!disabled) {
et2_filteredNodeIterator(node, readRowNode, this);
}
}
else {
readRowNode.call(this, node, nodeName);
}
y++;
}, this);
// Extra content rows
for (y; y < h; y++) {
x = 0;
et2_filteredNodeIterator(this.lastRowNode, readRowNode, this);
}
// Reset
for (var name in this.getArrayMgrs()) {
this.getArrayMgr(name).perspectiveData = currentPerspective;
}
}
_expandLastCells(_cells) {
const h = _cells.length;
const w = (h > 0) ? _cells[0].length : 0;
// Determine the last cell in each row and expand its span value if
// the span has not been explicitly set.
for (var y = 0; y < h; y++) {
for (var x = w - 1; x >= 0; x--) {
var cell = _cells[y][x];
if (cell.widget != null) {
if (cell.autoColSpan) {
cell.colSpan = w - x;
}
break;
}
}
}
// Determine the last cell in each column and expand its span value if
// the span has not been explicitly set.
for (var x = 0; x < w; x++) {
for (var y = h - 1; y >= 0; y--) {
var cell = _cells[y][x];
if (cell.widget != null) {
if (cell.autoRowSpan) {
cell.rowSpan = h - y;
}
break;
}
}
}
}
_createNamespace() {
return true;
}
/**
* As the does not fit very well into the default widget structure, we're
* overwriting the loadFromXML function and doing a two-pass reading -
* in the first step the
*
* @param {object} _node xml node to process
*/
loadFromXML(_node) {
// Keep the node for later changing / reloading
this.template_node = _node;
// Get the columns and rows tag
const rowsElems = et2_directChildrenByTagName(_node, "rows");
const columnsElems = et2_directChildrenByTagName(_node, "columns");
if (rowsElems.length == 1 && columnsElems.length == 1) {
const columns = columnsElems[0];
const rows = rowsElems[0];
const colData = [];
const rowData = [];
// Fetch the column and row data
this._fetchRowColData(columns, rows, colData, rowData);
// Initialize the cells
const cells = this._initCells(colData, rowData);
// Create the widgets inside the cells and read the span values
this._fillCells(cells, columns, rows);
// Expand the span values of the last cells
this._expandLastCells(cells);
// Create the table rows
this.createTableFromCells(cells, colData, rowData);
}
else {
throw ("Error while parsing grid, none or multiple rows or columns tags!");
}
}
createTableFromCells(_cells, _colData, _rowData) {
this.managementArray = [];
this.cells = _cells;
this.colData = _colData;
this.rowData = _rowData;
// Set the rowCount and columnCount variables
const h = this.rowCount = _cells.length;
const w = this.columnCount = (h > 0) ? _cells[0].length : 0;
// Create the table rows.
for (let y = 0; y < h; y++) {
let parent = this.tbody;
switch (this.rowData[y]["part"]) {
case 'header':
if (!this.tbody.children().length && !this.tfoot.children().length) {
parent = this.thead;
}
break;
case 'footer':
if (!this.tbody.children().length) {
parent = this.tfoot;
}
break;
}
const tr = jQuery(document.createElement("tr")).appendTo(parent)
.addClass(this.rowData[y]["class"]);
if (this.rowData[y].disabled) {
tr.hide();
}
if (this.rowData[y].height != "auto") {
tr.height(this.rowData[y].height);
}
if (this.rowData[y].valign) {
tr.attr("valign", this.rowData[y].valign);
}
if (this.rowData[y].id) {
tr.attr("id", this.rowData[y].id);
}
// Create the cells. x is incremented by the colSpan value of the
// cell.
for (let x = 0; x < w;) {
// Fetch a cell from the cells
const cell = this._getCell(_cells, x, y);
if (cell.td == null && cell.widget != null) {
// Create the cell
const td = jQuery(document.createElement("td")).appendTo(tr)
.addClass(cell["class"]);
if (cell.disabled) {
td.hide();
cell.widget.options.disabled = cell.disabled;
}
if (cell.width != "auto") {
td.width(cell.width);
}
if (cell.align) {
td.attr("align", cell.align);
}
// Add the entry for the widget to the management array
this.managementArray.push({
"cell": td[0],
"widget": cell.widget,
"disabled": cell.disabled
});
// Set the span values of the cell
const cs = (x == w - 1) ? w - x : Math.min(w - x, cell.colSpan);
const rs = (y == h - 1) ? h - y : Math.min(h - y, cell.rowSpan);
// Set the col and row span values
if (cs > 1) {
td.attr("colspan", cs);
}
if (rs > 1) {
td.attr("rowspan", rs);
}
// Assign the td to the cell
for (let sx = x; sx < x + cs; sx++) {
for (let sy = y; sy < y + rs; sy++) {
this._getCell(_cells, sx, sy).td = td;
}
}
x += cell.colSpan;
}
else {
x++;
}
}
}
}
getDOMNode(_sender) {
// If the parent class functions are asking for the DOM-Node, return the
// outer table.
if (_sender == this || typeof _sender == 'undefined') {
return this.wrapper != null ? this.wrapper[0] : this.table[0];
}
// Check whether the _sender object exists inside the management array
for (let i = 0; i < this.managementArray.length; i++) {
if (this.managementArray[i].widget == _sender) {
return this.managementArray[i].cell;
}
}
return null;
}
isInTree(_sender) {
let vis = true;
if (typeof _sender != "undefined" && _sender != this) {
vis = false;
// Check whether the _sender object exists inside the management array
for (let i = 0; i < this.managementArray.length; i++) {
if (this.managementArray[i].widget == _sender) {
vis = !(typeof this.managementArray[i].disabled === 'boolean' ?
this.managementArray[i].disabled :
this.getArrayMgr("content").parseBoolExpression(this.managementArray[i].disabled));
break;
}
}
}
return super.isInTree(this, vis);
}
/**
* Set the overflow attribute
*
* Grid needs special handling because HTML tables don't do overflow. We
* create a wrapper DIV to handle it.
* No value or default visible needs no wrapper, as table is always overflow visible.
*
* @param {string} _value Overflow value, must be a valid CSS overflow value, default 'visible'
*/
set_overflow(_value) {
let wrapper = this.wrapper || this.table.parent('[id$="_grid_wrapper"]');
this.overflow = _value;
if (wrapper.length == 0 && _value && _value !== 'visible') {
this.wrapper = wrapper = this.table.wrap('<div id="' + this.id + '_grid_wrapper"></div>').parent();
if (this.height) {
wrapper.css('height', this.height);
}
}
wrapper.css('overflow', _value);
if (wrapper.length && (!_value || _value === 'visible')) {
this.table.unwrap();
}
}
/**
* Change the content for the grid, and re-generate its contents.
*
* Changing the content does not allow changing the structure of the grid,
* as that is loaded from the template file. The rows and widgets inside
* will be re-created (including auto-repeat).
*
* @param {Object} _value New data for the grid
* @param {Object} [_value.content] New content
* @param {Object} [_value.sel_options] New select options
* @param {Object} [_value.readonlys] New read-only values
*/
set_value(_value) {
// Destroy children, empty grid
for (let i = 0; i < this.managementArray.length; i++) {
const cell = this.managementArray[i];
if (cell.widget) {
cell.widget.destroy();
}
}
this.managementArray = [];
this.thead.empty();
this.tfoot.empty();
this.tbody.empty();
// Update array managers
for (let key in _value) {
this.getArrayMgr(key).data = _value[key];
}
// Rebuild grid
this.loadFromXML(this.template_node);
// New widgets need to finish
let promises = [];
this.loadingFinished(promises);
}
/**
* Sortable allows you to reorder grid rows using the mouse.
* The new order is returned as part of the value of the
* grid, in 'sort_order'.
*
* @param {boolean|function} sortable Callback or false to disable
*/
set_sortable(sortable) {
const $node = jQuery(this.getDOMNode());
if (!sortable) {
$node.sortable("destroy");
return;
}
// Make sure rows have IDs, so sortable has something to return
jQuery('tr', this.tbody).each(function (index) {
const $this = jQuery(this);
// Header does not participate in sorting
if ($this.hasClass('th'))
return;
// If row doesn't have an ID, assign the index as ID
if (!$this.attr("id"))
$this.attr("id", index);
});
const self = this;
// Set up sortable
$node.sortable({
// Header does not participate in sorting
items: "> tbody > tr:not(.th)",
distance: 15,
cancel: this.options.sortable_cancel,
placeholder: this.options.sortable_placeholder,
containment: this.options.sortable_containment,
connectWith: this.options.sortable_connectWith,
update: function (event, ui) {
self.egw().json(sortable, [
self.getInstanceManager().etemplate_exec_id,
$node.sortable("toArray"),
self.id
], null, self, true).sendRequest();
},
receive: function (event, ui) {
if (typeof self.sortable_recieveCallback == 'function') {
self.sortable_recieveCallback.call(self, event, ui, self.id);
}
},
start: function (event, ui) {
if (typeof self.options.sortable_startCallback == 'function') {
self.options.sortable_startCallback.call(self, event, ui, self.id);
}
}
});
}
/**
* Override parent to apply actions on each row
*
* @param {array} actions [ {ID: attributes..}+] as for set_actions
*/
_link_actions(actions) {
// Get the top level element for the tree
// get appObjectManager for the actual app, it might not always be the current app(e.g. running app content under admin tab)
// @ts-ignore
let objectManager = egw_getAppObjectManager(true, this.getInstanceManager().app);
objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId, 2) || objectManager;
let widget_object = objectManager.getObjectById(this.id);
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = objectManager.insertObject(false, new egwActionObject(this.id, objectManager, new et2_action_object_impl(this).getAOI(), this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager));
}
// Delete all old objects
widget_object.clear();
// Go over the widget & add links - this is where we decide which actions are
// 'allowed' for this widget at this time
const action_links = this._get_action_links(actions);
// Deal with each row in tbody, ignore action-wise rows in thead or tfooter for now
let i = 0, r = 0;
for (; i < this.rowData.length; i++) {
if (this.rowData[i].part != 'body')
continue;
const content = this.getArrayMgr('content').getEntry(i);
if (content) {
// Add a new action object to the object manager
const row = jQuery('tr', this.tbody)[r];
const aoi = new et2_action_object_impl(this, row).getAOI();
const obj = widget_object.addObject(content.id || "row_" + r, aoi);
// Set the data to the content so it's available for the action
obj.data = content;
obj.updateActionLinks(action_links);
}
r++;
}
}
/**
* Code for implementing et2_IDetachedDOM
* This doesn't need to be implemented.
* Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
*
* @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
}
getDetachedNodes() {
return [this.getDOMNode()];
}
setDetachedAttributes(_nodes, _values) {
}
/**
* Generates nextmatch column name for headers in a grid
*
* Implemented here as default implementation in et2_externsion_nextmatch
* only considers children, but grid does NOT instanciate disabled rows as children.
*
* @return {string}
*/
_getColumnName() {
const ids = [];
for (let r = 0; r < this.cells.length; ++r) {
const cols = this.cells[r];
for (let c = 0; c < cols.length; ++c) {
if (cols[c].nm_id)
ids.push(cols[c].nm_id);
}
}
return ids.join('_');
}
resize(_height) {
if (typeof this.options != 'undefined' && _height
&& typeof this.options.resize_ratio != 'undefined' && this.options.resize_ratio) {
// apply the ratio
_height = (this.options.resize_ratio != '') ? _height * this.options.resize_ratio : _height;
if (_height != 0) {
if (this.wrapper) {
this.wrapper.height(this.wrapper.height() + _height);
}
else {
this.table.height(this.table.height() + _height);
}
}
}
}
/**
* Get a dummy row object containing all widget of a row
*
* This is only a temp. solution until rows are implemented as eT2 containers and
* _sender.getParent() will return a real row container.
*
* @deprecated Not used? Remove this if you find something using it
*
* @param {et2_widget} _sender
* @returns {Array|undefined}
*/
getRow(_sender) {
if (!_sender || !this.cells)
return;
for (let r = 0; r < this.cells.length; ++r) {
const row = this.cells[r];
for (var c = 0; c < row.length; ++c) {
if (!row[c].widget)
continue;
let found = row[c].widget === _sender;
if (!found)
row[c].widget.iterateOver(function (_widget) { if (_widget === _sender)
found = true; });
if (found) {
// return a fake row object allowing to iterate over it's children
const row_obj = new et2_widget(this, {});
for (var c = 0; c < row.length; ++c) {
if (row[c].widget)
row_obj.addChild(row[c].widget);
}
row_obj.isInTree = jQuery.proxy(this.isInTree, this);
// we must not free the children!
row_obj.destroy = function () {
// @ts-ignore
delete row_obj._children;
};
return row_obj;
}
}
}
}
/**
* Needed for the align interface, but we're not doing anything with it...
*/
get_align() {
return "";
}
}
et2_grid._attributes = {
// Better to use CSS, no need to warn about it
"border": {
"ignore": true
},
"align": {
"name": "Align",
"type": "string",
"default": "left",
"description": "Position of this element in the parent hbox"
},
"spacing": {
"ignore": true
},
"padding": {
"ignore": true
},
"sortable": {
"name": "Sortable callback",
"type": "string",
"default": et2_no_init,
"description": "PHP function called when user sorts the grid. Setting this enables sorting the grid rows. The callback will be passed the ID of the grid and the new order of the rows."
},
sortable_containment: {
name: "Sortable bounding area",
type: "string",
default: "",
description: "Defines bounding area for sortable items"
},
sortable_connectWith: {
name: "Sortable connectWith element",
type: "string",
default: "",
description: "Defines other sortable areas that should be connected to sort list"
},
sortable_placeholder: {
name: "Sortable placeholder",
type: "string",
default: "",
description: "Defines sortable placeholder"
},
sortable_cancel: {
name: "Sortable cancel class",
type: "string",
default: "",
description: "Defines sortable cancel which prevents sorting the matching element"
},
sortable_recieveCallback: {
name: "Sortable receive callback",
type: "js",
default: et2_no_init,
description: "Defines sortable receive callback function"
},
sortable_startCallback: {
name: "Sortable start callback",
type: "js",
default: et2_no_init,
description: "Defines sortable start callback function"
}
};
et2_register_widget(et2_grid, ["grid"]);
//# sourceMappingURL=et2_widget_grid.js.map

View File

@ -1,61 +0,0 @@
/**
* EGroupware eTemplate2 - JS Groupbox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2012
*/
/*egw:uses
et2_core_baseWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_baseWidget } from "./et2_core_baseWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
/**
* Class which implements the groupbox tag
*
* @augments et2_baseWidget
*/
export class et2_groupbox extends et2_baseWidget {
/**
* Constructor
*
* @memberOf et2_groupbox
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_groupbox._attributes, _child || {}));
this.setDOMNode(document.createElement("fieldset"));
}
}
et2_register_widget(et2_groupbox, ["groupbox"]);
/**
* @augments et2_baseWidget
*/
export class et2_groupbox_legend extends et2_baseWidget {
/**
* Constructor
*
* @memberOf et2_groupbox_legend
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_groupbox_legend._attributes, _child || {}));
let legend = jQuery(document.createElement("legend")).text(this.options.label);
this.setDOMNode(legend[0]);
}
}
et2_groupbox_legend._attributes = {
"label": {
"name": "Label",
"type": "string",
"default": "",
"description": "Label for group box",
"translate": true
}
};
et2_register_widget(et2_groupbox_legend, ["caption"]);
//# sourceMappingURL=et2_widget_groupbox.js.map

View File

@ -1,158 +0,0 @@
/**
* EGroupware eTemplate2 - JS HBox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
*/
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
import { et2_baseWidget } from "./et2_core_baseWidget";
import { et2_grid } from "./et2_widget_grid";
import { et2_filteredNodeIterator } from "./et2_core_xml";
import { et2_cloneObject } from "./et2_core_common";
import { et2_IAligned } from "./et2_core_interfaces";
/**
* Class which implements hbox tag
*
* @augments et2_baseWidget
*/
export class et2_hbox extends et2_baseWidget {
/**
* Constructor
*
* @memberOf et2_hbox
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_hbox._attributes, _child || {}));
this.alignData = {
"hasAlign": false,
"hasLeft": false,
"hasCenter": false,
"hasRight": false,
"lastAlign": "left"
};
this.leftDiv = null;
this.rightDiv = null;
this.div = null;
this.leftDiv = null;
this.rightDiv = null;
this.div = jQuery(document.createElement("div"))
.addClass("et2_" + super.getType())
.addClass("et2_box_widget");
super.setDOMNode(this.div[0]);
}
_createNamespace() {
return true;
}
_buildAlignCells() {
if (this.alignData.hasAlign) {
// Check whether we have more than one type of align
let mto = (this.alignData.hasLeft && this.alignData.hasRight) ||
(this.alignData.hasLeft && this.alignData.hasCenter) ||
(this.alignData.hasCenter && this.alignData.hasRight);
if (!mto) {
// If there is only one type of align, we simply have to set
// the align of the top container
if (this.alignData.lastAlign != "left") {
this.div.addClass("et2_hbox_al_" + this.alignData.lastAlign);
}
}
else {
// Create an additional container for elements with align type
// "right"
if (this.alignData.hasRight) {
this.rightDiv = jQuery(document.createElement("div"))
.addClass("et2_hbox_right")
.appendTo(this.div);
}
// Create an additional container for elements with align type
// left, as the top container is used for the centered elements
if (this.alignData.hasCenter) {
// Create the left div if an element is centered
this.leftDiv = jQuery(document.createElement("div"))
.addClass("et2_hbox_left")
.appendTo(this.div);
this.div.addClass("et2_hbox_al_center");
}
}
}
}
/**
* The overwritten loadFromXML function checks whether any child element has
* a special align value.
*
* @param {object} _node
*/
loadFromXML(_node) {
// Check whether any child node has an alignment tag
et2_filteredNodeIterator(_node, function (_node) {
let align = _node.getAttribute("align");
if (!align) {
align = "left";
}
if (align != "left") {
this.alignData.hasAlign = true;
}
this.alignData.lastAlign = align;
switch (align) {
case "left":
this.alignData.hasLeft = true;
break;
case "right":
this.alignData.hasRight = true;
break;
case "center":
this.alignData.hasCenter = true;
break;
}
}, this);
// Build the align cells
this._buildAlignCells();
// Load the nodes as usual
super.loadFromXML(_node);
}
assign(_obj) {
// Copy the align data and the cells from the object which should be
// assigned
this.alignData = et2_cloneObject(_obj.alignData);
this._buildAlignCells();
// Call the inherited assign function
super.assign(_obj);
}
getDOMNode(_sender) {
// Return a special align container if this hbox needs it
if (_sender != this && this.alignData.hasAlign) {
// Check whether we've create a special container for the widget
let align = (_sender.implements(et2_IAligned) ?
_sender.get_align() : "left");
if (align == "left" && this.leftDiv != null) {
return this.leftDiv[0];
}
if (align == "right" && this.rightDiv != null) {
return this.rightDiv[0];
}
}
// Normally simply return the hbox-div
return super.getDOMNode(_sender);
}
/**
* Tables added to the root node need to be inline instead of blocks
*
* @param {et2_widget} child child-widget to add
*/
addChild(child) {
super.addChild(child);
if (child.instanceOf && child.instanceOf(et2_grid) && this.isAttached() || child._type == 'et2_grid' && this.isAttached()) {
jQuery(child.getDOMNode(child)).css("display", "inline-table");
}
}
}
et2_register_widget(et2_hbox, ["hbox"]);
//# sourceMappingURL=et2_widget_hbox.js.map

View File

@ -1,572 +0,0 @@
/**
* EGroupware eTemplate2 - JS History log
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright 2012 Nathan Gray
*/
import { et2_createWidget, et2_register_widget, et2_registry } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_dataview } from "./et2_dataview";
import { et2_dataview_column } from "./et2_dataview_model_columns";
import { et2_dataview_controller } from "./et2_dataview_controller";
import { et2_diff } from "./et2_widget_diff";
import { et2_IDetachedDOM } from "./et2_core_interfaces";
import { et2_dynheight } from "./et2_widget_dynheight";
import { et2_selectbox } from "./et2_widget_selectbox";
/**
* eTemplate history log widget displays a list of changes to the current record.
* The widget is encapsulated, and only needs the record's ID, and a map of
* fields:widgets for display.
*
* It defers its initialization until the tab that it's on is selected, to avoid
* wasting time if the user never looks at it.
*
* @augments et2_valueWidget
*/
export class et2_historylog extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_historylog
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_historylog._attributes, _child || {}));
this.div = jQuery(document.createElement("div"))
.addClass("et2_historylog");
this.innerDiv = jQuery(document.createElement("div"))
.appendTo(this.div);
}
set_status_id(_new_id) {
this.options.status_id = _new_id;
}
doLoadingFinished() {
super.doLoadingFinished();
// Find the tab
let tab = this.get_tab_info();
if (tab) {
// Bind the action to when the tab is selected
const handler = function (e) {
e.data.div.unbind("click.history");
// Bind on click tap, because we need to update history size
// after a rezise happend and history log was not the active tab
e.data.div.bind("click.history", { "history": e.data.history, div: tab.flagDiv }, function (e) {
if (e.data.history && e.data.history.dynheight) {
e.data.history.dynheight.update(function (_w, _h) {
e.data.history.dataview.resize(_w, _h);
});
}
});
if (typeof e.data.history.dataview == "undefined") {
e.data.history.finishInit();
if (e.data.history.dynheight) {
e.data.history.dynheight.update(function (_w, _h) {
e.data.history.dataview.resize(_w, _h);
});
}
}
};
tab.flagDiv.bind("click.history", { "history": this, div: tab.flagDiv }, handler);
// Display if history tab is selected
if (tab.contentDiv.is(':visible') && typeof this.dataview == 'undefined') {
tab.flagDiv.trigger("click.history");
}
}
else {
this.finishInit();
}
return true;
}
_createNamespace() {
return true;
}
/**
* Finish initialization which was skipped until tab was selected
*/
finishInit() {
// No point with no ID
if (!this.options.value || !this.options.value.id) {
return;
}
this._filters = {
record_id: this.options.value.id,
appname: this.options.value.app,
get_rows: this.options.get_rows
};
// Warn if status_id is the same as history id, that causes overlap and missing labels
if (this.options.status_id === this.id) {
this.egw().debug("warn", "status_id attribute should not be the same as historylog ID");
}
// Create the dynheight component which dynamically scales the inner
// container.
this.div.parentsUntil('.et2_tabs').height('100%');
const parent = this.get_tab_info();
this.dynheight = new et2_dynheight(parent ? parent.contentDiv : this.div.parent(), this.innerDiv, 250);
// Create the outer grid container
this.dataview = new et2_dataview(this.innerDiv, this.egw());
const dataview_columns = [];
let _columns = typeof this.options.columns === "string" ?
this.options.columns.split(',') : this.options.columns;
for (var i = 0; i < et2_historylog.columns.length; i++) {
dataview_columns[i] = {
"id": et2_historylog.columns[i].id,
"caption": et2_historylog.columns[i].caption,
"width": et2_historylog.columns[i].width,
"visibility": _columns.indexOf(et2_historylog.columns[i].id) < 0 ?
et2_dataview_column.ET2_COL_VISIBILITY_INVISIBLE : et2_dataview_column.ET2_COL_VISIBILITY_VISIBLE
};
}
this.dataview.setColumns(dataview_columns);
// Create widgets for columns that stay the same, and set up varying widgets
this.createWidgets();
// Create the gridview controller
const linkCallback = function () {
};
this.controller = new et2_dataview_controller(null, this.dataview.grid);
this.controller.setContext(this);
this.controller.setDataProvider(this);
this.controller.setLinkCallback(linkCallback);
this.controller.setRowCallback(this.rowCallback);
this.controller.setActionObjectManager(null);
const total = typeof this.options.value.total !== "undefined" ?
this.options.value.total : 0;
// This triggers an invalidate, which updates the grid
this.dataview.grid.setTotalCount(total);
// Insert any data sent from server, so invalidate finds data already
if (this.options.value.rows && this.options.value.num_rows) {
this.controller.loadInitialData(this.options.value.dataStorePrefix, this.options.value.row_id, this.options.value.rows);
// Remove, to prevent duplication
delete this.options.value.rows;
// This triggers an invalidate, which updates the grid
this.dataview.grid.setTotalCount(total);
}
else {
// Trigger the initial update
this.controller.update();
}
// Write something inside the column headers
for (var i = 0; i < et2_historylog.columns.length; i++) {
jQuery(this.dataview.getHeaderContainerNode(i)).text(et2_historylog.columns[i].caption);
}
// Register a resize callback
jQuery(window).on('resize.' + this.options.value.app + this.options.value.id, function () {
if (this && typeof this.dynheight != 'undefined')
this.dynheight.update(function (_w, _h) {
this.dataview.resize(_w, _h);
}.bind(this));
}.bind(this));
}
/**
* Destroys all
*/
destroy() {
// Unbind, if bound
if (this.options.value && !this.options.value.id) {
jQuery(window).off('.' + this.options.value.app + this.options.value.id);
}
// Free the widgets
for (let i = 0; i < et2_historylog.columns.length; i++) {
if (et2_historylog.columns[i].widget)
et2_historylog.columns[i].widget.destroy();
}
for (let key in this.fields) {
this.fields[key].widget.destroy();
}
// Free the grid components
if (this.dataview)
this.dataview.destroy();
if (this.controller)
this.controller.destroy();
if (this.dynheight)
this.dynheight.destroy();
super.destroy();
}
/**
* Create all needed widgets for new / old values
*/
createWidgets() {
// Constant widgets - first 3 columns
for (let i = 0; i < et2_historylog.columns.length; i++) {
if (et2_historylog.columns[i].widget_type) {
// Status ID is allowed to be remapped to something else. Only affects the widget ID though
var attrs = { 'readonly': true, 'id': (i == et2_historylog.FIELD ? this.options.status_id : et2_historylog.columns[i].id) };
et2_historylog.columns[i].widget = et2_createWidget(et2_historylog.columns[i].widget_type, attrs, this);
et2_historylog.columns[i].widget.transformAttributes(attrs);
et2_historylog.columns[i].nodes = jQuery(et2_historylog.columns[i].widget.getDetachedNodes());
}
}
// Add in handling for links
if (typeof this.options.value['status-widgets']['~link~'] == 'undefined') {
et2_historylog.columns[et2_historylog.FIELD].widget.optionValues['~link~'] = this.egw().lang('link');
this.options.value['status-widgets']['~link~'] = 'link';
}
// Add in handling for files
if (typeof this.options.value['status-widgets']['~file~'] == 'undefined') {
et2_historylog.columns[et2_historylog.FIELD].widget.optionValues['~file~'] = this.egw().lang('File');
this.options.value['status-widgets']['~file~'] = 'vfs';
}
// Add in handling for user-agent & action
if (typeof this.options.value['status-widgets']['user_agent_action'] == 'undefined') {
et2_historylog.columns[et2_historylog.FIELD].widget.optionValues['user_agent_action'] = this.egw().lang('User-agent & action');
}
// Per-field widgets - new value & old value
this.fields = {};
let labels = et2_historylog.columns[et2_historylog.FIELD].widget.optionValues;
// Custom fields - Need to create one that's all read-only for proper display
let cf_widget = et2_createWidget('customfields', { 'readonly': true }, this);
cf_widget.loadFields();
// Override this or it may damage the real values
cf_widget.getValue = function () { return null; };
for (let key in cf_widget.widgets) {
// Add label
labels[cf_widget.prefix + key] = cf_widget.options.customfields[key].label;
// If it doesn't support detached nodes, just treat it as text
if (cf_widget.widgets[key].getDetachedNodes) {
var nodes = cf_widget.widgets[key].getDetachedNodes();
for (var i = 0; i < nodes.length; i++) {
if (nodes[i] == null)
nodes.splice(i, 1);
}
// Save to use for each row
this.fields[cf_widget.prefix + key] = {
attrs: cf_widget.widgets[key].options,
widget: cf_widget.widgets[key],
nodes: jQuery(nodes)
};
}
}
// Add all cf labels
et2_historylog.columns[et2_historylog.FIELD].widget.set_select_options(labels);
// From app
for (var key in this.options.value['status-widgets']) {
let attrs = jQuery.extend({ 'readonly': true, 'id': key }, this.getArrayMgr('modifications').getEntry(key));
const field = attrs.type || this.options.value['status-widgets'][key];
const options = null;
const widget = this._create_widget(key, field, attrs, options);
if (widget === null) {
continue;
}
if (widget.instanceOf(et2_selectbox))
widget.options.multiple = true;
widget.transformAttributes(attrs);
// Save to use for each row
let nodes = widget._children.length ? [] : jQuery(widget.getDetachedNodes());
for (let i = 0; i < widget._children.length; i++) {
// @ts-ignore
nodes.push(jQuery(widget._children[i].getDetachedNodes()));
}
this.fields[key] = {
attrs: attrs,
widget: widget,
nodes: nodes
};
}
// Widget for text diffs
const diff = et2_createWidget('diff', {}, this);
this.diff = {
// @ts-ignore
widget: diff,
nodes: jQuery(diff.getDetachedNodes())
};
}
_create_widget(key, field, attrs, options) {
let widget = null;
// If field has multiple parts (is object) and isn't an obvious select box
if (typeof field === 'object') {
// Check for multi-part statuses needing multiple widgets
let need_box = false; //!this.getArrayMgr('sel_options').getEntry(key);
for (let j in field) {
// Require widget to be a widget, to avoid invalid widgets
// (and template, which is a widget and an infolog todo status)
if (et2_registry[field[j]] && ['template'].indexOf(field[j]) < 0) // && (et2_registry[field[j]].prototype.instanceOf(et2_valueWidget))
{
need_box = true;
break;
}
}
if (need_box) {
// Multi-part value needs multiple widgets
widget = et2_createWidget('vbox', attrs, this);
for (var i in field) {
let type = field[i];
const child_attrs = jQuery.extend({}, attrs);
if (typeof type === 'object') {
child_attrs['select_options'] = field[i];
type = 'select';
}
else {
delete child_attrs['select_options'];
}
child_attrs.id = i;
const child = this._create_widget(i, type, child_attrs, options);
widget.addChild(child);
child.transformAttributes(child_attrs);
}
}
else {
attrs['select_options'] = field;
}
}
// Check for options after the type, ex: link-entry:infolog
else if (field.indexOf(':') > 0) {
var options = field.split(':');
field = options.shift();
}
if (widget === null) {
widget = et2_createWidget(typeof field === 'string' ? field : 'select', attrs, this);
}
if (!widget.instanceOf(et2_IDetachedDOM)) {
this.egw().debug("warn", this, "Invalid widget " + field + " for " + key + ". Status widgets must implement et2_IDetachedDOM.");
return null;
}
// Parse / set legacy options
if (options) {
const mgr = this.getArrayMgr("content");
let legacy = widget.constructor.legacyOptions || [];
for (let i = 0; i < options.length && i < legacy.length; i++) {
// Not set
if (options[i] === "")
continue;
const attr = widget.attributes[legacy[i]];
let attrValue = options[i];
// If the attribute is marked as boolean, parse the
// expression as bool expression.
if (attr.type === "boolean") {
attrValue = mgr.parseBoolExpression(attrValue);
}
else {
attrValue = mgr.expandName(attrValue);
}
attrs[legacy[i]] = attrValue;
if (typeof widget['set_' + legacy[i]] === 'function') {
widget['set_' + legacy[i]].call(widget, attrValue);
}
else {
widget.options[legacy[i]] = attrValue;
}
}
}
return widget;
}
getDOMNode(_sender) {
if (_sender == this) {
return this.div[0];
}
for (let i = 0; i < et2_historylog.columns.length; i++) {
if (_sender == et2_historylog.columns[i].widget) {
return this.dataview.getHeaderContainerNode(i);
}
}
return null;
}
dataFetch(_queriedRange, _callback, _context) {
// Skip getting data if there's no ID
if (!this.value.id)
return;
// Set num_rows to fetch via nextmatch
if (this.options.value['num_rows'])
_queriedRange['num_rows'] = this.options.value['num_rows'];
const historylog = this;
// Pass the fetch call to the API
this.egw().dataFetch(this.getInstanceManager().etemplate_exec_id, _queriedRange, this._filters, this.id, function (_response) {
_callback.call(this, _response);
}, _context, []);
}
// Needed by interface
dataRegisterUID(_uid, _callback, _context) {
this.egw().dataRegisterUID(_uid, _callback, _context, this.getInstanceManager().etemplate_exec_id, this.id);
}
dataUnregisterUID(_uid, _callback, _context) {
// Needed by interface
}
/**
* The row callback gets called by the gridview controller whenever
* the actual DOM-Nodes for a node with the given data have to be
* created.
*
* @param {type} _data
* @param {type} _row
* @param {type} _idx
* @param {type} _entry
*/
rowCallback(_data, _row, _idx, _entry) {
let tr = _row.getDOMNode();
jQuery(tr).attr("valign", "top");
let row = this.dataview.rowProvider.getPrototype("default");
let self = this;
jQuery("div", row).each(function (i) {
let nodes = [];
let widget = et2_historylog.columns[i].widget;
let value = _data[et2_historylog.columns[i].id];
if (et2_historylog.OWNER === i && _data['share_email']) {
// Show share email instead of owner
widget = undefined;
value = _data['share_email'];
}
// Get widget from list, unless it needs a diff widget
if ((typeof widget == 'undefined' || widget == null) && typeof self.fields[_data.status] != 'undefined' && (i < et2_historylog.NEW_VALUE ||
i >= et2_historylog.NEW_VALUE && (self.fields[_data.status].nodes || !self._needsDiffWidget(_data['status'], _data[et2_historylog.columns[et2_historylog.OLD_VALUE].id])))) {
widget = self.fields[_data.status].widget;
if (!widget._children.length) {
nodes = self.fields[_data.status].nodes.clone();
}
for (var j = 0; j < widget._children.length; j++) {
// @ts-ignore
nodes.push(self.fields[_data.status].nodes[j].clone());
if (widget._children[j].instanceOf(et2_diff)) {
self._spanValueColumns(jQuery(this));
}
}
}
else if (widget) {
nodes = et2_historylog.columns[i].nodes.clone();
}
else if ((
// Already parsed & cached
typeof _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] == "object" &&
typeof _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] != "undefined" &&
_data[et2_historylog.columns[et2_historylog.NEW_VALUE].id] !== null) || // typeof null === 'object'
// Large old value
self._needsDiffWidget(_data['status'], _data[et2_historylog.columns[et2_historylog.OLD_VALUE].id]) ||
// Large new value
self._needsDiffWidget(_data['status'], _data[et2_historylog.columns[et2_historylog.NEW_VALUE].id])) {
// Large text value - span both columns, and show a nice diff
let jthis = jQuery(this);
if (i === et2_historylog.NEW_VALUE) {
// Diff widget
widget = self.diff.widget;
nodes = self.diff.nodes.clone();
if (widget)
widget.setDetachedAttributes(nodes, {
value: value,
label: jthis.parents("td").prev().text()
});
self._spanValueColumns(jthis);
}
}
else {
// No widget fallback - display actual value
nodes = jQuery('<span>').text(value === null ? '' : value);
}
if (widget) {
if (widget._children.length) {
// Multi-part values
const box = jQuery(widget.getDOMNode()).clone();
for (var j = 0; j < widget._children.length; j++) {
const id = widget._children[j].id;
const widget_value = value ? value[id] || "" : "";
widget._children[j].setDetachedAttributes(nodes[j], { value: widget_value });
box.append(nodes[j]);
}
nodes = box;
}
else {
widget.setDetachedAttributes(nodes, { value: value });
}
}
jQuery(this).append(nodes);
});
jQuery(tr).append(row.children());
return tr;
}
/**
* How to tell if the row needs a diff widget or not
*
* @param {string} columnName
* @param {string} value
* @returns {Boolean}
*/
_needsDiffWidget(columnName, value) {
if (typeof value !== "string" && value) {
this.egw().debug("warn", "Crazy diff value", value);
return false;
}
return value === '***diff***';
}
/**
* Make a single row's new value cell span across both new value and old value
* columns. Used for diff widget.
*
* @param {jQuery} row jQuery wrapped row node
*/
_spanValueColumns(row) {
// Stretch column 4
row.parents("td").attr("colspan", 2)
.css("border-right", "none");
row.css("width", (this.dataview.getColumnMgr().getColumnWidth(et2_historylog.NEW_VALUE) +
this.dataview.getColumnMgr().getColumnWidth(et2_historylog.OLD_VALUE) - 10) + 'px');
// Skip column 5
row.parents("td").next().remove();
}
resize(_height) {
if (typeof this.options != 'undefined' && _height
&& typeof this.options.resize_ratio != 'undefined') {
// apply the ratio
_height = (this.options.resize_ratio != '') ? _height * this.options.resize_ratio : _height;
if (_height != 0) {
// 250px is the default value for history widget
// if it's not loaded yet and window is resized
// then add the default height with excess_height
if (this.div.height() == 0)
_height += 250;
this.div.height(this.div.height() + _height);
// trigger the history registered resize
// in order to update the height with new value
this.div.trigger('resize.' + this.options.value.app + this.options.value.id);
}
}
if (this.dynheight) {
this.dynheight.update();
}
// Resize diff widgets to match new space
if (this.dataview) {
const columns = this.dataview.getColumnMgr();
jQuery('.et2_diff', this.div).closest('.innerContainer')
.width(columns.getColumnWidth(et2_historylog.NEW_VALUE) + columns.getColumnWidth(et2_historylog.OLD_VALUE));
}
}
}
et2_historylog._attributes = {
"value": {
"name": "Value",
"type": "any",
"description": "Object {app: ..., id: ..., status-widgets: {}} where status-widgets is a map of fields to widgets used to display those fields"
},
"status_id": {
"name": "status_id",
"type": "string",
"default": "status",
"description": "The history widget is traditionally named 'status'. If you name another widget in the same template 'status', you can use this attribute to re-name the history widget. "
},
"columns": {
"name": "columns",
"type": "string",
"default": "user_ts,owner,status,new_value,old_value",
"description": "Columns to display. Default is user_ts,owner,status,new_value,old_value"
},
"get_rows": {
"name": "get_rows",
"type": "string",
"default": "EGroupware\\Api\\Storage\\History::get_rows",
"description": "Method to get rows"
}
};
et2_historylog.legacyOptions = ["status_id"];
et2_historylog.columns = [
{ 'id': 'user_ts', caption: 'Date', 'width': '120px', widget_type: 'date-time', widget: null, nodes: null },
{ 'id': 'owner', caption: 'User', 'width': '150px', widget_type: 'select-account', widget: null, nodes: null },
{ 'id': 'status', caption: 'Changed', 'width': '120px', widget_type: 'select', widget: null, nodes: null },
{ 'id': 'new_value', caption: 'New Value', 'width': '50%', widget: null, nodes: null },
{ 'id': 'old_value', caption: 'Old Value', 'width': '50%', widget: null, nodes: null }
];
et2_historylog.TIMESTAMP = 0;
et2_historylog.OWNER = 1;
et2_historylog.FIELD = 2;
et2_historylog.NEW_VALUE = 3;
et2_historylog.OLD_VALUE = 4;
et2_register_widget(et2_historylog, ['historylog']);
//# sourceMappingURL=et2_widget_historylog.js.map

View File

@ -1,34 +0,0 @@
/**
* EGroupware eTemplate2 - JS HRule object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
et2_core_baseWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_baseWidget } from "./et2_core_baseWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
/**
* Class which implements the hrule tag
*
* @augments et2_baseWidget
*/
export class et2_hrule extends et2_baseWidget {
/**
* Constructor
*
* @memberOf et2_hrule
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_hrule._attributes, _child || {}));
this.setDOMNode(document.createElement("hr"));
}
}
et2_register_widget(et2_hrule, ["hrule"]);
//# sourceMappingURL=et2_widget_hrule.js.map

View File

@ -1,98 +0,0 @@
/**
* EGroupware eTemplate2 - JS widget class containing raw HTML
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
jsapi.jsapi; // Needed for egw_seperateJavaScript
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
*/
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
/**
* @augments et2_valueWidget
*/
export class et2_html extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_html
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_html._attributes, _child || {}));
this.htmlNode = null;
// Allow no child widgets
this.supportedWidgetClasses = [];
this.htmlNode = jQuery(document.createElement("span"));
if (this.getType() == 'htmlarea') {
this.htmlNode.addClass('et2_textbox_ro');
}
if (this.options.label) {
this.htmlNode.append('<span class="et2_label">' + this.options.label + '</span>');
}
this.setDOMNode(this.htmlNode[0]);
}
loadContent(_data) {
// Create an object containg the given value and an empty js string
let html = { html: _data ? _data : '', js: '' };
// Separate the javascript from the given html. The js code will be
// written to the previously created empty js string
egw_seperateJavaScript(html);
// Append the html to the parent element
if (this.options.label) {
this.htmlNode.append('<span class="et2_label">' + this.options.label + '</span>');
}
this.htmlNode.append(html.html);
this.htmlNode.append(html.js);
}
set_value(_value) {
this.htmlNode.empty();
this.loadContent(_value);
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "class");
}
getDetachedNodes() {
return [this.htmlNode[0]];
}
setDetachedAttributes(_nodes, _values) {
this.htmlNode = jQuery(_nodes[0]);
if (typeof _values['value'] !== 'undefined') {
this.set_value(_values['value']);
}
}
}
et2_html._attributes = {
'label': {
'default': "",
description: "The label is displayed by default in front (for radiobuttons behind) each widget (if not empty). If you want to specify a different position, use a '%s' in the label, which gets replaced by the widget itself. Eg. '%s Name' to have the label Name behind a checkbox. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
ignore: false,
name: "Label",
translate: true,
type: "string"
},
"needed": {
"ignore": true
},
value: {
name: "Value",
description: "The value of the widget",
type: "html",
default: et2_no_init
}
};
et2_register_widget(et2_html, ["html", "htmlarea_ro"]);
//# sourceMappingURL=et2_widget_html.js.map

View File

@ -1,483 +0,0 @@
/**
* EGroupware eTemplate2 - JS widget for HTML editing
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh <hn@egroupware.org>
* @copyright Hadi Nategh <hn@egroupware.org>
* @version $Id$
*/
/*egw:uses
jsapi.jsapi; // Needed for egw_seperateJavaScript
/vendor/tinymce/tinymce/tinymce.min.js;
et2_core_editableWidget;
*/
import { et2_editableWidget } from "./et2_core_editableWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget, et2_createWidget } from "./et2_core_widget";
import { et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
import "../../../vendor/tinymce/tinymce/tinymce.min.js";
import { etemplate2 } from "./etemplate2";
/**
* @augments et2_inputWidget
*/
export class et2_htmlarea extends et2_editableWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_htmlarea._attributes, _child || {}));
this.editor = null;
this.htmlNode = null;
this.editor = null; // TinyMce editor instance
this.supportedWidgetClasses = []; // Allow no child widgets
this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea"))
.addClass('et2_textbox_ro');
if (this.options.height) {
this.htmlNode.css('height', this.options.height);
}
this.setDOMNode(this.htmlNode[0]);
}
/**
*
* @returns {undefined}
*/
doLoadingFinished() {
super.doLoadingFinished();
this.init_editor();
return true;
}
init_editor() {
if (this.mode == 'ascii' || this.editor != null || this.options.readonly)
return;
let imageUpload;
let self = this;
if (this.options.imageUpload && this.options.imageUpload[0] !== '/' && this.options.imageUpload.substr(0, 4) != 'http') {
imageUpload = egw.ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_htmlarea_upload") +
'&request_id=' + this.getInstanceManager().etemplate_exec_id + '&widget_id=' + this.options.imageUpload + '&type=htmlarea';
imageUpload = imageUpload.substr(egw.webserverUrl.length + 1);
}
else if (imageUpload) {
imageUpload = this.options.imageUpload.substr(egw.webserverUrl.length + 1);
}
else {
imageUpload = egw.ajaxUrl("EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_htmlarea_upload") +
'&request_id=' + this.getInstanceManager().etemplate_exec_id + '&type=htmlarea';
}
// default settings for initialization
let settings = {
base_url: egw.webserverUrl + '/vendor/tinymce/tinymce',
target: this.htmlNode[0],
body_id: this.dom_id + '_htmlarea',
menubar: false,
statusbar: this.options.statusbar,
toolbar_mode: this.options.toolbar_mode,
branding: false,
resize: false,
height: this.options.height,
width: this.options.width,
end_container_on_empty_block: true,
mobile: {
theme: 'silver'
},
formats: {
customparagraph: { block: 'p', styles: { "margin-block-start": "0px", "margin-block-end": "0px" } }
},
min_height: 100,
convert_urls: false,
language: et2_htmlarea.LANGUAGE_CODE[egw.preference('lang', 'common')],
language_url: egw.webserverUrl + '/api/js/tinymce/langs/' + et2_htmlarea.LANGUAGE_CODE[egw.preference('lang', 'common')] + '.js',
paste_data_images: true,
paste_filter_drop: true,
browser_spellcheck: true,
contextmenu: false,
images_upload_url: imageUpload,
file_picker_callback: jQuery.proxy(this._file_picker_callback, this),
images_upload_handler: this.options.images_upload_handler,
init_instance_callback: jQuery.proxy(this._instanceIsReady, this),
auto_focus: false,
valid_children: this.options.valid_children,
plugins: [
"print searchreplace autolink directionality ",
"visualblocks visualchars image link media template fullscreen",
"codesample table charmap hr pagebreak nonbreaking anchor toc ",
"insertdatetime advlist lists textcolor wordcount imagetools ",
"colorpicker textpattern help paste code searchreplace tabfocus"
],
toolbar: et2_htmlarea.TOOLBAR_SIMPLE,
block_formats: "Paragraph=p;Heading 1=h1;Heading 2=h2;Heading 3=h3;" +
"Heading 4=h4;Heading 5=h5;Heading 6=h6;Preformatted=pre;Custom Paragraph=customparagraph",
font_formats: "Andale Mono=andale mono,times;Arial=arial,helvetica," +
"sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book " +
"antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;" +
"Courier New=courier new,courier;Georgia=georgia,palatino;" +
"Helvetica=helvetica;Impact=impact,chicago;Segoe=segoe,segoe ui;Symbol=symbol;" +
"Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal," +
"monaco;Times New Roman=times new roman,times;Trebuchet " +
"MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;" +
"Wingdings=wingdings,zapf dingbats",
fontsize_formats: '8pt 10pt 12pt 14pt 18pt 24pt 36pt',
setup: function (ed) {
ed.on('init', function () {
this.getDoc().body.style.fontSize = egw.preference('rte_font_size', 'common')
+ egw.preference('rte_font_unit', 'common');
this.getDoc().body.style.fontFamily = egw.preference('rte_font', 'common');
});
}
};
// extend default settings with configured options and preferences
jQuery.extend(settings, this._extendedSettings());
this.tinymce = tinymce.init(settings);
// make sure value gets set in case of widget gets loaded by delay like
// inside an inactive tabs
this.tinymce.then(function () {
self.set_value(self.htmlNode.val());
self.resetDirty();
if (self.editor && self.editor.editorContainer) {
self.editor.formatter.toggle(egw.preference('rte_formatblock', 'common'));
jQuery(self.editor.editorContainer).height(self.options.height);
jQuery(self.editor.iframeElement.contentWindow.document).on('dragenter', function () {
if (jQuery('#dragover-tinymce').length < 1)
jQuery("<style id='dragover-tinymce'>.dragover:after {height:calc(100% - " + jQuery(this).height() + "px) !important;}</style>").appendTo('head');
});
}
});
}
/**
* set disabled
*
* @param {type} _value
* @returns {undefined}
*/
set_disabled(_value) {
super.set_disabled(_value);
if (_value) {
jQuery(this.tinymce_container).css('display', 'none');
}
else {
jQuery(this.tinymce_container).css('display', 'flex');
}
}
set_readonly(_value) {
if (this.options.readonly === _value)
return;
let value = this.get_value();
this.options.readonly = _value;
if (this.options.readonly) {
if (this.editor)
this.editor.remove();
this.htmlNode = jQuery(document.createElement(this.options.readonly ? "div" : "textarea"))
.addClass('et2_textbox_ro');
if (this.options.height) {
this.htmlNode.css('height', this.options.height);
}
this.editor = null;
this.setDOMNode(this.htmlNode[0]);
this.set_value(value);
}
else {
if (!this.editor) {
this.htmlNode = jQuery(document.createElement("textarea"))
.val(value);
if (this.options.height || this.options.editable_height) {
this.htmlNode.css('height', (this.options.editable_height ? this.options.editable_height : this.options.height));
}
this.setDOMNode(this.htmlNode[0]);
this.init_editor();
}
}
}
/**
* Callback function runs when the filepicker in image dialog is clicked
*
* @param {type} _callback
* @param {type} _value
* @param {type} _meta
*/
_file_picker_callback(_callback, _value, _meta) {
if (typeof this.file_picker_callback == 'function')
return this.file_picker_callback.call(arguments, this);
let callback = _callback;
// Don't rely only on app_name to fetch et2 object as app_name may not
// always represent current app of the window, e.g.: mail admin account.
// Try to fetch et2 from its template name.
let etemplate = jQuery('form').data('etemplate');
let et2;
if (etemplate && etemplate.name && !app[egw(window).app_name()]) {
et2 = etemplate2.getByTemplate(etemplate.name)[0]['widgetContainer'];
}
else {
et2 = app[egw(window).app_name()].et2;
}
let vfsSelect = et2_createWidget('vfs-select', {
id: 'upload',
mode: 'open',
name: '',
button_caption: "Link",
button_label: "Link",
dialog_title: "Link file",
method: "download"
}, et2);
jQuery(vfsSelect.getDOMNode()).on('change', function () {
callback(vfsSelect.get_value(), { alt: vfsSelect.get_value() });
});
// start the file selector dialog
vfsSelect.click();
}
/**
* Callback when instance is ready
*
* @param {type} _editor
*/
_instanceIsReady(_editor) {
console.log("Editor: " + _editor.id + " is now initialized.");
// try to reserve focus state as running command on editor may steal the
// current focus.
let focusedEl = jQuery(':focus');
this.editor = _editor;
this.editor.on('drop', function (e) {
e.preventDefault();
});
if (!this.disabled)
jQuery(this.editor.editorContainer).css('display', 'flex');
this.tinymce_container = this.editor.editorContainer;
// go back to reserved focused element
focusedEl.focus();
}
/**
* Takes all relevant preferences into account and set settings accordingly
*
* @returns {object} returns a object including all settings
*/
_extendedSettings() {
let rte_menubar = egw.preference('rte_menubar', 'common');
let rte_toolbar = egw.preference('rte_toolbar', 'common');
// we need to have rte_toolbar values as an array
if (rte_toolbar && typeof rte_toolbar == "object" && this.toolbar == '') {
rte_toolbar = Object.keys(rte_toolbar).map(function (key) { return rte_toolbar[key]; });
}
else if (this.toolbar != '') {
rte_toolbar = this.toolbar.split(',');
}
let settings = {
fontsize_formats: et2_htmlarea.FONT_SIZE_FORMATS[egw.preference('rte_font_unit', 'common')],
menubar: parseInt(rte_menubar) && this.menubar ? true : typeof rte_menubar != 'undefined' ? false : this.menubar
};
switch (this.mode) {
case 'simple':
settings['toolbar'] = et2_htmlarea.TOOLBAR_SIMPLE;
break;
case 'extended':
settings['toolbar'] = et2_htmlarea.TOOLBAR_EXTENDED;
break;
case 'advanced':
settings['toolbar'] = et2_htmlarea.TOOLBAR_ADVANCED;
break;
default:
this.mode = '';
}
// take rte_toolbar into account if no mode restrictly set from template
if (rte_toolbar && !this.mode) {
let toolbar_diff = et2_htmlarea.TOOLBAR_LIST.filter(function (i) { return !(rte_toolbar.indexOf(i) > -1); });
settings['toolbar'] = et2_htmlarea.TOOLBAR_ADVANCED;
toolbar_diff.forEach(function (a) {
let r = new RegExp(a);
settings['toolbar'] = settings['toolbar'].replace(r, '');
});
}
return settings;
}
destroy() {
if (this.editor) {
try {
this.editor.destroy();
}
catch (e) {
egw().debug("Error destroying editor", e);
}
}
this.editor = null;
this.tinymce = null;
this.tinymce_container = null;
this.htmlNode.remove();
this.htmlNode = null;
super.destroy();
}
set_value(_value) {
this._oldValue = _value;
if (this.editor) {
this.editor.setContent(_value);
}
else {
if (this.options.readonly) {
this.htmlNode.empty().append(_value);
}
else {
this.htmlNode.val(_value);
}
}
this.value = _value;
}
getValue() {
return this.editor ? this.editor.getContent() : (this.options.readonly ? this.value : this.htmlNode.val());
}
/**
* Resize htmlNode tag according to window size
* @param {type} _height excess height which comes from window resize
*/
resize(_height) {
var _a, _b;
if (_height && this.options.resize_ratio !== '0') {
// apply the ratio
_height = (this.options.resize_ratio != '') ? _height * this.options.resize_ratio : _height;
if (_height != 0) {
if (this.editor) // TinyMCE HTML
{
let h;
if (typeof this.editor.iframeElement != 'undefined' && this.editor.editorContainer.clientHeight > 0) {
h = (this.editor.editorContainer.clientHeight + _height) > 0 ?
(this.editor.editorContainer.clientHeight) + _height : this.editor.settings.min_height;
}
else // fallback height size
{
h = this.editor.settings.min_height + _height;
}
jQuery(this.editor.editorContainer).height(h);
jQuery(this.editor.iframeElement).height(h - (((_a = this.editor.editorContainer.getElementsByClassName('tox-editor-header')[0]) === null || _a === void 0 ? void 0 : _a.clientHeight) + ((_b = this.editor.editorContainer.getElementsByClassName('tox-statusbar')[0]) === null || _b === void 0 ? void 0 : _b.clientHeight)));
}
else // No TinyMCE
{
this.htmlNode.height(this.htmlNode.height() + _height);
}
}
}
}
}
et2_htmlarea._attributes = {
mode: {
'name': 'Mode',
'description': 'One of {ascii|simple|extended|advanced}',
'default': '',
'type': 'string'
},
height: {
'name': 'Height',
'default': et2_no_init,
'type': 'string'
},
width: {
'name': 'Width',
'default': et2_no_init,
'type': 'string'
},
value: {
name: "Value",
description: "The value of the widget",
type: "html",
default: et2_no_init
},
imageUpload: {
name: "imageUpload",
description: "Url to upload images dragged in or id of link_to widget to it's vfs upload. Can also be just a name for which content array contains a path to upload the picture.",
type: "string",
default: null
},
file_picker_callback: {
name: "File picker callback",
description: "Callback function to get called when file picker is clicked",
type: 'js',
default: et2_no_init
},
images_upload_handler: {
name: "Images upload handler",
description: "Callback function for handling image upload",
type: 'js',
default: et2_no_init
},
menubar: {
name: "Menubar",
description: "Display menubar at the top of the editor",
type: "boolean",
default: true
},
statusbar: {
name: "Status bar",
description: "Enable/disable status bar on the bottom of editor",
type: "boolean",
default: true
},
valid_children: {
name: "Valid children",
description: "Enables to control what child tag is allowed or not allowed of the present tag. For instance: +body[style], makes style tag allowed inside body",
type: "string",
default: "+body[style]"
},
toolbar: {
'name': 'Toolbar',
'description': 'Comma separated string of toolbar actions. It will only be considered if no Mode is restricted.',
'default': '',
'type': 'string'
},
toolbar_mode: {
'name': 'toolbar mode',
'type': 'string',
'default': 'floating',
'description': 'It allows to extend the toolbar to accommodate the overflowing toolbar buttons. {floating, sliding, scrolling, wrap}'
}
};
/**
* Array of toolbars
* @constant
*/
et2_htmlarea.TOOLBAR_LIST = ['undo', 'redo', 'formatselect', 'fontselect', 'fontsizeselect',
'bold', 'italic', 'strikethrough', 'forecolor', 'backcolor', 'link',
'alignleft', 'aligncenter', 'alignright', 'alignjustify', 'numlist',
'bullist', 'outdent', 'indent', 'ltr', 'rtl', 'removeformat', 'code', 'image', 'searchreplace', 'fullscreen', 'table'
];
/**
* arranged toolbars as simple mode
* @constant
*/
et2_htmlarea.TOOLBAR_SIMPLE = "undo redo|formatselect fontselect fontsizeselect | bold italic removeformat forecolor backcolor | " +
"alignleft aligncenter alignright alignjustify | bullist " +
"numlist outdent indent| link image pastetext | table";
/**
* arranged toolbars as extended mode
* @constant
*/
et2_htmlarea.TOOLBAR_EXTENDED = "fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | " +
"link | alignleft aligncenter alignright alignjustify | numlist " +
"bullist outdent indent | removeformat | image | fullscreen | table";
/**
* arranged toolbars as advanced mode
* @constant
*/
et2_htmlarea.TOOLBAR_ADVANCED = "undo redo| formatselect | fontselect fontsizeselect | bold italic strikethrough forecolor backcolor | " +
"alignleft aligncenter alignright alignjustify | bullist " +
"numlist outdent indent ltr rtl | removeformat code| link image pastetext | searchreplace | fullscreen | table";
/**
* font size formats
* @constant
*/
et2_htmlarea.FONT_SIZE_FORMATS = {
pt: "8pt 9pt 10pt 11pt 12pt 14pt 16pt 18pt 20pt 22pt 24pt 26pt 28pt 36pt 48pt 72pt",
px: "8px 9px 10px 11px 12px 14px 16px 18px 20px 22px 24px 26px 28px 36px 48px 72px"
};
/**
* language code represention for TinyMCE lang code
*/
et2_htmlarea.LANGUAGE_CODE = {
bg: "bg_BG", ca: "ca", cs: "cs", da: "da", de: "de", en: "en_CA",
el: "el", "es-es": "es", et: "et", eu: "eu", fa: "fa_IR", fi: "fi",
fr: "fr_FR", hi: "", hr: "hr", hu: "hu_HU", id: "id", it: "it", iw: "",
ja: "ja", ko: "ko_KR", lo: "", lt: "lt", lv: "lv", nl: "nl", no: "nb_NO",
pl: "pl", pt: "pt_PT", "pt-br": "pt_BR", ru: "ru", sk: "sk", sl: "sl_SI",
sv: "sv_SE", th: "th_TH", tr: "tr_TR", uk: "en_GB", vi: "vi_VN", zh: "zh_CN",
"zh-tw": "zh_TW"
};
et2_register_widget(et2_htmlarea, ["htmlarea"]);
//# sourceMappingURL=et2_widget_htmlarea.js.map

View File

@ -1,157 +0,0 @@
/**
* EGroupware eTemplate2 - JS widget class for an iframe
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
*/
/*egw:uses
et2_core_valueWidget;
*/
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
/**
* @augments et2_valueWidget
*/
export class et2_iframe extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_iframe
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_iframe._attributes, _child || {}));
this.htmlNode = null;
// Allow no child widgets
this.supportedWidgetClasses = [];
this.htmlNode = jQuery(document.createElement("iframe"));
if (this.options.label) {
this.htmlNode.append('<span class="et2_label">' + this.options.label + '</span>');
}
if (this.options.fullscreen) {
this.htmlNode.attr('allowfullscreen', 1);
}
this.setDOMNode(this.htmlNode[0]);
}
/**
* Set name of iframe (to be used as target for links)
*
* @param _name
*/
set_name(_name) {
this.options.name = _name;
this.htmlNode.attr('name', _name);
}
set_allow(_allow) {
this.options.allow = _allow;
this.htmlNode.attr('allow', _allow);
}
/**
* Make it look like part of the containing document
*
* @param _seamless boolean
*/
set_seamless(_seamless) {
this.options.seamless = _seamless;
this.htmlNode.attr("seamless", _seamless);
}
set_value(_value) {
if (typeof _value == "undefined")
_value = "";
if (_value.trim().indexOf("http") == 0 || _value.indexOf('about:') == 0 || _value[0] == '/') {
// Value is a URL
this.set_src(_value);
}
else {
// Value is content
this.set_srcdoc(_value);
}
}
/**
* Set the URL for the iframe
*
* Sets the src attribute to the given value
*
* @param _value String URL
*/
set_src(_value) {
if (_value.trim() != "") {
if (_value.trim() == 'about:blank') {
this.htmlNode.attr("src", _value);
}
else {
// Load the new page, but display a loader
let loader = jQuery('<div class="et2_iframe loading"/>');
this.htmlNode
.before(loader);
window.setTimeout(jQuery.proxy(function () {
this.htmlNode.attr("src", _value)
.one('load', function () {
loader.remove();
});
}, this), 0);
}
}
}
/**
* Sets the content of the iframe
*
* Sets the srcdoc attribute to the given value
*
* @param _value String Content of a document
*/
set_srcdoc(_value) {
this.htmlNode.attr("srcdoc", _value);
}
}
et2_iframe._attributes = {
'label': {
'default': "",
description: "The label is displayed by default in front (for radiobuttons behind) each widget (if not empty). If you want to specify a different position, use a '%s' in the label, which gets replaced by the widget itself. Eg. '%s Name' to have the label Name behind a checkbox. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
ignore: false,
name: "Label",
translate: true,
type: "string"
},
"needed": {
"ignore": true
},
"seamless": {
name: "Seamless",
'default': true,
description: "Specifies that the iframe should be rendered in a manner that makes it appear to be part of the containing document",
translate: false,
type: "boolean"
},
"name": {
name: "Name",
"default": "",
description: "Specifies name of frame, to be used as target for links",
type: "string"
},
fullscreen: {
name: "Fullscreen",
"default": false,
description: "Make the iframe compatible to be a fullscreen video player mode",
type: "boolean"
},
src: {
name: "Source",
"default": "",
description: "Specifies URL for the iframe",
type: "string"
},
allow: {
name: "Allow",
"default": "",
description: "Specifies list of allow features, e.g. camera",
type: "string"
}
};
et2_register_widget(et2_iframe, ["iframe"]);
//# sourceMappingURL=et2_widget_iframe.js.map

View File

@ -1,586 +0,0 @@
/**
* EGroupware eTemplate2 - JS Description object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_interfaces;
et2_core_baseWidget;
expose;
/vendor/bower-asset/cropper/dist/cropper.min.js;
*/
import { et2_baseWidget } from './et2_core_baseWidget';
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
import { et2_dialog } from "./et2_widget_dialog";
/**
* Class which implements the "image" XET-Tag
*
* @augments et2_baseWidget
*/
export class et2_image extends et2_baseWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_image._attributes, _child || {}));
this.image = null;
// Create the image or a/image tag
this.image = jQuery(document.createElement("img"));
if (this.options.label) {
this.image.attr("alt", this.options.label).attr("title", this.options.label);
}
if (this.options.href) {
this.image.addClass('et2_clickable');
}
if (this.options["class"]) {
this.image.addClass(this.options["class"]);
}
this.setDOMNode(this.image[0]);
}
click(_ev) {
if (this.options.href) {
this.egw().open_link(this.options.href, this.options.extra_link_target, this.options.extra_link_popup);
}
else {
super.click(_ev);
}
}
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
// Check to expand name
if (typeof _attrs["src"] != "undefined") {
let manager = this.getArrayMgr("content");
if (manager && _attrs["src"]) {
let src = manager.getEntry(_attrs["src"], false, true);
if (typeof src != "undefined" && src !== null) {
if (typeof src == "object") {
src = egw().link('/index.php', src);
}
_attrs["src"] = src;
}
}
}
}
set_label(_value) {
this.options.label = _value;
_value = this.egw().lang(_value);
// label is NOT the alt attribute in eTemplate, but the title/tooltip
this.image.attr("alt", _value).attr("title", _value);
}
setValue(_value) {
// Value is src, images don't get IDs
this.set_src(_value);
}
set_href(_value) {
if (!this.isInTree()) {
return false;
}
this.options.href = _value;
this.image.wrapAll('<a href="' + _value + '"></a>"');
let href = this.options.href;
let popup = this.options.extra_link_popup;
let target = this.options.extra_link_target;
let self = this;
this.image.click(function (e) {
if (self.options.expose_view) {
/*
TODO: Fix after implementing EXPOSE mixin class
*/
//self._init_blueimp_gallery(e,_value);
e.stopImmediatePropagation();
}
else {
egw.open_link(href, target, popup);
}
e.preventDefault();
return false;
});
return true;
}
/**
* Set image src
*
* @param {string} _value image, app/image or url
* @return {boolean} true if image was found, false if not (image is either not displayed or default_src is used)
*/
set_src(_value) {
if (!this.isInTree()) {
return false;
}
this.options.src = _value;
// allow url's too
if (_value[0] == '/' || _value.substr(0, 4) == 'http' || _value.substr(0, 5) == 'data:') {
this.image.attr('src', _value).show();
return true;
}
let src = this.egw().image(_value);
if (src) {
this.image.attr("src", src).show();
return true;
}
src = null;
if (this.options.default_src) {
src = this.egw().image(this.options.default_src);
}
if (src) {
this.image.attr("src", src).show();
}
else {
this.image.css("display", "none");
}
return false;
}
/**
* Function to get media content to feed the expose
* @param {type} _value
*/
getMedia(_value) {
let base_url = egw.webserverUrl.match(/^\/ig/) ? egw(window).window.location.origin + egw.webserverUrl + '/' : egw.webserverUrl + '/';
let mediaContent = [];
if (_value) {
mediaContent = [{
title: this.options.label,
href: base_url + _value,
type: this.options.type + "/*",
thumbnail: base_url + _value
}];
}
return mediaContent;
}
/**
* Implementation of "et2_IDetachedDOM" for fast viewing in gridview
*
* @param {array} _attrs
*/
getDetachedAttributes(_attrs) {
_attrs.push("src", "label", "href");
}
getDetachedNodes() {
return [this.image[0]];
}
setDetachedAttributes(_nodes, _values) {
// Set the given DOM-Nodes
this.image = jQuery(_nodes[0]);
// Set the attributes
if (_values["src"]) {
this.set_src(_values["src"]);
}
// Not valid, but we'll deal
if (_values["value"]) {
this.setValue(_values["value"]);
}
if (_values["label"]) {
this.set_label(_values["label"]);
}
if (_values["href"]) {
this.image.addClass('et2_clickable');
this.set_href(_values["href"]);
}
}
}
et2_image._attributes = {
"src": {
"name": "Image",
"type": "string",
"description": "Displayed image"
},
default_src: {
name: "Default image",
type: "string",
description: "Image to use if src is not found"
},
"href": {
"name": "Link Target",
"type": "string",
"description": "Link URL, empty if you don't wan't to display a link.",
"default": et2_no_init
},
"extra_link_target": {
"name": "Link target",
"type": "string",
"default": "_self",
"description": "Link target descriptor"
},
"extra_link_popup": {
"name": "Popup",
"type": "string",
"description": "widthxheight, if popup should be used, eg. 640x480"
},
"imagemap": {
// TODO: Do something with this
"name": "Image map",
"description": "Currently not implemented"
},
"label": {
"name": "Label",
"type": "string",
"description": "Label for image"
},
"expose_view": {
name: "Expose view",
type: "boolean",
default: false,
description: "Clicking on an image with href value would popup an expose view, and will show image referenced by href."
}
};
et2_image.legacyOptions = ["href", "extra_link_target", "imagemap", "extra_link_popup", "id"];
et2_register_widget(et2_image, ["image"]);
/**
* Widget displaying an application icon
*/
export class et2_appicon extends et2_image {
set_src(_app) {
if (!_app)
_app = this.egw().app_name();
this.image.addClass('et2_appicon');
return super.set_src(_app == 'sitemgr-link' ? 'sitemgr/sitemgr-link' : // got removed from jdots
(this.egw().app(_app, 'icon_app') || _app) + '/' + (this.egw().app(_app, 'icon') || 'navbar'));
}
}
et2_appicon._attributes = {
default_src: {
name: "Default image",
type: "string",
default: "nonav",
description: "Image to use if there is no application icon"
}
};
et2_register_widget(et2_appicon, ["appicon"]);
/**
* Avatar widget to display user profile picture or
* user letter avatar based on user's firstname lastname.
*
* @augments et2_baseWidget
*/
export class et2_avatar extends et2_image {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_avatar._attributes, _child || {}));
if (this.options.frame == 'circle') {
this.image.attr('style', 'border-radius:50%');
}
if (this.options.contact_id)
this.setValue(this.options.contact_id);
}
/**
* Generate letter avatar with given data
* @param {type} _fname
* @param {type} _lname
* @param {type} _id
* @returns {string} return data url
*/
static lavatar(_fname, _lname, _id) {
let str = _fname + _lname + _id;
let getBgColor = function (_str) {
let hash = 0;
for (let i = 0; i < _str.length; i++) {
hash = _str[i].charCodeAt(0) + hash;
}
return et2_avatar.LAVATAR_BG_COLORS[hash % et2_avatar.LAVATAR_BG_COLORS.length];
};
let bg = getBgColor(str);
let size = et2_avatar.LAVATAR_SIZE * (window.devicePixelRatio ? window.devicePixelRatio : 1);
let text = (_fname ? _fname[0].toUpperCase() : "") + (_lname ? _lname[0].toUpperCase() : "");
let canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
let context = canvas.getContext("2d");
context.fillStyle = bg;
context.fillRect(0, 0, canvas.width, canvas.height);
context.font = Math.round(canvas.width / 2) + "px Arial";
context.textAlign = "center";
context.fillStyle = et2_avatar.LAVATAR_TEXT_COLOR;
context.fillText(text, size / 2, size / 1.5);
let dataURL = canvas.toDataURL();
canvas.remove();
return dataURL;
}
/**
* Function runs after uplaod in avatar dialog is finished and it tries to
* update image and cropper container.
* @param {type} e
*/
static uploadAvatar_onFinish(e) {
let file = e.data.resumable.files[0].file;
let reader = new FileReader();
reader.onload = function (e) {
jQuery('#_cropper_image').attr('src', e.target.result);
jQuery('#_cropper_image').cropper('replace', e.target.result);
};
reader.readAsDataURL(file);
}
/**
* Function to set contact id
* contact id could be in one of these formats:
* 'number', will be consider as contact_id
* 'contact:number', similar to above
* 'account:number', will be consider as account id
* @example: contact_id = "account:4"
*
* @param {string} _contact_id contact id could be as above mentioned formats
*/
set_contact_id(_contact_id) {
let params = {};
let id = 'contact_id';
this.image.addClass('et2_avatar');
if (!_contact_id) {
_contact_id = this.egw().user('account_id');
}
else if (_contact_id.match(/account:/)) {
id = 'account_id';
_contact_id = _contact_id.replace('account:', '');
}
else {
id = 'contact_id';
_contact_id = _contact_id.replace('contact:', '');
}
// if our src (incl. cache-buster) already includes the correct id, use that one
if (this.options.src && this.options.src.match("(&|\\?)contact_id=" + _contact_id + "(&|\\$)")) {
return;
}
params[id] = _contact_id;
this.set_src(egw.link('/api/avatar.php', params));
}
/**
* Function to set value
*/
setValue(_value) {
this.set_contact_id(_value);
}
/**
* Implementation of "et2_IDetachedDOM" for fast viewing in gridview
*/
getDetachedAttributes(_attrs) {
_attrs.push("contact_id", "label", "href");
}
setDetachedAttributes(_nodes, _values) {
// Set the given DOM-Nodes
this.image = jQuery(_nodes[0]);
if (_values["contact_id"]) {
this.set_contact_id(_values["contact_id"]);
}
if (_values["label"]) {
this.set_label(_values["label"]);
}
if (_values["href"]) {
this.image.addClass('et2_clickable');
this.set_href(_values["href"]);
}
}
/**
* Build Editable Mask Layer (EML) in order to show edit/delete actions
* on top of profile picture.
* @param {boolean} _noDelete disable delete button in initialization
*/
_buildEditableLayer(_noDelete) {
let self = this;
// editable mask layer (eml)
let wrapper = jQuery(document.createElement('div')).addClass('avatar').insertAfter(this.image);
this.image.appendTo(wrapper);
let eml = jQuery(document.createElement('div'))
.addClass('eml')
.insertAfter(this.image);
// edit button
jQuery(document.createElement('div'))
.addClass('emlEdit')
.click(function () {
let buttons = [
{ "button_id": 1, "text": self.egw().lang('save'), id: 'save', image: 'check', "default": true },
{ "button_id": 0, "text": self.egw().lang('cancel'), id: 'cancel', image: 'cancelled' }
];
let dialog = function (_title, _value, _buttons, _egw_or_appname) {
return et2_createWidget("dialog", {
callback: function (_buttons, _value) {
if (_buttons == 'save') {
let canvas = jQuery('#_cropper_image').cropper('getCroppedCanvas');
self.image.attr('src', canvas.toDataURL("image/jpeg", 1.0));
self.egw().json('addressbook.addressbook_ui.ajax_update_photo', [self.getInstanceManager().etemplate_exec_id, canvas.toDataURL('image/jpeg', 1.0)], function (res) {
if (res) {
del.show();
}
}).sendRequest();
}
},
title: _title || egw.lang('Input required'),
buttons: _buttons || et2_dialog.BUTTONS_OK_CANCEL,
value: {
content: _value
},
width: "90%",
height: "450",
resizable: false,
position: "top+10",
template: egw.webserverUrl + '/api/templates/default/avatar_edit.xet?2'
}, et2_dialog._create_parent(_egw_or_appname));
};
dialog(egw.lang('Edit avatar'), self.options, buttons, null);
})
.appendTo(eml);
// delete button
var del = jQuery(document.createElement('div'))
.addClass('emlDelete')
.click(function () {
et2_dialog.show_dialog(function (_btn) {
if (_btn == et2_dialog.YES_BUTTON) {
self.egw().json('addressbook.addressbook_ui.ajax_update_photo', [self.getInstanceManager().etemplate_exec_id, null], function (res) {
if (res) {
self.image.attr('src', '');
del.hide();
egw.refresh('Avatar Deleted.', egw.app_name());
}
}).sendRequest();
}
}, egw.lang('Delete this photo?'), egw.lang('Delete'), null, et2_dialog.BUTTONS_YES_NO);
})
.appendTo(eml);
if (_noDelete)
del.hide();
// invisible the mask
eml.css('opacity', '0');
eml.parent().css('position', "relative");
// bind handler for activating actions on editable mask
eml.on({
mouseover: function () { eml.css('opacity', '0.9'); },
mouseout: function () { eml.css('opacity', '0'); }
});
}
/**
* We need to build the Editable Mask Layer after widget gets loaded
*/
doLoadingFinished() {
super.doLoadingFinished();
let self = this;
if (this.options.contact_id && this.options.editable) {
egw(window).json('addressbook.addressbook_ui.ajax_noPhotoExists', [this.options.contact_id], function (noPhotoExists) {
if (noPhotoExists)
self.image.attr('src', '');
self._buildEditableLayer(noPhotoExists);
}).sendRequest(true);
}
if (this.options.crop) {
jQuery(this.image).cropper({
aspectRatio: 1 / 1,
crop: function (e) {
console.log(e);
}
});
}
return true;
}
}
et2_avatar._attributes = {
"contact_id": {
name: "Contact id",
type: "string",
default: "",
description: "Contact id should be either user account_id {account:number} or contact_id {contact:number or number}"
},
"default_src": {
"ignore": true
},
"frame": {
name: "Avatar frame",
type: "string",
default: "circle",
description: "Define the shape of frame that avatar will be shown inside it. it can get {circle,rectangle} values which default value is cicle."
},
editable: {
name: "Edit avatar",
type: "boolean",
default: false,
description: "Make avatar widget editable to be able to crop profile picture or upload a new photo"
},
crop: {
name: "Crop avatar",
type: "boolean",
default: false,
description: "Create crop container and cropping feature"
}
};
/**
* background oolor codes
*/
et2_avatar.LAVATAR_BG_COLORS = [
'#5a8770', '#b2b7bb', '#6fa9ab', '#f5af29',
'#0088b9', '#f18636', '#d93a37', '#a6b12e',
'#0088b9', '#f18636', '#d93a37', '#a6b12e',
'#5c9bbc', '#f5888d', '#9a89b5', '#407887',
'#9a89b5', '#5a8770', '#d33f33', '#a2b01f',
'#f0b126', '#0087bf', '#f18636', '#0087bf',
'#b2b7bb', '#72acae', '#9c8ab4', '#5a8770',
'#eeb424', '#407887'
];
/**
* Text color
*/
et2_avatar.LAVATAR_TEXT_COLOR = '#ffffff';
et2_avatar.LAVATAR_SIZE = 128;
et2_register_widget(et2_avatar, ["avatar"]);
/**
* Avatar readonly widget to only display user profile picture or
* user letter avatar based on user's firstname lastname.
*/
export class et2_avatar_ro extends et2_avatar {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_avatar_ro._attributes, _child || {}));
this.options.editable = false;
}
}
et2_register_widget(et2_avatar_ro, ["avatar_ro"]);
/**
* Letter Avatar widget to display user profile picture (given url) or
* user letter avatar based on user's firstname lastname.
*
* It will use client-side lavatar if all the following conditions are met:
* - contact_id, lname and fname are all set.
* - the given src url includes flag of lavatar=1 which means there's
* no personal avatar set for the contact yet.
*
* @augments et2_baseWidget
*/
export class et2_lavatar extends et2_image {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_lavatar._attributes, _child || {}));
}
set_src(_url) {
if (_url && decodeURIComponent(_url).match("lavatar=1") && (this.options.fname || this.options.lname) && this.options.contact_id) {
this.set_src(et2_avatar.lavatar(this.options.fname, this.options.lname, this.options.contact_id));
return false;
}
super.set_src(_url);
}
}
et2_lavatar._attributes = {
lname: {
name: "last name",
type: "string",
default: "",
description: ""
},
fname: {
name: "first name",
type: "string",
default: "",
description: ""
},
contact_id: {
name: "contact id",
type: "string",
default: "",
description: ""
}
};
et2_register_widget(et2_lavatar, ["lavatar"]);
//# sourceMappingURL=et2_widget_image.js.map

View File

@ -1,335 +0,0 @@
/**
* EGroupware eTemplate2 - JS Itempicker object
* derived from et2_link_entry widget @copyright 2011 Nathan Gray
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Christian Binder
* @author Nathan Gray
* @copyright 2012 Christian Binder
* @copyright 2011 Nathan Gray
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
et2_core_valueWidget;
et2_extension_itempicker_actions;
egw_action.egw_action_common;
*/
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_csvSplit, et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "itempicker" XET-Tag
*
* @augments et2_inputWidget
*/
export class et2_itempicker extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_itempicker
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_itempicker._attributes, _child || {}));
this.last_search = ""; // Remember last search value
this.action = null; // Action function for button
this.current_app = ""; // Remember currently chosen application
this.div = null;
this.left = null;
this.right = null;
this.right_container = null;
this.app_select = null;
this.search = null;
this.button_action = null;
this.itemlist = null;
this.div = null;
this.left = null;
this.right = null;
this.right_container = null;
this.app_select = null;
this.search = null;
this.button_action = null;
this.itemlist = null;
if (this.options.action !== null && typeof this.options.action == "string") {
this.action = new egwFnct(this, "javaScript:" + this.options.action);
}
else {
console.log("itempicker widget: no action provided for button");
}
this.createInputWidget();
}
clearSearchResults() {
this.search.val("");
this.itemlist.html("");
this.search.focus();
this.clear.hide();
}
createInputWidget() {
let _self = this;
this.div = jQuery(document.createElement("div"));
this.left = jQuery(document.createElement("div"));
this.right = jQuery(document.createElement("div"));
this.right_container = jQuery(document.createElement("div"));
this.app_select = jQuery(document.createElement("ul"));
this.search = jQuery(document.createElement("input"));
this.clear = jQuery(document.createElement("span"));
this.itemlist = jQuery(document.createElement("div"));
// Container elements
this.div.addClass("et2_itempicker");
this.left.addClass("et2_itempicker_left");
this.right.addClass("et2_itempicker_right");
this.right_container.addClass("et2_itempicker_right_container");
// Application select
this.app_select.addClass("et2_itempicker_app_select");
let item_count = 0;
for (let key in this.options.select_options) {
let img_icon = this.egw().image(key + "/navbar");
if (img_icon === null) {
continue;
}
let img = jQuery(document.createElement("img"));
img.attr("src", img_icon);
let item = jQuery(document.createElement("li"));
item.attr("id", key)
.click(function () {
_self.selectApplication(jQuery(this));
})
.append(img);
if (item_count == 0) {
this.selectApplication(item); // select first item by default
}
this.app_select.append(item);
item_count++;
}
// Search input field
this.search.addClass("et2_itempicker_search");
this.search.keyup(function () {
let request = {};
request.term = jQuery(this).val();
_self.query(request);
});
this.set_blur(this.options.blur, this.search);
// Clear button for search
this.clear
.addClass("et2_itempicker_clear ui-icon ui-icon-close")
.click(function () {
_self.clearSearchResults();
})
.hide();
// Action button
this.button_action = et2_createWidget("button", {});
jQuery(this.button_action.getDOMNode()).addClass("et2_itempicker_button_action");
this.button_action.set_label(this.egw().lang(this.options.action_label));
this.button_action.click = function () { _self.doAction(); };
// Itemlist
this.itemlist.attr("id", "itempicker_itemlist");
this.itemlist.addClass("et2_itempicker_itemlist");
// Put everything together
this.left.append(this.app_select);
this.right_container.append(this.search);
this.right_container.append(this.clear);
this.right_container.append(this.button_action.getDOMNode());
this.right_container.append(this.itemlist);
this.right.append(this.right_container);
this.div.append(this.right); // right before left to have a natural
this.div.append(this.left); // z-index for left div over right div
this.setDOMNode(this.div[0]);
}
doAction() {
if (this.action !== null) {
let data = {};
data.app = this.current_app;
data.value = this.options.value;
data.checked = this.getSelectedItems();
return this.action.exec(this, data);
}
return false;
}
getSelectedItems() {
let items = [];
jQuery(this.itemlist).children("ul").children("li.selected").each(function (index) {
items[index] = jQuery(this).attr("id");
});
return items;
}
/**
* Ask server for entries matching selected app/type and filtered by search string
*/
query(request) {
if (request.term.length < 3) {
return true;
}
// Remember last search
this.last_search = request.term;
// Allow hook / tie in
if (this.options.query && typeof this.options.query == 'function') {
if (!this.options.query(request, response))
return false;
}
//if(request.term in this.cache) {
// return response(this.cache[request.term]);
//}
this.itemlist.addClass("loading");
this.clear.css("display", "inline-block");
egw.json("EGroupware\\Api\\Etemplate\\Widget\\ItemPicker::ajax_item_search", [this.current_app, '', request.term, request.options], this.queryResults, this, true, this).sendRequest();
}
/**
* Server found some results for query
*/
queryResults(data) {
this.itemlist.removeClass("loading");
this.updateItemList(data);
}
selectApplication(app) {
this.clearSearchResults();
jQuery(".et2_itempicker_app_select li").removeClass("selected");
app.addClass("selected");
this.current_app = app.attr("id");
return true;
}
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) {
let placeholder = _value;
if (e.data.val() == placeholder)
e.data.val("");
}).blur(input, function (e) {
let placeholder = _value;
if (e.data.val() == "")
e.data.val(placeholder);
});
if (input.val() == "")
input.val(_value);
}
}
else {
this.search.removeAttr("placeholder");
}
}
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
_attrs["select_options"] = {};
if (_attrs["application"]) {
let apps = et2_csvSplit(_attrs["application"], null, ",");
for (let 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');
}
// 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"] = {};
}
}
updateItemList(data) {
let list = jQuery(document.createElement("ul"));
let item_count = 0;
for (let id in data) {
let item = jQuery(document.createElement("li"));
if (item_count % 2 == 0) {
item.addClass("row_on");
}
else {
item.addClass("row_off");
}
item.attr("id", id)
.html(data[id])
.click(function (e) {
if (e.ctrlKey || e.metaKey) {
// add to selection
jQuery(this).addClass("selected");
}
else if (e.shiftKey) {
// select range
let start = jQuery(this).siblings(".selected").first();
if ((start === null || start === void 0 ? void 0 : start.length) == 0) {
// no start item - cannot select range - select single item
jQuery(this).addClass("selected");
return true;
}
let end = jQuery(this);
// swap start and end if start appears after end in dom hierarchy
if (start.index() > end.index()) {
let startOld = start;
start = end;
end = startOld;
}
// select start to end
start.addClass("selected");
start.nextUntil(end).addClass("selected");
end.addClass("selected");
}
else {
// select single item
jQuery(this).siblings(".selected").removeClass("selected");
jQuery(this).addClass("selected");
}
});
list.append(item);
item_count++;
}
this.itemlist.html(list);
}
}
et2_itempicker._attributes = {
"action": {
"name": "Action callback",
"type": "string",
"default": false,
"description": "Callback for action. Must be a function(context, data)"
},
"action_label": {
"name": "Action label",
"type": "string",
"default": "Action",
"description": "Label for action button"
},
"application": {
"name": "Application",
"type": "string",
"default": "",
"description": "Limit to the listed application or applications (comma separated)"
},
"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."
},
"value": {
"name": "value",
"type": "any",
"default": "",
"description": "Optional itempicker value(s) - can be used for e.g. environmental information"
},
"query": {
"name": "Query callback",
"type": "any",
"default": false,
"description": "Callback before query to server. Must return true, or false to abort query."
}
};
et2_itempicker.legacyOptions = ["application"];
et2_register_widget(et2_itempicker, ["itempicker"]);
//# sourceMappingURL=et2_widget_itempicker.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,176 +0,0 @@
/**
* EGroupware eTemplate2 - JS Number object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
/*egw:uses
et2_widget_textbox;
*/
import { et2_textbox, et2_textbox_ro } from "./et2_widget_textbox";
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
/**
* Class which implements the "int" and textbox type=float XET-Tags
*
* @augments et2_textbox
*/
export class et2_number extends et2_textbox {
/**
* Constructor
*
* @memberOf et2_number
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_number._attributes, _child || {}));
this.min = null;
this.max = null;
this.step = null;
}
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
if (typeof _attrs.validator == 'undefined') {
_attrs.validator = _attrs.type == 'float' ? '/^-?[0-9]*[,.]?[0-9]*$/' : '/^-?[0-9]*$/';
}
}
/**
* Clientside validation using regular expression in "validator" attribute
*
* @param {array} _messages
*/
isValid(_messages) {
let ok = true;
// if we have a html5 validation error, show it, as this.input.val() will be empty!
if (this.input && this.input[0] && this.input[0].validationMessage && !this.input[0].validity.stepMismatch) {
_messages.push(this.input[0].validationMessage);
ok = false;
}
return super.isValid(_messages) && ok;
}
createInputWidget() {
this.input = jQuery(document.createElement("input"));
this.input.attr("type", "number");
this.input.addClass("et2_textbox");
// bind invalid event to change, to trigger our validation
this.input.on('invalid', jQuery.proxy(this.change, this));
if (this.options.onkeypress && typeof this.options.onkeypress == 'function') {
var self = this;
this.input.keypress(function (_ev) {
return self.options.onkeypress.call(this, _ev, self);
});
}
this.setDOMNode(this.input[0]);
}
/**
* Set input widget size
*
* Overwritten from et2_textbox as input type=number seems to ignore size,
* therefore we set width in em instead, if not et2_fullWidth given.
*
* @param _size Rather arbitrary size units, approximately characters
*/
set_size(_size) {
if (typeof _size != 'undefined' && _size != this.input.attr("size")) {
this.size = _size;
this.input.attr("size", this.size);
if (typeof this.options.class == 'undefined' || this.options.class.search('et2_fullWidth') == -1) {
this.input.css('width', _size + 'em');
}
}
}
set_min(_value) {
this.min = _value;
if (this.min == null) {
this.input.removeAttr("min");
}
else {
this.input.attr("min", this.min);
}
}
set_max(_value) {
this.max = _value;
if (this.max == null) {
this.input.removeAttr("max");
}
else {
this.input.attr("max", this.max);
}
}
set_step(_value) {
this.step = _value;
if (this.step == null) {
this.input.removeAttr("step");
}
else {
this.input.attr("step", this.step);
}
}
}
et2_number._attributes = {
"value": {
"type": "float"
},
// Override default width, numbers are usually shorter
"size": {
"default": 5
},
"min": {
"name": "Minimum",
"type": "any",
"default": et2_no_init,
"description": "Minimum allowed value"
},
"max": {
"name": "Maximum",
"type": "any",
"default": et2_no_init,
"description": "Maximum allowed value"
},
"step": {
"name": "step value",
"type": "integer",
"default": et2_no_init,
"description": "Step attribute specifies the interval between legal numbers"
},
"precision": {
// TODO: Implement this in some nice way other than HTML5's step attribute
"name": "Precision",
"type": "integer",
"default": et2_no_init,
"description": "Allowed precision - # of decimal places",
"ignore": true
}
};
et2_register_widget(et2_number, ["int", "integer", "float"]);
/**
* Extend read-only to tell it to ignore special attributes, which
* would cause warnings otherwise
* @augments et2_textbox_ro
* @class
*/
export class et2_number_ro extends et2_textbox_ro {
set_value(_value) {
if (typeof this.options.precision != 'undefined' && "" + _value != "") {
_value = parseFloat(_value).toFixed(this.options.precision);
}
super.set_value(_value);
}
}
et2_number_ro._attributes = {
min: { ignore: true },
max: { ignore: true },
precision: {
name: "Precision",
type: "integer",
default: et2_no_init,
description: "Allowed precision - # of decimal places",
ignore: true
},
value: { type: "float" }
};
et2_register_widget(et2_number_ro, ["int_ro", "integer_ro", "float_ro"]);
//# sourceMappingURL=et2_widget_number.js.map

View File

@ -1,261 +0,0 @@
/**
* EGroupware eTemplate2 - JS Textbox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
et2_core_valueWidget;
*/
import './et2_core_common';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_textbox, et2_textbox_ro } from "./et2_widget_textbox";
import { et2_dialog } from "./et2_widget_dialog";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "textbox" XET-Tag
*
* @augments et2_inputWidget
*/
export class et2_password extends et2_textbox {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_password._attributes, _child || {}));
// The password is stored encrypted server side, and passed encrypted.
// This flag is for if we've decrypted the password to show it already
this.encrypted = true;
if (this.options.plaintext) {
this.encrypted = false;
}
}
createInputWidget() {
this.wrapper = jQuery(document.createElement("div"))
.addClass("et2_password");
this.input = jQuery(document.createElement("input"));
this.input.attr("type", "password");
// Make autocomplete default value off for password field
// seems browsers not respecting 'off' anymore and started to
// implement a new key called "new-password" considered as switching
// autocomplete off.
// https://developer.mozilla.org/en-US/docs/Web/Security/Securing_your_site/Turning_off_form_autocompletion
if (this.options.autocomplete === "" || this.options.autocomplete == "off")
this.options.autocomplete = "new-password";
if (this.options.size) {
this.set_size(this.options.size);
}
if (this.options.blur) {
this.set_blur(this.options.blur);
}
if (this.options.readonly) {
this.set_readonly(true);
}
this.input.addClass("et2_textbox")
.appendTo(this.wrapper);
this.setDOMNode(this.wrapper[0]);
if (this.options.value) {
this.set_value(this.options.value);
}
if (this.options.onkeypress && typeof this.options.onkeypress == 'function') {
var self = this;
this.input.on('keypress', function (_ev) {
return self.options.onkeypress.call(this, _ev, self);
});
}
this.input.on('change', function () {
this.encrypted = false;
}.bind(this));
// Show button is needed from start as you can't turn viewable on via JS
let attrs = {
class: "show_hide",
image: "visibility",
onclick: this.toggle_visibility.bind(this),
statustext: this.egw().lang("Show password")
};
if (this.options.viewable) {
this.show_button = et2_createWidget("button", attrs, this);
}
}
getInputNode() {
return this.input[0];
}
/**
* Override the parent set_id method to manuipulate the input DOM node
*
* @param {type} _value
* @returns {undefined}
*/
set_id(_value) {
super.set_id(_value);
// Remove the name attribute inorder to affect autocomplete="off"
// for no password save. ATM seems all browsers ignore autocomplete for
// input field inside the form
if (this.options.autocomplete === "off")
this.input.removeAttr('name');
}
/**
* Set whether or not the password is allowed to be shown in clear text.
*
* @param viewable
*/
set_viewable(viewable) {
this.options.viewable = viewable;
if (viewable) {
jQuery('.show_hide', this.wrapper).show();
}
else {
jQuery('.show_hide', this.wrapper).hide();
}
}
/**
* Turn on or off the suggest password button.
*
* When clicked, a password of the set length will be generated.
*
* @param length Length of password to generate. 0 to disable.
*/
set_suggest(length) {
if (typeof length !== "number") {
length = typeof length === "string" ? parseInt(length) : (length ? et2_password.DEFAULT_LENGTH : 0);
}
this.options.suggest = length;
if (length && !this.suggest_button) {
let attrs = {
class: "generate_password",
image: "generate_password",
onclick: this.suggest_password.bind(this),
statustext: this.egw().lang("Suggest password")
};
this.suggest_button = et2_createWidget("button", attrs, this);
if (this.parentNode) {
// Turned on after initial load, need to run loadingFinished()
this.suggest_button.loadingFinished();
}
}
if (length) {
jQuery('.generate_password', this.wrapper).show();
}
else {
jQuery('.generate_password', this.wrapper).hide();
}
}
/**
* If the password is viewable, toggle the visibility.
* If the password is still encrypted, we'll ask for the user's password then have the server decrypt it.
*
* @param on
*/
toggle_visibility(on) {
if (typeof on !== "boolean") {
on = this.input.attr("type") == "password";
}
if (!this.options.viewable) {
this.input.attr("type", "password");
return;
}
if (this.show_button) {
this.show_button.set_image(this.egw().image(on ? 'visibility_off' : 'visibility'));
}
// If we are not encrypted or not showing it, we're done
if (!this.encrypted || !on) {
this.input.attr("type", on ? "textbox" : "password");
return;
}
// Need username & password to decrypt
let callback = function (button, user_password) {
if (button == et2_dialog.CANCEL_BUTTON) {
return this.toggle_visibility(false);
}
let request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Password::ajax_decrypt", [user_password, this.options.value], function (decrypted) {
if (decrypted) {
this.encrypted = false;
this.input.val(decrypted);
this.input.attr("type", "textbox");
}
else {
this.set_validation_error(this.egw().lang("invalid password"));
window.setTimeout(function () {
this.set_validation_error(false);
}.bind(this), 2000);
}
}, this, true, this).sendRequest();
}.bind(this);
let prompt = et2_dialog.show_prompt(callback, this.egw().lang("Enter your password"), this.egw().lang("Authenticate"));
// Make the password prompt a password field
prompt.div.on("load", function () {
jQuery(prompt.template.widgetContainer.getWidgetById('value').getInputNode())
.attr("type", "password");
});
}
/**
* Ask the server for a password suggestion
*/
suggest_password() {
// They need to see the suggestion
this.encrypted = false;
this.options.viewable = true;
this.toggle_visibility(true);
let suggestion = "Suggestion";
let request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Password::ajax_suggest", [this.options.suggest], function (suggestion) {
this.encrypted = false;
this.input.val(suggestion);
this.input.trigger('change');
// Check for second password, update it too
let two = this.getParent().getWidgetById(this.id + '_2');
if (two && two.getType() == this.getType()) {
two.options.viewable = true;
two.toggle_visibility(true);
two.set_value(suggestion);
}
}, this, true, this).sendRequest();
}
destroy() {
super.destroy();
}
getValue() {
return this.input.val();
}
}
et2_password._attributes = {
"autocomplete": {
"name": "Autocomplete",
"type": "string",
"default": "off",
"description": "Whether or not browser should autocomplete that field: 'on', 'off', 'default' (use attribute from form). Default value is set to off."
},
"viewable": {
"name": "Viewable",
"type": "boolean",
"default": false,
"description": "Allow password to be shown"
},
"plaintext": {
name: "Plaintext",
type: "boolean",
default: true,
description: "Password is plaintext"
},
"suggest": {
name: "Suggest password",
type: "integer",
default: 0,
description: "Suggest password length (0 for off)"
}
};
et2_password.DEFAULT_LENGTH = 16;
et2_register_widget(et2_password, ["passwd"]);
export class et2_password_ro extends et2_textbox_ro {
set_value(value) {
this.value_span.text(value ? "********" : "");
}
}
et2_register_widget(et2_password_ro, ["passwd_ro"]);
//# sourceMappingURL=et2_widget_password.js.map

View File

@ -1,347 +0,0 @@
/**
* EGroupware eTemplate2 - JS Portlet object - used by Home
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package home
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_baseWidget;
*/
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_action_object_impl } from "./et2_core_DOMWidget";
import { egw } from "../jsapi/egw_global";
import { et2_no_init } from "./et2_core_common";
import { et2_IResizeable } from "./et2_core_interfaces";
import { et2_dialog } from "./et2_widget_dialog";
import { egw_getAppObjectManager, egwActionObject } from "../egw_action/egw_action.js";
/**
* Class which implements the UI of a Portlet
*
* This manages the frame and decoration, but also provides the UI for properties.
*
* Portlets are only internal to EGroupware.
*
* Home does not fully implement WSRP, but tries not to conflict, ether.
* @link http://docs.oasis-open.org/wsrp/v2/wsrp-2.0-spec-os-01.html
* @augments et2_baseWidget
*/
export class et2_portlet extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_portlet
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_portlet._attributes, _child || {}));
this.GRID = 55;
/**
* These are the "normal" actions that every portlet is expected to have.
* The widget provides default actions for all of these, but they can
* be added to or overridden if needed by setting the action attribute.
*/
this.default_actions = {
edit_settings: {
icon: "edit",
caption: "Configure",
"default": true,
hideOnDisabled: true,
group: "portlet"
},
remove_portlet: {
icon: "delete",
caption: "Remove",
group: "portlet"
}
};
let self = this;
// Create DOM nodes
this.div = jQuery(document.createElement("div"))
.addClass(this.options.class)
.addClass("ui-widget ui-widget-content ui-corner-all")
.addClass("et2_portlet")
/* Gridster */
.attr("data-sizex", this.options.width)
.attr("data-sizey", this.options.height)
.attr("data-row", this.options.row)
.attr("data-col", this.options.col)
.resizable({
autoHide: true,
grid: this.GRID,
//containment: this.getParent().getDOMNode(),
stop: function (event, ui) {
self.set_width(Math.round(ui.size.width / self.GRID));
self.set_height(Math.round(ui.size.height / self.GRID));
self.egw().jsonq("home.home_ui.ajax_set_properties", [self.id, {}, {
width: self.options.width,
height: self.options.height
}], null, self);
// Tell children
self.iterateOver(function (widget) { widget.resize(); }, null, et2_IResizeable);
}
});
this.header = jQuery(document.createElement("div"))
.attr('id', this.getInstanceManager().uniqueId + '_' + this.id.replace(/\./g, '-') + '_header')
.addClass("ui-widget-header ui-corner-all")
.appendTo(this.div)
.html(this.options.title);
this.content = jQuery(document.createElement("div"))
.attr('id', this.getInstanceManager().uniqueId + '_' + this.id.replace(/\./g, '-') + '_content')
.appendTo(this.div);
this.setDOMNode(this.div[0]);
}
destroy() {
for (let i = 0; i < this._children.length; i++) {
// Check for child is a different template and clear it,
// since it won't be cleared by destroy()
if (this._children[i].getInstanceManager() != this.getInstanceManager()) {
this._children[i].getInstanceManager().clear();
}
}
super.destroy();
}
doLoadingFinished() {
this.set_color(this.options.color);
return true;
}
/**
* If anyone asks, return the content node, so content goes inside
*/
getDOMNode(_sender) {
if (typeof _sender != 'undefined' && _sender != this) {
return this.content[0];
}
return super.getDOMNode(_sender);
}
/**
* Overriden from parent to add in default actions
*/
set_actions(actions) {
// Set targets for actions
let defaults = {};
for (let action_name in this.default_actions) {
defaults[action_name] = this.default_actions[action_name];
// Translate caption here, as translations aren't available earlier
defaults[action_name].caption = this.egw().lang(this.default_actions[action_name].caption);
if (typeof this[action_name] == "function") {
defaults[action_name].onExecute = jQuery.proxy(this[action_name], this);
}
}
// Add in defaults, but let provided actions override them
this.options.actions = jQuery.extend(true, {}, defaults, actions);
super.set_actions(this.options.actions);
}
/**
* Override _link_actions to remove edit action, if there is no settings
*
* @param actions
*/
_link_actions(actions) {
// Get the top level element
let objectManager = egw_getAppObjectManager(true);
let widget_object = objectManager.getObjectById(this.id);
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = objectManager.insertObject(false, new egwActionObject(this.id, objectManager, new et2_action_object_impl(this).getAOI(), this._actionManager || objectManager.manager.getActionById(this.id) || objectManager.manager));
}
// Delete all old objects
widget_object.clear();
// Go over the widget & add links - this is where we decide which actions are
// 'allowed' for this widget at this time
let action_links = [];
for (let i in actions) {
let id = typeof actions[i].id != 'undefined' ? actions[i].id : i;
let action = {
actionId: id,
enabled: true
};
// If there are no settings, there can be no customization, so remove the edit action
if (id == 'edit_settings' && (!this.options.settings || jQuery.isEmptyObject(this.options.settings))) {
this.egw().debug("log", "No settings for portlet %o, edit_settings action removed", this);
action.enabled = false;
}
action_links.push(action);
}
widget_object.updateActionLinks(action_links);
}
/**
* Create & show a dialog for customizing this portlet
*
* Properties for customization are sent in the 'settings' attribute
*/
edit_settings() {
let dialog = et2_createWidget("dialog", {
callback: jQuery.proxy(this._process_edit, this),
template: this.options.edit_template,
value: {
content: this.options.settings
},
buttons: et2_dialog.BUTTONS_OK_CANCEL
}, this);
// Set seperately to avoid translation
dialog.set_title(this.egw().lang("Edit") + " " + (this.options.title || ''));
}
_process_edit(button_id, value) {
if (button_id != et2_dialog.OK_BUTTON)
return;
// Save settings - server might reply with new content if the portlet needs an update,
// but ideally it doesn't
this.div.addClass("loading");
// Pass updated settings, unless we're removing
let settings = (typeof value == 'string') ? {} : this.options.settings || {};
this.egw().jsonq("home.home_ui.ajax_set_properties", [this.id, settings, value, this.settings ? this.settings.group : false], function (data) {
// This section not for us
if (!data || typeof data.attributes == 'undefined')
return false;
this.div.removeClass("loading");
this.set_value(data.content);
for (let key in data.attributes) {
if (typeof this["set_" + key] == "function") {
this["set_" + key].call(this, data.attributes[key]);
}
else if (this.attributes[key]) {
this.options[key] = data.attributes[key];
}
}
// Flagged as needing to edit settings? Open dialog
if (typeof data.edit_settings != 'undefined' && data.edit_settings) {
this.edit_settings();
}
// Only resize once, and only if needed
if (data.attributes.width || data.attributes.height) {
// Tell children
try {
this.iterateOver(function (widget) { widget.resize(); }, null, et2_IResizeable);
}
catch (e) {
// Something went wrong, but do not stop
egw.debug('warn', e, this);
}
}
}, this);
// Extend, not replace, because settings has types while value has just value
if (typeof value == 'object') {
jQuery.extend(this.options.settings, value);
}
}
/**
* Remove this portlet from the home page
*/
remove_portlet() {
let self = this;
et2_dialog.show_dialog(function (button_id) {
if (button_id != et2_dialog.OK_BUTTON)
return;
self._process_edit(button_id, '~remove~');
self.getParent().removeChild(self);
self.destroy();
}, this.egw().lang("Remove"), this.options.title, {}, et2_dialog.BUTTONS_OK_CANCEL, et2_dialog.QUESTION_MESSAGE);
}
/**
* Set the HTML content of the portlet
*
* @param value String HTML fragment
*/
set_value(value) {
this.content.html(value);
}
/**
* Set the content of the header
*
* @param value String HTML fragment
*/
set_title(value) {
this.header.contents()
.filter(function () {
return this.nodeType === 3;
})
.remove();
this.options.title = value;
this.header.append(value);
}
/**
* Let this portlet stand out a little by allowing a custom color
*/
set_color(color) {
this.options.color = color;
this.header.css("backgroundColor", color);
this.header.css('color', jQuery.Color(this.header.css("backgroundColor")).lightness() > 0.5 ? 'black' : 'white');
this.content.css("backgroundColor", color);
}
/**
* Set the number of grid cells this widget spans
*
* @param value int Number of horizontal grid cells
*/
set_width(value) {
this.options.width = value;
this.div.attr("data-sizex", value);
// Clear what's there from jQuery, we get width from CSS according to sizex
this.div.css('width', '');
}
/**
* Set the number of vertical grid cells this widget spans
*
* @param value int Number of vertical grid cells
*/
set_height(value) {
this.options.height = value;
this.div.attr("data-sizey", value);
// Clear what's there from jQuery, we get width from CSS according to sizey
this.div.css('height', '');
}
}
et2_portlet._attributes = {
"title": {
"name": "Title",
"description": "Goes in the little bit at the top with the icons",
"type": "string",
"default": ""
},
"edit_template": {
"name": "Edit template",
"description": "Custom eTemplate used to customize / set up the portlet",
"type": "string",
"default": egw.webserverUrl + "/home/templates/default/edit.xet"
},
"color": {
"name": "Color",
"description": "Set the portlet color",
"type": "string",
"default": ''
},
"settings": {
"name": "Customization settings",
"description": "Array of customization settings, similar in structure to preference settings",
"type": "any",
"default": et2_no_init
},
"actions": {
default: {}
},
"width": { "default": 2, "ignore": true },
"height": { "default": 1, "type": "integer" },
"rows": { "ignore": true, default: et2_no_init },
"cols": { "ignore": true, default: et2_no_init },
"resize_ratio": { "ignore": true, default: et2_no_init },
"row": {
"name": "Row",
"description": "Home page location (row) - handled by home app",
"default": 1
},
"col": {
"name": "Column",
"description": "Home page location(column) - handled by home app",
"default": 1
}
};
et2_register_widget(et2_portlet, ["portlet"]);
//# sourceMappingURL=et2_widget_portlet.js.map

View File

@ -1,154 +0,0 @@
/**
* EGroupware eTemplate2 - JS Progrss object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Ralf Becker
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_interfaces;
et2_core_valueWidget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "progress" XET-Tag
*
* @augments et2_valueWidget
*/
export class et2_progress extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_progress
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_progress._attributes, _child || {}));
this.progress = null;
let outer = document.createElement("div");
outer.className = "et2_progress";
this.progress = document.createElement("div");
this.progress.style.width = "0";
outer.appendChild(this.progress);
if (this.options.href) {
outer.className += ' et2_clickable';
}
if (this.options["class"]) {
outer.className += ' ' + this.options["class"];
}
this.setDOMNode(outer); // set's this.node = outer
}
click(e) {
super.click(e);
if (this.options.href) {
this.egw().open_link(this.options.href, this.options.extra_link_target, this.options.extra_link_popup);
}
}
// setting the value as width of the progress-bar
set_value(_value) {
super.set_value(_value);
_value = parseInt(_value) + "%"; // make sure we have percent attached
this.progress.style.width = _value;
if (!this.options.label)
this.set_label(_value);
}
// set's label as title of this.node
set_label(_value) {
this.node.title = _value;
}
// set's class of this.node; preserve baseclasses et2_progress and if this.options.href is set et2_clickable
set_class(_value) {
let baseClass = "et2_progress";
if (this.options.href) {
baseClass += ' et2_clickable';
}
this.node.setAttribute('class', baseClass + ' ' + _value);
}
set_href(_value) {
if (!this.isInTree()) {
return false;
}
this.options.href = _value;
if (_value) {
jQuery(this.node).addClass('et2_clickable')
.wrapAll('<a href="' + _value + '"></a>"');
let href = this.options.href;
let popup = this.options.extra_link_popup;
let target = this.options.extra_link_target;
jQuery(this.node).parent().click(function (e) {
egw.open_link(href, target, popup);
e.preventDefault();
return false;
});
}
else if (jQuery(this.node).parent('a').length) {
jQuery(this.node).removeClass('et2_clickable')
.unwrap();
}
return true;
}
/**
* Implementation of "et2_IDetachedDOM" for fast viewing in gridview
*
* * @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "label", "href");
}
getDetachedNodes() {
return [this.node, this.progress];
}
setDetachedAttributes(_nodes, _values) {
// Set the given DOM-Nodes
this.node = _nodes[0];
this.progress = _nodes[1];
// Set the attributes
if (_values["label"]) {
this.set_label(_values["label"]);
}
if (_values["value"]) {
this.set_value(_values["value"]);
}
else if (_values["label"]) {
this.set_value(_values["label"]);
}
if (_values["href"]) {
jQuery(this.node).addClass('et2_clickable');
this.set_href(_values["href"]);
}
}
}
et2_progress._attributes = {
"href": {
"name": "Link Target",
"type": "string",
"description": "Link URL, empty if you don't wan't to display a link."
},
"extra_link_target": {
"name": "Link target",
"type": "string",
"default": "_self",
"description": "Link target descriptor"
},
"extra_link_popup": {
"name": "Popup",
"type": "string",
"description": "widthxheight, if popup should be used, eg. 640x480"
},
"label": {
"name": "Label",
"default": "",
"type": "string",
"description": "The label is displayed as the title. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
"translate": true
}
};
et2_progress.legacyOptions = ["href", "extra_link_target", "imagemap", "extra_link_popup", "id"];
et2_register_widget(et2_progress, ["progress"]);
//# sourceMappingURL=et2_widget_progress.js.map

View File

@ -1,406 +0,0 @@
/**
* EGroupware eTemplate2 - JS Radiobox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
*/
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget';
/**
* Class which implements the "radiobox" XET-Tag
*
* A radio button belongs to same group by giving all buttons of a group same id!
*
* set_value iterates over all of them and (un)checks them depending on given value.
*
* @augments et2_inputWidget
*/
export class et2_radiobox extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_radiobox
*/
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_radiobox._attributes, _child || {}));
this.input = null;
this.id = "";
this.createInputWidget();
}
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
let readonly = this.getArrayMgr('readonlys').getEntry(this.id);
if (readonly && readonly.hasOwnProperty(_attrs.set_value)) {
_attrs.readonly = readonly[_attrs.set_value];
}
}
createInputWidget() {
this.input = jQuery(document.createElement("input"))
.val(this.options.set_value)
.attr("type", "radio")
.attr("disabled", this.options.readonly);
this.input.addClass("et2_radiobox");
this.setDOMNode(this.input[0]);
}
/**
* Overwritten to set different DOM level ids by appending set_value
*
* @param _id
*/
set_id(_id) {
super.set_id(_id);
this.dom_id = this.dom_id.replace('[]', '') + '-' + this.options.set_value;
if (this.input)
this.input.attr('id', this.dom_id);
}
/**
* Default for radio buttons is label after button
*
* @param _label String New label for radio button. Use %s to locate the radio button somewhere else in the label
*/
set_label(_label) {
if (_label.length > 0 && _label.indexOf('%s') == -1) {
_label = '%s' + _label;
}
super.set_label(_label);
}
/**
* Override default to match against set/unset value AND iterate over all siblings with same id
*
* @param {string} _value
*/
set_value(_value) {
this.getRoot().iterateOver(function (radio) {
if (radio.id == this.id) {
radio.input.prop('checked', _value == radio.options.set_value);
}
}, this, et2_radiobox);
}
/**
* Override default to iterate over all siblings with same id
*
* @return {string}
*/
getValue() {
let val = this.options.value; // initial value, when form is loaded
let values = [];
this.getRoot().iterateOver(function (radio) {
values.push(radio.options.set_value);
if (radio.id == this.id && radio.input && radio.input.prop('checked')) {
val = radio.options.set_value;
}
}, this, et2_radiobox);
return val && val.indexOf(values) ? val : null;
}
/**
* Overridden from parent so if it's required, only 1 in a group needs a value
*
* @param {array} messages
* @returns {Boolean}
*/
isValid(messages) {
let ok = true;
// Check for required
if (this.options && this.options.needed && !this.options.readonly && !this.disabled &&
(this.getValue() == null || this.getValue().valueOf() == '')) {
if (jQuery.isEmptyObject(this.getInstanceManager().getValues(this.getInstanceManager().widgetContainer)[this.id.replace('[]', '')])) {
messages.push(this.egw().lang('Field must not be empty !!!'));
ok = false;
}
}
return ok;
}
/**
* Set radio readonly attribute.
*
* @param _readonly Boolean
*/
set_readonly(_readonly) {
this.options.readonly = _readonly;
this.getRoot().iterateOver(function (radio) {
if (radio.id == this.id) {
radio.input.prop('disabled', _readonly);
}
}, this, et2_radiobox);
}
}
et2_radiobox._attributes = {
"set_value": {
"name": "Set value",
"type": "string",
"default": "true",
"description": "Value when selected"
},
"ro_true": {
"name": "Read only selected",
"type": "string",
"default": "x",
"description": "What should be displayed when readonly and selected"
},
"ro_false": {
"name": "Read only unselected",
"type": "string",
"default": "",
"description": "What should be displayed when readonly and not selected"
}
};
et2_radiobox.legacyOptions = ["set_value", "ro_true", "ro_false"];
et2_register_widget(et2_radiobox, ["radio"]);
/**
* @augments et2_valueWidget
*/
export class et2_radiobox_ro extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_radiobox_ro
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_radiobox_ro._attributes, _child || {}));
this.value = "";
this.span = null;
this.span = jQuery(document.createElement("span"))
.addClass("et2_radiobox");
this.setDOMNode(this.span[0]);
}
/**
* Override default to match against set/unset value
*
* @param {string} _value
*/
set_value(_value) {
this.value = _value;
if (_value == this.options.set_value) {
this.span.text(this.options.ro_true);
}
else {
this.span.text(this.options.ro_false);
}
}
set_label(_label) {
// no label for ro radio, we show label of checked option as content, unless it's x
// then we need the label for things to make sense
if (this.options.ro_true == "x") {
return super.set_label(_label);
}
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs
*/
getDetachedAttributes(_attrs) {
// Show label in nextmatch instead of just x
this.options.ro_true = this.options.label;
_attrs.push("value");
}
getDetachedNodes() {
return [this.span[0]];
}
setDetachedAttributes(_nodes, _values) {
this.span = jQuery(_nodes[0]);
this.set_value(_values["value"]);
}
}
et2_radiobox_ro._attributes = {
"set_value": {
"name": "Set value",
"type": "string",
"default": "true",
"description": "Value when selected"
},
"ro_true": {
"name": "Read only selected",
"type": "string",
"default": "x",
"description": "What should be displayed when readonly and selected"
},
"ro_false": {
"name": "Read only unselected",
"type": "string",
"default": "",
"description": "What should be displayed when readonly and not selected"
},
"label": {
"name": "Label",
"default": "",
"type": "string"
}
};
et2_radiobox_ro.legacyOptions = ["set_value", "ro_true", "ro_false"];
et2_register_widget(et2_radiobox_ro, ["radio_ro"]);
/**
* A group of radio buttons
*
* @augments et2_valueWidget
*/
export class et2_radioGroup extends et2_valueWidget {
/**
* Constructor
*
* @param parent
* @param attrs
* @memberOf et2_radioGroup
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_radioGroup._attributes, _child || {}));
this.node = null;
this.value = null;
this.node = jQuery(document.createElement("div"))
.addClass("et2_vbox")
.addClass("et2_box_widget");
if (this.options.needed) {
// This isn't strictly allowed, but it works
this.node.attr("required", "required");
}
this.setDOMNode(this.node[0]);
// The supported widget classes array defines a whitelist for all widget
// classes or interfaces child widgets have to support.
this.supportedWidgetClasses = [et2_radiobox, et2_radiobox_ro];
}
set_value(_value) {
this.value = _value;
for (let i = 0; i < this._children.length; i++) {
let radio = this._children[i];
radio.set_value(_value);
}
}
getValue() {
return jQuery("input:checked", this.getDOMNode()).val();
}
/**
* Set a bunch of radio buttons
*
* @param {object} _options object with value: label pairs
*/
set_options(_options) {
// Call the destructor of all children
for (let i = this._children.length - 1; i >= 0; i--) {
this._children[i].destroy();
}
this._children = [];
// create radio buttons for each option
for (let key in _options) {
let attrs = {
// Add index so radios work properly
"id": (this.options.readonly ? this.id : this.id + "[" + "]"),
set_value: key,
label: _options[key],
ro_true: this.options.ro_true,
ro_false: this.options.ro_false,
readonly: this.options.readonly
};
if (typeof _options[key] === 'object' && _options[key].label) {
attrs.set_value = _options[key].value;
attrs.label = _options[key].label;
}
// Can't have a required readonly, it will warn & be removed later, so avoid the warning
if (attrs.readonly === false) {
attrs['needed'] = this.options.needed;
}
et2_createWidget("radio", attrs, this);
}
this.set_value(this.value);
}
/**
* Set a label on the group of radio buttons
*
* @param {string} _value
*/
set_label(_value) {
// Abort if ther was no change in the label
if (_value == this.label) {
return;
}
if (_value) {
// Create the label container if it didn't exist yet
if (this._labelContainer == null) {
this._labelContainer = jQuery(document.createElement("label"));
this.getSurroundings().insertDOMNode(this._labelContainer[0]);
}
// Clear the label container.
this._labelContainer.empty();
// Create the placeholder element and set it
var ph = document.createElement("span");
this.getSurroundings().setWidgetPlaceholder(ph);
this._labelContainer
.append(document.createTextNode(_value))
.append(ph);
}
else {
// Delete the labelContainer from the surroundings object
if (this._labelContainer) {
this.getSurroundings().removeDOMNode(this._labelContainer[0]);
}
this._labelContainer = null;
}
}
/**
* Code for implementing et2_IDetachedDOM
* This doesn't need to be implemented.
* Individual widgets are detected and handled by the grid, but the interface is needed for this to happen
*
* @param {object} _attrs
*/
getDetachedAttributes(_attrs) {
}
getDetachedNodes() {
return [this.getDOMNode()];
}
setDetachedAttributes(_nodes, _values) {
}
}
et2_radioGroup._attributes = {
"label": {
"name": "Label",
"default": "",
"type": "string",
"description": "The label is displayed above the list of radio buttons. The label can contain variables, as descript for name. If the label starts with a '@' it is replaced by the value of the content-array at this index (with the '@'-removed and after expanding the variables).",
"translate": true
},
"value": {
"name": "Value",
"type": "string",
"default": "true",
"description": "Value for each radio button"
},
"ro_true": {
"name": "Read only selected",
"type": "string",
"default": "x",
"description": "What should be displayed when readonly and selected"
},
"ro_false": {
"name": "Read only unselected",
"type": "string",
"default": "",
"description": "What should be displayed when readonly and not selected"
},
"options": {
"name": "Radio options",
"type": "any",
"default": {},
"description": "Options for radio buttons. Should be {value: label, ...}"
},
"needed": {
"name": "Required",
"default": false,
"type": "boolean",
"description": "If required, the user must select one of the options before the form can be submitted"
}
};
// No such tag as 'radiogroup', but it needs something
et2_register_widget(et2_radioGroup, ["radiogroup"]);
//# sourceMappingURL=et2_widget_radiobox.js.map

View File

@ -1,55 +0,0 @@
/**
* EGroupware eTemplate2 - JS widget class containing javascript
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Ralf Becker
*/
/*egw:uses
et2_core_widget;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_widget } from "./et2_core_widget";
/**
* Function which executes the encapsulated script data.
*
* This should only be used for customization and NOT for regular EGroupware code!
*
* We can NOT create a script tag containing the content, as this violates our CSP policy!
*
* We use new Function(_content) instead. Therefore you have to use window to address global context:
*
* window.some_func = function() {...}
*
* instead of not working
*
* function some_funct() {...}
*
* @augments et2_widget
*/
export class et2_script extends et2_widget {
constructor(_parent, _attrs, _child) {
super();
// Allow no child widgets
this.supportedWidgetClasses = [];
}
;
/**
* We can NOT create a script tag containing the content, as this violoates our CSP policy!
*
* @param {string} _content
*/
loadContent(_content) {
try {
var func = new Function(_content);
func.call(window);
}
catch (e) {
this.egw.debug('error', 'Error while executing script: ', _content, e);
}
}
}
et2_register_widget(et2_script, ["script"]);
//# sourceMappingURL=et2_widget_script.js.map

View File

@ -1,715 +0,0 @@
/**
* EGroupware eTemplate2 - JS Select account widget
*
* Selecting accounts needs special UI, and displaying needs special consideration
* to avoid sending the entire user list to the client.
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2012
*/
/*egw:uses
et2_widget_link;
*/
import { et2_selectbox } from "./et2_widget_selectbox";
import { et2_createWidget, et2_register_widget } 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";
/**
* 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
*
*/
export class et2_selectAccount extends et2_selectbox {
/**
* Constructor
*
*/
constructor(_parent, _attrs, _child) {
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('<span class="ui-icon ui-icon-search" style="display:inline-block"/>');
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('<span class="ui-icon ui-icon-search"/>');
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]);
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><tbody><tr valign="top"><td id="search_col"/><td id="selection_col"/></tr></tbody></table>');
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('<span class="ui-icon ui-icon-circlesmall-plus et2_clickable"/>')
.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('<span class="ui-icon ui-icon-arrow-1-e et2_clickable"/>')
.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('<span class="ui-icon ui-icon-closethick"/>')
.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('<div class="ui-icon ui-icon-close et2_clickable"/>')
.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_selectAccount._attributes = {
'account_type': {
'name': 'Account type',
'default': 'accounts',
'type': 'string',
'description': 'Limit type of accounts. One of {accounts,groups,both,owngroups}.'
}
};
et2_selectAccount.legacyOptions = ['empty_label', 'account_type'];
et2_selectAccount.account_types = ['accounts', 'groups', 'both', 'owngroups'];
et2_register_widget(et2_selectAccount, ["select-account"]);
/**
* et2_selectAccount_ro is the readonly implementation of select account
* It extends et2_link to avoid needing the whole user list on the client.
* Instead, it just asks for the names of the ones needed, as needed.
*
* @augments et2_link_string
*/
export class et2_selectAccount_ro extends et2_link_string {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
/**
Resolve some circular dependency problems here
selectAccount extends link, link is in a file that needs select,
select has menulist wrapper, which needs to know about selectAccount before it allows it
*/
if (_parent.supportedWidgetClasses.indexOf(et2_selectAccount_ro) < 0) {
_parent.supportedWidgetClasses.push(et2_selectAccount_ro);
}
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_selectAccount_ro._attributes, _child || {}));
if (_parent.supportedWidgetClasses.indexOf(et2_selectAccount_ro) > 0) {
_parent.addChild(this);
}
// Legacy options could have row count or empty label in first slot
if (typeof this.options.empty_label == "string") {
if (isNaN(this.options.empty_label)) {
this.options.empty_label = this.egw().lang(this.options.empty_label);
}
}
this.options.application = 'api-accounts';
// Editable version allows app to set options that aren't accounts, so allow for them
let options = et2_selectbox.find_select_options(this, _attrs['select_options'], this.options);
if (!jQuery.isEmptyObject(options)) {
this.options.select_options = options;
}
// Don't make it look like a link though
this.list.removeClass("et2_link_string").addClass("et2_selectbox");
}
transformAttributes(_attrs) {
et2_selectbox.prototype.transformAttributes.apply(this, arguments);
}
set_value(_value) {
// Explode csv
if (typeof _value == 'string' && _value.indexOf(',') > 0) {
_value = _value.split(',');
}
// pass objects to link widget right away, as following code can't deal with objects
if (typeof _value == 'object') {
super.set_value(_value);
// Don't make it look like a link though
jQuery('li', this.list).removeClass("et2_link et2_link_string")
// No clicks either
.off();
return;
}
// Empty it before we fill it
jQuery('li', this.list).remove();
let found = false;
if (this.options.select_options && !jQuery.isEmptyObject(this.options.select_options) || this.options.empty_label) {
if (!_value) {
// Empty label from selectbox
this.list.append("<li>" + this.options.empty_label + "</li>");
found = true;
}
else if (typeof _value == 'object') {
// An array with 0 / empty in it?
for (let i = 0; i < _value.length; i++) {
if (!_value[i] || !parseInt(_value[i])) {
this.list.append("<li>" + this.options.empty_label + "</li>");
return;
}
else if (this.options.select_options[_value]) {
this.list.append("<li>" + this.options.select_options[_value] + "</li>");
found = true;
}
}
}
else {
// Options are not indexed, so we must look
var search = _value;
if (!jQuery.isArray(search)) {
search = [_value];
}
for (let j = 0; j < search.length; j++) {
// Not having a value to look up causes an infinite loop
if (!search[j])
continue;
for (let i in this.options.select_options) {
if (this.options.select_options[i].value == search[j]) {
this.list.append("<li>" + this.options.select_options[i].label + "</li>");
found = true;
break;
}
}
}
}
}
// if nothing found in select-options let link widget try
if (!found && !isNaN(_value)) {
super.set_value(_value);
// Don't make it look like a link though
jQuery('li', this.list).removeClass("et2_link et2_link_string")
// No clicks either
.off();
return;
}
}
}
et2_selectAccount_ro._attributes = {
"empty_label": {
"name": "Empty label",
"type": "string",
"default": "",
"description": "Textual label for first row, eg: 'All' or 'None'. ID will be ''",
translate: true
}
};
et2_selectAccount_ro.legacyOptions = ["empty_label"];
et2_register_widget(et2_selectAccount_ro, ["select-account_ro"]);
//# sourceMappingURL=et2_widget_selectAccount.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,357 +0,0 @@
/**
* EGroupware eTemplate2 - Split panel
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
jquery.splitter;
et2_core_baseWidget;
*/
import { et2_DOMWidget } from "./et2_core_DOMWidget";
import { et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_IResizeable } from "./et2_core_interfaces";
import { et2_no_init } from "./et2_core_common";
import { et2_dynheight } from "./et2_widget_dynheight";
/**
* A container widget that accepts 2 children, and puts a resize bar between them.
*
* This is the etemplate2 implementation of the traditional split pane / split panel.
* The split can be horizontal or vertical, and can be limited in size. You can also
* turn on double-click docking to minimize one of the children.
*
* @see http://methvin.com/splitter/ Uses Splitter
* @augments et2_DOMWidget
*/
export class et2_split extends et2_DOMWidget {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_split._attributes, _child || {}));
this.div = null;
this.dynheight = null;
this.div = jQuery(document.createElement("div"))
.addClass('et2_split');
// Create the dynheight component which dynamically scales the inner
// container.
this.dynheight = new et2_dynheight(this.getParent().getDOMNode() || this.getInstanceManager().DOMContainer, this.div, 100);
// Add something so we can see it - will be replaced if there's children
this.left = jQuery("<div>Top / Left</div>").appendTo(this.div);
this.right = jQuery("<div>Bottom / Right</div>").appendTo(this.div);
// Deferred object so we can wait for children
this.loading = jQuery.Deferred();
// Flag to temporarily ignore resizing
this.stop_resize = false;
}
destroy() {
// Stop listening
this.left.next().off("mouseup");
// Destroy splitter, restore children
this.div.trigger("destroy");
// Destroy dynamic full-height
this.dynheight.destroy();
super.destroy();
// Remove placeholder children
if (this._children.length == 0) {
this.div.empty();
}
this.div.remove();
}
/**
* Tap in here to check if we have real children, because all children should be created
* by this point. If there are, replace the placeholders.
*/
loadFromXML(_node) {
super.loadFromXML(_node);
if (this._children.length > 0) {
if (this._children[0]) {
this.left.detach();
this.left = jQuery(this._children[0].getDOMNode(this._children[0]))
.appendTo(this.div);
}
if (this._children[1]) {
this.right.detach();
this.right = jQuery(this._children[1].getDOMNode(this._children[1]))
.appendTo(this.div);
}
}
// Nextmatches (and possibly other "full height" widgets) need to be adjusted
// Trigger the dynamic height thing to re-initialize
for (var i = 0; i < this._children.length; i++) {
if (this._children[i].dynheight) {
this._children[i].dynheight.outerNode = (i == 0 ? this.left : this.right);
this._children[i].dynheight.initialized = false;
}
}
}
doLoadingFinished() {
super.doLoadingFinished();
// Use a timeout to give the children a chance to finish
var self = this;
window.setTimeout(function () {
self._init_splitter();
}, 1);
// Not done yet, but widget will let you know
return this.loading.promise();
}
/**
* Initialize the splitter UI
* Internal.
*/
_init_splitter() {
if (!this.isAttached())
return;
// Avoid trying to do anything while hidden - it ruins all the calculations
// Try again in resize()
if (!this.div.is(':visible'))
return;
let options = {
type: this.orientation,
dock: this.dock_side,
splitterClass: "et2_split",
outline: true,
eventNamespace: '.et2_split.' + this.id,
// Default sizes, in case the preference doesn't work
// Splitter would normally just go to 50%, but our deferred loading
// ruins sizing for it
sizeTop: this.dynheight.outerNode.height() / 2,
sizeLeft: this.dynheight.outerNode.width() / 2
};
let widget = this;
//Convert pixel size to percent
let pix2per = function (_size) {
let per;
if (widget.orientation == "v") {
per = _size * 100 / widget.dynheight.outerNode.width();
}
else {
per = _size * 100 / widget.dynheight.outerNode.height();
}
return per.toFixed(2) + "%";
};
// Check for position preference, load it in
if (this.id) {
let pref = this.egw().preference('splitter-size-' + this.id, this.egw().getAppName());
if (pref) {
if (this.orientation == "v" && pref['sizeLeft'] < this.dynheight.outerNode.width() ||
this.orientation == "h" && pref['sizeTop'] < this.dynheight.outerNode.height()) {
options = jQuery.extend(options, pref);
this.prefSize = pref[this.orientation == "v" ? 'sizeLeft' : 'sizeTop'];
}
}
// If there is no preference yet, set it to half size
// Otherwise the right pane gets the fullsize
if (typeof this.prefSize == 'undefined' || !this.prefSize) {
this.prefSize = this.orientation == "v" ? options.sizeLeft : options.sizeTop;
}
}
// Avoid double init
if (this.div.hasClass(options.splitterClass)) {
this.div.trigger("destroy");
}
// Initialize splitter
this.div.splitter(options);
this.div.trigger('resize', [options.sizeTop || options.sizeLeft || 0]);
// Start docked?
if (options.dock) {
if (options.dock == "bottomDock" && Math.abs(options.sizeTop - this.div.height()) < et2_split.DOCK_TOLERANCE ||
options.dock == "topDock" && options.sizeTop == 0 ||
!this.div.is(':visible') // Starting docked if hidden simplifies life when resizing
) {
this.dock();
}
}
// Add icon to splitter bar
let icon = "ui-icon-grip-"
+ (this.dock_side ? "solid" : "dotted") + "-"
+ (this.orientation == "h" ? "horizontal" : "vertical");
jQuery(document.createElement("div"))
.addClass("ui-icon")
.addClass(icon)
.appendTo(this.left.next());
// Save preference when size changed
if (this.id && this.egw().getAppName()) {
let self = this;
this.left.on("resize" + options.eventNamespace, function (e) {
// Force immediate layout, so proper layout & sizes are available
// for resize(). Chrome defers layout, so current DOM node sizes
// are not available to widgets if they ask.
var display = this.style.display;
this.style.display = 'none';
this.offsetHeight;
this.style.display = display;
if (e.namespace == options.eventNamespace.substr(1) && !self.isDocked()) {
// Store current position in preferences
var size = self.orientation == "v" ? { sizeLeft: self.left.width() } : { sizeTop: self.left.height() };
self.prefSize = size[self.orientation == "v" ? 'sizeLeft' : 'sizeTop'];
var prefInPercent = self.orientation == "v" ? { sizeLeft: pix2per(size.sizeLeft) } : { sizeTop: pix2per(size.sizeTop) };
if (parseInt(self.orientation == 'v' ? prefInPercent.sizeLeft : prefInPercent.sizeTop) < 100) {
self.egw().set_preference(self.egw().getAppName(), 'splitter-size-' + self.id, prefInPercent);
}
}
// Ok, update children
self.iterateOver(function (widget) {
// Extra resize would cause stalling chrome
// as resize might confilict with bottom download bar
// in chrome which does a window resize, so better to not
// trigger second resize and leave that to an application
// if it is neccessary.
// Above forcing is not enough for Firefox, defer
window.setTimeout(jQuery.proxy(function () { this.resize(); }, widget), 200);
}, self, et2_IResizeable);
});
}
this.loading.resolve();
}
/**
* Implement the et2_IResizable interface to resize
*/
resize() {
// Avoid doing anything while hidden - check here, and init if needed
if (this.div.children().length <= 2) {
this._init_splitter();
}
if (this.dynheight && !this.stop_resize) {
let old = { w: this.div.width(), h: this.div.height() };
this.dynheight.update(function (w, h) {
if (this.orientation == "v") {
this.left.height(h);
this.right.height(h);
if (this.left.width() + this.right.width() + this.div.find('.splitter-bar').outerWidth() < this.div.width() ||
this.left.width() + this.right.width() - this.div.find('.splitter-bar').outerWidth() > this.div.width())
this.div.trigger('resize.et2_split.' + this.id, this.prefSize);
}
if (this.orientation == "h") {
this.left.width(w);
this.right.width(w);
if (this.isDocked()) {
if (this.dock_side == "topDock") {
this.right.height(h);
this.left.height(0);
}
else {
this.left.height(h);
this.right.height(0);
}
}
}
if (w != old.w || h != old.h) {
this.div.trigger('resize.et2_split.' + this.id, this.prefSize);
}
}, this);
}
}
getDOMNode() {
return this.div[0];
}
/**
* Set splitter orientation
*
* @param orient String "v" or "h"
*/
set_orientation(orient) {
this.orientation = orient;
this._init_splitter();
}
/**
* Set the side for docking
*
* @param dock String One of leftDock, rightDock, topDock, bottomDock
*/
set_dock_side(dock) {
this.dock_side = dock;
this._init_splitter();
}
/**
* Turn on or off resizing while dragging
*
* @param outline boolean
*/
set_outline(outline) {
this.outline = outline;
this._init_splitter();
}
/**
* If the splitter has a dock direction set, dock it.
* Docking requires the dock attribute to be set.
*/
dock() {
if (this.isDocked())
return;
this.div.trigger("dock");
}
/**
* If the splitter is docked, restore it to previous size
* Docking requires the dock attribute to be set.
*/
undock() {
this.div.trigger("undock");
}
/**
* Determine if the splitter is docked
* @return boolean
*/
isDocked() {
var bar = jQuery('.splitter-bar', this.div);
return bar.hasClass('splitter-bar-horizontal-docked') || bar.hasClass('splitter-bar-vertical-docked');
}
/**
* Toggle the splitter's docked state.
* Docking requires the dock attribute to be set.
*/
toggleDock() {
this.div.trigger("toggleDock");
}
// Printing
/**
* Prepare for printing by stopping all the fuss
*
*/
beforePrint() {
// Resizing causes preference changes & relayouts. Don't do it.
this.stop_resize = true;
// Add the class, if needed
this.div.addClass('print');
// Don't return anything, just work normally
}
afterPrint() {
this.div.removeClass('print');
this.stop_resize = false;
}
}
et2_split._attributes = {
"orientation": {
"name": "Orientation",
"description": "Horizontal or vertical (v or h)",
"default": "v",
"type": "string"
},
"outline": {
"name": "Outline",
"description": "Use a 'ghosted' copy of the splitbar and does not resize the panes until the mouse button is released. Reduces flickering or unwanted re-layout during resize",
"default": false,
"type": "boolean"
},
"dock_side": {
"name": "Dock",
"description": "Allow the user to 'Dock' the splitbar to one side of the splitter, essentially hiding one pane and using the entire splitter area for the other pane. One of leftDock, rightDock, topDock, bottomDock.",
"default": et2_no_init,
"type": "string"
},
"width": {
"default": "100%"
},
// Not needed
"overflow": { "ignore": true },
"no_lang": { "ignore": true },
"rows": { "ignore": true },
"cols": { "ignore": true }
};
et2_split.DOCK_TOLERANCE = 15;
et2_register_widget(et2_split, ["split"]);
//# sourceMappingURL=et2_widget_split.js.map

View File

@ -1,80 +0,0 @@
/**
* EGroupware eTemplate2 - JS widget class containing styles
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
* @copyright EGroupware GmbH 2011-2021
*/
/*egw:uses
et2_core_widget;
*/
import { et2_register_widget, et2_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
/**
* Function which appends the encapsulated style data to the head tag of the
* page.
*
* TODO: The style data could be parsed for rules and appended using the JS
* stylesheet interface, allowing the style only to modifiy nodes of the current
* template.
*
* @augments et2_widget
*/
export class et2_styles extends et2_widget {
/**
* Constructor
*
* @memberOf et2_styles
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_styles._attributes, _child || {}));
// Allow no child widgets
this.supportedWidgetClasses = [];
// Create the style node and append it to the head node
this.styleNode = document.createElement("style");
this.styleNode.setAttribute("type", "text/css");
this.head = this.egw().window.document.getElementsByTagName("head")[0];
this.head.appendChild(this.styleNode);
}
destroy() {
// Remove the style node again and delete any reference to it
this.head.removeChild(this.styleNode);
super.destroy();
}
loadContent(_content) {
// @ts-ignore
if (this.styleNode.styleSheet) {
// IE
// @ts-ignore
this.styleNode.styleSheet.cssText += _content;
}
else {
this.styleNode.appendChild(document.createTextNode(_content));
}
}
/**
* Sets the id of the DOM-Node.
*
* DOM id's have dots "." replaced with dashes "-"
*
* @param {string} _value id to set
*/
set_id(_value) {
this.id = _value;
this.dom_id = _value ? this.getInstanceManager().uniqueId + '_' + _value.replace(/\./g, '-') : _value;
if (this.styleNode) {
if (_value != "") {
this.styleNode.setAttribute("id", this.dom_id);
}
else {
this.styleNode.removeAttribute("id");
}
}
}
}
et2_register_widget(et2_styles, ["styles"]);
//# sourceMappingURL=et2_widget_styles.js.map

View File

@ -1,476 +0,0 @@
/**
* EGroupware eTemplate2 - JS Tabs object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
jsapi.egw;
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_valueWidget;
*/
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget';
import { et2_nextmatch } from "./et2_extension_nextmatch";
import { et2_no_init } from "./et2_core_common";
import { et2_directChildrenByTagName, et2_filteredNodeIterator, et2_readAttrWithDefault } from "./et2_core_xml";
/**
* Class which implements the tabbox-tag
*/
export class et2_tabbox extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_tabbox._attributes, _child || {}));
/**
* Currently selected tab
*/
this.selected_index = 0;
this.tabData = [];
// Create the outer tabbox container
this.container = jQuery(document.createElement("div"))
.addClass("et2_tabbox");
// Create the upper container for the tab flags
this.flagContainer = jQuery(document.createElement("ul"))
.addClass("et2_tabheader")
.attr("role", "tablist")
.appendTo(this.container);
// Create the lower tab container
this.tabContainer = jQuery(document.createElement("div"))
.addClass("et2_tabs")
.appendTo(this.container);
}
destroy() {
super.destroy();
this.container = null;
this.flagContainer = null;
this.tabData = [];
}
_readTabs(tabData, tabs) {
var selected = "";
this.selected_index = false;
var hidden = {};
if (this.id) {
// Set the value for this element
var contentMgr = this.getArrayMgr("content");
if (contentMgr != null) {
var val = contentMgr.getEntry(this.id);
if (val !== null) {
selected = val;
}
}
contentMgr = this.getArrayMgr("readonlys");
if (contentMgr != null) {
var val = contentMgr.getEntry(this.id);
if (val !== null && typeof val !== 'undefined') {
hidden = val;
}
}
}
var i = 0;
et2_filteredNodeIterator(tabs, function (node, nodeName) {
if (nodeName == "tab") {
const index_name = et2_readAttrWithDefault(node, "id", '');
var hide = false;
var widget_options = {};
if (index_name) {
if (selected == index_name)
this.selected_index = i;
if (hidden[index_name]) {
hide = true;
}
// Get the class attribute and add it as widget_options
const classAttr = et2_readAttrWithDefault(node, "class", '');
if (classAttr) {
widget_options = { 'class': classAttr };
}
}
tabData.push({
"id": index_name,
"label": this.egw().lang(et2_readAttrWithDefault(node, "label", "Tab")),
"widget": null,
"widget_options": widget_options,
"contentDiv": null,
"flagDiv": null,
"hidden": hide,
"XMLNode": null,
"promise": null
});
}
else {
throw ("Error while parsing: Invalid tag '" + nodeName +
"' in tabs tag");
}
i++;
}, this);
// Make sure we don't try to display a hidden tab
for (var i = 0; i < tabData.length && this.selected_index === false; i++) {
if (!tabData[i].hidden)
this.selected_index = i;
}
}
_readTabPanels(tabData, tabpanels) {
var i = 0;
et2_filteredNodeIterator(tabpanels, function (node, nodeName) {
if (i < tabData.length) {
// Store node for later evaluation
tabData[i].XMLNode = node;
}
else {
throw ("Error while reading tabpanels tag, too many widgets!");
}
i++;
}, this);
}
loadFromXML(_node) {
// Get the tabs and tabpanels tags
var tabsElems = et2_directChildrenByTagName(_node, "tabs");
var tabpanelsElems = et2_directChildrenByTagName(_node, "tabpanels");
var tabData = [];
// Check for a parent height, we'll apply it to tab panels
var height = et2_readAttrWithDefault(_node.parentNode, "height", null);
if (height) {
this.tabContainer.css("height", height);
}
// if no tabs set or they should be added to tabs from xml
if (!this.options.tabs || this.options.add_tabs) {
if (tabsElems.length == 1 && tabpanelsElems.length == 1) {
var tabs = tabsElems[0];
var tabpanels = tabpanelsElems[0];
// Parse the "tabs" tag
this._readTabs(tabData, tabs);
// Read and create the widgets defined in the "tabpanels"
this._readTabPanels(tabData, tabpanels);
}
else {
this.egw().debug("error", "Error while parsing tabbox, none or multiple tabs or tabpanels tags!", this);
}
}
if (this.options.tabs) {
var readonly = this.getArrayMgr("readonlys").getEntry(this.id) || {};
for (var i = 0; i < this.options.tabs.length; i++) {
var tab = this.options.tabs[i];
var tab_id = tab.id || tab.template;
var tab_options = { id: tab_id, template: tab.template, url: tab.url, content: undefined };
if (tab.id) {
tab_options.content = tab.id;
}
tabData[tab.prepend ? 'unshift' : 'push'].call(tabData, {
"id": tab_id,
"label": this.egw().lang(tab.label),
"widget": null,
"widget_options": tab_options,
"contentDiv": null,
"flagDiv": null,
"hidden": typeof tab.hidden != "undefined" ? tab.hidden : readonly[tab_id] || false,
"XMLNode": null,
"promise": null
});
}
}
// Create the tab DOM-Nodes
this.createTabs(tabData);
}
/**
* Load is finished, set up tabs to load on their own
*/
doLoadingFinished() {
var tab_deferred = jQuery.Deferred();
var promises = [];
var tabs = this;
// Specially process the selected index so it shows up right away
this._loadTab(this.selected_index, promises);
// Avoid reloading if tabs were modified by data
if (this.isInTree() && this.isAttached())
return;
// Apply parent now, which actually puts into the DOM
// This has to be before loading the child, so the dom sub-tree is not
// disconnected, which causes problems for things like CKEditor
super.doLoadingFinished();
// We can do this and not wind up with 2 because child is a template,
// which has special handling
this._children[0].loadingFinished(promises);
// Defer parsing & loading of other tabs until later
window.setTimeout(function () {
for (var i = 0; i < tabs.tabData.length; i++) {
if (i == tabs.selected_index)
continue;
tabs._loadTab(i, promises);
}
jQuery.when.apply(jQuery, promises).then(function () {
tab_deferred.resolve();
tabs.resetDirty();
});
}, 0);
return tab_deferred.promise();
}
/**
* Load & render a tab's content
*
* @param {number} index numerical index of tab in this.tabData array
* @param {array} promises
*/
_loadTab(index, promises) {
var tabData = this.tabData[index];
if (!tabData || tabData.loaded)
return;
// Set loaded flag to not do this again, even if not fully done
tabData.loaded = true;
if (tabData.XMLNode != null) {
if (tabData.hidden) {
// Set hidden tab to readonly, so widgets aren't active
// Do not modify the XMLNode, or the change will be cached for all
tabData.XMLNode = tabData.XMLNode.cloneNode();
tabData.XMLNode.setAttribute('readonly', true);
}
tabData.widget = this.createElementFromNode(tabData.XMLNode, tabData.XMLNode.nodeName.toLowerCase());
// Release the XML node
tabData.XMLNode = null;
}
else if (tabData.widget_options) {
tabData.widget = et2_createWidget('template', tabData.widget_options, this);
}
// loadingFinished() will be called either when the promise from doLoadingFinished is resolved,
// or during the normal execution
}
/**
* Check for custom tabs
*
* @param {object} _attrs
*/
transformAttributes(_attrs) {
super.transformAttributes(_attrs);
// Add in settings that are objects
var data = this.getArrayMgr("modifications").getEntry(this.id);
for (var key in data) {
if (typeof data[key] === 'object' && !_attrs[key])
_attrs[key] = data[key];
}
}
createTabs(tabData) {
this.tabData = tabData;
this.tabContainer.empty();
this.flagContainer.empty();
for (var i = 0; i < this.tabData.length; i++) {
var entry = this.tabData[i];
entry.flagDiv = jQuery(document.createElement("li"))
.addClass("et2_tabflag")
.appendTo(this.flagContainer);
// Class to tab's div container
if (entry.widget_options && typeof entry.widget_options.class != 'undefined') {
entry.flagDiv.addClass(entry.widget_options.class);
}
entry.flagDiv.html("<a href=\"#\" role='tab'>" + (entry.label || "Tab") + "</a>");
if (entry.hidden || this.tabData.length === 1) {
entry.flagDiv.hide();
}
else {
entry.flagDiv.click({ "tabs": this, "idx": i }, function (e) {
e.data.tabs.setActiveTab(e.data.idx);
});
}
entry.contentDiv = jQuery(document.createElement("div"))
.addClass("et2_tabcntr")
.attr("role", "tabpanel")
.appendTo(this.tabContainer);
if (this.options.align_tabs == 'v') {
entry.flagDiv.unbind('click');
entry.flagDiv.text("");
jQuery(document.createElement('div'))
.addClass('et2_tabtitle')
.text(entry.label || "Tab")
.click({ "tabs": this, "idx": i }, function (e) {
e.data.tabs.flagContainer.children(":eq(" + e.data.idx + ")").toggleClass('active');
if (e.data.tabs.selected_index != e.data.idx)
e.data.tabs.setActiveTab(e.data.idx);
})
.appendTo(entry.flagDiv);
entry.contentDiv.appendTo(entry.flagDiv);
}
}
if (this.options.align_tabs == 'v') {
this.container.addClass('vertical');
this.tabContainer.hide();
}
// Check for a passed in value
if (this.options.value) {
this.selected_index = 0;
for (var i = 0; i < this.tabData.length; i++) {
if (this.tabData[i].id == this.options.value) {
this.selected_index = i;
break;
}
}
}
this.setActiveTab(this.selected_index);
}
/**
* Gets the index of the currently active tab
*
* @returns {number}
*/
get_active_tab() {
return this.selected_index;
}
/**
* Sets the currently active tab by index
*
* @param {number} _idx
*/
setActiveTab(_idx) {
this.selected_index = _idx;
// Remove the "active" flag from all tabs-flags
jQuery(".et2_tabflag", this.flagContainer).removeClass("active")
.attr("aria-selected", "false");
// Hide all tab containers
this.tabContainer.children().hide();
// Set the tab flag with the given index active and show the corresponding
// container
this.flagContainer.children(":eq(" + _idx + ")")
.addClass("active")
.attr("aria-selected", "true");
this.tabContainer.children(":eq(" + _idx + ")").show();
// lookup for nm children and trigger a resize, since nm inside inactive
// tabs are not getting render due to tab's deffer loading.
if (this._children.length > 0 && this.tabData && this.tabData.length > 0) {
this.tabData[_idx]['widget'].iterateOver(function (nm) {
if (nm && nm._type == 'nextmatch')
nm.resize();
}, this.tabData[_idx]['widget'], et2_nextmatch);
}
}
/**
* Activate the tab containing the given widget
*
* @param {et2_widget} widget
* @return {bool} widget was found in a tab
*/
activateTab(widget) {
var tab = widget;
while (tab._parent && tab._parent._type != 'tabbox') {
tab = tab._parent;
}
var child_index = this._children.indexOf(tab);
for (var i = 0; i < this.tabData.length; i++) {
if (this.tabData[i].widget == tab) {
this.setActiveTab(i);
return true;
}
}
return false;
}
getDOMNode(_sender) {
if (_sender === this || typeof _sender === 'undefined') {
return this.container[0];
}
else {
for (var i = 0; i < this.tabData.length; i++) {
if (this.tabData[i].widget == _sender) {
return this.tabData[i].contentDiv[0];
}
}
return null;
}
}
set_tab_height(_height) {
this.tab_height = _height;
this.tabContainer.css("height", _height);
}
set_height(_value) {
this.height = _value;
this.tabContainer.css("height", _value);
}
/**
* getValue has to return the value of the input widget
*/
getValue() {
return this.selected_index !== false ? this.tabData[this.selected_index].id : undefined;
}
/**
* Is dirty returns true if the value of the widget has changed since it
* was loaded.
*/
isDirty() {
// We consider tab changes are not real changes
return false;
}
/**
* Causes the dirty flag to be reseted.
*/
resetDirty() {
this.value = this.selected_index;
}
isValid(messages) {
return true;
}
resize(_height) {
if (_height) {
this.set_height(this.tabContainer.height() + _height);
}
//Set the height of tabs with the heighest height
else if (_height === 0) {
this.set_height(this.tabContainer.height());
}
}
/**
* Set up for printing
*
* @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
* (waiting for data)
*/
beforePrint() {
// Remove the "active" flag from all tabs-flags
jQuery(".et2_tabflag", this.flagContainer).removeClass("active");
// Remove height limit
this.tabContainer.css("height", '');
// Show all enabled tabs
for (var i = 0; i < this.tabData.length; i++) {
var entry = this.tabData[i];
if (entry.hidden)
continue;
entry.flagDiv.insertBefore(entry.contentDiv);
entry.contentDiv.show();
}
}
/**
* Reset after printing
*/
afterPrint() {
for (var i = 0; i < this.tabData.length; i++) {
var entry = this.tabData[i];
entry.flagDiv.appendTo(this.flagContainer);
}
this.setActiveTab(this.get_active_tab());
}
}
et2_tabbox._attributes = {
'tabs': {
'name': 'Tabs',
'default': et2_no_init,
'description': "Array of [extra] tabs. Each tab needs {label:..., template:...}. Additional optional keys are prepend, hidden and id, for access into content array"
},
'add_tabs': {
'name': 'Add tabs',
'default': false,
'description': 'Set to true if tabs should be added to tabs from read from template, default false if not'
},
'tab_height': {
name: 'Tabs innerHeight',
default: '',
description: 'Set the innerHeight for the tab content'
},
'align_tabs': {
name: 'Tabs alignment',
type: 'string',
default: 'h',
description: 'Set tabs and their headers arrangment either horizental (h) or vertical (v). Default value is horizental.'
}
};
et2_register_widget(et2_tabbox, ["tabbox"]);
//# sourceMappingURL=et2_widget_tabs.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,216 +0,0 @@
/**
* EGroupware eTemplate2 - JS Template base class
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
et2_core_xml;
et2_core_DOMWidget;
*/
import './et2_core_interfaces';
import { et2_DOMWidget } from './et2_core_DOMWidget';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
import { etemplate2 } from "./etemplate2";
import { et2_cloneObject, et2_no_init } from "./et2_core_common";
import { et2_loadXMLFromURL } from "./et2_core_xml";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "template" XET-Tag. When the id parameter is set,
* the template class checks whether another template with this id already
* exists. If yes, this template is removed from the DOM tree, copied and
* inserted in place of this template.
*/
export class et2_template extends et2_DOMWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_template._attributes, _child || {}));
// Set this early, so it's available for creating namespace
if (_attrs.content) {
this.content = _attrs.content;
}
// constructor was called here before!
this.div = document.createElement("div");
// Deferred object so we can load via AJAX
this.loading = jQuery.Deferred();
// run transformAttributes now, to get server-side modifications (url!)
if (_attrs.template) {
this.id = _attrs.template;
this.transformAttributes(_attrs);
this.options = et2_cloneObject(_attrs);
_attrs = {};
}
if (this.id != "" || this.options.template) {
var parts = (this.options.template || this.id).split('?');
var cache_buster = parts.length > 1 ? parts.pop() : null;
var template_name = parts.pop();
// Check to see if XML is known
var xml = null;
var templates = etemplate2.templates; // use global eTemplate cache
if (!(xml = templates[template_name])) {
// Check to see if ID is short form --> prepend parent/top-level name
if (template_name.indexOf('.') < 0) {
var root = _parent ? _parent.getRoot() : null;
var top_name = root && root._inst ? root._inst.name : null;
if (top_name && template_name.indexOf('.') < 0)
template_name = top_name + '.' + template_name;
}
xml = templates[template_name];
if (!xml) {
// Ask server
var url = this.options.url;
if (!this.options.url) {
var splitted = template_name.split('.');
var app = splitted.shift();
// use template base url from initial template, to continue using webdav, if that was loaded via webdav
url = this.getRoot()._inst.template_base_url + app + "/templates/default/" +
splitted.join('.') + ".xet" + (cache_buster ? '?download=' + cache_buster : '');
}
// if server did not give a cache-buster, fall back to current time
if (url.indexOf('?') == -1)
url += '?download=' + (new Date).valueOf();
if (this.options.url || splitted.length) {
var fetch_url_callback = function (_xmldoc) {
// Scan for templates and store them
for (var i = 0; i < _xmldoc.childNodes.length; i++) {
var template = _xmldoc.childNodes[i];
if (template.nodeName.toLowerCase() != "template")
continue;
templates[template.getAttribute("id")] = template;
}
// Read the XML structure of the requested template
if (typeof templates[template_name] != 'undefined')
this.loadFromXML(templates[template_name]);
// Update flag
this.loading.resolve();
};
et2_loadXMLFromURL(url, fetch_url_callback, this, function (error) {
url = egw.link('/' + app + "/templates/default/" +
splitted.join('.') + ".xet", { download: cache_buster ? cache_buster : (new Date).valueOf() });
et2_loadXMLFromURL(url, fetch_url_callback, this);
});
}
return;
}
}
if (xml !== null && typeof xml !== "undefined") {
this.egw().debug("log", "Loading template from XML: ", template_name);
this.loadFromXML(xml);
// Don't call this here - done by caller, or on whole widget tree
//this.loadingFinished();
// But resolve the promise
this.loading.resolve();
}
else {
this.egw().debug("warn", "Unable to find XML for ", template_name);
this.loading.reject();
}
}
else {
// No actual template
this.loading.resolve();
}
}
/**
* Override parent to support content attribute
* Templates always have ID set, but seldom do we want them to
* create a namespace based on their ID.
*/
checkCreateNamespace(_attrs) {
if (_attrs.content) {
var old_id = _attrs.id;
this.id = _attrs.content;
super.checkCreateNamespace.apply(this, arguments);
this.id = old_id;
}
}
_createNamespace() {
return true;
}
getDOMNode() {
return this.div;
}
attachToDOM() {
if (this.div) {
jQuery(this.div)
.off('.et2_template')
.bind("load.et2_template", this, function (e) {
e.data.load.call(e.data, this);
});
}
return super.attachToDOM();
}
/**
* Called after the template is fully loaded to handle any onload handlers
*/
load() {
if (typeof this.options.onload == 'function') {
// Make sure function gets a reference to the widget
var args = Array.prototype.slice.call(arguments);
if (args.indexOf(this) == -1)
args.push(this);
return this.options.onload.apply(this, args);
}
}
/**
* Override to return the promise for deferred loading
*/
doLoadingFinished() {
// Apply parent now, which actually puts into the DOM
super.doLoadingFinished();
// Fire load event when done loading
this.loading.done(jQuery.proxy(function () { jQuery(this).trigger("load"); }, this.div));
// Not done yet, but widget will let you know
return this.loading.promise();
}
}
et2_template._attributes = {
"template": {
"name": "Template",
"type": "string",
"description": "Name / ID of template with optional cache-buster ('?'+filemtime of template on server)",
"default": et2_no_init
},
"group": {
// TODO: Not implemented
"name": "Group",
"description": "Not implemented",
//"default": 0
"default": et2_no_init
},
"version": {
"name": "Version",
"type": "string",
"description": "Version of the template"
},
"lang": {
"name": "Language",
"type": "string",
"description": "Language the template is written in"
},
"content": {
"name": "Content index",
"default": et2_no_init,
"description": "Used for passing in specific content to the template other than what it would get by ID."
},
url: {
name: "URL of template",
type: "string",
description: "full URL to load template incl. cache-buster"
},
"onload": {
"name": "onload",
"type": "js",
"default": et2_no_init,
"description": "JS code which is executed after the template is loaded."
}
};
et2_register_widget(et2_template, ["template"]);
//# sourceMappingURL=et2_widget_template.js.map

View File

@ -1,539 +0,0 @@
/**
* EGroupware eTemplate2 - JS Textbox object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Andreas Stöckel
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_inputWidget;
et2_core_valueWidget;
*/
import './et2_core_common';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { et2_valueWidget } from './et2_core_valueWidget';
import { et2_inputWidget } from './et2_core_inputWidget';
import { et2_csvSplit, et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "textbox" XET-Tag
*
* @augments et2_inputWidget
*/
export class et2_textbox extends et2_inputWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_textbox._attributes, _child || {}));
this.input = null;
this.input = null;
this.createInputWidget();
}
createInputWidget() {
if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) {
this.input = jQuery(document.createElement("textarea"));
if (this.options.rows > 0) {
this.input.attr("rows", this.options.rows);
}
if (this.options.cols > 0) {
this.input.attr("cols", this.options.cols);
}
}
else {
this.input = jQuery(document.createElement("input"));
switch (this.options.type) {
case "hidden":
this.input.attr("type", "hidden");
break;
}
if (this.options.autocomplete)
this.input.attr("autocomplete", this.options.autocomplete);
}
if (this.options.size) {
this.set_size(this.options.size);
}
if (this.options.blur) {
this.set_blur(this.options.blur);
}
if (this.options.readonly) {
this.set_readonly(true);
}
this.input.addClass("et2_textbox");
this.setDOMNode(this.input[0]);
if (this.options.value) {
this.set_value(this.options.value);
}
if (this.options.onkeypress && typeof this.options.onkeypress == 'function') {
var self = this;
this.input.keypress(function (_ev) {
return self.options.onkeypress.call(this, _ev, self);
});
}
}
destroy() {
var node = this.getInputNode();
if (node)
jQuery(node).unbind("keypress");
super.destroy();
}
getValue() {
// only return "" for blur-value, if browser does not support html5 placeholder
if (this.options && this.options.blur && this.input &&
!this.input[0].placeholder &&
this.input.val() == this.options.blur) {
return "";
}
return super.getValue();
}
/**
* Clientside validation using regular expression in "validator" attribute
*
* @param {array} _messages
*/
isValid(_messages) {
var ok = true;
// Check input is valid
if (this.options && this.options.validator && !this.options.readonly && !this.disabled) {
if (typeof this.options.validator == 'string') {
var parts = this.options.validator.split('/');
var flags = parts.pop();
if (parts.length < 2 || parts[0] !== '') {
_messages.push(this.egw().lang("'%1' has an invalid format !!!", this.options.validator));
return false; // show invalid expression
}
parts.shift();
this.options.validator = new RegExp(parts.join('/'), flags);
}
var value = this.getValue();
if (!(ok = this.options.validator.test(value))) {
_messages.push(this.egw().lang("'%1' has an invalid format !!!", value));
}
}
return super.isValid(_messages) && ok;
}
/**
* Set input widget size
* @param _size Rather arbitrary size units, approximately characters
*/
set_size(_size) {
if (this.options.multiline || this.options.rows > 1 || this.options.cols > 1) {
this.input.css('width', _size + "em");
}
else if (typeof _size != 'undefined' && _size != parseInt(this.input.attr("size"))) {
this.size = _size;
this.input.attr("size", this.size);
}
}
/**
* Set maximum characters allowed
* @param _size Max characters allowed
*/
set_maxlength(_size) {
if (typeof _size != 'undefined' && _size != parseInt(this.input.attr("maxlength"))) {
this.maxLength = _size;
this.input.attr("maxLength", this.maxLength);
}
}
/**
* Set HTML readonly attribute.
* Do not confuse this with etemplate readonly, which would use et_textbox_ro instead
* @param _readonly Boolean
*/
set_readonly(_readonly) {
this.input.attr("readonly", _readonly);
this.input.toggleClass('et2_textbox_ro', _readonly);
}
set_blur(_value) {
if (_value) {
this.input.attr("placeholder", this.egw().lang(_value) + ""); // HTML5
if (!this.input[0].placeholder) {
// Not HTML5
if (this.input.val() == "")
this.input.val(this.egw().lang(this.options.blur));
this.input.focus(this, function (e) {
if (e.data.input.val() == e.data.egw().lang(e.data.options.blur))
e.data.input.val("");
}).blur(this, function (e) {
if (e.data.input.val() == "")
e.data.input.val(e.data.egw().lang(e.data.options.blur));
});
}
}
else {
if (!this.getValue())
this.input.val('');
this.input.removeAttr("placeholder");
}
this.options.blur = _value;
}
set_autocomplete(_value) {
this.options.autocomplete = _value;
this.input.attr('autocomplete', _value);
}
resize(_height) {
if (_height && this.options.multiline) {
// apply the ratio
_height = (this.options.resize_ratio != '') ? _height * this.options.resize_ratio : _height;
if (_height != 0) {
this.input.height(this.input.height() + _height);
// resize parent too, so mailvelope injected into parent inherits its height
this.input.parent().height(this.input.parent().height() + _height);
}
}
}
}
et2_textbox._attributes = {
"multiline": {
"name": "multiline",
"type": "boolean",
"default": false,
"description": "If true, the textbox is a multiline edit field."
},
"size": {
"name": "Size",
"type": "integer",
"default": et2_no_init,
"description": "Field width"
},
"maxlength": {
"name": "Maximum length",
"type": "integer",
"default": et2_no_init,
"description": "Maximum number of characters allowed"
},
"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."
},
// These for multi-line
"rows": {
"name": "Rows",
"type": "integer",
"default": -1,
"description": "Multiline field height - better to use CSS"
},
"cols": {
"name": "Size",
"type": "integer",
"default": -1,
"description": "Multiline field width - better to use CSS"
},
"validator": {
"name": "Validator",
"type": "string",
"default": et2_no_init,
"description": "Perl regular expression eg. '/^[0-9][a-f]{4}$/i'"
},
onkeypress: {
name: "onKeypress",
type: "js",
default: et2_no_init,
description: "JS code or app.$app.$method called when key is pressed, return false cancels it."
}
};
et2_textbox.legacyOptions = ["size", "maxlength", "validator"];
et2_register_widget(et2_textbox, ["textbox", "hidden"]);
/**
* et2_textbox_ro is the dummy readonly implementation of the textbox.
*
* @augments et2_valueWidget
*/
export class et2_textbox_ro extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_textbox_ro._attributes, _child || {}));
this.value = "";
this.span = jQuery(document.createElement("label"))
.addClass("et2_label");
this.value_span = jQuery(document.createElement("span"))
.addClass("et2_textbox_ro")
.appendTo(this.span);
this.setDOMNode(this.span[0]);
}
set_label(label) {
// Remove current label
this.span.contents()
.filter(function () { return this.nodeType == 3; }).remove();
var parts = et2_csvSplit(label, 2, "%s");
this.span.prepend(parts[0]);
this.span.append(parts[1]);
this.label = label;
// add class if label is empty
this.span.toggleClass('et2_label_empty', !label || !parts[0]);
}
set_value(_value) {
this.value = _value;
if (!_value) {
_value = "";
}
if (this.label != "") {
this.span.removeClass('et2_label_empty');
}
else {
this.span.addClass('et2_label_empty');
}
this.value_span.text(_value);
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "label");
}
getDetachedNodes() {
return [this.span[0], this.value_span[0]];
}
setDetachedAttributes(_nodes, _values) {
this.span = jQuery(_nodes[0]);
this.value_span = jQuery(_nodes[1]);
if (typeof _values["label"] != 'undefined') {
this.set_label(_values["label"]);
}
if (typeof _values["value"] != 'undefined') {
this.set_value(_values["value"]);
}
}
}
/**
* Ignore all more advanced attributes.
*/
et2_textbox_ro._attributes = {
"multiline": {
"ignore": true
},
"maxlength": {
"ignore": true
},
"onchange": {
"ignore": true
},
"rows": {
"ignore": true
},
"cols": {
"ignore": true
},
"size": {
"ignore": true
},
"needed": {
"ignore": true
}
};
et2_register_widget(et2_textbox_ro, ["textbox_ro"]);
/**
* et2_searchbox is a widget which provides a collapsable input search
* with on searching indicator and clear handler regardless of any browser limitation.
*/
export class et2_searchbox extends et2_textbox {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_searchbox._attributes, _child || {}));
this.value = "";
}
createInputWidget() {
var self = this;
this.div = jQuery(document.createElement('div'))
.addClass('et2_searchbox');
this.flex = jQuery(document.createElement('div'))
.addClass('flex')
.appendTo(this.div);
this.setDOMNode(this.div[0]);
if (this.options.overlay)
this.flex.addClass('overlay');
// search button indicator
// no need to create search button if it's a fix search field
if (!this.options.fix) {
this.button = jQuery(document.createElement('button'))
.css({ "background-image": egw.image('search') })
.click(function () {
self._show_hide(jQuery(self.flex).hasClass('hide'));
self.search.input.focus();
})
.addClass('et2_button');
this.div.prepend(this.button);
}
// input field
this.search = et2_createWidget('textbox', { "blur": egw.lang("search"),
onkeypress: function (event) {
if (event.which == 13) {
event.preventDefault();
self.getInstanceManager().autocomplete_fixer();
// Use a timeout to make sure we get the autocomplete value,
// if one was chosen, instead of what was actually typed.
// Chrome doesn't need this, but FF does.
window.setTimeout(function () {
self.set_value(self.search.input.val());
self.change();
}, 0);
}
} }, this);
// Autocomplete needs name
this.search.input.attr('name', this.id || 'searchbox');
this.search.input.on({
keyup: function (event) {
self.clear.toggle(self.get_value() != '' || !self.options.fix);
if (event.which == 27) // Escape
{
// Excape clears search
self.set_value('');
}
},
blur(event) {
if (egwIsMobile())
return;
if (!event.relatedTarget || !jQuery(event.relatedTarget.parentNode).hasClass('et2_searchbox')) {
self._show_hide((!self.options.overlay && self.get_value()));
}
if (typeof self.oldValue != 'undefined' && self._oldValue != self.get_value()) {
self.change();
}
},
mousedown: function (event) {
if (event.target.tagName == 'span')
event.stopImmediatePropagation();
}
});
// clear button implementation
this.clear = jQuery(document.createElement('span'))
.addClass('ui-icon clear')
.toggle(!this.options.fix || (this._oldValue != '' && !jQuery.isEmptyObject(this._oldValue)))
.on('mousedown', function (event) {
event.preventDefault();
})
.on('click', function (event) {
if (self.get_value()) {
self.search.input.val('');
self.search.input.focus();
self._show_hide(true);
if (self._oldValue)
self.change();
}
else {
self._show_hide(false);
}
if (self.options.fix)
self.clear.hide();
})
.appendTo(this.flex);
}
/**
* Show/hide search field
* @param {boolean} _stat true means show and false means hide
*/
_show_hide(_stat) {
// Not applied for fix option
if (this.options.fix)
return;
jQuery(this.flex).toggleClass('hide', !_stat);
jQuery(this.getDOMNode(this)).toggleClass('expanded', _stat);
}
/**
* toggle search button status based on value
*/
_searchToggleState() {
if (this.options.fix || egwIsMobile())
return;
if (!this.get_value()) {
jQuery(this.button.getDOMNode()).removeClass('toolbar_toggled');
this.button.set_statustext('');
}
else {
jQuery(this.button.getDOMNode()).addClass('toolbar_toggled');
this.button.set_statustext(egw.lang("search for '%1'", this.get_value()));
}
}
/**
* override change function in order to preset the toggle state
*/
change() {
this._searchToggleState();
super.change.apply(this, arguments);
}
getInputNode() {
var _a;
return (_a = this.search) === null || _a === void 0 ? void 0 : _a.input.get(0);
}
get_value() {
return this.search.input.val();
}
set_value(_value) {
super.set_value(_value);
if (this.search) {
this.search.input.val(_value);
this.clear.toggle(_value != '');
}
}
set_readonly(_readonly) {
this.search.set_readonly(_readonly);
}
set_blur(_value) {
this.search.set_blur(_value);
}
/**
* override doLoadingFinished in order to set initial state
*/
doLoadingFinished() {
super.doLoadingFinished();
if (!this.get_value()) {
this._show_hide(false);
}
else {
this._show_hide(!this.options.overlay);
this._searchToggleState();
}
return true;
}
getDOMNode(asker) {
if (asker && asker.getParent() == this) {
return this.flex[0];
}
return super.getDOMNode(asker);
}
/**
* Overrride attachToDOM in order to unbind change handler
*/
attachToDOM() {
let ret = super.attachToDOM();
var node = this.getInputNode();
if (node) {
jQuery(node).off('.et2_inputWidget');
}
return ret;
}
}
/**
* Advanced attributes
*/
et2_searchbox._attributes = {
overlay: {
name: "Overlay searchbox",
type: "boolean",
default: false,
description: "Define wheter the searchbox overlays while it's open (true) or stay as solid box infront of the search button (false). Default is false."
},
fix: {
name: "Fix searchbox",
type: "boolean",
default: true,
description: "Define whether the searchbox should be a fix input field or flexible search button. Default is true (fix)."
}
};
et2_register_widget(et2_searchbox, ["searchbox"]);
//# sourceMappingURL=et2_widget_textbox.js.map

View File

@ -1,172 +0,0 @@
/**
* EGroupware eTemplate2 - JS Timestamp button object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2017
*/
/*egw:uses
et2_button;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_button } from "./et2_widget_button";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
import { et2_IInput } from "./et2_core_interfaces";
/**
* Class which implements the "button-timestamper" XET-Tag
*
* Clicking the button puts the current time and current user at the end of
* the provided field.
*
* @augments et2_button
*/
export class et2_timestamper extends et2_button {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_timestamper._attributes, _child || {}));
jQuery(this.getDOMNode()).addClass('et2_timestamper');
}
/**
* Overwritten to maintain an internal clicked attribute
*
* @param _ev
* @returns {Boolean}
*/
click(_ev) {
// ignore click on readonly button
if (this.options.readonly)
return false;
this._insert_text();
return false;
}
_insert_text() {
let text = "";
let now = new Date(new Date().toLocaleString('en-US', {
timeZone: this.options.timezone ? this.options.timezone : egw.preference('tz')
}));
let format = (this.options.format ?
this.options.format :
egw.preference('dateformat') + ' ' + (egw.preference("timeformat") === "12" ? "h:ia" : "H:i")) + ' ';
text += date(format, now);
// Get properly formatted user name
let user = parseInt(egw.user('account_id'));
let accounts = egw.accounts('accounts');
for (let j = 0; j < accounts.length; j++) {
if (accounts[j]["value"] === user) {
text += accounts[j]["label"];
break;
}
}
text += ': ';
let widget = this._get_input(this.target);
let input = widget.input ? widget.input : widget.getDOMNode();
if (input.context) {
input = input.get(0);
}
let scrollPos = input.scrollTop;
let browser = ((input.selectionStart || input.selectionStart == "0") ?
"standards" : (document["selection"] ? "ie" : false));
let pos = 0;
let tinymce = tinyMCE && tinyMCE.EditorManager.get(input.id) || false;
// Find cursor or selection
if (browser == "ie") {
input.focus();
let range = document["selection"].createRange();
range.moveStart("character", -input.value.length);
pos = range.text.length;
}
else if (browser == "standards") {
pos = input.selectionStart;
}
// If tinymce, update it
if (tinymce) {
tinymce.insertContent(text);
}
else {
// Insert the text
let front = (input.value).substring(0, pos);
let back = (input.value).substring(pos, input.value.length);
input.value = front + text + back;
// Clean up a little
pos = pos + text.length;
if (browser == "ie") {
input.focus();
let range = document["selection"].createRange();
range.moveStart("character", -input.value.length);
range.moveStart("character", pos);
range.moveEnd("character", 0);
range.select();
}
else if (browser == "standards") {
input.selectionStart = pos;
input.selectionEnd = pos;
input.focus();
}
input.scrollTop = scrollPos;
input.focus();
}
// If on a tab, switch to that tab so user can see it
let tab = widget;
while (tab._parent && tab._type != 'tabbox') {
tab = tab._parent;
}
if (tab._type == 'tabbox')
tab.activateTab(widget);
}
_get_input(target) {
let input = null;
let widget = null;
if (typeof target == 'string') {
widget = this.getRoot().getWidgetById(target);
}
else if (target.instanceOf && target.instanceOf(et2_IInput)) {
widget = target;
}
else if (typeof target == 'string' && target.indexOf('#') < 0 && jQuery('#' + this.target).is('input')) {
input = this.target;
}
if (widget) {
return widget;
}
if (input === null || input === void 0 ? void 0 : input.context) {
input = input.get(0);
}
return input;
}
}
et2_timestamper._attributes = {
target: {
name: "Target field",
type: "string",
default: et2_no_init,
description: "Which field to place the timestamp in"
},
format: {
name: "Time format",
type: "string",
default: et2_no_init,
description: "Format for the timestamp. User is always after."
},
timezone: {
name: "Timezone",
type: "string",
default: et2_no_init,
description: "Timezone. Default is user time."
},
statustext: {
default: "Insert timestamp into description field"
},
image: {
default: "timestamp"
},
background_image: {
default: true
}
};
et2_register_widget(et2_timestamper, ["button-timestamp", "timestamper"]);
//# sourceMappingURL=et2_widget_timestamper.js.map

View File

@ -1,670 +0,0 @@
/**
* EGroupware eTemplate2 - JS toolbar object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2013
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/vendor/bower-asset/jquery-ui/jquery-ui.js;
et2_DOMWidget;
*/
import { et2_DOMWidget } from "./et2_core_DOMWidget";
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { egwActionObject, egw_getObjectManager, egwActionObjectManager } from '../egw_action/egw_action.js';
import { et2_dialog } from "./et2_widget_dialog";
import { egw } from "../jsapi/egw_global";
import { egwIsMobile } from "../egw_action/egw_action_common.js";
/**
* This toolbar gets its contents from its actions
*
* @augments et2_valueWidget
*/
export class et2_toolbar extends et2_DOMWidget {
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_toolbar._attributes, _child || {}));
/**
* id of last action executed / value of toolbar if submitted
*/
this.value = null;
/**
* actionbox is a div for stored actions
*/
this.actionbox = null;
/**
* actionlist is a div for active actions
*/
this.actionlist = null;
this.div = null;
this.countActions = 0;
this.dropdowns = {};
this.preference = {};
this.menu = null;
this._objectManager = null;
this.div = jQuery(document.createElement('div'))
.addClass('et2_toolbar ui-widget-header ui-corner-all');
// Set proper id and dom_id for the widget
this.set_id(this.id);
this.actionbox = jQuery(document.createElement('div'))
.addClass("et2_toolbar_more")
.attr('id', this.id + '-' + 'actionbox');
this.actionlist = jQuery(document.createElement('div'))
.addClass("et2_toolbar_actionlist")
.attr('id', this.id + '-' + 'actionlist');
this.countActions = 0;
this.dropdowns = {};
this.preference = {};
this._build_menu(et2_toolbar.default_toolbar, true);
}
destroy() {
// Destroy widget
if (this.div && this.div.data('ui-menu'))
this.menu.menu("destroy");
// Null children
// Remove
this.div.empty().remove();
this.actionbox.empty().remove();
this.actionlist.empty().remove();
}
/**
* Fix function in order to fix toolbar preferences with the new preference structure
* @param {action object} _action
* @todo ** SEE IMPORTANT TODO **
*/
_fix_preference(_action) {
// ** IMPORTANT TODO: This switch case should be removed for new release **
// This is an ugly hack but we need to add this switch becuase to update and fix
// current users toolbar preferences with the new structure which is:
// - All actions should be stored in preference
// - Actions inside menu set as true
// - Actions outside menu set as false
// - if an action gets added to toolbar it would be undefined in
// the preference which we need to consider to add it to the preference
// according to its toolbarDefault option.
if (this.dom_id === 'mail-display_displayToolbar' || this.dom_id === 'mail-index_toolbar') {
switch (_action.id) {
// Actions newly added to mail index and display toolbar
case 'read':
case 'label1':
case 'label2':
case 'label3':
case 'label4':
case 'label5':
this.set_prefered(_action.id, !_action.toolbarDefault);
break;
default:
// Fix structure and add the actions not the preference
// into the preference with value false, as they're already
// outside of the menu.
this.set_prefered(_action.id, false);
}
}
else {
// ** IMPORTANT TODO: This line needs to stay and be fixed with !toolbarDefault after the if condition
// has been removed.
this.set_prefered(_action.id, false /*!toolbarDefault*/);
}
}
/**
* Count number of actions including their children
* @param {object} actions
* @return {number} return total number of actions
*/
_countActions(actions) {
let totalCount = 0;
let childCounter = function (action, count) {
let children = action.children || 0, returnCounter = count || 0;
if (children) {
returnCounter -= 1;
for (let nChild in children) {
returnCounter += 1;
returnCounter = childCounter(children[nChild], returnCounter);
}
}
else {
returnCounter = count;
}
return returnCounter;
};
for (let nAction in actions) {
if (this.options.flat_list) {
totalCount += childCounter(actions[nAction], 1);
}
else {
totalCount++;
}
}
return totalCount;
}
/**
* Go through actions and build buttons for the toolbar
*
* @param {Object} actions egw-actions to build menu from
* @param {boolean} isDefault setting isDefault with true will
* avoid actions get into the preferences, for instandce, first
* time toolbar_default actions initialization.
*/
_build_menu(actions, isDefault) {
// Clear existing
this.div.empty();
this.actionbox.empty();
this.actionlist.empty();
let admin_setting = this.options.is_admin ? '<span class="toolbar-admin-pref" title="' + egw.lang('Admin settings') + ' ..."></span>' : '';
this.actionbox.append('<h class="ui-toolbar-menulistHeader">' + egw.lang('more') + ' ...' + admin_setting + '</h>');
this.actionbox.append('<div id="' + this.id + '-menulist' + '" class="ui-toolbar-menulist" ></div>');
let that = this;
if (this.options.is_admin) {
this.actionbox.find('.toolbar-admin-pref').click(function (e) {
e.stopImmediatePropagation();
egw.json('EGroupware\\Api\\Etemplate\\Widget\\Toolbar::ajax_get_default_prefs', [egw.app_name(), that.dom_id], function (_prefs) {
let prefs = [];
for (let p in _prefs) {
if (_prefs[p] === false)
prefs.push(p);
}
that._admin_settings_dialog.call(that, actions, prefs);
}).sendRequest(true);
});
}
let pref = (!egwIsMobile()) ? egw.preference(this.dom_id, this.egw().app_name()) : undefined;
if (pref && !jQuery.isArray(pref))
this.preference = pref;
//Set the default actions for the first time
if (typeof pref === 'undefined' && !isDefault) {
for (var name in actions) {
if ((typeof actions[name].children === 'undefined' || !this.options.flat_list) && actions[name].id) {
this.set_prefered(actions[name].id, !actions[name].toolbarDefault);
}
}
}
else if (!isDefault) {
for (var name in actions) {
// Check if the action is not in the preference, means it's an new added action
// therefore it needs to be added to the preference with taking its toolbarDefault
// option into account.
if ((typeof actions[name].children === 'undefined' || !this.options.flat_list)
&& typeof pref[name] === 'undefined') {
this._fix_preference(actions[name]);
}
}
}
let menuLen = 0;
for (let key in this.preference) {
if (this.preference[key])
menuLen++;
}
this.countActions = this._countActions(actions) - menuLen;
let last_group = null;
let last_group_id = null;
for (let name in actions) {
let action = actions[name];
if (typeof action == 'string')
action = { id: name, caption: action };
if (typeof action.group == 'undefined') {
action.group = 'default';
}
// Add in divider
if (last_group_id != action.group) {
last_group = jQuery('[data-group="' + action.group + '"]', this.actionlist);
if (last_group.length == 0) {
jQuery('<span data-group="' + action.group + '">').appendTo(this.actionlist);
}
last_group_id = action.group;
}
// Make sure there's something to display
if (!action.caption && !action.icon && !action.iconUrl)
continue;
if (action.children) {
let children = {};
let add_children = function (root, children) {
for (let id in root.children) {
let info = {
id: id || root.children[id].id,
label: root.children[id].caption
};
let childaction = {};
if (root.children[id].iconUrl) {
info['icon'] = root.children[id].iconUrl;
}
if (root.children[id].children) {
add_children(root.children[id], info);
}
children[id] = info;
if (that.options.flat_list) {
childaction = root.children[id];
if (typeof pref === 'undefined' && !isDefault) {
if (!childaction['toolbarDefault']) {
that.set_prefered(childaction['id'], true);
}
else {
that.set_prefered(childaction['id'], false);
}
}
else if (!isDefault) {
if (typeof pref[childaction['id']] === 'undefined') {
that._fix_preference(childaction);
}
}
if (typeof root.children[id].group !== 'undefined' &&
typeof root.group !== 'undefined') {
childaction['group'] = root.group;
}
that._make_button(childaction);
}
}
};
add_children(action, children);
if (this.options.flat_list && children) {
continue;
}
let dropdown = et2_createWidget("dropdown_button", {
id: action.id
}, this);
dropdown.set_select_options(children);
dropdown.set_label(action.caption);
//Set default selected action
if (typeof action.children != 'undefined') {
for (let child in action.children) {
if (action.children[child].default) {
dropdown.set_label(action.children[child].caption);
}
}
}
dropdown.set_image(action.iconUrl || '');
dropdown.onchange = jQuery.proxy(function (selected, dropdown) {
let action = that._actionManager.getActionById(selected.attr('data-id'));
dropdown.set_label(action.caption);
if (action) {
this.value = action.id;
action.execute([]);
}
//console.debug(selected, this, action);
}, action);
dropdown.onclick = jQuery.proxy(function (selected, dropdown) {
let action = that._actionManager.getActionById(this.getValue());
if (action) {
this.value = action.id;
action.execute([]);
}
//console.debug(selected, this, action);
}, dropdown);
jQuery(dropdown.getDOMNode())
.attr('id', this.id + '-' + dropdown.id)
.addClass(this.preference[action.id] ? 'et2_toolbar-dropdown et2_toolbar-dropdown-menulist' : 'et2_toolbar-dropdown')
.appendTo(this.preference[action.id] ? this.actionbox.children()[1] : jQuery('[data-group=' + action.group + ']', this.actionlist));
}
else {
this._make_button(action);
}
}
// ************** Drag and Drop feature for toolbar *****
this.actionlist.find('span[data-group]').sort(function (lg, g) {
return +lg.getAttribute('data-group') - +g.getAttribute('data-group');
}).appendTo(this.actionlist);
this.actionlist.appendTo(this.div);
this.actionbox.appendTo(this.div);
let toolbar = this.actionlist.find('span[data-group]').children(), toolbox = this.actionbox, menulist = jQuery(this.actionbox.children()[1]);
toolbar.draggable({
cancel: '',
zIndex: 1000,
delay: 500,
//revert:"invalid",
containment: "document",
cursor: "move",
helper: "clone",
appendTo: 'body',
stop: function (event, ui) {
that._build_menu(actions);
}
});
menulist.children().draggable({
cancel: '',
containment: "document",
helper: "clone",
appendTo: 'body',
zIndex: 1000,
cursor: "move",
start: function () {
jQuery(that.actionlist).addClass('et2_toolbarDropArea');
},
stop: function () {
jQuery(that.actionlist).removeClass('et2_toolbarDropArea');
}
});
toolbox.children().droppable({
accept: toolbar,
drop: function (event, ui) {
that.set_prefered(ui.draggable.attr('id').replace(that.id + '-', ''), true);
ui.draggable.appendTo(menulist);
if (that.actionlist.find(".ui-draggable").length == 0) {
that.preference = {};
egw.set_preference(that.egw().app_name(), that.dom_id, that.preference);
}
},
tolerance: "touch"
});
this.actionlist.droppable({
tolerance: "pointer",
drop: function (event, ui) {
that.set_prefered(ui.draggable.attr('id').replace(that.id + '-', ''), false);
ui.draggable.appendTo(that.actionlist);
that._build_menu(actions);
}
});
toolbox.accordion({
heightStyle: "fill",
collapsible: true,
active: 'none',
activate: function (event, ui) {
var menubox = event.target;
if (ui.oldHeader.length == 0) {
jQuery('html').on('click.outsideOfMenu', function (event) {
jQuery(menubox).accordion("option", "active", 2);
jQuery(this).unbind(event);
// Remove the focus class, user clicked elsewhere
jQuery(menubox).children().removeClass('ui-state-focus');
});
}
},
create: function (event, ui) {
jQuery('html').unbind('click.outsideOfMenu');
},
beforeActivate: function () {
if (egwIsMobile()) {
menulist.height(screen.availHeight - 50);
}
else {
menulist.css({ height: 'inherit' });
}
// Nothing to show in menulist
if (menulist.children().length == 0)
return false;
}
});
}
/**
* Add/Or remove an action from prefence
*
* @param {string} _action name of the action which needs to be stored in pereference
* @param {boolean} _state if set to true action will be set to actionbox, false will set it to actionlist
*
*/
set_prefered(_action, _state) {
this.preference[_action] = _state;
if (egwIsMobile())
return;
egw.set_preference(this.egw().app_name(), this.dom_id, this.preference);
}
/**
* Make a button based on the given action
*
* @param {Object} action action object with attributes icon, caption, ...
*/
_make_button(action) {
let button_options = {};
let button = jQuery(document.createElement('button'))
.addClass("et2_button et2_button_text et2_button_with_image")
.attr('id', this.id + '-' + action.id)
.attr('type', 'button')
.appendTo(this.preference[action.id] ? this.actionbox.children()[1] : jQuery('[data-group=' + action.group + ']', this.actionlist));
if (action && action.checkbox) {
if (action.data.toggle_on || action.data.toggle_off) {
let toggle = et2_createWidget('checkbox', {
id: this.id + '-' + action.id,
toggle_on: action.data.toggle_on,
toggle_off: action.data.toggle_off
}, this);
toggle.doLoadingFinished();
toggle.set_value(action.checked);
action.data.widget = toggle;
let toggle_div = toggle.toggle;
toggle_div.appendTo(button.parent())
.attr('id', this.id + '-' + action.id);
button.remove();
button = toggle_div;
}
else {
if (this.checkbox(action.id))
button.addClass('toolbar_toggled' + (typeof action.toggledClass != 'undefined' ? " " + action.toggledClass : ''));
}
}
this.egw().tooltipBind(button, action.hint ? action.hint : action.caption) + (action.shortcut ? ' (' + action.shortcut.caption + ')' : '');
if (action.iconUrl) {
button.attr('style', 'background-image:url(' + action.iconUrl + ')');
}
if (action.caption) {
if ((this.countActions <= parseInt(this.options.view_range) ||
this.preference[action.id] || !action.iconUrl) &&
typeof button[0] !== 'undefined' &&
!(action.checkbox && action.data && (action.data.toggle_on || action.data.toggle_off))) // no caption for slideswitch checkboxes
{
button.addClass(action.iconUrl ? 'et2_toolbar_hasCaption' : 'et2_toolbar_onlyCaption');
button[0].textContent = action.caption;
}
}
if (action.icon) {
button_options['icon'] = action.icon;
}
if (!jQuery.isEmptyObject(button_options)) {
button.button(button_options);
}
let self = this;
// Set up the click action
let click = function (e) {
let action = this._actionManager.getActionById(e.data);
if (action) {
if (action.checkbox) {
self.checkbox(action.id, !action.checked);
}
this.value = action.id;
action.data.event = e;
action.execute([]);
}
};
button.click(action.id, jQuery.proxy(click, this));
}
/**
* Link the actions to the DOM nodes / widget bits.
*
* @param {Object} actions egw-actions to build menu from
*/
_link_actions(actions) {
this._build_menu(actions);
let self = this;
let gom = egw_getObjectManager(this.egw().app_name(), true, 1);
if (this._objectManager == null) {
this._objectManager = gom.addObject(new egwActionObjectManager(this.id, this._actionManager));
this._objectManager.handleKeyPress = function (_keyCode, _shift, _ctrl, _alt) {
for (let i = 0; i < self._actionManager.children.length; i++) {
let action = self._actionManager.children[i];
if (typeof action.shortcut === 'object' &&
action.shortcut &&
_keyCode == action.shortcut.keyCode &&
_ctrl == action.shortcut.ctrl &&
_alt == action.shortcut.alt &&
_shift == action.shortcut.shift) {
self.value = action.id;
action.execute([]);
return true;
}
}
return egwActionObject.prototype.handleKeyPress.call(this, _keyCode, _shift, _ctrl, _alt);
};
this._objectManager.parent.updateFocusedChild(this._objectManager, true);
}
}
/**
* Set/Get the checkbox toolbar action
*
* @param {string} _action action name of the selected toolbar
* @param {boolean} _value value that needs to be set for the action true|false
* - if no value means checkbox value returns the current value
*
* @returns {boolean} returns boolean result of get checkbox value
* or returns undefined as Set result or failure
*/
checkbox(_action, _value) {
if (!_action || typeof this._actionManager == 'undefined')
return undefined;
let action_event = this._actionManager.getActionById(_action);
if (action_event && typeof _value != 'undefined') {
action_event.set_checked(_value);
var btn = jQuery('#' + this.id + '-' + _action);
if (action_event.data && action_event.data.widget) {
action_event.data.widget.set_value(_value);
}
else if (btn.length > 0) {
btn.toggleClass('toolbar_toggled' + (typeof action_event.data.toggledClass != 'undefined' ? " " + action_event.data.toggledClass : ''), _value);
}
}
else if (action_event) {
return action_event.checked;
}
else {
return undefined;
}
}
getDOMNode() {
return this.div[0];
}
/**
* getValue has to return the value of the input widget
*/
getValue() {
return this.value;
}
/**
* Is dirty returns true if the value of the widget has changed since it
* was loaded. We don't consider toolbars as dirtyable
*/
isDirty() {
return false;
}
/**
* Causes the dirty flag to be reseted.
*/
resetDirty() {
this.value = null;
}
/**
* Checks the data to see if it is valid, as far as the client side can tell.
* Return true if it's not possible to tell on the client side, because the server
* will have the chance to validate also.
*
* The messages array is to be populated with everything wrong with the data,
* so don't stop checking after the first problem unless it really makes sense
* to ignore other problems.
*
* @param {String[]} messages List of messages explaining the failure(s).
* messages should be fairly short, and already translated.
*
* @return {boolean} True if the value is valid (enough), false to fail
*/
isValid(messages) {
return true;
}
/**
* Attach the container node of the widget to DOM-Tree
* @returns {Boolean}
*/
doLoadingFinished() {
super.doLoadingFinished();
return false;
}
/**
* Builds dialog for possible admin settings (e.g. default actions pref)
*
* @param {type} _actions
* @param {object} _default_prefs
*/
_admin_settings_dialog(_actions, _default_prefs) {
let buttons = [
{ text: egw.lang("Save"), id: "save" },
{ text: egw.lang("Close"), id: "close" }
];
let self = this;
let sel_options = { actions: [] };
let content = { actions: [], reset: false };
for (let key in _actions) {
if (_actions[key]['children'] && this.options.flat_list) {
for (let child in _actions[key]['children']) {
sel_options.actions.push({
id: child,
value: child,
label: _actions[key]['children'][child]['caption'],
app: egw.app_name(),
icon: _actions[key]['children'][child]['iconUrl']
});
}
}
else {
sel_options.actions.push({
id: key,
value: key,
label: _actions[key]['caption'],
app: egw.app_name(),
icon: _actions[key]['iconUrl']
});
}
if ((!_default_prefs || _default_prefs.length == 0) && _actions[key]['toolbarDefault'])
content.actions.push(key);
}
if (_default_prefs && _default_prefs.length > 0)
content.actions = _default_prefs;
et2_createWidget("dialog", {
callback: function (_button_id, _value) {
if (_button_id == 'save' && _value) {
if (_value.actions) {
let pref = jQuery.extend({}, self.preference);
for (let i in pref) {
pref[i] = true;
if (_value.actions.includes(i))
pref[i] = false;
}
_value.actions = pref;
}
egw.json('EGroupware\\Api\\Etemplate\\Widget\\Toolbar::ajax_setAdminSettings', [_value, self.dom_id, egw.app_name()], function (_result) {
egw.message(_result);
}).sendRequest(true);
}
},
title: egw.lang('admin settings for %1', this.dom_id),
buttons: buttons,
minWidth: 600,
minHeight: 300,
value: { content: content, sel_options: sel_options },
template: egw.webserverUrl + '/api/templates/default/toolbarAdminSettings.xet?1',
resizable: false
}, et2_dialog._create_parent('api'));
}
}
et2_toolbar._attributes = {
"view_range": {
"name": "View range",
"type": "string",
"default": "5",
"description": "Define minimum action view range to show actions by both icons and caption"
},
"flat_list": {
"name": "Flat list",
"type": "boolean",
"default": true,
"description": "Define whether the actions with children should be shown as dropdown or flat list"
}
};
/**
* Default buttons, so there is something for the widget browser / editor to show
*/
et2_toolbar.default_toolbar = {
view: { caption: 'View', icons: { primary: 'ui-icon-check' }, group: 1, toolbarDefault: true },
edit: { caption: 'Edit', group: 1, toolbarDefault: true },
save: { caption: 'Save', group: 2, toolbarDefault: true }
};
et2_register_widget(et2_toolbar, ["toolbar"]);
//# sourceMappingURL=et2_widget_toolbar.js.map

View File

@ -1,841 +0,0 @@
/**
* EGroupware eTemplate2 - JS Tree object
*
* @link http://community.egroupware.org/egroupware/api/js/dhtmlxtree/docsExplorer/dhtmlxtree/
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @author Ralf Becker
* @copyright Nathan Gray 2011
*/
/*egw:uses
et2_core_inputWidget;
/api/js/egw_action/egw_dragdrop_dhtmlx_tree.js;
/api/js/dhtmlxtree/codebase/dhtmlxcommon.js;
// using debugable and fixed source of dhtmltree instead: /api/js/dhtmlxtree/js/dhtmlXTree.js;
/api/js/dhtmlxtree/sources/dhtmlxtree.js;
/api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js;
// /api/js/dhtmlxtree/sources/ext/dhtmlxtree_start.js;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_inputWidget } from "./et2_core_inputWidget";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_no_init } from "./et2_core_common";
import { egw, framework } from "../jsapi/egw_global";
import { egw_getAppObjectManager, egw_getObjectManager, egwActionObject } from "../egw_action/egw_action.js";
import { EGW_AO_FLAG_IS_CONTAINER } from "../egw_action/egw_action_constants.js";
import { dhtmlxtreeItemAOI } from "../egw_action/egw_dragdrop_dhtmlx_tree.js";
import { egwIsMobile } from "../egw_action/egw_action_common.js";
/* no module, but egw:uses is ignored, so adding it here commented out
import '../../../api/js/dhtmlxtree/sources/dhtmlxtree.js';
import '../../../api/js/dhtmlxtree/sources/ext/dhtmlxtree_json.js';
import '../../../api/js/dhtmlxtree/sources/ext/dhtmlxtree_start.js';
*/
/**
* Tree widget
*
* For syntax of nodes supplied via sel_options or autoloading refer to Etemplate\Widget\Tree class.
*
* @augments et2_inputWidget
*/
export class et2_tree extends et2_inputWidget {
/**
* Constructor
*
* @memberOf et2_tree
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_tree._attributes, _child || {}));
this.input = null;
/**
* Regexp used by _htmlencode
*/
this._lt_regexp = /</g;
this.input = null;
this.div = jQuery(document.createElement("div")).addClass("dhtmlxTree");
this.setDOMNode(this.div[0]);
}
destroy() {
if (this.input) {
this.input.destructor();
}
this.input = null;
super.destroy();
}
/**
* Get tree items from the sel_options data array
*
* @param {object} _attrs
*/
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)) {
return;
}
let name_parts = this.id.replace(/]/g, '').split('[');
// Try to find the options inside the "sel-options" array
if (this.getArrayMgr("sel_options")) {
// Select options tend to be defined once, at the top level, so try that first
let content_options = this.getArrayMgr("sel_options").getRoot().getEntry(name_parts[name_parts.length - 1]);
// Try again according to ID
if (!content_options)
content_options = this.getArrayMgr("sel_options").getEntry(this.id);
if (_attrs["select_options"] && !jQuery.isEmptyObject(_attrs["select_options"]) && content_options) {
_attrs["select_options"] = jQuery.extend({}, _attrs["select_options"], content_options);
}
else if (content_options) {
_attrs["select_options"] = content_options;
}
}
// Check whether the options entry was found, if not read it from the
// content array.
if (_attrs["select_options"] == null) {
// Again, try last name part at top level
let content_options = this.getArrayMgr('content').getRoot().getEntry(name_parts[name_parts.length - 1]);
// If that didn't work, check according to ID
_attrs["select_options"] = content_options ? content_options : this.getArrayMgr('content')
.getEntry("options-" + this.id);
}
// Default to an empty object
if (_attrs["select_options"] == null) {
_attrs["select_options"] = {};
}
}
// overwrite default onclick to do nothing, as we install onclick via dhtmlxtree
click(_node) { }
createTree(widget) {
widget.input = new dhtmlXTreeObject({
parent: widget.div[0],
width: '100%',
height: '100%',
image_path: widget.options.image_path,
checkbox: widget.options.multiple
});
// to allow "," in value, eg. folder-names, IF value is specified as array
widget.input.dlmtr = ':}-*(';
if (widget.options.std_images) {
widget.setImages.apply(widget, widget.options.std_images.split(','));
}
else {
// calling setImages to get our png or svg default images
widget.setImages();
}
// Add in the callback so we can keep the two in sync
widget.input.AJAX_callback = function (dxmlObject) {
widget._dhtmlxtree_json_callback(JSON.parse(dxmlObject.xmlDoc.responseText), widget.input.lastLoadedXMLId);
// Call this in case we added some options that were already selected, but missing
if (widget.options.multiple) {
widget.set_value(widget.value);
}
};
if (widget.options.autoloading) {
let url = widget.options.autoloading;
//Set escaping mode to utf8, as url in
//autoloading needs to be utf8 encoded.
//For instance item id with umlaut.
widget.input.setEscapingMode('utf8');
if (url.charAt(0) != '/' && url.substr(0, 4) != 'http') {
url = '/json.php?menuaction=' + url;
}
this.autoloading_url = url;
widget.input.setXMLAutoLoading(egw.link(url));
widget.input.setDataMode('JSON');
}
if (widget.options.multimarking) {
widget.input.enableMultiselection(!!widget.options.multimarking, widget.options.multimarking === 'strict');
}
// Enable/Disable highlighting
widget.input.enableHighlighting(!!widget.options.highlighting);
// if templates supplies open/close right/down arrows, show no more lines and use them instead of plus/minus
let open = egw.image('dhtmlxtree/open');
let close = egw.image('dhtmlxtree/close');
if (open && close) {
widget.input.enableTreeLines(false);
open = this._rel_url(open);
widget.input.setImageArrays('plus', open, open, open, open, open);
close = this._rel_url(close);
widget.input.setImageArrays('minus', close, close, close, close, close);
}
this._install_handler('onBeforeCheck', function () {
return !this.options.readonly;
}.bind(this));
}
/**
* Install event handlers on tree
*
* @param _name
* @param _handler
*/
_install_handler(_name, _handler) {
if (typeof _handler == 'function') {
if (this.input == null)
this.createTree(this);
// automatic convert onChange event to oncheck or onSelect depending on multiple is used or not
if (_name == 'onchange')
_name = this.options.multiple ? 'oncheck' : 'onselect';
let handler = _handler;
let widget = this;
this.input.attachEvent(_name, function (_id) {
let args = jQuery.makeArray(arguments);
// splice in widget as 2. parameter, 1. is new node-id, now 3. is old node id
args.splice(1, 0, widget);
// try to close mobile sidemenu after clicking on node
if (egwIsMobile() && typeof args[2] == 'string')
framework.toggleMenu('on');
return handler.apply(this, args);
});
}
}
set_onchange(_handler) { this._install_handler('onchange', _handler); }
set_onclick(_handler) { this._install_handler('onclick', _handler); }
set_onselect(_handler) { this._install_handler('onselect', _handler); }
set_onopenstart(_handler) { this._install_handler('onOpenStart', _handler); }
set_onopenend(_handler) { this._install_handler('onOpenEnd', _handler); }
set_select_options(options) {
let custom_images = false;
this.options.select_options = options;
if (this.input == null) {
this.createTree(this);
}
// Structure data for category tree
if (this.getType() == 'tree-cat') {
let data = { id: 0, item: [] };
let stack = {};
for (let key = 0; key < options.length; key++) {
// See if item has an icon
if (options[key].data && typeof options[key].data.icon !== 'undefined' && options[key].data.icon) {
let img = this.egw().image(options[key].data.icon, options[key].appname);
if (img) {
custom_images = true;
options[key].im0 = options[key].im1 = options[key].im2 = img;
}
}
// Item color - not working
if (options[key].data && typeof options[key].data.color !== 'undefined' && options[key].data.color) {
options[key].style = options[key].style || "" + "background-color:'" + options[key].data.color + "';";
}
// Tooltip
if (options[key].description && !options[key].tooltip) {
options[key].tooltip = options[key].description;
}
let parent_id = parseInt(options[key]['parent']);
if (isNaN(parent_id))
parent_id = 0;
if (!stack[parent_id])
stack[parent_id] = [];
stack[parent_id].push(options[key]);
}
if (custom_images) {
let path = this.input.iconURL;
this.input.setIconPath("");
for (let k = 0; k < this.input.imageArray.length; k++)
this.input.imageArray[k] = path + this.input.imageArray[k];
}
let f = function (data, _f) {
if (stack[data.id]) {
data.item = stack[data.id];
for (let j = 0; j < data.item.length; j++) {
f(data.item[j], _f);
}
}
};
f(data, f);
options = data;
}
// if no options given, but autoloading url, use that to load initial nodes
if (typeof options.id == 'undefined' && this.input.XMLsource)
this.input.loadJSON(this.input.XMLsource);
else
this.input.loadJSONObject(this._htmlencode_node(options));
}
/**
* html encoding of text of node
*
* We only do a minimal html encoding by replacing opening bracket < with &lt;
* as tree seems not to need more and we dont want to waste time.
*
* @param {string} _text text to encode
* @return {string}
*/
_htmlencode(_text) {
if (_text && _text.indexOf('<') >= 0) {
_text = _text.replace(this._lt_regexp, '&lt;');
}
return _text;
}
/**
* html encoding of text of node incl. all children
*
* @param {object} _item with required attributes text, id and optional tooltip and item
* @return {object} encoded node
*/
_htmlencode_node(_item) {
_item.text = this._htmlencode(_item.text);
if (_item.item && jQuery.isArray(_item.item)) {
for (let i = 0; i < _item.item.length; ++i) {
this._htmlencode_node(_item.item[i]);
}
}
return _item;
}
set_value(new_value) {
this.value = this._oldValue = (typeof new_value === 'string' && this.options.multiple ? new_value.split(',') : new_value);
if (this.input == null)
return;
if (this.options.multiple) {
// Clear all checked
let checked = this.input.getAllChecked().split(this.input.dlmtr);
for (let i = 0; i < checked.length; i++) {
this.input.setCheck(checked[i], false);
}
// Check selected
for (let i = 0; i < this.value.length; i++) {
this.input.setCheck(this.value[i], true);
// autoloading openning needs to be absolutely based on user interaction
// or open flag in folder structure, therefore, We should
// not force it to open the node
if (!this.options.autoloading)
this.input.openItem(this.value[i]);
}
}
else {
this.input.selectItem(this.value, false); // false = do not trigger onSelect
this.input.focusItem(this.value);
this.input.openItem(this.value);
}
}
/**
* Links actions to tree nodes
*
* @param {object} actions [ {ID: attributes..}+] as for set_actions
*/
_link_actions(actions) {
// Get the top level element for the tree
// Only look 1 level deep for application object manager
let objectManager = egw_getObjectManager(this.egw().app_name(), true, 1);
let treeObj = objectManager.getObjectById(this.id);
if (treeObj == null) {
// Add a new container to the object manager which will hold the tree
// objects
treeObj = objectManager.addObject(new egwActionObject(this.id, objectManager, null, this._actionManager, EGW_AO_FLAG_IS_CONTAINER), null, EGW_AO_FLAG_IS_CONTAINER);
}
// Delete all old objects
treeObj.clear();
// Go over the tree parts & add links
let action_links = this._get_action_links(actions);
if (typeof this.options.select_options != 'undefined') {
// Iterate over the options (leaves) and add action to each one
let apply_actions = function (treeObj, option) {
// Add a new action object to the object manager
// @ts-ignore
let obj = treeObj.addObject((typeof option.id == 'number' ? String(option.id) : option.id), new dhtmlxtreeItemAOI(this.input, option.id));
obj.updateActionLinks(action_links);
if (option.item && option.item.length > 0) {
for (let i = 0; i < option.item.length; i++) {
apply_actions.call(this, treeObj, option.item[i]);
}
}
};
apply_actions.call(this, treeObj, this.options.select_options);
}
}
/**
* getValue, retrieves the Id of the selected Item
* @return string or object or null
*/
getValue() {
if (this.input == null)
return null;
if (this.options.multiple) {
let allChecked = this.input.getAllChecked().split(this.input.dlmtr);
let allUnchecked = this.input.getAllUnchecked().split(this.input.dlmtr);
if (this.options.autoloading) {
let res = {};
for (let i = 0; i < allChecked.length; i++) {
res[allChecked[i]] = { value: true };
}
for (let i = 0; i < allUnchecked.length; i++) {
res[allUnchecked[i]] = { value: false };
}
return res;
}
else {
return allChecked;
}
}
return this.input.getSelectedItemId();
}
/**
* getSelectedLabel, retrieves the Label of the selected Item
* @return string or null
*/
getSelectedLabel() {
if (this.input == null)
return null;
if (this.options.multiple) {
/*
var out = [];
var checked = this.input.getAllChecked().split(this.input.dlmtr);
for(var i = 0; i < checked.length; i++)
{
out.push(this.input.getItemText(checked[i]));
}
return out;
*/
return null; // not supported yet
}
else {
return this.input.getSelectedItemText();
}
}
/**
* renameItem, renames an item by id
*
* @param {string} _id ID of the node
* @param {string} _newItemId ID of the node
* @param {string} _label label to set
*/
renameItem(_id, _newItemId, _label) {
if (this.input == null)
return null;
this.input.changeItemId(_id, _newItemId);
// Update action
// since the action ID has to = this.id, getObjectById() won't work
var treeObj = egw_getAppObjectManager().getObjectById(this.id);
for (var i = 0; i < treeObj.children.length; i++) {
if (treeObj.children[i].id == _id) {
treeObj.children[i].id = _newItemId;
if (treeObj.children[i].iface)
treeObj.children[i].iface.id = _newItemId;
break;
}
}
if (typeof _label != 'undefined')
this.setLabel(_newItemId, _label);
}
/**
* deleteItem, deletes an item by id
* @param _id ID of the node
* @param _selectParent select the parent node true/false
* @return void
*/
deleteItem(_id, _selectParent) {
if (this.input == null)
return null;
this.input.deleteItem(_id, _selectParent);
// Update action
// since the action ID has to = this.id, getObjectById() won't work
let treeObj = egw_getAppObjectManager().getObjectById(this.id);
for (let i = 0; i < treeObj.children.length; i++) {
if (treeObj.children[i].id == _id) {
treeObj.children.splice(i, 1);
}
}
}
/**
* Updates a leaf of the tree by requesting new information from the server using the
* autoloading attribute.
*
* @param {string} _id ID of the node
* @param {Object} [data] If provided, the item is refreshed directly with
* the provided data instead of asking the server
* @return void
*/
refreshItem(_id, data) {
if (this.input == null)
return null;
this.input.deleteChildItems(_id);
this.input.setDataMode('JSON');
/* Can't use this, it doesn't allow a callback
this.input.refreshItem(_id);
*/
let self = this;
if (typeof data != 'undefined' && data != null) {
this.input.loadJSONObject(data, function () { self._dhtmlxtree_json_callback(data, _id); });
}
else {
this.input.loadJSON(this.egw().link(this.autoloading_url, { id: _id }), function (dxmlObject) { self._dhtmlxtree_json_callback(JSON.parse(dxmlObject.xmlDoc.responseText), _id); });
}
}
/**
* focus the item, and scrolls it into view
*
* @param _id ID of the node
* @return void
*/
focusItem(_id) {
if (this.input == null)
return null;
this.input.focusItem(_id);
}
/**
* hasChildren
*
* @param _id ID of the node
* @return the number of childelements
*/
hasChildren(_id) {
if (this.input == null)
return null;
return this.input.hasChildren(_id);
}
/**
* Callback for after using dhtmlxtree's AJAX loading
* The tree has visually already been updated at this point, we just need
* to update the internal data.
*
* @param {object} new_data Fresh data for the tree
* @param {string} update_option_id optional If provided, only update that node (and children) with the
* provided data instead of the whole thing. Allows for partial updates.
* @return void
*/
_dhtmlxtree_json_callback(new_data, update_option_id) {
// not sure if it makes sense to try update_option_id, so far I only seen it to be -1
let parent_id = typeof update_option_id != 'undefined' && update_option_id != -1 ? update_option_id : new_data.id;
// find root of loaded data to merge it there
let option = this._find_in_item(parent_id, this.options.select_options);
// if we found it, merge it
if (option) {
jQuery.extend(option, new_data || {});
}
else // else store it in root
{
this.options.select_options = new_data;
}
// Update actions by just re-setting them
this.set_actions(this.options.actions || {});
}
/**
* Recursive search item object for given id
*
* @param {string} _id
* @param {object} _item
* @returns
*/
_find_in_item(_id, _item) {
if (_item && _item.id == _id) {
return _item;
}
if (_item && typeof _item.item != 'undefined') {
for (let i = 0; i < _item.item.length; ++i) {
let found = this._find_in_item(_id, _item.item[i]);
if (found)
return found;
}
}
return null;
}
/**
* Get node data by id
*
* @param {string} _id id of node
* @return {object} object with attributes id, im0-2, text, tooltip, ... as set via select_options or autoload url
*/
getNode(_id) {
return this._find_in_item(_id, this.options.select_options);
}
/**
* Sets label of an item by id
*
* @param _id ID of the node
* @param _label label to set
* @param _tooltip new tooltip, default is previous set tooltip
* @return void
*/
setLabel(_id, _label, _tooltip) {
if (this.input == null)
return null;
let tooltip = _tooltip || (this.getNode(_id) && this.getNode(_id).tooltip ?
this.getNode(_id).tooltip : "");
this.input.setItemText(_id, this._htmlencode(_label), tooltip);
}
/**
* Sets a style for an item by id
*
* @param {string} _id ID of node
* @param {string} _style style to set
* @return void
*/
setStyle(_id, _style) {
if (this.input == null)
return null;
this.input.setItemStyle(_id, _style);
}
/**
* getLabel, gets the Label of of an item by id
* @param _id ID of the node
* @return _label
*/
getLabel(_id) {
if (this.input == null)
return null;
return this.input.getItemText(_id);
}
/**
* getSelectedNode, retrieves the full node of the selected Item
* @return string or null
*/
getSelectedNode() {
if (this.input == null)
return null;
// no support for multiple selections
// as there is no get Method to return the full selected node, we use this
return this.options.multiple ? null : this.input._selected[0];
}
/**
* getTreeNodeOpenItems
*
* @param {string} _nodeID the nodeID where to start from (initial node)
* @param {string} mode the mode to run in: "forced" fakes the initial node openState to be open
* @return {object} structured array of node ids: array(message-ids)
*/
getTreeNodeOpenItems(_nodeID, mode) {
if (this.input == null)
return null;
let z = this.input.getSubItems(_nodeID).split(this.input.dlmtr);
let oS;
let PoS;
let rv;
let returnValue = [_nodeID];
let modetorun = "none";
if (mode) {
modetorun = mode;
}
PoS = this.input.getOpenState(_nodeID);
if (modetorun == "forced")
PoS = 1;
if (PoS == 1) {
for (let i = 0; i < z.length; i++) {
oS = this.input.getOpenState(z[i]);
//alert(z[i]+' OpenState:'+oS);
if (oS == -1) {
returnValue.push(z[i]);
}
if (oS == 0) {
returnValue.push(z[i]);
}
if (oS == 1) {
//alert("got here")
rv = this.getTreeNodeOpenItems(z[i]);
//returnValue.concat(rv); // not working as expected; the following does
for (let j = 0; j < rv.length; j++) {
returnValue.push(rv[j]);
}
}
}
}
//alert(returnValue.join('#,#'));
return returnValue;
}
/**
* Fetch user-data stored in specified node under given name
*
* User-data need to be stored in json as follows:
*
* {"id": "node-id", "im0": ..., "userdata": [{"name": "user-name", "content": "user-value"},...]}
*
* In above example getUserData("node-id", "user-name") will return "user-value"
*
* @param _nodeId
* @param _name
* @returns
*/
getUserData(_nodeId, _name) {
if (this.input == null)
return null;
return this.input.getUserData(_nodeId, _name);
}
/**
* Stores / updates user-data in specified node and name
*
* @param _nodeId
* @param _name
* @param _value
* @returns
*/
setUserData(_nodeId, _name, _value) {
if (this.input == null)
return null;
return this.input.setUserData(_nodeId, _name, _value);
}
/**
* Query nodes open state and optinal change it
*
* @param _id node-id
* @param _open specify to change true: open, false: close, everything else toggle
* @returns true if open, false if closed
*/
openItem(_id, _open) {
if (this.input == null)
return null;
let is_open = this.input.getOpenState(_id) == 1;
if (typeof _open != 'undefined' && is_open !== _open) {
if (is_open) {
this.input.closeItem(_id);
}
else {
this.input.openItem(_id);
}
}
return is_open;
}
/**
* reSelectItem, reselects an item by id
* @param _id ID of the node
*/
reSelectItem(_id) {
if (this.input == null)
return null;
this.input.selectItem(_id, false, false);
}
/**
* Set images for a specific node or all new nodes (default)
*
* If images contain an extension eg. "leaf" they are asumed to be in image path (/phpgwapi/templates/default/images/dhtmlxtree/).
* Otherwise they get searched via egw.image() in current app, phpgwapi or can be specified as "app/image".
*
* @param {string} _leaf leaf image, default "leaf"
* @param {string} _closed closed folder image, default "folderClosed"
* @param {string} _open opened folder image, default "folderOpen"
* @param {string} _id if not given, standard images for new nodes are set
*/
setImages(_leaf, _closed, _open, _id) {
let images = [_leaf || 'dhtmlxtree/leaf', _closed || 'dhtmlxtree/folderClosed', _open || 'dhtmlxtree/folderOpen'];
let image_extensions = /\.(gif|png|jpe?g|svg)/i;
for (let i = 0; i < 3; ++i) {
let image = images[i];
if (!image.match(image_extensions)) {
images[i] = this._rel_url(this.egw().image(image) || image);
}
}
if (typeof _id == 'undefined') {
this.input.setStdImages.apply(this.input, images);
}
else {
images.unshift(_id);
this.input.setItemImage2.apply(this.input, images);
}
}
/**
* Set state of node incl. it's children
*
* @param {string} _id id of node
* @param {boolean|string} _state or "toggle" to toggle state
*/
setSubChecked(_id, _state) {
if (_state === "toggle")
_state = !this.input.isItemChecked(_id);
this.input.setSubChecked(_id, _state);
}
/**
* Get URL relative to image_path option
*
* Both URL start with EGroupware webserverUrl and image_path gets allways appended to images by tree.
*
* @param {string} _url
* @return {string} relativ url
*/
_rel_url(_url) {
let path_parts = this.options.image_path.split(this.egw().webserverUrl);
path_parts = path_parts[1].split('/');
let url_parts = _url.split(this.egw().webserverUrl);
url_parts = url_parts[1].split('/');
for (let i = 0; i < path_parts.length; ++i) {
if (path_parts[i] != url_parts[i]) {
while (++i < path_parts.length)
url_parts.unshift('..');
break;
}
url_parts.shift();
}
return url_parts.join('/');
}
}
et2_tree._attributes = {
"multiple": {
"name": "multiple",
"type": "boolean",
"default": false,
"description": "Allow selecting multiple options"
},
"select_options": {
"type": "any",
"name": "Select options",
"default": {},
"description": "Used to set the tree options."
},
"onclick": {
"description": "JS code which gets executed when clicks on text of a node"
},
"onselect": {
"name": "onSelect",
"type": "js",
"default": et2_no_init,
"description": "Javascript executed when user selects a node"
},
"oncheck": {
"name": "onCheck",
"type": "js",
"default": et2_no_init,
"description": "Javascript executed when user checks a node"
},
// onChange event is mapped depending on multiple to onCheck or onSelect
onopenstart: {
"name": "onOpenStart",
"type": "js",
"default": et2_no_init,
"description": "Javascript function executed when user opens a node: function(_id, _widget, _hasChildren) returning true to allow opening!"
},
onopenend: {
"name": "onOpenEnd",
"type": "js",
"default": et2_no_init,
"description": "Javascript function executed when opening a node is finished: function(_id, _widget, _hasChildren)"
},
"image_path": {
"name": "Image directory",
"type": "string",
"default": egw().webserverUrl + "/api/templates/default/images/dhtmlxtree/",
"description": "Directory for tree structure images, set on server-side to 'dhtmlx' subdir of templates image-directory"
},
"value": {
"type": "any",
"default": {}
},
"actions": {
"name": "Actions array",
"type": "any",
"default": et2_no_init,
"description": "List of egw actions that can be done on the tree. This includes context menu, drag and drop. TODO: Link to action documentation"
},
"autoloading": {
"name": "Autoloading",
"type": "string",
"default": "",
"description": "JSON URL or menuaction to be called for nodes marked with child=1, but not having children, GET parameter selected contains node-id"
},
"std_images": {
"name": "Standard images",
"type": "string",
"default": "",
"description": "comma-separated names of icons for a leaf, closed and opend folder (default: leaf.png,folderClosed.png,folderOpen.png), images with extension get loaded from image_path, just 'image' or 'appname/image' are allowed too"
},
"multimarking": {
"name": "multimarking",
"type": "any",
"default": false,
"description": "Allow marking multiple nodes, default is false which means disabled multiselection, true or 'strict' activates it and 'strict' makes it strick to only same level marking"
},
highlighting: {
"name": "highlighting",
"type": "boolean",
"default": false,
"description": "Add highlighting class on hovered over item, highlighting is disabled by default"
}
};
et2_register_widget(et2_tree, ["tree", "tree-cat"]);
//# sourceMappingURL=et2_widget_tree.js.map

View File

@ -1,467 +0,0 @@
/**
* EGroupware eTemplate2 - JS URL object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Nathan Gray
* @copyright Nathan Gray 2011
*/
/*egw:uses
et2_textbox;
et2_valueWidget;
/api/js/jquery/jquery.base64.js;
*/
import { et2_register_widget } from "./et2_core_widget";
import { et2_textbox } from "./et2_widget_textbox";
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_valueWidget } from "./et2_core_valueWidget";
import { et2_no_init } from "./et2_core_common";
import { egw } from "../jsapi/egw_global";
/**
* Class which implements the "url" XET-Tag, which covers URLs, email & phone
*
* @augments et2_textbox
*/
export class et2_url extends et2_textbox {
constructor() {
super(...arguments);
this._button = null;
this.value = "";
}
/**
* @memberOf et2_url
*/
createInputWidget() {
this.input = jQuery(document.createElement("input"))
.blur(this, this.validate)
.blur(this, function (e) { e.data.set_value(e.data.getValue()); });
this._button = null;
if (this.size) {
this.set_size(this.size);
}
this.setDOMNode(this.input[0]);
}
destroy() {
if (this.input) {
this.input.unbind();
}
this._button = null;
super.destroy();
}
/**
* Override parent to update href of 'button'
*
* @param _value value to set
*/
set_value(_value) {
this.update_button(_value);
super.set_value(_value);
}
update_button(_value) {
if (this.value == _value)
return;
if (_value) {
// Create button if it doesn't exist yet
if (this._button == null) {
this._button = jQuery(document.createElement("a")).addClass("et2_url");
this.getSurroundings().insertDOMNode(this._button[0]);
this.getSurroundings().update();
}
this._button.removeClass("url phone email").removeAttr("href");
_value = this.get_link(this.getType(), _value);
switch (this.getType()) {
case "url":
// Silently use http if no protocol
this._button.attr("href", _value).attr("target", "_blank").addClass("url");
break;
case "url-phone":
case "url-fax":
if (_value) {
if (typeof _value == 'function') {
this._button.click(this, _value).addClass("phone").show();
}
else {
this._button.attr("href", _value).addClass("phone").show();
}
}
else if (_value === false) {
// Can't make a good handler, hide button
this._button.hide();
}
break;
case "url-email":
if (typeof _value == 'function') {
this._button.click(this, _value).addClass("email");
}
else {
this._button.attr("href", _value).addClass("email");
}
break;
}
}
else {
if (this._button)
this._button.hide();
if (this._button && this.getSurroundings && this.getSurroundings().removeDOMNode) {
this.getSurroundings().removeDOMNode(this._button[0]);
}
this._button = null;
}
}
get_link(type, value) {
if (!value)
return false;
// convert fax numbers to email, if configured
if (type === 'url-fax' && this.egw().config('fax_email') &&
(value = value.replace('&#9829;', '').replace('(0)', '').replace(/[^0-9+]/g, ''))) {
value = value.replace(new RegExp(this.egw().config('fax_email_regexp') || '(.*)'), this.egw().config('fax_email'));
type = 'url-email';
}
switch (type) {
case "url":
// Silently use http if no protocol
if (value.indexOf("://") == -1)
value = "http://" + value;
break;
case "url-phone":
case "url-fax":
// Clean number
value = value.replace('&#9829;', '').replace('(0)', '');
value = value.replace(/[abc]/gi, 2).replace(/[def]/gi, 3).replace(/[ghi]/gi, 4).replace(/[jkl]/gi, 5).replace(/[mno]/gi, 6);
value = value.replace(/[pqrs]/gi, 7).replace(/[tuv]/gi, 8).replace(/[wxyz]/gi, 9);
// remove everything but numbers and plus, as telephon software might not like it
value = value.replace(/[^0-9+]/g, '');
// movile Webkit (iPhone, Android) have precedence over server configuration!
if (navigator.userAgent.indexOf('AppleWebKit') !== -1 &&
(navigator.userAgent.indexOf("iPhone") !== -1 || navigator.userAgent.indexOf("Android") !== -1) &&
value.indexOf("tel:") == -1) {
value = "tel:" + value;
}
else if (this.egw().config("call_link")) {
var link = this.egw().config("call_link")
// tel: links use no URL encoding according to rfc3966 section-5.1.4
.replace("%1", this.egw().config("call_link").substr(0, 4) == 'tel:' ?
value : encodeURIComponent(value))
.replace("%u", this.egw().user('account_lid'))
.replace("%t", this.egw().user('account_phone'));
var popup = this.egw().config("call_popup");
value = function (ev) {
if (popup && popup !== '_self' || !link.match(/^https?:/)) // execute non-http(s) links eg. tel: like before
{
egw.open_link(link, '_phonecall', popup);
}
else {
// No popup, use AJAX. We don't care about the response.
(egw.window ? egw.window.jQuery : jQuery).ajax({
url: link,
async: true,
dataType: 'json',
type: "GET"
});
ev.preventDefault();
return false;
}
};
}
else {
// Can't make a good handler
return false;
}
break;
case "url-email":
if (value.indexOf("mailto:") == -1) {
value = "mailto:" + value;
}
if ((this.egw().user('apps').mail || this.egw().user('apps').felamimail) &&
this.egw().preference('force_mailto', 'addressbook') != '1') {
return function () { egw.open_link(value); };
}
break;
}
return value;
}
validate(e) {
e.data.hideMessage();
if (e.data._super) {
e.data._super.apply(this, arguments);
}
// Check value, and correct if possible
var value = jQuery.trim(e.data.getValue());
if (value == "")
return;
switch (e.data._type) {
case "url":
if (value.indexOf("://") == -1 && !e.data.options.allow_path) {
e.data.set_value("http://" + value);
e.data.showMessage(e.data.egw().lang("Protocol is required"), "hint", true);
}
else if (e.data.options.allow_path && value[0] !== '/') {
e.data.showMessage(e.data.egw().lang("Path must start with '/'"), "hint", true);
}
// Adjust trailing slash - required or forbidden
if (e.data.options.trailing_slash === true && value[value.length - 1] !== '/') {
e.data.set_value(value + '/');
}
else if (e.data.options.trailing_slash === false && value[value.length - 1] === '/') {
e.data.set_value(value.substr(0, value.length - 1));
}
break;
case "url-email":
if (!et2_url.EMAIL_PREG.test(value) ||
// If they use Text <email>, make sure the <> match
(value.indexOf("<") > 0 && value.indexOf(">") != value.length - 1) ||
(value.indexOf(">") > 0 && value.indexOf("<") < 0)) {
e.data.showMessage("Invalid email", "validation_error", true);
}
}
}
attachToDOM() {
let res = super.attachToDOM();
if (this.input[0].parentNode)
jQuery(this.input[0].parentNode).addClass('et2_url_span');
return res;
}
}
et2_url._attributes = {
"multiline": {
"ignore": true
},
"allow_path": {
type: "boolean",
name: "Allow path",
description: "Allow a path instead of a URL, path must start with /",
default: false
},
"trailing_slash": {
type: "boolean",
name: "Trailing slash",
description: "Require (or forbid) that the path ends with a /",
default: et2_no_init
}
};
/**
* Regexes for validating email addresses incl. email in angle-brackets eg.
* + "Ralf Becker <rb@stylite.de>"
* + "Ralf Becker (Stylite AG) <rb@stylite.de>"
* + "<rb@stylite.de>" or "rb@stylite.de"
* + '"Becker, Ralf" <rb@stylite.de>'
* + "'Becker, Ralf' <rb@stylite.de>"
* but NOT:
* - "Becker, Ralf <rb@stylite.de>" (contains comma outside " or ' enclosed block)
* - "Becker < Ralf <rb@stylite.de>" (contains < ----------- " ---------------)
*
* About umlaut or IDN domains: we currently only allow German umlauts in domain part!
* We forbid all non-ascii chars in local part, as Horde does not yet support SMTPUTF8 extension (rfc6531)
* and we get a "SMTP server does not support internationalized header data" error otherwise.
*
* Using \042 instead of " to NOT stall minifyer!
*
* Similar, but not identical, preg is in Etemplate\Widget\Url PHP class!
* We can not use "(?<![.\s])", used to check that name-part does not end in
* a dot or white-space. The expression is valid in recent Chrome, but fails
* eg. in Safari 11.0 or node.js 4.8.3 and therefore grunt uglify!
* Server-side will fail in that case because it uses the full regexp.
*/
et2_url.EMAIL_PREG = new RegExp(/^(([^\042',<][^,<]+|\042[^\042]+\042|\'[^\']+\'|"(?:[^"\\]|\\.)*")\s?<)?[^\x00-\x20()\xe2\x80\x8b<>@,;:\042\[\]\x80-\xff]+@([a-z0-9ÄÖÜäöüß](|[a-z0-9ÄÖÜäöüß_-]*[a-z0-9ÄÖÜäöüß])\.)+[a-z]{2,}>?$/i);
et2_register_widget(et2_url, ["url", "url-email", "url-phone", "url-fax"]);
/**
* et2_url_ro is the readonly implementation of the url, email & phone.
* It renders things as links, when possible
*
* @augments et2_valueWidget
*/
export class et2_url_ro extends et2_valueWidget {
/**
* Constructor
*
* @memberOf et2_url_ro
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_url_ro._attributes, _child || {}));
this.value = "";
this.span = null;
this.value = "";
this.span = jQuery(document.createElement("a"))
.addClass("et2_textbox readonly");
// Do not a tag if no call_link is set and not in mobile, empty a tag may conflict
// with some browser telephony addons (eg. telify in FF)
if (!egw.config('call_link') && this.getType() == 'url-phone' && !egwIsMobile()) {
this.span = jQuery(document.createElement("span"))
.addClass("et2_textbox readonly");
}
if (this.getType() == 'url-email') {
this.span.addClass('et2_email');
}
this.setDOMNode(this.span[0]);
}
/**
* Set the text of the link to the label
*
* @param _value
*/
set_label(_value) {
if (this.span && this.options.label !== _value) {
this.options.label = _value;
this.span.text(_value);
}
}
set_value(_value) {
this.value = _value;
let link = et2_url.prototype.get_link(this.getType(), _value);
if (!link) {
this.span.text(_value);
this.span.removeAttr("href");
return;
}
this.span.text(this.options.label || _value);
switch (this.getType()) {
case "url":
this.span.attr("href", link).attr("target", "_blank");
break;
case "url-phone":
case "url-fax":
if (typeof link == 'function') {
this.span.off('click.et2_url');
this.span.on('click.et2_url', link);
this.span.attr("href", "#");
}
else if (link) {
this.span.attr("href", link);
}
break;
case "url-email":
if (typeof link == 'function') {
this.span.off('click.et2_url');
this.span.on('click.et2_url', link);
this.span.removeAttr("href");
}
else {
this.span.attr("href", link);
if (!this.span.attr("target")) {
this.span.attr("target", "_blank");
}
}
// wrap email address if there's a name
if (this.span.text() && this.span.text().split("<") && this.options.full_email) {
let val = this.span.text().split("<");
val = val[0] != "" ? val[0] : val[2];
// need to preserve the original value somehow
// as it's been used for add contact plus feature
this.span.attr('title', _value);
this.span.text(val.replace(/"/g, ''));
this.span.append("<span class='noemail'>" +
_value.replace(val, '')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
+ "</span>");
}
// Add contact_plus button
if (this.options.contact_plus) {
// If user doesn't have access to addressbook, stop
if (!egw.app('addressbook'))
return;
// Bind onmouseenter event on <a> tag in order to add contact plus
// Need to keep span & value so it works inside nextmatch
this.span.on('mouseenter', jQuery.proxy(function (event) {
event.stopImmediatePropagation();
this.widget._add_contact_tooltip.call(this, et2_url_ro.email_cache[this.value]);
if (typeof et2_url_ro.email_cache[this.value] === 'undefined') {
// Ask server if we know this email
this.widget.egw().jsonq('EGroupware\\Api\\Etemplate\\Widget\\Url::ajax_contact', this.value, this.widget._add_contact_tooltip, this);
}
}, { widget: this, span: this.span, value: this.value }));
}
break;
}
}
/**
* Add a button to add the email address as a contact
*
* @param {boolean} email_exists True, or else nothing happens
*/
_add_contact_tooltip(email_exists) {
var value = this.value || this.widget.value || null;
et2_url_ro.email_cache[value] = email_exists;
// Close all the others
jQuery('.et2_email').each(function () {
if (jQuery(this).tooltip('instance')) {
jQuery(this).tooltip('close');
}
});
if (email_exists || !this.span.is(':hover'))
return;
this.span.tooltip({
items: 'a.et2_email',
position: { my: "right top", at: "left top", collision: "flipfit" },
tooltipClass: "et2_email_popup",
content() {
// Here we could do all sorts of things
var extra = {
'presets[email]': jQuery(this).attr('title') ? jQuery(this).attr('title') : jQuery(this).text()
};
return jQuery('<a href="#" class= "et2_url_email_contactPlus" title="' + egw.lang('Add a new contact') + '"><img src="'
+ (typeof email_exists === 'undefined' ? egw.image("loading") : egw.image("new")) + '"/></a>')
.on('click', function () {
egw.open('', 'addressbook', 'add', extra);
});
},
close(event, ui) {
ui.tooltip.hover(function () {
jQuery(this).stop(true).fadeTo(400, 1);
//.fadeIn("slow"); // doesn't work because of stop()
}, function () {
jQuery(this).fadeOut("400", function () { jQuery(this).remove(); });
});
}
})
.tooltip("open");
jQuery('.egwGridView_scrollarea').one('scroll', jQuery.proxy(function () {
if (this.tooltip("instance")) {
this.tooltip('destroy');
}
}, this.span));
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
_attrs.push("value", "class", "statustext");
}
getDetachedNodes() {
return [this.span[0]];
}
setDetachedAttributes(_nodes, _values) {
// Update the properties
this.span = jQuery(_nodes[0]);
if (typeof _values["value"] != "undefined") {
this.set_value(_values["value"]);
}
if (typeof _values["class"] != "undefined") {
_nodes[0].setAttribute("class", _values["class"]);
}
// Set to original status text if not set for this row
this.span.attr('title', _values.statustext ? _values.statustext : this.options.statustext);
}
}
et2_url_ro._attributes = {
"contact_plus": {
"name": "Add contact button",
"type": "boolean",
"default": false,
"description": "Allow to add email as contact to addressbook"
},
"full_email": {
"name": "Show full email address",
"type": "boolean",
"default": true,
"description": "Allow to show full email address if ture otherwise show only name"
}
};
et2_url_ro.email_cache = [];
et2_register_widget(et2_url_ro, ["url_ro", "url-email_ro", "url-phone_ro", "url-fax_ro"]);
//# sourceMappingURL=et2_widget_url.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,456 +0,0 @@
/**
* EGroupware eTemplate2 - JS Description object
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh <hn[at]stylite.de>
* @copyright EGroupware GmbH 2020-2021
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
et2_core_interfaces;
et2_core_baseWidget;
*/
import { et2_baseWidget } from './et2_core_baseWidget';
import { ClassWithAttributes } from "./et2_core_inheritance";
import { et2_register_widget } from "./et2_core_widget";
/**
* This widget represents the HTML5 video tag with all its optional attributes
*
* The widget can be created in the following ways:
* <code>
* var videoTag = et2_createWidget("video", {
* video_src: "../../test.mp4",
* src_type: "video/mp4",
* muted: true,
* autoplay: true,
* controls: true,
* poster: "../../poster.jpg",
* loop: true,
* height: 100,
* width: 200,
* });
* </code>
* Or by adding XET-tag in your template (.xet) file:
* <code>
* <video [attributes...]/>
* </code>
*/
/**
* Class which implements the "video" XET-Tag
*
* @augments et2_baseWidget
*/
export class et2_video extends et2_baseWidget {
constructor(_parent, _attrs, _child) {
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_video._attributes, _child || {}));
this.video = null;
/**
* keeps internal state of previousTime video played
* @private
*/
this._previousTime = 0;
this.set_src_type(this.options.src_type);
}
set_src_type(_type) {
this.options.src_type = _type;
if (this.video && this._isYoutube() === (this.video[0].tagName === 'DIV')) {
return;
}
//Create Video tag
this.video = jQuery(document.createElement(this._isYoutube() ? "div" : "video"))
.addClass('et2_video')
.attr('id', this.dom_id);
if (this._isYoutube()) {
// this div will be replaced by youtube iframe api when youtube gets ready
this.youtubeFrame = jQuery(document.createElement('div'))
.appendTo(this.video)
.attr('id', et2_video.youtubePrefixId + this.id);
if (!document.getElementById('youtube-api-script')) {
//Load youtube iframe api
let tag = document.createElement('script');
tag.id = 'youtube-api-script';
tag.src = "https://www.youtube.com/iframe_api";
let firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
}
}
if (!this._isYoutube() && this.options.controls) {
this.video.attr("controls", 1);
}
if (!this._isYoutube() && this.options.autoplay) {
this.video.attr("autoplay", 1);
}
if (this.options.muted) {
this.video.attr("muted", 1);
}
if (this.options.video_src) {
this.set_src(this.options.video_src);
}
if (this.options.loop) {
this.video.attr("loop", 1);
}
this.setDOMNode(this.video[0]);
this.set_width(this.options.width || 'auto');
this.set_height(this.options.height || 'auto');
}
/**
* Set video src
*
* @param {string} _value url
*/
set_src(_value) {
let self = this;
this.options.video_src = _value;
if (_value && !this._isYoutube()) {
this.video.attr('src', _value);
if (this.options.src_type) {
this.video.attr('type', this.options.src_type);
}
}
else if (_value) {
if (typeof YT == 'undefined') {
//initiate youtube Api object, it gets called automatically by iframe_api script from the api
window.onYouTubeIframeAPIReady = this._onYoutubeIframeAPIReady;
window.addEventListener('et2_video.onYoutubeIframeAPIReady', function () {
self._createYoutubePlayer(self.options.video_src);
});
}
else {
self._createYoutubePlayer(self.options.video_src);
}
}
}
/**
* Set autoplay option for video
* -If autoplay is set, video would be played automatically after the page is loaded
*
* @param {string} _value true set the autoplay and false not to set
*/
set_autoplay(_value) {
if (_value && !this._isYoutube()) {
this.video.attr("autoplay", _value);
}
}
/**
* Set controls option for video
*
* @param {string} _value true set the autoplay and false not to set
*/
set_controls(_value) {
if (_value && !this._isYoutube()) {
this.video.attr("controls", _value);
}
}
/**
* Set poster attribute in order to specify
* an image to be shown while video is loading or before user play it
*
* @param {string} _url url or image spec like "api/mime128_video"
*/
set_poster(_url) {
if (_url) {
if (_url[0] !== '/' && !_url.match(/^https?:\/\//)) {
_url = this.egw().image(_url);
}
this.video.attr("poster", _url);
}
}
/**
* Seek to a time / position
*
* @param _vtime in seconds
*/
seek_video(_vtime) {
if (this._isYoutube()) {
if (this.youtube.seekTo) {
this.youtube.seekTo(_vtime, true);
this._currentTime = _vtime;
}
}
else {
this.video[0].currentTime = _vtime;
}
}
/**
* Play video
*/
play_video() {
if (this._isYoutube()) {
let self = this;
return new Promise(function (resolve) {
if (self.youtube.playVideo) {
self.youtube.playVideo();
resolve();
}
});
}
return this.video[0].play();
}
/**
* Pause video
*/
pause_video() {
if (this._isYoutube()) {
if (this.youtube.pauseVideo) {
this.youtube.pauseVideo();
this.currentTime(this.youtube.getCurrentTime());
}
}
else {
this.video[0].pause();
}
}
/**
* play video
* ***Internal use and should not be overriden in its extended class***
*/
play() {
var _a;
return this._isYoutube() && ((_a = this.youtube) === null || _a === void 0 ? void 0 : _a.playVideo) ? this.youtube.playVideo() : this.video[0].play();
}
/**
* Get/set current video time / position in seconds
* @return returns currentTime
*/
currentTime(_time) {
var _a;
if (_time) {
if (this._isYoutube()) {
this.youtube.seekTo(_time);
}
else {
this.video[0].currentTime = _time;
}
return this._currentTime = _time;
}
if (this._isYoutube()) {
if (typeof this._currentTime != 'undefined') {
return this._currentTime;
}
return ((_a = this.youtube) === null || _a === void 0 ? void 0 : _a.getCurrentTime) ? this.youtube.getCurrentTime() : 0;
}
else {
return this.video[0].currentTime;
}
}
/**
* get duration time
* @return returns duration time
*/
duration() {
var _a;
if (this._isYoutube()) {
return ((_a = this.youtube) === null || _a === void 0 ? void 0 : _a.getDuration) ? this.youtube.getDuration() : 0;
}
else {
return this.video[0].duration;
}
}
/**
* get pasued
* @return returns paused flag
*/
paused() {
if (this._isYoutube()) {
return this.youtube.getPlayerState() == et2_video.youtube_player_states.paused;
}
return this.video[0].paused;
}
/**
* get ended
* @return returns ended flag
*/
ended() {
if (this._isYoutube()) {
return this.youtube.getPlayerState() == et2_video.youtube_player_states.ended;
}
return this.video[0].ended;
}
/**
* get/set priviousTime
* @param _time
* @return returns time
*/
previousTime(_time) {
if (_time)
this._previousTime = _time;
return this._previousTime;
}
doLoadingFinished() {
super.doLoadingFinished();
let self = this;
if (!this._isYoutube()) {
this.video[0].addEventListener("loadedmetadata", function () {
self._onReady();
});
this.video[0].addEventListener("timeupdate", function () {
self._onTimeUpdate();
});
}
else {
// need to create the player after the DOM is ready otherwise player won't show up
if (window.YT)
this._createYoutubePlayer(this.options.video_src);
}
return false;
}
videoLoadnigIsFinished() {
if (this.options.starttime) {
this.seek_video(this.options.starttime);
// unfortunately, youtube api autoplays the video after seekTo on initiation
// and there's no way to stop that therefore we need to trick it by manually
// pausing the video (this would bring up the spinner with the black screen,
// in order to avoid that we let the video plays for a second then we pause).
// since the youtube timeline is one second advanced we need to seek back to
// the original stattime although this time because it was manually paused
// we won't have the spinner and black screen instead we get the preview.
if (this._isYoutube())
window.setTimeout(function () {
this.youtube.pauseVideo();
this.youtube.seekTo(this.options.starttime);
;
}.bind(this), 1000);
}
}
_onReady() {
// need to set the video dom to transformed iframe
if (this._isYoutube() && this.youtube.getIframe)
this.youtubeFrame = jQuery(this.youtube.getIframe());
let event = document.createEvent("Event");
event.initEvent('et2_video.onReady.' + this.id, true, true);
this.video[0].dispatchEvent(event);
}
_onTimeUpdate() {
// update currentTime manually since youtube currentTime might be updated due to the loading
if (this._isYoutube() && this.youtube.getCurrentTime)
this._currentTime = this.youtube.getCurrentTime();
let event = document.createEvent("Event");
event.initEvent('et2_video.onTimeUpdate.' + this.id, true, true);
this.video[0].dispatchEvent(event);
}
/**
* check if the video is a youtube type
* @return return true if it's a youtube type video
* @private
*/
_isYoutube() {
return !!this.options.src_type.match('youtube');
}
_onStateChangeYoutube(_data) {
switch (_data.data) {
case et2_video.youtube_player_states.unstarted:
// do nothing
break;
case et2_video.youtube_player_states.playing:
this._youtubeOntimeUpdateIntrv = window.setInterval(jQuery.proxy(this._onTimeUpdate, this), 100);
break;
default:
window.clearInterval(this._youtubeOntimeUpdateIntrv);
}
console.log(_data);
}
/**
* youtube on IframeAPI ready event
*/
_onYoutubeIframeAPIReady() {
let event = document.createEvent("Event");
event.initEvent('et2_video.onYoutubeIframeAPIReady', true, true);
window.dispatchEvent(event);
}
/**
* create youtube player
*
* @param _value
*/
_createYoutubePlayer(_value) {
const matches = _value === null || _value === void 0 ? void 0 : _value.match(et2_video.youtubeRegexp);
if (matches && typeof YT != 'undefined') {
this.youtube = new YT.Player(et2_video.youtubePrefixId + this.id, {
height: this.options.height || '400',
width: '100%',
playerVars: {
'autoplay': 0,
'controls': 0,
'modestbranding': 1,
'fs': 0,
'disablekb': 1,
'rel': 0,
'iv_load_policy': 0,
'cc_load_policy': 0
},
videoId: matches[4],
events: {
'onReady': jQuery.proxy(this._onReady, this),
'onStateChange': jQuery.proxy(this._onStateChangeYoutube, this)
}
});
}
}
}
et2_video._attributes = {
"video_src": {
"name": "Video",
"type": "string",
"description": "Source of video to display"
},
"src_type": {
"name": "Source type",
"type": "string",
"description": "Defines the type the stream source provided"
},
"muted": {
"name": "Audio control",
"type": "boolean",
"default": false,
"description": "Defines that the audio output of the video should be muted"
},
"autoplay": {
"name": "Autoplay",
"type": "boolean",
"default": false,
"description": "Defines if Video will start playing as soon as it is ready"
},
starttime: {
"name": "Inital position of video",
"type": "float",
"default": 0,
"description": "Set initial position of video"
},
"controls": {
"name": "Control buttons",
"type": "boolean",
"default": false,
"description": "Defines if Video controls, play/pause buttons should be displayed"
},
"poster": {
"name": "Video Poster",
"type": "string",
"default": "",
"description": "Specifies an image to be shown while video is loading or before user play it"
},
"loop": {
"name": "Video loop",
"type": "boolean",
"default": false,
"description": "Defines if the video should be played repeatedly"
}
};
et2_video.youtube_player_states = {
unstarted: -1,
ended: 0,
playing: 1,
paused: 2,
buffering: 3,
video_cued: 5
};
/**
* prefix id used for addressing youtube player dom
* @private
*/
et2_video.youtubePrefixId = "frame-";
et2_video.youtubeRegexp = new RegExp(/^https:\/\/((www\.|m\.)?youtube(-nocookie)?\.com|youtu\.be)\/.*(?:\/|%3D|v=|vi=)([0-9A-z-_]{11})(?:[%#?&]|$)/m);
et2_register_widget(et2_video, ["video"]);
//# sourceMappingURL=et2_widget_video.js.map

View File

@ -1,587 +0,0 @@
/**
* EGroupware eTemplate2 - JS object implementing expose view of media and a gallery view
*
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @package etemplate
* @subpackage api
* @link https://www.egroupware.org
* @author Hadi Nategh <hn[at]stylite.de>
*/
/*egw:uses
/vendor/bower-asset/jquery/dist/jquery.js;
/api/js/jquery/blueimp/js/blueimp-gallery.min.js;
*/
"use strict";
import { et2_createWidget } from "./et2_core_widget";
import { et2_dialog } from "./et2_widget_dialog";
import { egw, egw_get_file_editor_prefered_mimes } from "../jsapi/egw_global";
import { et2_nextmatch } from "./et2_extension_nextmatch";
import { ET2_DATAVIEW_STEPSIZE } from "./et2_dataview_controller";
/**
* This function extends the given widget with blueimp gallery plugin
*
* @param {type} widget
* @returns {widget}
*/
export function expose(Base) {
"use strict";
// Minimum data to qualify as an image and not cause errors
const IMAGE_DEFAULT = {
title: egw.lang('loading'),
href: '',
type: 'image/png',
thumbnail: '',
loading: true
};
// For filtering to only show things we can handle
const MIME_REGEX = (navigator.userAgent.match(/(MSIE|Trident)/)) ?
// IE only supports video/mp4 mime type
new RegExp(/(video\/mp4)|(image\/:*(?!tif|x-xcf|pdf))|(audio\/:*)/) :
new RegExp(/(video\/(mp4|ogg|webm))|(image\/:*(?!tif|x-xcf|pdf))|(audio\/:*)/);
const MIME_AUDIO_REGEX = new RegExp(/(audio\/:*)/);
// open office document mime type currently supported by webodf editor
const MIME_ODF_REGEX = new RegExp(/application\/vnd\.oasis\.opendocument\.text/);
// Only one gallery
var gallery = null;
/**
* See if the current widget is in a nextmatch, as this allows us to display
* thumbnails underneath
*
* @param {et2_IExposable} widget
* @returns {et2_nextmatch | null}
*/
var find_nextmatch = function (widget) {
let current = widget;
let nextmatch = null;
while (nextmatch == null && current) {
current = current.getParent();
if (typeof current != 'undefined' && current.instanceOf(et2_nextmatch)) {
nextmatch = current;
}
}
// No nextmatch, or nextmatch not quite ready
// At the moment only filemanger nm would work
// as gallery, thus we disable other nestmatches
// to build up gallery but filemanager
if (nextmatch == null || nextmatch.controller == null || !nextmatch.dom_id.match(/filemanager/, 'ig'))
return null;
return nextmatch;
};
/**
* Read images out of the data for the nextmatch
*
* @param {et2_nextmatch} nm
* @param {Object[]} images
* @param {number} start_at
* @returns {undefined}
*/
var read_from_nextmatch = function (nm, images, start_at) {
if (!start_at)
start_at = 0;
let image_index = start_at;
let stop = Math.max.apply(null, Object.keys(nm.controller._indexMap));
for (let i = start_at; i <= stop; i++) {
if (!nm.controller._indexMap[i] || !nm.controller._indexMap[i].uid) {
// Returning instead of using IMAGE_DEFAULT means we stop as
// soon as a hole is found, instead of getting everything that is
// available. The gallery can't fill in the holes.
images[image_index++] = IMAGE_DEFAULT;
continue;
}
let uid = nm.controller._indexMap[i].uid;
if (!uid)
continue;
let data = egw.dataGetUIDdata(uid);
if (data && data.data && data.data.mime && MIME_REGEX.test(data.data.mime) && !MIME_AUDIO_REGEX.test(data.data.mime)) {
let media = this.getMedia(data.data);
images[image_index++] = jQuery.extend({}, data.data, media[0]);
}
}
};
/**
* Set a particular index/image in the gallery instead of just appending
* it to the end
*
* @param {integer} index
* @param {Object} image
* @returns {undefined}
*/
var set_slide = function (index, image) {
let active = (index == gallery.index);
// Pad with blanks until length is right
while (index > gallery.getNumber()) {
gallery.add([jQuery.extend({}, IMAGE_DEFAULT)]);
}
// Don't bother with adding a default, we just did that
if (image.loading) {
//Add load class if it's really a slide with error
if (gallery.slidesContainer.find('[data-index="' + index + '"]').hasClass(gallery.options.slideErrorClass))
jQuery(gallery.slides[index])
.addClass(gallery.options.slideLoadingClass)
.removeClass(gallery.options.slideErrorClass);
return;
}
// Remove the loading class if the slide is loaded
else {
jQuery(gallery.slides[index]).removeClass(gallery.options.slideLoadingClass);
}
// Just use add to let gallery create everything it needs
let new_index = gallery.num;
gallery.add([image]);
// Move it to where we want it.
// Gallery uses arrays and indexes and has several internal variables
// that need to be updated.
//
// list
gallery.list[index] = gallery.list[new_index];
gallery.list.splice(new_index, 1);
// indicators & slides
let dom_nodes = ['indicators', 'slides'];
for (let i in dom_nodes) {
let var_name = dom_nodes[i];
// Remove old one from DOM
jQuery(gallery[var_name][index]).remove();
// Move new one into it's place in gallery
gallery[var_name][index] = gallery[var_name][new_index];
// Move into place in DOM
let node = jQuery(gallery[var_name][index]);
node.attr('data-index', index)
.insertAfter(jQuery("[data-index='" + (index - 1) + "']", node.parent()));
if (active)
node.addClass(gallery.options.activeIndicatorClass);
gallery[var_name].splice(new_index, 1);
}
if (active) {
gallery.activeIndicator = jQuery(gallery.indicators[index]);
}
// positions
gallery.positions[index] = active ? 0 : (index > gallery.index ? gallery.slideWidth : -gallery.slideWidth);
gallery.positions.splice(new_index, 1);
// elements - removing will allow to re-do the slide
if (gallery.elements[index]) {
delete gallery.elements[index];
gallery.loadElement(index);
}
// Remove the one we just added
gallery.num -= 1;
};
return class exposable extends Base {
constructor(...args) {
// Call the inherited constructor
super(...args);
this.mime_regexp = MIME_REGEX;
this.mime_audio_regexp = MIME_AUDIO_REGEX;
this.mime_odf_regex = MIME_ODF_REGEX;
let self = this;
this.expose_options = {
// The Id, element or querySelector of the gallery widget:
container: '#blueimp-gallery',
// The tag name, Id, element or querySelector of the slides container:
slidesContainer: 'div',
// The tag name, Id, element or querySelector of the title element:
titleElement: 'h3',
// The class to add when the gallery is visible:
displayClass: 'blueimp-gallery-display',
// The class to add when the gallery controls are visible:
controlsClass: 'blueimp-gallery-controls',
// The class to add when the gallery only displays one element:
singleClass: 'blueimp-gallery-single',
// The class to add when the left edge has been reached:
leftEdgeClass: 'blueimp-gallery-left',
// The class to add when the right edge has been reached:
rightEdgeClass: 'blueimp-gallery-right',
// The class to add when the automatic slideshow is active:
playingClass: 'blueimp-gallery-playing',
// The class for all slides:
slideClass: 'slide',
// The slide class for loading elements:
slideLoadingClass: '',
// The slide class for elements that failed to load:
slideErrorClass: 'slide-error',
// The class for the content element loaded into each slide:
slideContentClass: 'slide-content',
// The class for the "toggle" control:
toggleClass: 'toggle',
// The class for the "prev" control:
prevClass: 'prev',
// The class for the "next" control:
nextClass: 'next',
// The class for the "close" control:
closeClass: 'close',
// The class for the "play-pause" toggle control:
playPauseClass: 'play-pause',
// The class to add for fullscreen button option
fullscreenClass: 'fullscreen',
// The list object property (or data attribute) with the object type:
typeProperty: 'type',
// The list object property (or data attribute) with the object title:
titleProperty: 'title',
// The list object property (or data attribute) with the object URL:
urlProperty: 'href',
// The gallery listens for transitionend events before triggering the
// opened and closed events, unless the following option is set to false:
displayTransition: true,
// Defines if the gallery slides are cleared from the gallery modal,
// or reused for the next gallery initialization:
clearSlides: true,
// Defines if images should be stretched to fill the available space,
// while maintaining their aspect ratio (will only be enabled for browsers
// supporting background-size="contain", which excludes IE < 9).
// Set to "cover", to make images cover all available space (requires
// support for background-size="cover", which excludes IE < 9):
stretchImages: true,
// Toggle the controls on pressing the Return key:
toggleControlsOnReturn: true,
// Toggle the automatic slideshow interval on pressing the Space key:
toggleSlideshowOnSpace: true,
// Navigate the gallery by pressing left and right on the keyboard:
enableKeyboardNavigation: true,
// Close the gallery on pressing the ESC key:
closeOnEscape: true,
// Close the gallery when clicking on an empty slide area:
closeOnSlideClick: false,
// Close the gallery by swiping up or down:
closeOnSwipeUpOrDown: true,
// Emulate touch events on mouse-pointer devices such as desktop browsers:
emulateTouchEvents: true,
// Stop touch events from bubbling up to ancestor elements of the Gallery:
stopTouchEventsPropagation: false,
// Hide the page scrollbars:
hidePageScrollbars: true,
// Stops any touches on the container from scrolling the page:
disableScroll: true,
// Carousel mode (shortcut for carousel specific options):
carousel: true,
// Allow continuous navigation, moving from last to first
// and from first to last slide:
continuous: false,
// Remove elements outside of the preload range from the DOM:
unloadElements: true,
// Start with the automatic slideshow:
startSlideshow: false,
// Delay in milliseconds between slides for the automatic slideshow:
slideshowInterval: 3000,
// The starting index as integer.
// Can also be an object of the given list,
// or an equal object with the same url property:
index: 0,
// The number of elements to load around the current index:
preloadRange: 2,
// The transition speed between slide changes in milliseconds:
transitionSpeed: 400,
//Hide controls when the slideshow is playing
hideControlsOnSlideshow: true,
//Request fullscreen on slide show
toggleFullscreenOnSlideShow: true,
// The transition speed for automatic slide changes, set to an integer
// greater 0 to override the default transition speed:
slideshowTransitionSpeed: undefined,
// The tag name, Id, element or querySelector of the indicator container:
indicatorContainer: 'ol',
// The class for the active indicator:
activeIndicatorClass: 'active',
// The list object property (or data attribute) with the thumbnail URL,
// used as alternative to a thumbnail child element:
thumbnailProperty: 'thumbnail',
// Defines if the gallery indicators should display a thumbnail:
thumbnailIndicators: true,
//thumbnail with image tag
thumbnailWithImgTag: true,
// Callback function executed when the Gallery is initialized.
// Is called with the gallery instance as "this" object:
onopen: jQuery.proxy(this.expose_onopen, this),
// Callback function executed when the Gallery has been initialized
// and the initialization transition has been completed.
// Is called with the gallery instance as "this" object:
onopened: jQuery.proxy(this.expose_onopened, this),
// Callback function executed on slide change.
// Is called with the gallery instance as "this" object and the
// current index and slide as arguments:
onslide: function (index, slide) {
// Call our onslide method, and include gallery as an attribute
self.expose_onslide.apply(self, [this, index, slide]);
},
// Callback function executed after the slide change transition.
// Is called with the gallery instance as "this" object and the
// current index and slide as arguments:
onslideend: function (index, slide) {
// Call our onslide method, and include gallery as an attribute
self.expose_onslideend.apply(self, [this, index, slide]);
},
//// Callback function executed on slide content load.
// Is called with the gallery instance as "this" object and the
// slide index and slide element as arguments:
onslidecomplete: function (index, slide) {
// Call our onslide method, and include gallery as an attribute
self.expose_onslidecomplete.apply(self, [this, index, slide]);
},
//// Callback function executed when the Gallery is about to be closed.
// Is called with the gallery instance as "this" object:
onclose: jQuery.proxy(this.expose_onclose, this),
// Callback function executed when the Gallery has been closed
// and the closing transition has been completed.
// Is called with the gallery instance as "this" object:
onclosed: jQuery.proxy(this.expose_onclosed, this)
};
let $body = jQuery('body');
if ($body.find('#blueimp-gallery').length == 0) {
// Gallery Main DIV container
let $expose_node = jQuery(document.createElement('div')).attr({
id: "blueimp-gallery",
class: "blueimp-gallery"
});
// Create Gallery DOM NODE
$expose_node.append('<div class="slides"></div><h3 class="title"></h3><a class="prev"></a><a class="next"></a><a title="' + egw().lang('Close') + '" class="close">×</a><a title="' + egw().lang('Play/Pause') + '" class="play-pause"></a><a title="' + egw().lang('Fullscreen') + '" class="fullscreen"></a><a title="' + egw().lang('Save') + '" class="download"></a><ol class="indicator"></ol>');
// Append the gallery Node to DOM
$body.append($expose_node);
}
}
set_value(_value) {
//todo: not sure if we need that with the new construction
//if (typeof this._super == 'undefined') return;
// @ts-ignore
super.set_value(_value);
// Do not run set value of expose if expose_view is not set
// it causes a wired error on nested image widgets which
// seems the expose is not its child widget
// @ts-ignore
if (!this.options.expose_view) {
return;
}
let fe = egw_get_file_editor_prefered_mimes();
let self = this;
// If the media type is not supported do not bind the click handler
if (!_value || typeof _value.mime != 'string' || (!_value.mime.match(MIME_REGEX, 'ig')
&& (!fe || fe.mime && !fe.mime[_value.mime])) || typeof _value.download_url == 'undefined') {
return;
}
// @ts-ignore
if (typeof this.options.expose_view != 'undefined' && this.options.expose_view) {
// @ts-ignore
jQuery(this.node).on('click', function (event) {
// Do not trigger expose view if one of the operator keys are held
if (!event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey) {
if (_value.mime.match(MIME_REGEX, 'ig') && !_value.mime.match(MIME_AUDIO_REGEX, 'ig')) {
self._init_blueimp_gallery(event, _value);
}
else if (_value.mime.match(MIME_AUDIO_REGEX, 'ig')) {
self._audio_player(_value);
}
else if (fe && fe.mime && fe.edit && fe.mime[_value.mime]) {
egw.open_link(egw.link('/index.php', {
menuaction: fe.edit.menuaction,
path: _value.path,
cd: 'no' // needed to not reload framework in sharing
}), '', fe.edit_popup);
}
}
event.stopImmediatePropagation();
}).addClass('et2_clickable');
}
}
_init_blueimp_gallery(event, _value) {
let mediaContent = [];
let nm = find_nextmatch(this);
let current_index = 0;
if (nm && !this._is_target_indepth(nm, event.target)) {
// Get the row that was clicked, find its index in the list
let current_entry = nm.controller.getRowByNode(event.target);
// But before it goes, we'll pull everything we can
read_from_nextmatch.call(this, nm, mediaContent);
// find current_entry in array and set it's array-index
for (let i = 0; i < mediaContent.length; i++) {
if ('filemanager::' + mediaContent[i].path == current_entry.uid) {
current_index = i;
break;
}
}
// This will trigger nm to refresh and get just the ones we can handle
// but it might take a while, so do it later - make sure our current
// one is loaded first.
window.setTimeout(function () {
nm.applyFilters({ col_filter: { mime: '/' + MIME_REGEX.source + '/' } });
}, 1);
}
else {
// @ts-ignore
mediaContent = this.getMedia(_value);
// Do not show thumbnail indicator on single expose view
this.expose_options.thumbnailIndicators = false;
}
this.expose_options.index = current_index;
// @ts-ignore
gallery = blueimp.Gallery(mediaContent, this.expose_options);
}
/**
* audio player expose
* @param _value
* @private
*/
_audio_player(_value) {
let button = [
{ "button_id": 1, "text": egw.lang('close'), id: '1', image: 'cancel', default: true }
];
// @ts-ignore
let mediaContent = this.getMedia(_value)[0];
et2_createWidget("dialog", {
callback: function (_btn, value) {
if (_btn == et2_dialog.OK_BUTTON) {
}
},
beforeClose: function () {
},
title: mediaContent.title,
buttons: button,
minWidth: 350,
minHeight: 200,
modal: false,
position: "right bottom,right-50 bottom-10",
value: {
content: {
src: mediaContent.download_href
}
},
resizable: false,
template: egw.webserverUrl + '/api/templates/default/audio_player.xet',
dialogClass: "audio_player"
});
}
/**
* Check if clicked target from nm is in depth
*
* @param nm nextmatch widget
* @param target selected target dom node
*
* @return {boolean} returns false if target is not in depth otherwise True
*/
_is_target_indepth(nm, target) {
let res = false;
if (nm) {
if (!target) {
// @ts-ignore
let target = this.getDOMNode();
}
let entry = nm.controller.getRowByNode(target);
if (entry && entry.controller.getDepth() > 0) {
res = true;
}
}
return res;
}
expose_onopen(event) { }
expose_onopened() {
// Check to see if we're in a nextmatch, do magic
let nm = find_nextmatch(this);
let self = this;
if (nm) {
// Add scrolling to the indicator list
let total_count = nm.controller._grid.getTotalCount();
if (total_count >= gallery.num) {
let $indicator = gallery.container.find('.indicator');
$indicator.off()
.addClass('paginating')
.swipe(function (event, direction, distance) {
// @ts-ignore
if (direction == jQuery.fn.swipe.directions.LEFT) {
distance *= -1;
}
// @ts-ignore
else if (direction == jQuery.fn.swipe.directions.RIGHT) {
// OK.
}
else {
return;
}
jQuery(this).css('left', Math.min(0, parseInt(jQuery(this).css('left')) - (distance * 30)) + 'px');
});
// Bind the mousewheel handler for FF (DOMMousewheel), and other browsers (mousewheel)
$indicator.bind('mousewheel DOMMousewheel', function (event, _delta) {
var delta = _delta || event.originalEvent.wheelDelta / 120;
if (delta > 0 && parseInt(jQuery(this).css('left')) > gallery.container.width() / 2)
return;
//Reload next pictures into the gallery by scrolling on thumbnails
if (delta < 0 && jQuery(this).width() + parseInt(jQuery(this).css('left')) < gallery.container.width()) {
var nextIndex = gallery.indicatorContainer.find('[title="loading"]')[0];
if (nextIndex)
self.expose_onslideend(gallery, nextIndex.dataset.index - 1);
return;
}
// Move it about 5 indicators
jQuery(this).css('left', parseInt(jQuery(this).css('left')) - (-delta * gallery.activeIndicator.width() * 5) + 'px');
event.preventDefault();
});
}
}
}
/**
* Trigger on slide left/right
*/
expose_onslide(gallery, index, slide) {
//todo
//if (typeof this._super == 'undefined') return;
// First let parent try
super.expose_onslide(gallery, index, slide);
let nm = find_nextmatch(this);
if (nm) {
// See if we need to move the indicator
let indicator = gallery.container.find('.indicator');
let current = jQuery('.active', indicator).position();
if (current) {
indicator.animate({ left: (gallery.container.width() / 2) - current.left }, 10);
}
}
}
expose_onslideend(gallery, index) {
// Check to see if we're in a nextmatch, do magic
let nm = find_nextmatch(this);
if (nm) {
// Check to see if we're near the end, or maybe some pagination
// would be good.
let total_count = nm.controller._grid.getTotalCount();
// Already at the end, don't bother
if (index == total_count - 1 || index == 0)
return;
// Try to determine direction from state of next & previous slides
let direction = 1;
for (let i in gallery.elements) {
// Loading or error
if (gallery.elements[i] == 1 || gallery.elements[i] == 3 || gallery.list[i].loading) {
direction = i >= index ? 1 : -1;
break;
}
}
if (!gallery.list[index + direction] || gallery.list[index + direction].loading ||
total_count > gallery.getNumber() && index + ET2_DATAVIEW_STEPSIZE > gallery.getNumber()) {
// This will get the next batch of rows
let start = Math.max(0, direction > 0 ? index : index - ET2_DATAVIEW_STEPSIZE);
let end = Math.min(total_count - 1, start + ET2_DATAVIEW_STEPSIZE);
nm.controller._gridCallback(start, end);
let images = [];
read_from_nextmatch.call(this, nm, images, start);
// Gallery always adds to the end, causing problems with pagination
for (let i in images) {
//if(i == index || i < gallery.num) continue;
set_slide(i, images[i]);
//gallery.add([images[i]]);
}
}
}
}
expose_onslidecomplete() { }
expose_onclose() {
// Check to see if we're in a nextmatch, remove magic
let nm = find_nextmatch(this);
if (nm && !this._is_target_indepth(nm)) {
// Remove scrolling from thumbnails
gallery.container.find('.indicator')
.removeClass('paginating')
.off('mousewheel')
.off('swipe');
// Remove applied mime filter
nm.applyFilters({ col_filter: { mime: '' } });
}
}
expose_onclosed() { }
};
}
//# sourceMappingURL=expose.js.map

View File

@ -1,298 +0,0 @@
/**
* EGroupware - VFS SELECT Widget UI
*
* @link https://www.egroupware.org
* @package et2_vfsSelect
* @author Hadi Nategh <hn@egroupware.org>
* @copyright (c) 2013-2021 by Hadi Nategh <hn@egroupware.org>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
/*egw:uses
/api/js/jsapi/egw_app.js
*/
import { EgwApp } from "../jsapi/egw_app";
import { egw } from "../jsapi/egw_global";
import { et2_dialog } from "./et2_widget_dialog";
import { et2_file } from "./et2_widget_file";
import { et2_textbox } from "./et2_widget_textbox";
import { et2_checkbox } from "./et2_widget_checkbox";
/**
* UI for VFS Select widget
*
*/
export class vfsSelectUI extends EgwApp {
/**
* Constructor
*
*/
constructor() {
// call parent
super('vfsSelectUI');
this.egw.langRequireApp(this.egw.window, 'filemanager');
}
/**
* Destructor
*/
destroy(_app) {
delete this.path_widget;
delete this.vfsSelectWidget;
// call parent
super.destroy(_app);
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*
* @param et2 etemplate2 Newly ready object
* @param {string} name template name
*/
et2_ready(et2, name) {
this.path_widget = this.et2.getWidgetById('path');
this.dirContent = this.et2.getArrayMgr('content').data.dir;
}
/**
* Get directory of a path
*
* @param {string} _path
* @returns string
*/
dirname(_path) {
let parts = _path.split('/');
parts.pop();
return parts.join('/') || '/';
}
/**
* Get name of a path
*
* @param {string} _path
* @returns string
*/
basename(_path) {
return _path.split('/').pop();
}
/**
* Get current working directory
*
* @return string
*/
get_path() {
return this.path_widget.get_value();
}
/**
* Send names of uploaded files (again) to server,
* to process them: either copy to vfs or ask overwrite/rename
*
* @param {event} _event
*/
storeFile(_event) {
let path = this.get_path();
if (!jQuery.isEmptyObject(_event.data.getValue())) {
let widget = _event.data;
egw(window).json('EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_vfsSelect_storeFile', [widget.getValue(), path], this._storeFile_callback, this, true, this).sendRequest(true);
widget.set_value('');
}
}
/**
* Callback for server response to storeFile request:
* - display message and refresh list
* - ask use to confirm overwritting existing files or rename upload
*
* @param {object} _data values for attributes msg, files, ...
*/
_storeFile_callback(_data) {
if (_data.msg || _data.uploaded)
egw(window).message(_data.msg);
let that = this;
for (let file in _data.uploaded) {
if (_data.uploaded[file].confirm && !_data.uploaded[file].confirmed) {
let buttons = [
{ text: this.egw.lang("Yes"), id: "overwrite", class: "ui-priority-primary", "default": true, image: 'check' },
{ text: this.egw.lang("Rename"), id: "rename", image: 'edit' },
{ text: this.egw.lang("Cancel"), id: "cancel" }
];
if (_data.uploaded[file].confirm === "is_dir")
buttons.shift();
let dialog = et2_dialog.show_prompt(function (_button_id, _value) {
let uploaded = {};
uploaded[this.my_data.file] = this.my_data.data;
switch (_button_id) {
case "overwrite":
uploaded[this.my_data.file].confirmed = true;
// fall through
case "rename":
uploaded[this.my_data.file].name = _value;
delete uploaded[this.my_data.file].confirm;
// send overwrite-confirmation and/or rename request to server
egw.json('EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_vfsSelect_storeFile', [uploaded, this.my_data.path], that._storeFile_callback, that, true, that).sendRequest();
return;
case "cancel":
// Remove that file from every file widget...
that.et2.iterateOver(function (_widget) {
_widget.remove_file(this.my_data.data.name);
}, this, et2_file);
}
}, _data.uploaded[file].confirm === "is_dir" ?
this.egw.lang("There's already a directory with that name!") :
this.egw.lang('Do you want to overwrite existing file %1 in directory %2?', _data.uploaded[file].name, _data.path), this.egw.lang('File %1 already exists', _data.uploaded[file].name), _data.uploaded[file].name, buttons, file);
// setting required data for callback in as my_data
dialog.my_data = {
file: file,
path: _data.path,
data: _data.uploaded[file],
};
}
else {
this.submit();
}
}
}
/**
* Prompt user for directory to create
*
* @param {egwAction|undefined|jQuery.Event} action Action, event or undefined if called directly
* @param {egwActionObject[] | undefined} selected Selected row, or undefined if called directly
*/
createdir(action, selected) {
let self = this;
et2_dialog.show_prompt(function (button, dir) {
if (button && dir) {
let path = self.get_path();
self.egw.json('EGroupware\\Api\\Etemplate\\Widget\\Vfs::ajax_create_dir', [dir, path], function (msg) {
self.egw.message(msg);
self.change_dir((path == '/' ? '' : path) + '/' + dir);
}).sendRequest(false);
}
}, this.egw.lang('New directory'), this.egw.lang('Create directory'));
}
/**
* Change directory
*
* @param {string} _dir directory to change to incl. '..' for one up
*/
change_dir(_dir) {
if (_dir == '..') {
_dir = this.dirname(this.get_path());
}
this.path_widget.set_value(_dir);
}
/**
* Row or filename in select-file dialog clicked
*
* @param {jQuery.event} event
* @param {et2_widget} widget
*/
select_clicked(event, widget) {
if (!widget || typeof widget.value != 'object') {
}
else if (widget.value.is_dir) // true for "httpd/unix-directory" and "egw/*"
{
let path = null;
// Cannot do this, there are multiple widgets named path
// widget.getRoot().getWidgetById("path");
widget.getRoot().iterateOver(function (widget) {
if (widget.id == "path")
path = widget;
}, null, et2_textbox);
if (path) {
path.set_value(widget.value.path);
}
}
else if (this.et2 && this.et2.getArrayMgr('content').getEntry('mode') != 'open-multiple') {
this.et2.setValueById('name', widget.value.name);
}
else {
let file = widget.value.name;
widget.getParent().iterateOver(function (widget) {
if (widget.options.selected_value == file) {
widget.set_value(widget.get_value() == file ? widget.options.unselected_value : file);
}
}, null, et2_checkbox);
}
// Stop event or it will toggle back off
event.preventDefault();
event.stopPropagation();
return false;
}
/**
* Handles action and offer it to the submit
*
* @param {string} action action name
* @param {object} widget widget which action was called from
*/
do_action(action, widget) {
if (!action)
return;
let field = '', value = '';
switch (action) {
case 'path':
field = 'path';
value = widget.getValue();
break;
case 'home':
field = 'action';
value = 'home';
break;
case 'app':
field = 'app';
value = widget.getValue();
break;
case 'mime':
field = 'mime';
value = widget.getValue();
break;
}
this.submit(field, value);
}
/**
* Sumbits content value after modification
*
* @param {string} _field content field to be modified
* @param {any} _val value of field
* @param {function} _callback
*/
submit(_field, _val, _callback) {
let arrMgrs = this.et2.getArrayMgrs();
if (_field) {
arrMgrs.content.data[_field] = _val;
jQuery.extend(arrMgrs.content.data, arrMgrs.modifications.data);
this.et2.setArrayMgrs(arrMgrs);
}
// preserve value of the name
if (arrMgrs && this.et2.getWidgetById('name')) {
arrMgrs.content.data['name'] = this.et2.getWidgetById('name').get_value();
}
this.vfsSelectWidget._content(arrMgrs.content.data, _callback);
}
/**
* search through dir content and set its content base on searched query
* @returns
*/
search(_widget) {
let dir = this.et2.getWidgetById('dir');
let query = _widget.get_value();
if (query == "") {
dir.set_value({ content: this.dirContent });
return;
}
let self = this;
let searchQuery = function (_query) {
let result = {};
let reg = RegExp(_query, 'ig');
let key = 0;
for (let i in self.dirContent) {
if (typeof self.dirContent[i]['name'] != 'undefined' && self.dirContent[i]['name'].match(reg)) {
result[key] = self.dirContent[i];
key++;
}
else if (typeof self.dirContent[i]['name'] == 'undefined' && isNaN(i)) {
result[i] = self.dirContent[i];
}
}
return result;
};
dir.set_value({ content: searchQuery(query) });
}
}
app.classes.vfsSelectUI = vfsSelectUI;
//# sourceMappingURL=vfsSelectUI.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,329 +0,0 @@
import { egw } from "../../api/js/jsapi/egw_global";
export class View {
/**
* Translated label for header
* @param {Object} state
* @returns {string}
*/
static header(state) {
let formatDate = new Date(state.date);
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
return View._owner(state) + date(egw.preference('dateformat'), formatDate);
}
/**
* If one owner, get the owner text
*
* @param {object} state
*/
static _owner(state) {
let owner = '';
if (state.owner.length && state.owner.length == 1 && app.calendar.sidebox_et2) {
var own = app.calendar.sidebox_et2.getWidgetById('owner').getDOMNode();
if (own.selectedIndex >= 0) {
owner = own.options[own.selectedIndex].innerHTML + ": ";
}
}
return owner;
}
/**
* Get the start date for this view
* @param {Object} state
* @returns {Date}
*/
static start_date(state) {
const d = state.date ? new Date(state.date) : new Date();
d.setUTCHours(0);
d.setUTCMinutes(0);
d.setUTCSeconds(0);
d.setUTCMilliseconds(0);
return d;
}
/**
* Get the end date for this view
* @param {Object} state
* @returns {Date}
*/
static end_date(state) {
const d = state.date ? new Date(state.date) : new Date();
d.setUTCHours(23);
d.setUTCMinutes(59);
d.setUTCSeconds(59);
d.setUTCMilliseconds(0);
return d;
}
/**
* Get the owner for this view
*
* This is always the owner from the given state, we use a function
* to trigger setting the widget value.
*
* @param {number[]|String} state state.owner List of owner IDs, or a comma seperated list
* @returns {number[]|String}
*/
static owner(state) {
return state.owner || 0;
}
/**
* Should the view show the weekends
*
* @param {object} state
* @returns {boolean} Current preference to show 5 or 7 days in weekview
*/
static show_weekend(state) {
return state.weekend;
}
/**
* How big or small are the displayed time chunks?
*
* @param {object} state
*/
static granularity(state) {
var list = egw.preference('use_time_grid', 'calendar');
if (list == '0' || typeof list === 'undefined') {
return parseInt('' + egw.preference('interval', 'calendar')) || 30;
}
if (typeof list == 'string')
list = list.split(',');
if (!list.indexOf && jQuery.isPlainObject(list)) {
list = jQuery.map(list, function (el) {
return el;
});
}
return list.indexOf(state.view) >= 0 ?
0 :
parseInt(egw.preference('interval', 'calendar')) || 30;
}
static extend(sub) {
return jQuery.extend({}, this, { _super: this }, sub);
}
/**
* Determines the new date after scrolling. The default is 1 week.
*
* @param {number} delta Integer for how many 'ticks' to move, positive for
* forward, negative for backward
* @returns {Date}
*/
static scroll(delta) {
var d = new Date(app.calendar.state.date);
d.setUTCDate(d.getUTCDate() + (7 * delta));
return d;
}
}
// List of etemplates to show for this view
View.etemplates = ['calendar.view'];
/**
* Etemplates and settings for the different views. Some (day view)
* use more than one template, some use the same template as others,
* most need different handling for their various attributes.
*/
export class day extends View {
static header(state) {
var formatDate = new Date(state.date);
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
return date('l, ', formatDate) + super.header(state);
}
static start_date(state) {
var d = super.start_date(state);
state.date = app.calendar.date.toString(d);
return d;
}
static show_weekend(state) {
state.days = '1';
return true;
}
static scroll(delta) {
var d = new Date(app.calendar.state.date);
d.setUTCDate(d.getUTCDate() + (delta));
return d;
}
}
day.etemplates = ['calendar.view', 'calendar.todo'];
export class day4 extends View {
static end_date(state) {
var d = super.end_date(state);
state.days = '4';
d.setUTCHours(24 * 4 - 1);
d.setUTCMinutes(59);
d.setUTCSeconds(59);
d.setUTCMilliseconds(0);
return d;
}
static show_weekend(state) {
state.weekend = 'true';
return true;
}
static scroll(delta) {
var d = new Date(app.calendar.state.date);
d.setUTCDate(d.getUTCDate() + (4 * delta));
return d;
}
}
export class week extends View {
static header(state) {
var end_date = state.last;
if (!week.show_weekend(state)) {
end_date = new Date(state.last);
end_date.setUTCDate(end_date.getUTCDate() - 2);
}
return super._owner(state) + app.calendar.egw.lang('Week') + ' ' +
app.calendar.date.week_number(state.first) + ': ' +
app.calendar.date.long_date(state.first, end_date);
}
static start_date(state) {
return app.calendar.date.start_of_week(super.start_date(state));
}
static end_date(state) {
var d = app.calendar.date.start_of_week(state.date || new Date());
// Always 7 days, we just turn weekends on or off
d.setUTCHours(24 * 7 - 1);
d.setUTCMinutes(59);
d.setUTCSeconds(59);
d.setUTCMilliseconds(0);
return d;
}
}
export class weekN extends View {
static header(state) {
return super._owner(state) + app.calendar.egw.lang('Week') + ' ' +
app.calendar.date.week_number(state.first) + ' - ' +
app.calendar.date.week_number(state.last) + ': ' +
app.calendar.date.long_date(state.first, state.last);
}
static start_date(state) {
return app.calendar.date.start_of_week(super.start_date(state));
}
static end_date(state) {
state.days = '' + (state.days >= 5 ? state.days : egw.preference('days_in_weekview', 'calendar') || 7);
var d = app.calendar.date.start_of_week(super.start_date(state));
// Always 7 days, we just turn weekends on or off
d.setUTCHours(24 * 7 * (parseInt(app.calendar.egw.preference('multiple_weeks', 'calendar')) || 3) - 1);
return d;
}
}
export class month extends View {
static header(state) {
var formatDate = new Date(state.date);
formatDate = new Date(formatDate.valueOf() + formatDate.getTimezoneOffset() * 60 * 1000);
return super._owner(state) + app.calendar.egw.lang(date('F', formatDate)) + ' ' + date('Y', formatDate);
}
static start_date(state) {
var d = super.start_date(state);
d.setUTCDate(1);
return app.calendar.date.start_of_week(d);
}
static end_date(state) {
var d = super.end_date(state);
d = new Date(d.getFullYear(), d.getUTCMonth() + 1, 1, 0, -d.getTimezoneOffset(), 0);
d.setUTCSeconds(d.getUTCSeconds() - 1);
return app.calendar.date.end_of_week(d);
}
static scroll(delta) {
var d = new Date(app.calendar.state.date);
// Set day to 15 so we don't get overflow on short months
// eg. Aug 31 + 1 month = Sept 31 -> Oct 1
d.setUTCDate(15);
d.setUTCMonth(d.getUTCMonth() + delta);
return d;
}
}
export class planner extends View {
static header(state) {
var startDate = new Date(state.first);
startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
var endDate = new Date(state.last);
endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
return super._owner(state) + date(egw.preference('dateformat'), startDate) +
(startDate == endDate ? '' : ' - ' + date(egw.preference('dateformat'), endDate));
}
static group_by(state) {
return state.sortby ? state.sortby : 0;
}
// Note: Planner uses the additional value of planner_view to determine
// the start & end dates using other view's functions
static start_date(state) {
// Start here, in case we can't find anything better
var d = super.start_date(state);
if (state.sortby && state.sortby === 'month') {
d.setUTCDate(1);
}
else if (state.planner_view && app.classes.calendar.views[state.planner_view]) {
d = app.classes.calendar.views[state.planner_view].start_date.call(this, state);
}
else {
d = app.calendar.date.start_of_week(d);
d.setUTCHours(0);
d.setUTCMinutes(0);
d.setUTCSeconds(0);
d.setUTCMilliseconds(0);
return d;
}
return d;
}
static end_date(state) {
var d = super.end_date(state);
if (state.sortby && state.sortby === 'month') {
d.setUTCDate(0);
d.setUTCFullYear(d.getUTCFullYear() + 1);
}
else if (state.planner_view && app.classes.calendar.views[state.planner_view]) {
d = app.classes.calendar.views[state.planner_view].end_date(state);
}
else if (state.days) {
// This one comes from a grid view, but we'll use it
d.setUTCDate(d.getUTCDate() + parseInt(state.days) - 1);
delete state.days;
}
else {
d = app.calendar.date.end_of_week(d);
}
return d;
}
static hide_empty(state) {
var check = state.sortby == 'user' ? ['user', 'both'] : ['cat', 'both'];
return (check.indexOf(egw.preference('planner_show_empty_rows', 'calendar') + '') === -1);
}
static scroll(delta) {
if (app.calendar.state.planner_view && !isNaN(delta) && app.calendar.state.sortby !== "month") {
return app.classes.calendar.views[app.calendar.state.planner_view].scroll(delta);
}
let d = new Date(app.calendar.state.date);
let days = 1;
delta = parseInt(delta) || 0;
// Yearly view, grouped by month - scroll 1 month
if (app.calendar.state.sortby === 'month') {
d.setUTCMonth(d.getUTCMonth() + delta);
d.setUTCDate(1);
d.setUTCHours(0);
d.setUTCMinutes(0);
return d;
}
// Need to set the day count, or auto date ranging takes over and
// makes things buggy
if (app.calendar.state.first && app.calendar.state.last) {
//@ts-ignore
let diff = new Date(app.calendar.state.last) - new Date(app.calendar.state.first);
days = Math.round(diff / (1000 * 3600 * 24));
}
d.setUTCDate(d.getUTCDate() + (days * delta));
if (days > 8) {
d = app.calendar.date.start_of_week(d);
}
return d;
}
}
planner.etemplates = ['calendar.planner'];
export class listview extends View {
static header(state) {
var startDate = new Date(state.first || state.date);
startDate = new Date(startDate.valueOf() + startDate.getTimezoneOffset() * 60 * 1000);
var start_check = '' + startDate.getFullYear() + startDate.getMonth() + startDate.getDate();
var endDate = new Date(state.last || state.date);
endDate = new Date(endDate.valueOf() + endDate.getTimezoneOffset() * 60 * 1000);
var end_check = '' + endDate.getFullYear() + endDate.getMonth() + endDate.getDate();
return super._owner(state) +
date(egw.preference('dateformat'), startDate) +
(start_check == end_check ? '' : ' - ' + date(egw.preference('dateformat'), endDate));
}
}
listview.etemplates = ['calendar.list'];
//# sourceMappingURL=View.js.map

View File

@ -1,988 +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
*/
/*egw:uses
et2_core_valueWidget;
/calendar/js/et2_widget_event.js;
*/
import { et2_createWidget, et2_register_widget } from "../../api/js/etemplate/et2_core_widget";
import { et2_valueWidget } from "../../api/js/etemplate/et2_core_valueWidget";
import { et2_calendar_timegrid } from "./et2_widget_timegrid";
import { et2_calendar_view } from "./et2_widget_view";
import { et2_calendar_event } from "./et2_widget_event";
import { ClassWithAttributes } from "../../api/js/etemplate/et2_core_inheritance";
import { et2_no_init } from "../../api/js/etemplate/et2_core_common";
import { egw } from "../../api/js/jsapi/egw_global";
import { egwIsMobile } from "../../api/js/egw_action/egw_action_common.js";
import { CalendarApp } from "./app";
import { sprintf } from "../../api/js/egw_action/egw_action_common.js";
/**
* Class which implements the "calendar-timegrid" XET-Tag for displaying a single days
*
* This widget is responsible mostly for positioning its events
*
*/
export class et2_calendar_daycol extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_calendar_daycol._attributes, _child || {}));
this.registeredUID = null;
// Init to defaults, just in case - they will be updated from parent
this.display_settings = {
wd_start: 60 * 9,
wd_end: 60 * 17,
granularity: 30,
rowsToDisplay: 10,
rowHeight: 20,
// Percentage; not yet available
titleHeight: 2.0
};
// Main container
this.div = jQuery(document.createElement("div"))
.addClass("calendar_calDayCol")
.css('width', this.options.width)
.css('left', this.options.left);
this.header = jQuery(document.createElement('div'))
.addClass("calendar_calDayColHeader")
.css('width', this.options.width)
.css('left', this.options.left);
this.title = jQuery(document.createElement('div'))
.addClass('et2_clickable et2_link')
.appendTo(this.header);
this.user_spacer = jQuery(document.createElement('div'))
.addClass("calendar_calDayColHeader_spacer")
.appendTo(this.header);
this.all_day = jQuery(document.createElement('div'))
.addClass("calendar_calDayColAllDay")
.css('max-height', (egw.preference('limit_all_day_lines', 'calendar') || 3) * 1.4 + 'em')
.appendTo(this.header);
this.event_wrapper = jQuery(document.createElement('div'))
.addClass("event_wrapper")
.appendTo(this.div);
this.setDOMNode(this.div[0]);
// Used for its date calculations - note this is a datetime, parent
// uses just a date
this._date_helper = et2_createWidget('date-time', {}, null);
this._date_helper.loadingFinished();
}
doLoadingFinished() {
let result = super.doLoadingFinished();
// Parent will have everything we need, just load it from there
if (this.getParent() && this.getParent().options.owner) {
this.set_owner(this.getParent().options.owner);
}
if (this.title.text() === '' && this.options.date &&
this.getParent() && this.getParent().instanceOf(et2_calendar_timegrid)) {
// Forces an update
const date = this.options.date;
this.options.date = '';
this.set_date(date);
}
return result;
}
destroy() {
super.destroy();
this.div.off();
this.header.off().remove();
this.title.off();
this.div = null;
this.header = null;
this.title = null;
this.user_spacer = null;
// date_helper has no parent, so we must explicitly remove it
this._date_helper.destroy();
this._date_helper = null;
egw.dataUnregisterUID(this.registeredUID, null, this);
}
getDOMNode(sender) {
if (!sender || sender === this)
return this.div[0];
if (sender.instanceOf && sender.instanceOf(et2_calendar_event)) {
if (this.display_settings.granularity === 0) {
return this.event_wrapper[0];
}
if (sender.options.value.whole_day_on_top ||
sender.options.value.whole_day && sender.options.value.non_blocking === true) {
return this.all_day[0];
}
return this.div[0];
}
}
/**
* Draw the individual divs for clicking to add an event
*/
_draw() {
// Remove any existing
jQuery('.calendar_calAddEvent', this.div).remove();
// Grab real values from parent
if (this.getParent() && this.getParent().instanceOf(et2_calendar_timegrid)) {
this.display_settings.wd_start = 60 * this.getParent().options.day_start;
this.display_settings.wd_end = 60 * this.getParent().options.day_end;
this.display_settings.granularity = this.getParent().options.granularity;
const header = this.getParent().dayHeader.children();
// Figure out insert index
let idx = 0;
const siblings = this.getParent().getDOMNode(this).childNodes;
while (idx < siblings.length && siblings[idx] != this.getDOMNode()) {
idx++;
}
// Stick header in the right place
if (idx == 0) {
this.getParent().dayHeader.prepend(this.header);
}
else if (header.length) {
header.eq(Math.min(header.length, idx) - 1).after(this.header);
}
}
this.div.attr('data-date', this.options.date);
}
getDate() {
return this.date;
}
get date_helper() {
return this._date_helper;
}
/**
* Set the date
*
* @param {string|Date} _date New date
* @param {Object[]} events =false List of event data to be displayed, or false to
* automatically fetch data from content array
* @param {boolean} force_redraw =false Redraw even if the date is the same.
* Used for when new data is available.
*/
set_date(_date, events, force_redraw) {
if (typeof events === 'undefined' || !events) {
events = false;
}
if (typeof force_redraw === 'undefined' || !force_redraw) {
force_redraw = false;
}
if (!this.getParent() || !this.getParent().date_helper) {
egw.debug('warn', 'Day col widget "' + this.id + '" is missing its parent.');
return false;
}
if (typeof _date === "object") {
this.getParent().date_helper.set_value(_date);
}
else if (typeof _date === "string") {
// Need a new date to avoid invalid month/date combinations when setting
// month then day. Use a string to avoid browser timezone.
this.getParent().date_helper.set_value(_date.substring(0, 4) + '-' + (_date.substring(4, 6)) + '-' + _date.substring(6, 8) + 'T00:00:00Z');
}
this.date = new Date(this.getParent().date_helper.getValue());
// Keep internal option in Ymd format, it gets passed around in this format
const new_date = "" + this.getParent().date_helper.get_year() +
sprintf("%02d", this.getParent().date_helper.get_month()) +
sprintf("%02d", this.getParent().date_helper.get_date());
// Set label
if (!this.options.label) {
// Add timezone offset back in, or formatDate will lose those hours
const formatDate = new Date(this.date.valueOf() + this.date.getTimezoneOffset() * 60 * 1000);
this.title.html('<span class="long_date">' + jQuery.datepicker.formatDate('DD', formatDate) +
'</span><span class="short_date">' + jQuery.datepicker.formatDate('D', formatDate) + '</span>' +
jQuery.datepicker.formatDate('d', formatDate));
}
this.title
.attr("data-date", new_date)
.toggleClass('et2_label', !!this.options.label);
this.header
.attr('data-date', new_date)
.attr('data-whole_day', true);
// Avoid redrawing if date is the same
if (new_date === this.options.date &&
this.display_settings.granularity === this.getParent().options.granularity &&
!force_redraw) {
return;
}
const cache_id = CalendarApp._daywise_cache_id(new_date, this.options.owner);
if (this.options.date && this.registeredUID &&
cache_id !== this.registeredUID) {
egw.dataUnregisterUID(this.registeredUID, null, this);
// Remove existing events
while (this._children.length > 0) {
const node = this._children[this._children.length - 1];
this.removeChild(node);
node.destroy();
}
}
this.options.date = new_date;
// Set holiday and today classes
this.day_class_holiday();
// Update all the little boxes
this._draw();
// Register for updates on events for this day
if (this.registeredUID !== cache_id) {
this.registeredUID = cache_id;
egw.dataRegisterUID(this.registeredUID, this._data_callback, this, this.getInstanceManager().execId, this.id);
}
}
/**
* Set the owner of this day
*
* @param {number|number[]|string|string[]} _owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
*/
set_owner(_owner) {
this.title
.attr("data-owner", _owner);
this.header.attr('data-owner', _owner);
this.div.attr('data-owner', _owner);
// Simple comparison, both numbers
if (_owner === this.options.owner)
return;
// More complicated comparison, one or the other is an array
if ((typeof _owner == 'object' || typeof this.options.owner == 'object') &&
_owner.toString() == this.options.owner.toString()) {
return;
}
this.options.owner = typeof _owner !== 'object' ? [_owner] : _owner;
const cache_id = CalendarApp._daywise_cache_id(this.options.date, _owner);
if (this.options.date && this.registeredUID &&
cache_id !== this.registeredUID) {
egw.dataUnregisterUID(this.registeredUID, null, this);
}
if (this.registeredUID !== cache_id) {
this.registeredUID = cache_id;
egw.dataRegisterUID(this.registeredUID, this._data_callback, this, this.getInstanceManager().execId, this.id);
}
}
set_class(classnames) {
this.header.removeClass(this.class);
super.set_class(classnames);
this.header.addClass(classnames);
}
/**
* Callback used when the daywise data changes
*
* Events should update themselves when their data changes, here we are
* dealing with a change in which events are displayed on this day.
*
* @param {String[]} event_ids
* @returns {undefined}
*/
_data_callback(event_ids) {
const events = [];
if (event_ids == null || typeof event_ids.length == 'undefined')
event_ids = [];
for (let i = 0; i < event_ids.length; i++) {
let event = egw.dataGetUIDdata('calendar::' + event_ids[i]);
event = event && event.data || false;
if (event && event.date && et2_calendar_event.owner_check(event, this) && (event.date === this.options.date ||
// Accept multi-day events
new Date(event.start) <= this.date //&& new Date(event.end) >= this.date
)) {
events.push(event);
}
else if (event) {
// Got an ID that doesn't belong
event_ids.splice(i--, 1);
}
}
if (!this.div.is(":visible")) {
// Not visible, defer the layout or it all winds up at the top
// Cancel any existing listener & bind
jQuery(this.getInstanceManager().DOMContainer.parentNode)
.off('show.' + CalendarApp._daywise_cache_id(this.options.date, this.options.owner))
.one('show.' + CalendarApp._daywise_cache_id(this.options.date, this.options.owner), function () {
this._update_events(events);
}.bind(this));
return;
}
if (!this.getParent().disabled)
this._update_events(events);
}
set_label(label) {
this.options.label = label;
this.title.text(label);
this.title.toggleClass('et2_clickable et2_link', label === '');
}
set_left(left) {
if (this.div) {
this.div.css('left', left);
}
}
set_width(width) {
this.options.width = width;
if (this.div) {
this.div.outerWidth(this.options.width);
this.header.outerWidth(this.options.width);
}
}
/**
* Applies class for today, and any holidays for current day
*/
day_class_holiday() {
this.title
// Remove all special day classes
.removeClass('calendar_calToday calendar_calBirthday calendar_calHoliday')
// Except this one...
.addClass("et2_clickable et2_link");
this.title.attr('data-holiday', '');
// Set today class - note +1 when dealing with today, as months in JS are 0-11
const today = new Date();
today.setUTCMinutes(today.getUTCMinutes() - today.getTimezoneOffset());
this.title.toggleClass("calendar_calToday", this.options.date === '' + today.getUTCFullYear() +
sprintf("%02d", today.getUTCMonth() + 1) +
sprintf("%02d", today.getUTCDate()));
// Holidays and birthdays
let holidays = et2_calendar_view.get_holidays(this, this.options.date.substring(0, 4));
const holiday_list = [];
let holiday_pref = (egw.preference('birthdays_as_events', 'calendar') || []);
if (typeof holiday_pref === 'string') {
holiday_pref = holiday_pref.split(',');
}
else {
holiday_pref = jQuery.extend([], holiday_pref);
}
// Show holidays as events on mobile or by preference
const holidays_as_events = egwIsMobile() || egw.preference('birthdays_as_events', 'calendar') === true ||
holiday_pref.indexOf('holiday') >= 0;
const birthdays_as_events = egwIsMobile() || holiday_pref.indexOf('birthday') >= 0;
if (holidays && holidays[this.options.date]) {
holidays = holidays[this.options.date];
for (let i = 0; i < holidays.length; i++) {
if (typeof holidays[i]['birthyear'] !== 'undefined') {
// Show birthdays as events on mobile or by preference
if (birthdays_as_events) {
// Create event
this.getParent().date_helper.set_value(this.options.date.substring(0, 4) + '-' +
(this.options.date.substring(4, 6)) + '-' + this.options.date.substring(6, 8) +
'T00:00:00Z');
var event = et2_createWidget('calendar-event', {
id: 'event_' + holidays[i].name,
value: {
title: holidays[i].name,
whole_day: true,
whole_day_on_top: true,
start: new Date(this.getParent().date_helper.get_value()),
end: this.options.date,
owner: this.options.owner,
participants: this.options.owner,
app: 'calendar',
class: 'calendar_calBirthday'
},
readonly: true,
class: 'calendar_calBirthday'
}, this);
event.doLoadingFinished();
event._update();
}
if (!egwIsMobile()) {
//If the birthdays are already displayed as event, don't
//show them in the caption
this.title.addClass('calendar_calBirthday');
holiday_list.push(holidays[i]['name']);
}
}
else {
// Show holidays as events on mobile
if (holidays_as_events) {
// Create event
this.getParent().date_helper.set_value(this.options.date.substring(0, 4) + '-' +
(this.options.date.substring(4, 6)) + '-' + this.options.date.substring(6, 8) +
'T00:00:00Z');
var event = et2_createWidget('calendar-event', {
id: 'event_' + holidays[i].name,
value: {
title: holidays[i].name,
whole_day: true,
whole_day_on_top: true,
start: new Date(this.getParent().date_helper.get_value()),
end: this.options.date,
owner: this.options.owner,
participants: this.options.owner,
app: 'calendar',
class: 'calendar_calHoliday'
},
readonly: true,
class: 'calendar_calHoliday'
}, this);
event.doLoadingFinished();
event._update();
}
else {
this.title.addClass('calendar_calHoliday');
this.title.attr('data-holiday', holidays[i]['name']);
//If the birthdays are already displayed as event, don't
//show them in the caption
if (!this.options.display_holiday_as_event) {
holiday_list.push(holidays[i]['name']);
}
}
}
}
}
this.title.attr('title', holiday_list.join(', '));
}
/**
* Load the event data for this day and create event widgets for each.
*
* If event information is not provided, it will be pulled from the content array.
*
* @param {Object[]} [_events] Array of event information, one per event.
*/
_update_events(_events) {
let c;
const events = _events || this.getArrayMgr('content').getEntry(this.options.date) || [];
// Remove extra events
while (this._children.length > 0) {
const node = this._children[this._children.length - 1];
this.removeChild(node);
node.destroy();
}
// Make sure children are in cronological order, or columns are backwards
events.sort(function (a, b) {
const start = new Date(a.start) - new Date(b.start);
const end = new Date(a.end) - new Date(b.end);
// Whole day events sorted by ID, normal events by start / end time
if (a.whole_day && b.whole_day) {
return (a.app_id - b.app_id);
}
else if (a.whole_day || b.whole_day) {
return a.whole_day ? -1 : 1;
}
return start ? start : end;
});
for (c = 0; c < events.length; c++) {
// Create event
var event = et2_createWidget('calendar-event', {
id: 'event_' + events[c].id,
value: events[c]
}, this);
}
// Seperate loop so column sorting finds all children in the right place
let child_length = this._children.length;
for (c = 0; c < events.length && c < child_length; c++) {
let event = this.getWidgetById('event_' + events[c].id);
if (!event)
continue;
if (this.isInTree()) {
event.doLoadingFinished();
}
}
// Show holidays as events on mobile or by preference
if (egwIsMobile() || egw.preference('birthdays_as_events', 'calendar')) {
this.day_class_holiday();
}
// Apply styles to hidden events
this._out_of_view();
}
/**
* Apply styles for out-of-view and partially hidden events
*
* There are 3 different states or modes of display:
*
* - 'Normal' - When showing events positioned by time, the indicator is just
* a bar colored by the last category color. On hover it shows either the
* title of a single event or "x event(s)" if more than one are hidden.
* Clicking adjusts the current view to show the earliest / latest hidden
* event
*
* - Fixed - When showing events positioned by time but in a fixed-height
* week (not auto-sized to fit screen) the indicator is the same as sized.
* On hover it shows the titles of the hidden events, clicking changes
* the view to the selected day.
*
* - GridList - When showing just a list, the indicator shows "x event(s)",
* and on hover shows the category color, title & time. Clicking changes
* the view to the selected day, and opens the event for editing.
*/
_out_of_view() {
// Reset
this.header.children('.hiddenEventBefore').remove();
this.div.children('.hiddenEventAfter').remove();
this.event_wrapper.css('overflow', 'visible');
this.all_day.removeClass('overflown');
jQuery('.calendar_calEventBody', this.div).css({ 'padding-top': '', 'margin-top': '' });
const timegrid = this.getParent();
// elem is jquery div of event
function isHidden(elem) {
// Add an extra 5px top and bottom to include events just on the
// edge of visibility
const docViewTop = timegrid.scrolling.scrollTop() + 5, docViewBottom = docViewTop + (this.display_settings.granularity === 0 ?
this.event_wrapper.height() :
timegrid.scrolling.height() - 10), elemTop = elem.position().top, elemBottom = elemTop + elem.outerHeight(true);
if ((elemBottom <= docViewBottom) && (elemTop >= docViewTop)) {
// Entirely visible
return false;
}
const visible = {
hidden: elemTop > docViewTop ? 'bottom' : 'top',
completely: false
};
visible.completely = visible.hidden == 'top' ? elemBottom < docViewTop : elemTop > docViewBottom;
return visible;
}
// In gridlist view, we can quickly check if we need it at all
if (this.display_settings.granularity === 0 && this._children.length) {
jQuery('div.calendar_calEvent', this.div).show(0);
if (Math.ceil(this.div.height() / this._children[0].div.height()) > this._children.length) {
return;
}
}
// Check all day overflow
this.all_day.toggleClass('overflown', this.all_day[0].scrollHeight - this.all_day.innerHeight() > 5);
// Check each event
this.iterateOver(function (event) {
// Skip whole day events and events missing value
if (this.display_settings.granularity && ((!event.options || !event.options.value || event.options.value.whole_day_on_top))) {
return;
}
// Reset
event.title.css({ 'top': '', 'background-color': '' });
event.body.css({ 'padding-top': '', 'margin-top': '' });
const hidden = isHidden.call(this, event.div);
const day = this;
if (!hidden) {
return;
}
// Only top is hidden, move label
// Bottom hidden is fine
if (hidden.hidden === 'top' && !hidden.completely && !event.div.hasClass('calendar_calEventSmall')) {
const title_height = event.title.outerHeight();
event.title.css({
'top': timegrid.scrolling.scrollTop() - event.div.position().top,
'background-color': 'transparent'
});
event.body.css({
'padding-top': timegrid.scrolling.scrollTop() - event.div.position().top + title_height,
'margin-top': -title_height
});
}
// Too many in gridlist view, show indicator
else if (this.display_settings.granularity === 0 && hidden) {
if (jQuery('.hiddenEventAfter', this.div).length == 0) {
this.event_wrapper.css('overflow', 'hidden');
}
this._hidden_indicator(event, false, function () {
app.calendar.update_state({ view: 'day', date: day.date });
});
// Avoid partially visible events
// We need to hide all, or the next row will be visible
event.div.hide(0);
}
// Completely out of view, show indicator
else if (hidden.completely) {
this._hidden_indicator(event, hidden.hidden == 'top', false);
}
}, this, et2_calendar_event);
}
/**
* Show an indicator that there are hidden events
*
* The indicator works 3 different ways, depending on if the day can be
* scrolled, is fixed, or if in gridview.
*
* @see _out_of_view()
*
* @param {et2_calendar_event} event Event we're creating the indicator for
* @param {boolean} top Events hidden at the top (true) or bottom (false)
* @param {function} [onclick] Callback for when user clicks on the indicator
*/
_hidden_indicator(event, top, onclick) {
let indicator = null;
const day = this;
const timegrid = this.getParent();
const fixed_height = timegrid.div.hasClass('calendar_calTimeGridFixed');
// Event is before the displayed times
if (top) {
// Create if not already there
if (jQuery('.hiddenEventBefore', this.header).length === 0) {
indicator = jQuery('<div class="hiddenEventBefore"></div>')
.appendTo(this.header)
.attr('data-hidden_count', 1);
if (!fixed_height) {
indicator
.text(event.options.value.title)
.on('click', typeof onclick === 'function' ? onclick : function () {
jQuery('.calendar_calEvent', day.div).first()[0].scrollIntoView();
return false;
});
}
}
else {
indicator = jQuery('.hiddenEventBefore', this.header);
indicator.attr('data-hidden_count', parseInt(indicator.attr('data-hidden_count')) + 1);
if (!fixed_height) {
indicator.text(day.egw().lang('%1 event(s) %2', indicator.attr('data-hidden_count'), ''));
}
}
}
// Event is after displayed times
else {
indicator = jQuery('.hiddenEventAfter', this.div);
// Create if not already there
if (indicator.length === 0) {
indicator = jQuery('<div class="hiddenEventAfter"></div>')
.attr('data-hidden_count', 0)
.appendTo(this.div);
if (!fixed_height) {
indicator
.on('click', typeof onclick === 'function' ? onclick : function () {
jQuery('.calendar_calEvent', day.div).last()[0].scrollIntoView(false);
// Better re-run this to clean up
day._out_of_view();
return false;
});
}
else {
indicator
.on('mouseover', function () {
indicator.css({
'height': (indicator.attr('data-hidden_count') * 1.2) + 'em',
'margin-top': -(indicator.attr('data-hidden_count') * 1.2) + 'em'
});
})
.on('mouseout', function () {
indicator.css({
'height': '',
'margin-top': ''
});
});
}
}
const count = parseInt(indicator.attr('data-hidden_count')) + 1;
indicator.attr('data-hidden_count', count);
if (this.display_settings.granularity === 0) {
indicator.append(event.div.clone());
indicator.attr('data-hidden_label', day.egw().lang('%1 event(s) %2', indicator.attr('data-hidden_count'), ''));
}
else if (!fixed_height) {
indicator.text(day.egw().lang('%1 event(s) %2', indicator.attr('data-hidden_count'), ''));
}
indicator.css('top', timegrid.scrolling.height() + timegrid.scrolling.scrollTop() - indicator.innerHeight());
}
// Show different stuff for fixed height
if (fixed_height) {
indicator
.append("<div id='" + event.dom_id +
"' data-id='" + event.options.value.id + "'>" +
event.options.value.title +
"</div>");
}
// Match color to the event
if (indicator !== null) {
// Avoid white, which is hard to see
// Use border-bottom-color, Firefox doesn't give a value with border-color
const color = jQuery.Color(event.div.css('background-color')).toString() !== jQuery.Color('white').toString() ?
event.div.css('background-color') : event.div.css('border-bottom-color');
if (color !== 'rgba(0, 0, 0, 0)') {
indicator.css('border-color', color);
}
}
}
/**
* Sort a day's events into minimally overlapping columns
*
* @returns {Array[]} Events sorted into columns
*/
_spread_events() {
if (!this.date)
return [];
let day_start = this.date.valueOf() / 1000;
const dst_check = new Date(this.date);
dst_check.setUTCHours(12);
// if daylight saving is switched on or off, correct $day_start
// gives correct times after 2am, times between 0am and 2am are wrong
const daylight_diff = day_start + 12 * 60 * 60 - (dst_check.valueOf() / 1000);
if (daylight_diff) {
day_start -= daylight_diff;
}
const eventCols = [], col_ends = [];
// Make sure children are in cronological order, or columns are backwards
this._children.sort(function (a, b) {
const start = new Date(a.options.value.start) - new Date(b.options.value.start);
const end = new Date(a.options.value.end) - new Date(b.options.value.end);
// Whole day events sorted by ID, normal events by start / end time
if (a.options.value.whole_day && b.options.value.whole_day) {
// Longer duration comes first so we have nicer bars across the top
const duration = (new Date(b.options.value.end) - new Date(b.options.value.start)) -
(new Date(a.options.value.end) - new Date(a.options.value.start));
return duration ? duration : (a.options.value.app_id - b.options.value.app_id);
}
else if (a.options.value.whole_day || b.options.value.whole_day) {
return a.options.value.whole_day ? -1 : 1;
}
return start ? start : end;
});
for (let i = 0; i < this._children.length; i++) {
const event = this._children[i].options.value || false;
if (!event)
continue;
if (event.date && event.date != this.options.date &&
// Multi-day events date may be different
(new Date(event.start) >= this.date || new Date(event.end) < this.date)) {
// Still have a child event that has changed date (DnD)
this._children[i].destroy();
this.removeChild(this._children[i]);
continue;
}
let c = 0;
event['multiday'] = false;
if (typeof event.start !== 'object') {
event.start = new Date(event.start);
}
if (typeof event.end !== 'object') {
event.end = new Date(event.end);
}
event['start_m'] = parseInt(String((event.start.valueOf() / 1000 - day_start) / 60), 10);
if (event['start_m'] < 0) {
event['start_m'] = 0;
event['multiday'] = true;
}
event['end_m'] = parseInt(String((event.end.valueOf() / 1000 - day_start) / 60), 10);
if (event['end_m'] >= 24 * 60) {
event['end_m'] = 24 * 60 - 1;
event['multiday'] = true;
}
if (!event.start.getUTCHours() && !event.start.getUTCMinutes() && event.end.getUTCHours() == 23 && event.end.getUTCMinutes() == 59) {
event.whole_day_on_top = (event.non_blocking && event.non_blocking != '0');
}
if (!event['whole_day_on_top']) {
for (c = 0; event['start_m'] < col_ends[c]; ++c)
;
col_ends[c] = event['end_m'];
}
if (typeof eventCols[c] === 'undefined') {
eventCols[c] = [];
}
eventCols[c].push(this._children[i]);
}
return eventCols;
}
/**
* Position the event according to its time and how this widget is laid
* out.
*
* @param {et2_calendar_event} [event] - Event to be updated
* If a single event is not provided, all events are repositioned.
*/
position_event(event) {
// If hidden, skip it - it takes too long
if (!this.div.is(':visible'))
return;
// Sort events into minimally-overlapping columns
const columns = this._spread_events();
for (let c = 0; c < columns.length; c++) {
// Calculate horizontal positioning
let left = Math.ceil(5 + (1.5 * 100 / (parseFloat(this.options.width) || 100)));
let right = 2;
if (columns.length !== 1) {
right = !c ? 30 : 2;
left += c * (100.0 - left) / columns.length;
}
for (let i = 0; (columns[c].indexOf(event) >= 0 || !event) && i < columns[c].length; i++) {
// Calculate vertical positioning
let top = 0;
let height = 0;
// Position the event
if (this.display_settings.granularity === 0) {
if (this.all_day.has(columns[c][i].div).length) {
columns[c][i].div.prependTo(this.event_wrapper);
}
columns[c][i].div.css('top', '');
columns[c][i].div.css('height', '');
columns[c][i].div.css('left', '');
columns[c][i].div.css('right', '');
// Strip out of view padding
columns[c][i].body.css('padding-top', '');
continue;
}
if (columns[c][i].options.value.whole_day_on_top) {
if (!this.all_day.has(columns[c][i].div).length) {
columns[c][i].div.css('top', '');
columns[c][i].div.css('height', '');
columns[c][i].div.css('left', '');
columns[c][i].div.css('right', '');
columns[c][i].body.css('padding-top', '');
columns[c][i].div
.appendTo(this.all_day);
this.getParent().resizeTimes();
}
continue;
}
else {
if (this.all_day.has(columns[c][i].div).length) {
columns[c][i].div.appendTo(this.event_wrapper);
this.getParent().resizeTimes();
}
top = this._time_to_position(columns[c][i].options.value.start_m);
height = this._time_to_position(columns[c][i].options.value.end_m) - top;
}
// Position the event
if (event && columns[c].indexOf(event) >= 0 || !event) {
columns[c][i].div.css('top', top + '%');
columns[c][i].div.css('height', height + '%');
// Remove spacing from border, but only if visible or the height will be wrong
if (columns[c][i].div.is(':visible')) {
const border_diff = columns[c][i].div.outerHeight() - columns[c][i].div.height();
columns[c][i].div.css('height', 'calc(' + height + '% - ' + border_diff + ')');
}
// This gives the wrong height
//columns[c][i].div.outerHeight(height+'%');
columns[c][i].div.css('left', left.toFixed(1) + '%');
columns[c][i].div.css('right', right.toFixed(1) + '%');
columns[c][i].div.css('z-index', parseInt(20) + c);
columns[c][i]._small_size();
}
}
// Only wanted to position this event, leave the other columns alone
if (event && columns[c].indexOf(event) >= 0) {
return;
}
}
}
/**
* Calculates the vertical position based on the time
*
* This calculation is a percentage from 00:00 to 23:59
*
* @param {int} time in minutes from midnight
* @return {float} position in percent
*/
_time_to_position(time) {
let pos = 0.0;
// 24h
pos = ((time / 60) / 24) * 100;
return pos.toFixed(1);
}
attachToDOM() {
let result = super.attachToDOM();
// Remove the binding for the click handler, unless there's something
// custom here.
if (!this.onclick) {
jQuery(this.node).off("click");
}
// But we do want to listen to certain clicks, and handle them internally
jQuery(this.node).on('click.et2_daycol', '.calendar_calDayColHeader,.calendar_calAddEvent', jQuery.proxy(this.click, this));
return result;
}
/**
* Click handler calling custom handler set via onclick attribute to this.onclick,
* or the default which is to open a new event at that time.
*
* Normally, you don't bind to this one, but the attribute is supported if you
* can get a reference to the widget.
*
* @param {Event} _ev
* @returns {boolean}
*/
click(_ev) {
if (this.getParent().options.readonly)
return;
// Drag to create in progress
if (this.getParent().drag_create.start !== null)
return;
// Click on the title
if (jQuery(_ev.target).hasClass('calendar_calAddEvent')) {
if (this.header.has(_ev.target).length == 0 && !_ev.target.dataset.whole_day) {
// Default handler to open a new event at the selected time
var options = {
date: _ev.target.dataset.date || this.options.date,
hour: _ev.target.dataset.hour || this.getParent().options.day_start,
minute: _ev.target.dataset.minute || 0,
owner: this.options.owner
};
app.calendar.add(options);
return false;
}
// Header, all day non-blocking
else if (this.header.has(_ev.target).length && !jQuery('.hiddenEventBefore', this.header).has(_ev.target).length ||
this.header.is(_ev.target)) {
// Click on the header, but not the title. That's an all-day non-blocking
const end = this.date.getFullYear() + '-' + (this.date.getUTCMonth() + 1) + '-' + this.date.getUTCDate() + 'T23:59';
let options = {
start: this.date.toJSON(),
end: end,
non_blocking: true,
owner: this.options.owner
};
app.calendar.add(options);
return false;
}
}
// Day label
else if (this.title.is(_ev.target) || this.title.has(_ev.target).length) {
app.calendar.update_state({ view: 'day', date: this.date.toJSON() });
return false;
}
}
/**
* Code for implementing et2_IDetachedDOM
*
* @param {array} _attrs array to add further attributes to
*/
getDetachedAttributes(_attrs) {
}
getDetachedNodes() {
return [this.getDOMNode(this)];
}
setDetachedAttributes(_nodes, _values) {
}
// Resizable interface
/**
* Resize
*
* Parent takes care of setting proper width & height for the containing div
* here we just need to adjust the events to fit the new size.
*/
resize() {
if (this.disabled || !this.div.is(':visible') || this.getParent().disabled) {
return;
}
if (this.display_settings.granularity !== this.getParent().options.granularity) {
// Layout has changed
this._draw();
// Resize & position all events
this.position_event();
}
else {
// Don't need to resize & reposition, just clear some stuff
// to reset for _out_of_view()
this.iterateOver(function (widget) {
widget._small_size();
}, this, et2_calendar_event);
}
this._out_of_view();
}
}
et2_calendar_daycol._attributes = {
date: {
name: "Date",
type: "any",
description: "What date is this daycol for. YYYYMMDD or Date",
default: et2_no_init
},
owner: {
name: "Owner",
type: "any",
default: et2_no_init,
description: "Account ID number of the calendar owner, if not the current user"
},
display_birthday_as_event: {
name: "Birthdays",
type: "boolean",
default: false,
description: "Display birthdays as events"
},
display_holiday_as_event: {
name: "Holidays",
type: "boolean",
default: false,
description: "Display holidays as events"
}
};
et2_register_widget(et2_calendar_daycol, ["calendar-daycol"]);
//# sourceMappingURL=et2_widget_daycol.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,139 +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
*/
/*egw:uses
et2_widget_taglist;
*/
import { et2_register_widget } from "../../api/js/etemplate/et2_core_widget";
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_selectbox
*/
export class et2_calendar_owner extends et2_taglist_email {
constructor() {
super(...arguments);
// Allows sub-widgets to override options to the library
this.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_calendar_owner._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."
}
};
et2_register_widget(et2_calendar_owner, ["calendar-owner"]);
//# sourceMappingURL=et2_widget_owner.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,649 +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
*/
/*egw:uses
/calendar/js/et2_widget_view.js;
/calendar/js/et2_widget_daycol.js;
/calendar/js/et2_widget_event.js;
*/
import { et2_createWidget, et2_register_widget } from "../../api/js/etemplate/et2_core_widget";
import { et2_valueWidget } from "../../api/js/etemplate/et2_core_valueWidget";
import { ClassWithAttributes } from "../../api/js/etemplate/et2_core_inheritance";
import { et2_action_object_impl } from "../../api/js/etemplate/et2_core_DOMWidget";
import { egw_getObjectManager, egwActionObject } from "../../api/js/egw_action/egw_action.js";
import { EGW_AI_DRAG_OUT, EGW_AI_DRAG_OVER } from "../../api/js/egw_action/egw_action_constants.js";
import { egw } from "../../api/js/jsapi/egw_global";
/**
* Class for one row of a planner
*
* This widget is responsible for the label on the side
*
*/
export class et2_calendar_planner_row extends et2_valueWidget {
/**
* Constructor
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_calendar_planner_row._attributes, _child || {}));
this._row_height = 20;
// Main container
this.div = jQuery(document.createElement("div"))
.addClass("calendar_plannerRowWidget")
.css('width', this.options.width);
this.title = jQuery(document.createElement('div'))
.addClass("calendar_plannerRowHeader")
.appendTo(this.div);
this.rows = jQuery(document.createElement('div'))
.addClass("calendar_eventRows")
.appendTo(this.div);
this.setDOMNode(this.div[0]);
// Used for its date calculations
this._date_helper = et2_createWidget('date-time', {}, null);
this._date_helper.loadingFinished();
this.set_start_date(this.options.start_date);
this.set_end_date(this.options.end_date);
this._cached_rows = [];
}
doLoadingFinished() {
super.doLoadingFinished();
this.set_label(this.options.label);
this._draw();
// Actions are set on the parent, so we need to explicitly get in here
// and get ours
this._link_actions(this.getParent().options.actions || []);
return true;
}
destroy() {
super.destroy();
// date_helper has no parent, so we must explicitly remove it
this._date_helper.destroy();
this._date_helper = null;
}
getDOMNode(_sender) {
if (_sender === this || !_sender) {
return this.div[0];
}
if (_sender._parent === this) {
return this.rows[0];
}
}
/**
* Link the actions to the DOM nodes / widget bits.
*
* @param {object} actions {ID: {attributes..}+} map of egw action information
*/
_link_actions(actions) {
// Get the parent? Might be a grid row, might not. Either way, it is
// just a container with no valid actions
let objectManager = egw_getObjectManager(this.getInstanceManager().app, true, 1);
objectManager = objectManager.getObjectById(this.getInstanceManager().uniqueId, 2) || objectManager;
let parent = objectManager.getObjectById(this.id, 1) || objectManager.getObjectById(this.getParent().id, 1) || objectManager;
if (!parent) {
egw.debug('error', 'No parent objectManager found');
return;
}
// This binds into the egw action system. Most user interactions (drag to move, resize)
// are handled internally using jQuery directly.
let widget_object = this._actionObject || parent.getObjectById(this.id);
const aoi = new et2_action_object_impl(this, this.getDOMNode(this)).getAOI();
const planner = this.getParent();
for (let i = 0; i < parent.children.length; i++) {
const parent_finder = jQuery(parent.children[i].iface.doGetDOMNode()).find(this.div);
if (parent_finder.length > 0) {
parent = parent.children[i];
break;
}
}
// Determine if we allow a dropped event to use the invite/change actions
const _invite_enabled = function (action, event, target) {
var event = event.iface.getWidget();
const row = target.iface.getWidget() || false;
if (event === row || !event || !row ||
!event.options || !event.options.value.participants) {
return false;
}
let owner_match = false;
const own_row = event.getParent() === row;
for (let id in event.options.value.participants) {
owner_match = owner_match || row.node.dataset.participants === '' + id;
}
const enabled = !owner_match &&
// Not inside its own timegrid
!own_row;
widget_object.getActionLink('invite').enabled = enabled;
widget_object.getActionLink('change_participant').enabled = enabled;
// If invite or change participant are enabled, drag is not
widget_object.getActionLink('egw_link_drop').enabled = !enabled;
};
aoi.doTriggerEvent = function (_event, _data) {
// Determine target node
var event = _data.event || false;
if (!event)
return;
if (_data.ui.draggable.hasClass('rowNoEdit'))
return;
/*
We have to handle the drop in the normal event stream instead of waiting
for the egwAction system so we can get the helper, and destination
*/
if (event.type === 'drop' && widget_object.getActionLink('egw_link_drop').enabled) {
this.getWidget().getParent()._event_drop.call(jQuery('.calendar_d-n-d_timeCounter', _data.ui.helper)[0], this.getWidget().getParent(), event, _data.ui, this.getWidget());
}
const drag_listener = function (_event, ui) {
if (planner.options.group_by === 'month') {
var position = { left: _event.clientX, top: _event.clientY };
}
else {
var position = { top: ui.position.top, left: ui.position.left - jQuery(this).parent().offset().left };
}
aoi.getWidget().getParent()._drag_helper(jQuery('.calendar_d-n-d_timeCounter', ui.helper)[0], position, 0);
let event = _data.ui.draggable.data('selected')[0];
if (!event || event.id && event.id.indexOf('calendar') !== 0) {
event = false;
}
if (event) {
_invite_enabled(widget_object.getActionLink('invite').actionObj, event, widget_object);
}
};
const time = jQuery('.calendar_d-n-d_timeCounter', _data.ui.helper);
switch (_event) {
// Triggered once, when something is dragged into the timegrid's div
case EGW_AI_DRAG_OVER:
// Listen to the drag and update the helper with the time
// This part lets us drag between different timegrids
_data.ui.draggable.on('drag.et2_timegrid_row' + widget_object.id, drag_listener);
_data.ui.draggable.on('dragend.et2_timegrid_row' + widget_object.id, function () {
_data.ui.draggable.off('drag.et2_timegrid_row' + widget_object.id);
});
widget_object.iface.getWidget().div.addClass('drop-hover');
// Disable invite / change actions for same calendar or already participant
var event = _data.ui.draggable.data('selected')[0];
if (!event || event.id && event.id.indexOf('calendar') !== 0) {
event = false;
}
if (event) {
_invite_enabled(widget_object.getActionLink('invite').actionObj, event, widget_object);
}
if (time.length) {
// The out will trigger after the over, so we count
time.data('count', time.data('count') + 1);
}
else {
_data.ui.helper.prepend('<div class="calendar_d-n-d_timeCounter" data-count="1"><span></span></div>');
}
break;
// Triggered once, when something is dragged out of the timegrid
case EGW_AI_DRAG_OUT:
// Stop listening
_data.ui.draggable.off('drag.et2_timegrid_row' + widget_object.id);
// Remove highlight
widget_object.iface.getWidget().div.removeClass('drop-hover');
// Out triggers after the over, count to not accidentally remove
time.data('count', time.data('count') - 1);
if (time.length && time.data('count') <= 0) {
time.remove();
}
break;
}
};
if (widget_object == null) {
// Add a new container to the object manager which will hold the widget
// objects
widget_object = parent.insertObject(false, new egwActionObject(this.id, parent, aoi, this._actionManager || parent.manager.getActionById(this.id) || parent.manager));
}
else {
widget_object.setAOI(aoi);
}
this._actionObject = widget_object;
// Delete all old objects
widget_object.clear();
widget_object.unregisterActions();
// Go over the widget & add links - this is where we decide which actions are
// 'allowed' for this widget at this time
const action_links = this._get_action_links(actions);
this.getParent()._init_links_dnd(widget_object.manager, action_links);
widget_object.updateActionLinks(action_links);
}
/**
* Get all action-links / id's of 1.-level actions from a given action object
*
* Here we are only interested in drop events.
*
* @param actions
* @returns {Array}
*/
_get_action_links(actions) {
const action_links = [];
// Only these actions are allowed without a selection (empty actions)
const empty_actions = ['add'];
for (let i in actions) {
const action = actions[i];
if (empty_actions.indexOf(action.id) !== -1 || action.type == 'drop') {
action_links.push(typeof action.id != 'undefined' ? action.id : i);
}
}
return action_links;
}
/**
* Draw the individual divs for weekends and events
*/
_draw() {
// Remove any existing
this.rows.remove('.calendar_eventRowsMarkedDay,.calendar_eventRowsFiller').nextAll().remove();
let days = 31;
let width = '100';
if (this.getParent().options.group_by === 'month') {
days = this.options.end_date.getUTCDate();
if (days < 31) {
const diff = 31 - days;
width = 'calc(' + (diff * 3.23) + '% - ' + (diff * 7) + 'px)';
}
}
// mark weekends and other special days in yearly planner
if (this.getParent().options.group_by == 'month') {
this.rows.append(this._yearlyPlannerMarkDays(this.options.start_date, days));
}
if (this.getParent().options.group_by === 'month' && days < 31) {
// add a filler for non existing days in that month
this.rows.after('<div class="calendar_eventRowsFiller"' +
' style="width:' + width + ';" ></div>');
}
}
set_label(label) {
this.options.label = label;
this.title.text(label);
if (this.getParent().options.group_by === 'month') {
this.title.attr('data-date', this.options.start_date.toJSON());
this.title.attr('data-sortby', 'user');
this.title.addClass('et2_clickable et2_link');
}
else {
this.title.attr('data-date', '');
this.title.removeClass('et2_clickable');
}
}
/**
* Change the start date
*
* @param {Date} new_date New end date
* @returns {undefined}
*/
set_start_date(new_date) {
if (!new_date || new_date === null) {
throw new TypeError('Invalid end date. ' + new_date.toString());
}
this.options.start_date = new Date(typeof new_date == 'string' ? new_date : new_date.toJSON());
this.options.start_date.setUTCHours(0);
this.options.start_date.setUTCMinutes(0);
this.options.start_date.setUTCSeconds(0);
}
/**
* Change the end date
*
* @param {string|number|Date} new_date New end date
* @returns {undefined}
*/
set_end_date(new_date) {
if (!new_date || new_date === null) {
throw new TypeError('Invalid end date. ' + new_date.toString());
}
this.options.end_date = new Date(typeof new_date == 'string' ? new_date : new_date.toJSON());
this.options.end_date.setUTCHours(23);
this.options.end_date.setUTCMinutes(59);
this.options.end_date.setUTCSeconds(59);
}
/**
* Mark special days (birthdays, holidays) on the planner
*
* @param {Date} start Start of the month
* @param {number} days How many days in the month
*/
_yearlyPlannerMarkDays(start, days) {
const day_width = 3.23;
const t = new Date(start);
let content = '';
for (let i = 0; i < days; i++) {
const holidays = [];
// TODO: implement this, pull / copy data from et2_widget_timegrid
const day_class = this.getParent().day_class_holiday(t, holidays);
if (day_class) // no regular weekday
{
content += '<div class="calendar_eventRowsMarkedDay ' + day_class +
'" style="left: ' + (i * day_width) + '%; width:' + day_width + '%;"' +
(holidays ? ' title="' + holidays.join(',') + '"' : '') +
' ></div>';
}
t.setUTCDate(t.getUTCDate() + 1);
}
return content;
}
/**
* Callback used when the daywise data changes
*
* Events should update themselves when their data changes, here we are
* dealing with a change in which events are displayed on this row.
*
* @param {String[]} event_ids
* @returns {undefined}
*/
_data_callback(event_ids) {
const events = [];
if (event_ids == null || typeof event_ids.length == 'undefined')
event_ids = [];
for (let i = 0; i < event_ids.length; i++) {
let event = egw.dataGetUIDdata('calendar::' + event_ids[i]);
event = event && event.data || false;
if (event && event.date) {
events.push(event);
}
else if (event) {
// Got an ID that doesn't belong
event_ids.splice(i--, 1);
}
}
if (!this.getParent().disabled && event_ids.length > 0) {
this.resize();
this._update_events(events);
}
}
get date_helper() {
return this._date_helper;
}
/**
* Load the event data for this day and create event widgets for each.
*
* If event information is not provided, it will be pulled from the content array.
*
* @param {Object[]} [events] Array of event information, one per event.
*/
_update_events(events) {
// Remove all events
while (this._children.length > 0) {
const node = this._children[this._children.length - 1];
this.removeChild(node);
node.destroy();
}
this._cached_rows = [];
for (var c = 0; c < events.length; c++) {
// Create event
var event = et2_createWidget('calendar-event', {
id: 'event_' + events[c].row_id,
value: events[c]
}, this);
}
// Seperate loop so column sorting finds all children in the right place
for (var c = 0; c < events.length; c++) {
let event = this.getWidgetById('event_' + events[c].row_id);
if (!event)
continue;
if (this.isInTree()) {
event.doLoadingFinished();
}
}
}
/**
* Position the event according to it's time and how this widget is laid
* out.
*
* @param {undefined|Object|et2_calendar_event} event
*/
position_event(event) {
const rows = this._spread_events();
const height = rows.length * this._row_height;
let row_width = this.rows.width();
if (row_width == 0) {
// Not rendered yet or something
row_width = this.getParent().gridHeader.width() - this.title.width();
}
row_width -= 15;
for (let c = 0; c < rows.length; c++) {
// Calculate vertical positioning
const top = c * (100.0 / rows.length);
for (let i = 0; (rows[c].indexOf(event) >= 0 || !event) && i < rows[c].length; i++) {
// Calculate horizontal positioning
const left = this._time_to_position(rows[c][i].options.value.start);
const width = this._time_to_position(rows[c][i].options.value.end) - left;
// Position the event
rows[c][i].div.css('top', top + '%');
rows[c][i].div.css('height', (100 / rows.length) + '%');
rows[c][i].div.css('left', left.toFixed(1) + '%');
rows[c][i].div.outerWidth((width / 100 * row_width) + 'px');
}
}
if (height) {
this.div.height(height + 'px');
}
}
/**
* Sort a day's events into non-overlapping rows
*
* @returns {Array[]} Events sorted into rows
*/
_spread_events() {
// Keep it so we don't have to re-do it when the next event asks
let cached_length = 0;
this._cached_rows.map(function (row) { cached_length += row.length; });
if (cached_length === this._children.length) {
return this._cached_rows;
}
// sorting the events in non-overlapping rows
const rows = [];
const row_end = [0];
// Sort in chronological order, so earliest ones are at the top
this._children.sort(function (a, b) {
const start = new Date(a.options.value.start) - new Date(b.options.value.start);
const end = new Date(a.options.value.end) - new Date(b.options.value.end);
// Whole day events sorted by ID, normal events by start / end time
if (a.options.value.whole_day && b.options.value.whole_day) {
// Longer duration comes first so we have nicer bars across the top
const duration = (new Date(b.options.value.end) - new Date(b.options.value.start)) -
(new Date(a.options.value.end) - new Date(a.options.value.start));
return duration ? duration : (a.options.value.app_id - b.options.value.app_id);
}
else if (a.options.value.whole_day || b.options.value.whole_day) {
return a.options.value.whole_day ? -1 : 1;
}
return start ? start : end;
});
for (let n = 0; n < this._children.length; n++) {
const event = this._children[n].options.value || false;
if (typeof event.start !== 'object') {
this._date_helper.set_value(event.start);
event.start = new Date(this._date_helper.getValue());
}
if (typeof event.end !== 'object') {
this._date_helper.set_value(event.end);
event.end = new Date(this._date_helper.getValue());
}
if (typeof event['start_m'] === 'undefined') {
let day_start = event.start.valueOf() / 1000;
const dst_check = new Date(event.start);
dst_check.setUTCHours(12);
// if daylight saving is switched on or off, correct $day_start
// gives correct times after 2am, times between 0am and 2am are wrong
const daylight_diff = day_start + 12 * 60 * 60 - (dst_check.valueOf() / 1000);
if (daylight_diff) {
day_start -= daylight_diff;
}
event['start_m'] = event.start.getUTCHours() * 60 + event.start.getUTCMinutes();
if (event['start_m'] < 0) {
event['start_m'] = 0;
event['multiday'] = true;
}
event['end_m'] = event.end.getUTCHours() * 60 + event.end.getUTCMinutes();
if (event['end_m'] >= 24 * 60) {
event['end_m'] = 24 * 60 - 1;
event['multiday'] = true;
}
if (!event.start.getUTCHours() && !event.start.getUTCMinutes() && event.end.getUTCHours() == 23 && event.end.getUTCMinutes() == 59) {
event.whole_day_on_top = (event.non_blocking && event.non_blocking != '0');
}
}
// Skip events entirely on hidden weekends
if (this._hidden_weekend_event(event)) {
const node = this._children[n];
this.removeChild(n--);
node.destroy();
continue;
}
const event_start = new Date(event.start).valueOf();
for (var row = 0; row_end[row] > event_start; ++row)
; // find a "free" row (no other event)
if (typeof rows[row] === 'undefined')
rows[row] = [];
rows[row].push(this._children[n]);
row_end[row] = new Date(event['end']).valueOf();
}
this._cached_rows = rows;
return rows;
}
/**
* Check to see if the event is entirely on a hidden weekend
*
* @param values Array of event values, not an et2_widget_event
*/
_hidden_weekend_event(values) {
if (!this.getParent() || this.getParent().options.group_by == 'month' || this.getParent().options.show_weekend) {
return false;
}
// Starts on Saturday or Sunday, ends Sat or Sun, less than 2 days long
else if ([0, 6].indexOf(values.start.getUTCDay()) !== -1 && [0, 6].indexOf(values.end.getUTCDay()) !== -1
&& values.end - values.start < 2 * 24 * 3600 * 1000) {
return true;
}
return false;
}
/**
* Calculates the horizontal position based on the time given, as a percentage
* between the start and end times
*
* @param {int|Date|string} time in minutes from midnight, or a Date in string or object form
* @param {int|Date|string} start Earliest possible time (0%)
* @param {int|Date|string} end Latest possible time (100%)
* @return {float} position in percent
*/
_time_to_position(time, start, end) {
let pos = 0.0;
// Handle the different value types
start = this.options.start_date;
end = this.options.end_date;
if (typeof start === 'string') {
start = new Date(start);
end = new Date(end);
}
const wd_start = 60 * (parseInt('' + egw.preference('workdaystarts', 'calendar')) || 9);
const wd_end = 60 * (parseInt('' + egw.preference('workdayends', 'calendar')) || 17);
let t = time;
if (typeof time === 'number' && time < 3600) {
t = new Date(start.valueOf() + wd_start * 3600 * 1000);
}
else {
t = new Date(time);
}
// Limits
if (t <= start)
return 0; // We are left of our scale
if (t >= end)
return 100; // We are right of our scale
// Remove space for weekends, if hidden
let weekend_count = 0;
let weekend_before = 0;
let partial_weekend = 0;
if (this.getParent().options.group_by !== 'month' && this.getParent() && !this.getParent().options.show_weekend) {
const counter_date = new Date(start);
do {
if ([0, 6].indexOf(counter_date.getUTCDay()) !== -1) {
if (counter_date.getUTCDate() === t.getUTCDate() && counter_date.getUTCMonth() === t.getUTCMonth()) {
// Event is partially on a weekend
partial_weekend += (t.getUTCHours() * 60 + t.getUTCMinutes()) * 60 * 1000;
}
else if (counter_date < t) {
weekend_before++;
}
weekend_count++;
}
counter_date.setUTCDate(counter_date.getUTCDate() + 1);
} while (counter_date < end);
// Put it in ms
weekend_before *= 24 * 3600 * 1000;
weekend_count *= 24 * 3600 * 1000;
}
// Basic scaling, doesn't consider working times
pos = (t - start - weekend_before - partial_weekend) / (end - start - weekend_count);
// Month view
if (this.getParent().options.group_by !== 'month') {
// Daywise scaling
/* Needs hourly scales that consider working hours
var start_date = new Date(start.getUTCFullYear(), start.getUTCMonth(),start.getUTCDate());
var end_date = new Date(end.getUTCFullYear(), end.getUTCMonth(),end.getUTCDate());
var t_date = new Date(t.getUTCFullYear(), t.getUTCMonth(),t.getUTCDate());
var days = Math.round((end_date - start_date) / (24 * 3600 * 1000))+1;
pos = 1 / days * Math.round((t_date - start_date) / (24*3600 * 1000));
var time_of_day = typeof t === 'object' ? 60 * t.getUTCHours() + t.getUTCMinutes() : t;
if (time_of_day >= wd_start)
{
var day_percentage = 0.1;
if (time_of_day > wd_end)
{
day_percentage = 1;
}
else
{
var wd_length = wd_end - wd_start;
if (wd_length <= 0) wd_length = 24*60;
day_percentage = (time_of_day-wd_start) / wd_length; // between 0 and 1
}
pos += day_percentage / days;
}
*/
}
else {
// 2678400 is the number of seconds in 31 days
pos = (t - start) / 2678400000;
}
pos = 100 * pos;
return pos;
}
// Resizable interface
/**
* Resize
*
* Parent takes care of setting proper width & height for the containing div
* here we just need to adjust the events to fit the new size.
*/
resize() {
if (this.disabled || !this.div.is(':visible') || this.getParent().disabled) {
return;
}
const row = jQuery('<div class="calendar_plannerEventRowWidget"></div>').appendTo(this.rows);
this._row_height = (parseInt(window.getComputedStyle(row[0]).getPropertyValue("height")) || 20);
row.remove();
// Resize & position all events
this.position_event();
}
}
et2_calendar_planner_row._attributes = {
start_date: {
name: "Start date",
type: "any"
},
end_date: {
name: "End date",
type: "any"
},
value: {
type: "any"
}
};
et2_register_widget(et2_calendar_planner_row, ["calendar-planner_row"]);
//# sourceMappingURL=et2_widget_planner_row.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,590 +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
*/
/*egw:uses
/etemplate/js/et2_core_valueWidget;
*/
import { et2_createWidget } from "../../api/js/etemplate/et2_core_widget";
import { et2_valueWidget } from "../../api/js/etemplate/et2_core_valueWidget";
import { ClassWithAttributes } from "../../api/js/etemplate/et2_core_inheritance";
import { sprintf } from "../../api/js/egw_action/egw_action_common.js";
/**
* Parent class for the various calendar views to reduce copied code
*
*
* et2_calendar_view is responsible for its own loader div, which is displayed while
* the times & days are redrawn.
*
* @augments et2_valueWidget
*/
export class et2_calendar_view extends et2_valueWidget {
/**
* Constructor
*
*/
constructor(_parent, _attrs, _child) {
// Call the inherited constructor
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_calendar_view._attributes, _child || {}));
this.dataStorePrefix = 'calendar';
this.update_timer = null;
this.now_timer = null;
// Used for its date calculations
this._date_helper = et2_createWidget('date-time', {}, null);
this._date_helper.loadingFinished();
this.loader = jQuery('<div class="egw-loading-prompt-container ui-front loading"></div>');
this.now_div = jQuery('<div class="calendar_now"/>');
this.update_timer = null;
this.now_timer = null;
// Used to support dragging on empty space to create an event
this.drag_create = {
start: null,
end: null,
parent: null,
event: null
};
}
destroy() {
super.destroy();
// date_helper has no parent, so we must explicitly remove it
this._date_helper.destroy();
this._date_helper = null;
// Stop the invalidate timer
if (this.update_timer) {
window.clearTimeout(this.update_timer);
}
// Stop the 'now' line
if (this.now_timer) {
window.clearInterval(this.now_timer);
}
}
doLoadingFinished() {
super.doLoadingFinished();
this.loader.hide(0).prependTo(this.div);
this.div.append(this.now_div);
if (this.options.owner)
this.set_owner(this.options.owner);
// Start moving 'now' line
this.now_timer = window.setInterval(this._updateNow.bind(this), 60000);
return true;
}
/**
* Something changed, and the view need to be re-drawn. We wait a bit to
* avoid re-drawing twice if start and end date both changed, then recreate
* as needed.
*
* @param {boolean} [trigger_event=false] Trigger an event once things are done.
* Waiting until invalidate completes prevents 2 updates when changing the date range.
* @returns {undefined}
*
* @memberOf et2_calendar_view
*/
invalidate(trigger_event) {
// If this wasn't a stub, we'd set this.update_timer
}
/**
* Returns the current start date
*
* @returns {Date}
*
* @memberOf et2_calendar_view
*/
get_start_date() {
return new Date(this.options.start_date);
}
/**
* Returns the current start date
*
* @returns {Date}
*
* @memberOf et2_calendar_view
*/
get_end_date() {
return new Date(this.options.end_date);
}
/**
* Change the start date
*
* Changing the start date will invalidate the display, and it will be redrawn
* after a timeout.
*
* @param {string|number|Date} new_date New starting date. Strings can be in
* any format understood by et2_widget_date, or Ymd (eg: 20160101).
* @returns {undefined}
*
* @memberOf et2_calendar_view
*/
set_start_date(new_date) {
if (!new_date || new_date === null) {
new_date = new Date();
}
// Use date widget's existing functions to deal
if (typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8) {
this._date_helper.set_value(new_date);
}
else if (typeof new_date === "string") {
this._date_helper.set_year(new_date.substring(0, 4));
// Avoid overflow into next month, since we re-use date_helper
this._date_helper.set_date(1);
this._date_helper.set_month(new_date.substring(4, 6));
this._date_helper.set_date(new_date.substring(6, 8));
}
var old_date = this.options.start_date;
this.options.start_date = new Date(this._date_helper.getValue());
if (old_date !== this.options.start_date && this.isAttached()) {
this.invalidate(true);
}
}
/**
* Change the end date
*
* Changing the end date will invalidate the display, and it will be redrawn
* after a timeout.
*
* @param {string|number|Date} new_date - New end date. Strings can be in
* any format understood by et2_widget_date, or Ymd (eg: 20160101).
* @returns {undefined}
*
* @memberOf et2_calendar_view
*/
set_end_date(new_date) {
if (!new_date || new_date === null) {
new_date = new Date();
}
// Use date widget's existing functions to deal
if (typeof new_date === "object" || typeof new_date === "string" && new_date.length > 8) {
this._date_helper.set_value(new_date);
}
else if (typeof new_date === "string") {
this._date_helper.set_year(new_date.substring(0, 4));
// Avoid overflow into next month, since we re-use date_helper
this._date_helper.set_date(1);
this._date_helper.set_month(new_date.substring(4, 6));
this._date_helper.set_date(new_date.substring(6, 8));
}
var old_date = this.options.end_date;
this.options.end_date = new Date(this._date_helper.getValue());
if (old_date !== this.options.end_date && this.isAttached()) {
this.invalidate(true);
}
}
/**
* Set which users to display
*
* Changing the owner will invalidate the display, and it will be redrawn
* after a timeout.
*
* @param {number|number[]|string|string[]} _owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
*
* @memberOf et2_calendar_view
*/
set_owner(_owner) {
var old = this.options.owner;
// 0 means current user, but that causes problems for comparison,
// so we'll just switch to the actual ID
if (_owner == '0') {
_owner = [egw.user('account_id')];
}
if (!jQuery.isArray(_owner)) {
if (typeof _owner === "string") {
_owner = _owner.split(',');
}
else {
_owner = [_owner];
}
}
else {
_owner = jQuery.extend([], _owner);
}
this.options.owner = _owner;
if (this.isAttached() && (typeof old === "number" && typeof _owner === "number" && old !== this.options.owner ||
// Array of ids will not compare as equal
((typeof old === 'object' || typeof _owner === 'object') && old.toString() !== _owner.toString()) ||
// Strings
typeof old === 'string' && '' + old !== '' + this.options.owner)) {
this.invalidate(true);
}
}
/**
* Provide specific data to be displayed.
* This is a way to set start and end dates, owner and event data in one call.
*
* If events are not provided in the array,
* @param {Object[]} events Array of events, indexed by date in Ymd format:
* {
* 20150501: [...],
* 20150502: [...]
* }
* Days should be in order.
* {string|number|Date} events.start_date - New start date
* {string|number|Date} events.end_date - New end date
* {number|number[]|string|string[]} event.owner - Owner ID, which can
* be an account ID, a resource ID (as defined in calendar_bo, not
* necessarily an entry from the resource app), or a list containing a
* combination of both.
*/
set_value(events) {
if (typeof events !== 'object')
return false;
if (events.length && events.length > 0 || !jQuery.isEmptyObject(events)) {
this.set_disabled(false);
}
if (events.id) {
this.set_id(events.id);
delete events.id;
}
if (events.start_date) {
this.set_start_date(events.start_date);
delete events.start_date;
}
if (events.end_date) {
this.set_end_date(events.end_date);
delete events.end_date;
}
// set_owner() wants start_date set to get the correct week number
// for the corner label
if (events.owner) {
this.set_owner(events.owner);
delete events.owner;
}
this.value = events || {};
// None of the above changed anything, hide the loader
if (!this.update_timer) {
window.setTimeout(jQuery.proxy(function () { this.loader.hide(); }, this), 200);
}
}
get date_helper() {
return this._date_helper;
}
_createNamespace() {
return true;
}
/**
* Update the 'now' line
*
* Here we just do some limit checks and return the current date/time.
* Extending widgets should handle position.
*
* @private
*/
_updateNow() {
var tempDate = new Date();
var now = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate(), tempDate.getHours(), tempDate.getMinutes() - tempDate.getTimezoneOffset(), 0);
// Use date widget's existing functions to deal
this._date_helper.set_value(now.toJSON());
now = new Date(this._date_helper.getValue());
if (this.get_start_date() <= now && this.get_end_date() >= now) {
return now;
}
this.now_div.hide();
return false;
}
/**
* Calendar supports many different owner types, including users & resources.
* This translates an ID to a user-friendly name.
*
* @param {string} user
* @returns {string}
*
* @memberOf et2_calendar_view
*/
_get_owner_name(user) {
var label = undefined;
if (parseInt(user) === 0) {
// 0 means current user
user = egw.user('account_id');
}
if (et2_calendar_view.owner_name_cache[user]) {
return et2_calendar_view.owner_name_cache[user];
}
if (!isNaN(user)) {
user = parseInt(user);
var accounts = egw.accounts('both');
for (var j = 0; j < accounts.length; j++) {
if (accounts[j].value === user) {
label = accounts[j].label;
break;
}
}
}
if (typeof label === 'undefined') {
// Not found? Ask the sidebox owner widget (it gets updated) or the original arrayMgr
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();
}
else {
options = this.getArrayMgr("sel_options").getRoot().getEntry('owner');
}
if (options && options.find) {
var found = options.find(function (element) { return element.id == user; }) || {};
if (found && found.label && found.label !== user) {
label = found.label;
}
}
if (!label) {
// No sidebox? Must be in home or sitemgr (no caching) - ask directly
label = '?';
egw.jsonq('calendar_owner_etemplate_widget::ajax_owner', user, function (data) {
et2_calendar_view.owner_name_cache[user] = data;
this.invalidate(true);
// Set owner to make sure labels get set
if (this.owner && typeof this.owner.set_value === 'function') {
this.owner.set_value(data);
}
}.bind(this), this);
}
}
if (label) {
et2_calendar_view.owner_name_cache[user] = label;
}
return label;
}
/**
* Find the event information linked to a given DOM node
*
* @param {HTMLElement} dom_node - It should have something to do with an event
* @returns {Object}
*/
_get_event_info(dom_node) {
// Determine as much relevant info as can be found
var event_node = jQuery(dom_node).closest('[data-id]', this.div)[0];
var day_node = jQuery(event_node).closest('[data-date]', this.div)[0];
var result = jQuery.extend({
event_node: event_node,
day_node: day_node
}, event_node ? event_node.dataset : {}, day_node ? day_node.dataset : {});
// Widget ID should be the DOM node ID without the event_ prefix
if (event_node && event_node.id) {
var widget_id = event_node.id || '';
widget_id = widget_id.split('event_');
widget_id.shift();
result.widget_id = 'event_' + widget_id.join('');
}
return result;
}
/**
* Starting (mousedown) handler to support drag to create
*
* Extending classes need to set this.drag_create.parent, which is the
* parent container (child of extending class) that will directly hold the
* event.
*
* @param {String} start Date string (JSON format)
*/
_drag_create_start(start) {
this.drag_create.start = jQuery.extend({}, start);
if (!this.drag_create.start.date) {
this.drag_create.start = null;
}
this.drag_create.end = start;
// Clear some stuff, if last time did not complete
if (this.drag_create.event) {
if (this.drag_create.event.destroy) {
this.drag_create.event.destroy();
}
this.drag_create.event = null;
}
// Wait a bit before adding an "event", it may be just a click
window.setTimeout(jQuery.proxy(function () {
// Create event
this._drag_create_event();
}, this), 250);
}
/**
* Create or update an event used for feedback while dragging on empty space,
* so user can see something is happening
*/
_drag_create_event() {
if (!this.drag_create.parent || !this.drag_create.start) {
return;
}
if (!this.drag_create.event) {
this._date_helper.set_value(this.drag_create.start.date);
var value = jQuery.extend({}, this.drag_create.start, this.drag_create.end, {
start: this.drag_create.start.date,
end: this.drag_create.end && this.drag_create.end.date || this.drag_create.start.date,
date: "" + this._date_helper.get_year() +
sprintf("%02d", this._date_helper.get_month()) +
sprintf("%02d", this._date_helper.get_date()),
title: '',
description: '',
owner: this.options.owner,
participants: this.options.owner,
app: 'calendar',
whole_day_on_top: this.drag_create.start.whole_day
});
this.drag_create.event = et2_createWidget('calendar-event', {
id: 'event_drag',
value: value
}, this.drag_create.parent);
this.drag_create.event._values_check(value);
this.drag_create.event.doLoadingFinished();
}
}
_drag_update_event() {
if (!this.drag_create.event || !this.drag_create.start || !this.drag_create.end
|| !this.drag_create.parent || !this.drag_create.event._type) {
return;
}
else if (this.drag_create.end) {
this.drag_create.event.options.value.end = this.drag_create.end.date;
this.drag_create.event._values_check(this.drag_create.event.options.value);
}
this.drag_create.event._update();
this.drag_create.parent.position_event(this.drag_create.event);
}
/**
* Ending (mouseup) handler to support drag to create
*
* @param {String} end Date string (JSON format)
*/
_drag_create_end(end) {
this.div.css('cursor', '');
if (typeof end === 'undefined') {
end = {};
}
if (this.drag_create.start && end.date &&
JSON.stringify(this.drag_create.start.date) !== JSON.stringify(end.date)) {
// Drag from start to end, open dialog
var options = {
start: this.drag_create.start.date < end.date ? this.drag_create.start.date : end.date,
end: this.drag_create.start.date < end.date ? end.date : this.drag_create.start.date
};
// Whole day needs to go from 00:00 to 23:59
if (end.whole_day || this.drag_create.start.whole_day) {
var start = new Date(options.start);
start.setUTCHours(0);
start.setUTCMinutes(0);
options.start = start.toJSON();
var end = new Date(options.end);
end.setUTCHours(23);
end.setUTCMinutes(59);
options.end = end.toJSON();
}
// Add anything else that was set, but not date
jQuery.extend(options, this.drag_create.start, end);
delete (options.date);
// Make sure parent is set, if needed
let app_calendar = this.getInstanceManager().app_obj.calendar || app.calendar;
if (this.drag_create.parent && this.drag_create.parent.options.owner !== app_calendar.state.owner && !options.owner) {
options.owner = this.drag_create.parent.options.owner;
}
// Remove empties
for (var key in options) {
if (!options[key])
delete options[key];
}
app.calendar.add(options, this.drag_create.event);
// Wait a bit, having these stops the click
window.setTimeout(jQuery.proxy(function () {
this.drag_create.start = null;
this.drag_create.end = null;
this.drag_create.parent = null;
if (this.drag_create.event) {
this.drag_create.event = null;
}
}, this), 100);
return false;
}
this.drag_create.start = null;
this.drag_create.end = null;
this.drag_create.parent = null;
if (this.drag_create.event) {
try {
if (this.drag_create.event.destroy) {
this.drag_create.event.destroy();
}
}
catch (e) { }
this.drag_create.event = null;
}
return true;
}
/**
* Check if the view should be consolidated into one, or listed seperately
* based on the user's preferences
*
* @param {string[]} owners List of owners
* @param {string} view Name of current view (day, week)
* @returns {boolean} True of only one is needed, false if each owner needs
* to be listed seperately.
*/
static is_consolidated(owners, view) {
// Seperate owners, or consolidated?
return !(owners.length > 1 &&
(view === 'day' && owners.length < parseInt('' + egw.preference('day_consolidate', 'calendar')) ||
view === 'week' && owners.length < parseInt('' + egw.preference('week_consolidate', 'calendar'))));
}
/**
* Fetch and cache a list of the year's holidays
*
* @param {et2_calendar_timegrid} widget
* @param {string|numeric} year
* @returns {Array}
*/
static get_holidays(widget, year) {
// Loaded in an iframe or something
var view = egw.window.et2_calendar_view ? egw.window.et2_calendar_view : this;
// No country selected causes error, so skip if it's missing
if (!view || !egw.preference('country', 'common'))
return {};
var cache = view.holiday_cache[year];
if (typeof cache == 'undefined') {
// Fetch with json instead of jsonq because there may be more than
// one widget listening for the response by the time it gets back,
// and we can't do that when it's queued.
view.holiday_cache[year] = jQuery.getJSON(egw.link('/calendar/holidays.php', { year: year }));
}
cache = view.holiday_cache[year];
if (typeof cache.done == 'function') {
// pending, wait for it
cache.done(jQuery.proxy(function (response) {
view.holiday_cache[this.year] = response || undefined;
egw.window.setTimeout(jQuery.proxy(function () {
// Make sure widget hasn't been destroyed while we wait
if (typeof this.widget.free == 'undefined') {
this.widget.day_class_holiday();
}
}, this), 1);
}, { widget: widget, year: year }))
.fail(jQuery.proxy(function () {
view.holiday_cache[this.year] = undefined;
}, { widget: widget, year: year }));
return {};
}
else {
return cache;
}
}
}
et2_calendar_view._attributes = {
owner: {
name: "Owner",
type: "any",
default: [egw.user('account_id')],
description: "Account ID number of the calendar owner, if not the current user"
},
start_date: {
name: "Start date",
type: "any"
},
end_date: {
name: "End date",
type: "any"
}
};
/**
* Cache to map owner & resource IDs to names, helps cut down on server requests
*/
et2_calendar_view.owner_name_cache = {};
et2_calendar_view.holiday_cache = {};
//# sourceMappingURL=et2_widget_view.js.map

File diff suppressed because it is too large Load Diff

View File

@ -1,165 +0,0 @@
/**
* EGroupware - Import/Export - Javascript UI
*
* @link http://www.egroupware.org
* @package importexport
* @author Nathan Gray
* @copyright (c) 2013 Nathan Gray
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
* @version $Id$
*/
import 'jquery';
import 'jqueryui';
import '../jsapi/egw_global';
import '../etemplate/et2_types';
import { EgwApp } from '../../api/js/jsapi/egw_app';
import { egw } from "../../api/js/jsapi/egw_global";
/**
* JS for Import/Export
*
* @augments AppJS
*/
class ImportExportApp extends EgwApp {
/**
* Constructor
*
* @memberOf app.infolog
*/
constructor() {
// call parent
super('importexport');
}
/**
* Destructor
*/
destroy(_app) {
// call parent
super.destroy(_app);
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*
* @param {etemplate2} _et2 newly ready object
* @param {string} _name template name
*/
et2_ready(_et2, _name) {
// call parent
super.et2_ready(_et2, _name);
if (this.et2.getWidgetById('export')) {
if (!this.et2.getArrayMgr("content").getEntry("definition")) {
// et2 doesn't understand a disabled button in the normal sense
jQuery(this.et2.getDOMWidgetById('export').getDOMNode()).attr('disabled', 'disabled');
jQuery(this.et2.getDOMWidgetById('preview').getDOMNode()).attr('disabled', 'disabled');
}
if (!this.et2.getArrayMgr("content").getEntry("filter")) {
jQuery('input[value="filter"]').parent().hide();
}
// Disable / hide definition filter if not selected
if (this.et2.getArrayMgr("content").getEntry("selection") != 'filter') {
jQuery('div.filters').hide();
}
}
}
/**
* Callback to download the file without destroying the etemplate request
*
* @param data URL to get the export file
*/
download(data) {
// Try to get the file to download in the parent window
let app_templates = this.egw.top.etemplate2.getByApplication(framework.activeApp.appName);
if (app_templates.length > 0) {
app_templates[0].download(data);
}
else {
// Couldn't download in opener, download here before popup closes
this.et2.getInstanceManager().download(data);
}
}
export_preview(event, widget) {
var preview = jQuery(widget.getRoot().getWidgetById('preview_box').getDOMNode());
jQuery('.content', preview).empty()
.append('<div class="loading" style="width:100%;height:100%"></div>');
preview
.show(100, jQuery.proxy(function () {
widget.clicked = true;
widget.getInstanceManager().submit(false, true);
widget.clicked = false;
}, this));
return false;
}
import_preview(event, widget) {
var test = widget.getRoot().getWidgetById('dry-run');
if (test.getValue() == test.options.unselected_value)
return true;
// Show preview
var preview = jQuery(widget.getRoot().getWidgetById('preview_box').getDOMNode());
jQuery('.content', preview).empty();
preview
.addClass('loading')
.show(100, jQuery.proxy(function () {
widget.clicked = true;
widget.getInstanceManager().submit(false, true);
widget.clicked = false;
jQuery(widget.getRoot().getWidgetById('preview_box').getDOMNode())
.removeClass('loading');
}, this));
return false;
}
/**
* Open a popup to run a given definition
*
* @param {egwAction} action
* @param {egwActionObject[]} selected
*/
run_definition(action, selected) {
if (!selected || selected.length != 1)
return;
var id = selected[0].id || null;
var data = egw.dataGetUIDdata(id).data;
if (!data || !data.type)
return;
egw.open_link(egw.link('/index.php', {
menuaction: 'importexport.importexport_' + data.type + '_ui.' + data.type + '_dialog',
appname: data.application,
definition: data.definition_id
}), "", '850x440', data.application);
}
/**
* Allowed users widget has been changed, if 'All users' or 'Just me'
* was selected, turn off any other options.
*/
allowed_users_change(node, widget) {
var value = widget.getValue();
// Only 1 selected, no checking needed
if (value == null || value.length <= 1)
return;
// Don't jump it to the top, it's weird
widget.selected_first = false;
var index = null;
var specials = ['', 'all'];
for (var i = 0; i < specials.length; i++) {
var special = specials[i];
if ((index = value.indexOf(special)) >= 0) {
if (window.event.target.value == special) {
// Just clicked all/private, clear the others
value = [special];
}
else {
// Just added another, clear special
value.splice(index, 1);
}
// A little highlight to call attention to the change
jQuery('input[value="' + special + '"]', node).parent().parent().effect('highlight', {}, 500);
break;
}
}
if (index >= 0) {
widget.set_value(value);
}
}
}
app.classes.importexport = ImportExportApp;
//# sourceMappingURL=app.js.map

View File

@ -1,870 +0,0 @@
import { E as EgwApp, e as egw, a as etemplate2, B as nm_open_popup } from '../../chunks/etemplate2-0eb045cf.js';
import { C as CRMView } from '../../chunks/CRM-49d7b139.js';
import '../../chunks/egw_dragdrop_dhtmlx_tree-31643465.js';
import '../../chunks/egw-5f30b5ae.js';
import '../../vendor/bower-asset/jquery/dist/jquery.min.js';
import '../../vendor/bower-asset/jquery-ui/jquery-ui.js';
import '../../chunks/egw_json-98998d7e.js';
import '../../chunks/egw_core-0ec5dc11.js';
import '../../vendor/tinymce/tinymce/tinymce.min.js';
/**
* EGroupware - Infolog - Javascript UI
*
* @link: https://www.egroupware.org
* @package infolog
* @author Hadi Nategh <hn-AT-stylite.de>
* @copyright (c) 2008-13 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
/**
* UI for Infolog
*
* @augments AppJS
*/
class InfologApp extends EgwApp {
// These fields help with push filtering & access control to see if we care about a push message
push_grant_fields = ["info_owner", "info_responsible"];
push_filter_fields = ["info_owner", "info_responsible"];
/**
* Constructor
*
* @memberOf app.infolog
*/
constructor() {
// call parent
super('infolog');
}
/**
* Destructor
*/
destroy(_app) {
// call parent
super.destroy(_app);
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*
* @param {etemplate2} _et2 newly ready object
* @param {string} _name template name
*/
et2_ready(_et2, _name) {
// call parent
super.et2_ready(_et2, _name); // CRM View
if (typeof CRMView !== "undefined") {
CRMView.view_ready(_et2, this);
}
switch (_name) {
case 'infolog.index':
this.filter_change(); // Show / hide descriptions according to details filter
var nm = this.et2.getWidgetById('nm');
var filter2 = nm.getWidgetById('filter2');
this.show_details(filter2.get_value() == 'all', nm.getDOMNode(nm)); // Remove the rule added by show_details() if the template is removed
jQuery(_et2.DOMContainer).on('clear', jQuery.proxy(function () {
egw.css(this);
}, '#' + nm.getDOMNode(nm).id + ' .et2_box.infoDes')); // Enable decrypt on hover
if (this.egw.user('apps').stylite) {
this._get_stylite(function () {
this.mailvelopeAvailable(function () {
app.stylite?.decrypt_hover(nm);
});
});
} // blur count, if limit modified optimization used
if (nm.getController()?.getTotalCount() === 9999) {
this.blurCount(true);
}
break;
case 'infolog.edit.print':
if (this.et2.getArrayMgr('content').data.info_des.indexOf(this.begin_pgp_message) != -1) {
this.mailvelopeAvailable(this.printEncrypt);
} else {
// Trigger print command if the infolog oppend for printing purpose
this.infolog_print_preview_onload();
}
break;
case 'infolog.edit':
if (this.et2.getArrayMgr('content').data.info_des && this.et2.getArrayMgr('content').data.info_des.indexOf(this.begin_pgp_message) != -1) {
this._get_stylite(jQuery.proxy(function () {
this.mailvelopeAvailable(jQuery.proxy(function () {
this.toggleEncrypt(); // Decrypt history on hover
var history = this.et2.getWidgetById('history');
app.stylite.decrypt_hover(history, 'span');
jQuery(history.getDOMNode(history)).tooltip('option', 'position', {
my: 'top left',
at: 'top left',
of: history.getDOMNode(history)
});
}, this));
}, this)); // This disables the diff in history
var history = this.et2.getArrayMgr('content').getEntry('history');
history['status-widgets'].De = 'description';
}
break;
}
}
/**
* Observer method receives update notifications from all applications
*
* InfoLog currently reacts to timesheet updates, as it might show time-sums.
* @todo only trigger update, if times are shown
*
* @param {string} _msg message (already translated) to show, eg. 'Entry deleted'
* @param {string} _app application name
* @param {(string|number)} _id id of entry to refresh or null
* @param {string} _type either 'update', 'edit', 'delete', 'add' or null
* - update: request just modified data from given rows. Sorting is not considered,
* so if the sort field is changed, the row will not be moved.
* - edit: rows changed, but sorting may be affected. Requires full reload.
* - delete: just delete the given rows clientside (no server interaction neccessary)
* - add: requires full reload for proper sorting
* @param {string} _msg_type 'error', 'warning' or 'success' (default)
* @param {object|null} _links app => array of ids of linked entries
* or null, if not triggered on server-side, which adds that info
*/
observer(_msg, _app, _id, _type, _msg_type, _links) {
if (typeof _links != 'undefined') {
if (typeof _links.infolog != 'undefined') {
switch (_app) {
case 'timesheet':
var nm = this.et2 ? this.et2.getWidgetById('nm') : null;
if (nm) nm.applyFilters();
break;
}
}
} // Refresh handler for Addressbook CRM view
if (_app == 'infolog' && this.et2.getInstanceManager() && this.et2.getInstanceManager().app == 'addressbook' && this.et2.getInstanceManager().name == 'infolog.index') {
this.et2._inst.refresh(_msg, _app, _id, _type);
}
}
/**
* Retrieve the current state of the application for future restoration
*
* Reimplemented to add action/action_id from content set by server
* when eg. viewing infologs linked to contacts.
*
* @return {object} Application specific map representing the current state
*/
getState() {
let state = {
action: null,
action_id: null
};
let nm = {}; // Get index etemplate
var et2 = etemplate2.getById('infolog-index');
if (et2) {
state = et2.widgetContainer.getWidgetById("nm").getValue();
let content = et2.widgetContainer.getArrayMgr('content');
nm = content && content.data && content.data.nm ? content.data.nm : {};
}
state.action = nm.action || null;
state.action_id = nm.action_id || null;
return state;
}
/**
* Set the application's state to the given state.
*
* Reimplemented to also reset action/action_id.
*
* @param {{name: string, state: object}|string} state Object (or JSON string) for a state.
* Only state is required, and its contents are application specific.
*
* @return {boolean} false - Returns false to stop event propagation
*/
setState(state) {
// as we have to set state.state.action, we have to set all other
// for "No filter" favorite to work as expected
var to_set = {
col_filter: null,
filter: '',
filter2: '',
cat_id: '',
search: '',
action: null
};
if (typeof state.state == 'undefined') state.state = {};
for (var name in to_set) {
if (typeof state.state[name] == 'undefined') state.state[name] = to_set[name];
}
return super.setState(state);
}
/**
* Enable or disable the date filter
*
* If the filter is set to something that needs dates, we enable the
* header_left template. Otherwise, it is disabled.
*/
filter_change() {
var filter = this.et2.getWidgetById('filter');
var nm = this.et2.getWidgetById('nm');
var dates = this.et2.getWidgetById('infolog.index.dates');
if (nm && filter) {
switch (filter.getValue()) {
case 'bydate':
case 'duedate':
if (filter && dates) {
dates.set_disabled(false);
window.setTimeout(function () {
jQuery(dates.getWidgetById('startdate').getDOMNode()).find('input').focus();
}, 0);
}
break;
default:
if (dates) {
dates.set_disabled(true);
}
break;
}
}
}
/**
* show or hide the details of rows by selecting the filter2 option
* either 'all' for details or 'no_description' for no details
*
* @param {Event} event Change event
* @param {et2_nextmatch} nm The nextmatch widget that owns the filter
*/
filter2_change(event, nm) {
var filter2 = nm.getWidgetById('filter2');
if (nm && filter2) {
// Show / hide descriptions
this.show_details(filter2.get_value() === 'all', nm.getDOMNode(nm));
} // Only change columns for a real user event, to avoid interfering with
// favorites
if (nm && filter2 && !nm.update_in_progress) {
// Store selection as implicit preference
egw.set_preference('infolog', nm.options.settings.columnselection_pref.replace('-details', '') + '-details-pref', filter2.get_value()); // Change preference location - widget is nextmatch
nm.options.settings.columnselection_pref = nm.options.settings.columnselection_pref.replace('-details', '') + (filter2.get_value() == 'all' ? '-details' : ''); // Load new preferences
var colData = nm.columns.slice();
for (var i = 0; i < nm.columns.length; i++) colData[i].visible = false;
if (egw.preference(nm.options.settings.columnselection_pref, 'infolog')) {
nm.set_columns(egw.preference(nm.options.settings.columnselection_pref, 'infolog').split(','));
}
nm._applyUserPreferences(nm.columns, colData); // Now apply them to columns
for (var i = 0; i < colData.length; i++) {
nm.dataview.getColumnMgr().columns[i].set_width(colData[i].width);
nm.dataview.getColumnMgr().columns[i].set_visibility(colData[i].visible);
}
nm.dataview.getColumnMgr().updated(); // Update page - set update_in_progress to true to avoid triggering
// the change handler and looping if the user has a custom field
// column change
var in_progress = nm.update_in_progress;
nm.update_in_progress = true; // Set the actual filter value here
nm.activeFilters.filter2 = filter2.get_value();
nm.dataview.updateColumns();
nm.update_in_progress = in_progress;
}
return false;
}
/**
* Show or hide details by changing the CSS class
*
* @param {boolean} show
* @param {DOMNode} dom_node
*/
show_details(show, dom_node) {
// Show / hide descriptions
egw.css((dom_node && dom_node.id ? "#" + dom_node.id + ' ' : '') + ".et2_box.infoDes", "display:" + (show ? "block;" : "none;"));
if (egwIsMobile()) {
var $select = jQuery('.infoDetails');
show ? $select.each(function (i, e) {
jQuery(e).hide();
}) : $select.each(function (i, e) {
jQuery(e).show();
});
}
}
/**
* Confirm delete
* If entry has children, asks if you want to delete children too
*
*@param _action
*@param _senders
*/
confirm_delete(_action, _senders) {
let children = false;
let child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0);
this._action_all = _action.parent.data.nextmatch?.getSelection().all;
this._action_ids = [];
if (child_button) {
for (let i = 0; i < _senders.length; i++) {
this._action_ids.push(_senders[i].id.split("::").pop());
if (jQuery(_senders[i].iface.getDOMNode()).hasClass('infolog_rowHasSubs')) {
children = true;
break;
}
}
child_button.style.display = children ? 'block' : 'none';
}
nm_open_popup(_action, _senders);
}
_action_ids = [];
_action_all = false;
/**
* Callback for action using ids set(!) in this._action_ids and this._action_all
*
* @param _action
*/
actionCallback(_action) {
egw.json("infolog.infolog_ui.ajax_action", [_action, this._action_ids, this._action_all]).sendRequest(true);
}
/**
* Add email from addressbook
*
* @param ab_id
* @param info_cc
*/
add_email_from_ab(ab_id, info_cc) {
var ab = document.getElementById(ab_id);
if (!ab || !ab.value) {
jQuery("tr.hiddenRow").css("display", "table-row");
} else {
var cc = document.getElementById(info_cc);
for (var i = 0; i < ab.options.length && ab.options[i].value != ab.value; ++i);
if (i < ab.options.length) {
cc.value += (cc.value ? ', ' : '') + ab.options[i].text.replace(/^.* <(.*)>$/, '$1');
ab.value = ''; // @ts-ignore
ab.onchange();
jQuery("tr.hiddenRow").css("display", "none");
}
}
return false;
}
/**
* If one of info_status, info_percent or info_datecompleted changed --> set others to reasonable values
*
* @param {string} changed_id id of changed element
* @param {string} status_id
* @param {string} percent_id
* @param {string} datecompleted_id
*/
status_changed(changed_id, status_id, percent_id, datecompleted_id) {
// Make sure this doesn't get executed while template is loading
if (this.et2 == null || this.et2.getInstanceManager() == null) return;
var status = document.getElementById(status_id);
var percent = document.getElementById(percent_id);
var datecompleted = document.getElementById(datecompleted_id + '[str]');
if (!datecompleted) {
datecompleted = jQuery('#' + datecompleted_id + ' input').get(0);
}
var completed;
switch (changed_id) {
case status_id:
completed = status.value == 'done' || status.value == 'billed';
if (completed || status.value == 'not-started' || status.value == 'ongoing' != (parseFloat(percent.value) > 0 && parseFloat(percent.value) < 100)) {
if (completed) {
percent.value = '100';
} else if (status.value == 'not-started') {
percent.value = '0';
} else if (!completed && (parseInt(percent.value) == 0 || parseInt(percent.value) == 100)) {
percent.value = '10';
}
}
break;
case percent_id:
completed = parseInt(percent.value) == 100;
if (completed != (status.value == 'done' || status.value == 'billed') || status.value == 'not-started' != (parseInt(percent.value) == 0)) {
status.value = parseInt(percent.value) == 0 ? jQuery('[value="not-started"]', status).length ? 'not-started' : 'ongoing' : parseInt(percent.value) == 100 ? 'done' : 'ongoing';
}
break;
case datecompleted_id + '[str]':
case datecompleted_id:
completed = datecompleted.value != '';
if (completed != (status.value == 'done' || status.value == 'billed')) {
status.value = completed ? 'done' : 'not-started';
}
if (completed != (parseInt(percent.value) == 100)) {
percent.value = completed ? '100' : '0';
}
break;
}
if (!completed && datecompleted && datecompleted.value != '') {
datecompleted.value = '';
} else if (completed && datecompleted && datecompleted.value == '') {// todo: set current date in correct format
}
}
/**
* handle "print" action from "Actions" selectbox in edit infolog window.
* check if the template is dirty then submit the template otherwise just open new window as print.
*
*/
edit_actions() {
var widget = this.et2.getWidgetById('action');
var template = this.et2._inst;
if (template) {
var id = template.widgetContainer.getArrayMgr('content').data['info_id'];
}
if (widget) {
switch (widget.get_value()) {
case 'print':
if (template.isDirty()) {
template.submit();
}
egw.open(id, 'infolog', 'edit', {
print: 1
});
break;
case 'ical':
template.postSubmit();
break;
default:
template.submit();
}
}
}
/**
* Open infolog entry for printing
*
* @param {aciton object} _action
* @param {object} _selected
*/
infolog_menu_print(_action, _selected) {
var id = _selected[0].id.replace(/^infolog::/g, '');
egw.open(id, 'infolog', 'edit', {
print: 1
});
}
/**
* Trigger print() onload window
*/
infolog_print_preview_onload() {
var that = this;
jQuery('#infolog-edit-print').bind('load', function () {
var isLoadingCompleted = true;
jQuery('#infolog-edit-print').bind("DOMSubtreeModified", function (event) {
isLoadingCompleted = false;
jQuery('#infolog-edit-print').unbind("DOMSubtreeModified");
});
setTimeout(function () {
isLoadingCompleted = false;
}, 1000);
var interval = setInterval(function () {
if (!isLoadingCompleted) {
clearInterval(interval);
that.infolog_print_preview();
}
}, 100);
});
}
/**
* Trigger print() function to print the current window
*/
infolog_print_preview() {
this.egw.message(this.egw.lang('Printing...'));
this.egw.window.print();
}
/**
*
*/
add_link_sidemenu() {
egw.open('', 'infolog', 'add');
}
/**
* Wrapper so add -> New actions in the context menu can pass current
* filter values into new edit dialog
*
* @see add_with_extras
*
* @param {egwAction} action
* @param {egwActionObject[]} selected
*/
add_action_handler(action, selected) {
var nm = action.getManager().data.nextmatch || false;
if (nm) {
this.add_with_extras(nm, action.id, nm.getArrayMgr('content').getEntry('action'), nm.getArrayMgr('content').getEntry('action_id'));
}
}
/**
* Opens a new edit dialog with some extra url parameters pulled from
* standard locations. Done with a function instead of hardcoding so
* the values can be updated if user changes them in UI.
*
* @param {et2_widget} widget Originating/calling widget
* @param _type string Type of infolog entry
* @param _action string Special action for new infolog entry
* @param _action_id string ID for special action
*/
add_with_extras(widget, _type, _action, _action_id) {
// We use widget.getRoot() instead of this.et2 for the case when the
// addressbook tab is viewing a contact + infolog list, there's 2 infolog
// etemplates
var nm = widget.getRoot().getWidgetById('nm');
var nm_value = nm.getValue() || {}; // It's important that all these keys are here, they override the link
// registry.
var action_id = nm_value.action_id ? nm_value.action_id : (_action_id != '0' ? _action_id : "") || "";
if (typeof action_id == "object" && typeof action_id.length == "undefined") {
// Need a real array here
action_id = jQuery.map(action_id, function (val) {
return val;
});
} // No action? Try the linked filter, in case it's set
if (!_action && !_action_id) {
if (nm_value.col_filter && nm_value.col_filter.linked) {
var split = nm_value.col_filter.linked.split(':') || '';
_action = split[0] || '';
action_id = split[1] || '';
}
}
var extras = {
type: _type || nm_value.col_filter.info_type || "task",
cat_id: nm_value.cat_id || "",
action: nm_value.action || _action || "",
// egw_link can handle arrays; but server is expecting CSV
action_id: typeof action_id.join != "undefined" ? action_id.join(',') : action_id
};
egw.open('', 'infolog', 'add', extras);
}
/**
* Get title in order to set it as document title
* @returns {string}
*/
getWindowTitle() {
var widget = this.et2.getWidgetById('info_subject');
if (widget) return widget.options.value;
}
/**
* View parent entry with all children
*
* @param {aciton object} _action
* @param {object} _selected
*/
view_parent(_action, _selected) {
var data = egw.dataGetUIDdata(_selected[0].id);
if (data && data.data && data.data.info_id_parent) {
egw.link_handler(egw.link('/index.php', {
menuaction: "infolog.infolog_ui.index",
action: "sp",
action_id: data.data.info_id_parent,
ajax: "true"
}), "infolog");
}
}
/**
* Mess with the query for parent widget to exclude self
*
* @param {Object} request
* @param {et2_link_entry} widget
* @returns {boolean}
*/
parent_query(request, widget) {
// No ID yet, no need to filter
if (!widget.getRoot().getArrayMgr('content').getEntry('info_id')) {
return true;
}
if (!request.options) {
request.options = {};
} // Exclude self from results - no app needed since it's just one app
request.options.exclude = [widget.getRoot().getArrayMgr('content').getEntry('info_id')];
return true;
}
/**
* View a list of timesheets for the linked infolog entry
*
* Only one infolog entry at a time is allowed, we just pick the first one
*
* @param {egwAction} _action
* @param {egwActionObject[]} _selected
*/
timesheet_list(_action, _selected) {
var extras = {
link_app: 'infolog',
link_id: false
};
for (var i = 0; i < _selected.length; i++) {
// Remove UID prefix for just contact_id
var ids = _selected[i].id.split('::');
ids.shift();
ids = ids.join('::');
extras.link_id = ids;
break;
}
egw.open("", "timesheet", "list", extras, 'timesheet');
}
/**
* Go to parent entry
*
* @param {aciton object} _action
* @param {object} _selected
*/
has_parent(_action, _selected) {
var data = egw.dataGetUIDdata(_selected[0].id);
return data && data.data && data.data.info_id_parent > 0;
}
/**
* Submit template if widget has a value
*
* Used for project-selection to update pricelist items from server
*
* @param {DOMNode} _node
* @param {et2_widget} _widget
*/
submit_if_not_empty(_node, _widget) {
if (_widget.get_value()) this.et2._inst.submit();
}
/**
* Toggle encryption
*
* @param {jQuery.Event} _event
* @param {et2_button} _widget
* @param {DOMNode} _node
*/
toggleEncrypt(_event, _widget, _node) {
if (!this.egw.user('apps').stylite) {
this.egw.message(this.egw.lang('InfoLog encryption requires EPL Subscription') + ': <a href="http://www.egroupware.org/EPL">www.egroupware.org/EPL</a>');
return;
}
this._get_stylite(function () {
app.stylite.toggleEncrypt.call(app.stylite, _event, _widget, _node);
});
}
/**
* Make sure stylite javascript is loaded, and call the given callback when it is
*
* @param {function} callback
* @param {object} attrs
*
*/
_get_stylite(callback, attrs) {
// use app object from etemplate2, which might be private and not just window.app
var app = this.et2.getInstanceManager().app_obj;
if (!app.stylite) {
this.egw.includeJS('/stylite/js/app.js', undefined, undefined, egw.webserverUrl).then(() => {
app.stylite = new app.classes.stylite();
app.stylite.et2 = this.et2;
if (callback) {
callback.apply(app.stylite, attrs);
}
});
} else {
app.stylite.et2 = this.et2;
callback.apply(app.stylite, attrs);
}
}
/**
* OnChange callback for responsible
*
* @param {jQuery.Event} _event
* @param {et2_widget} _widget
*/
onchangeResponsible(_event, _widget) {
if (app.stylite && app.stylite.onchangeResponsible) {
app.stylite.onchangeResponsible.call(app.stylite, _event, _widget);
}
}
/**
* Action handler for context menu change responsible action
*
* We populate the dialog with the current value.
*
* @param {egwAction} _action
* @param {egwActionObject[]} _selected
*/
change_responsible(_action, _selected) {
var et2 = _selected[0].manager.data.nextmatch.getInstanceManager();
var responsible = et2.widgetContainer.getWidgetById('responsible');
if (responsible) {
responsible.set_value([]);
et2.widgetContainer.getWidgetById('responsible_action[title]').set_value('');
et2.widgetContainer.getWidgetById('responsible_action[title]').set_class('');
et2.widgetContainer.getWidgetById('responsible_action[ok]').set_disabled(_selected.length !== 1);
et2.widgetContainer.getWidgetById('responsible_action[add]').set_disabled(_selected.length === 1);
et2.widgetContainer.getWidgetById('responsible_action[delete]').set_disabled(_selected.length === 1);
}
if (_selected.length === 1) {
var data = egw.dataGetUIDdata(_selected[0].id);
if (responsible && data && data.data) {
et2.widgetContainer.getWidgetById('responsible_action[title]').set_value(data.data.info_subject);
et2.widgetContainer.getWidgetById('responsible_action[title]').set_class(data.data.sub_class);
responsible.set_value(data.data.info_responsible);
}
}
nm_open_popup(_action, _selected);
}
/**
* Handle encrypted info_desc for print purpose
* and triggers print action after decryption
*
* @param {Keyring} _keyring Mailvelope keyring to use
*/
printEncrypt(_keyring) {
//this.mailvelopeAvailable(this.toggleEncrypt);
var info_desc = this.et2.getWidgetById('info_des');
var self = this;
mailvelope.createDisplayContainer('#infolog-edit-print_info_des', info_desc.value, _keyring).then(function (_container) {
var $info_des_dom = jQuery(self.et2.getWidgetById('info_des').getDOMNode()); // $info_des_dom.children('iframe').height($info_des_dom.height());
$info_des_dom.children('span').hide(); //Trigger print action
self.infolog_print_preview();
}, function (_err) {
self.egw.message(_err, 'error');
});
}
/**
* Blur NM count (used for limit modified optimization not returning (an exact) count
*
* @param blur
*/
blurCount(blur) {
document.querySelector('div#infolog-index_nm.et2_nextmatch .header_count')?.classList.toggle('blur_count', blur);
}
}
app.classes.infolog = InfologApp;
//# sourceMappingURL=app.js.map

View File

@ -1,62 +0,0 @@
/**
* EGroupware Preferences
*
* @link https://www.egroupware.org
* @author Ralf Becker <rb-At-egroupware.org>
* @package preferences
*/
import {AppJS} from "../../api/js/jsapi/app_base.js";
/**
* JavaScript for WebAuthn
*
* @augments AppJS
*/
app.classes.preferences = AppJS.extend(
{
appname: 'preferences',
/**
* et2 widget container
*/
et2: null,
/**
* path widget
*/
/**
* Constructor
*
* @memberOf app.timesheet
*/
init: function()
{
// call parent
this._super.apply(this, arguments);
},
/**
* Destructor
*/
destroy: function()
{
delete this.et2;
// call parent
this._super.apply(this, arguments);
},
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*
* @param et2 etemplate2 Newly ready object
*/
et2_ready: function(et2)
{
// call parent
this._super.apply(this, arguments);
}
});

View File

@ -1,127 +0,0 @@
/**
* EGroupware - Resources - Javascript UI
*
* @link https://www.egroupware.org
* @package resources
* @author Hadi Nategh <hn-AT-egroupware.org>
* @copyright (c) 2008-21 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
import { EgwApp } from "../../api/js/jsapi/egw_app";
import { fetchAll } from "../../api/js/etemplate/et2_extension_nextmatch_actions.js";
import { egw } from "../../api/js/jsapi/egw_global";
/**
* UI for resources
*/
class resourcesApp extends EgwApp {
/**
* Constructor
*/
constructor() {
super('resources');
}
/**
* Destructor
*/
destroy(_app) {
delete this.et2;
super.destroy(_app);
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*/
et2_ready(et2, name) {
super.et2_ready(et2, name);
}
/**
* call calendar planner by selected resources
*
* @param {action} _action actions
* @param {action} _senders selected action
*
*/
view_calendar(_action, _senders) {
let res_ids = [];
let matches = [];
let nm = _action.parent.data.nextmatch;
let selection = nm.getSelection();
let show_calendar = function (res_ids) {
egw(window).message(this.egw.lang('%1 resource(s) View calendar', res_ids.length));
let current_owners = (app.calendar ? app.calendar.state.owner || [] : []).join(',');
if (current_owners) {
current_owners += ',';
}
this.egw.open_link('calendar.calendar_uiviews.index&view=planner&sortby=user&owner=' + current_owners + 'r' + res_ids.join(',r') + '&ajax=true');
}.bind(this);
if (selection && selection.all) {
// Get selected ids from nextmatch - it will ask server if user did 'select all'
fetchAll(res_ids, nm, show_calendar);
}
else {
for (let i = 0; i < _senders.length; i++) {
res_ids.push(_senders[i].id);
matches = res_ids[i].match(/^(?:resources::)?([0-9]+)(:([0-9]+))?$/);
if (matches) {
res_ids[i] = matches[1];
}
}
show_calendar(res_ids);
}
}
/**
* Calendar sidebox hook change handler
*
*/
sidebox_change(ev, widget) {
if (ev[0] != 'r') {
widget.setSubChecked(ev, widget.getValue()[ev].value || false);
}
let owner = jQuery.extend([], app.calendar.state.owner) || [];
for (let i = owner.length - 1; i >= 0; i--) {
if (owner[i][0] == 'r') {
owner.splice(i, 1);
}
}
let value = widget.getValue();
for (let key in value) {
if (key[0] !== 'r')
continue;
if (value[key].value && owner.indexOf(key) === -1) {
owner.push(key);
}
}
app.calendar.update_state({ owner: owner });
}
/**
* Book selected resource for calendar
*
* @param {action} _action actions
* @param {action} _senders selected action
*/
book(_action, _senders) {
let res_ids = [], matches = [];
for (let i = 0; i < _senders.length; i++) {
res_ids.push(_senders[i].id);
matches = res_ids[i].match(/^(?:resources::)?([0-9]+)(:([0-9]+))?$/);
if (matches) {
res_ids[i] = matches[1];
}
}
egw(window).message(this.egw.lang('%1 resource(s) booked', res_ids.length));
this.egw.open_link('calendar.calendar_uiforms.edit&participants=r' + res_ids.join(',r'), '_blank', '700x700');
}
/**
* set the picture_src to own_src by uploding own file
*
*/
select_picture_src() {
let rBtn = this.et2.getWidgetById('picture_src');
if (typeof rBtn != 'undefined') {
rBtn.set_value('own_src');
}
}
}
app.classes.resources = resourcesApp;
//# sourceMappingURL=app.js.map

View File

@ -1,214 +0,0 @@
import { E as EgwApp, e as egw } from '../../chunks/etemplate2-0eb045cf.js';
import '../../chunks/egw_dragdrop_dhtmlx_tree-31643465.js';
import '../../chunks/egw-5f30b5ae.js';
import '../../vendor/bower-asset/jquery/dist/jquery.min.js';
import '../../vendor/bower-asset/jquery-ui/jquery-ui.js';
import '../../chunks/egw_json-98998d7e.js';
import '../../chunks/egw_core-0ec5dc11.js';
import '../../vendor/tinymce/tinymce/tinymce.min.js';
/**
* EGroupware - Timesheet - Javascript UI
*
* @link http://www.egroupware.org
* @package timesheet
* @author Hadi Nategh <hn-AT-stylite.de>
* @copyright (c) 2008-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
*/
/**
* UI for timesheet
*
* @augments AppJS
*/
class TimesheetApp extends EgwApp {
// These fields help with push filtering & access control to see if we care about a push message
push_grant_fields = ["ts_owner"];
push_filter_fields = ["ts_owner"];
constructor() {
super('timesheet');
}
/**
* This function is called when the etemplate2 object is loaded
* and ready. If you must store a reference to the et2 object,
* make sure to clean it up in destroy().
*
* @param et2 etemplate2 Newly ready object
* @param string name
*/
et2_ready(et2, name) {
// call parent
super.et2_ready(et2, name);
if (name == 'timesheet.index') {
this.filter_change();
this.filter2_change();
}
}
/**
*
*/
filter_change() {
var filter = this.et2.getWidgetById('filter');
var dates = this.et2.getWidgetById('timesheet.index.dates');
let nm = this.et2.getDOMWidgetById('nm');
if (filter && dates) {
dates.set_disabled(filter.get_value() !== "custom");
if (filter.get_value() == 0) nm.activeFilters.startdate = null;
if (filter.value == "custom") {
jQuery(this.et2.getWidgetById('startdate').getDOMNode()).find('input').focus();
}
}
return true;
}
/**
* show or hide the details of rows by selecting the filter2 option
* either 'all' for details or 'no_description' for no details
*
*/
filter2_change() {
var nm = this.et2.getWidgetById('nm');
var filter2 = this.et2.getWidgetById('filter2');
if (nm && filter2) {
egw.css("#timesheet-index span.timesheet_titleDetails", "font-weight:" + (filter2.getValue() == '1' ? "bold;" : "normal;")); // Show / hide descriptions
egw.css(".et2_label.ts_description", "display:" + (filter2.getValue() == '1' ? "block;" : "none;"));
}
}
/**
* Wrapper so add action in the context menu can pass current
* filter values into new edit dialog
*
* @see add_with_extras
*
* @param {egwAction} action
* @param {egwActionObject[]} selected
*/
add_action_handler(action, selected) {
var nm = action.getManager().data.nextmatch || false;
if (nm) {
this.add_with_extras(nm);
}
}
/**
* Opens a new edit dialog with some extra url parameters pulled from
* nextmatch filters.
*
* @param {et2_widget} widget Originating/calling widget
*/
add_with_extras(widget) {
var nm = widget.getRoot().getWidgetById('nm');
var nm_value = nm.getValue() || {};
var extras = {};
if (nm_value.cat_id) {
extras.cat_id = nm_value.cat_id;
}
if (nm_value.col_filter && nm_value.col_filter.linked) {
var split = nm_value.col_filter.linked.split(':') || '';
extras.link_app = split[0] || '';
extras.link_id = split[1] || '';
}
if (nm_value.col_filter && nm_value.col_filter.pm_id) {
extras.link_app = 'projectmanager';
extras.link_id = nm_value.col_filter.pm_id;
} else if (nm_value.col_filter && nm_value.col_filter.ts_project) {
extras.ts_project = nm_value.col_filter.ts_project;
}
egw.open('', 'timesheet', 'add', extras);
}
/**
* Change handler for project selection to set empty ts_project string, if project get deleted
*
* @param {type} _egw
* @param {et2_widget_link_entry} _widget
* @returns {undefined}
*/
pm_id_changed(_egw, _widget) {
// Update price list
var ts_pricelist = _widget.getRoot().getWidgetById('pl_id');
egw.json('projectmanager_widget::ajax_get_pricelist', [_widget.getValue()], function (value) {
ts_pricelist.set_select_options(value || {});
}).sendRequest(true);
var ts_project = this.et2.getWidgetById('ts_project');
if (ts_project) {
ts_project.set_blur(_widget.getValue() ? _widget.search.val() : '');
}
}
/**
* Update custom filter timespan, without triggering a change
*/
update_timespan(start, end) {
if (this && this.et2) {
var nm = this.et2.getWidgetById('nm');
if (nm) {
// Toggle update_in_progress to avoid another request
nm.update_in_progress = true;
this.et2.getWidgetById('startdate').set_value(start);
this.et2.getWidgetById('enddate').set_value(end);
nm.activeFilters.startdate = start;
nm.activeFilters.enddate = end;
nm.update_in_progress = false;
}
}
}
/**
* Get title in order to set it as document title
* @returns {string}
*/
getWindowTitle() {
var widget = this.et2.getWidgetById('ts_title');
if (widget) return widget.options.value;
}
/**
* Run action via ajax
*
* @param _action
* @param _senders
*/
ajax_action(_action, _senders) {
let all = _action.parent.data.nextmatch?.getSelection().all;
let ids = [];
for (let i = 0; i < _senders.length; i++) {
ids.push(_senders[i].id.split("::").pop());
}
egw.json("timesheet.timesheet_ui.ajax_action", [_action.id, ids, all]).sendRequest(true);
}
}
app.classes.timesheet = TimesheetApp;
//# sourceMappingURL=app.js.map