Knockout Extension Issue - Better Solution? - javascript

I'm new to knockout, and still learning how best to work with it. I have a few input fields in an app which are tied to a bunch of calculations that update in real time. The fields on their own work great, and all is fine...
EXCEPT, I need to format the input as the user enters it, for display only (the raw data must be retained for the calculations, but 3 should appear as 3% or in another field 3000000 should appear as 3,000,000 etc.). I have this somewhat working, but I think there's a major flaw with my solution as the result is consistently buggy and it's possible to break the input field entirely.
So, an example of one of the input fields, which ties to another field to always equal 100%:
<input id='sm' data-bind='textInput: s_smixe' readonly='true'>
Is bound to:
self.s_smixebase = ko.observable(30);
self.s_smixe = ko.pureComputed({
read: function(){
return this.s_smixebase();
},
write: function(value){
if (parseFloat(value)<100) {
var otherValue = 100 - parseFloat(value);
this.s_smixebase(value);
this.s_rmixebase(otherValue);
} else {
value = 100;
this.s_smixebase(value);
this.s_rmixebase(0);
}
},
owner: this
}).extend({percent:{}});
self.s_smixeraw = self.s_smixe.raw;
Which is then extended by:
ko.extenders.percent = function(target) {
var raw = ko.observable();
var result = ko.computed({
read: function() {
var value = target();
if (value.toString().indexOf('%')===-1){
raw(parseFloat(value));
value = value + '%';
return value;
} else {
value = value.replace('%','');
raw(parseFloat(value));
value = value + '%';
return value;
}
},
write: target
}).extend({notify:'always'});
result.raw = raw;
return result;
};
So, what happens here, is that the first character input by the user formats correctly, the second character input by the user disappears, and the third joins the first and formats correctly. This happens the same if the field is computed or a regular observable, and the computed code is working fine without the extension applied. So to input 77% you would have to type 7 - X - 7 (where X can be any value since it gets lost to the process somewhere).
It should also be noted that I am using a virtual javascript numeric keyboard in this app so I am adding values via javascript (though this has not affected any of the other functionality, so I'm not sure why it would here).
Can anyone offer suggestions on what I'm doing wrong? What am I missing that is causing the input to be so buggy? I'm really determined not to ditch this notion of real-time input formatting as it makes for much cleaner presentation, but I if I have to I'll just format on blur.
Thanks in advance for any suggestions.

Because it's tricky to position the cursor properly when the formatting function replaces what you're typing as you type, I'd recommend having a field that has two modes: one where you're typing in it, and the other where it's displaying the formatted value. Which displays depends on cursor focus.
<div data-bind="with:pctInput">
<label>Value</label>
<input class="activeInput" data-bind='textInput: base, event:{blur:toggle}, visible:editing, hasFocus:editing' />
<input data-bind='textInput: formatted, event:{focus:toggle}, visible:!editing()' readonly='true' />
</div>
A working example is here:
http://jsfiddle.net/q473mu4w/1/

So, for anyone who comes across this later, I ended up using a modified version of #RoyJ 's solution from the thread mentioned in the initial comments. I do need to come up with a way to make this scale if I'm ever going to use it in larger projects, but it's sufficient for something with a small number of inputs. Also, in my case there are many formatted fields calculating their values based on the inputs, hence the multPercent and multNumber computed values. I wanted to ensure that all the inputs were carrying over properly to calculations. Here's a sample of the code with a working jsfiddle below:
<input data-bind="textInput:textPercent" />
<div data-bind="text:multPercent"></div>
<input data-bind="textInput:textNumber" />
<div data-bind="text:multNumber"></div>
and the accompanying javascript:
function dataBindings() {
var self = this;
self.percent = function(str){
var splice = str.toString().replace('%','');
splice = splice + '%';
return splice;
};
self.number = function(numStr){
var formatted;
if (Number(numStr) % 1) {
var integer = numStr.toString().replace(/\.\d+/g,'');
var decimal = numStr.toString().replace(/\d+\./g,'');
integer = integer.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,"); //add comma formatting
formatted = integer + '.' + decimal;
console.log('formatted = '+formatted);
return formatted;
} else {
formatted = numStr.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, "$1,");
return formatted;
}
};
self.displayPercent = ko.observable('5%');
self.rawPercent = ko.observable(5);
self.formattedPercent = ko.computed({
read: function() {
return self.displayPercent();
},
write: function(newValue) {
if (newValue==='') {
newValue = 0;
self.rawPercent(0);
var f = self.percent(newValue);
self.displayPercent(f);
} else {
if (newValue.charAt(0)==='0') {
newValue = newValue.slice(1);
}
self.rawPercent(parseFloat(newValue.toString().replace('%','')));
var f = self.percent(newValue);
self.displayPercent(f);
}
}
});
self.displayNumber = ko.observable('3,000');
self.rawNumber = ko.observable(3000);
self.formattedNumber = ko.computed({
read: function(){
return self.displayNumber();
},
write: function(newValue) {
if (newValue==='') {
newValue = 0;
self.rawNumber(0);
self.displayNumber('0');
} else {
if (newValue.charAt(0)==='0') {
newValue = newValue.slice(1);
}
newValue = newValue.replace(/(,)+/g,'');
self.rawNumber(parseFloat(newValue));
var n = self.number(newValue);
self.displayNumber(n);
}
}
});
self.multPercent = ko.computed(function(){
return self.percent(self.rawPercent() * self.rawPercent());
});
self.multNumber = ko.computed(function(){
return self.number(self.rawNumber() * self.rawNumber());
});
return {
textPercent: self.formattedPercent,
multPercent: self.multPercent,
textNumber: self.formattedNumber,
multNumber: self.multNumber
};
}
ko.applyBindings(new dataBindings());
http://jsfiddle.net/jschevling/mwbzp55t/

