Knockout - Copy an observableArray without tracking its depcencies - javascript

I've ran in to an issue lately where as I thought it would be fairly easy to fix but ran in to problems.
I am using Knockout and MVC where I have a form where users can enter data and they have an option of either submitting the changes that they have made or cancel.
The problem comes when they want to cancel, the changes still get changed, since it binds automatically to the observable. What i want to do is to revert to the old model, before they changed.
me.contactEditCancel = function() {
me.Contact= oldContact;
}
Where the oldContact is assigned when the edit form is opened.
I cant get it to loose its depedencies tracking... Any idea what to do? To copy the observableArray without the tracking, so I dont have to reload the entire page to get the old model back.
Hopefully my question was clear enough, appreciate any help!

You need to work with original values and edit values, which is a copy of original values. When users submit, you save the edit values. If they cancel, you show the original values.
I don't know about your UI patterns, but I often have a grid of data with Edit buttons. Click one, and you're editing that item in a popup. This is roughly the pattern I use for managing that:
self.itemInEdit = ko.observable(null);
self.edit = function(item) {
self.itemInEdit(new MyViewModel(ko.toJS(item)));
};
self.cancel = function () {
self.itemInEdit(null);
};
self.submit = function() {
// Save self.itemInEdit
};

Related

Angular: *ngFor not updating view after array changes

I'm making a web app with Angular however I'm having trouble with the load button. So the user presses the load button, selects a save to load, and a new set of properties is loaded into forms that each constitute a step-item in a stepper. The forms array that loads all of the forms is 'reset' with this function:
private resetPropForms(): void {
this.propForms = [];
}
This is the function that receives as an argument a properties event and then sets the forms array:
onPropertiesEmitted(properties: Property[]): void {
this.resetPropForms();
this.resetDividedProperties();
this.resetUndividedProperties();
this.setDividedProperties( properties );
this.setForms();
this.setUndividedProperties( properties );
}
And this is what my template looks like:
<se-stepper
*ngIf="undividedProperties"
[linearMode]="false"
[(activeStepIndex)]="activeStepIndex"
>
<se-step-item
*ngFor="let form of propForms"
>
<app-properties-form
[propertiesForm]="form"
(change)="onPropertiesChanged($event)"
>
</app-properties-form>
</se-step-item>
</se-stepper>
Lastly, the view is updated but only after I go to the next step and come back. And if I save the properties then the correct value is sent to the backend even though the value displayed in the view is incorrect/not updated. Any ideas why this is happening. I tried using trackBy with a unique identifier for each form ( using a random number ) but that didn't work. I tried using ChangesRef and detectChanges() and that didn't work. When I leave out the *ngFor and just display the first form in the array it updates properly so that leads me to believe that this problem has something to do with the *ngFor.
edit:I'm pretty sure setTimeout() placed in onPropertiesEmitted() worked but I forgot where I put it to make it work and it seemed like a not-so-great solution to the problem
I Think the problem is that the array is the same and change detect won't detect changes, try this
private resetPropForms(): void {
//this.propForms = [];
//array.slice() returns new array
this.propForms = this.propForms.slice(0,0);
}
You could also reset the form before assigning new properties ( form.reset() )

Disambiguating bootstrap-switch state changes in knockout

