New presentation data layer (compositions), CollectionContainerOptions

This commit is contained in:
Konstantin Krivopustov 2018-09-04 13:21:38 +04:00 committed by Yuriy Artamonov
parent aa694610d2
commit d14876a5a3
22 changed files with 532 additions and 41 deletions

View File

@ -49,6 +49,11 @@
</property>
</view>
<view class="com.haulmont.cuba.security.entity.UserRole" name="tmp.user.edit">
<property name="user" view="_minimal"/>
<property name="role" view="_minimal"/>
</view>
<view class="com.haulmont.cuba.security.entity.User" name="user.browse" extends="_local" systemProperties="true">
<property name="group" view="_minimal"/>
</view>

View File

@ -0,0 +1,134 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.gui.components.data.options;
import com.haulmont.bali.events.EventHub;
import com.haulmont.bali.events.Subscription;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.gui.components.data.BindingState;
import com.haulmont.cuba.gui.components.data.EntityOptionsSource;
import com.haulmont.cuba.gui.components.data.OptionsSource;
import com.haulmont.cuba.gui.model.CollectionContainer;
import com.haulmont.cuba.gui.model.CollectionLoader;
import javax.annotation.Nullable;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Stream;
public class CollectionContainerOptions<E extends Entity<K>, K> implements OptionsSource<E>, EntityOptionsSource<E> {
protected CollectionContainer<E> container;
private CollectionLoader loader;
protected EventHub events = new EventHub();
protected BindingState state = BindingState.INACTIVE;
protected E deferredSelectedItem;
public CollectionContainerOptions(CollectionContainer<E> container, @Nullable CollectionLoader loader) {
this.container = container;
this.loader = loader;
this.container.addCollectionChangeListener(this::containerCollectionChanged);
this.container.addItemPropertyChangeListener(this::containerItemPropertyChanged);
}
protected void containerCollectionChanged(CollectionContainer.CollectionChangeEvent<E> e) {
if (deferredSelectedItem != null) {
container.setItem(deferredSelectedItem);
deferredSelectedItem = null;
}
events.publish(OptionsChangeEvent.class, new OptionsChangeEvent<>(this));
}
@SuppressWarnings("unchecked")
protected void containerItemPropertyChanged(CollectionContainer.ItemPropertyChangeEvent<E> e) {
events.publish(ValueChangeEvent.class, new ValueChangeEvent(this, e.getPrevValue(), e.getValue()));
}
@Override
public MetaClass getEntityMetaClass() {
return container.getEntityMetaClass();
}
@Override
public void setSelectedItem(E item) {
if (item == null) {
container.setItem(null);
} else {
if (container.getItems().size() > 0) {
container.setItem(item);
} else {
this.deferredSelectedItem = item;
}
}
}
@Override
public boolean containsItem(E item) {
return item != null && container.getItemIndex(item.getId()) > -1;
}
@Override
public void updateItem(E item) {
container.replaceItem(item);
}
@Override
public void refresh() {
if (loader != null)
loader.load();
}
@Override
public void refresh(Map<String, Object> parameters) {
if (loader != null)
loader.load();
}
@Override
public Stream<E> getOptions() {
return container.getItems().stream();
}
@Override
public BindingState getState() {
return BindingState.ACTIVE;
}
@SuppressWarnings("unchecked")
@Override
public Subscription addStateChangeListener(Consumer<StateChangeEvent<E>> listener) {
return events.subscribe(StateChangeEvent.class, (Consumer) listener);
}
@SuppressWarnings("unchecked")
@Override
public Subscription addValueChangeListener(Consumer<ValueChangeEvent<E>> listener) {
return events.subscribe(ValueChangeEvent.class, (Consumer) listener);
}
@SuppressWarnings("unchecked")
@Override
public Subscription addOptionsChangeListener(Consumer<OptionsChangeEvent<E>> listener) {
return events.subscribe(OptionsChangeEvent.class, (Consumer) listener);
}
}

View File

@ -43,6 +43,8 @@ public interface CollectionContainer<E extends Entity> extends InstanceContainer
int getItemIndex(Object entityId);
void replaceItem(E entity);
/**
*
*/

View File

