There are many blog posts and discussions about editing data in Knockout.
The main problem I have is that you need an easy way to revert your data to the previous value when the user cancel an editing operation (or when an Ajax call goes wrong).
I was not satisfied with the examples I found: I needed something VERY simply even when editing very complex models.
This is the solution I found, I'm sharing it in order to understand possible drawbacks from someone more expert than me, and of course to help.
When an "edit" button is clicked, I create a copy of the data being edited.
Edit button (ie: on each row of a table with a foreach binding)
<button data-bind="click: editItem">Edit</button>
From my ViewModel:
this.selectedItem = ko.observable();
this.selectedItemCache = ko.observable();
this.editItem = function (item) {
self.selectedItem (item);
self.selectedItemCache (ko.mapping.toJS(item)); // ko.mapping.toJS "detach" my item from the view model
}
When the user click on the "cancel" button or when the AJAX call used to update the server fails, I copy the data from the "cache" observable with:
this.cancelEditItem = function () {
for (var prop in self.selectedItem) {
if (typeof self.selectedItem[prop] === 'function') {
self.selectedItem[prop](self.selectedItemCache()[prop]);
}
}
}
I agree with Nicola's answer in that approach is the most popular approach.
I've used this http://www.knockmeout.net/2011/03/guard-your-model-accept-or-cancel-edits.html which is a fairly polished way to do this. It has worked well in my apps.
Ryan Niemeyer's "Getting the Most Out of Knockout.js" screencast includes a nice way to do this (along with a load of other useful tips) - I recommend checking it out.
http://vimeo.com/51103092
He talks about reverting at about 16:30.
Related
I have an existing Quick Action button on an object, which I would like to display as a button for Community Users
I have tried implementing lightning:quickActionAPI in a Lightning Component that I created, then added the component to the record detail page in Community Builder. I have changed the actual names of objects and fields with general names
<lightning:quickActionAPI aura:id="quickActionAPI" />
<lightning:button label="Update" onclick="{!c.updateRequestStatus }" />
updateRequestStatus : function(component, event, helper) {
//debugger;
var actionAPI = component.find("quickActionAPI");
var fields = {fieldApiName: {value:"Closed"}};
var args = {actionName: "objectApiName.quickActionName", entityName: "objectApiName", targetFields: fields};
actionAPI.setActionFieldValues(args).then(function(){
actionAPI.invokeAction(args);
}).catch(function(e){
console.error(e.errors);
});
}
Expected result: when clicking on the button in the community, the quick action will be called and a window will open
Actual result: clicking on the button executes the JS method but nothing happens
I'm currently seeing the same thing in my Community. The documentation says lightning:quickActionAPI is only available in Lightning Experience, and makes no references to Communities. I don't think it's supported, yet. Though it is confusing that the actionAPI object will instantiate just fine in a Community context but its promises never complete.
Is there a way to set a listener to column-menu, so that an event is fired when I open and close the menu?
Feature description: https://www.ag-grid.com/javascript-grid-column-menu/
I already searched in the official documentation, but didn't find an answer.
Background is:
I want to store the table state with displayed cols, sorting, position of cols, filter etc. in a database. Of course I could use the listeners like onFilterChanged, onDisplayedColumnsChanged or onSortChanged.
Problem is, that it will be fired every time when something changes and so there are produced a lot of unwanted api-calls.
Thats why I want to perform one call when the column-menu is closed.
Update
As Viqas said in his Answer, there is no official way to do it. I
tried to avoid the solution with postProcessPopup and tried to find a cleaner
solution for my problem - to store the table state.
For a workaround with a callback when ColumnMenu is closed Viqas Answer is more appropriate.
Notice that this is no workaround for the callback itself - it is just a (possible) solution to store the table state and perform ONE API Call
I used the ngOnDestory() function of Angular.
ngOnDestory(): void {
const tableState = {
columnState: this.gridOptions.columnApi.getColumnState(),
columnGroupState: this.gridOptions.columnApi.getColumnGroupState(),
sortState: this.gridOptions.api.getSortModel(),
filterState: this.gridOptions.api.getFilterModel(),
displayedColumns: this.gridOptions.columnApi.getAllDisplayedColumns()
};
// submit it to API
}
You're right, there's no official way to do it. A workaround could be to detect when the menu is closed yourself. Ag-grid does provide you the postProcessPopup callback (see here) which provides the parameter of type PostProcessPopupParams; this contains the column menu popup element that is displayed, so you could check when the menu is no longer visible.
Create a variable to store the columnMenu element in:
columnMenu: any = null;
Store the columnMenu in this variable using the ag-grid event postProcessPopup:
<ag-grid-angular [postProcessPopup]="postProcessPopup"></ag-grid-angular>
this.postProcessPopup = function(params) {
this.columnMenu = params.ePopup;
}.bind(this);
Then create a listener to detect when the column menu is no longer visible in the dom:
this.renderer.listen('window', 'click',(e:Event)=>{
console.log(this.columnMenu)
const columnMenuIsInDom = document.body.contains(this.columnMenu);
if (!columnMenuIsInDom && this.columnMenu != null)
{
this.columnMenu = null;
}
});
This is slightly hacky and a workaround, but I can't think of a better way at the moment.
Take a look at this Plunker for illustration.
Im trying to adapt jqGrid add function to my own purposes. I have navButton that I want to make a specific behavior:
When user click that button, new row, in edit mode appears on the
grid.
When user type data, and click enter, then data is send to
server.
Right now, my code looks like this:
.navButtonAdd("#${pagerId}", {
caption: "${resolveMessage('assign')}",
buttonicon: "ui-icon-add",
onClickButton: function() {
if (assignMode == false){
assignMode = true;
$('#${tableId}').jqGrid('addRowData', 0, {});
$('#${tableId}').jqGrid('editRow', 0);
$('#${tableId}').jqGrid('saveRow', 0, function(){ assignMode = false; }, "${assignURL}");
}
},
position:"last"
})
My problem is that saveRow function is not waiting for the end of editRow. It send empty data to server after the button is clicked immediately. How to made saveRow to wait for typed data?
I recommend you to use inlineNav instead of re-implementing the same functionality yourself. For example, you current code, for example uses the same rowid 0 for every added row, which is very bad. Moreover you call editRow without any additional options and then call saveRow immediately after editRow which force saving the empty row with probably wrong data. Saving can fail during validation.
Moreover I'd recommend you to consider to use free jqGrid available for free under MIT or GNU GPLv2 licenses instead of usage commercial Guriddo jqGrid JS (see the prices here). I develop free jqGrid fork since the end of 2014 (short after the post) and have implemented really many fixes, improvements and new features described shortly in readmes to every published version, wiki articles and the answers on the steckoverflow which I posted at the time.
just a very short question on using Backbone.js with LocalStorage:
I'm storing a list of things (Backbone collection) in LocalStorage. When my website is open in multiple browser windows / tabs and the user in both windows adds something to the list, one window's changes will overwrite the changes made in the other window.
If you want to try for yourself, just use the example Backbone.js Todo app:
Open http://backbonejs.org/examples/todos/index.html in two browser tabs
Add an item 'item1' in the first tab and 'item2' in the second tab
Refresh both tabs: 'item1' will disappear and you'll be left with 'item2' only
Any suggestions how to prevent this from happening, any standard way to deal with this?
Thxx
The issue is well-known concurrency lost updates problem, see Lost update in Concurrency control?.
Just for your understanding I might propose the following quick and dirty fix, file backbone-localstorage.js, Store.prototype.save:
save: function() {
// reread data right before writing
var store = localStorage.getItem(this.name);
var data = (store && JSON.parse(store)) || {};
// we may choose what is overwritten with what here
_.extend(this.data, data);
localStorage.setItem(this.name, JSON.stringify(this.data));
}
For the latest Github version of Backbone localStorage, I think this should look like this:
save: function() {
var store = this.localStorage().getItem(this.name);
var records = (store && store.split(",")) || [];
var all = _.union(records, this.records);
this.localStorage().setItem(this.name, all.join(","));
}
You may want to use sessionStorage instead.
See http://en.wikipedia.org/wiki/Web_storage#Local_and_session_storage.
Yaroslav's comment about checking for changes before persisting new ones is one solution but my suggestion would be different. Remember that localStorage is capable of firing events when it performs actions that change the data it holds. Bind to those events and have each tab listen for those changes and view re-render after it happens.
Then, when I make deletions or additions in one tab and move over to the next, it will get an event and change to reflect what happened in the other tab. There won't be weird discrepancies in what I'm seeing tab to tab.
You will want to give some thought to making sure that I don't lose something I was in the middle of adding (say I start typing a new entry for my to-do list), switch to another tab and delete something, and then come back I want to see the entry disappear but my partially typed new item should still be available for me.
I'm working using scriptaculous library. However I'm facing some issues with inclusion of the JSON library for the prototype library. It adds a toJSONSTring and parseJSONSTRING method to all objects automatically and frankly this is causing havoc in places. Like I can't seem to use the Ajax Updater function and I suspect its because of this toJSONSTring method that has been attached to my options object which I pass to it.
Is there anyway to unset or atleast somehow remove a function which has been added to the Object.
EDIT:::
Actually I'm trying to make an ajax request and I'm facing an issue in the
Ajax.Updater = Class.create(Ajax.Request,....
part of the prototype library. At the part where its supposed to execute and post an AJAX request it doesn't - especially at:
$super(url, options);
To be precise I'm using this sortable and editable table grid here at this url:
http://cloud.millstream.com.au/millstream.com.au/upload/code/tablekit/index.html
Basically you clcik on a table cell to edit it and push the OK button to confirm. Upon clicking the button an ajax request is made.
The editable feature of the table calls the Ajax updater as follows in a submit function:
submit : function(cell, form) {
var op = this.options;
form = form ? form : cell.down('form');
var head = $(TableKit.getHeaderCells(null, cell)[TableKit.getCellIndex(cell)]);
var row = cell.up('tr');
var table = cell.up('table');
var ss = '&row=' + (TableKit.getRowIndex(row)+1) + '&cell=' + (TableKit.getCellIndex(cell)+1) + '&id=' + row.id + '&field=' + head.id + '&' + Form.serialize(form);
this.ajax = new Ajax.Updater(cell, op.ajaxURI || TableKit.option('editAjaxURI', table.id)[0], Object.extend(op.ajaxOptions || TableKit.option('editAjaxOptions', table.id)[0], {
postBody : ss,
onComplete : function() {
var data = TableKit.getCellData(cell);
data.active = false;
data.refresh = true; // mark cell cache for refreshing, in case cell contents has changed and sorting is applied
}
}));
},
The problem is that the request is never made and I keep pushing the OK button to no avail.
EDIT::::::::::::::::
I'm still stumped here - I've even tried calling the Ajax.Updater function on my own and it won't work at all. Its like this function has officially been rendered as useless all of a sudden. I've made the changes you said but all to no avail :( frankly I'm running out of options here - another idea would be to ditch this tablekit and look for something else which has similar functionality in the hopes that THAT MIGHT work!
It sounds like those methods are being added to the prototype of Object. By adding to Object's prototype, the library is automatically giving everything that derives from Object (in other words, everything) those methods as well. You might want to take do some reading on Prototypal inheritance in Javascript to get a better handle on this.
Anyway, you can remove those methods by doing this:
delete Object.prototype.toJSONString;
delete Object.prototype.parseJSONString;
You can delete anything from an object using "delete":
a.toJSON = function () {};
delete a.toJSON;
a.toJSON() => error: toJSON is not a function
a.toJSON => undefined
However I don't think that what is happening happens because of what you think is happening :) Maybe give more details on the problem you have with Ajax.Updater?
Seen the edit. OK, can you also post the actual line of code that calls Ajax.Updater and, also important, explain in detail how the options object you feed to it is made?
Also, please make sure you're doing something like this:
new Ajax.Updater(stuff)
and NOT just:
Ajax.Updater(stuff)
You NEED to create a new Updater object and use "new" (most probably you're already doing that, just making sure).
OK I'm still not sure what is getting passed to ajax.Updater since you extend stuff that I can't see, but try this: remove the "&" from the beginning of the variable "ss"; in the options object use parameters: ss instead of postBody: ss.
delete obj.property
In this case:
delete obj.toJSONSTring;
delete obj.parseJSONSTRING;