How to keep cents in a Angular math function - javascript

I have a math function that adds two input fields and puts the value in a third. I also have a filter that adds the cents to the input. you will see in the plunkr that the filter is not being applied to the Revised Contract. Original Contract + Total CO = Revised Contract.
plunkr
$scope.$watch('currentItem.JobOriginalContract -- currentItem.JobTotalCO', function (value) {
if (!$scope.currentItem) $scope.currentItem = {};
$scope.currentItem.JobRevisedContract = value;
});
$scope.$watch('currentItem.JobRevisedContract * .85 ', function (value) {
if (!$scope.currentItem) $scope.currentItem = {};
$scope.currentItem.JobOriginalBudget = value;
});

$parsers run only when you change the ngmodel value from the DOM (ex:- entering in the input box). When you programatically change the value (as you do for JobRevisedContract and JobOriginalBudget) it is $formatters that run. So you would need to format in formatters.
Example:-
ctrl.$formatters.unshift(function (a) {
return getFormattedValue(ctrl.$modelValue);
});
ctrl.$parsers.unshift(function (viewValue) {
var plainNumber = viewValue.replace(/[^\d|\-+]/g, '');
elem.val(getFormattedValue(plainNumber));
return plainNumber;
});
function getFormattedValue(value){
return $filter(attrs.format)(value/100,2);
}
Demo

Related

How to pass a value to javascript?

I'm working currently with Highcharts, I'm trying to create a function that use the data I write in a number field and present it as a new point in the chart once I click on add point, the add point showing in the link is to add point randomly to the chart.
http://jsfiddle.net/BlackLabel/2kw1b5o0/
<div id="container"></div>
<input type="number" id="add" name="new-point" value="">
<button id="add-point">Add point</button>
<script>
var chart = Highcharts.chart('container', {
series: [{
data: [1, 2, 3, 4, 5]
}]
});
function getValue(add) {
ext = document.getElementById(id).value; //value of the text input
alert(text);
return false;
}
var x= document.getElementsByClassName("new-point").value;
document.getElementById('add-point').addEventListener('click', function() {
chart.series[0].addPoint();
});
There were a few issues with your code :
Your getValue function needs the name of the field as a parameter and that parameter will be used in the document.getElementById() function to retrieve the fields you want to read data from
You need to return the read value by using the return keyword
Your chart expects a number. So, you need to parse read with Number.parseFloat()
Then, your event handler needs to call the getValue function and provided the name of the field :
function getValue(fieldName) {
let value = document.getElementById(fieldName).value; //value of the text input
return Number.parseFloat(value);
}
document.getElementById('add-point').addEventListener('click', function() {
let value = getValue("add");
chart.series[0].addPoint(value);
});
Updated JSFiddle
Just get the value of input as you are doing in getValue function. You can either return the value from that function like
function getValue() {
var val= document.getElementById("add").value,
return val;
}
and now you can use it like
document.getElementById('add-point').addEventListener('click', function() {
var val = getValue();
chart.series[0].addPoint(val);
});
Or you can directly access the value in you click handler as
var el = document.getElementById("add")
document.getElementById('add-point').addEventListener('click', function() {
chart.series[0].addPoint(el.value);
});
Here is your updated fiddle
document.getElementById(id).value returns a String value, and addPoint is expecting an Int, try this:
document.getElementById('add-point').addEventListener('click', function() {
var point = document.getElementById('add').value;
chart.series[0].addPoint(parseInt(point));
});
From HighChart's documentation, addPoint doesn't accept parameters of type String : https://api.highcharts.com/class-reference/Highcharts.Series#addPoint
Since the value of the input returns a String, you need to parse it to a float or a number :
function getValue(id) {
let inputValue = document.getElementById(id).value;
return parseFloat(inputValue);
}
Now you can call getValue in your EventListener :
chart.series[0].addPoint(getValue('add'));

Ensure Observable Array is populated before Filter runs