Related

How can I get the value of two inputs, calculate them and put the result in another?

I need to refactor this code to improve performance. I take the value of two inputs, and I need to calculate them and display the result in another:
var price;
var percentage;
var value;
function total() {
value = ((price* (percentage/ 100)) + parseFloat(price));
$("#Total").val(value.toFixed(2));
}
$("#price").keyup(function () {
price = $(this).val();
total()
});
$("#percentage").keyup(function () {
percentage = $(this).val();
total()
});
You shouldn't use global variables like this. They could cause race conditions.
// this self executing function will prevent the *Field variables to be bound to your browsers' window variable.
(function() {
let priceField = $("#price");
let percentageField = $("#percentage");
let totalField = $("#Total");
function total() {
let price = priceField.val();
let percentage = percentageField.val();
let value = (price * (percentage/ 100)) + parseFloat(price);
totalField.val(value.toFixed(2));
}
priceField.keyup(function () {
total()
});
percentageField.keyup(function () {
total()
});
})()
I have to say this is a solution for your question, but it's not the nicest one out there. But based on my guess, this solution needs to be simple.
A few extra tips would be to selectively search for input fields like $("input#price") to prevent any other potential collisions, although an id should be unique.
I also would suggest to add some protection in the code. If anybody entered some nonnumeric values, what should happen? Should they be stripped from the input before the calculations are made, or should they trigger an error to the user stating that the user's input is not valid?

directive $formatters affect ngModel when writing

