Save kendo grid batch edit along with other field - kendo-ui

I have a scenario where I want to save kendo grid batch edit and other fields in the form in one submit button press. I want to use this scenario since I have to save the whole form in one sql transaction.

You might get it implementing a transport.parameterMap function where you create the data structure that you want to send.
Example:
function(options, operation) {
if (operation !== "read" && options.models) {
return {
data: options.models,
field1: $("#form_field1").val(),
};
}
}
Where data is the original data and field1 is the one got from your form and in an html input field which id is form_field1.
Alternatively, you can use transport.update.data where you can define extra data to send to the server on an update (same exists for read, destroy and create).
Here you would have something like:
transport: {
update: {
data: function() {
return {
field1: $("#form_field1").val()
}
}
}
}

Related

How to create Strapi component data in a lifecycle

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.

How do I create a generic reducer plugin, that fires on all forms?

Currently my code works, but only for the forms specified specifically in the combine reducer function. But, I would like to have my code work generally for all forms loaded in my single page app.
Here is the relevant code:
import { reducer as formReducer } from 'redux-form';
export default combineReducers({
someReducer,
anotherReducer,
form: formReducer.plugin({
specificFormId: (state, action) => { // <-- I don't want this only for specificFormId, I want this to happen for all my forms,
// or at least have a dynamic way of adding more forms
const {type, payload} = action;
switch(type) {
case 'RESET_LINK_TYPE_FIELDS': {
return {
...state,
registeredFields: {
...state.registeredFields,
// Do some custom restting here based on payload
}
};
}
default:
return state;
}
}
})
});
So, anytime my <Field ..of a certain type/> fires off this the RESET_LINK_TYPE_FIELDS action, I want the correct form to respond to it.
In the action payload, I can specifically the form identifier or anything else I would need to make this work.
In fact, if the .plugin let me do my own form state slicing, I could easily do this, but because it forces me to pass an object, with a hardcoded form identifier it doesn't work.
Is there a way to have the plugin give me the WHOLE form state, and then I will slice as needed, and return state as needed based on payload?
There is currently no way to do this with the existing API.
You could jury rig a solution by wrapping the redux-form reducer in your own thing.
export default combineReducers({
someReducer,
anotherReducer,
form: resetHack(formReducer)
})
function resetHack(formReducer) {
return (state, action) => {
if(action.RESET_LINK_TYPE_FIELDS) {
// manipulate slice somehow
} else {
return formReducer(state, action)
}
}
}

How to properly update nested data in redux

This is my current object which i need to update:
[
{ id: q1,
answers:[
{ id: a1,
answered: false
},
...
]
},
...
]
I can't figure out how to update this object and set for example answered = true.
Is there any better way saving this kind of object? I tried to use the update addon from React but can't get it to work properly.
You can update the answers list this way, in your reducer:
function update(state, action) {
// assuming you are passing an id of the item to be updated via action.itemId
let obj = state.whatever_list.filter(item => item.id === action.itemId)[0]
//assuming you are passing an id of the answer to be updated via action.answerId
//also, assuming action.payload contains {answered: true}
let answers = obj.answers.map(answer => answer.id === action.answerId ?
Object.assign({}, answer, action.payload) : answer)
obj = Object.assign({}, obj, {answers: answers})
return {
whatever_list: state.whatever_list.map(item => item.id == action.itemId? Object.assign({}, item, obj) : item)
}
}
Here is what your action might look like:
function updateAnswer(itemId, answerId, payload) {
return {
type: UPDATE_ANSWER,
itemId: itemId,
answerId: answerId,
payload: payload
}
}
In your react component class, assuming there is an event handler for monitoring whether if a question is answered:
export default class Whatever extends React.Component {
...
// assuming your props contains itemId and answerId
handleAnswered = (e) => {
this.props.dispatch(updateAnswer(this.props.itemId, this.props.answerId, {answered: true}))
}
...
}
So basically what happens is this:
Your event handler calls the action and pass the updated data to it
When your action is called, it returns the updated data along with a type parameter
When your reducer sees the type parameter, the corresponding handler will be triggered (the first piece of the code above)
The reducer will pull out the existing data from the list, replace the old data with the new one, and then return a list containing the new data
You can create a sub-reducer for the answers key. Look at this example:
https://github.com/rackt/redux/blob/master/examples/async/reducers/index.js
You could use dot-prop-immutable and an update would be as simple as:
return dotProp.set(state, 'quiz.0.answers.0.answered', true);

Kendo AutoComplete

I want to use kendo AutoComplete in a kendoGrid for inline editing. When user inputs anything I'd use it to call a RESTful web service to return a list of products with names that start with the input value.
My questions are:
My web service expects a request looks like http://localhost/myService/appl where "appl" is the value that user enters and the prefix. However, kendo seems to always format the request something like http://localhost/myService?product=appl. How do I change the format?
How do I get the value that user has input in the grid (the AutoComplete textbox) so I can pass it in the request URL?
Define in the DataSource of your autocomplete an url function.
In that function, you can get typed value as:
var val = op.filter.filters[0].value;
and then return the url with the composed value.
Then it is something like:
dataSource: new kendo.data.DataSource({
transport: {
read: {
url: function (op) {
var val = op.filter.filters[0].value;
return "/myService/" + val;
}
}
}
})

ID of new item in Kendo UI data source

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.

Resources