Backbone ModelBinder and jQuery Plugin - javascript

In an application written with Backbone & Marionette, I want some of the form inputs as numeric only and with thousands separator. Backbone ModelBinder automatically detects changes in the form. I implemented jQuery number plugin which works fine. But the problem is that when there is a thousands separator in a numeric input, ModelBinder doesn't work. When there is less than 4 digits (without a separator everything is ok.
The problem occurs on Chrome. There isn't any problem on Firefox.
I don't have any clue how to solve or debug the problem.

You're asking for trouble by combining the two: model binder triggers change events when inputs change and forwards the fields data to the model. Except it has been tampered with by the number plugin, so issues arise.
Instead, try using ModelBinder's converter binding settings (https://github.com/theironcook/Backbone.ModelBinder#formatting-and-converting-values), it will allow you to defined how data should be formatted/parsed when going from the model to the form and back.

Use ModelBinder's converters for this instead of the jQuery plug-in. Here's an example that cleans up a time (e.g. 3p, 3:00, 3PM, 15:00, 1500, etc.), stores the data in the model in a canonical form (time portion of ISO8601) if the input can be parsed, and if not then it is stored as is so that validation can check it separately and provide an error.
ModelBinder's converters get called twice when a user changes an input. First when the input data is copied from the view to the model, direction === 'ViewToModel'. This allows for cleanup to occur and to transform the input value into a canonical form suited for storage, e.g. in 24-hour time with seconds (15:30:00) in this example. And secondly, from the model back to the view, direction === 'ModelToView', which allows you to present the data to the user in a friendly manner, in 12-hour time (3:30 PM) in this example.
This example uses a time library to manipulate the time input, parse it, and format it.
Binding
In this case onRender is called right after the view is rendered using Backbone.Marionette
onRender: function() {
var bindings = ModelBinder.createDefaultBindings(this.$el, 'name');
bindings.value.converter = ModelSaver.timeStringConverter;
this.modelbinder.bind(this.model, this.$el, bindings);
}
Converter
ModelSaver.timeStringConverter = function(direction, value, attributeName, model, els) {
var result;
if (direction === 'ViewToModel') {
if (!value)
// if the input is empty, just pass it along so it can be deleted, and/or validation can indicate it was a required field
result = value;
else {
// parse the input value and store it with the second component only if it's valid, if not, store the invalid value so that model validation can catch it
result = new Time(value);
result = result.isValid() ? result.format('HH:mm')+':00' : value;
}
}
if (direction === 'ModelToView') {
// chop off the seconds, parse, check for validity, and format
result = value && new Time(value.substr(0,5));
result = (value && result.isValid()) ? result.format('h:mm AM') : value;
}
return result;
};

Related

How do I detect whether or not an input with type=time has any values entered

I have a javascript script that's supposed to detect whenever an html form input with type="time" has any value entered.
However, whenever I enter a partial value (for instance, type one number, instead of a full time with AM/PM), it doesn't detect the input as having a value.
In the below example, timeSelector is the input with type="time".
if (timeSelector.value == "") {
timeSelector.classList.add("empty");
} else {
timeSelector.classList.remove("empty");
}
Is there any way to detect this type of thing?
To clarify, since apparently I didn't ask my question clearly enough, I need to detect when a time input has something entered, even if that something is an invalid or incomplete input.
Well the problem with html5 inputs is they do not give the text in the input if it is not valid. So you can use checkValidity when the user removes focus from the element.
var checkInput = function() {
var value = this.value
var isValid = this.checkValidity()
if (!this.value.length && isValid) {
console.log('empty');
} else if (!this.value.length && !isValid) {
console.log('invalid time entered')
} else {
console.log('valid time entered')
}
}
var input = document.querySelector("input")
input.addEventListener("input", checkInput)
input.addEventListener("blur", checkInput)
<input type="time" />
Per the specification on Input Elements with type time ( HTML Spec ) :
The value attribute, if specified and not empty, must have a value that is a valid time string.
If the value of the element is not a valid time string, then set it to the empty string instead.
This means that input and change events don't occur until the entire time field has been filled out. Why? Because nothing really has changed.
You may think that you can circumvent this by using keydown or keyup events, but this is simply not the case.
The value is not changed and is therefore inaccessible until a full string that is capable of being parsed as a time is inside the time input box.
By filling in the below example you can see how the events fire. Notice the lack of value until everything is filled in.
let i = document.querySelector("input"),
on = type => i.addEventListener(type, function() { console.log(`${type}, value: ${i.value}`); });
on("keydown");
on("keyup");
on("change");
on("input");
<input type="time">
The only way to possibly get around the lack of a changing value is to set a default value as below:
let i = document.querySelector("input"),
on = type => i.addEventListener(type, function() { console.log(`${type}, value: ${i.value}`); });
on("change");
<input type="time" value="00:00">
However, with a default value there is a risk that the user will submit a time that isn't something that you'd likely want.
You could write some validation code to take care of this depending on the complexity of your functionality this may be possible.
Overall if this is something you need and the functionality is a bit more complicated than you think you can handle validating yourself, it would be best to either create your own time input interface from other input types, or to use a library or UI kit from a source that has already done the legwork.

