Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Add record in combo box #2088

Merged
merged 37 commits into from Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
c30563b
Add new record button appears in combo box if NewRecordScreenBinding …
JindrichSusen Oct 4, 2023
3574f14
Changed menu item in demo project
JindrichSusen Oct 5, 2023
bd8d6e8
MenuItemId sent to client
JindrichSusen Oct 6, 2023
4170768
Merge branch 'master' into add-record-in-combo-box
JindrichSusen Oct 10, 2023
9142738
Adding a new record in combo box is partially implemented
JindrichSusen Oct 13, 2023
db976a7
Save and Cancel buttons added to the new record screen
JindrichSusen Oct 20, 2023
25fd9da
New record screen is always open in form view
JindrichSusen Oct 20, 2023
ae3c27c
NewRecordScreens are closed after browser reload
JindrichSusen Oct 20, 2023
ec7d48f
Clicked dropdown editor is closed after clicking the "+" and is reope…
JindrichSusen Oct 20, 2023
2a622f8
NewRecordScreen refactored
JindrichSusen Oct 20, 2023
1f24e1e
Newly created record is set as the current value in the source combo box
JindrichSusen Oct 23, 2023
9974570
Merge branch 'master' into add-record-in-combo-box
JindrichSusen Oct 23, 2023
51f09db
NewRecordSessionStore refactored
JindrichSusen Oct 23, 2023
ff14bae
"Add New Record" localized
JindrichSusen Oct 23, 2023
958bb39
openNewForm method refactored
JindrichSusen Oct 23, 2023
a8c8d53
Dropdown width was not adjusted to "Add New Record" text
JindrichSusen Oct 23, 2023
a78a0a6
"Add New Record" is highlighted and can be activated by pressing Enter
JindrichSusen Oct 23, 2023
9289df5
Enter in new record screen will save the data and close the screen
JindrichSusen Oct 24, 2023
088c80d
Enter in new record screen will save the data and close the screen
JindrichSusen Oct 24, 2023
2971d87
Close button is not shown on the new record screen
JindrichSusen Oct 24, 2023
66c570e
Pressing Esc will close the new record screen
JindrichSusen Oct 24, 2023
dbc9e10
Added NoRecursiveNewRecordScreenBindings rule
JindrichSusen Oct 25, 2023
9153278
New record screen works in form view
JindrichSusen Oct 25, 2023
3415d75
Merge remote-tracking branch 'origin/add-record-in-combo-box' into ad…
JindrichSusen Oct 25, 2023
120c40e
Build fixes
JindrichSusen Oct 25, 2023
ae48d6d
License added
JindrichSusen Oct 25, 2023
e14ae8d
License added
JindrichSusen Oct 25, 2023
a20036c
Code formatting
washibana Oct 26, 2023
9fafc34
Redundant import removed
JindrichSusen Oct 26, 2023
849f0e6
Merge remote-tracking branch 'origin/add-record-in-combo-box' into ad…
JindrichSusen Oct 26, 2023
e105179
Lookups are resolved from ScreenSections
JindrichSusen Oct 27, 2023
815673c
if statement simplified
JindrichSusen Oct 27, 2023
12277aa
Blank line removed
JindrichSusen Oct 27, 2023
bfc5b4b
Redundant code removed
JindrichSusen Oct 27, 2023
f515423
First valid NewRecordScreenBinding returned
JindrichSusen Oct 27, 2023
b582ff6
Read only role and read only access are checked
JindrichSusen Oct 30, 2023
a4472cf
Merge branch 'master' into add-record-in-combo-box
JindrichSusen Nov 7, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -22,7 +22,7 @@
using System;
using System.Xml;
using System.Data;

using System.Linq;
using Origam.Schema;
using Origam.Workbench.Services;
using Origam.Schema.EntityModel;
Expand Down Expand Up @@ -161,6 +161,17 @@ public static void BuildCommonDropdown(XmlElement propertyElement, Guid lookupId
useCache = false;
}
}