@ -17,6 +17,7 @@
package com.haulmont.cuba.gui.model.impl;
import com.haulmont.bali.events.Subscription;
import com.haulmont.bali.util.Preconditions;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.gui.model.CollectionContainer;
@ -48,8 +49,8 @@ public class CollectionContainerImpl<E extends Entity>
@Override
public void setItem(@Nullable E item) {
if (item != null) {
Integer idx = getItemIndex(item.getId());
if (idx < -1) {
int idx = getItemIndex(item.getId());
if (idx == -1) {
throw new IllegalArgumentException("CollectionContainer does not contain " + item);
}
E existingItem = collection.get(idx);
@ -89,7 +90,7 @@ public class CollectionContainerImpl<E extends Entity>
@Nullable
@Override
public E getItemOrNull(Object entityId) {
Integer idx = getItemIndex(entityId);
int idx = getItemIndex(entityId);
return idx != -1 ? collection.get(idx) : null;
}
@ -99,6 +100,27 @@ public class CollectionContainerImpl<E extends Entity>
return idx != null ? idx : -1;
}
@Override
public void replaceItem(E entity) {
Preconditions.checkNotNullArgument(entity, "entity is null");
Object id = entity.getId();
int idx = getItemIndex(id);
if (idx > -1) {
E prev = collection.get(idx);
detachListener(prev);
if (prev == getItemOrNull()) {
this.item = entity;
fireItemChanged(prev);
}
collection.set(idx, entity);
} else {
collection.add(entity);
}
attachListener(entity);
buildIdMap();
fireCollectionChanged();
}
@Override
public E getItem(Object entityId) {
E item = getItemOrNull(entityId);
@ -149,8 +171,23 @@ public class CollectionContainerImpl<E extends Entity>
}
protected void clearItemIfNotExists() {
if (item != null && getItemIndex(item.getId()) == -1) {
setItem(null);
if (item != null) {
int idx = getItemIndex(item.getId());
if (idx == -1) {
// item doesn't exist in the collection
E prevItem = item;
detachListener(prevItem);
item = null;
fireItemChanged(prevItem);
} else {
E newItem = collection.get(idx);
if (newItem != item) {
E prevItem = item;
detachListener(prevItem);
item = newItem;
fireItemChanged(prevItem);
}
}
}
}

View File

@ -76,7 +76,7 @@ public class InstanceContainerImpl<T extends Entity> implements InstanceContaine
final MetaClass aClass = item.getMetaClass();
if (!aClass.equals(entityMetaClass) && !entityMetaClass.getDescendants().contains(aClass)) {
throw new DevelopmentException(String.format("Invalid item's metaClass '%s'", aClass),
ParamsMap.of("datasource", toString(), "metaClass", aClass));
ParamsMap.of("container", toString(), "metaClass", aClass));
}
attachListener(item);
}

View File

@ -55,7 +55,7 @@ public class ScreenDataXmlLoader {
for (Element el : element.elements()) {
if (el.getName().equals("collection")) {
loadNestedContainer(screenData, el);
loadCollectionContainer(screenData, el);
} else if (el.getName().equals("instance")) {
loadInstanceContainer(screenData, el);
}
@ -80,7 +80,7 @@ public class ScreenDataXmlLoader {
}
}
protected void loadNestedContainer(ScreenData screenData, Element element) {
protected void loadCollectionContainer(ScreenData screenData, Element element) {
String containerId = getRequiredAttr(element, "id");
CollectionContainer<Entity> container = factory.createCollectionContainer(getEntityClass(element));

View File

@ -16,6 +16,7 @@
package com.haulmont.cuba.gui.model.impl;
import com.google.common.collect.Sets;
import com.haulmont.bali.events.EventRouter;
import com.haulmont.bali.util.Numbers;
import com.haulmont.bali.util.Preconditions;
@ -25,6 +26,7 @@ import com.haulmont.chile.core.model.impl.AbstractInstance;
import com.haulmont.cuba.core.entity.*;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.gui.model.DataContext;
import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext;
import javax.annotation.Nullable;
@ -139,6 +141,7 @@ public class StandardDataContext implements DataContext {
if (managedInstance != null) {
if (managedInstance != entity) {
copyState(entity, managedInstance);
copyReferences(entity, managedInstance);
}
return managedInstance;
}
@ -152,6 +155,31 @@ public class StandardDataContext implements DataContext {
return entity;
}
protected void copyReferences(Entity srcEntity, Entity dstEntity) {
EntityStates entityStates = getEntityStates();
for (MetaProperty property : getMetadata().getClassNN(srcEntity.getClass()).getProperties()) {
String propertyName = property.getName();
if (!property.getRange().isClass()
|| property.getRange().getCardinality().isMany()
|| !entityStates.isLoaded(srcEntity, propertyName)
|| !entityStates.isLoaded(dstEntity, propertyName)) {
continue;
}
Object value = srcEntity.getValue(propertyName);
boolean srcNew = entityStates.isNew(srcEntity);
if (!srcNew || value != null) {
if (value == null) {
dstEntity.setValue(propertyName, null);
} else {
Entity srcRef = (Entity) value;
Entity dstRef = internalMerge(srcRef);
((AbstractInstance) dstEntity).setValue(propertyName, dstRef, false);
}
}
}
}
/**
* (1) src.new -> dst.new : copy all non-null - should not happen (happens in setParent?)
* (2) src.new -> dst.det : do nothing - should not happen
@ -426,6 +454,52 @@ public class StandardDataContext implements DataContext {
return resultList;
}
public String printContent() {
StringBuilder sb = new StringBuilder();
for (Map.Entry<Class<?>, Map<Object, Entity>> entry : content.entrySet()) {
sb.append("=== ").append(entry.getKey().getSimpleName()).append(" ===\n");
for (Entity entity : entry.getValue().values()) {
sb.append(printEntity(entity, 1, Sets.newIdentityHashSet())).append('\n');
}
}
return sb.toString();
}
protected String printEntity(Entity entity, int level, Set<Entity> visited) {
StringBuilder sb = new StringBuilder();
sb.append(printObject(entity)).append(" ").append(entity.toString()).append("\n");
if (visited.contains(entity)) {
return sb.toString();
}
visited.add(entity);
for (MetaProperty property : getMetadata().getClassNN(entity.getClass()).getProperties()) {
if (!property.getRange().isClass() || !getEntityStates().isLoaded(entity, property.getName()))
continue;
Object value = entity.getValue(property.getName());
String prefix = StringUtils.repeat(" ", level);
if (value instanceof Entity) {
String str = printEntity((Entity) value, level + 1, visited);
if (!str.equals(""))
sb.append(prefix).append(str);
} else if (value instanceof Collection) {
sb.append(prefix).append(value.getClass().getSimpleName()).append("[\n");
for (Object item : (Collection) value) {
String str = printEntity((Entity) item, level + 1, visited);
if (!str.equals(""))
sb.append(prefix).append(str);
}
sb.append(prefix).append("]\n");
}
}
return sb.toString();
}
protected String printObject(Object object) {
return "{" + object.getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(object)) + "}";
}
protected class ChangeListener implements Instance.PropertyChangeListener {
@Override
public void propertyChanged(Instance.PropertyChangeEvent e) {

View File

@ -22,8 +22,15 @@ import com.haulmont.cuba.gui.components.CaptionMode;
import com.haulmont.cuba.gui.components.DatasourceComponent;
import com.haulmont.cuba.gui.components.Frame;
import com.haulmont.cuba.gui.components.LookupField;
import com.haulmont.cuba.gui.components.data.options.CollectionContainerOptions;
import com.haulmont.cuba.gui.components.data.value.CollectionContainerTableSource;
import com.haulmont.cuba.gui.data.CollectionDatasource;
import com.haulmont.cuba.gui.data.Datasource;
import com.haulmont.cuba.gui.model.CollectionContainer;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.model.ScreenData;
import com.haulmont.cuba.gui.screen.FrameOwner;
import com.haulmont.cuba.gui.screen.ScreenUtils;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Element;
@ -131,6 +138,24 @@ public class LookupFieldLoader extends AbstractFieldLoader<LookupField> {
}
}
@SuppressWarnings("unchecked")
@Override
protected void loadContainer(LookupField component, Element element) {
super.loadContainer(component, element);
String containerId = element.attributeValue("optionsContainer");
if (containerId != null) {
FrameOwner frameOwner = context.getFrame().getFrameOwner();
ScreenData screenData = ScreenUtils.getScreenData(frameOwner);
InstanceContainer container = screenData.getContainer(containerId);
if (!(container instanceof CollectionContainer)) {
throw new GuiDevelopmentException("Not a CollectionContainer: " + containerId, context.getCurrentFrameId());
}
component.setOptionsSource(
new CollectionContainerOptions((CollectionContainer) container, screenData.findLoaderOf(container)));
}
}
@Override
protected void loadDatasource(DatasourceComponent component, Element element) {
super.loadDatasource(component, element);

View File

@ -18,6 +18,7 @@ package com.haulmont.cuba.web.gui.components;
import com.haulmont.bali.events.Subscription;
import com.haulmont.chile.core.datatypes.Datatype;
import com.haulmont.chile.core.model.Range;
import com.haulmont.cuba.core.global.UserSessionSource;
import com.haulmont.cuba.gui.components.data.DataAwareComponentsTools;
import com.haulmont.cuba.gui.components.TextField;
@ -100,7 +101,13 @@ public class WebTextField<V> extends WebV8AbstractField<CubaTextField, String, V
if (valueBinding != null
&& valueBinding.getSource() instanceof EntityValueSource) {
EntityValueSource entityValueSource = (EntityValueSource) valueBinding.getSource();
Datatype<V> propertyDataType = entityValueSource.getMetaPropertyPath().getRange().asDatatype();
Range range = entityValueSource.getMetaPropertyPath().getRange();
if (!range.isDatatype()) {
throw new IllegalStateException(String.format(
"Property '%s' has %s. TextField can be bound only to a simple data type",
entityValueSource.getMetaPropertyPath().getMetaProperty().getName(), range));
}
Datatype<V> propertyDataType = range.asDatatype();
return nullToEmpty(propertyDataType.format(modelValue));
}

View File

@ -69,7 +69,7 @@ public class DcScreen5 extends Screen {
dcScreen6.addAfterCloseListener(afterCloseEvent -> {
CloseAction closeAction = afterCloseEvent.getCloseAction();
if ((closeAction instanceof StandardCloseAction) && ((StandardCloseAction) closeAction).getActionId().equals(Window.COMMIT_ACTION_ID)) {
usersCont.getMutableItems().add(0, (User) dcScreen6.getEditedEntity());
usersCont.getMutableItems().add(0, dcScreen6.getEditedEntity());
}
});
screens.show(dcScreen6);

View File

@ -18,22 +18,26 @@ package com.haulmont.cuba.web.tmp;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.EntityStates;
import com.haulmont.cuba.gui.Screens;
import com.haulmont.cuba.gui.components.Button;
import com.haulmont.cuba.gui.components.Window;
import com.haulmont.cuba.gui.model.CollectionContainer;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.screen.StandardEditor;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.gui.screen.*;
import com.haulmont.cuba.gui.screen.events.BeforeShowEvent;
import com.haulmont.cuba.security.entity.User;
import com.haulmont.cuba.security.entity.UserRole;
import javax.inject.Inject;
@UiController("dcScreen6")
@UiDescriptor("dc-screen-6.xml")
public class DcScreen6 extends StandardEditor<User> {
@Inject
protected EntityStates entityStates;
protected Screens screens;
@Inject
protected CollectionContainer<UserRole> userRolesCont;
@Override
protected InstanceContainer<Entity> getEditedEntityContainer() {
@ -45,6 +49,25 @@ public class DcScreen6 extends StandardEditor<User> {
getScreenData().loadAll();
}
@Subscribe("editBtn")
private void onEditClick(Button.ClickEvent event) {
UserRole selectedUserRole = userRolesCont.getItemOrNull();
if (selectedUserRole != null) {
TmpUserRoleEdit userRoleEdit = screens.create(TmpUserRoleEdit.class, OpenMode.THIS_TAB);
userRoleEdit.setEntityToEdit(selectedUserRole);
ScreenUtils.getScreenData(userRoleEdit).getDataContext().setParent(getScreenData().getDataContext());
userRoleEdit.addAfterCloseListener(afterCloseEvent -> {
CloseAction closeAction = afterCloseEvent.getCloseAction();
if ((closeAction instanceof StandardCloseAction) && ((StandardCloseAction) closeAction).getActionId().equals(Window.COMMIT_ACTION_ID)) {
userRolesCont.replaceItem(userRoleEdit.getEditedEntity());
}
});
screens.show(userRoleEdit);
}
}
@Subscribe("okBtn")
protected void onOkClick(Button.ClickEvent event) {
closeWithCommit();

View File

@ -0,0 +1,52 @@
/*
* Copyright (c) 2008-2018 Haulmont.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.haulmont.cuba.web.tmp;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.gui.components.Button;
import com.haulmont.cuba.gui.model.InstanceContainer;
import com.haulmont.cuba.gui.screen.StandardEditor;
import com.haulmont.cuba.gui.screen.Subscribe;
import com.haulmont.cuba.gui.screen.UiController;
import com.haulmont.cuba.gui.screen.UiDescriptor;
import com.haulmont.cuba.gui.screen.events.BeforeShowEvent;
import com.haulmont.cuba.security.entity.UserRole;
@UiController("tmpUserRoleEdit")
@UiDescriptor("tmp-user-role-edit.xml")
public class TmpUserRoleEdit extends StandardEditor<UserRole> {
@Override
protected InstanceContainer<Entity> getEditedEntityContainer() {
return getScreenData().getContainer("userRoleCont");
}
@Subscribe
protected void beforeShow(BeforeShowEvent event) {
getScreenData().loadAll();
}
@Subscribe("okBtn")
protected void onOkClick(Button.ClickEvent event) {
closeWithCommit();
}
@Subscribe("cancelBtn")
protected void onCancelClick(Button.ClickEvent event) {
close(WINDOW_CLOSE_ACTION);
}
}

View File

@ -14,13 +14,13 @@
~ limitations under the License.
-->
<window caption="Screen 5">
<window caption="User Browser">
<data>
<collection id="usersCont"
class="com.haulmont.cuba.security.entity.User" view="user.browse">
<loader id="usersLoader">
<loader id="usersLoader">
<query>
select u from sec$User u
order by u.name

View File

@ -14,21 +14,35 @@
~ limitations under the License.
-->
<window caption="Screen 6">
<window caption="User Editor">
<data>
<instance id="userCont" class="com.haulmont.cuba.security.entity.User" view="user.edit">
<loader id="userLoader"/>
<collection id="userRolesCont" property="userRoles"/>
</instance>
</data>
<layout spacing="true" expand="spacer">
<layout spacing="true" expand="userRolesTable">
<textField id="loginField" container="userCont" property="login"/>
<textField id="nameField" container="userCont" property="name"/>
<table id="userRolesTable"
width="100%">
<buttonsPanel>
<!--<button id="createBtn" caption="Create"/>-->
<button id="editBtn" caption="Edit"/>
<!--<button id="removeBtn" caption="Remove"/>-->
</buttonsPanel>
<columns>
<column id="role.name"/>
<column id="role.locName"/>
</columns>
<rows container="userRolesCont"/>
</table>
<hbox spacing="true">
<button id="okBtn" caption="OK"/>
<button id="cancelBtn" caption="Cancel"/>
</hbox>
<label id="spacer"/>
</layout>
</window>

View File

@ -0,0 +1,43 @@
<!--
~ Copyright (c) 2008-2018 Haulmont.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<window caption="User Role">
<data>
<instance id="userRoleCont" class="com.haulmont.cuba.security.entity.UserRole" view="tmp.user.edit">
<loader id="userRoleLoader"/>
</instance>
<collection id="rolesCont" class="com.haulmont.cuba.security.entity.Role" view="_minimal">
<loader id="rolesLoader">
<query>
select r from sec$Role r
order by r.name
</query>
</loader>
</collection>
</data>
<layout spacing="true" expand="spacer">
<textField id="userField" container="userRoleCont" property="user.login"/>
<lookupField id="roleField" container="userRoleCont" property="role" optionsContainer="rolesCont"/>
<hbox spacing="true">
<button id="okBtn" caption="OK"/>
<button id="cancelBtn" caption="Cancel"/>
</hbox>
<label id="spacer"/>
</layout>
</window>

View File

@ -50,4 +50,14 @@ public class Customer extends StandardEntity {
public void setStatus(Status status) {
this.status = status.getId();
}
@Override
public String toString() {
return "Customer{" +
"id=" + id +
", version=" + version +
", name='" + name + '\'' +
", status='" + status + '\'' +
"}@" + Integer.toHexString(System.identityHashCode(this));
}
}

View File

@ -102,4 +102,15 @@ public class Order extends StandardEntity {
public void setOrderLines(List<OrderLine> orderLines) {
this.orderLines = orderLines;
}
@Override
public String toString() {
return "Order{" +
"id=" + id +
", version=" + version +
", number='" + number + '\'' +
", date=" + date +
", amount=" + amount +
"}@" + Integer.toHexString(System.identityHashCode(this));
}
}

View File

@ -62,6 +62,10 @@ public class OrderLine extends StandardEntity {
@Override
public String toString() {
return super.toString() + '@' + System.identityHashCode(this);
return "OrderLine{" +
"id=" + id +
", version=" + version +
", quantity=" + quantity +
"}@" + Integer.toHexString(System.identityHashCode(this));
}
}

View File

@ -63,4 +63,14 @@ public class Product extends StandardEntity {
public void setTags(List<ProductTag> tags) {
this.tags = tags;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", version=" + version +
", name='" + name + '\'' +
", price=" + price +
"}@" + Integer.toHexString(System.identityHashCode(this));
}
}

View File

@ -38,4 +38,13 @@ public class ProductTag extends StandardEntity {
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "ProductTag{" +
"id=" + id +
", version=" + version +
", name='" + name + '\'' +
"}@" + Integer.toHexString(System.identityHashCode(this));
}
}

View File

@ -340,6 +340,29 @@ class CompositionTest extends WebSpec {
committed.upd.find { it == orderLine11}.quantity == 12
}
def "one level of composition - changed reference"() {
def orderScreen = new OrderScreen()
def orderLineScreen = new LineScreen()
orderScreen.open(order1)
orderScreen.linesCnt.item = orderLine11
orderLineScreen.open(orderScreen.linesCnt.item, orderScreen.dataContext)
orderLineScreen.lineCnt.item.product = product12
when:
def committed = mockCommit()
orderLineScreen.dataContext.commit()
orderScreen.dataContext.commit()
then:
committed.upd.size() == 1
committed.upd[0].product == product12
}
def "one level of composition - remove item"() {
def orderScreen = new OrderScreen()

View File

@ -16,7 +16,9 @@
package spec.cuba.web.datacontext
import com.haulmont.cuba.client.testsupport.TestSupport
import com.haulmont.cuba.core.app.DataService
import com.haulmont.cuba.core.entity.Entity
import com.haulmont.cuba.core.global.CommitContext
import com.haulmont.cuba.core.global.EntityStates
import com.haulmont.cuba.core.global.Metadata
@ -26,8 +28,8 @@ import com.haulmont.cuba.gui.model.impl.DataContextAccessor
import com.haulmont.cuba.security.entity.Role
import com.haulmont.cuba.security.entity.User
import com.haulmont.cuba.security.entity.UserRole
import com.haulmont.cuba.web.testmodel.sales.OrderLine
import com.haulmont.cuba.web.testmodel.sales.Product
import com.haulmont.cuba.web.testmodel.sales.ProductTag
import com.haulmont.cuba.web.testsupport.TestContainer
import com.haulmont.cuba.web.testsupport.TestServiceProxy
import org.junit.ClassRule
@ -603,36 +605,36 @@ class DataContextTest extends Specification {
removed.isEmpty()
}
def "track many-to-many"() {
def "commit returns different reference"() {
DataContext context = factory.createDataContext()
Product product1 = new Product(name: "p1", price: 100)
Product product2 = new Product(name: "p2", price: 200)
OrderLine line = new OrderLine(quantity: 10, product: product1)
makeDetached(product1, product2, line)
context.merge(line)
Collection committed = []
TestServiceProxy.mock(DataService, Mock(DataService) {
commit(_) >> Collections.emptySet()
commit(_) >> { CommitContext cc ->
committed.addAll(cc.commitInstances)
def entities = TestServiceProxy.getDefault(DataService).commit(cc)
entities.find { it == line }.product = TestSupport.reserialize(product2)
entities
}
})
Product product = new Product(name: 'p1', price: 100, tags: [])
makeDetached(product)
context.merge(product)
ProductTag tag = new ProductTag(name: 't1')
context.merge(tag)
when:
Collection modified = []
context.addPreCommitListener({ e->
modified.addAll(e.modifiedInstances)
})
product.tags.add(tag)
line.quantity = 20
context.commit()
then:
modified.contains(product)
modified.contains(tag)
def line1 = context.find(OrderLine, line.id)
line1.quantity == 20
line1.product == product2
}
private <T> T createDetached(Class<T> entityClass) {
@ -645,6 +647,12 @@ class DataContextTest extends Specification {
entityStates.makeDetached(entity)
}
private void makeDetached(Entity... entities) {
for (Entity entity : entities) {
entityStates.makeDetached(entity)
}
}
private static <T> T makeSaved(T entity) {
TestServiceProxy.getDefault(DataService).commit(new CommitContext().addInstanceToCommit(entity))[0] as T
}