Knockout.js: Custom binding resetting the value - javascript

In the following code the idea is that if you fill in 'test' that this is considered invalid and shouldn't write the value to the observable. The problem I have is that my custom binding is somehow resetting the observable's value.
A working version of the code below: http://jsfiddle.net/6hcpM/1/
var underlying = ko.observable('foo');
var errors = ko.observableArray()
var isValid = ko.dependentObservable(function(){
return errors().length === 0
});
var vm = {
name : ko.dependentObservable({
read : underlying,
write : function(value){
errors([]);
if(value==='test'){
errors([ 'Cant be test matey' ]);
};
if(isValid()){
underlying(value);
};
}
})
};
vm.name.isValid = isValid;
ko.bindingHandlers.validateCss = {
update: function(element, valueAccessor) {
observable = valueAccessor();
observable.isValid(); //this somehow causes the problem
}
};
ko.applyBindings(vm);

Your validateCss binding creates a dependency to vm.name.isValid. When you write "test" to the field, errors is updated, which causes isValid to update, which triggers all of the bindings in the input's data-bind attribute to be re-evaluated. So, the value binding gets evaluated again and replaces the current value with the value from name (which is stored in underlying).
So, basically the value binding is running again based on isValid changing.
There are several ways that you could handle it. You could let underlying get updated every time and, if necessary, store a clean value elsewhere that only gets updated when it is valid.
You could also put your custom binding on a different element. For example, you could wrap your input in a span or div and put the binding on it. I assume that you want the validateCss binding to assign a CSS class when the value is invalid.

Related

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.

How to find the sender or source which causes the ko.computable to trigger

I am working on Typescript and KnockoutJs. I am trying to get the sender for knockout computable function. Basically i have two controls, if either of the control's value gets changed then my knockout computable will be called,
self.userValue = ko.computed(() => {
if(self.control1.value())
{
}
if(self.control2.value())
{
}
});
It is working fine whenever either of the control's value changes it is calling. But i need to know due to which control's (among the two) value change, made the computable to gets called. I have checked for any sender like property for ko.computable but no use. Please guide me.
You can use subscription and call a function with shared code:
function update(sender, newVal) {
// some shared logic
}
self.control1.value.subscribe((newVal) => {
// you can add here some specific logic for control1
update(self.control1, newVal); // may be pass some additional params
});
self.control2.value.subscribe((newVal) => {
// you can add here some specific logic for control2
update(self.control2, newVal); // may be pass some additional params
});

I want Ember.js' {{input value="searchTerm"}} to set its value to ' ' when the URL changes

I'll try to simplify this problem as much as possible,
but I need my handlebar.js' {{input value="searchTerm"}} to set the value of searchTerm to 'null' every time the URL changes.
The input described above is meant to auto filter a list on the same page. Typing in the input automatically updates the value of 'searchTerm' to whatever is typed
The value searchTerm is defined in this action named searchResults
This is a property in the controller:
searchResults: function() {
var searchTerm = this.get('searchTerm');
var regExp = new RegExp(searchTerm, 'i');
// We use `this.filter` because the contents of `this` is an array of objects
var filteredResults = this.filter(function(category) {
return regExp.test(category.get('categoryName'));
});
return filteredResults;
}.property('#each.categoryName', 'searchTerm'),
So to break down what I need:
Regardless of what the current value of searchTerm is, it needs to be set to ' '.
Setting the value of searchTerm to ' ' only occurs when the page transitions
This can be done on the Route via didTransition or willTransition event handlers. Routes have a reference to their controller (via this.controller), and you could still get it though controllerFor (e.g.: var c = this.controllerFor('controllerName'); so you could do something like this:
App.SomeRouteThatErasesTheSearchTermRoute = Ember.Route.extend({
actions: {
// you could implement with didTransition. No arguments for didTransition tho
didTransition: function() {
this.controller.set('searchTerm', '');
},
// OR, implement with willTransition
willTransition: function(transition) {
this.controller.set('searchTerm', '');
}
}
});
You could also implement it as a separate method that gets fired on('willTransition'), so you keep any existing handler and just append yours. Then you could make it into a mixin for other routes to use or other routes can inherit from this class without breaking any other custom transition handler, if any.

ExtJs, custom TextField's getValue() function not being called when form is submitted

I have a custom text field that extends Ext.form.TextField (ExtJs 3.1.1). I have overridden getValue to submit a calculated value instead of the raw value in the superclass. When calling getValue directly the return value is correct. However when submitting the parent form, getValue is not being called at all (debug messages in the getValue method do not appear in the console). How can I override the value returned when the form is submitted? Isn't getValue the correct method to override?
Here's the general layout of the class (I can't provide the actual code):
MyField = Ext.extend(Ext.form.Textfield, {
constructor: function(config) {
[initialize some basic variables....]
MyField.superclass.constructor.call(this, config);
},
initComponent : function() {
this.on('keypress', this.handler, this);
},
handler : function(field, event, args) {
[set an internal value this.myValue based on superclass value]
},
getValue : function () {return this.myValue;}
});
The above is working fine in terms of calculating myValue and returning it when getValue is called. However when added in a form, the value in the Ext.form.TextField is being returned, and I have also noticed that MyField.getValue is not being called at all when FormPanel.getForm().submit() is called on the parent FormPanel object.
I don't think you can achieve what you're trying to do without overriding a lot more behavior.
If you debug into the submit action, you'll see the following in Ext.form.Action.Submit:
Ext.Ajax.request(Ext.apply(this.createCallback(o), {
form:this.form.el.dom,
url:this.getUrl(isGet),
method: method,
headers: o.headers,
params:!isGet ? this.getParams() : null,
isUpload: this.form.fileUpload
}));
Note that it passes the actual form DOM element (i.e. this.form.el.dom) which would contain your field's actual value, not your calculated value.
If you continue debugging, you'll see that within the call to Ext.Ajax.request the form parameter gets serialized via Ext.lib.Ajax.serializeForm(form). The resulting serialized value is passed into Ext.lib.Ajax.request as the data parameter. That value is what is actually sent to the server.

