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;
|
||||
} // 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() {
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -211,7 +211,9 @@ 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
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue