IDEMPIERE-5202 Implement auto save of current tab (#1201)

* IDEMPIERE-5202 Implement auto save of current tab

* IDEMPIERE-5202 Implement auto save of current tab

- Fix auto save trigger for every keystore on text field.

* IDEMPIERE-5202 Implement auto save of current tab

- Fix handling of the confirmation dialog for close of tab.
- Focus tweak for child to parent tab navigation.
- Restore focus if user abandon closing of tab.

* IDEMPIERE-5202 Implement auto save of current tab

- Fix infinite error loop when auto save fail with error
This commit is contained in:
hengsin 2022-03-01 16:05:36 +08:00 committed by GitHub
parent 10997b2a95
commit 1ae1aa040e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 146 additions and 4 deletions

View File

@ -0,0 +1,10 @@
-- IDEMPIERE-5202 Implement auto save of current tab
SELECT register_migration_script('202202232008_IDEMPIERE-5202.sql') FROM dual;
SET SQLBLANKLINES ON
SET DEFINE OFF
-- Feb 23, 2022, 8:08:00 PM MYT
INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200193,0,0,TO_TIMESTAMP('2022-02-23 20:07:59','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2022-02-23 20:07:59','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','ZK_AUTO_SAVE_CHANGES','N','Y/N - Define if the application will auto save changes of current tab','D','C','f7d6a7ee-7af4-465c-a9c5-21094250c736')
;

View File

@ -0,0 +1,7 @@
-- IDEMPIERE-5202 Implement auto save of current tab
SELECT register_migration_script('202202232008_IDEMPIERE-5202.sql') FROM dual;
-- Feb 23, 2022, 8:08:00 PM MYT
INSERT INTO AD_SysConfig (AD_SysConfig_ID,AD_Client_ID,AD_Org_ID,Created,Updated,CreatedBy,UpdatedBy,IsActive,Name,Value,Description,EntityType,ConfigurationLevel,AD_SysConfig_UU) VALUES (200193,0,0,TO_TIMESTAMP('2022-02-23 20:07:59','YYYY-MM-DD HH24:MI:SS'),TO_TIMESTAMP('2022-02-23 20:07:59','YYYY-MM-DD HH24:MI:SS'),100,100,'Y','ZK_AUTO_SAVE_CHANGES','N','Y/N - Define if the application will auto save changes of current tab','D','C','f7d6a7ee-7af4-465c-a9c5-21094250c736')
;

View File

@ -1026,6 +1026,15 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable
return false;
} // dataSave
/**
*
* @return true if need save and all mandatory field has value
*/
public boolean isNeedSaveAndMandatoryFill()
{
return m_mTable.isNeedSaveAndMandatoryFill();
}
// Validate if the current tab record has changed in database or any parent record
// Return if there are changes
public boolean hasChangedCurrentTabAndParents() {

View File

@ -2536,6 +2536,54 @@ public class GridTable extends AbstractTableModel
return sb.toString();
} // getMandatory
/**
*
* @return true if need save and all mandatory field has value
*/
public boolean isNeedSaveAndMandatoryFill() {
if (!m_open)
{
return false;
}
// no need - not changed - row not positioned - no Value changed
if (m_rowChanged == -1)
{
return false;
}
// Value not changed
if (m_rowData == null)
{
return false;
}
if (m_readOnly)
{
return false;
}
// row not positioned - no Value changed
if (m_rowChanged == -1)
{
if (m_newRow != -1) // new row and nothing changed - might be OK
m_rowChanged = m_newRow;
else
{
return false;
}
}
// get updated row data
Object[] rowData = getDataAtRow(m_rowChanged);
// Check Mandatory
String missingColumns = getMandatory(rowData);
if (missingColumns.length() != 0) {
return false;
}
return true;
}
/*************************************************************************/
/** LOB Info */

View File

@ -174,6 +174,7 @@ public class MSysConfig extends X_AD_SysConfig
public static final String VALIDATE_MATCHING_TO_ORDERED_QTY = "VALIDATE_MATCHING_TO_ORDERED_QTY";
public static final String WEBUI_LOGOURL = "WEBUI_LOGOURL";
public static final String ZK_ADVANCE_FIND_FILTER_COLUMN_LIST = "ZK_ADVANCE_FIND_FILTER_COLUMN_LIST";
public static final String ZK_AUTO_SAVE_CHANGES = "ZK_AUTO_SAVE_CHANGES";
public static final String ZK_BROWSER_ICON = "ZK_BROWSER_ICON";
public static final String ZK_BROWSER_TITLE = "ZK_BROWSER_TITLE";
public static final String ZK_BUTTON_STYLE = "ZK_BUTTON_STYLE";

View File

