SAPui5 create entry with child entries - sap-fiori

I want to create an entry and its child from the from ui5 so I used the approach of create entry the problem is I want the user to be able to update values of the child nodes so I did the following
var oParentContext = this._oODataModel.createEntry("/Parent",
{ changeId: "edit", properties: {object}, success: this._fnEntityCreated.bind(this), error: this._fnEntityCreationFailed.bind(this) });
for (var i = 0; i < childArray.length; i++) {
var child = childArray[i];
aChildCtx = this._oODataModel.createEntry("/child", {
changeId: "edit",
properties: child,
context: oParentContext
});
aChildEntries.push(aChildCtx.getPath().substring(1));
}
this.getView().setBindingContext(oParentContext);
// I attached also the relation to the front end
this.getView().getModel().setProperty("ToChild", aChildEntries, oParentContext);
In the view I did the binding to the relation ToChild to the table for the user to enter his values.
The display of the parent and child works however I am facing a problem that the view is issuing a get request to Odata with the temporary ID/ToChild. I couldn't find any solution for this. How should we do a deep insert in standard?
P.S. I don't want to use deep_create
Thanks
Best Regards

The create requests are currently being created asynchronously. The definitive parent ID will only be known in the frontend once the parent create request returns, but the requests to create the children are generated before this.
I can see two ways you could make sure the children will be created with the definitive parent id:
Send the parent and child create requests synchronously. Wait until the parent create request has returned succesfully before creating the children.
Send both as a change set. Let the implementation of the oData-service create the parent first and then the children with the now-created parent ID. (You are now setting changeId, the API specifies changeSetId)

Related

How to prevent OData service call on model change