Problem: When My filter method is executing that is used to display an Html element, my observable array has not been populated yet by my web-api call resulting in nothing to filter from.
My Solution :
If I place an alert right before my filter method execution, everything seems to be working.
Length of my observable array is 0 right before my alert call whereas it has a value after my alert.
Question: How can I ensure my array is populated without placing an alert.
This issue is occurring on multiple Pages, placing an alert before my Html is rendered makes everything works fine.
I have a simple Observable Array and a Filter Method on it.
Part of my Knockout Code :
self.currentVendorSupport = ko.observable(new VendorContact());
//Populates Observable Array - allManufactures
self.allManufacturers = ko.observableArray([]);
$.getJSON(serviceRoot + '/api/Manufacturer', function (data) {
var mappedManufacturers = $.map(data, function (item) {
return new Manufacturer(manID = item.manID, name = item.name);
});
self.allManufacturers(mappedManufacturers);
});
//Filters allManufacturers
self.GetCurrentVendor = function () {
alert('allManufacturerLength value again:' + allManufacturerLength);
return ko.utils.arrayFirst(self.allManufacturers(), function (item) {
return item.manID === self.currentVendorSupport().manID();
});
}
It seems to be working.
It is not working with arrayFilter though, is it because of return type difference between the two, wrong syntax or something else?
self.GetCurrentManufacturer = ko.computed(function () {
if (self.allManufacturers().length > 0)
{
return ko.utils.arrayFilter(self.allManufacturers(), function (item)
{
return item.manufacturerID ===
self.currentVendorSupport().manufacturerID() });
}
else return new Manufacturer(0, '...');
}, self);
Html Code:
<label class="control-label readOnlyLabel" data-bind="text: GetCurrentVendor().name"></label>
You can simply make GetCurrentVendor a computedObservable instead so that you can conditionally show a value based on the observable array length. Since it is computed it would react on changes made to the array and update its value.
You can even make it pureComputed so it is only ever activated/computed when called.
For example the computedObservable currentVendor would show "..." when the array is empty and the filtered name when the array is populated.
Computed:
self.currentVendor = ko.computed(function () {
if(this.allManufacturers().length > 0) {
return ko.utils.arrayFirst(this.allManufacturers(), function (item) {
return item.manID === this.currentVendorSupport().manID();
}).name;
} else {
return '...'
}
}, this)
HTML:
<label class="control-label readOnlyLabel" data-bind="text: currentVendor"></label>
Right now, your code is written such that GetCurrentVendor is called only once, by the HTML. And obviously it's called too soon and doesn't get updated afterwards. This is exactly what an observable is for, so that the HTML gets updated when the JS values get updated. So try this:
JS
self.currentVendorSupport = ko.observable(new VendorContact());
//Populates Observable Array - allManufactures
self.allManufacturers = ko.observableArray([]);
//New observable, initially empty
self.currentVendorName = ko.observable();
$.getJSON(serviceRoot + '/api/Manufacturer', function (data) {
var mappedManufacturers = $.map(data, function (item) {
return new Manufacturer(manID = item.manID, name = item.name);
});
self.allManufacturers(mappedManufacturers);
//updated value after api call is complete
self.currentVendorName(self.GetCurrentVendor().name);
});
//Filters allManufacturers
self.GetCurrentVendor = function () {
//alert('allManufacturerLength value again:' + allManufacturerLength);
return ko.utils.arrayFirst(self.allManufacturers(), function (item) {
return item.manID === self.currentVendorSupport().manID();
});
}
HTML
//this automatically updates when a new value is available
<label class="control-label readOnlyLabel" data-bind="text: currentVendorName"></label>

Revert change with Knockoutjs and bindingHandlers using computed

