diff --git a/api/js/etemplate/et2_extension_nextmatch.js b/api/js/etemplate/et2_extension_nextmatch.js index 4ad1464de9..69713f1c79 100644 --- a/api/js/etemplate/et2_extension_nextmatch.js +++ b/api/js/etemplate/et2_extension_nextmatch.js @@ -509,8 +509,10 @@ var et2_nextmatch = /** @class */ (function (_super) { case "delete": // Handled above, more code to execute after loop break; - case "edit": case "add": + this.refresh_add(uid); + break; + case "edit": default: // Trigger refresh this.applyFilters(); @@ -520,6 +522,28 @@ var et2_nextmatch = /** @class */ (function (_super) { // Trigger an event so app code can act on it jQuery(this).triggerHandler("refresh", [this, _row_ids, _type]); }; + /** + * An entry has been added. Put it in the list. + * + * @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); + return; + } + // 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) { + data.class += "new_entry"; + this.egw().dataUnregisterUID(uid, callback, this); + }; + this.egw().dataRegisterUID(uid, callback, this, this.getInstanceManager().etemplate_exec_id, this.id); + }; /** * Gets the selection * @@ -2054,6 +2078,12 @@ var et2_nextmatch = /** @class */ (function (_super) { "default": et2_no_init, "description": "JS code that gets executed when a _file_ is dropped on a row. Other drop interactions are handled by the action system. Return false to prevent the default link action." }, + "onadd": { + "name": "onAdd", + "type": "js", + "default": et2_no_init, + "description": "JS code that gets executed when a new entry is added via refresh(). Allows apps to override the default handling. Return false to cancel the add." + }, "settings": { "name": "Settings", "type": "any", diff --git a/api/js/etemplate/et2_extension_nextmatch.ts b/api/js/etemplate/et2_extension_nextmatch.ts index 5530e5a413..8e877b9f47 100644 --- a/api/js/etemplate/et2_extension_nextmatch.ts +++ b/api/js/etemplate/et2_extension_nextmatch.ts @@ -179,6 +179,12 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 "default": et2_no_init, "description": "JS code that gets executed when a _file_ is dropped on a row. Other drop interactions are handled by the action system. Return false to prevent the default link action." }, + "onadd": { + "name": "onAdd", + "type": "js", + "default": et2_no_init, + "description": "JS code that gets executed when a new entry is added via refresh(). Allows apps to override the default handling. Return false to cancel the add." + }, "settings": { "name": "Settings", "type": "any", @@ -769,8 +775,10 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 case "delete": // Handled above, more code to execute after loop break; - case "edit": case "add": + this.refresh_add(uid); + break; + case "edit": default: // Trigger refresh this.applyFilters(); @@ -781,6 +789,33 @@ export class et2_nextmatch extends et2_DOMWidget implements et2_IResizeable, et2 jQuery(this).triggerHandler("refresh",[this,_row_ids,_type]); } + /** + * An entry has been added. Put it in the list. + * + * @param uid + */ + 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)) + { + this.controller._grid.deleteRow(entry.idx); + return; + } + + // 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"); + let callback = function(data) { + data.class += "new_entry"; + this.egw().dataUnregisterUID(uid, callback, this); + }; + this.egw().dataRegisterUID(uid, callback, this, this.getInstanceManager().etemplate_exec_id, this.id); + } + /** * Gets the selection * diff --git a/api/js/jsapi/egw_app.js b/api/js/jsapi/egw_app.js index e4141b4b3b..f275f9b103 100644 --- a/api/js/jsapi/egw_app.js +++ b/api/js/jsapi/egw_app.js @@ -183,6 +183,8 @@ var EgwApp = /** @class */ (function () { EgwApp.prototype.updateList = function (nm, pushData) { switch (pushData.type) { case 'add': + nm.refresh(this.uid(pushData), 'add'); + break; case 'unknown': nm.applyFilters(); break; diff --git a/api/js/jsapi/egw_app.ts b/api/js/jsapi/egw_app.ts index 2120b069a3..9e5a87da8b 100644 --- a/api/js/jsapi/egw_app.ts +++ b/api/js/jsapi/egw_app.ts @@ -284,6 +284,8 @@ export abstract class EgwApp switch (pushData.type) { case 'add': + nm.refresh(this.uid(pushData), 'add'); + break; case 'unknown': nm.applyFilters(); break; diff --git a/api/templates/default/etemplate2.css b/api/templates/default/etemplate2.css index 0c781ce502..fa5cc31331 100644 --- a/api/templates/default/etemplate2.css +++ b/api/templates/default/etemplate2.css @@ -2094,6 +2094,25 @@ table.egwGridView_outer thead tr th.noResize:hover { .et2_nextmatch .subentry.level_3 div.et2_vbox { margin-left: 7.5em; } + +/** + * New entry (via push) + */ +.et2_nextmatch .new_entry { + animation: new_entry_pulse 5s; + animation-fill-mode: forwards; +} +@keyframes new_entry_pulse { + 0% { + background-color: rgba(255,255,185,0); + } + 15% { + background-color: rgba(255,255,185, 1); + } + 100% { + background-color: rgba(255,255,185,0.3); + } +} /** * itempicker widget */