var dataLookupService = ServiceManager.Services.GetService<DataLookupService>();
NewRecordScreenBinding newRecordScreenBinding = dataLookupService.GetNewRecordScreenBinding(lookup);
if (newRecordScreenBinding != null)
{
XmlElement newRecordElement = propertyElement.OwnerDocument.CreateElement("NewRecordScreen");
newRecordElement.SetAttribute("Width", XmlConvert.ToString(newRecordScreenBinding.DialogWidth));
newRecordElement.SetAttribute("Height", XmlConvert.ToString(newRecordScreenBinding.DialogHeight));
newRecordElement.SetAttribute("MenuItemId", XmlConvert.ToString(newRecordScreenBinding.MenuItemId));
propertyElement.AppendChild(newRecordElement);
}

propertyElement.SetAttribute("Cached", XmlConvert.ToString(useCache));
}
Expand Down
Expand Up @@ -353,7 +353,8 @@ public DataStructureSortSet ListSortSet
public override Type[] NewItemTypes => new[]
{
typeof(DataLookupMenuBinding),
typeof(DataServiceDataTooltip)
typeof(DataServiceDataTooltip),
typeof(NewRecordScreenBinding)
};

public override T NewItem<T>(
Expand Down
@@ -0,0 +1,232 @@
#region license
/*
Copyright 2005 - 2023 Advantage Solutions, s. r. o.

This file is part of ORIGAM (http://www.origam.org).

ORIGAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ORIGAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ORIGAM. If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Security.Principal;
using System.Xml.Serialization;
using Origam.DA.Common;
using Origam.DA.ObjectPersistence;
using Origam.Schema.EntityModel;
using Origam.Schema.GuiModel;
using Origam.Schema.MenuModel;
using Origam.Workbench.Services;

namespace Origam.Schema.LookupModel;

[SchemaItemDescription("New Record Screen Binding", "icon_menu-binding.png")]
[HelpTopic("")]
[XmlModelRoot(CategoryConst)]
[DefaultProperty("MenuItem")]
[ClassMetaVersion("6.0.0")]
public class NewRecordScreenBinding : AbstractSchemaItem, IAuthorizationContextContainer, IComparable
{
public const string CategoryConst = "NewRecordScreenBinding";

public NewRecordScreenBinding() { }

public NewRecordScreenBinding(Guid schemaExtensionId) : base(schemaExtensionId) { }

public NewRecordScreenBinding(Key primaryKey) : base(primaryKey) {}


public bool IsAvailable
{
get
{
if (MenuItem is not FormReferenceMenuItem referenceMenuItem ||
referenceMenuItem.ReadOnlyAccess)
{
return false;
}

IParameterService parameterService = ServiceManager.Services
.GetService<IParameterService>();
IOrigamAuthorizationProvider authorizationProvider =
SecurityManager.GetAuthorizationProvider();
IPrincipal principal = SecurityManager.CurrentPrincipal;

string authContext = SecurityManager
.GetReadOnlyRoles(referenceMenuItem.AuthorizationContext);
bool hasReadOnlyRole = SecurityManager
.GetAuthorizationProvider()
.Authorize(SecurityManager.CurrentPrincipal, authContext);
if (hasReadOnlyRole)
{
return false;
}

return
tvavrda marked this conversation as resolved.
Show resolved Hide resolved
authorizationProvider.Authorize(principal, AuthorizationContext)
&& authorizationProvider.Authorize(principal,
MenuItem.AuthorizationContext)
&& parameterService.IsFeatureOn(MenuItem.Features);
}
}


#region Overriden AbstractSchemaItem Members
public override string ItemType => CategoryConst;

public override void GetExtraDependencies(ArrayList dependencies)
{
dependencies.Add(MenuItem);

AbstractSchemaItem menu = MenuItem;
JindrichSusen marked this conversation as resolved.
Show resolved Hide resolved
while(menu.ParentItem != null)
{
menu = menu.ParentItem;
dependencies.Add(menu);
}
base.GetExtraDependencies (dependencies);
}

public override SchemaItemCollection ChildItems => new();

public override bool CanMove(UI.IBrowserNode2 newNode)
{
return newNode is AbstractDataLookup;
}

#endregion

#region Properties
public Guid MenuItemId;

[Category("Menu Reference")]
[TypeConverter(typeof(MenuItemConverter))]
[NotNullModelElementRule]
[NotNullMenuRecordEditMethod]
[NoRecursiveNewRecordScreenBindingsRule]
[XmlReference("menuItem", "MenuItemId")]
public AbstractMenuItem MenuItem
{
get => PersistenceProvider.RetrieveInstance<AbstractMenuItem>(MenuItemId);
set => MenuItemId = value == null
? Guid.Empty
: (Guid)value.PrimaryKey["Id"];
}

[Category("Security")]
[NotNullModelElementRule]
[XmlAttribute("roles")]
public string Roles { get; set; }

[XmlAttribute("dialogWidth")]
public int DialogWidth { get; set; }

[XmlAttribute("dialogHeight")]
public int DialogHeight { get; set; }

#endregion

#region IAuthorizationContextContainer Members

[Browsable(false)]
public string AuthorizationContext => Roles;

#endregion
}



[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)]
class NoRecursiveNewRecordScreenBindingsRule : AbstractModelElementRuleAttribute
{
public override Exception CheckRule(object instance)
{
if (!(instance is NewRecordScreenBinding newRecordScreenBinding))
{
return new Exception(
$"{nameof(NoRecursiveNewRecordScreenBindingsRule)} can be only applied to type {nameof(NewRecordScreenBinding)}");
}

if (newRecordScreenBinding.MenuItem == null)
{
return null;
}

if (newRecordScreenBinding.MenuItem is not FormReferenceMenuItem menuItem)
{
return new Exception(
$"The MenuItem in {nameof(NewRecordScreenBinding)} must be a {nameof(FormReferenceMenuItem)}");
}

IEnumerable<IDataLookup> allLookups = menuItem.Screen.ChildrenRecursive
.OfType<ControlSetItem>()
.SelectMany(controlSetItem =>
{
IEnumerable<PanelControlSet> panelControlSets = FindScreenSections(controlSetItem);
IEnumerable<ControlSetItem> comboBoxes = panelControlSets.SelectMany(FindComboBoxes);
IEnumerable<IDataLookup> lookups = comboBoxes.SelectMany(GetLookups);
return lookups;
})
.Distinct();

var conflictingNewRecordBindingIds = allLookups
.Select(lookup => lookup.ChildItems.ToGeneric().OfType<NewRecordScreenBinding>().FirstOrDefault())
.Where(binding => binding != null)
.Select(binding => binding.Id.ToString())
.ToList();

if (conflictingNewRecordBindingIds.Count != 0)
{
return new Exception("The selected menu item references a screen with NewRecordScreenBindings. " +
"This would lead to nested NewRecordScreenBindings and is therefore not allowed. " +
$"The conflicting NewRecordScreenBinding ids are: [{string.Join(", ",conflictingNewRecordBindingIds)}]");
}

return null;
}

private IEnumerable<PanelControlSet> FindScreenSections(ControlSetItem controlSetItem)
{
var dependencies = new ArrayList();
controlSetItem.GetExtraDependencies(dependencies);
return dependencies.OfType<PanelControlSet>();
}

private IEnumerable<ControlSetItem> FindComboBoxes(PanelControlSet panelControlSet)
{
if (panelControlSet == null)
{
return Enumerable.Empty<ControlSetItem>();
}
return panelControlSet.ChildrenRecursive
.OfType<ControlSetItem>()
.Where(item => item.Name == "AsCombo");
tvavrda marked this conversation as resolved.
Show resolved Hide resolved
}

private IEnumerable<IDataLookup> GetLookups(ControlSetItem comboBox)
{
var dependencies = new ArrayList();
comboBox.GetExtraDependencies(dependencies);
return dependencies.OfType<IDataLookup>();
}

public override Exception CheckRule(object instance, string memberName)
{
return CheckRule(instance);
}
}
9 changes: 8 additions & 1 deletion backend/Origam.Server/Common/SessionManager.cs
Expand Up @@ -257,7 +257,14 @@ public SessionStore CreateSessionStore(UIRequest request, IBasicUIService basicU
menuItem = ps.SchemaProvider.RetrieveInstance(typeof(AbstractMenuItem), new ModelElementKey(new Guid(request.ObjectId))) as AbstractMenuItem;
formMenuItem = menuItem as FormReferenceMenuItem;
// FORM
ss = new FormSessionStore(basicUIService, request, menuItem.Name, analytics);
if (request.CreateNewRecord)
{
ss = new NewRecordSessionStore(basicUIService, request, menuItem.Name, analytics);
}
else
{
ss = new FormSessionStore(basicUIService, request, menuItem.Name, analytics);
}
break;

case UIRequestType.WorkflowReferenceMenuItem:
Expand Down
1 change: 1 addition & 0 deletions backend/Origam.Server/Common/UIRequest.cs
Expand Up @@ -63,6 +63,7 @@ public class UIRequest
public string Icon { get; set; }
public string ObjectId { get; set; }
public bool IsSingleRecordEdit { get; set; }
public bool CreateNewRecord { get; set; }
public bool RequestCurrentRecordId { get; set; }
public int DialogWidth { get; set; }
public int DialogHeight { get; set; }
Expand Down
2 changes: 1 addition & 1 deletion backend/Origam.Server/ServerCoreUIService.cs
Expand Up @@ -145,7 +145,7 @@ public PortalResult InitPortal(int maxRequestLength)
{
var sessionStore = mainSessionStore.ActiveSession
?? mainSessionStore;
if((sessionStore is SelectionDialogSessionStore)
if(sessionStore is SelectionDialogSessionStore
|| sessionStore.IsModalDialog)
{
sessionsToDestroy.Add(sessionStore.Id);
Expand Down
73 changes: 73 additions & 0 deletions backend/Origam.Server/Session Stores/NewRecordSessionStore.cs
@@ -0,0 +1,73 @@
#region license
/*
Copyright 2005 - 2023 Advantage Solutions, s. r. o.

This file is part of ORIGAM (http://www.origam.org).

ORIGAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

ORIGAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with ORIGAM. If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

using System;
using System.Globalization;
using Origam.DA;
using Origam.DA.ObjectPersistence;
using Origam.Schema;
using Origam.Schema.EntityModel;
using Origam.Schema.GuiModel;
using Origam.Schema.MenuModel;
using Origam.Workbench.Services;

namespace Origam.Server;

public class NewRecordSessionStore : FormSessionStore
{
public NewRecordSessionStore(IBasicUIService service, UIRequest request,
string name, FormReferenceMenuItem menuItem, Analytics analytics)
: base(service, request, name, menuItem, analytics)
{
IsModalDialog = true;
}

public NewRecordSessionStore(IBasicUIService service, UIRequest request,
string name, Analytics analytics)
: base(service, request, name, analytics)
{
IsModalDialog = true;
}

public override void Init()
{
IPersistenceService persistence = ServiceManager.Services.GetService<IPersistenceService>();
IPersistenceProvider schemaProvider = persistence.SchemaProvider;
var formMenuItem = schemaProvider.RetrieveInstance<FormReferenceMenuItem>(new Guid(Request.ObjectId));
var screen = schemaProvider.RetrieveInstance<FormControlSet>(formMenuItem.ScreenId);
var dataStructure = schemaProvider.RetrieveInstance<DataStructure>(screen.DataSourceId);
var rootEntity = ((DataStructureEntity)dataStructure.Entities[0])!.RootEntity;

var dataService = Workbench.Services.CoreServices.DataServiceFactory.GetDataService();
var dataSet = dataService.GetEmptyDataSet(
rootEntity.ParentItemId, CultureInfo.InvariantCulture);
var table = dataSet.Tables[rootEntity.Name];
var row = table.NewRow();

DatasetTools.ApplyPrimaryKey(row);
DatasetTools.UpdateOrigamSystemColumns(
row, true, SecurityManager.CurrentUserProfile().Id);
row.Table.NewRow();

dataSet.Tables[rootEntity.Name].Rows.Add(row);
SetDataSource(dataSet);
}
}