Let's say I have something like <input type="text" data-bind="format: quantity">, that uses an custom bindingHandlers called format defined like this:
var getElementValue = function($element) {
return $element.val().replace(/\$/g, '');
};
ko.bindingHandlers.format = {
init: function(element, bindingAccessor) {
var $element = $(element),
bindings = bindingAccessor();
$element.val('$' + ko.unwrap(bindings));
$element.change(function () {
bindings(getElementValue($element));
});
},
update: function(element, bindingAccessor) {
var $element = $(element),
bindings = bindingAccessor();
$element.val('$' + ko.unwrap(bindings));
}
};
And a view model like:
var ViewModel = function() {
var self = this;
self._quantity = ko.observable(0);
self.quantity = ko.computed({
read: self._quantity,
write: function(newValue) {
if (newValue < 0) {
self._quantity.valueHasMutated();
console.log('quantity is invalid');
return;
}
self._quantity(newValue);
}
});
}
Since negative quantity is not allowed, the idea would be to revert the input to it previous value if that is provided.
However, the self._quantity.valueHasMutated(); in the write function is not notifying the bindingHandlers update of a mutation.
Any ideas? I have a JSFiddle setup for more details.
The simplest solution is to tell knockout to react to changes also when the old value and the new value are the same, by using .extend({ notify: 'always' }) for both, the "backup" observable, and the computed to reflect the changes in the text input:
self._quantity = ko.observable(0).extend({ notify: 'always' });
self.quantity = ko.computed({
read: self._quantity,
write: function(newValue) {
if (newValue < 0) {
self._quantity(self._quantity.peek());
//self._quantity.valueHasMutated();
console.log('quantity is invalid');
return;
}
self._quantity(newValue);
}
}).extend({ notify: 'always' });
Fiddle: http://jsfiddle.net/k0deqt8x/
EDIT:
BTW, this solution also works when using ko.options.deferUpdates = true;
Alternatively, you cold use a more standard solution like the one created by rniemeyer here: http://www.knockmeout.net/2011/03/guard-your-model-accept-or-cancel-edits.html
I'm not exactly sure why exactly your code doesn't work, but I did try some stuff and came up with something that does...
Since your input is bound to quantity, it'd make sense to send out a message that this value changed. You can do so by calling notifySubscribers on the computed:
self.quantity.notifySubscribers(self.quantity.peek());
This still does feel a bit strange, and I'd suggest looking into an extender like the ones in these examples.
Here's an updated fiddle: http://jsfiddle.net/pxxnuu2z/

set variable value to jquery message + jquery.validate

How to get value of var total in message,
and also i tried declare inside function but it gives undefined variable
var total = '';
$.validator.addMethod("valueNotEquals", function (value, element, arg) {
var fund_old = $("#current_fund").val();
var fund_new = $("#new_fund").val();
total = parseFloat(9999999999.999999) - parseFloat(fund_old);
if (parseFloat(fund_new) <= parseFloat(total)) {
return true;
} else {
return false;
}
return true;
}, 'sry sktiman' + total + 'is remaining value');
In result i am getting blank value of total
According to the documentation for jQuery.validator.addMethod() you can use jQuery.validator.format() which generates a function that receives the arguments to the validation method and returns a string message, so creating a function that ignores any arguments and returns a string should work:
var total = '';
$.validator.addMethod("valueNotEquals", function (value, element, arg) {
var fund_old = $("#current_fund").val();
var fund_new = $("#new_fund").val();
total = parseFloat(9999999999.999999) - parseFloat(fund_old);
if (parseFloat(fund_new) <= parseFloat(total)) {
return true;
} else {
return false;
}
return true;
}, function() {return 'sry sktiman' + total + 'is remaining value'});
EDIT
The fiddle for this solution can be found here (thanks to Sparky for providing the code).
The optional parameters can be used inside the message.
The third argument, the optional parameters, (you call it arg) can be represented within your code as arg[0], arg[1], etc.
Then the corresponding values can be used in your message as {0}, {1}, etc.
Do your calculation external to .addMethod() and pass the value in using the arg argument.
$.validator.addMethod("valueNotEquals", function(value, element, arg){
var fund_old= $("#current_fund").val();
var fund_new =$("#new_fund").val();
// do this calculation where the rule is declared. See 'validate()'
//total = parseFloat(9999999999.999999) - parseFloat(fund_old);
if(parseFloat(fund_new) <= parseFloat(total)){
return true;
} else {
return false;
}
return true;
},'sry sktiman {0} is remaining value');
Since you didn't show your .validate() or the HTML markup, maybe something like this...
$('#myform').validate({
rules: {
fund_old: {
// your rules
},
fund_new: {
valueNotEquals: function() {
return ($parseFloat(9999999999.999999) - parseFloat($("#current_fund").val()));
}
}
}
});
Crude demo: http://jsfiddle.net/eZm7x/
Just add this line function() {return 'sry sktiman' + total + 'is remaining value'} as your second argument, you can easily use this variable
var total = '';
$.validator.addMethod("valueNotEquals", function (value, element, arg) {
//Your default code here
}, function() {return 'sry sktiman' + total + 'is remaining value'});
Try,
var total = '';
$.validator.addMethod("valueNotEquals", function(value, element, arg){
var fund_old= $("#current_fund").val();
var fund_new =$("#new_fund").val();
total = parseFloat(9999999999.999999) - parseFloat(fund_old);
if(parseFloat(fund_new) <= parseFloat(total)){
return true;
}else{
return false;
}
return true;
},'sry sktiman'+ (parseFloat(9999999999.999999) - parseFloat($("#current_fund").val())) + 'is remaining value');
In addMethod Documentation says: "Add a custom validation method. It must consist of a name (must be a legal javascript identifier), a javascript based function and a default string message."
So, you are adding a custom validation function to plugin, and NOT executing that function.
Try to set a field with your custom validation and fill that field to run your custom function. Just AFTER that check total var value.

