I've been looking on forums for 2 days now and can't find a good answer so I'll just post it.
I appear to be having a problem posting JSON back to the controller to save. The JSON should map to model view but it keeps getting default(constructor)values rather then the values from the POST.
We have a series of JS widgets that contain a data field with json in them. We do all our data manipulation in these widget objects on the client side. When a user wants to save we grab the data we need from the widgets involved and we put it into another JSON object that matches a ViewModel and POST that back to the server.
For example:
$("#Save").click(function () {
if (itemDetails.preparedForSubmit() && itemConnections.preparedForSubmit()) {
itemComposite.data.Details = itemDetails.data;
itemComposite.data.Connections= itemConnections.data;
$.post(MYURL, itemComposite.data);
} else {
alert("failed to save");
}
});
The preparedForSubmit() method simple does stuff like any validation checks or last minute formatting you might need to do client side.
The itemDetails widgets data matches a ViewModel.
The itemConnections widgets data matches a collection of ViewModels.
The Controller looks like this:
[HttpPost]
virtual public JsonResult SaveItemDetailsComposite(ItemComposite inItemData)
{
if (ModelState.IsValid)
{
try
{
_Mapper.Save(itemComposite.Details , itemComposite.Connections);
return Json(true);
}
catch (Exception ex)
{
_log.Error("Exception " + ex.InnerException.Message);
throw;
}
}
return Json(SiteMasterUtilities.CreateValidationErrorResponse(ModelState));
}
The ItemComposite Class is a simple View Model that contains a single itemDetails object and a collection of itemConnections. When it returns data to here it is just getting the default data as if it got a new ItemComposite rather than converting the POST data.
in Firebug I see the data is posted. Although it looks weird not automatically formatted in firebug.
Are you saying that itemComposite.data is formatted as a JSON object? If so, I'm pretty sure you're going to have to de-serialize it before you can cast it to your object. Something like:
ItemComposite ic = jsSerializer.Deserialize<ItemComposite>(this.HttpContext.Request.Params[0]);
You may want to look into a framework like JSON.NET to ensure that your data is being serialized properly when it gets supplied to your Action.
JSON.NET seems like it's one of the main stream frameworks: http://json.codeplex.com/releases/view/43775
Hope this helps.
Cory
You could also use the JSON Serializer in WCF: http://msdn.microsoft.com/en-us/library/system.runtime.serialization.json.datacontractjsonserializer.aspx
SO wouldn't let me put both links in one answer, sorry for the split answer.
Thanks everyone. I think I have solved my problem and I'm pretty sure that I had four issues. For the most part I followed thatSteveguys's suggestion and read more on this article: http://haacked.com/archive/2010/04/15/sending-json-to-an-asp-net-mvc-action-method-argument.aspx
Using jQuery's post() method and specifying json as the type didn't seem to actually send it as json. By using the ajax() method and specifying json it sent it as json.
The JSON.serialize() method was also need to cleanly send over the json.
Also my ViewModel design was a big problem. We are using the MS code analytic build junk and it didn't want me having a setter for my collections in the ViewModel. So me being from a java/hibernate world, thought it didn't need them to bind and it would just come in as a serialized object magically. Once I just suppressed the error and reset up my setters. I am getting the collections now in my controller.
I believe using the MVC2 Future's Value Providers are doing something but it still doesn't convert json dates robustly, So I am still investigating the best way to do that.
I hope my issues help out others.
UPDATE: using this method to update collections of data appears to be super slow. A collection with 200 entries in it and 8 fields per entry takes 3 minutes to get to the controller. Just 1 or 2 entries take very little time. The only thing I know of that is happening between here is data binding to the model view. I don't know if MVC2 provides a easy way to send this much data and bind it.
Related
I set the view.setModel(model), get the model for the view, and request for model.read("/entitySet('10000')").
The model is then filled up with /entitySet('10000')/properties.
But it is difficult to assign them to view fields, as now in the view, <Text text="{property}"> doesn't work. It has to be <Text text="{/entitySet('10000')/property}">.
On the other hand, if I set view's context binding to "/entitySet('10000')", then <Text text="{property}"> would start working.
Which one is the preferred method? When to use .read?
I almost never use .read if I want to use the results from an OData call directly in a binding context. The only time I use .read is if I want to manipulate the results before doing anything with them.
Look at this example from the sdk for instance: https://ui5.sap.com/#/entity/sap.ui.table.Table/sample/sap.ui.table.sample.OData
Syntax on this kind of binding is similar to read but with a few differences in events, and a few different types of methods depending on what you want to bind. Binding to a view for instance uses bindElement:
this.getView().bindElement("/entitySet('1000')");
After this, fields on that particular entity can be accessed as <Text text="{property}" />.
Here's an example from one of my current apps with events and some other call parameters:
this.getView().bindElement({
path: `/Orders('${currentOrderNumber}')`,
parameters: {
expand: 'Texts'
},
events: {
dataRequested: _ => this.getView().setBusy(true),
dataReceived: data => {
if (!this.getView().getBindingContext()) {
// navigate to `Not Found` view
}
},
change: _ => this.getView().setBusy(false)
}
});
For a table, it's slightly different, since it depends on the aggregation you wish to bind, such as
oTable.bindRows({
path: "properties"
});
Which is the same as:
<Table rows="{properties}" />
It's always important to be more expressive. Use the API that is specifically designed to do that one task.
Comparing the two variants:
myModel.read(sPath) with text="{/path/property}"
myControl.bindElement(sPath) with text="{property}"
I'd be perplexed about the 1st call whereas in the 2nd call, I'd know exactly what you want to achieve (You want to bind element. Alternatively, bindObject can be also used).
The same applies to the framework. Since you're telling exactly what you want to achieve, the framework can improve its behavior based on your intent. E.g.: in (route)PatternMatched handler when the user navigates to the same page, .bindElement with the same path won't trigger another request since the model already stored the entity from the previous call. It can show the result immediately.
With .read, however, the framework doesn't know what you want to achieve, so it sends the request right away regardless of the application state.
Additionally, the 1st variant is anything but future-proof. It relies on the cached results. It's almost a side-effect that it works at all. The problem is that there is no guarantee that this behavior will continue to work in later versions. Also there won't be read method in V4 ODataModel.
TL;DR
v2.ODataModel#read
Does not create context from the response. Repeating .read("<same path>") always sends a new request.
Less expressive. Encourages app developers to work with a client-side model (e.g. JSONModel).
Application loses context awareness, increasing TCO, less future-proof.
bindElement or bindObject
Creates context from the response and stores it internally so that the same request can return the data immediately.
Clearly expresses the intent; application as well as framework can work with the existing APIs.
More future-proof: v4.ODataModel does not support manual read. Imagine you've built your applications with the v2ODataModel.read-jsonModel.setData approach, and you need to migrate to v4. Have fun. :)
I honestly think that v2.ODataModel#read should have never become a public method. I wouldn't encourage anyone to use .read except of when reading the $count value manually.
If the entity values need to be formatted, there are formatters and binding types out of the box which are also easy to extend.
If the application needs to restructure the response body, usually it's a sign of a poor design of the data model or the service not conforming to the OData spec.
I almost agree with Jorg, but not entirely:
It really depends on what are you trying to achieve. If looking to display data from backend then the easiest way to go is with this.getView().bindElement()
but if you are in need to manipulate data before displaying (like formatting text, displaying images from base64 strings, etc) OR if you would like to create new entity using some of existing data, OR update the existing data - the this.getModel(sName).read() is the way to go - as you can set read entity with all its deep entities to JSONModel in successCallback and manipulate it from the localModel.
If using localModel the dataBinding is pretty much the same in the view - except you have to additionally give model name to take data from. So for example, if in successCallback of your Model.read() you set your data to Model named "localModel":
this.getModel().read(sObjectPath, {
urlParameters: {
$expand: ""
},
success: function (oData) {
// "localModel" is name you gave in onInit function to your new JSONMOdel, when setting it to View e.g. this.getView().setModel(oJSONMOdel, "localModel")
this.getModel("localModel").setData(oData);
}
})
then in XML view you would indicate that
<Text text="{localModel>/mainPropertyName}"/>
// for displaying deep entities as List items or in Table
<List items="{localModel>/deepEntityName}">
<StandardListItem title="{localModel>propertyNamefromDeepEntity}" />
</List>
From my experience working with more complex apps that Read-Only - I always use Model.read().
I override the record view, by creating in custom/modules/myModule/clients/base/views/record/record.js this file record.js. I want to get the value of a field for the current object and I use this.model.get('duration'), but I get nothing.The only field available is "id". How can I retrieve the values for others fileds?
When the record.js script is initially called, the model won't have fully loaded, so the only available field with be the id.
Your best bet is probably to override the _renderHtml function; by the the time the view is being rendered all the model details will have fully loaded:
_renderHtml: function() {
// custom code involving this.model.get('duration')
// call parent
app.view.View.prototype._renderHtml.call(this);
}
Note that you may find _renderHtml is called multiple times, sometines before the model is fully loaded. This is just a quirk of Sugar so it may be best to add a check in your code:
if (this.model.get('duration')) {
// custom code involving this.model.get('duration')
}
Dont forget that app.model.get('myfield') only delivers the right content (from this field) when your field is already displayed in detailview - else you will get "undefined"!
So you
Have to call the rest api (rest/v10/yourmodel/yourid) - than you
have all the values available
Display your fields (even you dont want to) to be able to use it in app.model.get('yourfield'), an alternative you could append your record.js (after rendering) with $('div [data-name="yourfield"]').hide();
I know this question is quite old already (but if someone else run into this he could find this useful).
I've been working on this for sometime now, and I keep running into a wall. I think I'm close, but I figured someone out here in the land of SO might have some deeper insight if not a better way of doing what I'm trying to do.
Basically lets look at this scenario. I have a logo w/ some text that can be set from a few different places. If we look at the setup here is what it looks like.
Hiearchy:
Homepage [has designPath]
- Child Microsite Page [has designPath]
- Logo Component
Logic Flow (in logo component):
if properties.get("logoText") {
use this
} else if currentStyle.get("logoTextFromStyle") {
use this
} else if parentStyle.get("logoTextFromGlobal") {
use this
} else {
be blank
}
My query is with how to get the "parentStyle" of this page. Looking at the docs here: http://dev.day.com/docs/en/cq/5-5/javadoc/com/day/cq/wcm/api/designer/Style.html
I've been able to come up with the fact that I can get a Style object from the "designer" object made available via defineObjects. This is defined with the other utility objects like "pageManager, resourceUtil, resource, currentPage, etc".
With that being said this doesn't seem to work.
//assuming we have getting homePage earlier and it is a valid cq Page resource
Resource homePageResource.slingRequest.getResourceResolver().getResource(homePage.getPath());
Style homePageStyle = designer.getStyle(homePageResource);
at this point homePageStyle is null. To do some more testing I i tried passing currentPage.getPath() instead of homePage.getPath(). I assumed this would give me the currentPage resource and would in end yield the currentStyle object. This also resulted in a null Style object. From this I think I can safely conclude I'm passing the incorrect resource type.
I attempted to load the the cq:designPath into the resource hoping to get a Designer resourceType but to no avail.
I am curious if anyone has run into this problem before. I apologize if I've gone into too much detail, but I wanted to lay out the "why" to my question as well, just in case there was a better way overall of accomplishing this.
I've figured out how to return the style. Here is the rundown of what I did.
//get your page object
Page targetPage = pageManager.getPage("/path/to/target");
//get the Design object of the target page
Design homePageDesign = designer.getDesign(homePage);
//extract the style from the design using the design path
Style homePageStyle = homePageDesign.getStyle(homePageDesign.getPath());
it's very interesting the definition of "getStyle" is a little different from the designer.getStyle vs a Design.getStyle. designer.getStyle asks for a resource whereas Design.getStyle will take the path to a Design "cell" and return the appropriate Style.
I did some testing and it looks like it does work with inherited Styles/Designs. So if my cq:designPath is set at level 1 and I look up a page on at level 2 they will return the Design/Style at the cq:designPath set at level 1.
I hope this helps someone else down the way.
I tried this approach but was not getting the Styles in the Style object.
When we do this:
Design homePageDesign = designer.getDesign(homePage);
In this Design object we get the path till the project node i.e etc/design/myproject
After this if we try to extract the Style from the design path we do not get it.
However I implemented it in a different way.
In the design object, we also get the complete JSON of designs for(etc/design/myproject).
Get the sling:resourceType of the target page and get the value after last index of "/".
Check if this JSON contains the last value. If it contains, you can get your styles, i.e. image, etc.
I'm working on a sample app for Facebook, using Flash Builder and Flex.
Now, I've got everything up and running - but there's one problem, specifically with the work history part.
When I try to display the user's work history..here's the code for logging in:
protected function login():void
{
FacebookDesktop.login(loginHandler, ["user_birthday", "user_work_history"]);
}
Here, loginHandler's a callback function, that then goes ahead and displays data about the user:
protected function loginHandler(success:Object,fail:Object):void
{
if (success){
currentState = "LoggedIn";
fname.text = success.user.name;
userImg.source=FacebookDesktop.getImageUrl(success.uid,"small");
birthdayLbl.text=success.user.birthday;
workLbl.text=success.user.work;
}
}
Now, the problem occurs with success.user.work - it ends up printing the following:
[object,Object],[object,Object],[object,Object],[object,Object]
Obviously, I'm doing something wrong..but I can't figure out what exactly it is. Would be grateful for some pointers!
Thanks!
Rudi.
The object contained in success.user.work is most likely an array of objects, each item representing a work period, so you'll have to treat it as such. Either use a list and a custom renderer for each item, or create a string by iterating over the array, and appending the fields that you're interested in.
To see what the individual objects contain, either use a breakpoint during debug and inspect them, or check to see if they're documented in the facebook development documentation.
I have a Strain model that has a belongsTo relationship with a Sample model, i. e. a strain belongs to a sample.
I am configuring a hidden field in the StrainForm configure() method this way:
$defaultId = (int)$this->getObject()->getSample()->getTable()->getDefaultSampleId();
$this->setWidget('sample_id', new sfWidgetFormInputHidden(array('default' => $defaultId)));
Whenever I create a new Strain, the $form->save() fails. The debug toolbar revealed that it tries to save a Sample object first and I do not know why.
However, if I retrieve the default sample ID using the table it works like a charm:
$defaultId = (int)Doctrine_Core::getTable('Sample')->getDefaultSampleId();
$this->setWidget('sample_id', new sfWidgetFormInputHidden(array('default' => $defaultId)));
My question here is what can be happening with the getObject()->getSample()... sequence of methods that causes the StrainForm to think it has to save a Sample object instead of Strain.
I tried to debug with xdebug but I cannot came up with a clear conclusion.
Any thoughts?
Thanks!!
When you call getSample its creating a Sample instance. This is automatically attached to the Strain object, thus when you save you also save the Sample.
An altenrative to calling getSample would be to chain through Strain object to the Sample table since i assume youre only doing this so your not hardcodeing the Sample's name in related form:
// note Sample is the alias not necessarily the Model name
$defaultId = Doctrine_Core::getTable($this->getObject()->getTable()->getRelation('Sample')->getModel())->getDefaultId();
Your solution probably falls over because you can't use getObject() on a new form (as at that stage the object simply doesn't exist).
Edit: Why don't you pass the default Sample in via the options array and then access it from within the form class via $this->getOption('Sample') (if I remember correctly)?