diff --git a/api/js/etemplate/et2_core_inputWidget.js b/api/js/etemplate/et2_core_inputWidget.js index 3aad70a0da..4fed8a4251 100644 --- a/api/js/etemplate/et2_core_inputWidget.js +++ b/api/js/etemplate/et2_core_inputWidget.js @@ -58,6 +58,14 @@ var et2_inputWidget = /** @class */ (function (_super) { _super.prototype.destroy.call(this); this._labelContainer = null; }; + /** + * Make sure dirty flag is properly set + */ + et2_inputWidget.prototype.doLoadingFinished = function () { + var result = _super.prototype.doLoadingFinished.call(this); + this.resetDirty(); + return result; + }; /** * Load the validation errors from the server * @@ -212,7 +220,25 @@ var et2_inputWidget = /** @class */ (function (_super) { return this._oldValue; }; et2_inputWidget.prototype.isDirty = function () { - return this._oldValue != this.getValue(); + var value = this.getValue(); + if (typeof value !== typeof this._oldValue) { + return true; + } + if (this._oldValue === value) { + return false; + } + switch (typeof this._oldValue) { + case "object": + if (this._oldValue.length !== value.length) + return true; + for (var key in this._oldValue) { + if (this._oldValue[key] !== value[key]) + return true; + } + return false; + default: + return this._oldValue != value; + } }; et2_inputWidget.prototype.resetDirty = function () { this._oldValue = this.getValue(); diff --git a/api/js/etemplate/et2_core_inputWidget.ts b/api/js/etemplate/et2_core_inputWidget.ts index 8a241e24c8..0230a1dd83 100644 --- a/api/js/etemplate/et2_core_inputWidget.ts +++ b/api/js/etemplate/et2_core_inputWidget.ts @@ -96,6 +96,18 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_ this._labelContainer = null; } + /** + * Make sure dirty flag is properly set + */ + doLoadingFinished() : boolean | JQueryPromise + { + let result = super.doLoadingFinished(); + + this.resetDirty(); + + return result; + } + /** * Load the validation errors from the server * @@ -300,7 +312,27 @@ export class et2_inputWidget extends et2_valueWidget implements et2_IInput, et2_ isDirty() { - return this._oldValue != this.getValue(); + let value = this.getValue(); + if(typeof value !== typeof this._oldValue) + { + return true; + } + if(this._oldValue === value) + { + return false; + } + switch(typeof this._oldValue) + { + case "object": + if(this._oldValue.length !== value.length) return true; + for(let key in this._oldValue) + { + if(this._oldValue[key] !== value[key]) return true; + } + return false; + default: + return this._oldValue != value; + } } resetDirty() diff --git a/api/js/etemplate/et2_extension_customfields.js b/api/js/etemplate/et2_extension_customfields.js index cf1c8746d0..f65c60addc 100644 --- a/api/js/etemplate/et2_extension_customfields.js +++ b/api/js/etemplate/et2_extension_customfields.js @@ -316,10 +316,10 @@ var et2_customfields_list = /** @class */ (function (_super) { return value; }; et2_customfields_list.prototype.isDirty = function () { - var dirty = true; + var dirty = false; for (var field_name in this.widgets) { if (this.widgets[field_name].isDirty) { - dirty = dirty && this.widgets[field_name].isDirty(); + dirty = dirty || this.widgets[field_name].isDirty(); } } return dirty; diff --git a/api/js/etemplate/et2_extension_customfields.ts b/api/js/etemplate/et2_extension_customfields.ts index 6c1b9c3606..a5a1bd4d93 100644 --- a/api/js/etemplate/et2_extension_customfields.ts +++ b/api/js/etemplate/et2_extension_customfields.ts @@ -430,12 +430,12 @@ export class et2_customfields_list extends et2_valueWidget implements et2_IDetac isDirty( ) { - let dirty = true; + let dirty = false; for(let field_name in this.widgets) { if(this.widgets[field_name].isDirty) { - dirty = dirty && this.widgets[field_name].isDirty(); + dirty = dirty || this.widgets[field_name].isDirty(); } } return dirty; diff --git a/api/js/etemplate/et2_widget_selectbox.js b/api/js/etemplate/et2_widget_selectbox.js index 723c28d0ec..e0ec8e6202 100644 --- a/api/js/etemplate/et2_widget_selectbox.js +++ b/api/js/etemplate/et2_widget_selectbox.js @@ -344,8 +344,8 @@ var et2_selectbox = /** @class */ (function (_super) { this.setDOMNode(node[0]); }; et2_selectbox.prototype.doLoadingFinished = function () { - _super.prototype.doLoadingFinished.call(this); this.set_tags(this.options.tags, this.options.width); + _super.prototype.doLoadingFinished.call(this); return true; }; et2_selectbox.prototype.loadFromXML = function (_node) { @@ -1366,6 +1366,12 @@ var et2_selectbox_ro = /** @class */ (function (_super) { et2_selectbox_ro.prototype.getValue = function () { return null; }; + /** + * Readonly selectbox can't be dirty + */ + et2_selectbox_ro.prototype.isDirty = function () { + return false; + }; /** * Functions for et2_IDetachedDOM */ diff --git a/api/js/etemplate/et2_widget_selectbox.ts b/api/js/etemplate/et2_widget_selectbox.ts index 59d6cc844d..017b4995e7 100644 --- a/api/js/etemplate/et2_widget_selectbox.ts +++ b/api/js/etemplate/et2_widget_selectbox.ts @@ -484,10 +484,10 @@ export class et2_selectbox extends et2_inputWidget doLoadingFinished() { - super.doLoadingFinished(); - this.set_tags(this.options.tags, this.options.width); + super.doLoadingFinished(); + return true; } @@ -1679,6 +1679,14 @@ export class et2_selectbox_ro extends et2_selectbox implements et2_IDetachedDOM return null; } + /** + * Readonly selectbox can't be dirty + */ + isDirty() + { + return false; + } + /** * Functions for et2_IDetachedDOM */ diff --git a/api/js/etemplate/et2_widget_tabs.js b/api/js/etemplate/et2_widget_tabs.js index 2044ca94c4..ff579dc56d 100644 --- a/api/js/etemplate/et2_widget_tabs.js +++ b/api/js/etemplate/et2_widget_tabs.js @@ -218,6 +218,7 @@ var et2_tabbox = /** @class */ (function (_super) { } jQuery.when.apply(jQuery, promises).then(function () { tab_deferred.resolve(); + tabs.resetDirty(); }); }, 0); return tab_deferred.promise(); diff --git a/api/js/etemplate/et2_widget_tabs.ts b/api/js/etemplate/et2_widget_tabs.ts index 329fb95769..3802fb9995 100644 --- a/api/js/etemplate/et2_widget_tabs.ts +++ b/api/js/etemplate/et2_widget_tabs.ts @@ -274,6 +274,7 @@ class et2_tabbox extends et2_valueWidget implements et2_IInput,et2_IResizeable,e } jQuery.when.apply(jQuery,promises).then(function() { tab_deferred.resolve(); + tabs.resetDirty(); }); },0); diff --git a/api/js/etemplate/et2_widget_taglist.js b/api/js/etemplate/et2_widget_taglist.js index ee8f9e7b0e..dc89d07888 100644 --- a/api/js/etemplate/et2_widget_taglist.js +++ b/api/js/etemplate/et2_widget_taglist.js @@ -279,6 +279,7 @@ var et2_taglist = /** @class */ (function (_super) { this.div.on('blur', 'input', function () { jQuery('.ms-ctn-focus', widget.div).removeClass('ms-ctn-focus'); }); + this.resetDirty(); return true; }; /** diff --git a/api/js/etemplate/et2_widget_taglist.ts b/api/js/etemplate/et2_widget_taglist.ts index d264137039..4eb9b624e9 100644 --- a/api/js/etemplate/et2_widget_taglist.ts +++ b/api/js/etemplate/et2_widget_taglist.ts @@ -430,6 +430,8 @@ export class et2_taglist extends et2_selectbox implements et2_IResizeable this.div.on('blur', 'input', function() { jQuery('.ms-ctn-focus', widget.div).removeClass('ms-ctn-focus'); }); + + this.resetDirty(); return true; } diff --git a/api/js/etemplate/etemplate2.js b/api/js/etemplate/etemplate2.js index ed20f628eb..f1960b3947 100644 --- a/api/js/etemplate/etemplate2.js +++ b/api/js/etemplate/etemplate2.js @@ -274,20 +274,34 @@ var etemplate2 = /** @class */ (function () { * calls, eg. via et2_dialog. */ etemplate2.prototype.bind_unload = function () { + // Prompt user to save for dirty popups + if (window !== egw_topWindow() && !this.close_prompt) { + this.close_prompt = this._close_changed_prompt.bind(this); + window.addEventListener("beforeunload", this.close_prompt); + } if (this._etemplate_exec_id) { this.destroy_session = jQuery.proxy(function (ev) { // need to use async === "keepalive" to run via beforeunload egw.json("EGroupware\\Api\\Etemplate::ajax_destroy_session", [this._etemplate_exec_id], null, null, "keepalive").sendRequest(); }, this); - if (!window.onbeforeunload) { - window.onbeforeunload = this.destroy_session; - } + window.addEventListener("beforeunload", this.destroy_session); } }; + etemplate2.prototype._close_changed_prompt = function (e) { + if (!this.isDirty()) { + return; + } + // Cancel the event + e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown + // Chrome requires returnValue to be set + e.returnValue = ''; + }; /** * Unbind our unload handler */ etemplate2.prototype.unbind_unload = function () { + window.removeEventListener("beforeunload", this.destroy_session); + window.removeEventListener("beforeunload", this.close_prompt); if (window.onbeforeunload === this.destroy_session) { window.onbeforeunload = null; } diff --git a/api/js/etemplate/etemplate2.ts b/api/js/etemplate/etemplate2.ts index f53a1db445..8136f40a81 100644 --- a/api/js/etemplate/etemplate2.ts +++ b/api/js/etemplate/etemplate2.ts @@ -107,6 +107,7 @@ export class etemplate2 private resize_timeout: number | boolean; private destroy_session: any; + private close_prompt: any; private app_obj: EgwApp; app: string; @@ -344,6 +345,12 @@ export class etemplate2 */ bind_unload() { + // Prompt user to save for dirty popups + if(window !== egw_topWindow() && !this.close_prompt) + { + this.close_prompt = this._close_changed_prompt.bind(this); + window.addEventListener("beforeunload", this.close_prompt); + } if (this._etemplate_exec_id) { this.destroy_session = jQuery.proxy(function (ev) @@ -353,18 +360,31 @@ export class etemplate2 [this._etemplate_exec_id], null, null, "keepalive").sendRequest(); }, this); - if (!window.onbeforeunload) - { - window.onbeforeunload = this.destroy_session; - } + window.addEventListener("beforeunload", this.destroy_session); } } + private _close_changed_prompt(e : BeforeUnloadEvent) + { + if(!this.isDirty()) + { + return; + } + + // Cancel the event + e.preventDefault(); // If you prevent default behavior in Mozilla Firefox prompt will always be shown + + // Chrome requires returnValue to be set + e.returnValue = ''; + } + /** * Unbind our unload handler */ unbind_unload() { + window.removeEventListener("beforeunload", this.destroy_session); + window.removeEventListener("beforeunload", this.close_prompt); if (window.onbeforeunload === this.destroy_session) { window.onbeforeunload = null; diff --git a/calendar/js/app.js b/calendar/js/app.js index ab77efa985..f869d86c30 100644 --- a/calendar/js/app.js +++ b/calendar/js/app.js @@ -386,9 +386,7 @@ var CalendarApp = /** @class */ (function (_super) { //send Syncronus ajax request to the server to unlock the on close entry //set onbeforeunload with json request to send request when the window gets close by X button if (content.data.lock_token) { - window.onbeforeunload = function () { - this.egw.json('calendar.calendar_uiforms.ajax_unlock', [content.data.id, content.data.lock_token], null, true, "keepalive", null).sendRequest(); - }; + window.addEventListener("beforeunload", this._unlock.bind(this)); } } this.alarm_custom_date(); @@ -788,6 +786,15 @@ var CalendarApp = /** @class */ (function (_super) { sortable.sortable('disable'); } }; + /** + * Unlock the event before closing the popup + * + * @private + */ + CalendarApp.prototype._unlock = function () { + var content = this.et2.getArrayMgr('content'); + this.egw.json('calendar.calendar_uiforms.ajax_unlock', [content.data.id, content.data.lock_token], null, this, "keepalive", null).sendRequest(); + }; /** * Bind scroll event * When the user scrolls, we'll move enddate - startdate days diff --git a/calendar/js/app.ts b/calendar/js/app.ts index 1ff37da86b..707b31a0e3 100644 --- a/calendar/js/app.ts +++ b/calendar/js/app.ts @@ -286,10 +286,7 @@ class CalendarApp extends EgwApp //set onbeforeunload with json request to send request when the window gets close by X button if (content.data.lock_token) { - window.onbeforeunload = function () { - this.egw.json('calendar.calendar_uiforms.ajax_unlock', - [content.data.id, content.data.lock_token],null,true,"keepalive",null).sendRequest(); - }; + window.addEventListener("beforeunload", this._unlock.bind(this)); } } this.alarm_custom_date(); @@ -758,6 +755,17 @@ class CalendarApp extends EgwApp } } + /** + * Unlock the event before closing the popup + * + * @private + */ + private _unlock () { + const content = this.et2.getArrayMgr('content'); + this.egw.json('calendar.calendar_uiforms.ajax_unlock', + [content.data.id, content.data.lock_token],null,this,"keepalive",null).sendRequest(); + } + /** * Bind scroll event * When the user scrolls, we'll move enddate - startdate days