mirror of
https://github.com/EGroupware/egroupware.git
synced 2024-12-04 22:01:05 +01:00
1964 lines
79 KiB
JavaScript
1964 lines
79 KiB
JavaScript
/**
|
|
* EGroupware eTemplate2 - JS Link 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 2011 Nathan Gray
|
|
*/
|
|
var _a;
|
|
/*egw:uses
|
|
/vendor/bower-asset/jquery/dist/jquery.js;
|
|
/vendor/bower-asset/jquery-ui/jquery-ui.js;
|
|
et2_core_inputWidget;
|
|
et2_core_valueWidget;
|
|
et2_widget_selectbox;
|
|
expose;
|
|
|
|
// Include menu system for list context menu
|
|
egw_action.egw_menu_dhtmlx;
|
|
*/
|
|
import { et2_createWidget, et2_register_widget } from "./et2_core_widget";
|
|
import { ClassWithAttributes } from "./et2_core_inheritance";
|
|
import { et2_valueWidget } from "./et2_core_valueWidget";
|
|
import { et2_inputWidget } from "./et2_core_inputWidget";
|
|
import { et2_selectbox } from "./et2_widget_selectbox";
|
|
import { et2_dialog } from "./et2_widget_dialog";
|
|
import { egw, egw_get_file_editor_prefered_mimes } from "../jsapi/egw_global";
|
|
import { et2_csvSplit, et2_no_init } from "./et2_core_common";
|
|
import { expose } from "./expose";
|
|
import { egwMenu } from "../egw_action/egw_menu.js";
|
|
/**
|
|
* UI widgets for Egroupware linking system
|
|
*/
|
|
export class et2_link_to extends et2_inputWidget {
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @memberOf et2_link_to
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_to._attributes, _child || {}));
|
|
this.div = jQuery(document.createElement("div")).addClass("et2_link_to et2_toolbar");
|
|
this.link_button = null;
|
|
this.status_span = null;
|
|
this.link_entry = null;
|
|
this.file_upload = null;
|
|
this.createInputWidget();
|
|
}
|
|
destroy() {
|
|
this.link_button = null;
|
|
this.status_span = null;
|
|
if (this.link_entry) {
|
|
this.link_entry.destroy();
|
|
this.link_entry = null;
|
|
}
|
|
if (this.file_upload) {
|
|
this.file_upload.destroy();
|
|
this.file_upload = null;
|
|
}
|
|
this.div = null;
|
|
super.destroy.apply(this, arguments);
|
|
}
|
|
/**
|
|
* Override to provide proper node for sub widgets to go in
|
|
*
|
|
* @param {Object} _sender
|
|
*/
|
|
getDOMNode(_sender) {
|
|
if (_sender == this) {
|
|
return this.div[0];
|
|
}
|
|
else if (_sender._type == 'link-entry') {
|
|
return this.link_div[0];
|
|
}
|
|
else if (_sender._type == 'file') {
|
|
return this.file_div[0];
|
|
}
|
|
else if (_sender._type == 'vfs-select') {
|
|
return this.filemanager_button[0];
|
|
}
|
|
}
|
|
createInputWidget() {
|
|
// Need a div for file upload widget
|
|
this.file_div = jQuery(document.createElement("div")).css({ display: 'inline-block' }).appendTo(this.div);
|
|
// Filemanager link popup
|
|
this.filemanager_button = jQuery(document.createElement("div")).css({ display: 'inline-block' }).appendTo(this.div);
|
|
// Need a div for link-to widget
|
|
this.link_div = jQuery(document.createElement("div"))
|
|
.addClass('div_link')
|
|
// Leave room for link button
|
|
.appendTo(this.div);
|
|
if (!this.options.readonly) {
|
|
// One common link button
|
|
this.link_button = jQuery(document.createElement("button"))
|
|
.text(this.egw().lang(this.options.link_label))
|
|
.appendTo(this.div).hide()
|
|
.addClass('link')
|
|
.click(this, this.createLink);
|
|
// Span for indicating status
|
|
this.status_span = jQuery(document.createElement("span"))
|
|
.appendTo(this.div).addClass("status").hide();
|
|
}
|
|
this.setDOMNode(this.div[0]);
|
|
}
|
|
doLoadingFinished() {
|
|
super.doLoadingFinished.apply(this, arguments);
|
|
var self = this;
|
|
if (this.link_entry && this.vfs_select && this.file_upload) {
|
|
// Already done
|
|
return false;
|
|
}
|
|
// Link-to
|
|
var link_entry_attrs = {
|
|
id: this.id + '_link_entry',
|
|
only_app: this.options.only_app,
|
|
application_list: this.options.application_list,
|
|
blur: this.options.search_label ? this.options.search_label : this.egw().lang('Search...'),
|
|
query: function () { self.link_button.hide(); return true; },
|
|
select: function () { self.link_button.show(); return true; },
|
|
readonly: this.options.readonly
|
|
};
|
|
this.link_entry = et2_createWidget("link-entry", link_entry_attrs, this);
|
|
// Filemanager select
|
|
var select_attrs = {
|
|
button_label: egw.lang('Link'),
|
|
button_caption: '',
|
|
button_icon: 'link',
|
|
readonly: this.options.readonly,
|
|
dialog_title: egw.lang('Link'),
|
|
extra_buttons: [{ text: egw.lang("copy"), id: "copy", image: "copy" },
|
|
{ text: egw.lang("move"), id: "move", image: "move" }],
|
|
onchange: function () {
|
|
var values = true;
|
|
// If entry not yet saved, store for linking on server
|
|
if (!self.options.value.to_id || typeof self.options.value.to_id == 'object') {
|
|
values = self.options.value.to_id || {};
|
|
var files = self.vfs_select.getValue();
|
|
if (typeof files !== 'undefined') {
|
|
for (var i = 0; i < files.length; i++) {
|
|
values['link:' + files[i]] = {
|
|
app: 'link',
|
|
id: files[i],
|
|
type: 'unknown',
|
|
icon: 'link',
|
|
remark: '',
|
|
title: files[i]
|
|
};
|
|
}
|
|
}
|
|
}
|
|
self._link_result(values);
|
|
}
|
|
};
|
|
// only set server-side callback, if we have a real application-id (not null or array)
|
|
// otherwise it only gives an error on server-side
|
|
if (self.options.value && self.options.value.to_id && typeof self.options.value.to_id != 'object') {
|
|
select_attrs.method = 'EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_existing';
|
|
select_attrs.method_id = self.options.value.to_app + ':' + self.options.value.to_id;
|
|
}
|
|
this.vfs_select = et2_createWidget("vfs-select", select_attrs, this);
|
|
this.vfs_select.set_readonly(this.options.readonly);
|
|
// File upload
|
|
var file_attrs = {
|
|
multiple: true,
|
|
id: this.id + '_file',
|
|
label: '',
|
|
// Make the whole template a drop target
|
|
drop_target: this.getInstanceManager().DOMContainer.getAttribute("id"),
|
|
readonly: this.options.readonly,
|
|
// Change to this tab when they drop
|
|
onStart: function (event, file_count) {
|
|
// Find the tab widget, if there is one
|
|
var tabs = self;
|
|
do {
|
|
tabs = tabs.getParent();
|
|
} while (tabs != self.getRoot() && tabs.getType() != 'tabbox');
|
|
if (tabs != self.getRoot()) {
|
|
tabs.activateTab(self);
|
|
}
|
|
return true;
|
|
},
|
|
onFinish: function (event, file_count) {
|
|
event.data = self;
|
|
self.filesUploaded(event);
|
|
// Auto-link uploaded files
|
|
self.createLink(event);
|
|
}
|
|
};
|
|
this.file_upload = et2_createWidget("file", file_attrs, this);
|
|
this.file_upload.set_readonly(this.options.readonly);
|
|
return true;
|
|
}
|
|
getValue() {
|
|
return this.options.value;
|
|
}
|
|
filesUploaded(event) {
|
|
var self = this;
|
|
this.link_button.show();
|
|
}
|
|
/**
|
|
* Create a link using the current internal values
|
|
*
|
|
* @param {Object} event
|
|
*/
|
|
createLink(event) {
|
|
// Disable link button
|
|
event.data.link_button.attr("disabled", true);
|
|
var values = event.data.options.value;
|
|
var self = event.data;
|
|
var links = [];
|
|
// Links to other entries
|
|
event.data = self.link_entry;
|
|
self.link_entry.createLink(event, links);
|
|
// Files
|
|
if (!self.options.no_files) {
|
|
for (var file in self.file_upload.options.value) {
|
|
links.push({
|
|
app: 'file',
|
|
id: file,
|
|
name: self.file_upload.options.value[file].name,
|
|
type: self.file_upload.options.value[file].type,
|
|
remark: jQuery("li[file='" + self.file_upload.options.value[file].name.replace(/'/g, '"') + "'] > input", self.file_upload.progress)
|
|
.filter(function () { return jQuery(this).attr("placeholder") != jQuery(this).val(); }).val()
|
|
});
|
|
}
|
|
}
|
|
if (links.length == 0) {
|
|
return;
|
|
}
|
|
var request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", [values.to_app, values.to_id, links], self._link_result, self, true, self);
|
|
request.sendRequest();
|
|
}
|
|
/**
|
|
* Sent some links, server has a result
|
|
*
|
|
* @param {Object} success
|
|
*/
|
|
_link_result(success) {
|
|
if (success) {
|
|
this.link_button.hide().attr("disabled", false);
|
|
this.status_span.removeClass("error").addClass("success");
|
|
this.status_span.fadeIn().delay(1000).fadeOut();
|
|
delete this.options.value.app;
|
|
delete this.options.value.id;
|
|
for (var file in this.file_upload.options.value) {
|
|
delete this.file_upload.options.value[file];
|
|
}
|
|
this.file_upload.progress.empty();
|
|
// Server says it's OK, but didn't store - we'll send this again on submit
|
|
// This happens if you link to something before it's saved to the DB
|
|
if (typeof success == "object") {
|
|
// Save as appropriate in value
|
|
if (typeof this.options.value != "object") {
|
|
this.options.value = {};
|
|
}
|
|
this.options.value.to_id = success;
|
|
for (let link in success) {
|
|
// Icon should be in registry
|
|
if (typeof success[link].icon == 'undefined') {
|
|
success[link].icon = egw.link_get_registry(success[link].app, 'icon');
|
|
// No icon, try by mime type - different place for un-saved entries
|
|
if (success[link].icon == false && success[link].id.type) {
|
|
// Triggers icon by mime type, not thumbnail or app
|
|
success[link].type = success[link].id.type;
|
|
success[link].icon = true;
|
|
}
|
|
}
|
|
// Special handling for file - if not existing, we can't ask for title
|
|
if (success[link].app == 'file' && typeof success[link].title == 'undefined') {
|
|
success[link].title = success[link].id.name || '';
|
|
}
|
|
}
|
|
}
|
|
// Look for a link-list with the same ID, refresh it
|
|
var self = this;
|
|
var list_widget = null;
|
|
this.getRoot().iterateOver(function (widget) {
|
|
if (widget.id == self.id) {
|
|
list_widget = widget;
|
|
if (success === true) {
|
|
widget._get_links();
|
|
}
|
|
}
|
|
}, this, et2_link_list);
|
|
// If there's an array of data (entry is not yet saved), updating the list will
|
|
// not work, so add them in explicitly.
|
|
if (list_widget && success) {
|
|
// Clear list
|
|
list_widget.set_value(null);
|
|
// Add temp links in
|
|
for (var link_id in success) {
|
|
let link = success[link_id];
|
|
if (typeof link.title == 'undefined') {
|
|
// Callback to server for title
|
|
egw.link_title(link.app, link.id, function (title) {
|
|
link.title = title;
|
|
list_widget._add_link(link);
|
|
});
|
|
}
|
|
else {
|
|
// Add direct
|
|
list_widget._add_link(link);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
this.status_span.removeClass("success").addClass("error")
|
|
.fadeIn();
|
|
}
|
|
this.div.trigger('link.et2_link_to', success);
|
|
}
|
|
set_no_files(no_files) {
|
|
if (this.options.readonly)
|
|
return;
|
|
if (no_files) {
|
|
this.file_div.hide();
|
|
this.filemanager_button.hide();
|
|
}
|
|
else {
|
|
this.file_div.show();
|
|
this.filemanager_button.show();
|
|
}
|
|
this.options.no_files = no_files;
|
|
}
|
|
}
|
|
et2_link_to._attributes = {
|
|
"only_app": {
|
|
"name": "Application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Limit to just this one application - hides app selection"
|
|
},
|
|
"application_list": {
|
|
"name": "Application list",
|
|
"type": "any",
|
|
"default": "",
|
|
"description": "Limit to the listed application or applications (comma seperated)"
|
|
},
|
|
"blur": {
|
|
"name": "Placeholder",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text.",
|
|
translate: true
|
|
},
|
|
"no_files": {
|
|
"name": "No files",
|
|
"type": "boolean",
|
|
"default": false,
|
|
"description": "Suppress attach-files"
|
|
},
|
|
"search_label": {
|
|
"name": "Search label",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Label to use for search"
|
|
},
|
|
"link_label": {
|
|
"name": "Link label",
|
|
"type": "string",
|
|
"default": "Link",
|
|
"description": "Label for the link button"
|
|
},
|
|
"value": {
|
|
// Could be string or int if application is provided, or an Object
|
|
"type": "any"
|
|
}
|
|
};
|
|
et2_register_widget(et2_link_to, ["link-to"]);
|
|
/**
|
|
* List of applications that support link
|
|
*/
|
|
export class et2_link_apps extends et2_selectbox {
|
|
/**
|
|
* Constructor
|
|
*
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_apps._attributes, _child || {}));
|
|
if (this.options.select_options != null) {
|
|
// Preset to last application
|
|
if (!this.options.value) {
|
|
this.set_value(egw.preference('link_app', this.egw().getAppName()));
|
|
}
|
|
// Register to update preference
|
|
var self = this;
|
|
this.input.bind("click", function () {
|
|
if (typeof self.options.value != 'undefined')
|
|
var appname = self.options.value.to_app;
|
|
egw.set_preference(appname || self.egw().getAppName(), 'link_app', self.getValue());
|
|
});
|
|
}
|
|
}
|
|
/**
|
|
* We get some minor speedups by overriding parent searching and directly setting select options
|
|
*
|
|
* @param {Array} _attrs an array of attributes
|
|
*/
|
|
transformAttributes(_attrs) {
|
|
var select_options = {};
|
|
// Limit to one app
|
|
if (_attrs.only_app) {
|
|
select_options[_attrs.only_app] = this.egw().lang(_attrs.only_app);
|
|
}
|
|
else if (_attrs.application_list) {
|
|
select_options = _attrs.application_list;
|
|
}
|
|
else {
|
|
select_options = egw.link_app_list('query');
|
|
if (typeof select_options['addressbook-email'] !== 'undefined') {
|
|
delete select_options['addressbook-email'];
|
|
}
|
|
}
|
|
_attrs.select_options = select_options;
|
|
super.transformAttributes(_attrs);
|
|
}
|
|
}
|
|
et2_link_apps._attributes = {
|
|
"only_app": {
|
|
"name": "Application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Limit to just this one application - hides app selection"
|
|
},
|
|
"application_list": {
|
|
"name": "Application list",
|
|
"type": "any",
|
|
"default": "",
|
|
"description": "Limit to the listed application or applications (comma seperated)"
|
|
}
|
|
};
|
|
et2_register_widget(et2_link_apps, ["link-apps"]);
|
|
/**
|
|
* Search and select an entry for linking
|
|
*/
|
|
export class et2_link_entry extends et2_inputWidget {
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @memberOf et2_link_entry
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_entry._attributes, _child || {}));
|
|
this.cache = {};
|
|
this.processing = false;
|
|
this.search = null;
|
|
this.clear = null;
|
|
this.app_select = null;
|
|
this._oldValue = {
|
|
id: null,
|
|
app: this.options.value && this.options.value.app ? this.options.value.app : this.options.only_app
|
|
};
|
|
if (typeof this.options.value == 'undefined' || this.options.value == null) {
|
|
this.options.value = {};
|
|
}
|
|
this.cache = {};
|
|
this.request = null;
|
|
this.createInputWidget();
|
|
var self = this;
|
|
jQuery(this.getInstanceManager().DOMContainer).on('clear', function () {
|
|
// We need to unbind events to prevent a second triggerd event handler
|
|
// (eg. setting a project in infolog edit dialog) when the widget gets cleared.
|
|
jQuery(self.getDOMNode()).off();
|
|
});
|
|
}
|
|
destroy() {
|
|
super.destroy.apply(this, arguments);
|
|
this.div = null;
|
|
if (this.search.data("ui-autocomplete")) {
|
|
this.search.autocomplete("destroy");
|
|
}
|
|
this.search = null;
|
|
this.clear = null;
|
|
this.app_select = null;
|
|
this.request = null;
|
|
}
|
|
createInputWidget() {
|
|
var self = this;
|
|
this.div = jQuery(document.createElement("div")).addClass("et2_link_entry");
|
|
// Application selection
|
|
jQuery.widget("custom.iconselectmenu", jQuery.ui.selectmenu, {
|
|
_setText: function (element, value) {
|
|
if (element === this.buttonText) {
|
|
this._setButtonText(value);
|
|
}
|
|
else {
|
|
this._superApply(element, value);
|
|
}
|
|
},
|
|
_setButtonText: function (value) {
|
|
var _value = this.focusIndex;
|
|
if (typeof this.focusIndex === 'undefined') {
|
|
_value = this.element.find("option:selected").val();
|
|
}
|
|
else {
|
|
var selected = this.items[_value] || {};
|
|
_value = selected.value;
|
|
}
|
|
var url = self.egw().image('navbar', _value);
|
|
var buttonItem = jQuery("<span>", {
|
|
"class": "ui-selectmenu-text",
|
|
title: value
|
|
});
|
|
jQuery('.ui-selectmenu-text', this.button).replaceWith(buttonItem);
|
|
buttonItem.css('background-image', 'url(' + url + ')');
|
|
},
|
|
_renderItem: function (ul, item) {
|
|
var li = jQuery("<li>", { class: "et2_link_entry_app_option" }), wrapper = jQuery("<div>", { text: item.label });
|
|
if (item.disabled) {
|
|
li.addClass("ui-state-disabled");
|
|
}
|
|
ul.addClass(self.div.attr("class"));
|
|
var url = self.egw().image('navbar', item.value);
|
|
jQuery("<span>", {
|
|
style: 'background-image: url("' + url + '");',
|
|
"class": "ui-icon " + item.element.attr("data-class"),
|
|
title: item.label
|
|
})
|
|
.appendTo(wrapper);
|
|
return li.append(wrapper).appendTo(ul);
|
|
}
|
|
});
|
|
this.app_select = jQuery(document.createElement("select")).appendTo(this.div)
|
|
.change(function (e) {
|
|
// Clear cache when app changes
|
|
self.cache = {};
|
|
// Update preference with new value
|
|
egw.set_preference(self.options.value.to_app || self.egw().getAppName(), 'link_app', self.app_select.val());
|
|
if (typeof self.options.value != 'object')
|
|
self.options.value = {};
|
|
self.options.value.app = self.app_select.val();
|
|
})
|
|
.attr("aria-label", egw.lang("linkapps"));
|
|
var opt_count = 0;
|
|
for (var key in this.options.select_options) {
|
|
opt_count++;
|
|
var option = jQuery(document.createElement("option"))
|
|
.attr("value", key)
|
|
.text(this.options.select_options[key]);
|
|
option.appendTo(this.app_select);
|
|
}
|
|
if (this.options.only_app) {
|
|
this.app_select.val(this.options.only_app);
|
|
this.app_select.hide();
|
|
if (this.options.app_icons && this.app_select.iconselectmenu('instance')) {
|
|
this.app_select.iconselectmenu('widget').hide();
|
|
}
|
|
this.div.addClass("no_app");
|
|
}
|
|
else {
|
|
// Now that options are in, set to last used app
|
|
this.app_select.val(this.options.value.app || '');
|
|
if (this.app_select.iconselectmenu('instance')) {
|
|
this.app_select.iconselectmenu('update');
|
|
}
|
|
}
|
|
// Search input
|
|
this.search = jQuery(document.createElement("input"))
|
|
// .attr("type", "search") // Fake it for all browsers below
|
|
.focus(function () {
|
|
if (!self.options.only_app) {
|
|
// Adjust width, leave room for app select & link button
|
|
self.div.removeClass("no_app");
|
|
if (self.options.app_icons) {
|
|
self.app_select.iconselectmenu('widget').show();
|
|
}
|
|
else {
|
|
self.app_select.show();
|
|
}
|
|
}
|
|
})
|
|
.blur(function (e) {
|
|
if (self.div.has(e.relatedTarget).length)
|
|
return;
|
|
if (self.options.app_icons) {
|
|
// Adjust width, leave room for app select & link button
|
|
self.div.addClass("no_app");
|
|
self.app_select.iconselectmenu('widget').hide();
|
|
}
|
|
else if (self.search.val()) {
|
|
if (self.options.only_app) {
|
|
// Adjust width, leave room for app select & link button
|
|
self.div.addClass("no_app");
|
|
}
|
|
}
|
|
})
|
|
.attr("aria-label", egw.lang("Link search"))
|
|
.attr("role", "searchbox")
|
|
.appendTo(this.div);
|
|
this.set_blur(this.options.blur ? this.options.blur : this.egw().lang("search"), this.search);
|
|
// Autocomplete
|
|
this.search.autocomplete({
|
|
source: function (request, response) {
|
|
return self.query(request, response);
|
|
},
|
|
select: function (event, item) {
|
|
event.data = self;
|
|
// Correct changed value from server
|
|
if (item.item.value) {
|
|
item.item.value = ("" + item.item.value).trim();
|
|
}
|
|
self.select(event, item);
|
|
return false;
|
|
},
|
|
focus: function (event, item) {
|
|
event.stopPropagation();
|
|
self.search.val(item.item.label);
|
|
return false;
|
|
},
|
|
minLength: et2_link_entry.minimum_characters,
|
|
delay: et2_link_entry.search_timeout,
|
|
disabled: self.options.disabled,
|
|
appendTo: self.div
|
|
});
|
|
// Custom display (colors)
|
|
this.search.data("uiAutocomplete")._renderItem = function (ul, item) {
|
|
var li = jQuery(document.createElement('li'))
|
|
.data("item.autocomplete", item);
|
|
var extra = {};
|
|
// Extra stuff
|
|
if (typeof item.label == 'object') {
|
|
extra = item.label;
|
|
item.label = extra.label ? extra.label : extra;
|
|
if (extra['style.backgroundColor'] || extra.color) {
|
|
li.css({ 'border-left': '5px solid ' + (extra.color ? extra.color : extra['style.backgroundColor']) });
|
|
}
|
|
// Careful with this, some browsers may have trouble loading all at once, which can slow display
|
|
if (extra.icon) {
|
|
var img = self.egw().image(extra.icon);
|
|
if (img) {
|
|
jQuery(document.createElement("img"))
|
|
.attr("src", img)
|
|
.css("float", "right")
|
|
.appendTo(li);
|
|
}
|
|
}
|
|
}
|
|
// Normal stuff
|
|
li.append(jQuery("<a></a>").text(item.label))
|
|
.appendTo(ul);
|
|
window.setTimeout(function () { ul.css('max-width', jQuery('.et2_container').width() - ul.offset().left); }, 300);
|
|
return li;
|
|
};
|
|
// Bind to enter key to start search early
|
|
this.search.keydown(function (e) {
|
|
var keycode = (e.keyCode ? e.keyCode : e.which);
|
|
if (keycode == 13 && !self.processing) {
|
|
self.search.autocomplete("option", "minLength", 0);
|
|
self.search.autocomplete("search");
|
|
self.search.autocomplete("option", "minLength", self.minimum_characters);
|
|
return false;
|
|
}
|
|
});
|
|
// Clear / last button
|
|
this.clear = jQuery(document.createElement("span"))
|
|
.addClass("ui-icon ui-icon-close")
|
|
.click(function (e) {
|
|
if (!self.search)
|
|
return; // only gives an error, we should never get into that situation
|
|
// No way to tell if the results is open, so if they click the button while open, it clears
|
|
if (self.last_search && self.last_search != self.search.val()) {
|
|
// Repeat last search (should be cached)
|
|
self.search.val(self.last_search);
|
|
self.last_search = "";
|
|
self.search.autocomplete("search");
|
|
}
|
|
else {
|
|
// Clear
|
|
self.search.autocomplete("close");
|
|
self.set_value(null);
|
|
self.search.val("");
|
|
// call trigger, after finishing this handler, not in the middle of it
|
|
window.setTimeout(function () {
|
|
self.search.trigger("change");
|
|
}, 0);
|
|
}
|
|
self.search.focus();
|
|
})
|
|
.appendTo(this.div)
|
|
.hide();
|
|
this.setDOMNode(this.div[0]);
|
|
}
|
|
getDOMNode() {
|
|
return this.div ? this.div[0] : null;
|
|
}
|
|
transformAttributes(_attrs) {
|
|
super.transformAttributes.apply(this, arguments);
|
|
_attrs["select_options"] = {};
|
|
if (_attrs["application_list"]) {
|
|
var apps = (typeof _attrs["application_list"] == "string") ? et2_csvSplit(_attrs["application_list"], null, ",") : _attrs["application_list"];
|
|
for (var i = 0; i < apps.length; i++) {
|
|
_attrs["select_options"][apps[i]] = this.egw().lang(apps[i]);
|
|
}
|
|
}
|
|
else {
|
|
_attrs["select_options"] = this.egw().link_app_list('query');
|
|
if (typeof _attrs["select_options"]["addressbook-email"] != 'undefined')
|
|
delete _attrs["select_options"]["addressbook-email"];
|
|
}
|
|
// Check whether the options entry was found, if not read it from the
|
|
// content array.
|
|
if (_attrs["select_options"] == null) {
|
|
_attrs["select_options"] = this.getArrayMgr('content')
|
|
.getEntry("options-" + this.id);
|
|
}
|
|
// Default to an empty object
|
|
if (_attrs["select_options"] == null) {
|
|
_attrs["select_options"] = {};
|
|
}
|
|
}
|
|
doLoadingFinished() {
|
|
if (typeof this.options.value == 'object' && !this.options.value.app) {
|
|
this.options.value.app = egw.preference('link_app', this.options.value.to_app || this.egw().getAppName());
|
|
// If there's no value set for app, then take the first one from the selectbox
|
|
if (typeof this.options.value.app == 'undefined' || !this.options.value.app) {
|
|
this.options.value.app = Object.keys(this.options.select_options)[0];
|
|
}
|
|
this.app_select.val(this.options.value.app);
|
|
}
|
|
if (this.options.app_icons) {
|
|
var self = this;
|
|
this.div.addClass('app_icons');
|
|
this.app_select.iconselectmenu({
|
|
width: 50,
|
|
change: function () {
|
|
window.setTimeout(function () {
|
|
self.app_select.trigger("change");
|
|
}, 0);
|
|
}
|
|
})
|
|
.iconselectmenu("menuWidget");
|
|
this.app_select.iconselectmenu('widget').hide();
|
|
}
|
|
return super.doLoadingFinished.apply(this, arguments);
|
|
}
|
|
getValue() {
|
|
var value = this.options && this.options.only_app ? this.options.value.id : this.options ? this.options.value : null;
|
|
if (this.options && !this.options.only_app && this.search) {
|
|
value.search = this.search.val();
|
|
}
|
|
return value;
|
|
}
|
|
set_value(_value) {
|
|
if (typeof _value == 'string' || typeof _value == 'number') {
|
|
if (typeof _value == 'string' && _value.indexOf(",") > 0)
|
|
_value = _value.replace(",", ":");
|
|
if (typeof _value == 'string' && _value.indexOf(":") >= 0) {
|
|
var split = _value.split(":");
|
|
_value = {
|
|
app: split.shift(),
|
|
id: split.length == 1 ? split[0] : split
|
|
};
|
|
}
|
|
else if (_value && this.options.only_app) {
|
|
_value = {
|
|
app: this.options.only_app,
|
|
id: _value
|
|
};
|
|
}
|
|
}
|
|
this._oldValue = this.options.value;
|
|
if (!_value || _value.length == 0 || _value == null || jQuery.isEmptyObject(_value)) {
|
|
this.search.val("");
|
|
this.clear.hide();
|
|
this.options.value = _value = { 'id': null };
|
|
}
|
|
if (!_value.app)
|
|
_value.app = this.options.only_app || this.app_select.val();
|
|
if (_value.id) {
|
|
// Remove specific display and revert to CSS file
|
|
// show() would use inline, should be inline-block
|
|
this.clear.css('display', '');
|
|
}
|
|
else {
|
|
this.clear.hide();
|
|
return;
|
|
}
|
|
if (typeof _value != 'object' || (!_value.app && !_value.id)) {
|
|
console.warn("Bad value for link widget. Need an object with keys 'app', 'id', and optionally 'title'", _value);
|
|
return;
|
|
}
|
|
if (!_value.title) {
|
|
var title = this.egw().link_title(_value.app, _value.id);
|
|
if (title != null) {
|
|
_value.title = title;
|
|
}
|
|
else {
|
|
// Title will be fetched from server and then set
|
|
var title = this.egw().link_title(_value.app, _value.id, function (title) {
|
|
this.search.removeClass("loading").val(title + "");
|
|
// Remove specific display and revert to CSS file
|
|
// show() would use inline, should be inline-block
|
|
this.clear.css('display', '');
|
|
}, this);
|
|
this.search.addClass("loading");
|
|
}
|
|
}
|
|
if (_value.title) {
|
|
this.search.val(_value.title + "");
|
|
}
|
|
this.options.value = _value;
|
|
jQuery("option[value='" + _value.app + "']", this.app_select).prop("selected", true);
|
|
this.app_select.hide();
|
|
if (this.options.app_icons && this.app_select.iconselectmenu('instance')) {
|
|
this.app_select.iconselectmenu('widget').hide();
|
|
}
|
|
this.div.addClass("no_app");
|
|
}
|
|
set_blur(_value, input) {
|
|
if (typeof input == 'undefined')
|
|
input = this.search;
|
|
if (_value) {
|
|
input.attr("placeholder", _value); // HTML5
|
|
if (!input[0].placeholder) {
|
|
// Not HTML5
|
|
if (input.val() == "")
|
|
input.val(_value);
|
|
input.focus(input, function (e) {
|
|
var placeholder = _value;
|
|
if (e.data.val() == placeholder)
|
|
e.data.val("");
|
|
}).blur(input, function (e) {
|
|
var placeholder = _value;
|
|
if (e.data.val() == "")
|
|
e.data.val(placeholder);
|
|
});
|
|
if (input.val() == "") {
|
|
input.val(_value);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
this.search.removeAttr("placeholder");
|
|
}
|
|
}
|
|
/**
|
|
* Set the query callback
|
|
*
|
|
* @param {function} f
|
|
*/
|
|
set_query(f) {
|
|
this.options.query = f;
|
|
}
|
|
/**
|
|
* Set the select callback
|
|
*
|
|
* @param {function} f
|
|
*/
|
|
set_select(f) {
|
|
this.options.select = f;
|
|
}
|
|
/**
|
|
* Ask server for entries matching selected app/type and filtered by search string
|
|
*
|
|
* @param {Object} request
|
|
* @param {Object} response
|
|
*/
|
|
query(request, response) {
|
|
// If there is a pending request, abort it
|
|
if (this.request) {
|
|
this.request.abort();
|
|
this.request = null;
|
|
}
|
|
// Remember last search
|
|
this.last_search = this.search.val();
|
|
// Allow hook / tie in
|
|
if (this.options.query && typeof this.options.query == 'function') {
|
|
if (!this.options.query(request, this))
|
|
return false;
|
|
}
|
|
if ((typeof request.no_cache == 'undefined' && !request.no_cache) && request.term in this.cache) {
|
|
return response(this.cache[request.term]);
|
|
}
|
|
// Remember callback
|
|
this.response = response;
|
|
this.search.addClass("loading");
|
|
// Remove specific display and revert to CSS file
|
|
// show() would use inline, should be inline-block
|
|
this.clear.css('display', '');
|
|
this.request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_search", [this.app_select.val(), '', request.term, request.options], this._results, this, true, this).sendRequest();
|
|
}
|
|
/**
|
|
* User selected a value
|
|
*
|
|
* @param {Object} event
|
|
* @param {Object} selected
|
|
*
|
|
*/
|
|
select(event, selected) {
|
|
if (selected.item.value !== null && typeof selected.item.value == "string") {
|
|
// Correct changed value from server
|
|
selected.item.value = selected.item.value.trim();
|
|
}
|
|
if (this.options.select && typeof this.options.select == 'function') {
|
|
if (!this.options.select(event, selected))
|
|
return false;
|
|
}
|
|
if (typeof event.data.options.value != 'object' || event.data.options.value == null) {
|
|
event.data.options.value = {};
|
|
}
|
|
event.data.options.value.id = selected.item.value;
|
|
// Set a processing flag to filter some events
|
|
event.data.processing = true;
|
|
// Remove specific display and revert to CSS file
|
|
// show() would use inline, should be inline-block
|
|
this.clear.css('display', '');
|
|
event.data.search.val(selected.item.label);
|
|
// Fire change event
|
|
this.search.change();
|
|
// Turn off processing flag when done
|
|
window.setTimeout(jQuery.proxy(function () { delete this.processing; }, event.data));
|
|
}
|
|
/**
|
|
* Server found some results
|
|
*
|
|
* @param {Array} data
|
|
*/
|
|
_results(data) {
|
|
if (this.request) {
|
|
this.request = null;
|
|
}
|
|
this.search.removeClass("loading");
|
|
var result = [];
|
|
for (var id in data) {
|
|
result.push({ "value": id, "label": data[id] });
|
|
}
|
|
this.cache[this.search.val()] = result;
|
|
this.response(result);
|
|
}
|
|
/**
|
|
* Create a link using the current internal values
|
|
*
|
|
* @param {Object} event
|
|
* @param {Object} _links
|
|
*/
|
|
createLink(event, _links) {
|
|
var values = event.data.options.value;
|
|
var self = event.data;
|
|
var links = [];
|
|
if (typeof _links == 'undefined') {
|
|
links = [];
|
|
}
|
|
else {
|
|
links = _links;
|
|
}
|
|
// Links to other entries
|
|
if (values.id) {
|
|
links.push({
|
|
app: values.app,
|
|
id: values.id
|
|
});
|
|
self.search.val("");
|
|
}
|
|
// If a link array was passed in, don't make the ajax call
|
|
if (typeof _links == 'undefined') {
|
|
var request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link", [values.to_app, values.to_id, links], self._link_result, this, true);
|
|
request.sendRequest();
|
|
}
|
|
}
|
|
/**
|
|
* Sent some links, server has a result
|
|
*
|
|
* @param {Object} success
|
|
*
|
|
*/
|
|
_link_result(success) {
|
|
if (success) {
|
|
this.status_span.fadeIn().delay(1000).fadeOut();
|
|
delete this.options.value.app;
|
|
delete this.options.value.id;
|
|
}
|
|
}
|
|
}
|
|
et2_link_entry._attributes = {
|
|
"value": {
|
|
"type": "any",
|
|
"default": {}
|
|
},
|
|
"only_app": {
|
|
"name": "Application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Limit to just this one application - hides app selection"
|
|
},
|
|
"application_list": {
|
|
"name": "Application list",
|
|
"type": "any",
|
|
"default": "",
|
|
"description": "Limit to the listed applications (comma seperated)"
|
|
},
|
|
"app_icons": {
|
|
"name": "Application icons",
|
|
"type": "boolean",
|
|
"default": false,
|
|
"description": "Show application icons instead of names"
|
|
},
|
|
"blur": {
|
|
"name": "Placeholder",
|
|
"type": "string",
|
|
"default": et2_no_init,
|
|
"description": "This text get displayed if an input-field is empty and does not have the input-focus (blur). It can be used to show a default value or a kind of help-text.",
|
|
translate: true
|
|
},
|
|
"query": {
|
|
"name": "Query callback",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "Callback before query to server. It will be passed the request & et2_link_entry objects. Must return true, or false to abort query."
|
|
},
|
|
"select": {
|
|
"name": "Select callback",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "Callback when user selects an option. Must return true, or false to abort normal action."
|
|
}
|
|
};
|
|
et2_link_entry.legacyOptions = ["only_app", "application_list"];
|
|
et2_link_entry.search_timeout = 500; //ms after change to send query
|
|
et2_link_entry.minimum_characters = 4; // Don't send query unless there's at least this many chars
|
|
et2_register_widget(et2_link_entry, ["link-entry"]);
|
|
/**
|
|
* UI widget for a single (read-only) link
|
|
*
|
|
*/
|
|
export class et2_link extends et2_valueWidget {
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @memberOf et2_link
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link._attributes, _child || {}));
|
|
this.label_span = jQuery(document.createElement("label"))
|
|
.addClass("et2_label");
|
|
this.link = jQuery(document.createElement("span"))
|
|
.addClass("et2_link")
|
|
.appendTo(this.label_span);
|
|
if (this.options['class'])
|
|
this.label_span.addClass(this.options['class']);
|
|
this.setDOMNode(this.label_span[0]);
|
|
}
|
|
destroy() {
|
|
if (this.link)
|
|
this.link.unbind();
|
|
this.link = null;
|
|
super.destroy.apply(this, arguments);
|
|
}
|
|
set_label(label) {
|
|
// Remove current label
|
|
this.label_span.contents()
|
|
.filter(function () { return this.nodeType == 3; }).remove();
|
|
var parts = et2_csvSplit(label, 2, "%s");
|
|
this.label_span.prepend(parts[0]);
|
|
this.label_span.append(parts[1]);
|
|
this.label = label;
|
|
// add class if label is empty
|
|
this.label_span.toggleClass('et2_label_empty', !label || !parts[0]);
|
|
}
|
|
set_value(_value) {
|
|
if (typeof _value != 'object' && _value && !this.options.only_app) {
|
|
if (_value.indexOf(':') >= 0) {
|
|
var app = _value.split(':', 1);
|
|
var id = _value.substr(app[0].length + 1);
|
|
_value = { 'app': app[0], 'id': id };
|
|
}
|
|
else {
|
|
console.warn("Bad value for link widget. Need an object with keys 'app', 'id', and optionally 'title'", _value);
|
|
return;
|
|
}
|
|
}
|
|
// Application set, just passed ID
|
|
else if (typeof _value != "object") {
|
|
_value = {
|
|
app: this.options.only_app,
|
|
id: _value
|
|
};
|
|
}
|
|
if (!_value || jQuery.isEmptyObject(_value)) {
|
|
this.link.text("").unbind();
|
|
return;
|
|
}
|
|
var self = this;
|
|
this.link.unbind();
|
|
if (_value.id && _value.app) {
|
|
this.link.addClass("et2_link");
|
|
this.link.click(function (e) {
|
|
// try to fetch value.title if it wasn't fetched during initiation.
|
|
if (!_value.title)
|
|
_value.title = self.egw().link_title(_value.app, _value.id);
|
|
if (!self.options.target_app) {
|
|
self.options.target_app = _value.app;
|
|
}
|
|
const target = self.options.extra_link_target || _value.app;
|
|
self.egw().open(_value, "", self.options.link_hook, _value.extra_args, target, self.options.target_app);
|
|
e.stopImmediatePropagation();
|
|
});
|
|
}
|
|
else {
|
|
this.link.removeClass("et2_link");
|
|
}
|
|
if (!_value.title) {
|
|
var self = this;
|
|
var node = this.link[0];
|
|
if (_value.app && _value.id) {
|
|
var title = this.egw().link_title(_value.app, _value.id, function (title) { self.set_title(node, title); }, this);
|
|
if (title != null) {
|
|
_value.title = title;
|
|
}
|
|
else {
|
|
// Title will be fetched from server and then set
|
|
return;
|
|
}
|
|
}
|
|
else {
|
|
_value.title = "";
|
|
}
|
|
}
|
|
this.set_title(this.link, _value.title);
|
|
}
|
|
/**
|
|
* Sets the text to be displayed.
|
|
* Used as a callback, so node is provided to make sure we get the right one
|
|
*
|
|
* @param {Object} node
|
|
* @param {String} _value description
|
|
*/
|
|
set_title(node, _value) {
|
|
if (_value === false || _value === null)
|
|
_value = "";
|
|
if (this.options.break_title) {
|
|
// Set up title to optionally break on the provided character - replace all space with nbsp, add a
|
|
// zero-width space after the break string
|
|
_value = _value
|
|
.replace(this.options.break_title, this.options.break_title.trimEnd() + "\u200B")
|
|
.replace(/ /g, '\u00a0');
|
|
}
|
|
jQuery(node).text(_value + "");
|
|
}
|
|
/**
|
|
* Creates a list of attributes which can be set when working in the
|
|
* "detached" mode. The result is stored in the _attrs array which is provided
|
|
* by the calling code.
|
|
*
|
|
* @param {Array} _attrs an array of attributes
|
|
*/
|
|
getDetachedAttributes(_attrs) {
|
|
_attrs.push("label", "value");
|
|
}
|
|
/**
|
|
* Returns an array of DOM nodes. The (relatively) same DOM-Nodes have to be
|
|
* passed to the "setDetachedAttributes" function in the same order.
|
|
*/
|
|
getDetachedNodes() {
|
|
return [this.node, this.link[0]];
|
|
}
|
|
/**
|
|
* Sets the given associative attribute->value array and applies the
|
|
* attributes to the given DOM-Node.
|
|
*
|
|
* @param _nodes is an array of nodes which have to be in the same order as
|
|
* the nodes returned by "getDetachedNodes"
|
|
* @param _values is an associative array which contains a subset of attributes
|
|
* returned by the "getDetachedAttributes" function and sets them to the
|
|
* given values.
|
|
*/
|
|
setDetachedAttributes(_nodes, _values) {
|
|
this.node = _nodes[0];
|
|
this.label_span = jQuery(_nodes[0]);
|
|
this.link = jQuery(_nodes[1]);
|
|
if (typeof _values["id"] !== "undefined")
|
|
this.set_id(_values['id']);
|
|
if (typeof _values["label"] !== "undefined")
|
|
this.set_label(_values['label']);
|
|
if (typeof _values["value"] !== "undefined")
|
|
this.set_value(_values["value"]);
|
|
}
|
|
}
|
|
et2_link._attributes = {
|
|
"only_app": {
|
|
"name": "Application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Use the given application, so you can pass just the ID for value"
|
|
},
|
|
"value": {
|
|
description: "Array with keys app, id, and optionally title",
|
|
type: "any"
|
|
},
|
|
"needed": {
|
|
"ignore": true
|
|
},
|
|
"link_hook": {
|
|
"name": "Type",
|
|
"type": "string",
|
|
"default": "view",
|
|
"description": "Hook used for displaying link (view/edit/add)"
|
|
},
|
|
"target_app": {
|
|
"name": "Target application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Optional parameter to be passed to egw().open in order to open links in specified application"
|
|
},
|
|
"extra_link_target": {
|
|
"name": "Link target",
|
|
"type": "string",
|
|
"default": null,
|
|
"description": "Optional parameter to be passed to egw().open in order to open links in specified target eg. _blank"
|
|
},
|
|
"break_title": {
|
|
"name": "break title",
|
|
"type": "string",
|
|
"default": null,
|
|
"description": "Breaks title into multiple lines based on selected delimiter by replacing it with '\r\n'"
|
|
}
|
|
};
|
|
et2_link.legacyOptions = ["only_app"];
|
|
et2_register_widget(et2_link, ["link", "link-entry_ro"]);
|
|
/**
|
|
* UI widget for one or more links, comma separated
|
|
*
|
|
* TODO: This one used to have expose
|
|
*/
|
|
export class et2_link_string extends expose((_a = class et2_link_string extends et2_valueWidget {
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @memberOf et2_link_string
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_string._attributes, _child || {}));
|
|
this.list = jQuery(document.createElement("ul"))
|
|
.addClass("et2_link_string");
|
|
if (this.options['class'])
|
|
this.list.addClass(this.options['class']);
|
|
this.setDOMNode(this.list[0]);
|
|
}
|
|
destroy() {
|
|
super.destroy.apply(this, arguments);
|
|
if (this.node != null) {
|
|
jQuery(this.node).children().unbind();
|
|
}
|
|
}
|
|
set_value(_value) {
|
|
// Get data
|
|
if (!_value || _value == null || !this.list) {
|
|
// List can be missing if the AJAX call returns after the form is destroyed
|
|
if (this.list) {
|
|
this.list.empty();
|
|
}
|
|
return;
|
|
}
|
|
if (typeof _value == "string" && _value.indexOf(',') > 0) {
|
|
_value = _value.split(',');
|
|
}
|
|
if (!_value.to_app && typeof _value == "object" && this.options.application) {
|
|
_value.to_app = this.options.application;
|
|
}
|
|
if (typeof _value == 'object' && _value.to_app && _value.to_id) {
|
|
this.value = _value;
|
|
this._get_links();
|
|
return;
|
|
}
|
|
this.list.empty();
|
|
if (typeof _value == 'object' && _value.length > 0) {
|
|
// Have full info
|
|
// Don't store new value, just update display
|
|
// Make new links
|
|
for (var i = 0; i < _value.length; i++) {
|
|
if (!this.options.only_app || this.options.only_app && _value[i].app == this.options.only_app) {
|
|
this._add_link(_value[i].id ? _value[i] : { id: _value[i], app: _value.to_app });
|
|
}
|
|
}
|
|
}
|
|
else if (this.options.application) {
|
|
this._add_link({ id: _value, app: this.options.application });
|
|
}
|
|
}
|
|
_get_links() {
|
|
var _value = this.value;
|
|
// Just IDs - get from server
|
|
if (this.options.only_app) {
|
|
_value.only_app = this.options.only_app;
|
|
}
|
|
this.egw().jsonq('EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_list', [_value], this.set_value, this);
|
|
return;
|
|
}
|
|
/**
|
|
* 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(/^\//, 'ig') ? egw(window).window.location.origin + egw.webserverUrl : egw.webserverUrl;
|
|
let mediaContent = [];
|
|
if (_value && typeof _value.type != 'undefined' && _value.type.match(/video\/|audio\//, 'ig')) {
|
|
mediaContent = [{
|
|
title: _value.id,
|
|
type: _value.type,
|
|
poster: '',
|
|
href: base_url + egw().mime_open(_value),
|
|
download_href: base_url + egw().mime_open(_value) + '?download'
|
|
}];
|
|
}
|
|
else if (_value) {
|
|
mediaContent = [{
|
|
title: _value.id,
|
|
href: base_url + egw().mime_open(_value).url,
|
|
download_href: base_url + egw().mime_open(_value).url + '?download',
|
|
type: _value.type
|
|
}];
|
|
}
|
|
if (mediaContent[0].href && mediaContent[0].href.match(/\/webdav.php/, 'ig'))
|
|
mediaContent[0]["download_href"] = mediaContent[0].href + '?download';
|
|
return mediaContent;
|
|
}
|
|
_add_link(_link_data) {
|
|
var self = this;
|
|
var link = jQuery(document.createElement("li"))
|
|
.appendTo(this.list)
|
|
.addClass("et2_link loading")
|
|
.click(function (e) {
|
|
var fe = egw_get_file_editor_prefered_mimes(_link_data.type);
|
|
if (self.options.expose_view && typeof _link_data.type != 'undefined'
|
|
&& _link_data.type.match(self.mime_regexp, 'ig') && !_link_data.type.match(self.mime_audio_regexp, 'ig')) {
|
|
self._init_blueimp_gallery(e, _link_data);
|
|
}
|
|
else if (_link_data.type && _link_data.type.match(self.mime_audio_regexp, 'ig')) {
|
|
self._audio_player(_link_data);
|
|
}
|
|
else if (typeof _link_data.type != 'undefined' && fe && fe.mime && fe.mime[_link_data.type]) {
|
|
egw.open_link(egw.link('/index.php', {
|
|
menuaction: fe.edit.menuaction,
|
|
path: egw().mime_open(_link_data).url.replace('/webdav.php', '')
|
|
}), '', fe.edit_popup);
|
|
}
|
|
else {
|
|
self.egw().open(_link_data, "", "view", null, _link_data.app, _link_data.app);
|
|
}
|
|
e.stopImmediatePropagation();
|
|
});
|
|
if (_link_data.title) {
|
|
link.text(_link_data.title)
|
|
.removeClass("loading");
|
|
}
|
|
// Now that link is created, get title from server & update
|
|
else {
|
|
this.egw().link_title(_link_data.app, _link_data.id, function (title) {
|
|
if (title)
|
|
this.removeClass("loading").text(title);
|
|
else
|
|
this.remove(); // no rights or not found
|
|
}, link);
|
|
}
|
|
}
|
|
/**
|
|
* Creates a list of attributes which can be set when working in the
|
|
* "detached" mode. The result is stored in the _attrs array which is provided
|
|
* by the calling code.
|
|
*
|
|
* @param {Array} _attrs an array of attributes
|
|
*/
|
|
getDetachedAttributes(_attrs) {
|
|
// 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]);
|
|
this.getSurroundings().update();
|
|
}
|
|
_attrs.push("value", "label");
|
|
}
|
|
/**
|
|
* Returns an array of DOM nodes. The (relatively) same DOM-Nodes have to be
|
|
* passed to the "setDetachedAttributes" function in the same order.
|
|
*/
|
|
getDetachedNodes() {
|
|
// 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]);
|
|
}
|
|
return [this.list[0], this._labelContainer[0]];
|
|
}
|
|
/**
|
|
* Sets the given associative attribute->value array and applies the
|
|
* attributes to the given DOM-Node.
|
|
*
|
|
* @param _nodes is an array of nodes which have to be in the same order as
|
|
* the nodes returned by "getDetachedNodes"
|
|
* @param _values is an associative array which contains a subset of attributes
|
|
* returned by the "getDetachedAttributes" function and sets them to the
|
|
* given values.
|
|
*/
|
|
setDetachedAttributes(_nodes, _values) {
|
|
this.list = jQuery(_nodes[0]);
|
|
this.set_value(_values["value"]);
|
|
// Special detached, to prevent DOM node modification of the normal method
|
|
this._labelContainer = _nodes.length > 1 ? jQuery(_nodes[1]) : null;
|
|
if (_values['label']) {
|
|
this.set_label(_values['label']);
|
|
}
|
|
else if (this._labelContainer) {
|
|
this._labelContainer.contents().not(this.list).remove();
|
|
}
|
|
}
|
|
},
|
|
_a._attributes = {
|
|
"application": {
|
|
"name": "Application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Use the given application, so you can pass just the ID for value"
|
|
},
|
|
"value": {
|
|
"description": "Either an array of link information (see egw_link::link()) or array with keys to_app and to_id",
|
|
"type": "any"
|
|
},
|
|
"only_app": {
|
|
"name": "Application filter",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Appname, eg. 'projectmananager' to list only linked projects"
|
|
},
|
|
"link_type": {
|
|
"name": "Type filter",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Sub-type key to list only entries of that type"
|
|
},
|
|
"expose_view": {
|
|
name: "Expose view",
|
|
type: "boolean",
|
|
default: true,
|
|
description: "Clicking on description with href value would popup an expose view, and will show content referenced by href."
|
|
}
|
|
},
|
|
_a)) {
|
|
}
|
|
;
|
|
et2_register_widget(et2_link_string, ["link-string"]);
|
|
/**
|
|
* UI widget for one or more links in a list (table)
|
|
*/
|
|
export class et2_link_list extends et2_link_string {
|
|
/**
|
|
* Constructor
|
|
*
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_list._attributes, _child || {}));
|
|
this.list = jQuery(document.createElement("table"))
|
|
.addClass("et2_link_list");
|
|
if (this.options['class'])
|
|
this.list.addClass(this.options['class']);
|
|
this.setDOMNode(this.list[0]);
|
|
// Set up context menu
|
|
var self = this;
|
|
this.context = new egwMenu();
|
|
this.context.addItem("comment", this.egw().lang("Comment"), "", function () {
|
|
var link_id = typeof self.context.data.link_id == 'number' ? self.context.data.link_id : self.context.data.link_id.replace(/[:\.]/g, '_');
|
|
et2_dialog.show_prompt(function (button, comment) {
|
|
if (button != et2_dialog.OK_BUTTON)
|
|
return;
|
|
var remark = jQuery('#link_' + (self.context.data.dom_id ? self.context.data.dom_id : link_id), self.list).children('.remark');
|
|
if (isNaN(self.context.data.link_id)) // new entry, not yet stored
|
|
{
|
|
remark.text(comment);
|
|
// Look for a link-to with the same ID, refresh it
|
|
if (self.context.data.link_id) {
|
|
var _widget = link_id.widget || null;
|
|
self.getRoot().iterateOver(function (widget) {
|
|
if (widget.id == self.id) {
|
|
_widget = widget;
|
|
}
|
|
}, self, et2_link_to);
|
|
var value = _widget != null ? _widget.getValue() : false;
|
|
if (_widget && value && value.to_id) {
|
|
value.to_id[self.context.data.link_id].remark = comment;
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
remark.addClass("loading");
|
|
var request = egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_link_comment", [link_id, comment], function () {
|
|
if (remark) {
|
|
// Append "" to make sure it's a string, not undefined
|
|
remark.removeClass("loading").text(comment + "");
|
|
// Update internal data
|
|
self.context.data.remark = comment + "";
|
|
}
|
|
}, this, true).sendRequest();
|
|
}, '', self.egw().lang("Comment"), self.context.data.remark || '');
|
|
});
|
|
this.context.addItem("file_info", this.egw().lang("File information"), this.egw().image("edit"), function (menu_item) {
|
|
var link_data = self.context.data;
|
|
if (link_data.app == 'file') {
|
|
// File info is always the same
|
|
var url = '/apps/' + link_data.app2 + '/' + link_data.id2 + '/' + decodeURIComponent(link_data.id);
|
|
if (typeof url == 'string' && url.indexOf('webdav.php')) {
|
|
// URL is url to file in webdav, so get rid of that part
|
|
url = url.replace('/webdav.php', '');
|
|
}
|
|
self.egw().open(url, "filemanager", "edit");
|
|
}
|
|
});
|
|
this.context.addItem("-", "-");
|
|
this.context.addItem("save", this.egw().lang("Save as"), this.egw().image('save'), function (menu_item) {
|
|
var link_data = self.context.data;
|
|
// Download file
|
|
if (link_data.download_url) {
|
|
var url = link_data.download_url;
|
|
if (url[0] == '/')
|
|
url = egw.link(url);
|
|
let a = document.createElement('a');
|
|
if (typeof a.download == "undefined") {
|
|
window.location.href = url + "?download";
|
|
return false;
|
|
}
|
|
// Multiple file download for those that support it
|
|
a = jQuery(a)
|
|
.prop('href', url)
|
|
.prop('download', link_data.title || "")
|
|
.appendTo(self.getInstanceManager().DOMContainer);
|
|
var evt = document.createEvent('MouseEvent');
|
|
evt.initMouseEvent('click', true, true, window, 1, 0, 0, 0, 0, false, false, false, false, 0, null);
|
|
a[0].dispatchEvent(evt);
|
|
a.remove();
|
|
return false;
|
|
}
|
|
self.egw().open(link_data, "", "view", 'download', link_data.target ? link_data.target : link_data.app, link_data.app);
|
|
});
|
|
this.context.addItem("zip", this.egw().lang("Save as Zip"), this.egw().image('save_zip'), function (menu_item) {
|
|
// Highlight files for nice UI indicating what will be in the zip.
|
|
// Files have negative IDs.
|
|
jQuery('[id^="link_-"]', this.list).effect('highlight', {}, 2000);
|
|
// Download ZIP
|
|
window.location = self.egw().link('/index.php', {
|
|
menuaction: 'api.EGroupware\\Api\\Etemplate\\Widget\\Link.download_zip',
|
|
app: self.value.to_app,
|
|
id: self.value.to_id
|
|
});
|
|
});
|
|
// Only allow this option if the entry has been saved, and has a real ID
|
|
if (self.options.value && self.options.value.to_id && typeof self.options.value.to_id != 'object') {
|
|
this.context.addItem("copy_to", this.egw().lang("Copy to"), this.egw().image('copy'), function (menu_item) {
|
|
// Highlight files for nice UI indicating what will be copied
|
|
jQuery('[id="link_' + self.context.data.link_id + ']', this.list).effect('highlight', {}, 2000);
|
|
// Get target
|
|
var select_attrs = {
|
|
mode: "select-dir",
|
|
button_caption: '',
|
|
button_icon: 'copy',
|
|
button_label: egw.lang("copy"),
|
|
//extra_buttons: [{text: egw.lang("link"), id:"link", image: "link"}],
|
|
dialog_title: egw.lang('Copy to'),
|
|
method: "EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_copy_to",
|
|
method_id: self.context.data
|
|
};
|
|
let vfs_select = et2_createWidget("vfs-select", select_attrs, self);
|
|
// No button, just open it
|
|
vfs_select.button.hide();
|
|
vfs_select.click(null);
|
|
});
|
|
}
|
|
this.context.addItem("-", "-");
|
|
this.context.addItem("delete", this.egw().lang("Delete link"), this.egw().image("delete"), function (menu_item) {
|
|
var link_id = isNaN(self.context.data.link_id) ? self.context.data : self.context.data.link_id;
|
|
var row = jQuery('#link_' + (self.context.data.dom_id ? self.context.data.dom_id : self.context.data.link_id), self.list);
|
|
et2_dialog.show_dialog(function (button) { if (button == et2_dialog.YES_BUTTON)
|
|
self._delete_link(link_id, row); }, egw.lang('Delete link?'));
|
|
});
|
|
// Native DnD - Doesn't play nice with jQueryUI Sortable
|
|
// Tell jQuery to include this property
|
|
jQuery.event.props.push('dataTransfer');
|
|
}
|
|
destroy() {
|
|
super.destroy.apply(this, arguments);
|
|
if (this.context) {
|
|
this.context.clear();
|
|
delete this.context;
|
|
}
|
|
}
|
|
set_value(_value) {
|
|
this.list.empty();
|
|
// Handle server passed a list of links that aren't ready yet
|
|
if (_value && typeof _value == "object") {
|
|
var list = [];
|
|
if (_value.to_id && typeof _value.to_id == "object") {
|
|
list = _value.to_id;
|
|
}
|
|
else if (_value.length) {
|
|
list = _value;
|
|
}
|
|
if (list.length > 0) {
|
|
for (var id in list) {
|
|
var link = list[id];
|
|
if (link.app) {
|
|
// Temp IDs can cause problems since the ID includes the file name or :
|
|
if (link.link_id && typeof link.link_id != 'number') {
|
|
link.dom_id = 'temp_' + egw.uid();
|
|
}
|
|
// Icon should be in registry
|
|
if (!link.icon) {
|
|
link.icon = egw.link_get_registry(link.app, 'icon');
|
|
// No icon, try by mime type - different place for un-saved entries
|
|
if (link.icon == false && link.id.type) {
|
|
// Triggers icon by mime type, not thumbnail or app
|
|
link.type = link.id.type;
|
|
link.icon = true;
|
|
}
|
|
}
|
|
// Special handling for file - if not existing, we can't ask for title
|
|
if (typeof link.id == 'object' && !link.title) {
|
|
link.title = link.id.name || '';
|
|
}
|
|
this._add_link(link);
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
super.set_value(_value);
|
|
}
|
|
}
|
|
}
|
|
_add_link(_link_data) {
|
|
var row = jQuery(document.createElement("tr"))
|
|
.attr("id", "link_" + (_link_data.dom_id ? _link_data.dom_id : (typeof _link_data.link_id == "string" ? _link_data.link_id.replace(/[:\.]/g, '_') : _link_data.link_id || _link_data.id)))
|
|
.attr("draggable", _link_data.app == 'file' ? "true" : "")
|
|
.appendTo(this.list);
|
|
if (!_link_data.link_id) {
|
|
for (var k in _link_data) {
|
|
row[0].dataset[k] = _link_data[k];
|
|
}
|
|
}
|
|
// Icon
|
|
var icon = jQuery(document.createElement("td"))
|
|
.appendTo(row)
|
|
.addClass("icon");
|
|
if (_link_data.icon) {
|
|
var icon_widget = et2_createWidget("image", {});
|
|
var src = '';
|
|
// Creat a mime widget if the link has type
|
|
if (_link_data.type) {
|
|
// VFS - file
|
|
var vfs_widget = et2_createWidget('vfs-mime', {});
|
|
vfs_widget.set_value({
|
|
download_url: _link_data.download_url,
|
|
name: _link_data.title,
|
|
mime: _link_data.type,
|
|
path: _link_data.icon
|
|
});
|
|
icon.append(vfs_widget.getDOMNode());
|
|
}
|
|
else {
|
|
src = this.egw().image(_link_data.icon);
|
|
if (src)
|
|
icon_widget.set_src(src);
|
|
icon.append(icon_widget.getDOMNode());
|
|
}
|
|
}
|
|
var columns = ['title', 'remark'];
|
|
var self = this;
|
|
for (var i = 0; i < columns.length; i++) {
|
|
var $td = jQuery(document.createElement("td"))
|
|
.appendTo(row)
|
|
.addClass(columns[i])
|
|
.text(_link_data[columns[i]] ? _link_data[columns[i]] + "" : "");
|
|
var dirs = _link_data[columns[i]] ? _link_data[columns[i]].split('/') : [];
|
|
if (columns[i] == 'title' && _link_data.type && dirs.length > 1) {
|
|
this._format_vfs($td, dirs, _link_data);
|
|
}
|
|
//Bind the click handler if there is download_url
|
|
if (_link_data && (typeof _link_data.download_url != 'undefined' || _link_data.app != 'egw-data')) {
|
|
$td.click(function () {
|
|
var fe_mime = egw_get_file_editor_prefered_mimes(_link_data.type);
|
|
// Check if the link entry is mime with media type, in order to open it in expose view
|
|
if (typeof _link_data.type != 'undefined' &&
|
|
(_link_data.type.match(self.mime_regexp, 'ig') || (fe_mime && fe_mime.mime[_link_data.type]))) {
|
|
var $vfs_img_node = jQuery(this).parent().find('.vfsMimeIcon');
|
|
if ($vfs_img_node.length > 0)
|
|
$vfs_img_node.click();
|
|
}
|
|
else {
|
|
if (!self.options.target_app) {
|
|
self.options.target_app = _link_data.app;
|
|
}
|
|
self.egw().open(_link_data, "", "view", null, _link_data.target ? _link_data.target : _link_data.app, self.options.target_app);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
if (typeof _link_data.title == 'undefined') {
|
|
// Title will be fetched from server and then set
|
|
jQuery('td.title', row).addClass("loading");
|
|
var title = this.egw().link_title(_link_data.app, _link_data.id, function (title) {
|
|
jQuery('td.title', this).removeClass("loading").text(title + "");
|
|
}, row);
|
|
}
|
|
// Date
|
|
/*
|
|
var date_row = jQuery(document.createElement("td"))
|
|
.appendTo(row);
|
|
if(_link_data.lastmod)
|
|
{
|
|
var date_widget = et2_createWidget("date-since");
|
|
date_widget.set_value(_link_data.lastmod);
|
|
date_row.append(date_widget.getDOMNode());
|
|
}
|
|
*/
|
|
// Delete
|
|
// build delete button if the link is not readonly
|
|
if (!this.options.readonly) {
|
|
var delete_button = jQuery(document.createElement("td"))
|
|
.appendTo(row);
|
|
jQuery("<div />")
|
|
.appendTo(delete_button)
|
|
// We don't use ui-icon because it assigns a bg image
|
|
.addClass("delete icon")
|
|
.bind('click', function () {
|
|
et2_dialog.show_dialog(function (button) {
|
|
if (button == et2_dialog.YES_BUTTON) {
|
|
self._delete_link(self.value && typeof self.value.to_id != 'object' && _link_data.link_id ? _link_data.link_id : _link_data, row);
|
|
}
|
|
}, egw.lang('Delete link?'));
|
|
});
|
|
}
|
|
// Context menu
|
|
row.bind("contextmenu", function (e) {
|
|
// Comment only available if link_id is there and not readonly
|
|
self.context.getItem("comment").set_enabled(typeof _link_data.link_id != 'undefined' && !self.options.readonly);
|
|
// File info only available for existing files
|
|
self.context.getItem("file_info").set_enabled(typeof _link_data.id != 'object' && _link_data.app == 'file');
|
|
self.context.getItem("save").set_enabled(typeof _link_data.id != 'object' && _link_data.app == 'file');
|
|
// Zip download only offered if there are at least 2 files
|
|
self.context.getItem("zip").set_enabled(jQuery('[id^="link_-"]', this.list).length >= 2);
|
|
// Show delete item only if the widget is not readonly
|
|
self.context.getItem("delete").set_enabled(!self.options.readonly);
|
|
self.context.data = _link_data;
|
|
self.context.showAt(e.pageX, e.pageY, true);
|
|
e.preventDefault();
|
|
});
|
|
// Drag - adapted from egw_action_dragdrop, sidestepping action system
|
|
// so all linked files get it
|
|
// // Unfortunately, dragging files is currently only supported by Chrome
|
|
if (navigator && navigator.userAgent.indexOf('Chrome') >= 0) {
|
|
row.on("dragstart", _link_data, function (event) {
|
|
if (event.dataTransfer == null) {
|
|
return;
|
|
}
|
|
var data = event.data || {};
|
|
if (data && data.type && data.download_url) {
|
|
event.dataTransfer.dropEffect = "copy";
|
|
event.dataTransfer.effectAllowed = "copy";
|
|
var url = data.download_url;
|
|
// NEED an absolute URL
|
|
if (url[0] == '/')
|
|
url = egw.link(url);
|
|
// egw.link adds the webserver, but that might not be an absolute URL - try again
|
|
if (url[0] == '/')
|
|
url = window.location.origin + url;
|
|
// Unfortunately, dragging files is currently only supported by Chrome
|
|
if (navigator && navigator.userAgent.indexOf('Chrome')) {
|
|
event.dataTransfer.setData("DownloadURL", data.type + ':' + data.title + ':' + url);
|
|
}
|
|
// Include URL as a fallback
|
|
event.dataTransfer.setData("text/uri-list", url);
|
|
}
|
|
if (event.dataTransfer.types.length == 0) {
|
|
// No file data? Abort: drag does nothing
|
|
event.preventDefault();
|
|
return;
|
|
}
|
|
//event.dataTransfer.setDragImage(event.delegate.target,0,0);
|
|
var div = jQuery(document.createElement("div"))
|
|
.attr('id', 'drag_helper')
|
|
.css({
|
|
position: 'absolute',
|
|
top: '0px',
|
|
left: '0px',
|
|
width: '300px'
|
|
});
|
|
div.append(event.target.cloneNode(true));
|
|
self.list.append(div);
|
|
event.dataTransfer.setDragImage(div.get(0), 0, 0);
|
|
})
|
|
.on('drag', function () {
|
|
jQuery('#drag_helper', self.list).remove();
|
|
});
|
|
}
|
|
}
|
|
_delete_link(link_id, row) {
|
|
if (row) {
|
|
var delete_button = jQuery('.delete', row);
|
|
delete_button.removeClass("delete").addClass("loading");
|
|
row.off();
|
|
}
|
|
if (this.onchange) {
|
|
this.onchange(this, link_id, row);
|
|
}
|
|
if (typeof link_id != "object") {
|
|
egw.json("EGroupware\\Api\\Etemplate\\Widget\\Link::ajax_delete", [link_id], function (data) { if (data) {
|
|
row.slideUp(row.remove);
|
|
} }).sendRequest();
|
|
}
|
|
else if (row) {
|
|
// No link ID means a link on an unsaved entry.
|
|
// Just remove the row, but need to adjust the link_to value also
|
|
row.slideUp(row.remove);
|
|
// Look for a link-to with the same ID, refresh it
|
|
if (link_id.link_id) {
|
|
var self = this;
|
|
var _widget = link_id.widget || null;
|
|
this.getRoot().iterateOver(function (widget) {
|
|
if (widget.id == self.id) {
|
|
_widget = widget;
|
|
}
|
|
}, this, et2_link_to);
|
|
var value = _widget != null ? _widget.getValue() : false;
|
|
if (_widget && value && value.to_id) {
|
|
delete value.to_id[link_id.link_id];
|
|
_widget.set_value(value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/**
|
|
* When the link is to a VFS file, we do some special formatting.
|
|
*
|
|
* Instead of listing the full path, we use
|
|
* Path: - filename
|
|
* When multiple files from the same directory are linked, we exclude
|
|
* the directory name from all but the first link to that directory
|
|
*
|
|
* @param {JQuery} $td Current table data cell for the title
|
|
* @param {String[]} dirs List of directories in the linked file's path
|
|
* @param {String[]} _link_data Data for the egw_link
|
|
* @returns {undefined}
|
|
*/
|
|
_format_vfs($td, dirs, _link_data) {
|
|
// Keep it here for matching next row
|
|
$td.attr('data-title', _link_data['title']);
|
|
// VFS link - check for same dir as above, and hide dir
|
|
var reformat = false;
|
|
var span_size = 1;
|
|
var prev = jQuery('td.title', $td.parent().prev('tr'));
|
|
if (prev.length === 1) {
|
|
var prev_dirs = (prev.attr('data-title') || '').split('/');
|
|
if (prev_dirs.length > 1 && prev_dirs.length == dirs.length) {
|
|
for (var i = 0; i < dirs.length; i++) {
|
|
// Current is same as prev, blank it
|
|
if (dirs[i] === prev_dirs[i]) {
|
|
reformat = true;
|
|
span_size += dirs[i].length + 1;
|
|
dirs[i] = '';
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var filename = dirs.pop();
|
|
if (reformat && (dirs.length - i) === 0) {
|
|
$td.html('<span style="display: inline-block; width:' + span_size + 'ex;"></span> - ' + filename);
|
|
}
|
|
else {
|
|
// Different format for directory
|
|
span_size += dirs.join('/').length + 1;
|
|
$td.html('<span style="display: inline-block; text-align: right; width:' + span_size + 'ex;">' + dirs.join('/') + ':</span> - ' + filename);
|
|
}
|
|
}
|
|
}
|
|
et2_link_list._attributes = {
|
|
"show_deleted": {
|
|
"name": "Show deleted",
|
|
"type": "boolean",
|
|
"default": false,
|
|
"description": "Show links that are marked as deleted, being held for purge"
|
|
},
|
|
"onchange": {
|
|
"name": "onchange",
|
|
"type": "js",
|
|
"default": et2_no_init,
|
|
"description": "JS code which is executed when the links change."
|
|
},
|
|
readonly: {
|
|
name: "readonly",
|
|
type: "boolean",
|
|
"default": false,
|
|
description: "Does NOT allow user to enter data, just displays existing data"
|
|
},
|
|
"target_app": {
|
|
"name": "Target application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Optional parameter to be passed to egw().open in order to open links in specified application "
|
|
}
|
|
};
|
|
et2_register_widget(et2_link_list, ["link-list"]);
|
|
/**
|
|
*
|
|
*
|
|
*/
|
|
export class et2_link_add extends et2_inputWidget {
|
|
/**
|
|
* Constructor
|
|
*/
|
|
constructor(_parent, _attrs, _child) {
|
|
super(_parent, _attrs, ClassWithAttributes.extendAttributes(et2_link_add._attributes, _child || {}));
|
|
this.span = jQuery(document.createElement("span"))
|
|
.text(this.egw().lang("Add new"))
|
|
.addClass('et2_link_add_span');
|
|
this.div = jQuery(document.createElement("div")).append(this.span);
|
|
this.setDOMNode(this.div[0]);
|
|
}
|
|
doLoadingFinished() {
|
|
super.doLoadingFinished.apply(this, arguments);
|
|
if (this.app_select && this.button) {
|
|
// Already done
|
|
return false;
|
|
}
|
|
this.app_select = et2_createWidget("link-apps", jQuery.extend({}, this.options, {
|
|
'id': this.options.id + 'app',
|
|
value: this.options.application ? this.options.application : this.options.value && this.options.value.add_app ? this.options.value.add_app : null,
|
|
application_list: this.options.application ? this.options.application : null
|
|
}), this);
|
|
this.div.append(this.app_select.getDOMNode());
|
|
this.button = et2_createWidget("button", { id: this.options.id + "_add", label: this.egw().lang("add") }, this);
|
|
this.button.set_label(this.egw().lang("add"));
|
|
var self = this;
|
|
this.button.click = function () {
|
|
self.egw().open(self.options.value.to_app + ":" + self.options.value.to_id, self.app_select.get_value(), 'add');
|
|
return false;
|
|
};
|
|
this.div.append(this.button.getDOMNode());
|
|
return true;
|
|
}
|
|
/**
|
|
* Should be handled client side.
|
|
* Return null to avoid overwriting other link values, in case designer used the same ID for multiple widgets
|
|
*/
|
|
getValue() {
|
|
return null;
|
|
}
|
|
}
|
|
et2_link_add._attributes = {
|
|
"value": {
|
|
"description": "Either an array of link information (see egw_link::link()) or array with keys to_app and to_id",
|
|
"type": "any"
|
|
},
|
|
"application": {
|
|
"name": "Application",
|
|
"type": "string",
|
|
"default": "",
|
|
"description": "Limit to the listed application or applications (comma seperated)"
|
|
}
|
|
};
|
|
et2_register_widget(et2_link_add, ["link-add"]);
|
|
//# sourceMappingURL=et2_widget_link.js.map
|