I am formatting a input into currency and using writable computed variables to update the value back to textbox.
e.g. I have a value 1234.12
I am copying the value from notepad and pasting it into the textbox, on tab out it is hitting the read function and getting formatted into currency and getting written back to textbox as $1,234.
When I am pasting same value 1234, its is not hitting the read, directly getting written as it is, in to the textbox as 1234 on tab out.
I have seen this problem in js also.
Do you have any idea how to format the value if I paste the same value multiple times.
You can use the { notify: "always" } extender to ensure your data always gets formatted.
In the example below:
The _backingValue contains "1234"
When inputing "1234" in the <input>, the computed writes "1234" to the _backingValue
Under normal conditions, the _backingValue would not notify any subscribers of a value change, since "1234" === "1234. However, because we explicitly told it to always trigger a valueHasMutated "event", it does notify its subscribers.
formattedValue's read property has a dependency on _backingValue.
Because _backingValue notifies us it has changed, the formatting function will run again, outputting "1234$".
Under normal conditions, formattedValue would not notify any subscribers of a value change, since "1234$" === "1234$". Again however, because of the extension, a valueHasMutated is triggered.
The <input>'s value binding receives an update and renders "1234$" to the screen.
const _backingValue = ko.observable("1234")
.extend({ notify: "always" });
const formattedValue = ko.computed({
read: () => _backingValue().replace(/\$/g, "") + "$",
write: _backingValue
}).extend({ notify: "always" });
ko.applyBindings({ formattedValue });
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<input data-bind="value: formattedValue">
Related
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.
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.
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.
On a project I'm working on, a number of checkboxes in a view are used to apply and remove a set of filters from a search result set. The values of these checkboxes are written to the query string to facilitate the ability to pre-load a filter state via navigation. To simplify, I'll pretend there is only one checkbox and ignore the fact that there's supposed to be some filtering.
To ensure the state in the query string is correct, when a check-box is checked, a function is called which writes the checkbox's value to the query string. The checkbox's value is bound to a scope property called "checkBoxValue".
<input ng-model="checkBoxValue"
ng-change="WriteValueToQueryString()"
type="checkbox"/>
In addition to this, when there is an update to the query string, I call a function which gets the current value from the query string and writes it to $scope.checkBoxValue. It ensures that the value in my controller and the value in the query string are in sync with one another, regardless of where a change in value originates. The altogether simplified version of this all looks like this:
app = angular.module 'myApp', []
app.controller 'MyCtrl', ['$scope', '$location', ($scope, $location) ->
$scope.GetValueFromQueryString = () ->
queryStringValue = $location.search()['checkbox'] or false
#making sure I write a true/false and not "true"/"false"
$scope.checkBoxValue = queryStringValue is true or queryStringValue is 'true'
$scope.WriteValueToQueryString = () ->
$location.search 'checkbox', $scope.checkBoxValue.toString()
$scope.$on '$locationChangeSuccess', () ->
$scope.GetValueFromQueryString()
]
When I check the box, it changes checkBoxValue, updates the value in the query string, and everything is just lovely. If I press the "Back" button in my browser, it notices that the location has changed, updates its value to match the query string (efectively undoing my last), and everything is just lovely.
...Unless I'm using Internet Explorer 10 or 11...
In IE10 and IE11, the browser "Back" action doesn't appear to do anything. In reality, it is performing a browser "Back" action (when I look at it, the query string reads as it should after a browser "Back" action). The problem seems to be that $locationChangeSuccess never fires. The value is thus never retrieved from the query string, which means that my checkbox's actual value is never updated from the query string. Eventually, if you press "Back" enough, it just boots you off to whatever actual page you were on before you got to my own page, checkbox still in the state it was before you started hitting "Back".
Funny thing is? IE8 and IE9 have no problems with this.
Link to CodePen (try it in IE10 or IE11): http://codepen.io/anon/pen/zaGwt/
Thanks for reading my ramblings for this long. Any help you could give on this issue would be tremendously appreciated!
Things I've tried:
$scope.$watch ( () -> $location.search() ), () ->
$scope.GetValueFromQueryString()
Which appears to behave identically to what's listed above. No dice.
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.