Update form record for disabled fields - javascript

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.

Related

KnockoutJS Binding not updating in checkbox click event

I'm running into an issue with KnockoutJS where it appears the data-bindings aren't updating as expected.
In my view model, I have an array of objects with an Enabled property that I initialize to True, and have that property bound to enable on a checkbox with a click handler CheckItems:
self.Answers(data.filter(function (step) {
if (step.Type == 0) {
step.Enabled = true;
return true;
}
else {
return false;
}
}));
...
<ul data-bind="foreach: $root.Answers">
<input type="checkbox" data-bind="click: $root.CheckItems, ..., enable: Enabled>
</ul>
Everything work fine and dandy, until the CheckItems handler runs and I try to set the Enabled property to false:
self.CheckItems = function (step) {
...
self.Answers().forEach(function (option) {
<call my handler>.done(function (data) {
option.Enabled = data;
}
);
});
// EDIT: added valueHasMutated() call here
self.Answers.valueHasMutated();
return true;
}
When I hit return true, I can inspect the self.Answers() object and it shows that the answers that should be disabled have Enabled = false (correctly), but once we're the handler, none of the checkboxes are disabled, and clicking on any other checkbox present and going through the handler shows that the Enabled property seems to have been reset to True. I triple checked and made sure there's nothing else touching the Enabled property anywhere in the code between checkbox clicks.
The binding itself seems to be working, too, since when I switch the initial Enable set to false, all of the checkboxes are disabled.
I'm making changes to the self.Answers array various other places in the script as well, and those go through fine, when a change is made it goes into knockout-latest.debug.js and lands in the notifySubscribers function with the appropriate updates to make. Maybe there's something special about the click event for input fields in knockout?
Any ideas? Still fairly new to Knockout overall, but I thought this was the entire point, that I could update a property in the Answers() observable array and it would update in the corresponding UI.
Edit: based on comments I tried calling valueHasMutated() after the edits were made, but it still isn't updating properly.
Edit2: tried making a copy of the self.Answers array, doing the handler calls to update Enabled, then setting with self.Answers(newAnswers), and this had the same result.
I was advised that the issue was occurring because properties of the objects in CurrentAnswers() weren't observable as well. Since there weren't changes to the array itself, knockout wasn't registering that it had to rebind anything, which is why calling valueHasMutated() didn't change anything as the array looked exactly the same before and after CheckItems() from an array standpoint (also why updating with a copy of the array didn't work, same issue). When setting CurrentAnswers() to a blank array (self.CurrentAnswers([])), then setting it to the updated value, things changed properly since KO recognized the actual array changes.
The solution I ended up using was to utilize ko.mapping.fromJS() method to fill CurrentAnswers() with objects that had observable properties. After some syntax changes in the HTML and JS to properly reference the new observables, everything worked properly.

Detect When a object changes Angular 5

I'm trying to detect when ever an object changes. The object is connected to a large form. Whenever a user changes the input I would like it to have save/cancel buttons popup at the bottom of the page.
My idea was to just make a copy of the object and do *ngIf="object !== object_copy" and if they hit cancel set the data equal to the copied object. I don't know if this the proper way to do it since I will be using it twice as many variables for a small task, but I've only used angular for a short time. I can't get this method to work however because when ever I make a type copy the object losses it's type.
Can someone help me with this or figure out a better way to do this?
If you are using a Form, then you could take advantage of Angular's form control, which will tell you anytime a form and any of its values have been altered in any way. Then, you could do something as simple as:
form.dirty
or even specific fields. There are tons of things you can do with reactive and template forms from Angular.
https://angular.io/guide/forms
You have to subscribe an event to handle the change event:
constructor(private formBuilder: FormBuilder) {
this.myForm = formBuilder.group({
name: 'Jose Anibal Rodriguez',
age: 23
})
this.myForm.valueChanges.subscribe(data => {
console.log('Form changes', data);
})
}
It should works.
ReactiveForm supports the dirty property. You can use 'myForm.dirty' to check the dirty status of the form.
Otherwise, you can set the initial value of the form to an object property using the getRawValue() method
this.initailFormValue = this.myForm.getRawValue();
Then just subscribe the form changes using
myForm.valueChanges.subscribe((value) => {
this.updatedFormValue = this.myForm.getRawValue();
},
(err) => {
//
}
);
Now you have the initial and current form values. You can compare and do the remaining.

Updating ngModel with value returned from service

Being unhappy with the way Angular does form validation, I decided to implement my own, and have run into an issue that has honestly left me stumped.
My setup is as follows:
A directive is used to instantiate a new form.
Its controller accesses the relevant form schema, which is then used to generate fields in the view via ng-repeat.
These input & textarea fields are then bound to the controller via ng-model.
On field change or form submit, the controller sends the form data to a validation service which returns an error if applicable, itself then bound to the DOM.
I've run into an issue trying to implement a sanitation step before the validation in part 4. This sanitation step should in theory update the controller data with the return value from a service method, updating the DOM binding and allowing the validation step to use the updated value. Although the controller value itself is being updated, this change is not being reflected in the DOM.
The relevant code is as follows:
View:
<div ng-repeat="(field, value) in form.schema">
<!-- ... -->
<textarea ng-model="form.data[field]" ng-model-options="{ updateOn: 'blur' }" ng-change="form.changed(field)"></textarea>
<div class="message">{{ form.errors[field] }}</div>
</div>
Controller:
// Controller submit method
ctrl.submit = function () {
var err;
for (var field in ctrl.schema) {
ctrl.data[field] = validationService.sanitizeField(ctrl.data[field], ctrl.schema[field]);
ctrl.errors[field] = validationService.validateField(ctrl.data[field], ctrl.schema[field]);
if (ctrl.errors[field] !== undefined) {
err = true;
}
}
if (err) {
return;
}
// Proceed ...
Service:
// Public field sanitation method
var sanitizeField = function (value, schema) {
try {
// Try sanitation
}
catch (e) {
// Error
}
return value;
}
Logging the new ctrl.data[field] value in the controller after sanitation yields the correct result. This result is also being correctly passed to the subsequent validateField method. However, the new data value isn't being updated in the DOM.
At first, I figured it might be an issue with the scope not being applied, or an issue with promises. Updating the service & controller accordingly didn't solve the issue. I've also tried wrapping the sanitation return value in an object, to no avail.
Strangely enough, changing the return value in the service from the value variable to a primitive, e.g. 'test', updates the DOM on return.
Likewise, errors returned from the service validation method (also strings rather than a variable) are updated in the DOM accordingly.
Despite a decent amount of searching, I haven't been able to find anything concrete on the topic. Any insights would be much appreciated!
Solved!
Unbeknownst to me, Angular features an ngTrim directive which is automatically bound to input fields and is by default set to true [Documentation].
With this directive, data is automatically trimmed before being picked up by the controller on form submission - the trimming being performed by my sanitation service therefore wasn't changing the data, which in turn wouldn't be reflected in the DOM as Angular wasn't picking up any changes.
This behaviour can be mitigated by setting ng-trim="false" on relevant fields in your view.
Try doing the follwoing;
for (var field in ctrl.schema) {
ctrl.data[field] = angular.copy(validationService.sanitizeField(ctrl.data[field], ctrl.schema[field]));
ctrl.errors[field] = angular.copy(validationService.validateField(ctrl.data[field], ctrl.schema[field]));
if (ctrl.errors[field] !== undefined) {
err = true;
}
}
Angular is tricky when it comes to updating objects/arrays with nested properties. You can either use $scope.$watchCollection to make sure that the object/array has updated, or you can use angular.copy(), which will make sure that DOM updates.

Knockout - Copy an observableArray without tracking its depcencies

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
};

understand Backbone set method and Model

All, I am a newbie of Backbone. and I am trying to understand the Model of Backone. Especially how to define a Model. so far, I didn't saw a clear or formal way about how to define a Model for backbone.
For example Let's see the set method in help doc .
set
model.set(attributes, [options])
Set a hash of attributes (one or many) on the model.
Say we have some code like below . I think set method actually is assign a javascript object to the Model.
window.Employee = Backbone.Model.extend({
validate:function(attrs){
for(var key in attrs){
if(attrs[key] == ''){
return key + "can not be null";
}
if(key == 'age' && isNaN(attrs.age)){
return "age is numeric";
}
}
}
});
....
var attr = {}; // I can't not sure what is {} mean.
$('#emp-form input,#emp-form select').each(function(){
var input = $(this);//using jquery select input and select. and enumerate all of them.
attr[input.attr('name')] = input.val();//I am not sure what does it means
});
if(employee.set(attr)){
Employees.create(employee);
}
....
in this example ,I didn't saw the classical way which we can see in java class or c# class to define the class fields or methods. but only see a validate function .Is there anybody who can tell me more about it to help me understand? thanks.
To define a model in Backbone you have to extend the Backbone.Model object. For example if you'll like to create a new User model you could write something like this:
var User = Backbone.Model.extend({})
You can also overwrite some model methods to fill your needs. For example you can change the urlRoot attribute to tell the model where should he fetch the data.
Backbone models contain your data in the attributes attribute. You change those attributes by using the model set method and you can read the value stored in the model using the get method. So if you had some inputs where a user can enter information, for example creating a new user with his name and email and you have a form with a text input for both of them. You could do domething like this:
var user = new User;
user.set('name', $('#name').val());
user.set('email', $('#email').val());
attributes = {
name: user.get('name'),
email: user.get('email')
};
user.save(attributes);
There are a lot of ways to re-factor this code to make it look better but it help to see how you could use those methods. You should check the Backbone documentation to explore how they work a little bit more. Hope this helps!
PD: In my example I set an attribute a time, but you could also send a hash of attributes to set more values in one call.
The model in JS is basically a wrapper for data, with CRUD and simple validation functions. To work properly you need to make server functions to work with (ajax), I think this tutorial says it all http://backbonetutorials.com/what-is-a-model/. Instead of database the model works with your application server side.
If you have custom actions (not just add/edit/remove) on your data, you can manually "set()" data, use "onchange" event and refresh your view when needed. You can even attach "onchange" events only on specific fields and make custom functions in your view to handle each special field (for validation or display).
You can define fields at initialize and defaults value, but not custom functions (ofc you can do model.customFuntion() but I don't recommend it.
In order to make it more "clasical way" you need to use the other Backbone functions http://backbonejs.org/#Collection-Underscore-Methods and Backbone.Collection.

Categories

Resources