I'm using bootstrap-switch together with the knockout binding handler referenced from this question shown below:
ko.bindingHandlers.bootstrapSwitchOn = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
$elem = $(element);
$elem.bootstrapSwitch();
// Set intial state
$elem.bootstrapSwitch('setState', ko.utils.unwrapObservable(valueAccessor()));
$elem.on('switch-change', function (e, data) {
// Update the model when changed.
valueAccessor()(data.value);
});
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var vStatus = $(element).bootstrapSwitch('status');
var vmStatus = ko.utils.unwrapObservable(valueAccessor());
if (vStatus != vmStatus) {
$(element).bootstrapSwitch('setState', vmStatus);
}
}
};
This seems to be working quite nicely and I've mocked up a fiddle to illustrate how I'm using it here:
http://jsfiddle.net/swervo/of0q42j0/5/
However, I have a few issues which I can't seem to solve in a satisfactory manner:
1) If I have an array of items in an ko.observable array I can put a click handler on all of them and have them call a function in the parent view model like this:
data-bind="click: $parent.clickHandler"
Which, when called, passes through the items own view model. This is really convenient for getting properties of the item that was clicked, eg., id. I've put a button in the fiddle above to illustrate how easy this is to do.
However, if I'm using the bootstrap-switch instead of a simple button the switch doesn't seem to know about it's parent and I can't find an elegant way of passing through the view model containing the switch to its parent - like you can with a button. I have tried giving each item in the array a reference to it's parent view model and this does work but creates a circular reference and thus doesn't seem like the correct approach.
2) In the application that I'm building the state of items in a list can be changed on a different clients - and the local state needs to update to reflect these remote clients. Equally the state can also be changed on the local client which is then propagated to other clients. My problem here is how to disambiguate between changes to state that have happened locally (ie., due to the user clicking on the switch), and changes that have happened remotely (ie., due to an update coming from the server). In my actual project I'm using knockout subscribe to listen for changes in the values linked to the switches like this:
viewModel.observableValue.subscribe(function(newValue) {
// test value on server and if it is different update
});
I want to avoid receiving an update from the server and then updating the server again (with the same state) when my switch changes to reflect the new state. At the moment I've fixed this by testing the server state (as implied in the code snippet above) before I send the update and if it is the same as the pending state update I discard it. (I've simulated a server update using a button in the referenced fiddle above).
Neither of my solutions to these problems feel elegant hence the question here.
Any help would be much appreciated.
I'm not sure what you mean by the 'the switch doesn't seem to know about it's parent'. Looking at http://knockoutjs.com/documentation/custom-bindings.html, I can see that init and update both have a 5th param, bindingContext that has the parent information, should you wish to access it.
Ahem, one of the projects we worked on the past had a toggle button that suffered from the same issue and it was fixed is a very simple way. For events that are generated locally, just attach a property to the object, like .local = true; and check for it in the update (or attach it in your REST handler) to distinguish local/vs REST. Don't forget to delete the property from the view model once done in update though.

rivets.js: prepopulate model with data from view on init

Perhaps this seems a bit backwards, but I have a view bound with Rivets.js for which I'd like the view to populate the model on initialization.
The usecase is that I'm using server-side rendering to return a snippet (the view) including rivets' data-attributes. So NO JSON is returned from server to client.
Now, by pressing 'edit' a user may put the content in 'edit'-mode, and start editing at will. (Using contenteditable, but this is out of scope here I guess).
So how to make sure the model is populated with values from the view on init?
I know that this question is a little outdated but I recentry tried rivets and I came across the same problem.
The solution:
// In your rivets configuration you disable preload:
rivets.configure({
templateDelimiters: ['[[', ']]'],
preloadData: false
});
// you bind your data
var binding = rivets.bind($('#auction'), {auction: auction});
// you manually publish it once to populate your model with form's data
binding.publish();
And that's it. I still don't know how to disable prelaod per bind
From the example on Rivets website (assign to 'rivetBinding')
var view = rivets.bind($('#auction'), {auction: auction});
doing rivetBinding.publish(); will bootstrap the model with values from the view for all bindings that have 'publishes = true'.
This question is old but it still has no accepted answer, so here goes:
You need to disable the preload configuration so rivets doesn't override whatever is in the input with what you have in your model at the time you do the binding. This can be done via the preloadData=false configuration, either globally (rivets.configure(...)) or view-scoped (third param to rivets.bind(...)).
After the binding, you need to publish the view (pull the values to your model). You also need to set up the observers via sync() call, otherwise your binded methods won't be triggered.
Using the same example as the previous answers:
var view = rivets.bind($('#auction'), { auction: auction }, {
preloadData: false
});
view.publish();
view.sync();

Ember model lifecycle, and reverting to original state