I have a problem to use $formatters.
My goal is to hide phone number, just leave the last 4 chars visible.
It's ok if you don't write anything in the input.
If you write something, the model is affected by the mask and I register the hidden phone in DB ...
Here's the directive I use:
.directive('tsHideField', function () {
return {
require: 'ngModel',
restrict: 'A',
link: function (scope, element, attributes, controller) {
var maskValue = function (value) {
if (!value) {
return "";
}
if (value.length <= 4) {
return value;
}
var valueHide = "";
if (value.indexOf('#') === -1) {
//leave last 4 chars
valueHide = value.toString().substring(0, value.length - 4).replace(/[\S]/g, "\u2022");
return valueHide + value.toString().substring(value.length - 4);
} else {
//Adresse email, on laisse après le # et on cache tout sauf les 4 dernières lettre avant
//'lambertjer#gmail.com'.substring(0,'lambertjer#gmail.com'.indexOf('#') - 4).replace(/[\S]/g, "\u2022") + 'lambertjer#gmail.com'.substring('lambertjer#gmail.com'.indexOf('#') - 4)
valueHide = value.toString().substring(0, value.indexOf('#') - 4).replace(/[\S]/g, "\u2022");
return valueHide + value.toString().substring(value.indexOf('#') - 4);
}
// replace all characters with the mask character
//return (value || "").replace(/[\S]/g, "\u2022");
}
/** SI ON VEUT EGALEMENT CACHER A L ECRIT:
*
* var createMaskedInputElement = function() {
if (! maskedInputElement || ! maskedInputElement.length) {
maskedInputElement = element.clone(true);
maskedInputElement.attr("type", "password"); // ensure the value is masked
maskedInputElement.removeAttr("name"); // ensure the password save prompt won't show
maskedInputElement.removeAttr("core.application.main.directive.mask"); // ensure an infinite loop of clones isn't created
maskedInputElement.bind("blur", function() {
element.removeClass("ng-hide");
maskedInputElement.remove();
maskedInputElement = null;
});
$compile(maskedInputElement)(scope);
element.after(maskedInputElement);
}
};
element.bind("focus", function() {
createMaskedInputElement();
element.addClass("ng-hide");
maskedInputElement[0].focus();
});
*/
controller.$formatters.push(function (value) {
return maskValue(value);
});
}
};
});
And for your facility, here's a fiddle with a little implementation:
http://jsfiddle.net/nqp4qtLk/2/
How to prevent model to be affected by the mask ??
EDIT: I adapt the answer of Gr3g to match to my requirements
see the updated fiddle: Updated fiddle
Please see my EDITED fiddles :
If you don't allow *'s to be deleted :
Fiddle
If you allow *'s to be deleted :
Punker
Note :
If you allow *'s to be deleted, you will see in the plunker I do not allow following :
- Deleting star(s) when number(s) are visible.
- Adding a number between 2 stars or at the first position.
Code has grown up so I can only show you partial code here.
Obviously, you needed the $parsers pipeline :
controller.$parsers.push(function(val){
//Modify your value
return modifiedValue || val;
});
Notice i added 2 functions in each pipeline so I can access a String in the function where I need to modify the value. I don't have to care (too much) about casts.
controller.$parsers.unshift(function(val){
return String(val);
});
You can probably make it faster, but be careful when refactoring to think about all possibilities to handle. Especially when *'s can be deleted.
I don't think you can, imagine i go between 2 points and delete one, how will you do ?
You should use 2 differents components : one to type each character, the other showing the phone number with only 4 last displayed.
The hardest possible way : handle all key event on the input yourself so you could even resolve what i said in the beginning of my post.
You can use $parsers.push to control value to be saved in the model.
var unmask = function(value) {
var original = scope.vm.phone.toString();
var last4 = value.substring(value.length-4);
var newstr = original.substring(0, original.length-4);
return (newstr+last4);
// you can have whatever logic you want, to manipulate the original value
}
controller.$parsers.push(function (value) {
return unmask(value);
// or do what ever you want.
});
Updated fiddle- http://jsfiddle.net/anh9y8d9/3/

JQuery - populate two inputs with a percentage multiple of another input on change