Unique value validation with Knockout-Validation plugin

I'm using KnockoutJS with the Knockout-Validation plugin to validate fields on a form. I'm having problems validating that a value is unique using the native validation rule - unique
I'm using the Editor Pattern from Ryan Niemeyer to allow the user to edit or create a Location. Here's my fiddle to see my problem in its entirety.
function Location(data, names) {
var self = this;
self.id = data.id;
self.name = ko.observable().extend({ unique: { collection: names }});
// other properties
self.errors = ko.validation.group(self);
// update method left out for brevity
}
function ViewModel() {
var self = this;
self.locations = ko.observableArray([]);
self.selectedLocation = ko.observable();
self.selectedLocationForEditing = ko.observable();
self.names = ko.computed(function(){
return ko.utils.arrayMap(self.locations(), function(item) {
return item.name();
});
});
self.edit = function(item) {
self.selectedLocation(item);
self.selectedLocationForEditing(new Location(ko.toJS(item), self.types));
};
self.cancel = function() {
self.selectedLocation(null);
self.selectedLocationForEditing(null);
};
self.update = function(item) {
var selected = self.selectedLocation(),
updated = ko.toJS(self.selectedLocationForEditing()); //get a clean copy
if(item.errors().length == 0) {
selected.update(updated);
self.cancel();
}
else
alert("Error");
};
self.locations(ko.utils.arrayMap(seedData, function(item) {
return new Location(item, self.types, self.names());
}));
}
I'm having an issue though. Since the Location being edited is "detached" from the locations observableArray (see Location.edit method), when I make changes to name in the detached Location that value isn't updated in the names computed array. So when the validation rule compares it to the names array it will always return a valid state of true since the counter will only ever be 1 or 0. (Please see knockout-validation algorithm below)
Within the options argument for the unique validation rule I can pass in a property for externalValue. If this value is not undefined then it will check to see if the count of matched names is greater or equal to 1 instead of 2. This works except for cases when the user changes the name, goes on to another field, and then goes back to the name and wants to change it back to the original value. The rule just sees that the value already exists in the names array and returns a valid state of false.
Here is the algorithm from knockout.validation.js that handles the unique rule...
function (val, options) {
var c = utils.getValue(options.collection),
external = utils.getValue(options.externalValue),
counter = 0;
if (!val || !c) { return true; }
ko.utils.arrayFilter(ko.utils.unwrapObservable(c), function (item) {
if (val === (options.valueAccessor ? options.valueAccessor(item) : item)) { counter++; }
});
// if value is external even 1 same value in collection means the value is not unique
return counter < (external !== undefined && val !== external ? 1 : 2);
}
I've thought about using this as a base to create a custom validation rule but I keep getting stuck on how to handle the situation when the user wants go back to the original value.
I appreciate any and all help.
One possible solution is to not include the name of the currently edit item (of course when creating a new item you need the full list) in the unique validator.
So the unique check won't be triggered when changing the Location name back to its original value:
self.namesExceptCurrent = function(name){
return ko.utils.arrayMap(self.locations(), function(item) {
if (item.name() !== name)
return item.name();
});
}
self.edit = function(item) {
self.selectedLocation(item);
self.selectedLocationForEditing(
new Location(ko.toJS(item),
self.types,
self.namesExceptCurrent(item.name())));
};
Demo JSFiddle.

Categories