diff --git a/api/js/etemplate/et2_types.d.ts b/api/js/etemplate/et2_types.d.ts index c6616da7c3..4dc22cc7cd 100644 --- a/api/js/etemplate/et2_types.d.ts +++ b/api/js/etemplate/et2_types.d.ts @@ -145,4 +145,11 @@ declare var et2_vfsSelect : any; declare var et2_video : any; declare var et2_IExposable : any; declare function et2_createWidget(type : string, params : {}, parent? : any) : any; -declare function nm_action(_action : {}, _senders : [], _target : any, _ids? : any) : void; \ No newline at end of file +declare function nm_action(_action : {}, _senders : [], _target : any, _ids? : any) : void; +declare function fetchAll(ids, nextmatch, callback : Function) : boolean; +declare function doLongTask(idsArr : string[], all : boolean, _action : any, nextmatch : any) : boolean; +declare function nm_compare_field(_action, _senders, _target) : boolean; +declare function nm_open_popup(_action, _selected) : void; +declare function nm_submit_popup(button) : void; +declare function nm_hide_popup(element, div_id) : false; +declare function nm_activate_link(_action, _senders) : void; \ No newline at end of file diff --git a/api/js/jsapi/egw_global.d.ts b/api/js/jsapi/egw_global.d.ts index ae24b8ac9d..e6af95b112 100644 --- a/api/js/jsapi/egw_global.d.ts +++ b/api/js/jsapi/egw_global.d.ts @@ -40,6 +40,15 @@ declare interface Iapplication icon_app? : string; } +/** + * Data stored by egw_data + */ +declare interface IegwData +{ + timestamp: number; + data: {[key:string]: any}; +} + /** * Interface for all window global methods (existing only in top window) */ @@ -111,15 +120,15 @@ declare interface IegwGlobal * @param _uid is the uid for which should be checked whether it has some * data. */ - dataGetUIDdata(_uid : string) : object; + dataGetUIDdata(_uid : string) : IegwData; /** * Returns all uids that have the given prefix * * @param {string} _prefix - * @return {array} + * @return {array} of uids * TODO: Improve this */ - dataKnownUIDs(_prefix : string) : any[]; + dataKnownUIDs(_prefix : string) : string[]; /** * Stores data for the uid and calls all callback functions registered * for that uid. @@ -682,6 +691,7 @@ declare class JsonRequest */ declare interface IegwWndLocal extends IegwGlobal { + window : Window; /** * implemented in egw_css.js */ @@ -938,7 +948,7 @@ declare interface IegwWndLocal extends IegwGlobal * @param {boolean} _check_popup_blocker TRUE check if browser pop-up blocker is on/off, FALSE no check * - This option only makes sense to be enabled when the open_link requested without user interaction */ - open(id_data : string|number|object, app? : string, type? : "edit"|"view"|"view_list"|"add", + open(id_data : string|number|object, app? : string, type? : "edit"|"view"|"view_list"|"add"|"list", extra? : string|object, target? : string, target_app? : string, _check_popup_blocker? : boolean) : string; /** * Open a link, which can be either a menuaction, a EGroupware relative url or a full url @@ -1185,10 +1195,13 @@ declare interface IegwAppLocal extends IegwWndLocal declare function egw_getFramework() : any; declare var chrome : any; declare var InstallTrigger : any; -declare var app : {classes: any}; +declare var app : {classes: any, [propName: string]: any}; declare var egw_globalObjectManager : any; declare var framework : any; +declare var egw_LAB : any; +declare function egwIsMobile() : string|null; declare var mailvelope : any; -declare function egw_refresh(_msg : string, app : string, id? : string|number, _type?, targetapp?, replace?, _with?, msgtype?); \ No newline at end of file +declare function egw_refresh(_msg : string, app : string, id? : string|number, _type?, targetapp?, replace?, _with?, msgtype?); +declare function egw_open(); \ No newline at end of file diff --git a/infolog/js/app.js b/infolog/js/app.js index 6c17bfa364..405ff112d9 100644 --- a/infolog/js/app.js +++ b/infolog/js/app.js @@ -1,855 +1,721 @@ +"use strict"; /** * EGroupware - Infolog - Javascript UI * - * @link http://www.egroupware.org + * @link: https://www.egroupware.org * @package infolog * @author Hadi Nategh * @copyright (c) 2008-13 by Ralf Becker * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License - * @version $Id$ */ - +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +Object.defineProperty(exports, "__esModule", { value: true }); +/*egw:uses + /api/js/jsapi/egw_app.js + */ +require("jquery"); +require("jqueryui"); +require("../jsapi/egw_global"); +require("../etemplate/et2_types"); +var egw_app_1 = require("../../api/js/jsapi/egw_app"); /** * UI for Infolog * * @augments AppJS */ -app.classes.infolog = AppJS.extend( -{ - appname: 'infolog', - - /** - * Constructor - * - * @memberOf app.infolog - */ - init: function() - { - // call parent - this._super.apply(this, arguments); - }, - - /** - * Destructor - */ - destroy: function() - { - // 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 {etemplate2} _et2 newly ready object - * @param {string} _name template name - */ - et2_ready: function(_et2, _name) - { - // call parent - this._super.apply(this, arguments); - - 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.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);});}); - } - 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: function(_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: function() - { - // call parent - var state = this._super.apply(this, arguments); - var nm = {}; - - // Get index etemplate - var et2 = etemplate2.getById('infolog-index'); - if(et2) - { - var 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: function(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 this._super.apply(this, arguments); - }, - - /** - * 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: function() - { - 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: function(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.value); - - // Change preference location - widget is nextmatch - nm.options.settings.columnselection_pref = nm.options.settings.columnselection_pref.replace('-details','') + (filter2.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 = true; - - // 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: function(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_2: function (_action, _senders) - { - var children = false; - var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0); - if(child_button) - { - for(var i = 0; i < _senders.length; i++) - { - if (jQuery(_senders[i].iface.node).hasClass('infolog_rowHasSubs')) - { - children = true; - break; - } - } - child_button.style.display = children ? 'block' : 'none'; - } - var callbackDeleteDialog = function (button_id) - { - if (button_id == et2_dialog.YES_BUTTON ) - { - - } - }; - et2_dialog.show_dialog(callbackDeleteDialog, this.egw.lang("Do you really want to DELETE this Rule"),this.egw.lang("Delete"), {},et2_dialog.BUTTONS_YES_NO_CANCEL, et2_dialog.WARNING_MESSAGE); - }, - - /** - * Confirm delete - * If entry has children, asks if you want to delete children too - * - *@param _action - *@param _senders - */ - confirm_delete: function(_action, _senders) - { - var children = false; - var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0); - if(child_button) - { - for(var i = 0; i < _senders.length; i++) - { - if (jQuery(_senders[i].iface.getDOMNode()).hasClass('infolog_rowHasSubs')) - { - children = true; - break; - } - } - child_button.style.display = children ? 'block' : 'none'; - } - nm_open_popup(_action, _senders); - }, - - /** - * Add email from addressbook - * - * @param ab_id - * @param info_cc - */ - add_email_from_ab: function(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 = ''; - 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: function(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') != (percent.value > 0 && percent.value < 100)) - { - if(completed) - { - percent.value = 100; - } - else if (status.value == 'not-started') - { - percent.value = 0; - } - else if (!completed && (percent.value == 0 || percent.value == 100)) - { - percent.value = 10; - } - } - break; - - case percent_id: - completed = percent.value == 100; - if (completed != (status.value == 'done' || status.value == 'billed') || - (status.value == 'not-started') != (percent.value == 0)) - { - status.value = percent.value == 0 ? (jQuery('[value="not-started"]',status).length ? 'not-started':'ongoing') : (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 != (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: function() - { - 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: function(_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: function () - { - 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: function() - { - this.egw.message(this.egw.lang('Printing...')); - this.egw.window.print(); - }, - - /** - * - */ - add_link_sidemenu: function() - { - 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: function(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: function(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: function() - { - 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: function(_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: function(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: function(_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: function(_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: function(_node, _widget) - { - if (_widget.get_value()) this.et2._inst.submit(); - }, - - /** - * Toggle encryption - * - * @param {jQuery.Event} _event - * @param {et2_button} _widget - * @param {DOMNode} _node - */ - toggleEncrypt: function(_event, _widget, _node) - { - if (!this.egw.user('apps').stylite) - { - this.egw.message(this.egw.lang('InfoLog encryption requires EPL Subscription')+': www.egroupware.org/EPL'); - 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: function(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) - { - var self = this; - egw_LAB.script('stylite/js/infolog-encryption.js?'+this.et2.getArrayMgr('content').data.encryption_ts).wait(function() - { - app.stylite = new app.classes.stylite; - app.stylite.et2 = self.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: function(_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: function(_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: function (_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'); - }); - } - -}); +var InfologApp = /** @class */ (function (_super) { + __extends(InfologApp, _super); + /** + * Constructor + * + * @memberOf app.infolog + */ + function InfologApp() { + // call parent + return _super.call(this) || this; + } + /** + * Destructor + */ + InfologApp.prototype.destroy = function () { + // call parent + _super.prototype.destroy.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 {etemplate2} _et2 newly ready object + * @param {string} _name template name + */ + InfologApp.prototype.et2_ready = function (_et2, _name) { + // call parent + _super.prototype.et2_ready.apply(this, arguments); + 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.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); }); }); + } + 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 + */ + InfologApp.prototype.observer = function (_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 + */ + InfologApp.prototype.getState = function () { + // call parent + var state = _super.prototype.observer.apply(this, arguments); + var nm = {}; + // Get index etemplate + var et2 = etemplate2.getById('infolog-index'); + if (et2) { + var 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 + */ + InfologApp.prototype.setState = function (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.prototype.setState.apply(this, arguments); + }; + /** + * 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. + */ + InfologApp.prototype.filter_change = function () { + 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 + */ + InfologApp.prototype.filter2_change = function (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.value); + // Change preference location - widget is nextmatch + nm.options.settings.columnselection_pref = nm.options.settings.columnselection_pref.replace('-details', '') + (filter2.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 = true; + // 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 + */ + InfologApp.prototype.show_details = function (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(); }); + } + }; + InfologApp.prototype.confirm_delete_2 = function (_action, _senders) { + var children = false; + var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0); + if (child_button) { + for (var i = 0; i < _senders.length; i++) { + if (jQuery(_senders[i].iface.node).hasClass('infolog_rowHasSubs')) { + children = true; + break; + } + } + child_button.style.display = children ? 'block' : 'none'; + } + var callbackDeleteDialog = function (button_id) { + if (button_id == et2_dialog.YES_BUTTON) { + } + }; + et2_dialog.show_dialog(callbackDeleteDialog, this.egw.lang("Do you really want to DELETE this Rule"), this.egw.lang("Delete"), {}, et2_dialog.BUTTONS_YES_NO_CANCEL, et2_dialog.WARNING_MESSAGE); + }; + /** + * Confirm delete + * If entry has children, asks if you want to delete children too + * + *@param _action + *@param _senders + */ + InfologApp.prototype.confirm_delete = function (_action, _senders) { + var children = false; + var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0); + if (child_button) { + for (var i = 0; i < _senders.length; i++) { + if (jQuery(_senders[i].iface.getDOMNode()).hasClass('infolog_rowHasSubs')) { + children = true; + break; + } + } + child_button.style.display = children ? 'block' : 'none'; + } + nm_open_popup(_action, _senders); + }; + /** + * Add email from addressbook + * + * @param ab_id + * @param info_cc + */ + InfologApp.prototype.add_email_from_ab = function (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 = ''; + 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 + */ + InfologApp.prototype.status_changed = function (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') != (percent.value > 0 && percent.value < 100)) { + if (completed) { + percent.value = 100; + } + else if (status.value == 'not-started') { + percent.value = 0; + } + else if (!completed && (percent.value == 0 || percent.value == 100)) { + percent.value = 10; + } + } + break; + case percent_id: + completed = percent.value == 100; + if (completed != (status.value == 'done' || status.value == 'billed') || + (status.value == 'not-started') != (percent.value == 0)) { + status.value = percent.value == 0 ? (jQuery('[value="not-started"]', status).length ? 'not-started' : 'ongoing') : (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 != (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. + * + */ + InfologApp.prototype.edit_actions = function () { + 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 + */ + InfologApp.prototype.infolog_menu_print = function (_action, _selected) { + var id = _selected[0].id.replace(/^infolog::/g, ''); + egw_open(id, 'infolog', 'edit', { print: 1 }); + }; + /** + * Trigger print() onload window + */ + InfologApp.prototype.infolog_print_preview_onload = function () { + 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 + */ + InfologApp.prototype.infolog_print_preview = function () { + this.egw.message(this.egw.lang('Printing...')); + this.egw.window.print(); + }; + /** + * + */ + InfologApp.prototype.add_link_sidemenu = function () { + 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 + */ + InfologApp.prototype.add_action_handler = function (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 + */ + InfologApp.prototype.add_with_extras = function (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} + */ + InfologApp.prototype.getWindowTitle = function () { + 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 + */ + InfologApp.prototype.view_parent = function (_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} + */ + InfologApp.prototype.parent_query = function (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 + */ + InfologApp.prototype.timesheet_list = function (_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 + */ + InfologApp.prototype.has_parent = function (_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 + */ + InfologApp.prototype.submit_if_not_empty = function (_node, _widget) { + if (_widget.get_value()) + this.et2._inst.submit(); + }; + /** + * Toggle encryption + * + * @param {jQuery.Event} _event + * @param {et2_button} _widget + * @param {DOMNode} _node + */ + InfologApp.prototype.toggleEncrypt = function (_event, _widget, _node) { + if (!this.egw.user('apps').stylite) { + this.egw.message(this.egw.lang('InfoLog encryption requires EPL Subscription') + ': www.egroupware.org/EPL'); + 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 + * + */ + InfologApp.prototype._get_stylite = function (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) { + var self = this; + egw_LAB.script('stylite/js/infolog-encryption.js?' + this.et2.getArrayMgr('content').data.encryption_ts).wait(function () { + app.stylite = new app.classes.stylite; + app.stylite.et2 = self.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 + */ + InfologApp.prototype.onchangeResponsible = function (_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 + */ + InfologApp.prototype.change_responsible = function (_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 + */ + InfologApp.prototype.printEncrypt = function (_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'); + }); + }; + return InfologApp; +}(egw_app_1.EgwApp)); +app.classes.infolog = InfologApp; +//# sourceMappingURL=app.js.map \ No newline at end of file diff --git a/infolog/js/app.ts b/infolog/js/app.ts new file mode 100644 index 0000000000..49f332410d --- /dev/null +++ b/infolog/js/app.ts @@ -0,0 +1,869 @@ +/** + * EGroupware - Infolog - Javascript UI + * + * @link: https://www.egroupware.org + * @package infolog + * @author Hadi Nategh + * @copyright (c) 2008-13 by Ralf Becker + * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License + */ + +/*egw:uses + /api/js/jsapi/egw_app.js + */ + +import 'jquery'; +import 'jqueryui'; +import '../jsapi/egw_global'; +import '../etemplate/et2_types'; + +import { EgwApp } from '../../api/js/jsapi/egw_app'; + +/** + * UI for Infolog + * + * @augments AppJS + */ +class InfologApp extends EgwApp +{ + readonly appname = 'infolog'; + + /** + * Constructor + * + * @memberOf app.infolog + */ + constructor() + { + // call parent + super(); + } + + /** + * Destructor + */ + destroy() + { + // call parent + super.destroy.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 {etemplate2} _et2 newly ready object + * @param {string} _name template name + */ + et2_ready(_et2, _name) + { + // call parent + super.et2_ready.apply(this, arguments); + + 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.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);});}); + } + 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() + { + // call parent + var state = super.observer.apply(this, arguments); + var nm : any = {}; + + // Get index etemplate + var et2 = etemplate2.getById('infolog-index'); + if(et2) + { + var 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.apply(this, arguments); + } + + /** + * 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.value); + + // Change preference location - widget is nextmatch + nm.options.settings.columnselection_pref = nm.options.settings.columnselection_pref.replace('-details','') + (filter2.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 = true; + + // 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_2(_action, _senders) + { + var children = false; + var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0); + if(child_button) + { + for(var i = 0; i < _senders.length; i++) + { + if (jQuery(_senders[i].iface.node).hasClass('infolog_rowHasSubs')) + { + children = true; + break; + } + } + child_button.style.display = children ? 'block' : 'none'; + } + var callbackDeleteDialog = function (button_id) + { + if (button_id == et2_dialog.YES_BUTTON ) + { + + } + }; + et2_dialog.show_dialog(callbackDeleteDialog, this.egw.lang("Do you really want to DELETE this Rule"),this.egw.lang("Delete"), {},et2_dialog.BUTTONS_YES_NO_CANCEL, et2_dialog.WARNING_MESSAGE); + } + + /** + * Confirm delete + * If entry has children, asks if you want to delete children too + * + *@param _action + *@param _senders + */ + confirm_delete(_action, _senders) + { + var children = false; + var child_button = jQuery('#delete_sub').get(0) || jQuery('[id*="delete_sub"]').get(0); + if(child_button) + { + for(var i = 0; i < _senders.length; i++) + { + if (jQuery(_senders[i].iface.getDOMNode()).hasClass('infolog_rowHasSubs')) + { + children = true; + break; + } + } + child_button.style.display = children ? 'block' : 'none'; + } + nm_open_popup(_action, _senders); + } + + /** + * 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')+': www.egroupware.org/EPL'); + 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 : Function, attrs? : any[]) + { + // use app object from etemplate2, which might be private and not just window.app + var app = this.et2.getInstanceManager().app_obj; + + if (!app.stylite) + { + var self = this; + egw_LAB.script('stylite/js/infolog-encryption.js?'+this.et2.getArrayMgr('content').data.encryption_ts).wait(function() + { + app.stylite = new app.classes.stylite; + app.stylite.et2 = self.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'); + }); + } + +} + +app.classes.infolog = InfologApp;