jquery novice here so please resist the urge to punish my ignorance. I am attempting to auto calculate and fill the value of two inputs on change of another. One should be 120% of the original. The other should be 140% of the original. Where am i going wrong here:
jQuery(document).ready(function({
jQuery("#jform_listprice").on("change", function() {
var val = +this.value || 0;
var result_low = +val * 1.20.val();
var result_high = +val * 1.40.val();
jQuery("#jform_newprice").val(result_high.toFixed(2));
jQuery("#jform_usedprice").val(result_low.toFixed(2));
});
});
jQuery(document).ready(function(){
jQuery("#jform_listprice").on("change", function() {
var val = $(this).val();
if(isNaN(val)) return; // Leave now if value is not a number
var result_low = val * 1.20,
result_high = val * 1.40;
jQuery("#jform_newprice").val(result_high.toFixed(2));
jQuery("#jform_usedprice").val(result_low.toFixed(2));
});
});
Actually you don't need to typecast an integer or a float, JS tries to convert it as soon there's an arithmetic operation involved. If it fails to do so, the result will be NaN. Also .val() is a jQuery method, not a JS native one which you can call on a Number object.

Memorize random pick from Table

I'm using this method for random picking a cell from a Table:
Math.floor(Math.random() * example.length)
and it works without a problem.
But I need to memorize my choice so I could pick again that cell later in my project.
Is there any method to do this thing?
edit:
I'm using this method in a test script:
lvwPrivilegestable().click(
atCell(atRow((int)Math.floor(Math.random() * orderTable.getRowCount())),
atColumn((int)Math.floor(Math.random() * orderTable.getColumnCount()))));
So I could click at a cell in a table when I playback my test. And I want to click at the same cells later.
Answer:
It seems that camus was right. Passing the result to a variable and then reading it afterwards resolved my problem. I thought it would be a little more complicated.
edit:
Now, is there a method to pick at random a different cell everytime?
Because using my method there is a chance to pick the same cells sometimes.
You could build a small utility object which can remember random numbers:
function replayRandom() {
var results = [];
var replay = false;
return {
replay: function() {
replay = true;
},
next: function() {
if (!replay) {
val = Math.random();
results.push(val);
return val;
}
val = results[0];
results.shift();
return val;
}
};
}
Example:
r = replayRandom();
r.next();
0.7939797908670404
>>> r.next();
0.6103413074215163
>>> r.replay();
>>> r.next();
0.7939797908670404
>>> r.next();
0.6103413074215163

Javascript Math Functions

How can I use these JavaScript math functions ?
For example, I want to compute the square of all <input> values in a form, without submiting the form.
Can you give a little example? Thank you.
JQuery doesn't need to support math functions as it is an addon library for Javascript, you can still use Javascript in your JQuery code, so you can still use all the native math functions.
Examples:
Addition
var x = 1;
var y = 2;
var lol = x+y;
alert(lol);
Subtraction
var x = 10;
var y = 1;
var lol = x-y;
alert(lol);
Edit: Now we understand your question a little better...
<input type="text" id="field1" value="16" />
<input type="text" id="field2" value="25" />
<input type="text" id="field3" value="36" />
var field1Value = document.getElementById("field1").value;
var field2Value = document.getElementById("field2").value;
var field3Value = document.getElementById("field3").value;
alert(Math.sqrt(field1Value ));
alert(Math.PI * field2Value);
alert(Math.sin(field3Value));
You can act on each individual input using an each()(docs) loop.
Click here to test a working example. (jsFiddle)
$('a.square').click(function() {
$('#myform :text').each(function() {
this.value *= this.value;
});
});
$('a.square_root').click(function() {
$('#myform :text').each(function() {
this.value = Math.sqrt(this.value);
});
});
When either link is clicked, it finds all the text inputs in myform and iterates over them.
Inside the each function, this refers to the current input element.
JavaScript is the programming language, not jQuery, which is a library for web application programming written in JavaScript. To effectively use jQuery, you need to know JavaScript.
It is, however, possible to use jQuery's functionality to easily work with multiple textboxes at once:
// Set each single-line textbox's value to the square
// of its numeric value, if its value is in fact a number.
$('input:text').each(function() {
var num = +this.value;
if(!isNaN(num)) {
this.value = num * num; // or Math.pow(num, 2)
}
});
It would be quite useful if jQuery had a reduce() function.
When dealing with lists of data, most functional languages, and indeed most traditional languages these days, have methods that perform a repetitive function over the entire list, taking each element in turn and applying a function to it.
The simplest of these is map, which jQuery implements for you. This takes a list and applies a function to each element and returns the list of results, one result per entry in the list. eg. [1,2,3] -> (map x2) -> [2,4,6].
Sometimes you want a total or collective result from a list, rather than a list of individual mappings. This is where the reduce (or fold) operation comes in. Unfortunately jQuery does not have this method available as standard, so below is a plugin for it. A reduce function takes an accumulator value and the value of the current element, and returns the modified accumulator, which will be passed on to the next call. eg. [1,2,3,4] -> (reduce + [initial:0]) -> 10 = ( ( ( (0 + 1) + 2 ) + 3 ) + 4 ) or ([1,2,3,4] -> (reduce * [initial:1]) -> 24 = ( ( ( (1 * 1) * 2 ) * 3 ) * 4 ).
(function($) {
$.reduce = function(arr, callback, initial) {
var accumulator = initial || 0;
$.each(arr, function(index, value) {
accumulator = callback(accumulator, value, index);
});
return accumulator;
}
})(jQuery);
Then you can use it like this to get a sum of squares:
var answer = $.reduce($('input:text'), function(acc, elem) {
var cVal = $(elem).val();
return acc + cVal * cVal;
}, 0);
i was looking for a solution too , and i saw a lot of questions here that doesn't work (even this one) in case someone wondering like me , here is my working solutiuon :
$("#apport").keyup(
function(){
var apport = parseFloat($("#apport").val());
var montant = parseFloat($("#montant-financer").val());
var moinmontant = parseFloat(montant) - parseFloat(apport);
$("#montant-financer").val(moinmontant);
}
);
All the id's selector are input
Use the jquery map function to create an array
$('input:text').map(function() {
return this.value * this.value; // math calculation goes here
}).get();
See a live example
Looking at the initial question that was posted, it clearly states compute the square of all values in a form, without submiting the form.
i think keyup would be the best solution.
$("input").keyup(function () {
var value = $(this).val();
var x=value*value;
$("p").text(x);
}).keyup();
Click here to check the working example.
http://jsfiddle.net/informativejavascript/Sfdsj/3/
For more details visit http://informativejavascript.blogspot.nl/

Categories