I have a sap.m.Table whose "items" are bound to oData v2 model. I need to delete item on click on delete icon. Here is what I do:
On click of delete icon, I get all the rows in the model, delete the one in question and set the property of model again.
However since the model is changed, it triggers a backend round trip and brings the latest data and table shows the original rows again.
I tried setting binding mode to OneTime but that does not work. Also tried setting RefreshAfterChange to false but even then service was called again.
Here is my code -
Controller
onInit: function() {
var oModel = new sap.ui.model.odata.v2.ODataModel("url", {
json: true,
useBatch : false,
refreshAfterChange: false,
defaultBindingMode: "OneTime"
});
this.getView.().setModel(oModel, "model1");
},
onDeleteIconPress : function(oEvent) {
// get the selected row
// get all the rows in oOriginalRows
// loop over oOriginalRows and delete the selected row from it
// set the model to reformed oOriginalRows
this.getView().getModel("omodel1").setProperty("/", oOriginalRows);
// Till this point every thing looks fine. I can see changes in the model
// refresh is called automatically and data service triggers backend call
// This fetches original data again and table shows all data again
}
How can I not trigger the round trip again? I need to update the locally
Your approach won't work with a ODataModel as it is strictly server side. Please use the corresponding remove method to delete an entity from the server.
Since Odata is server side model, it always triggered a round trip. So I did not bind my sap.m.Table to Data model. Instead I triggered a read manually. On success I copied the data received to local JSON model. I bound my table items to this JSON model. Now the delete button works just fine.
// Define a JSON Model
oJsonModel = new sap.ui.model.json.JSONModel();
//oModel is Odata model defined in manifest file
oModel.read("/entity1", {
success: function(oData, oResponse){
oJsonModel.setProperty("/entity1", oData.results);
// bind oJsonModel to table here
}
}

Proper Handling of hasMany/belongsTo in Ember Data with Rails Persistence

I have an Ember.js app (1.0.0-rc.3) using Ember Data (Rev 12) to persist to a Rails backend. On the client side, I have two models that have a one-to-many relationship:
App.Child = DS.Model.extend(
name: DS.attr('string')
parent: DS.belongsTo('App.Parent')
App.Parent = DS.Model.extend(
name: DS.attr('string')
children: DS.hasMany('App.Child')
)
I have configured my store to expect JSON from Rails that embeds relationships rather than side-loading them:
App.Store = DS.Store.extend(
revision: 12
adapter: DS.RESTAdapter.extend(serializer: DS.RESTSerializer.extend(init: ->
#_super()
#map "App.Child",
parents:
embedded: "always"
))
)
And then I have a form for creating a new App.Child record. In the childNewController for the new App.Child form view, I create a new App.Child and a new App.Parent as follows:
#transaction = #get('store').transaction()
#set('content', #transaction.createRecord(App.Child, {}))
#set('content.parent', #transaction.createRecord(App.Parent, {}))
I have textfields bound to the various attributes of both child and parent. When I click the save button on the form, an action in the childNewController commits the changes with #transaction.commit().
THE PROBLEM
When the transaction is committed, Rails receives two JSON POST requests—one to the ParentController with JSON that looks like:
{"parent"=>{"name"=>"William"}}
...and a second POST to the ChildController with JSON that looks like:
{"child"=>{"name"=>"Henry", "parent"=>{"name"=>"William"}}}
Now the Rails ChildController and ParentController are both trying to save the new parent.
Ideally, the request to the ParentController would go away, since I don't want it, and it lacks sufficient information to establish a relationship with the child. I would simply ignore the request to ParentController, but Ember Data is expecting a response from the server.
Another option would be to let ParentController save the new parent, and then ChildController could look up the newly-saved parent record and create the linkage to the new child record. The problem is that the sequence of the two requests is unpredictable.
Because of this sequencing issue, I tried putting Parent.find_or_create_by_name(parent_name) in both Rails controllers, on the theory that if one controller has already created the new parent, the other would only execute a lookup of that new parent record. However, this resulted in duplicate parent records!
I'm really at a loss to understand why Ember Data insists on pushing duplicate JSON representations of the parent to the backend. I'm sure it's a simple configuration error on my part.
Update
I found an acceptable workaround for now by chaining the commits:
newParent = #transaction.createRecord(App.Parent, {name: parentName})
currentChild = #get('content') # we assigned a new App.Child to the childNewController in the new child route's controller setup
currentStore = #get('store')
newParent.one('didCreate', this, ->
now = new Date()
transaction = currentStore.transaction()
transaction.add currentChild
transaction.commit()
transaction = null
)
newParent.get('transaction').commit()
#transaction = null
Is there a better way to do this?

Is there an event in Breezejs that is fired when an entity is added or deleted?

I have looked around for an answer to this, but I have come up dry so far. What I would like to do is have an event handler in a specific view model that listens to Breeze for entities being added or deleted so the view model can take appropriate action on the array it is managing. Does such an event exist?
I have a Jobs view model for my Jobs view that contains, among other properties, a ko.observableArray of Job entities, and a NewJob view model for my NewJob view. Both view models share the same data service. I would like to simply use the DataService from the NewJob view model to insert a new Job entity in to Breeze, and then have the Jobs view model simply subscribed to an event so it would know to add the new Job into it's Jobs array.
TIA
The Breeze EntityManager has an entityChanged event that may be used like so:
var em = new EntityManager( {serviceName: "api/NorthwindIBModel" });
em.entityChanged.subscribe(function(changeArgs) {
// This code will be executed any time any entity within the entityManager is added,
// modified, deleted or detached for any reason.
var action = changeArgs.entityAction;
var entity = changeArgs.entity;
// .. do something to this entity when it is changed.
});

EF: Adding new item in a collection and notifying all other collections that new item is added

Lest say that we have several pages that retrieve items from the same EntitySet. If I add a new entity on one page, I need to add it to both EntitySet collection and myCollection:
Context.EntitySet.Add(item);
myCollection.Add(item);
What is the best way to notify other pages that new item is added (or deleted)? Editing an entity is no problem, since all pages get change notification without any problem.
Instead of binding to different instances of IEnumerable<T> myCollection, the recommended approach is to bind directly to Context.EntitySet<T>. EntitySet<T> implements INotifyCollectionChanged and INotifyPropertyChanged interfaces. When you bind to the same instance of EntitySet<T>, each page may be notified of changes by responding to the EntitySet<T>.CollectionChanged event. For example:
// Page 1
_myCollection = Context.EntitySet<MyItem>();
_myCollection.CollectionChanged += MyItemsChanged;
...
// Page 2
_myCollection = Context.EntitySet<MyItem>();
_myCollection.CollectionChanged += MyItemsChanged;
When any page adds or removes from the collection, all pages are notified.
In regards to your comment, IEnumerable<T> does not implement the INotifyCollectionChanged interface and does not publish any change notifications. The best results come from using the EntitySet<T> directly.

Apostrophe CMS: Engine Creation

I'm attempting to create a Product Engine for Apostrophe. I'm having trouble extending the Page Settings form, at the moment I want to add a simple textarea to add a synopsis to the page - eventually I want to add Product settings but I need to get the basics working first.
I've created a form and a settings partial, it's displaying fine and saving the data (with the help of a little hack - might not be correct). The trouble I'm having is when you edit a page the data is not being pulled back in to the form. To be honest I probably doing something fundamentally wrong but I lack experience in Symfony.
My table schema
ccProduct:
tableName: cc_product
actAs:
Timestampable: ~
columns:
page_id:
type: integer
notnull: true
synopsis:
type: text
relations:
Page:
class: aPage
local: page_id
foreign: id
type: one
onDelete: CASCADE
My form ccProductEngineForm.class.php
class ccProductEngineForm extends ccProductForm
{
public function __construct($object = null, $options = array(), $CSRFSecret = null)
{
// when editing the page the values are not show, this is an attempt to get it to work - it still doesn't :(
$page_id = sfContext::getInstance()->getRequest()->getParameter('id');
sfContext::getInstance()->getRequest()->setParameter('page_id', $page_id);
$ccProduct = Doctrine::getTable('ccProduct')->findOneByPageId($page_id);
if ($ccProduct) {
sfContext::getInstance()->getRequest()->setParameter('id', $ccProduct->getId());
}
// aPageForm object is passed in
parent::__construct(null, $options, $CSRFSecret); // construct normally
//$this->mergeForm(new aPageForm($object, $options, $CSRFSecret)); // merge the aPageForm - Nope, ignore it!?
}
public function setup() {
parent::setup();
$this->useFields(array('synopsis'));
$this->widgetSchema->setNameFormat('enginesettings[%s]');
$this->widgetSchema->setFormFormatterName('aPageSettings');
}
protected function doSave($con = null)
{
// page_id is missing! possible bug? BaseaActions.class.php ~ 520
$this->values['page_id'] = sfContext::getInstance()->getRequest()->getParameter('enginesettings[pageid]');
parent::doSave($con);
}
}
Thanks in advance for any help
EDIT:
Thanks for your answer Tom, I'll try to add a little more detail.
I was aware that a page object is passed into the Engine, but I wasn't exactly sure what do to with it - see my confused line of code:
//$this->mergeForm(new aPageForm($object, $options, $CSRFSecret)); // merge the aPageForm - Nope, ignore it!?
To clarify my 'product' is a page that uses the ccProduct engine. I now want to add extra information to that page. Does that make sense? In your words..
Are you trying to actually create a unique product that has its sole "home" on a product engine page? That's what subclassing ccProductForm would do
Yes :)
EDIT 2:
Following Tom's first suggestion (Apostrophe CMS: Engine Creation) I was able to extend the aPage table with my extra fields and the Engine is now saving these.
However, the standard aPageTable::getPagesInfo function isn't returning the fields I saved. I assume I'll have to select these separately?
EDIT 3:
aPageTable::retrieveBySlug() will do the job :)
REVISITED
I decided to revisit this and try Tom's second approach..
The other approach (if for whatever reason you don't want extra columns in aPage) is to keep your ccProduct table and fetch the relevant one
I managed to get this working, my ccProductEngine form constructor now looks like this..
class ccProductEngineForm extends ccProductForm
{
public function __construct($aPage = null, $options = array(), $CSRFSecret = null)
{
$page_id = $aPage->getId();
if ($page_id) {
$product = Doctrine_Core::getTable('ccProduct')->findOneByPage_id($page_id);
if ($product) {
$ccProduct = $product;
} else {
$ccProduct = new ccProduct();
}
}
parent::__construct($ccProduct, $options, $CSRFSecret);
}
I hope this helps someone :)
The main thing to remember is that your engine settings form receives a page object as the first parameter to the constructor, and you need to associate whatever your data is with that page object. Usually the engine settings form is a subclass of aPageForm, but it does not have to be. All that is required is that you associate your product object(s) with the page object in some way. Depending on your goals you probably want a refClass that creates a one-to-many relationship between product engine pages and products, and a form for manipulating those relationships.
From your code it is difficult for me to guess what you really want to do. Are you trying to actually create a unique product that has its sole "home" on a product engine page? That's what subclassing ccProductForm would do. Or do you just want to select an existing product from the product table and associate it with each engine page? Or do you want to select one or more products and associate them with the engine page?
Stuffing things into the request object is definitely not the way to go (:
Please clarify and I can help you further.
Tom Boutell, Apostrophe senior developer
There are two approaches you could follow here.
One is to just extend the schema of aPage in your project level config/doctrine/schema.yml file:
aPage:
columns:
synopsis:
type: text
Now every aPage object has a synopsis column, which will be null by default, and your engine settings form can just manipulate that one column. Your engine form subclasses aPageForm. You don't need a constructor at all (the default one will suit you), and your configure() method is just:
$this->useFields(array('synopsis'));
Boom, you have a textarea for the synopsis that appears when the page type is set to this engine. You don't need a ccProduct table at all.
The other approach (if for whatever reason you don't want extra columns in aPage) is to keep your ccProduct table and fetch the relevant one. Your engine form class then does not subclass aPageForm, and your constructor has to use the page passed to it to fetch the related ccProduct object (using a Doctrine relation) or create a new one if there is none yet. This is not difficult, but so far it looks like you can keep it even simpler by just adding a column to aPage.

Resources