I have created a table using handsontable:
hot = new Handsontable(container, {
data: [],
dataSchema: {id: null},
startRows: 5,
startCols: 1,
colHeaders: ["Car"],
columns: [
{data: 'id'}
],
rowHeaders: true,
minSpareRows: 1,
persistentState: true,
onChange: function (change, source) {
console.log(change);
console.log(source);
}
});
They have a pretty elaborate example on saving/loading to/from server using ajax. However, I just want use persistent state to save load all the stuff.
In particular I want when a cell's value in hot is changed I want to save this information in some local storage, so that I can retrieve it later. The closest I got was to use change parameter in onChange and save it manually to localStorage. My main question is how can I save cell's info once it changed to some local storage? Better to persistentStorage.
Also, can I save the whole hot table to local storage? What is more efficient to update the whole table every time, or to update just the value of that particular cell? Is there a quick way to load table? Can you provide a good example how can I save/load table to local storage on change?
Eventually, I went with something like this:
hot = new Handsontable(container, {
data: [],
dataSchema: {id: null},
startCols: 4,
colHeaders: ["ID"],
columns: [
{data: 'id'}
],
rowHeaders: true,
minSpareRows: 4,
afterChange: function (change, source) {
// restore table after reload of a page
if (source === "loadData") {
// load data from local storage
if (localStorage['data']) {
var data = JSON.parse(localStorage['data'])
this.loadData(data);
this.render();
return
}
}
else {
// save all data to local storge if the edit happends
localStorage['data'] = JSON.stringify(this.getData());
return
}
}
});
There are many ways of doing what you ask so you'll have to try it out and see. The most common way to do what you ask is to get the data onAfterChange. In this callback (read the documentation) you have access to the new value and the row and col indeces. At that point, you can do one of two things:
You can store in localStorage the (row,col) with the data at that position. That's the first option. Then onLoad you can go through all available key,value pairs and set the data array.
The second thing you can do is much easier but more costly. In the onAfterChange callback, every time, save the contents of hot.getData(). You can do it like this:
// assuming 'hot' is the name of your handsontable instance
localStorage["data"] = hot.getData()
Storing each cell is fast and takes minimal space on saving (so this part will be fast) but it's harder to implement the loading code. The storing of all the data might take longer but it makes your code much easier to implement (on load, simply set the data object to the value of localStorage.data).
I would try the storing of all the data first (on the largest data set you think you'll ever have) and seeing if it is slow. If it is, use the per-cell approach.
EDIT: persistent state taken from documentation
The following is the direct quotation. The difference here from what I posted earlier is that instead of doing localStorage["data"] = hot.getData() you would instead do hot.persistantStateSave("data", hot.getData())
How to save data locally.
You can save any sort of data in local storage in to preserve table state between page reloads. In order to enable data storage mechanism, persistantState option must be set to true (you can set it either during Handsontable initialization or using the updateSettings method). When persistentState is enabled it exposes 3 hooks:
persistentStateSave (key: String, value: Mixed) Saves value under given key in browser local storage.
persistentStateLoad (key: String, valuePlaceholder: Object)
Loads value, saved under given key, form browser local storage. The loaded value will be saved in valuePlaceholder.value (this is due to specific behaviour of PluginHooks.execute() method). If no value have been saved under key valuePlaceholder.value will be undefined.
persistentStateReset (key: String)
Clears the value saved under key. If no key is given, all values associated with table will be cleared.
Related
I have treePanel with widgetColumn which includes combobox widget in it wuth default text. My requirement is when i select the defalt text, one new record should get inserted in store & also get saved in database.
{
text: 'TC',
dataIndex: 'scrTC',
xtype: 'widgetcolumn',
widget: {
xtype: 'combo',
store: 'TCStore',
valueField: 'id',
displayField: 'name',
matchFieldWidth: false,
listeners: {
select: 'selectDefault'
}
}
}
Controller Method:
selectDefault: function(combo){
loadData(combo, id, name); //there is a logic to get id & name, then pass it to loadData method
}
loadData: function(combo, id, name){
var store = combo.getStore();
store.insert(0,{id: id, name: name});
store.sync();
combo.setValue(id);
}
Issue is when i first time select default text, store sync method is not inserting the data in database but the combo show the new value & store also the new value(seen using debugger).
When i select again then the data is inserted into database.
I Debugged code, the execution flow is correct, only thing is sync is not calling backend to insert data at first instance, but works for second time.
Can someone help.
If you assign id to the inserted record, sync thinks, that it already exists in the DB and won’t fire add event (inserted record won't get phantom property). Either rename id field or set idProperty for the model to something else.
The store will use it's proxy to sync the data with your backend, so ensure that it is configured to the type of backend that you are using. The store itself may be configured with a proxy, or it may be defaulting to the proxy on it's model. Without seeing the store configuration, I can't say for sure.
For example, if you are using a REST backend, then use a REST proxy on the model that the store is configured with: https://docs.sencha.com/extjs/6.6.0/modern/Ext.data.proxy.Rest.html
I have a form that consists of a number of multi-select fields. Each select has a unique ID, and are named accordingly:
values[foobar1][]
values[foobar2][]
values[foobar3][]
... etc.
This form could potentially contain hundreds of these fields, and so is paged by ajax. The result of that is that there is no guarantee that all records are going to available at once at the front end. Therefore, it is impossible for me to submit the entire form. I do, however, have access to the entire list of records server-side.
My solution to this was to watch for changes in the form fields and, for every field that is changed, store the values in an array to keep track of just the altered field values. So if you make a change to just foobar2, the resulting serialized array that is sent to the server will look like this:
0: Object {
name: "values[foobar2][]"
value: "thevalue1"
},
1: Object {
name: "values[foobar2][]"
value: "thevalue3"
}
So this works fine except for, as you may have guessed, when the select multiple is emptied. No matter what format I use for storing the altered values, be it arraySerialization of each field or as an associative array, when I pass my array to $.param() for the ajax request the resulting serialized string contains no trace of the empty value. So there is no way for the server to determine that the value has been emptied.
Can anyone suggest a way of either passing the data to the server so that the empt(ied) array remains intact, or another way of dealing with the initial problem.
Thanks in advance!
You want to calculate the diff between current and previous state, send the change to the server, and apply it to the data.
You can do so using the JSON patch standard (rfc6902).
JSON Patch is a format for describing changes to a JSON document. It
can be used to avoid sending a whole document when only a part has
changed. When used in combination with the HTTP PATCH method it allows
partial updates for HTTP APIs in a standards compliant way.
To create the diff you can use an NPM module, such as jiff. A diff is set a of patching commands, that can transform a JSON document. For example:
[
{ "op": "replace", "path": "/values/foobar2/", "value": ["thevalue1"] },
{ "op": "remove", "path": "/values/foobar2/"}
]
You send the diff to the server, and then use a server module, such as php-jsonpatch, to apply the patch to the current data on the server.
Create a single object for all select field values you can use localStorage or sessionStorage to store it. Since the form is in a lot of pages and you use ajax to get each select field. Place the selected values of each field in an array. Creating an object like this is the idea.
{
formValues: {
foobar1: {
values: ["thevalue1","thevalue2"]
},
foobar2: {
values: ["thevalue3"]
},
...
foobarN: {
values: []
}
}
}
Every time you update a select vield value or values make sure to update the localStorage saved value. e.g.
var valuesObject = {
formValues: {
foobar1: {
values: ["thevalue1","thevalue2"]
},
foobar2: {
values: ["thevalue3"]
},
foobar3: {
values: []
}
}
}
// Put the object into storage
localStorage.setItem('valuesObject', JSON.stringify(valuesObject));
// Retrieve the object from storage
var valuesObjectA = localStorage.getItem('valuesObject');
//console.log('valuesObject: ', JSON.parse(valuesObjectA));
// post your data
$.post( "ajax.php", valuesObjectA ).done(function( data ) {
alert( "Data Loaded: " + data );
}).fail(function() {
console.log( "error" );
});
Sample fiddle
I have my normalized store with lists and id mappings:
{
"byId": {
"images": {
"10984": {"id": "10984", "src": "/img.png"}
}
},
"myImages": [
"10948"
]
}
Now I want to create a new image, and add it to the list. The problem is that I don't have an id for it until I send it to the server. So I could generate a random id
"tempid19048": {"id": "tempid19048", src: "/img.png"}
"myImages": [
"10948",
"tempid19048"
]
And then I save it to the server and get an id back I dispatch an action. I may have tempid19048 used in multiple parts of the state.
What's a sane way to update everything with the new id? Am I approaching this all wrong?
Because Redux is really a client-side state manager, generally you just want to mirror the global state and thats it. As you know, unlike Flux/GraphQL, there isn't any functionality for caching, optimistic updates etc. My gut tells me building this would be difficult without compromising the pure simplicity that Redux is built on.
That said, I found redux-optimistic that mimicks Flux's optimistic updates for Redux. I've not used it, but it looks nice enough.
But with even with that said, I'd still highlight the simple solution of designing-out any need for temp ids / optimistic updates etc:
make a request to create the image on the server
show a spinner
on success message from the server, add the new image with the new id to the global state
You can make IDs in "myImage" as key-value pair.
"myImages": {
"10948": "10948",
"tempid19048": "tempid19048"
}
Now where ever you want to use you can use as
myImages["tempid19048"]
This will give you its current value as "tempid19048".
And if you need to change this tempId you need to change only the value, and key remains same.
"myImages": {
"10948": "10948",
"tempid19048": "newServerID"
}
Hence, irrespective of number of places you are using tempID now you need to update in only one place.
Question in a nutshell
With each column tweak, I save the new column model to the server, for that user, and immediately start mutating the JSON to align with the new column order for that user's ajax requests. In this scenario, do I still need the remapColumns function or the permuatation variable?
My initial jQGrid (4.4.5) is loaded using datatype:'json', loadonce:true (fwiw)
The server explicitly writes out the javascript variables to properly initialize the grid's colNames and colModel in the desired order, width and desired 'hidden-ness' - unique to that users' last saved settings (if any). For example:
var MainTableColumnNames = ['Work Order','DatePickedUp','Generator','Order Status'];
var MainTableColumnModel = [
{name: 'Work Order', index: 'Work Order', editable:false, align: 'left', hidden:false, width:82 },
{name: 'DatePickedUp', index: 'DatePickedUp', editable:false, align: 'left', hidden:false, formatter:'date', width:120 },
{name: 'Generator', index: 'Generator', editable:false, align: 'left', hidden:false, width:264 }, width:293 },
{name: 'Order Status', index: 'Order Status', editable:false, align: 'left', hidden:false, width:50 }
];
I have my own method for saving col model data to the server and am not using any of the concepts that I've run across recently (it seems Oleg has addressed this a lot). FWIW: I only save width,size,hidden (and the column layout order is implied). Thus, some of the above col model is supplied from user settings, while other parts are derived from defaults or by reflecting on properties of the .NET System.Data.DataColumn
Furthermore, the resizeStop event and the extended column chooser both point to a common script function which persists changes in the column layout. Thus, the next time the user loads the web page, everything will be laid out according to the new settings saved for that user. That is all working fine.
But.. even before they reload the web page, any new requests to the server will now be sending the json data back to the user in the new column order. And, I am not sure what is going wrong. The column sorting issue that I've seen others refer to elsewhere on StackOverflow is not happening to me.. you can click a column header and the data re-sorts in place (I presume it is because I use loadonce, so it sorts using existing localdata).
But, if I click on a node in my tree control (i have my own tree implementation that loads various slices of data into the main grid) the new JSON data coming back is not successfully loading into jqGrid- a couple of columns look transposed -
Now if I do a full page refresh everything is fine.
An example of how I get new data into the grid, say, when we click one of my tree nodes:
$('#myCustomTreeNode').bind('tree.click', function(event){
var node = event.node;
var newUrl = '/api/DataView/MainGrid/' + node.id;
$grid.jqGrid('setGridParam',{url : newUrl, datatype: 'json', postData: { 'guid':someVariable} }).trigger('reloadGrid');
});
Update: So here's my question:
So, my main question is: after updating a jqGrid column model (not only jqGrid's client model, but when the successive server-supplied data will immediately conform to the new column order).. I want to know the proper way to continue using the current instantiated jqGrid instance w/o a full page re-draw. Must I do a full GridUnload or GridDestroy? I'm hoping someone can give me pointers on something I must be doing wrong with my .trigger 'reloadGrid' call illustrated above.
More detail
As stated in my opening paragraph, each user has their own "MainTableColumnModel" variable written to their browser session.. literally on page draw, the physical column order is written in the order of their last saved column permutation.
And, I think this is where I am possibly getting things wrong.
In other examples like this one by Oleg it seems all users should be getting the same initial column model, and if they've saved a specific model into their user settings, then I should be loading up that as a permutation variable for remapColumns to process when the page intially loads, rather than transforming all the JSON data requests according to each users' individual column-model setting.
Since I've got my alternate method almost working, I'm still hoping I can get this right.
I'm using Jerome's localStorage adapter with Backbone and it works great for collections.
But, now I have a single model that I need to save. So in my model I set:
localStorage: new Store("msg")
I then do my saves and fetch. My problem is that everytime I do a refresh and initialize my app a new representation of my model is added to localStorage, see below.
What am I doing wrong?
window.localStorage.msg = {
// Created after first run
"1de5770c-1431-3b15-539b-695cedf3a415":{
"title":"First run",
"id":"1de5770c-1431-3b15-539b-695cedf3a415"
},
// Created after second run
"26c1fdb7-5803-a61f-ca12-2701dba9a09e":{
"0":{
"title":"First run",
"id":"1de5770c-1431-3b15-539b-695cedf3a415"
},
"title":"Second run",
"id":"26c1fdb7-5803-a61f-ca12-2701dba9a09e"
}
}
I ran into same issue. Maybe you have something similar to this
var Settings = Backbone.Model.extend({
localStorage: new Store("Settings"),
defaults: { a: 1 }
});
var s = new Settings;
s.fetch();
I changed to
var s = new Settings({ id: 1 });
localStorage adapter check for id like
case "read": resp = model.id ? store.find(model) : store.findAll(); break;
so 0 or "" for id wont work and it will return all models in one
I'm new to backbone.js too, but it looks like the persistence model is analogous to database tables. That is to say, it's designed to create/delete/read records from a table. The localStorage adapter does the same, so what you are doing there is creating a Msg "table"
in localStorage, and creating a new Msg "record" each time, and the adapter gives each new Msg a unique id.
If you just have one object, it's probably easier to just use localStorage directly. The API is really straight forward:
localStorage.setItem("key","value");
Keep in mind that localStorage only deals with key/value pairs as strings, so you'd need to convert to/from string format.
Take a look a this question for more on doing that:
Storing Objects in HTML5 localStorage