@ -211,8 +211,10 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
/**
* Maintain no of quick form tabs open
*/
ArrayList <Integer> quickFormOpenTabs = new ArrayList <Integer>();
protected ArrayList <Integer> quickFormOpenTabs = new ArrayList <Integer>();
protected Component lastFocusEditor = null;
/**
* Constructor
* @param ctx
@ -246,6 +248,7 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
comp.addEventListener(ON_DEFER_SET_DETAILPANE_SELECTION_EVENT, this);
comp.addEventListener(ON_FOCUS_DEFER_EVENT, this);
comp.setAttribute(ITabOnSelectHandler.ATTRIBUTE_KEY, this);
return comp;
}
@ -946,6 +949,8 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
protected ADWindow adwindow;
protected boolean showingOnExitDialog;
/**
* @see ToolbarListener#onLock()
*/
@ -1142,11 +1147,22 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
/**
* @param callback
*/
public void onExit(Callback<Boolean> callback)
public synchronized void onExit(Callback<Boolean> callback)
{
if (isPendingChanges())
{
FDialog.ask(curWindowNo, null, "CloseUnSave?", callback);
showingOnExitDialog = true;
FDialog.ask(curWindowNo, null, "CloseUnSave?", b -> {
showingOnExitDialog = false;
callback.onCallback(b);
if (!b)
{
//restore focus
if (lastFocusEditor != null && lastFocusEditor instanceof HtmlBasedComponent &&
lastFocusEditor.getPage() != null && LayoutUtils.isReallyVisible(lastFocusEditor))
((HtmlBasedComponent)lastFocusEditor).focus();
}
});
}
else
{
@ -1795,6 +1811,10 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
adTabbox.getSelectedGridTab().isNew() ||
(adTabbox.getSelectedDetailADTabpanel() != null && adTabbox.getSelectedDetailADTabpanel().getGridTab().isNew()));
if (!e.isError() && Util.isEmpty(adInfo)) {
autoSaveChanges(e);
}
//
// No Rows
if (e.getTotalRows() == 0 && insertRecord && !detailTab && !tabPanel.getGridTab().isSortTab())
@ -1907,6 +1927,35 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
}
private synchronized void autoSaveChanges(DataStatusEvent e) {
if (!e.isInitEdit() && toolbar.isSaveEnable() && MSysConfig.getBooleanValue(MSysConfig.ZK_AUTO_SAVE_CHANGES, false, Env.getAD_Client_ID(Env.getCtx()))) {
final IADTabpanel dirtyTabpanel = adTabbox.getDirtyADTabpanel();
if (dirtyTabpanel != null && !dirtyTabpanel.getGridTab().isSortTab()
&& Util.isEmpty(dirtyTabpanel.getGridTab().getCommitWarning(), true)
&& Env.isAutoCommit(ctx, curWindowNo)) {
if (dirtyTabpanel.getGridTab().isNeedSaveAndMandatoryFill()) {
//sleep needed for onClose to show confirmation dialog
try {
Thread.sleep(200);
} catch (InterruptedException e2) {
}
if (!showingOnExitDialog)
Executions.schedule(getComponent().getDesktop(), e1 -> asyncAutoSave(), new Event("onAutoSave"));
}
}
}
}
private synchronized void asyncAutoSave() {
//ensure still dirty and can save
if (toolbar.isSaveEnable() && !showingOnExitDialog) {
final IADTabpanel dirtyTabpanel = adTabbox.getDirtyADTabpanel();
if (dirtyTabpanel != null && dirtyTabpanel.getGridTab().isNeedSaveAndMandatoryFill()) {
onSave(false, false, null);
}
}
}
/**
* @return boolean
*/
@ -2397,7 +2446,7 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
if (result) {
WindowValidatorEvent event = new WindowValidatorEvent(adwindow, WindowValidatorEventType.AFTER_SAVE.getName());
WindowValidatorManager.getInstance().fireWindowValidatorEvent(event, callback);
} else {
} else if (callback != null) {
callback.onCallback(result);
}
}
@ -3896,4 +3945,13 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
public GridWindow getGridWindow() {
return gridWindow;
}
/**
* set component of last focus editor.
* Use in onClose/Exit to restore focus
* @param component
*/
public void setLastFocusEditor(Component component) {
lastFocusEditor = component;
}
}

View File

@ -1094,6 +1094,7 @@ public class CompositeADTabbox extends AbstractADTabbox
}
if (adtab.getGridTab().getCurrentRow() != currentRow)
adtab.getGridTab().setCurrentRow(currentRow, true);
Executions.schedule(getComponent().getDesktop(), e->((ADTabpanel)headerTab).focusToFirstEditor(), new Event("onFocusToHeaderTab"));
break;
}
}

View File

@ -26,6 +26,7 @@ import java.util.Properties;
import org.adempiere.webui.AdempiereWebUI;
import org.adempiere.webui.ClientInfo;
import org.adempiere.webui.LayoutUtils;
import org.adempiere.webui.adwindow.ADWindow;
import org.adempiere.webui.adwindow.IFieldEditorContainer;
import org.adempiere.webui.component.Bandbox;
import org.adempiere.webui.component.Button;
@ -332,6 +333,13 @@ public abstract class WEditor implements EventListener<Event>, PropertyChangeLis
component.addEventListener(INIT_EDIT_EVENT, this);
component.setAttribute("idempiere.editor", this);
component.addEventListener(Events.ON_FOCUS, e -> {
ADWindow adwindow = ADWindow.findADWindow(component);
if (adwindow != null) {
adwindow.getADWindowContent().setLastFocusEditor(component);
}
});
}
/**