Quotes Required in Google Script Function Parameters?

Running into a bit of a weird issue here. I'm still learning the ins and outs of writing functions for Google Apps, and my first real project is a "shipping dashboard" - basically, a spreadsheet where I can take a tracking number from UPS, FedEx, etc., and from it parse the carrier, generate a tracking link, that type of thing. I'm trying to set it up as a function where I can set the type of data being requested (say, carrier), a tracking number, and have it return said information. Here's where I'm at right now:
function trackingData(infoType,trackingNumber) {
//Regex for various carrier's tracking numbers.
var upsValue = /\b(1Z ?[0-9A-Z]{3} ?[0-9A-Z]{3} ?[0-9A-Z]{2} ?[0-9A-Z]{4} ?[0-9A-Z]{3} ?[0-9A-Z]|[\dT]\d\d\d ?\d\d\d\d ?\d\d\d)\b/i;
var fedexValue = /(\b96\d{20}\b)|(\b\d{15}\b)|(\b\d{12}\b)/;
var uspsValue = /\b(91\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d|91\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d ?\d\d\d\d)\b/i;
//First option: we want to know the carrier we're shipping with.
if (infoType == 'carrier') {
if (upsValue.test(trackingNumber)) {
return 'UPS';
}
else if (fedexValue.test(trackingNumber)) {
return 'FedEx';
}
else if (uspsValue.test(trackingNumber)) {
return 'USPS';
}
else return null;
}
The issue comes when passing a value for infoType - if I reference a cell, or set each infoType as a variable and put in a value directly when calling the formula it works just fine. However, if I call it by putting in a cell:
=infoType(carrier,trackingNumber)
I get:
error: Unknown range name carrier
The weird thing is it DOES work if I call it with:
=infoType("carrier",trackingNumber)
(note the quotes around "carrier").
I've looked all over the place to find a solution to keep from having to put the quotes around the formula when it is called but so far haven't had any luck. Anyone have ideas?
error: Unknown range name carrier
That error message is due to the way that the Spreadsheet is interpreting the function arguments.
A function parameter is either a reference or a data element (number, date, string). References can be to a single cell (A1) or range (A1..A23), to a range on a different sheet (Sheet2!A1), or a named range. When you entered carrier without quotes, it was interpreted as a named range. Read Named and protected ranges if you're interested.
Here's a little hint from the preamble text with the Google spreadsheets function list:
... don't forget to add quotation marks around all function components made of alphabetic characters that aren't referring to cells or columns.
Summary - if you want an argument to be passed as a string, put quotes on it.

CRM 2011 - Set field's 'requiredlevel' to REQUIRED based on another field's value

I am working on the Opportunity form in CRM 2011.
I have 2 fields I am working with: azdb_payment1type & new_payment1cclast4orcheckgc
azdb_payment1type has option set values:
Visa = 807,370,000
Mastercard = 807,370,001
American Express = 807,370,002
Discover = 807,370,003
Check = 807,370,004
Cash = 807,370,005
Credit Rollover = 807,370,006
IF the value of this field (azdb_payment1type) is 807,370,004 or less, I'd like to REQUIRE that the new_paymentcclast4orcheckgc field is filled out.
I created a function for the azdb_payment1type field called "requireCClast_onchange", then added the below code to the form's main library:
function requireCClast_onchange() {
var payment1type = Xrm.Page.getAttribute('azdb_payment1type').getValue();
alert(payment1type);
if (payment1type <= '807,370,004') {
Xrm.Page.getAttribute("new_payment1cclast4orcheckgc").setRequiredLevel("required");
}
}
With the code inserted as onchange, nothing happens when I select a CC, then tab off the field. If I change the function to onsave, then edit the library with the changes, it DOES return the alert I set up in the code, but it doesn't change the Requirement Level from "Business Recommended" to "Required".
Is this even possible with Javascript?
I would have a look at this line. payment1type <= '807,370,004'
You are comparing a number to a string, I'm not sure have JavaScript will resolve that but I can't imagine its the way you would want.
I believe you should be doing, payment1type <= 807370004

Knockout Can I get around this computed loop?

I have the following structure for a bunch of objects in my viewmodel
I have an underlying array filled with objects that have ko.observable items in.
Ex: selections = [{Legs:{'0':ko.observable(12)}}, {Legs:{'0':ko.observable(0)}}]
What I am trying to achieve is that when a user clicks on a checkbox, that should toggle the Selected value of that Runner. Now when that happens I would also like to update the Cache value to reflect the Runners selected state
Cache is used as binary storage 12 == 1100 == Checkboxes 3 and 4 are checked
Now all of that I can get to work no problem, I obviously don't need to even make Cache observable.
But, I also have a need to programatically change the Cache values, and I would like the checkboxes to reflect these changes automatically.
What is below sorta works, but creates a Loop, which knockout gracefully handles, but its results are unreliable and this slows things down.
How can I create this binding setup?
function Runner(name, odds, race, leg, post) {
var runner = {
Name: name,
Odds: odds,
Post: post,
Race: race,
Leg: leg,
Cache: selections[race].Legs[leg],
Selected: ko.observable(false),
Enabled: ko.observable(true),
Valid: true
};
runner.Check = ko.computed(function() {
if (!this.Enabled.peek() || !this.Valid ) return;
var checked = this.Selected();
var cache = this.Cache();
if (checked) {
this.Cache(cache | 1 << this.Post);
} else {
this.Cache(cache & ~(1 << this.Post));
}
}, runner);
return runner;
}
Edit
<input type="checkbox" data-bind="checked: Selected, enable: Enabled"/>
I had a moment of clarity after writing my question. But I think its a good question none the less so rather than changing or removing my question ill just post my newest solution and get some critique hopefully.
So in the end I forgo the Selected value entirely
Note The this.Post + 1 is specific to my needs, its not needed normally, I simply wish to leave the first bit unused for future use.
runner.Check = ko.computed({
read: function() {
var cache = ko.utils.unwrapObservable(this.Cache); //Edit 1
return cache & 1 << (this.Post + 1);
},
write:function(value) {
var cache = this.Cache();
if (!this.Enabled.peek() || !this.Valid || this.Post === -1) return;
var mask = 1 << (this.Post+1);
if(value === !(cache & mask)){ //Edit 2
this.Cache(value ? cache | mask : cache & ~mask);
}
}
}, runner);
One bad thing about doing things this way is that if I have 20 runners who all use the same Cache, then when a user selects 1 of them all 20 will re-check themselves...
For my specific case a future change may be removing the Peek on Enabled, and performing a check that says if !Enabled then turn that bit off by default rather than possibly allowing a Disabled Checked checkbox.
Edit
Changed 'read' function to use unwrapObservable() in case the Cache is cleared by ways of the observable being deleted/removed elsewhere.
Edit 2
While answering a comment in the original question I realized that to help prevent some redundant calls I could add a check to see if the bit's value is already equal to value and if so do nothing, so if programatically I try to turn on a bit that is already on then it won't fire the computed since nothing has actually changed.

Using a funcCall that retrieves multiple fields

Ello, I'm using jQuery-Validation-Engine and i don't know if this is currently possible but, Is there a way for you to possibly use the funcCall in a senario such as this.
I have to check a date range using 2 textboxes. I want to bind funcCall[dateRangeCheck] in such a way that I can get access
to 2 fields as opposed to just the one. The end result would would something like this.
dateRangeCheck = function (fields, rules, i, options) {
if (isDate(fields[0]) && isDate(fields[1])) {
if (!dateCompare(fields[0], fields[1])) {
return "* Invalid Date Range";
}
}
};
Or possibly all the fields that a utilizing this particular funcCall like this
dateRangeCheck = function (fields, rules, i, options) {
for (var i=0;i<fields.length;i++)
if(!isDate(fields[i]){
return "* Invalid Date Range";
}
}
};
Is there some way to accomplish this?
Well now jQuery-validation-engine does support date range checking. Just made the commit myself waiting for it to be reviewed. lol, But no to that second example as of now.
Here is the source. Have a nice day.
Update
The latest version does support group validation.

Categories

Resources