I'm struggling to understand everything about the ember model lifecycle. I have created this jsfiddle to illustrate my problem. When clicking on one of the entries in the list, editing a value, and clicking the versions link to go back to the list, I get the following error:
Uncaught Error: Attempted to handle event loadedData on while in state rootState.loaded.updated.uncommitted. Called with {}
What is causing this? I get that the object state is now dirty, but how can I force a refresh of all objects when the list is opened?
Also, any suggestions on how to discard all changes to the properties if the form is not saved? I was thinking about cloning the object, using that clone in the edit form, and merging that with the original when saving. Not as easy as I first imagined.
Using latest ember and ember-data.
After quick discussion with #tchak, a solution could be to override the Version route's exit function, and rollback the current model.
App.VersionRoute = Ember.Route.extend({
exit: function() {
var controller = this.controllerFor(this.templateName),
content = controller.get('content');
if (content.get('isDirty')) {
content.get('transaction').rollback();
}
this._super();
}
});

Update form record for disabled fields

I have a complex form in ExtJS 4, where various parts of the form are dynamically enabled or disabled based on what the user selects from some of the form fields. Whenever I disable a form field, I also clear any value it currently has in it.
I have a model class representing the form. To load the form, I use form.loadRecord(model). To update my model when the user "submits" the form, I use model.set(form.getValues()).
The problem is that ext's getValues() implementation skips form fields that are disabled. This causes problems in my case, because some of form fields that have changed values are disabled (ie. form fields whose values I cleared when I disabled them). As a result, these fields are not updated (cleared) in the model when I call model.set(...).
What would be the best way to work around this problem? I've considered the following ideas, but none seems very good. If you have a better one, I'd like to hear it.
Clear the model (set all fields to undefined) before calling model.setValues(). Unfortunately, there is no model.clear() method, so this gets ugly quickly - I have to get all fields and iterate over them, clearing each one individually.
Clear model fields also when I disable and clear the form fields. This seems to violate separation of concerns and also means the model gets changed, even when the user chooses to cancel and not submit the form.
Override ext's implementation of form.getValues() to not skip disabled fields. This is even more ugly because the actual code that needs to be changed is in the Ext.form.field.Field class, not Ext.form.Basic.
Disabled fields are commonly (not only extjs) always excluded from post data. Instead set fields readonly. The mean difference between readonly and disabled fields is just that.
This is the solution that you exposed in the thrid point:
The only way you have to change this behaviour is override this method.
Ext.override('Ext.form.field.Field', {
getSubmitData: function() {
var me = this,
data = null;
if (!me.isFileUpload()) {
data = {};
data[me.getName()] = '' + me.getValue();
}
return data;
}
});
About your first point, isnĀ“t .reject(false) useful?
The latest option could be override the getSubmitData for every single field in your form as follow:
{
xtype: 'textfield',
getSubmitData: this.getSubmitDataMyOwnVersion
}
I realize this is an old post but I have run into this same issue. IMHO this is a rather serious issue because it can cause data problems without you knowing about it. In my case I also set several check boxes to false when disabled but because of the way this works they were being left as true behind the scenes.
As a work around I now loop through all the fields in the form and manually update the record for each one. It's more work but I don't have to override any classes and the loop is generic enough that it will continue to work if/when the form definition is changed.
var fields = this.getForm().getForm().getFields();
var record = this.getForm().getRecord();
for (var i = 0; i < fields.length; i++) {
var name = fields.items[i].name;
var value = fields.items[i].value;
record.set(name, value);
}
Note that Mark Wagoner's answer breaks any advanced components / features of other components since it takes the value directly rather then getting the getSubmitValue(). I had to slightly modify Iontivero's answer as that was not the class I found Extjs calling at least in 4.2.0.
Ext.define('YourAppName.override.Field', {
override: 'Ext.form.field.Base',
getSubmitData: function() {
var me = this,
data = null,
val;
if (me.submitValue && !me.isFileUpload()) {
val = me.getSubmitValue();
if (val !== null) {
data = {};
data[me.getName()] = val;
}
}
return data;
}
});
then in Ext.application:
requires: ['YourAppName.override.Field'],
I haven't encountered that problem so far, but I update my model using the update() method rather than the setValue(). Maybe it handles disabled fields differently? Or maybe I'm headed down a path to need this answer as well since we're just starting major testing? -- This is the basic usage of the Form.update method though, assuming form is an Ext.form.Panel and this.record is a model instance:
//Save record
form.updateRecord(this.record);
this.record.save();
this.record.commit();
If that doesn't work for you, I would suggest writing a similarly named method and extending the form panel to include it that gets the array of values then goes through each one and updates it only if it's not null.

Categories

Resources