I have 3 domains :
- EligibilityInclusion
- EligibilityExclusion
- EligibilitySummary
I build also eligibility.gsp (mix use of 3 templates : _inclusion, _exclusion, _summary ; and I'm also using JQueryUI tab to render each domain in one tab).
Everything fine for viewing, but now I would like to use only one controller to create, edit, list and show.
How can I handle 3 domains via only one controller?
(for example, I would like to use EligibilityController to handle my 3 domains)
What is the best usage:
- binding multiple objets?
- use command objects?
Unfortunately command objects don't help with the input model for a view, they are specifically designed to aide the output model for the binding and validation of request parameters. However you can roll your own View Model based on a command object if you are prepared to delve into some meta programming to to achieve the data binding for the creation of the view model.
Here's a basic approach. The following code constructs the Command Object which you can then pass as the model to the view in the controller:
class ItemCommand {
// attribute declarations ...
public void bindData(def domainInstance){
domainInstance.properties.keySet().each { prop ->
if(prop == "class"){
// not needed
} else if(prop == "metaClass") {
// not needed
} else if(this.properties.containsKey(prop)){
this."${prop}" = domainInstance."${prop}"
}
}
}
This will allow you to bind the data from different domain objects by calling bindData for each of the domain objects.
This is the essence of the solution I use. You will need to store the ids of the different domain objects (and the version attribute) as hidden fields if you intend to do updates to the domain objects.
You can't just submit multiple objects, if some of them have same field names, right?
I'd try to join the 3 objects into single Command with 3 fields, like: inclusionInstance1, inclusingInstance2, summaryInstance1, and name fields in gsp-s respectively, like name='command.inclusionInstance1.name'. Assigning command.properties = params should work when submitting the form.
Related
I need the extjs 4 behaviour for new model objects to have a null id rather than a "Model-1" generated value in extjs 5+
Therefore I have created a id generator
Ext.define('Example.data.identifier.Custom', {
extend : 'Ext.data.identifier.Generator',
alias : 'data.identifier.custom',
generate : function() {
return null;
}
});
But now my grid grouping features behave strange. I think it has to do with the id assigned to the additional grouping row to be also null.
Is there a way around?
The ExtJS 4 behaviour of null ids does not work in ExtJS 5+ - you need to have ids, especially for objects in stores.
So accept the fact that all of your Model instances will need to have id values. You can determine if they were created locally or via retrieving from the backend using the phantom property. You can use a custom proxy to strip the id property when saving if you need to. You can use different endpoints with the AJAX proxy, or different HTTP Request Methods with the REST Proxy, so your backend can know if you are making a new record or updating an existing one.
But you can't get away with having duplicate IDs, including null values. Accept that, and move on.
I'm developing my first Spring 3 webapp. In Spring 2, we used to have formBackingObject load data from the database, then let Spring binding update some of the fields, and then onSubmit would persist those changes.
In Spring 3 it seems I have two options:
Let the user edit 100% of the persistent object. This would mean that the object's ID would need to be a hidden field
Create a form object which holds the editable data, and then map that onto the persistent object on submit
The first option is not truly an option, we cannot let the user edit all fields, and we'd rather not present data in hidden fields where anyone capable of pressing F12 can alter the values.
The second option seems like a decent design approach. However, it appears that it requires to essentially clone every editable, persistent class.
#Entity
public class Company {
private String uuid; // not editable!
.. 30 other properties, 2 are not editable
}
public class CompanyForm {
.. 28 of above properties
}
and then some mapping mechanism with lots of
public void map(CompanyForm cf, Company c) {
cf.setName(c.getName());
.. 27 other set(get())
}
I'm praying this is not the "as designed" approach of Spring's MVC binding. However, all tutorial I've found so far are terribly trivial and implement option 1 from above. Does anyone have some suggestions for implementing option 2?
Thanks, Simon
DataBinder API
Note that there are potential security implications in failing to set an array of allowed fields. In the case of HTTP form POST data for example, malicious clients can attempt to subvert an application by supplying values for fields or properties that do not exist on the form. In some cases this could lead to illegal data being set on command objects or their nested objects. For this reason, it is highly recommended to specify the allowedFields property on the DataBinder.
You can use it together with option 1
A pragmatic way would be to just ignore the non editable fields on the update statement.
I have actually circumvented this in the past by using a #ModelAttribute annotation and detecting the PK on the request, if you do it this way Spring will use the object that is returned from #ModelAttribute and automatically copy the submitted object to it.
It's kind of a hack and not obvious to someone who comes in to maintain the code though.
I currently get an item in a collection for a user like so:
me.user = Backbone.Collection.Users.collection().get(id);
This returns the default set of attribute required in the app. On the user profile page, I want to show additional attributes that aren't necessary anywhere else.
How can I get an item in a collection (which queries the server) with additional attributes that I can specify?
Thanks
So to go along with the comment, what I think you want is to produce extra models instead of creating two user models, one with redundant + extra data.
One way you could do this is to give a relationship between different models.
Say a user model consists of simply a name and email. That's fine and dandy but you also want to render a user profile on the page (or whatever 'extras' you intend.) This seems like a good opportunity to create a separate model representing the extra data you desire.
You can do it a few ways. For example, if every user has a profile you could bake it into your user model. Something like when you create a user model:
user.profile = new Profile(); // model
I've seen some people put other models inside a model's attributes user.set('profile', new Profile()) but I'm not sure if this is a great idea. I like to keep my model attributes isolated to just that model.
Each profile model would have a url that corresponds to the model.id.
So then you could just user.profile.fetch() and use that profile attributes to populate the data in your view. Maybe it does something like /user/1/profile
Another aspect about your question that I think you might be alluding to is sending data from the server in one go when you fetch the collection. Maybe your server replies with data like this:
[{"name":"Jake", "email":"j#stack.com", "profile":"{"aboutme":"Some story"}"}, ... ]
and the profile data is only available for those who have it etc. In this case, you can then use the parse() function to pull out that extra data out and doing something before sending the name and email attributes through to the model set method.
Although, recently I think I read that using the parse to do stuff with the extra data is bad form. Override set So instead of parsing, you might just want to save that for namespacing and then in your overridden set method do something like:
set: function(attributes, options) {
if (!_.isUndefined(this.profile) && attributes.profile) {
this.profile = new Profile();
this.profile.set(attributes.profile);
} else if (attributes.profile) {
this.profile.set(attributes.profile);
}
delete attributes.profile;
return Backbone.Model.prototype.set.call(this, attributes, options);
}
You can do something similar for really unique users such as the main user using your app. When I instantiate a user model for my app (the one representing user him/herself) I also initialize a few other special models only that user would have (like an auth model for fetching authentication data etc.)
I'm not sure if I hit what you were asking but I hope I hit something.
Is collection an instance already, and I assume so? If so, you should only do:
me.user = Backbone.Collection.Users.collection.get(id);
I.e. removing the brackets () after collection.
When you create a Data Extender for a CME list – for instance to add a column for the Schema as in this example – it all works fine and dandy whenever you do actions that force a List reload.
However, some actions don’t force a list reload (like editing a component in a folder, then saving & closing) and it looks like Anguilla is loading the data for the item that changed using a different mechanism that loads only the data for the item in question (which makes sense).
If I would want my extended list view to behave properly and also load my additional attributes whenever a given item changes (instead of only when the list view is reloaded) what else do I need to do?
I found how Anguilla takes care of this. When you implement a Data Extender, you are extending the information regarding the items displayed in the list, which basically means that you are extending the Data (Model) behind the item in question.
Each Item in Tridion has its own class in the Anguilla Framework, for example a Component has its own Tridion.ContentManager.Component javascript "class".
Having said this, and going back to the example that shows the schema name of the component, we are not actually extending the model, since that information is already available in the component. However, we need to overwrite the methods exposed on each used for displaying information in the lists the item is in, in this case a Component.
So, when we deal with a Data Extender, if we want a full implementation of this functionality, we not only need to define the data extender:
<ext:dataextender
name="IntelligentDataExtender"
type="Com.Tridion.PS.Extensions.IntelligentDataExtender,PS.GUI.Extensions">
<ext:description>Shows extra info</ext:description>
</ext:dataextender>
But also we need to define what's the column we are adding:
<ext:lists>
<ext:add>
<ext:extension name="IntelligentColumnExtender"
assignid="IntelligentDataColumnExtender">
<ext:listDefinition>
<ext:selectornamespaces/>
<ext:columns>
<column
xmlns="http://www.sdltridion.com/2009/GUI/extensions/List"
id="IntelligentData"
type="data"
title="Additional Info"
selector="#ExtendedInfo"
translate="String"/>
</ext:columns>
</ext:listDefinition>
<ext:apply>
<ext:view name="DashboardView" />
</ext:apply>
</ext:extension>
</ext:add>
</ext:lists>
Once we have this, the GUI will display the column we just added: "Additional Info"
Well, now we need to achieve the list refreshing when the item is edited/checked-out and in, etc...
For that, we need to extend the model and implement a few methods in the Object we are extending. In this example I am extending the Page object, so whenever a page is edited, the row in the list we want to update gets refreshed, together with the rest of the cells in the table.
To extend the model we need to define what types are we extending, in this example I am going to use the "Page" class as an example. First of all you need to define the model extension in the config file of your Editor:
<cfg:group name="Com.Tridion.PS.Extensions.UI.Model"
merger="Tridion.Web.UI.Core.Configuration.Resources.DomainModelProcessor"
merge="always">
<cfg:domainmodel name="Com.Tridion.PS.Extensions.UI.Model">
<cfg:fileset>
<cfg:file type="script">/Scripts/PSPage.js</cfg:file>
</cfg:fileset>
<cfg:services />
</cfg:domainmodel>
</cfg:group>
and
<ext:modelextensions>
<cfg:itemtypes>
<cfg:itemtype id="tcm:64" implementation="Com.Tridion.PS.Extensions.UI.PSPage" />
</cfg:itemtypes>
</ext:modelextensions>
As you can see I am extending the Page by using the "Com.Tridion.PS.Extensions.UI.PSPage" class that is defined in the Javascript file "/Scripts/PSPage.js".
The only method that handles the row refreshing is the following:
Com.Tridion.PS.Extensions.UI.PSPage.prototype.getListItemXmlAttributes
= function PSPage$getListItemXmlAttributes(customAttributes) {
var attribs = {};
var p = this.properties;
if (customAttributes) {
for (var attr in customAttributes) {
attribs[attr] = customAttributes[attr];
}
}
//This adds my custom column back when the item is updated
attribs["ExtendedInfo"] = p.extendedInfo;
return this.callBase(
"Tridion.ContentManager.Page",
"getListItemXmlAttributes",
[attribs])
};
As you can see I am implementing the "ExtendedInfo" attribute which is the one displayed in my additional column.
There's more than just adding a Data Extender when dealing with adding a column to our lists. I will write a post in my blog here to provide with a fully working example.
I hope it makes sense.
Well, Jaime correctly described how CME updates changed items in Lists. But I want to add some additional information on how List controls, domain model List and Items are interact with each other. This might help you building your own extension with similar functionality.
Most of the domain model List items inherit from Tridion.ContentManager.ListTcmItems class. On the moment when any List item, based on mentioned class, is loaded it will be registered in Lists Registry (and un-registered when the List is unloaded). This will allow Model to use registered Lists as source of static data for Items and to update changed Items data in these Lists.
Update Item static data
For example, we have loaded ListCategories and there is only one Category in the List:
var pub = $models.getItem("tcm:0-1-1");
var list = pub.getListCategories();
list.load();
// After list is loaded
list.getXml();
That getXml() returns an XML like (simplified):
<tcm:ListCategories>
<tcm:Item ID="tcm:1-4-512" Type="512" Title="Keys" />
</tcm:ListCategories>
After that, if you try to get some static data for Category "Keys" it will be already set:
var category = $models.getItem("tcm:1-4-512");
category.isLoaded(); // return false
category.isStaticLoaded(); // return false
category.getTitle(); // return undefined
category.getStaticTitle(); // return "Keys"!
That is possible because $models.getItem call will do two things: it will return an existing (or create a new) domain model object and call $models.updateItemData method with it. This method call will go through all registered Lists in the Lists Registry and for all Lists whose TimeStamp bigger than Item's Last Update TimeStamp will call list.updateItemData with the model object.
The updateItemData method will check if the passed Item is in the list and if it is, then the Item will be updated with the static data that is available from the List.
Updating data of changed Items in the List
When a domain model Item is modified (updated, removed, created new) one of these methods is called:
$models.itemUpdated
$models.itemRemoved
These methods will go through the Lists in Lists Registry and call list.itemUpdated (or list.itemRemoved). These methods will check is passed Item is contained in their List and if so they will update the List xml from the Item data.
For that purpose there is a getListItemXmlNode method in the Tridion.ContentManager.Item class. This method will build List xml node based on the array of attributes, provided by getListItemXmlAttributes method on the Item. That's what Jaime mentioned in his answer.
If the List xml was updated, one of these events will be fired on List object:
itemadd
itemupdate
itemremove
Listening to these events on a List object in your view will allow you to timely update your List Control.
So if you want this mechanism to work with your extension, stick to these rules:
If you are creating new domain model List object - it should inherit Tridion.ContentManager.ListTcmItems class or it should implement the getId(), itemUpdated(item), itemsUpdated(item), itemRemoved(item) and updateItemData(item) methods
If you want to see changes in List control - attach handlers to corresponding events on the domain model List object and update your List control
If you are creating new domain model Item - it should inherit Tridion.ContentManager.Item class and you should improve getListItemXmlAttributes method to return correct array of attributes for the List
The CME will indeed update the items in the list dynamically after the save occurs, without going to the server.
To do so, it calls a method named "getListItemXml" which returns the update XML element for the list. It will then update or add this element, which will update or add the item in the list view.
getListItemXml is a method of the different Model objects.
So how do you take advantage of this? I'm not sure.
Perhaps you could overwrite the method (or maybe getListItemXmlAttributes is best) with your own to add the additional data?
There is also an "itemupdate" event fired whenever an item is updated in the list.
You can hook into that by doing something like this:
var myEventHandler = function(event)
{
$log.message("Item updated. TridionEvent object passed: " + event);
}
var view = $display.getView();
var list = view.getListObject("uri-of-Folder");
list.addEventListener("itemupdate", myEventHandler);
I suppose you could use that to update the list entry for the item after the fact.
Be sure to call removeEventHandler at some point too.
None of this is optimal, obviously.
But I don't know of any extension point that would solve this particular problem.
I think I would (attempt to) implement this by monitoring the items in a folder periodically and updating that list after this polling mechanism had detected a change in that folder.
For example, I would write some javascript timeout or interval that runs in the background and checks the items in the current folder. If it detects a change, it triggers the update of the list.
Alternatively, you could also try to intercept the action that changed your list (e.g. the creation of a new item), maybe by means of an event system, and as such update your list. I don't think this is much different than the first approach, as I think it still implies some level of polling from the GUI side.
Grails offers the ability to automatically create and bind domain objects to a hasMany List, as described in the grails user guide.
So, for example, if my domain object "Author" has a List of many "Book" objects, I could create and bind these using the following markup (from the user guide):
<g:textField name="books[0].title" value="the Stand" />
<g:textField name="books[1].title" value="the Shining" />
<g:textField name="books[2].title" value="Red Madder" />
In this case, if any of the books specified don't already exist, Grails will create them and set their titles appropriately. If there are already books in the specified indices, their titles will be updated and they will be saved. My question is: is there some easy way to tell Grails to remove one of those books from the 'books' association on data bind?
The most obvious way to do this would be to omit the form element that corresponds to the domain instance you want to delete; unfortunately, this does not work, as per the user guide:
Then Grails will automatically create
a new instance for you at the defined
position. If you "skipped" a few
elements in the middle ... Then Grails
will automatically create instances in
between.
I realize that a specific solution could be engineered as part of a command object, or as part of a particular controller- however, the need for this functionality appears repeatedly throughout my application, across multiple domain objects and for associations of many different types of objects. A general solution, therefore, would be ideal. Does anyone know if there is something like this included in Grails?
removeFrom*
Opposite of the addTo method in that it removes instances from an association.
Examples
def author = Author.findByName("Stephen King")
def book = author.books.find { it.title = 'The Stand' }
author.removeFromBooks(book)
Just ran into this issue myself. It's easy to solve. Grails uses java.util.Set to represent lists. You can just use the clear() method to wipe the data, and then add in the ones you want.
//clear all documents
bidRequest.documents.clear()
//add the selected ones back
params.documentId.each() {
def Document document = Document.get(it)
bidRequest.documents.add(document)
log.debug("in associateDocuments: added " + document)
};
//try to save the changes
if (!bidRequest.save(flush: true)) {
return error()
} else {
flash.message = "Successfully associated documents"
}
I bet you can do the same thing by using the "remove()" method in the case that you don't want to "clear()" all the data.
For a good explanation of deleting a collection of child objects with GORM have a look at the Deleting Children section of this blog post - GORM gotchas part 2
It's recommended reading, as are parts 1 and 3 of the series.
I am just starting to learn Grails myself and saw your question as an interesting research exercise for me. I do not think you can use the conventional data binding mechanism - as it fills in the blanks using some kind of Lazy map behind the scenes. So for you to achieve your goal your "save" method (or is it a function?) is unlikely to contain anything like:
def Book = new Book(params)
You need a mechanism to modify your controller's "save" method.
After some research, I understand you can modify your scaffolding template which is responsible for generating your controller code or runtime methods. You can get a copy of all the templates used by Grails by running "grails install-templates" and the template file you would need to modify is called "Controller.groovy".
So in theory, you could modify the "save" method for your whole application this way.
Great! You would think that all you need to do now is modify your save method in the template so that it iterates through the object entries (e.g. books) in the params map, saving and deleting as you go.
However, I think your required solution could still be quite problematic to achieve. My instinct tells me that there are many reasons why the mechanism you suggest is a bad idea.
For one reason, off the top of my head, imagine you had a paginated list of books. Could that mean your "save" could delete the entire database table except the currently visible page? Okay, let us say you manage to work out how many items are displayed on each page, what if the list was sorted so it was no longer in numerical order - what do you delete now?
Maybe multiple submit buttons in your form would be a better approach (e.g. save changes, add, delete). I have not tried this kind of thing in Grails but understand actionSubmit should help you achieve multiple submit buttons. I certainly used to do this kind of thing in Struts!
HTH
I'm just running into this same issue.
My application's domain is quite simple: it has Stub objects which have a hasMany relationship with Header objects. Since the Header objects have no life of their own, they're entirely managed by the Stub controller and views.
The domain class definitions:
class Stub {
List headers = new ArrayList();
static hasMany = [headers:Header]
static mapping = {headers lazy: false}
}
class Header {
String value
static belongsTo = Stub
}
I've tried the "clear and bind" method but the end result is that the "cleared" objects are left over in the database and grails will just create new instances for the ones that were not removed from the relationship. It does seem to work from an user's perspective, but it will leave lots of garbage objects in the database.
The code in the controller's update() method is:
stubInstance.headers.clear()
stubInstance.properties = params
An example: while editing the -many side of this relationship I have (for a given Stub with id=1):
<g:textField name="headers[0].value" value="zero" id=1 />
<g:textField name="headers[1].value" value="one" id=2 />
<g:textField name="headers[2].value" value="two" id=3 />
in the database there are 3 Header instances:
id=1;value="zero"
id=2;value="one"
id=3;value"two"
after removing header "one" and saving the Stub object the database will have headers:
id=1;value="zero"
id=2;value="one"
id=3;value"two"
id=4;value="zero"
id=5;value="two"
and the Stub object will now have an association with Headers with id=4 and id=5...
Furthermore, without the clearing of the list, if an index is not present in the submitted request.headers list, on data binding grails will keep the existing object at that location unchanged.
The solution that occurs to me is to bind the data, then check the Stub's headers for elements that are not present in the submitted list and remove them.
This looks like a pretty simple scenario, isn't there any built-in functionality to address it?
It's a bit overkill to have to write your own synchronization logic for maintaining relationships, especially when the quirks that make it non-trivial are caused by grails itself.
What about deletion, shouldn't the clear()'ed elements be gone from the database? Am I missing something in the relationship or domain object definitions?
class Stub {
List headers = new ArrayList();
static hasMany = [headers:Header]
static mapping = {
headers lazy: false
**headers cascade: "all-delete-orphan"**
}
}
class Header {
String value
static belongsTo = Stub
}
I have added the cascade property on the owning side of relationship and Now if you try to save the stub, it will take care of removing deleted items from the collection and delete them from the DataBase.