Etemplate: Add a callback so apps can decide where to put new push rows

This commit is contained in:
nathangray 2020-07-21 15:32:13 -06:00
parent 8e213a7de5
commit f0b924008b
8 changed files with 185 additions and 112 deletions

View File

@ -453,7 +453,8 @@ var et2_nextmatch = /** @class */ (function (_super) {
jQuery(this.getInstanceManager().DOMContainer.parentNode).one('show.et2_nextmatch',
// Important to use anonymous function instead of just 'this.refresh' because
// of the parameters passed
jQuery.proxy(function () { this.refresh(); }, this));
function () { this.nm.refresh(this.ids, this.type); }
.bind({ nm: this, ids: _row_ids, type: _type }));
return;
}
if (typeof _type == 'undefined')
@ -528,14 +529,20 @@ var et2_nextmatch = /** @class */ (function (_super) {
* @param uid
*/
et2_nextmatch.prototype.refresh_add = function (uid) {
var entry = this.controller._selectionMgr._getRegisteredRowsEntry(uid);
// Insert at the top of the list
entry.idx = 0;
this.controller._insertDataRow(entry, true);
if (this.onadd && !this.onadd(entry)) {
this.controller._grid.deleteRow(entry.idx);
var index = 0;
var appname = this._get_appname();
if (appname && this.egw().window.app[appname] && typeof this.egw().window.app[appname].nm_refresh_add == "function") {
var sort = Object.values(this.controller._indexMap).map(function (e) { return ({ index: e.idx, uid: e.uid }); });
index = this.egw().window.app[appname].nm_refresh_add(this, uid, sort);
}
// App cancelled the add
if (index === false) {
return;
}
// Insert at the top of the list, or where app said
var entry = this.controller._selectionMgr._getRegisteredRowsEntry(uid);
entry.idx = typeof index == "number" ? index : 0;
this.controller._insertDataRow(entry, true);
// Set "new entry" class - but it has to stay so register and re-add it after the data is there
entry.row.tr.addClass("new_entry");
var callback = function (data) {
@ -544,6 +551,18 @@ var et2_nextmatch = /** @class */ (function (_super) {
};
this.egw().dataRegisterUID(uid, callback, this, this.getInstanceManager().etemplate_exec_id, this.id);
};
et2_nextmatch.prototype._get_appname = function () {
var app = '';
var list = [];
list = et2_csvSplit(this.options.settings.columnselection_pref, 2, ".");
if (this.options.settings.columnselection_pref.indexOf('nextmatch') == 0) {
app = list[0].substring('nextmatch'.length + 1);
}
else {
app = list[0];
}
return app;
};
/**
* Gets the selection
*
@ -1385,9 +1404,9 @@ var et2_nextmatch = /** @class */ (function (_super) {
et2_nextmatch.prototype._set_autorefresh = function (time) {
// Store preference
var refresh_preference = "nextmatch-" + this.options.settings.columnselection_pref + "-autorefresh";
var app = this.options.template.split(".");
var app = this._get_appname();
if (this._get_autorefresh() != time) {
this.egw().set_preference(app[0], refresh_preference, time);
this.egw().set_preference(app, refresh_preference, time);
}
// Start / update timer
if (this._autorefresh_timer) {
@ -1427,8 +1446,7 @@ var et2_nextmatch = /** @class */ (function (_super) {
*/
et2_nextmatch.prototype._get_autorefresh = function () {
var refresh_preference = "nextmatch-" + this.options.settings.columnselection_pref + "-autorefresh";
var app = this.options.template.split(".");
return this.egw().preference(refresh_preference, app[0]);
return this.egw().preference(refresh_preference, this._get_appname());
};
/**
* When the template attribute is set, the nextmatch widget tries to load

View File

@ -703,7 +703,8 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
jQuery(this.getInstanceManager().DOMContainer.parentNode).one('show.et2_nextmatch',
// Important to use anonymous function instead of just 'this.refresh' because
// of the parameters passed
jQuery.proxy(function() {this.refresh();},this)
function() {this.nm.refresh(this.ids, this.type);}
.bind({nm: this, ids: _row_ids, type: _type})
);
return;
}
@ -796,16 +797,24 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
*/
protected refresh_add(uid:string)
{
var entry = this.controller._selectionMgr._getRegisteredRowsEntry(uid);
// Insert at the top of the list
entry.idx = 0;
this.controller._insertDataRow(entry,true);
if(this.onadd && !this.onadd(entry))
let index = 0;
let appname = this._get_appname();
if(appname && this.egw().window.app[appname] && typeof this.egw().window.app[appname].nm_refresh_add == "function")
{
let sort = Object.values(this.controller._indexMap).map(e => ({index:e.idx, uid:e.uid}));
index = this.egw().window.app[appname].nm_refresh_add(this, uid, sort)
}
// App cancelled the add
if(index === false)
{
this.controller._grid.deleteRow(entry.idx);
return;
}
// Insert at the top of the list, or where app said
var entry = this.controller._selectionMgr._getRegisteredRowsEntry(uid);
entry.idx = typeof index == "number" ? index : 0;
this.controller._insertDataRow(entry,true);
// Set "new entry" class - but it has to stay so register and re-add it after the data is there
entry.row.tr.addClass("new_entry");
@ -816,6 +825,23 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
this.egw().dataRegisterUID(uid, callback, this, this.getInstanceManager().etemplate_exec_id, this.id);
}
private _get_appname()
{
let app = '';
let list = [];
list = et2_csvSplit(this.options.settings.columnselection_pref, 2, ".");
if(this.options.settings.columnselection_pref.indexOf('nextmatch') == 0)
{
app = list[0].substring('nextmatch'.length + 1);
}
else
{
app = list[0];
}
return app;
}
/**
* Gets the selection
*
@ -1902,10 +1928,10 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
{
// Store preference
const refresh_preference = "nextmatch-" + this.options.settings.columnselection_pref + "-autorefresh";
const app = this.options.template.split(".");
const app = this._get_appname();
if(this._get_autorefresh() != time)
{
this.egw().set_preference(app[0],refresh_preference,time);
this.egw().set_preference(app,refresh_preference,time);
}
// Start / update timer
@ -1953,8 +1979,7 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2
_get_autorefresh( )
{
const refresh_preference = "nextmatch-" + this.options.settings.columnselection_pref + "-autorefresh";
const app = this.options.template.split(".");
return this.egw().preference(refresh_preference,app[0]);
return this.egw().preference(refresh_preference,this._get_appname());
}
/**

View File

@ -239,38 +239,41 @@ var AppJS = (function(){ "use strict"; return Class.extend(
}
},
/**
* Callback from nextmatch so application can have some control over
* where new rows (added via push) are added. This is only called when
* the type is "add".
*
* @param nm Nextmatch the entry is going to be added to
* @param uid
* @param current_order
*/
nm_refresh_add: function(nm, uid, current_order)
{
// Do we have a modified field so we can check nm sort order?
if(this.modification_field_name)
{
let value = nm.getValue();
let sort = value.sort || {};
if(sort && sort.id == this.modification_field_name && sort.asc == false)
{
// Sorting by modification time, DESC. Put it at the top.
return 0;
}
}
// Don't actually add it in.
return false;
},
/**
* Get (possible) app-specific uid
*
* @param {object} pushData see push method for individual attributes
*/
uid(pushData)
uid: function(pushData)
{
return pushData.app + '::' + pushData.id;
},
/**
* Method called after apps push implementation checked visibility
*
* @param {et2_nextmatch} nm
* @param pushData see push method for individual attributes
* @todo implement better way to update nextmatch widget without disturbing the user / state
* @todo show indicator that an update has happend
* @todo rate-limit update frequency
*/
updateList: function(nm, pushData)
{
switch (pushData.type)
{
case 'add':
case 'unknown':
nm.applyFilters();
break;
default:
egw.dataRefreshUID(this.uid(pushData));
break;
}
return pushData.app + "::" + pushData.id;
},
/**

View File

@ -15,6 +15,9 @@ require("jquery");
require("jqueryui");
require("../jsapi/egw_global");
var etemplate2_1 = require("../etemplate/etemplate2");
var et2_extension_nextmatch_1 = require("../etemplate/et2_extension_nextmatch");
var et2_widget_dialog_1 = require("../etemplate/et2_widget_dialog");
var et2_core_widget_1 = require("../etemplate/et2_core_widget");
/**
* Common base class for application javascript
* Each app should extend as needed.
@ -50,12 +53,14 @@ var EgwApp = /** @class */ (function () {
* Initialization and setup goes here, but the etemplate2 object
* is not yet ready.
*/
function EgwApp(appname) {
function EgwApp(appname, modified_field) {
if (modified_field === void 0) { modified_field = ""; }
/**
* Mailvelope "egroupware" Keyring
*/
this.mailvelope_keyring = undefined;
this.appname = appname;
this.modification_field_name = modified_field;
this.egw = egw(this.appname, window);
// Initialize sidebox for non-popups.
// ID set server side
@ -172,26 +177,29 @@ var EgwApp = /** @class */ (function () {
return pushData.app + '::' + pushData.id;
};
/**
* Method called after apps push implementation checked visibility
* Callback from nextmatch so application can have some control over
* where new rows (added via push) are added. This is only called when
* the type is "add".
*
* @param {et2_nextmatch} nm
* @param pushData see push method for individual attributes
* @todo implement better way to update nextmatch widget without disturbing the user / state
* @todo show indicator that an update has happend
* @todo rate-limit update frequency
* @param nm Nextmatch the entry is going to be added to
* @param uid
* @param current_order
*/
EgwApp.prototype.updateList = function (nm, pushData) {
switch (pushData.type) {
case 'add':
nm.refresh(this.uid(pushData), 'add');
break;
case 'unknown':
nm.applyFilters();
break;
default:
egw.dataRefreshUID(this.uid(pushData));
break;
EgwApp.prototype.nm_refresh_add = function (nm, uid, current_order) {
var _a;
// Do we have a modified field so we can check nm sort order?
if (this.modification_field_name) {
var value = nm.getValue();
var sort = ((_a = value) === null || _a === void 0 ? void 0 : _a.sort) || {};
if (sort && sort.id == this.modification_field_name && sort.asc == false) {
// Sorting by modification time, DESC. Put it at the top.
return 0;
}
// Don't actually add it in.
return false;
}
// Just put it in at the top
return 0;
};
/**
* Open an entry.
@ -228,11 +236,11 @@ var EgwApp = /** @class */ (function () {
if (typeof confirm_msg != 'undefined') {
var that = this;
var action_id = _action.id;
et2_dialog.show_dialog(function (button_id, value) {
if (button_id != et2_dialog.NO_BUTTON) {
et2_widget_dialog_1.et2_dialog.show_dialog(function (button_id, value) {
if (button_id != et2_widget_dialog_1.et2_dialog.NO_BUTTON) {
that._do_action(action_id, _elems);
}
}, confirm_msg, egw.lang('Confirmation required'), et2_dialog.BUTTONS_YES_NO, et2_dialog.QUESTION_MESSAGE);
}, confirm_msg, egw.lang('Confirmation required'), et2_widget_dialog_1.et2_dialog.BUTTONS_YES_NO, et2_widget_dialog_1.et2_dialog.QUESTION_MESSAGE);
}
else if (typeof this._do_action == 'function') {
this._do_action(_action.id, _elems);
@ -305,7 +313,7 @@ var EgwApp = /** @class */ (function () {
}
_widget.applyFilters(state.state || state.filter || {});
nextmatched = true;
}, this, et2_nextmatch);
}, this, et2_extension_nextmatch_1.et2_nextmatch);
if (nextmatched)
return false;
}
@ -342,7 +350,7 @@ var EgwApp = /** @class */ (function () {
for (var i = 0; i < et2.length; i++) {
et2[i].widgetContainer.iterateOver(function (_widget) {
state = _widget.getValue();
}, this, et2_nextmatch);
}, this, et2_extension_nextmatch_1.et2_nextmatch);
}
return state;
};
@ -646,7 +654,7 @@ var EgwApp = /** @class */ (function () {
var apps = egw().user('apps');
var is_admin = (typeof apps['admin'] != "undefined");
if (is_admin) {
this.favorite_popup.group = et2_createWidget("select-account", {
this.favorite_popup.group = et2_core_widget_1.et2_createWidget("select-account", {
id: "favorite[group]",
account_type: "groups",
empty_label: "Groups",
@ -762,7 +770,7 @@ var EgwApp = /** @class */ (function () {
line.addClass('loading');
// Make sure first
var do_delete = function (button_id) {
if (button_id != et2_dialog.YES_BUTTON) {
if (button_id != et2_widget_dialog_1.et2_dialog.YES_BUTTON) {
line.removeClass('loading');
return;
}
@ -785,7 +793,7 @@ var EgwApp = /** @class */ (function () {
}, jQuery(trash).parentsUntil("li").parent(), true, jQuery(trash).parentsUntil("li").parent());
request.sendRequest(true);
};
et2_dialog.show_dialog(do_delete, (egw.lang("Delete") + " " + name + "?"), egw.lang("Delete"), et2_dialog.YES_NO, et2_dialog.QUESTION_MESSAGE);
et2_widget_dialog_1.et2_dialog.show_dialog(do_delete, (egw.lang("Delete") + " " + name + "?"), egw.lang("Delete"), et2_widget_dialog_1.et2_dialog.YES_NO, et2_widget_dialog_1.et2_dialog.QUESTION_MESSAGE);
return false;
};
/**
@ -1271,15 +1279,15 @@ var EgwApp = /** @class */ (function () {
*/
EgwApp.prototype.mailvelopeDeleteBackup = function () {
var self = this;
et2_dialog.show_dialog(function (_button_id) {
if (_button_id == et2_dialog.YES_BUTTON) {
et2_widget_dialog_1.et2_dialog.show_dialog(function (_button_id) {
if (_button_id == et2_widget_dialog_1.et2_dialog.YES_BUTTON) {
self._mailvelopeBackupFileOperator(undefined, 'DELETE', function () {
self.egw.message(self.egw.lang('The backup key has been deleted.'));
}, function (_err) {
self.egw.message(self.egw.lang('Was not able to delete the backup key because %1', _err));
});
}
}, self.egw.lang('Are you sure, you would like to delete the backup key?'), self.egw.lang('Delete backup key'), {}, et2_dialog.BUTTONS_YES_CANCEL, et2_dialog.QUESTION_MESSAGE, undefined, self.egw);
}, self.egw.lang('Are you sure, you would like to delete the backup key?'), self.egw.lang('Delete backup key'), {}, et2_widget_dialog_1.et2_dialog.BUTTONS_YES_CANCEL, et2_widget_dialog_1.et2_dialog.QUESTION_MESSAGE, undefined, self.egw);
};
/**
* Create mailvelope restore dialog
@ -1335,7 +1343,7 @@ var EgwApp = /** @class */ (function () {
{ label: "Backup Key", image: "save", onclick: "app." + appname + ".mailvelopeCreateBackupDialog('#_mvelo', false)" }
];
var dialog = function (_content, _callback) {
return et2_createWidget("dialog", {
return et2_core_widget_1.et2_createWidget("dialog", {
callback: function (_button_id, _value) {
if (typeof _callback == "function") {
_callback.call(this, _button_id, _value.value);
@ -1381,7 +1389,7 @@ var EgwApp = /** @class */ (function () {
{ "text": egw.lang('Close'), id: 'close', image: 'cancelled' }
];
var dialog = function (_content, _callback) {
return et2_createWidget("dialog", {
return et2_core_widget_1.et2_createWidget("dialog", {
callback: function (_button_id, _value) {
if (typeof _callback == "function") {
_callback.call(this, _button_id, _value.value);
@ -1416,11 +1424,11 @@ var EgwApp = /** @class */ (function () {
else if (typeof InstallTrigger != 'undefined' && InstallTrigger.enabled()) {
InstallTrigger.install({ mailvelope: "https://download.mailvelope.com/releases/latest/mailvelope.firefox.xpi" }, function (_url, _status) {
if (_status == 0) {
et2_dialog.alert(egw.lang('Mailvelope addon installation succeded. Now you may configure the options.'));
et2_widget_dialog_1.et2_dialog.alert(egw.lang('Mailvelope addon installation succeded. Now you may configure the options.'));
return;
}
else {
et2_dialog.alert(egw.lang('Mailvelope addon installation failed! Please try again.'));
et2_widget_dialog_1.et2_dialog.alert(egw.lang('Mailvelope addon installation failed! Please try again.'));
}
});
}
@ -1483,16 +1491,16 @@ var EgwApp = /** @class */ (function () {
});
delete buttons[1].default;
}
et2_dialog.show_dialog(function (_button_id) {
if (_button_id != et2_dialog.NO_BUTTON) {
et2_widget_dialog_1.et2_dialog.show_dialog(function (_button_id) {
if (_button_id != et2_widget_dialog_1.et2_dialog.NO_BUTTON) {
var keys = {};
keys[self.egw.user('account_id')] = _pubKey;
self.egw.json('addressbook.addressbook_bo.ajax_set_pgp_keys', [keys, _button_id != et2_dialog.YES_BUTTON ? true : undefined]).sendRequest()
self.egw.json('addressbook.addressbook_bo.ajax_set_pgp_keys', [keys, _button_id != et2_widget_dialog_1.et2_dialog.YES_BUTTON ? true : undefined]).sendRequest()
.then(function (_data) {
self.egw.message(_data.response['0'].data);
});
}
}, self.egw.lang('It is recommended to store your public key in addressbook, so other users can write you encrypted mails.'), self.egw.lang('Store your public key in Addressbook?'), {}, buttons, et2_dialog.QUESTION_MESSAGE, undefined, self.egw);
}, self.egw.lang('It is recommended to store your public key in addressbook, so other users can write you encrypted mails.'), self.egw.lang('Store your public key in Addressbook?'), {}, buttons, et2_widget_dialog_1.et2_dialog.QUESTION_MESSAGE, undefined, self.egw);
}, function (_err) {
self.egw.message(_err.message + "\n\n" +
self.egw.lang("You will NOT be able to send or receive encrypted mails before completing that step!"), 'error');
@ -1665,7 +1673,7 @@ var EgwApp = /** @class */ (function () {
egw.message('Failed to copy the link!');
};
jQuery("body").on("click", "[name=share_link]", copy_link_to_clipboard);
et2_createWidget("dialog", {
et2_core_widget_1.et2_createWidget("dialog", {
callback: function (button_id, value) {
jQuery("body").off("click", "[name=share_link]", copy_link_to_clipboard);
return true;

View File

@ -15,6 +15,9 @@ import 'jqueryui';
import '../jsapi/egw_global';
import {etemplate2} from "../etemplate/etemplate2";
import {et2_container} from "../etemplate/et2_core_baseWidget";
import {et2_nextmatch} from "../etemplate/et2_extension_nextmatch";
import {et2_dialog} from "../etemplate/et2_widget_dialog";
import {et2_createWidget} from "../etemplate/et2_core_widget";
/**
* Type for push-message
@ -62,10 +65,15 @@ export interface PushData
export abstract class EgwApp
{
/**
* Internal application name - override this
* Internal application name - pass this in constructor
*/
readonly appname: string;
/**
* Name of the modification timestamp in entry data
*/
readonly modification_field_name : string;
/**
* Internal reference to the most recently loaded etemplate2 widget tree
*
@ -91,7 +99,7 @@ export abstract class EgwApp
*
* @example <caption>Access via etemplate2 object</caption>
* // Instead of this.et2, using it's unique ID
* var et2 = etemplate2.getById('myapp-index)
* var et2 = etemplate2.getById("myapp-index")
* if(et2)
* {
* et2.widgetContainer. ...
@ -132,9 +140,10 @@ export abstract class EgwApp
* Initialization and setup goes here, but the etemplate2 object
* is not yet ready.
*/
constructor(appname: string)
constructor(appname: string, modified_field:string = "")
{
this.appname = appname;
this.modification_field_name = modified_field;
this.egw = egw(this.appname, window);
// Initialize sidebox for non-popups.
@ -271,29 +280,34 @@ export abstract class EgwApp
}
/**
* Method called after apps push implementation checked visibility
* Callback from nextmatch so application can have some control over
* where new rows (added via push) are added. This is only called when
* the type is "add".
*
* @param {et2_nextmatch} nm
* @param pushData see push method for individual attributes
* @todo implement better way to update nextmatch widget without disturbing the user / state
* @todo show indicator that an update has happend
* @todo rate-limit update frequency
* @param nm Nextmatch the entry is going to be added to
* @param uid
* @param current_order
*/
updateList(nm, pushData : PushData)
nm_refresh_add(nm: et2_nextmatch, uid: string, current_order: string[]) : number|boolean
{
switch (pushData.type)
// Do we have a modified field so we can check nm sort order?
if(this.modification_field_name)
{
case 'add':
nm.refresh(this.uid(pushData), 'add');
break;
case 'unknown':
nm.applyFilters();
break;
let value = nm.getValue();
let sort = value?.sort || {};
default:
egw.dataRefreshUID(this.uid(pushData));
break;
if(sort && sort.id == this.modification_field_name && sort.asc == false)
{
// Sorting by modification time, DESC. Put it at the top.
return 0;
}
// Don't actually add it in.
return false;
}
// Just put it in at the top
return 0;
}
/**

View File

@ -14,6 +14,8 @@
/api/js/jquery/jquery.base64.js;
*/
import {etemplate2} from "../../api/js/etemplate/etemplate2";
/**
* UI for mail
*
@ -406,7 +408,8 @@ app.classes.mail = AppJS.extend(
let profile_id = pushData.id.split('::')[1];
if (nm_value && nm_value.col_filter && nm_value.selectedFolder.split("::")[0] == profile_id)
{
this.updateList(nm, pushData);
// Just update the nm
nm.refresh(pushData.id, pushData.type);
}
// update unseen counter in folder-tree
if (pushData.type === 'add' && pushData.acl.folder && pushData.acl.unseen)

View File

@ -30,6 +30,7 @@ require("jqueryui");
require("../jsapi/egw_global");
require("../etemplate/et2_types");
var egw_app_1 = require("../../api/js/jsapi/egw_app");
var etemplate2_1 = require("../../api/js/etemplate/etemplate2");
/**
* UI for timesheet
*
@ -38,7 +39,7 @@ var egw_app_1 = require("../../api/js/jsapi/egw_app");
var TimesheetApp = /** @class */ (function (_super) {
__extends(TimesheetApp, _super);
function TimesheetApp() {
return _super.call(this, 'timesheet') || this;
return _super.call(this, 'timesheet', "ts_start") || this;
}
/**
* This function is called when the etemplate2 object is loaded
@ -206,7 +207,7 @@ var TimesheetApp = /** @class */ (function (_super) {
if (nm && nm_value && ((_c = nm_value.col_filter) === null || _c === void 0 ? void 0 : _c.ts_owner) && nm_value.col_filter.ts_owner != pushData.acl) {
return;
}
this.updateList(nm, pushData);
etemplate2_1.etemplate2.app_refresh("", pushData.app, pushData.id, pushData.type);
};
return TimesheetApp;
}(egw_app_1.EgwApp));

View File

@ -19,6 +19,7 @@ import '../etemplate/et2_types';
import {EgwApp} from '../../api/js/jsapi/egw_app';
import {et2_nextmatch} from "../../api/js/etemplate/et2_extension_nextmatch";
import {etemplate2} from "../../api/js/etemplate/etemplate2";
/**
* UI for timesheet
@ -30,7 +31,7 @@ class TimesheetApp extends EgwApp
constructor()
{
super('timesheet');
super('timesheet',"ts_start");
}
/**
@ -239,7 +240,7 @@ class TimesheetApp extends EgwApp
{
return;
}
this.updateList(nm, pushData);
etemplate2.app_refresh("",pushData.app, pushData.id, pushData.type);
}
}