When I create a new item in the server-side using a Kendo UI data source, how do I update the ID of the client-side data item with the ID of the new record inserted in the database in the server-side?
Doing more research I have found this extremely useful information which, indeed, should be in the docs, but it is "hidden" in a not-so-easy-to-find forum search message:
http://www.kendoui.com/forums/ui/grid/refresh-grid-after-datasource-sync.aspx#2124402
I am not sure if this is the best approach, but it resolved my problem!
This solution simply uses the data source read method to update the model instances with data from server.
The precious info is where it is done: in the "complete" event of the transport.create object!
Here is the code:
transport: {
read: {
url: "http://myurl.json"
},
create: {
url: "http://mycreate.json",
type: "POST",
complete: function(e) {
$("#grid").data("kendoGrid").dataSource.read();
}
},
To avoid the additional server call introduced by the read method, if you have your create method return an object the Data Source will automaticly insert it for you.
Knowing that all you need to do is set the id field from the database and return the model.
e.g. psudo code for ASP MVC action for create.
public JsonResult CreateNewRow(RowModel rowModel)
{
// rowModel.id will be defaulted to 0
// save row to server and get new id back
var newId = SaveRowToServer(rowModel);
// set new id to model
rowModel.id = newId;
return Json(rowModel);
}
I've had the same problem and think I may have found the answer. If in the schema you define the object that holds the results, you must return the result of the created link in that same object. Example:
schema: {
data: "Results",
total: "ResultsCount", ....
}
Example MVC method:
public JsonResult CreateNewRow(RowModel rowModel)
{
// rowModel.id will be defaulted to 0
// save row to server and get new id back
var newId = SaveRowToServer(rowModel);
// set new id to model
rowModel.id = newId;
return Json(new {Results = new[] {rowModel}});
}
Just to add to Jack's answer (I don't have the reputation to comment), if your Create and Update actions return data with the same schema as defined in the kendo DataSource, the DataSource will automatically update the Id field as well as any other fields that may have been modified by the action call. You don't have to do anything other that form your results correctly. I use this feature to calculate a bunch of stuff on the server side and present the client with the results w/o requiring a complete reload of the data.
Related
I want to add content to a repeatable component in my beforeUpdate hook. (adding a changed slug to a “previous slugs” list)
in v3, I could just push new data on the component array and it would save.
in v4, it doesn’t work like that. Component data now holds __pivot: and such. I do not know how to add new data to this. I’ve tried adding a component with the entityService first, and adding that result to the array. It seemed to work, but it has strange behavior that the next saves puts in two entries. I feel like there should be an easier way to go about this.
It seems like the way to go about this is to create the pivot manually:
// create an entry for the component
const newRedirect = await strapi.entityService.create('redirects.redirect', {
data: {
from: oldData.slug,
},
});
// add the component to this model entry
data.redirects = [...data.redirects, {
id: newRedirect.id,
__pivot: { field: 'redirects', component_type: 'redirects.redirect' },
}];
But this feels pretty hacky. If I change the components name or the field key, this will break. I'd rather have a Strapi core way of doing this
the way strapi currently handles components is by providing full components array, so in case you want to inject something, you have to read components first and then apply full update, if it makes it clear.
Update
So after few hours of searching, had to do few hours of trail and error, however here is the solution, using knex:
module.exports = {
async beforeUpdate(event) {
// get previous slug
const { slug: previousSlug } = await strapi.db
.query("api::test.test")
.findOne({ where: event.params.where });
// create component
const [component] = await strapi.db
// this name of components table in database
.connection("components_components_previous_slugs")
.insert({ slug: previousSlug })
.returning("id");
// append component to event
event.params.data.previousSlugs = [
...event.params.data.previousSlugs,
{
id: component.id,
// the pivot, you have to copy manually
// 'field' is the name of the components property
// 'component_type' is internal name of component
__pivot: {
field: "previousSlugs",
component_type: "components.previous-slugs",
},
},
];
},
};
So, seems there is no service, or something exposed in strapi to create component for you.
The stuff that also required to be noted, on my first attempt i try to create relation manually in tests_components table, made for me after i added a repeatable component, to content-type, but after an hour more i found out that is WRONG and should not be done, seems strapi does that under the hood and modifying that table actually breaks logic...
so if there is more explanation needed, ping me here...
result:
You can update, create and delete component data that is attached to a record with Query Engine API, which is provided by Strapi.
To modify component data you just need the ID.
const { data } = event.params;
const newData = {
field1: value1,
etc...
};
await strapi.query('componentGroup.component').update({
where: { id: data.myField.id },
data: newData
})
When you have a component field that equals null you need to create that component and point to it.
const tempdata = await strapi.query('componentGroup.component').create(
{ data: newData }
);
data.myField = {
id: tempdata.id,
__pivot: {
field: 'myField',
component_type: 'componentGroup.component'
}
}
Se the Strapi forum for more information.
I'm trying to do my first webapp with backbone/mvc3 and i would like to have some advices to populate a collection.
Here is a part of my collection
window.TaskList = Backbone.Collection.extend({
model: Task,
url: "../../api/Tasks";
},.......
I can use the crud methods to get/update the models but i've the following problem :
When i open the page, my collection is populated (calling the get method serverside) But i would like to have this kind of behavior :
Page 1 : put/delete/get methods => as usual but the collection has to be populated calling the getTasksByWorkshopId serverside method
Page 2 : put/delete/get methods => as usual but the collection has to be populated calling another serverside method to filter the list
...
(ie : i cant filter the collection client side because of the amount of data)
So, my question is : how to keep a generic collection url (as api/Tasks) and populate the collection with another method (do i have to override smth ?)
(sorry for this newbie question)
Thanks in advance
In a comment to the other answer you said that "When the collection is loaded, the url called is /api/Tasks/Workshop/1 (the good one) but, when i want to update a task, the url called is /api/Tasks/Workshop/1/141 instead of /api/Tasks/141."
In order to "update a task" (a task model, I assume) to a different URL, then your Collection & Model should have different URLs. If you define a collection without specifying the model property, the URL used when saving/fetching/deleting a model will be based off of the collection's URL. The same is also true if the collection's model has no defined url property. See below.
Also, JSFiddle example here.
var WorkshopModel = Backbone.Model.extend({
urlRoot: "api/tasks/"
});
var WorkshopCollection = Backbone.Collection.extend({
model: WorkshopModel,
urlRoot: "api/tasks/workshop",
url: function() { return this.urlRoot + '/' + this.id; },
initialize: function(models, options) {
this.id = options.id;
}
});
var c = new WorkshopCollection(null, { id: 1 });
c.fetch(); // GET => api/tasks/workshop/1
var m = c.add({ id: 300, color: 'red' });
m.save(); // PUT => api/tasks/300
m.destroy(); // DELETE => api/tasks/300
m.fetch(); // GET => api/tasks/300
If you remove the urlRoot property from the WorkshopModel, then the URL that the models use will be the collection.url() + '/' + model.id ( api/tasks/workshop/1/300 )
You can do like this :
window.TaskList = Backbone.Collection.extend({
model: Task,
urlRoot: "../../api/Tasks",
url: function() {
if (/*page 1*/) { // you can access this.options where you can pass parameters to distinct the 2 services, when calling the fetch function
return this.urlRoot + // getTasksByWorkshopId URL ;
} else {
return this.urlRoot + // the other service URL ;
}
} ...
}
I queried my database using Breezejs using the following code:
viewModel = {
products = ko.observableArray([])
};
var manager = new entityModel.EntityManager('/api/Products');
manager.executeQuery(query)
.then(function (data) {
viewModel.products.removeAll();
viewModel.products(data.results);
});
However the products rows contain numeric properties like Quantity which are wired to my page using the data-bind property. On saving the model through manager.saveChanges() I get a validation error. This is because KnockoutJS saves the edited numbers as strings.
What's the recommended way to get around this issue?
As of breeze v 0.80.2, this capability is now supported. ( along with the capability to customize type coercion)
One option is to create a CustomBinding.
I use this for decimals:
ko.bindingHandlers.decimal = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
$(element).blur(function () {
var value = valueAccessor();
var valor = Globalize.parseFloat($(element).val());
if (ko.isWriteableObservable(value)) {
value(valor);
ko.bindingHandlers.decimal.update(element, valueAccessor);
}
});
},
update: function (element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).val(Globalize.format(value, "n2"));
}
};
It's a good question.
Currently, breeze does not attempt type coercion, but...
We have discussed having Breeze automatically attempt to coerce any values used within a set operation to the datatype of the property being set ( as defined in breeze metadata). This would occur across all binding libraries, (i.e angular, backbone etc) as well as knockout.
Please feel free to add this to the breeze User Voice. We take these submissions very seriously.
I am using JQGrid with the Trirand.Web.Mvc class, and trying to figure out how to do custom paging.
I have seen the paging demos here
The problem with these demos is that they bind directly to a linq context object and lets MVC take care of the paging.
// This method is called when the grid requests data. You can choose any method to call
// by setting the JQGrid.DataUrl property
public JsonResult PerformanceLinq_DataRequested()
{
// Get both the grid Model and the data Model
// The data model in our case is an autogenerated linq2sql database based on Northwind.
var gridModel = new OrdersJqGridModel();
var northWindModel = new NorthwindDataContext();
// return the result of the DataBind method, passing the datasource as a parameter
// jqGrid for ASP.NET MVC automatically takes care of paging, sorting, filtering/searching, etc
return gridModel.OrdersGrid.DataBind(northWindModel.OrdersLarges);
}
The data set I want to bind to is quite complex and I am returning it from a stored procedure, which does the paging for me.
So all I have to give JQGrid is the correct size of rows for a specific page of the entire resultset. I can also return the total row count.
So I have my results in a List myListOfObjects.
I can pass this into the DataBind using myListOfObjects.AsQueryable()
The problem is, JQGrid thinks there is only {page size} rows, so does not display any of the paging options.
Is it possible to pass in the total row count?
Other grids, like Teleriks MVC grid allows you to pass in the Total row count, and it displays the paging correctly
Ok, so I've managed to solve this myself. There may be other ways to do it, if so I'd love to hear them!
The JQGrid.DataBind produces an JsonResult object, whose Data value is set to Trirands own object Trirand.Web.Mvc.JsonResponse
It's an internal class to their Trirand.Web.Mvc, so i had to copy its structure which I could see using Visual Studio debugging.
It has:
page - the current page number
records - the total record count
rows - of type Trirand.Web.Mvc.JsonRow (which I need to replicate too)
total - the total number of pages needed
JsonRow looks like:
cell - a string array of your columns
id - your row ID
So my code looked like this:
var jsonList = new List<JSONRow>();
myData.ForEach(x => jsonList.Add(new JSONRow(x)));
var jsonResult = Json (new
{
page = page,
rows = jsonList.ToArray(),
records = totalRows,
total = Math.Round((double)totalRows / rows, MidpointRounding.AwayFromZero)
}, JsonRequestBehavior.AllowGet);
return jsonResult;
My JsonRow looks like this:
public class JSONRow
{
public string[] cell { get; set; }
public string id { get; set; }
public JSONRow(MyObjectType myObject)
{
id = myObject.id;
cell = new string[3];
cell[0] = myObject.Col1;
cell[1] = myObject.Col2?? "";
cell[2] = myObject.Col3?? "";
}
}
Say you're writing a web page for fruit vendors using Spring MVC's SimpleFormController, version 2.5.6. On this page the vendor can do simple things like change their name or their address. They can also change their inventory based on a drop down list filled with present inventory selections.
When this drop down list selection changes, the entire form changes to match the inventory of what has been selected. So one stock selection may have bananas and pears, another may have melons, blueberries and grapefruit.
Inside each inventory selection is a input field that needs to be propagated back to the database, for the sake of this example let's say that the user enters the number of fruit.
The way this is modeled in the database is that each Stock name is stored in a table, which has a one to many relationship with the contents of each stock, which would be the type of fruit in this example. Then the type of fruit has a one to many relationship with the quantity the vendor selects. Stock name and the type of fruit in each stock are stored in the database and are unchangeable by the user, with the connected fruit quantity table being editable.
My question is, how do you model the form described above in Spring MVC?
I've tried overriding the isFormChangeRequest and onFormChange to facilitate the form change, but I think I may be misunderstanding the intent of these methods. When I change my backing command object the next time the page is post it tries to bind the request into the form, which breaks if you adjust the size of the Stock array (say from 3 to 2, it will try and bind into the 3rd value, even if it is empty).
If you have a limited amount of different stocks, you can use different handler mappings for each one with a different backing model:
#RequestMapping(params="stock=example1")
ModelAndView handleExample1(#ModelAttribute("stock") ApplesOrangesPears stockObject)
#RequestMapping(params="stock=example2")
ModelAndView handleExample2(#ModelAttribute("stock") BananasPotatos stockObject)
But I guess that is not the case, there are a lot of different stock types and they are dynamic. In that case you can register custom property editor (#InitBinder), and determine dynamically the actual type of the backing object for the inventory, then validate, and convert to or from it explicitly.
What I ended up doing is firing a JavaScript event when the selection in the drop down is changed. This JavaScript (seen below) generates a URL based on the selection of the drop down and uses a location.replace to go to the new URL, which causes the controller to generate a new form.
Using this method over overriding the isFormChangeRequest and onFormChange has allowed me to avoid binding errors caused by left over post data.
function changeUrl(selectionValue) {
var param = getParams();
param["dropdownselection"] = selectionValue;
window.location.replace(getBaseUrl() + buildQueryString(param));
}
//taken from http://javascript.about.com/library/blqs1.htm
function getParams() {
var qsParm = new Array();
var query = window.location.search.substring(1);
var parms = query.split('&');
for (var i = 0; i < parms.length; i++) {
var pos = parms[i].indexOf('=');
if (pos > 0) {
var key = parms[i].substring(0,pos);
var val = parms[i].substring(pos+1);
qsParm[key] = val;
}
}
return qsParm;
}
function getBaseUrl() {
var url = document.location.toString();
if (url.indexOf('?') != -1) {
url = url.substring(0, url.indexOf('?'));
}
return url;
}
function buildQueryString(param) {
var queryString = "?";
for (var key in param) {
queryString += key + "=" + param[key] + "&";
}
//remove last "&"
return queryString.substring(0,queryString.length - 1);
}