Knockout js Computed not being fired

The code can be found on http://jsfiddle.net/6kMWM/10/.
In the FilterViewModel I am creating an observable object.
var FilterViewModel= ko.observable({
Name: ko.observable("test"),
Code: ko.observable("test"),
Number: ko.observable("test")
});
Then in the BankViewModel I am running a computed method which when any of the input boxes change it should fire.
var BankViewModel = function(){
var self = this;
self.Collection = ko.observableArray([]),
self.filteredCollection = ko.computed(function () {
var filter = FilterViewModel();
alert("invoked");
}),
self.add = function (bankObject) {
self.Collection.push(bankObject);
},
self.isSelected = function (data) {
$('.bank').css('background-color', 'white');
$('.bank p').css('color', '#333');
$('#bank-row-' + data.Code()).css('background-color', 'blue');
$('#bank-row-' + data.Code()+" p").css('color', 'white');
}
};
For some reason it is not being fired. Can any one help me out please.
Thank-you in advanced
There are several problems with your fiddle:
You bind to values instead of observables. When you write <input
type="text" data-bind="value: global.filterViewModel().Name()"
placeholder="Filter by Name"/> ko uses the value of global.filterViewModel().Name not the observable. Thus there is no real binding (updating ko will not update the interface, updating the interface will not update ko). You need to remove the last parenthesis: global.filterViewModel().Name
You put Name instead of Code and vice versa in the binding
You subscribed to FilterViewModel's changes, but not it's child observable changes. To do this, include the evaluation of the child observables in your computed observable:
-
self.filteredCollection = ko.computed(function () {
var filter = FilterViewModel();
if (filter.Name() != 'testname')
alert("name");
if (filter.Code() != 'testcode')
alert("code");
if (filter.Number() != 'testnumber')
alert("number");
}),
You can test here http://jsfiddle.net/b37tu/1/
You need to instantiate your view model with a statement like this:
var model = new BankViewModel();
When the model is instantiated, its computed methods are evaluated initially. This is where your alert will fire.
But, i assume you want your computed method to subscribe to Name, Code and Number properties. In this case, you need to read these properties at least once in your computed method.
This is how dependency tracking works in KO. It records all the observables that you mention in your computed function and logs them. And your computed is evaluated again when one of those observables are updated.
For your code up there, your computed will subscribe to FilterViewModel but not to its individual properties Name, Code and Number. So if you need to subscribe for the changes in these individual properties, you have to mention them individually in your computed function. Well, it wouldn't make sense to have your computed to subscribe to them if they don't affect your computed function anyway.
If you want to learn how the process works, please take a look at its documentation:
http://knockoutjs.com/documentation/computedObservables.html

Categories

Resources