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:
parent
10997b2a95
commit
1ae1aa040e
|
@ -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')
|
||||||
|
;
|
||||||
|
|
|
@ -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')
|
||||||
|
;
|
||||||
|
|
|
@ -1026,6 +1026,15 @@ public class GridTab implements DataStatusListener, Evaluatee, Serializable
|
||||||
return false;
|
return false;
|
||||||
} // dataSave
|
} // 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
|
// Validate if the current tab record has changed in database or any parent record
|
||||||
// Return if there are changes
|
// Return if there are changes
|
||||||
public boolean hasChangedCurrentTabAndParents() {
|
public boolean hasChangedCurrentTabAndParents() {
|
||||||
|
|
|
@ -2536,6 +2536,54 @@ public class GridTable extends AbstractTableModel
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
} // getMandatory
|
} // 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 */
|
/** LOB Info */
|
||||||
|
|
|
@ -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 VALIDATE_MATCHING_TO_ORDERED_QTY = "VALIDATE_MATCHING_TO_ORDERED_QTY";
|
||||||
public static final String WEBUI_LOGOURL = "WEBUI_LOGOURL";
|
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_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_ICON = "ZK_BROWSER_ICON";
|
||||||
public static final String ZK_BROWSER_TITLE = "ZK_BROWSER_TITLE";
|
public static final String ZK_BROWSER_TITLE = "ZK_BROWSER_TITLE";
|
||||||
public static final String ZK_BUTTON_STYLE = "ZK_BUTTON_STYLE";
|
public static final String ZK_BUTTON_STYLE = "ZK_BUTTON_STYLE";
|
||||||
|
|
|
@ -211,8 +211,10 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
|
||||||
/**
|
/**
|
||||||
* Maintain no of quick form tabs open
|
* 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
|
* Constructor
|
||||||
* @param ctx
|
* @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_DEFER_SET_DETAILPANE_SELECTION_EVENT, this);
|
||||||
comp.addEventListener(ON_FOCUS_DEFER_EVENT, this);
|
comp.addEventListener(ON_FOCUS_DEFER_EVENT, this);
|
||||||
comp.setAttribute(ITabOnSelectHandler.ATTRIBUTE_KEY, this);
|
comp.setAttribute(ITabOnSelectHandler.ATTRIBUTE_KEY, this);
|
||||||
|
|
||||||
return comp;
|
return comp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -946,6 +949,8 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
|
||||||
|
|
||||||
protected ADWindow adwindow;
|
protected ADWindow adwindow;
|
||||||
|
|
||||||
|
protected boolean showingOnExitDialog;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see ToolbarListener#onLock()
|
* @see ToolbarListener#onLock()
|
||||||
*/
|
*/
|
||||||
|
@ -1142,11 +1147,22 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
|
||||||
/**
|
/**
|
||||||
* @param callback
|
* @param callback
|
||||||
*/
|
*/
|
||||||
public void onExit(Callback<Boolean> callback)
|
public synchronized void onExit(Callback<Boolean> callback)
|
||||||
{
|
{
|
||||||
if (isPendingChanges())
|
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
|
else
|
||||||
{
|
{
|
||||||
|
@ -1795,6 +1811,10 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
|
||||||
adTabbox.getSelectedGridTab().isNew() ||
|
adTabbox.getSelectedGridTab().isNew() ||
|
||||||
(adTabbox.getSelectedDetailADTabpanel() != null && adTabbox.getSelectedDetailADTabpanel().getGridTab().isNew()));
|
(adTabbox.getSelectedDetailADTabpanel() != null && adTabbox.getSelectedDetailADTabpanel().getGridTab().isNew()));
|
||||||
|
|
||||||
|
if (!e.isError() && Util.isEmpty(adInfo)) {
|
||||||
|
autoSaveChanges(e);
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// No Rows
|
// No Rows
|
||||||
if (e.getTotalRows() == 0 && insertRecord && !detailTab && !tabPanel.getGridTab().isSortTab())
|
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
|
* @return boolean
|
||||||
*/
|
*/
|
||||||
|
@ -2397,7 +2446,7 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
|
||||||
if (result) {
|
if (result) {
|
||||||
WindowValidatorEvent event = new WindowValidatorEvent(adwindow, WindowValidatorEventType.AFTER_SAVE.getName());
|
WindowValidatorEvent event = new WindowValidatorEvent(adwindow, WindowValidatorEventType.AFTER_SAVE.getName());
|
||||||
WindowValidatorManager.getInstance().fireWindowValidatorEvent(event, callback);
|
WindowValidatorManager.getInstance().fireWindowValidatorEvent(event, callback);
|
||||||
} else {
|
} else if (callback != null) {
|
||||||
callback.onCallback(result);
|
callback.onCallback(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3896,4 +3945,13 @@ public abstract class AbstractADWindowContent extends AbstractUIPart implements
|
||||||
public GridWindow getGridWindow() {
|
public GridWindow getGridWindow() {
|
||||||
return gridWindow;
|
return gridWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* set component of last focus editor.
|
||||||
|
* Use in onClose/Exit to restore focus
|
||||||
|
* @param component
|
||||||
|
*/
|
||||||
|
public void setLastFocusEditor(Component component) {
|
||||||
|
lastFocusEditor = component;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1094,6 +1094,7 @@ public class CompositeADTabbox extends AbstractADTabbox
|
||||||
}
|
}
|
||||||
if (adtab.getGridTab().getCurrentRow() != currentRow)
|
if (adtab.getGridTab().getCurrentRow() != currentRow)
|
||||||
adtab.getGridTab().setCurrentRow(currentRow, true);
|
adtab.getGridTab().setCurrentRow(currentRow, true);
|
||||||
|
Executions.schedule(getComponent().getDesktop(), e->((ADTabpanel)headerTab).focusToFirstEditor(), new Event("onFocusToHeaderTab"));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.Properties;
|
||||||
import org.adempiere.webui.AdempiereWebUI;
|
import org.adempiere.webui.AdempiereWebUI;
|
||||||
import org.adempiere.webui.ClientInfo;
|
import org.adempiere.webui.ClientInfo;
|
||||||
import org.adempiere.webui.LayoutUtils;
|
import org.adempiere.webui.LayoutUtils;
|
||||||
|
import org.adempiere.webui.adwindow.ADWindow;
|
||||||
import org.adempiere.webui.adwindow.IFieldEditorContainer;
|
import org.adempiere.webui.adwindow.IFieldEditorContainer;
|
||||||
import org.adempiere.webui.component.Bandbox;
|
import org.adempiere.webui.component.Bandbox;
|
||||||
import org.adempiere.webui.component.Button;
|
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.addEventListener(INIT_EDIT_EVENT, this);
|
||||||
component.setAttribute("idempiere.editor", this);
|
component.setAttribute("idempiere.editor", this);
|
||||||
|
|
||||||
|
component.addEventListener(Events.ON_FOCUS, e -> {
|
||||||
|
ADWindow adwindow = ADWindow.findADWindow(component);
|
||||||
|
if (adwindow != null) {
|
||||||
|
adwindow.getADWindowContent().setLastFocusEditor(component);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue