Is it good a good practice to stringify array? - javascript

Let's say I have an Array consist of Boolean.
myArray = [false,false,false]
And I will need to do different stuff according to every status.
ex :
[false,true,false], [false,false,true], [true,true,false], ...
and so on.
Usually this kind of operation is done by checking each individual element to see fit.
ex:
if(!myArray[0] && myArray[1] && !myArray[2]) do A
if(!myArray[0] && !myArray[1] && myArray[2]) do B
if(myArray[0] && myArray[1] && !myArray[2]) do C
...
Instead of doing that. I convert the array into String:
switch(JSON.stringify(myArray))
case : "[false,true,false]"
do A
break
case : "[false,false,true]"
do B
break
case : "[true,true,false]"
do C
break
......
Is this considered good practice? Is there any hidden error I might face with this method?
Will this method increase readability ?

About Using JSON.stringify
The use of JSON.stringify will work fine for arrays (of arrays (of arrays...)) of (JSON-compatible) primitives, i.e. boolean, string, number, null. There is no ambiguity. The ECMAScript specification also is clear about the "gap" to produce between distinct values: it should be the empty string, unless explicitly defined differently via the optional argument. See ECMAScript Language Specification on the topic.
One caveat is that undefined is not a thing in JSON, and so [null] and [undefined] would both be stringified to "[null]". But since you are only using false and true, this is not an issue.
Now to your questions:
Is this considered good practice?
That is a matter of opinion. My first intuition says "no", mainly because of the type conversion, but since there are no caveats for your particular use case (i.e. array of boolean), we cannot really have much to criticise about.
Is there any hidden error I might face with this method?
No
Will this method increase readability ?
Yes
There is however at least one alternative that has all the same advantages, but does not rely on some data type conversion:
Alternative: bitset
In case your array is an array of boolean, you can opt to use a numeric data type instead, where each binary bit of a number corresponds to a boolean in the array version.
It is to be expected that this method will be more efficient than the strinfigication option, because that data type conversion obviously takes some time.
Here is a run of the original array pattern:
let arr = [false, false, true];
if (!arr[0] && arr[1] && !arr[2]) console.log("do A");
if (!arr[0] && !arr[1] && arr[2]) console.log("do B");
if (arr[0] && arr[1] && !arr[2]) console.log("do C");
Here is the same logic implemented with a bitset:
let bitset = 0b001;
if (bitset === 0b010) console.log("do A");
if (bitset === 0b001) console.log("do B");
if (bitset === 0b110) console.log("do C");
If ever you need more bits than are available in the number data type (53 bits), then you can go for BigInt. For instance:
let bitset = 0b1010101010101010101010101010101010101010101010101010101n;
// A partial comparison
if ((bitset & 0b100010001n) === 0b100010001n) console.log("do A");
Of course, it is not necessary to use 0b literals; this is just to highlight the correspondence with the array version. You can save some characters by writing numbers in hexadecimal notation (0x) or even plain decimal, although the latter would be more obscure for the reader of your code.

Not much wrong with it per se, depends on the context; how many of these booleans are there, where do they come from / what sets them, how many cases, etc. Bad part could be that it can be very hard to understand what is happening and to debug, especially if you have 10+ values in there.
You could perhaps convert each boolean to a string/word that means something in that context, so you get a sentence. If you have a unique word for each boolean, you could leave out the false ones so you only have to write down certain combinations.
Also instead of JSON.stringify() you can use myArray.join(',') which outputs true,true,false (so without the brackets).

Your goal is to write a readable if?
Stringify is a solution, but (maybe only for me) it is a little bit strange.
Here is my advice. Write a function like this:
isEqual(left, right) {
if (left.length !== right.length) return false;
for (var i = 0; i < left.length; i++) {
if (left[i] !== right[i]) return false;
}
return true;
}
And use it like this:
if (isEqual(array, [true, false, true])) alert('isEqual');

Related

how many ways to check if x is integer and which one is most efficient?

I already checked what is the best way to check variable type in javascript
question on So but didn't found answer of my question.
In javascript how many ways to find if input type is integer? which one is efficient?
I was looking the way to find the integer in javascript and found number of ways to do this:
Using typeof
function isInteger(x)
{
return (typeof x === 'number') && (x % 1 === 0);
}
console.log(isInteger(10));
console.log(isInteger(10.1))
Using parseInt
function isInteger(x)
{
return parseInt(x, 10) === x;
}
console.log(isInteger(10));
console.log(isInteger(10.1));
Using Math.round
function isInteger(x)
{
return Math.round(x) === x;
}
console.log(isInteger(10));
console.log(isInteger(10.1));
Is there any other way to find the type of Integer and which one will be most efficient for consider small to large integer values.
The most intuitive one is Number.isInteger(), at least in my opinion
function isInteger(x)
{
return Number.isInteger(x);
}
console.log(isInteger(10)); // Output: True
console.log(isInteger(10.1)); // Output: False
Edit
As for efficiency, I created a benchmark on jsben.ch where I try all your methods and mine, you can see for yourself ;)
Link
I just tested the speed of each code at JSBEN.CH
typeof => 347 ms
parseInt => 338 ms
Math.round => 367 ms
Interestingly, parseInt is the fastest way!
It's only the speed comparison. Go with Number.isInteger(x) instead! It's the instinctive & fastest of all.
Your propositions seem fine, but let's add for completion the polyfill Mozilla puts forward :
Number.isInteger = Number.isInteger || function(value) {
return typeof value === 'number' &&
isFinite(value) &&
Math.floor(value) === value;
};
I am concluding answer for this question after some resarch
From ECMAscript 6 which introduces a new Number.isInteger() function for precisely this purpose.
However, prior to ECMAScript 6, this is a bit more complicated, since no equivalent of the Number.isInteger() method is provided.
The simplest and cleanest pre-ECMAScript-6 solution (which is also sufficiently robust to return false even if a non-numeric value such as a string or null is passed to the function) would be the following use of the bitwise XOR operator:
function isInteger(x)
{
return (x ^ 0) === x;
}
Others like Math.round() ,typeof and parseInt can be used also to find the Integer.
While this parseInt-based approach will work well for many values of x, once x becomes quite large, it will fail to work properly. The problem is that parseInt() coerces its first parameter to a string before parsing digits. Therefore, once the number becomes sufficiently large, its string representation will be presented in exponential form (e.g., 1e+21). Accordingly, parseInt() will then try to parse 1e+21, but will stop parsing when it reaches the e character and will therefore return a value of 1.

How to select first of several items in JSON that exists?

JSON files are compromised of a series of key's and values. I know the potential key's in a given JSON, but not whether or not they have corresponding non-empty values. I have loaded the JSON file into an object called JSON. I want to find the first of several possible key's with a value and then assign that value to a variable. When I say "first" I mean "first" according to a priority list that is not related to the structure of the JSON:
I could do the following and it works:
if(json.age)
myValue = json.age;
else if(json.classYear)
myValue = json.classYear;
else if(json.seniority)
myValue = json.seniority
else
myValue = false;
This works but sucks for several reasons:
It is slow to write
It is annoying to rewrite the key value name each twice in each row
It is a little hard to read
It is very difficult to reason with programatically. I don't have a use case that requires this, but I can imagine wanting to arbitrarily change the order of priority from within my code.
While not terribly slow to process, I can imagine that some other approach may compute faster.
These reasons lead me to believe that the method listed above is not ideal. Is there some other pattern that would be better?
(Note: I recognize that this question borders on a "how best to" as opposed to "how to" phrasing. I know SO is not wild about that sort of question and I don't mean my question to be interpreted as such. Rather, my question should be interpreted as asking, "is there some design pattern that is particularly suited for the problem describe above?)
(Note: I will only accept a vanilla answer, but feel free to provide other answers if you believe they will be helpful).
You could use short-circuit evaluation. You'll still have to write out all of the property names, but I'm not sure there's a way to accomplish this task without doing that.
const myValue = json.age || json.classYear || json.senority || false;
Okay, so if you have a one-dimensional hash table and an array for the priority of keys, then you can use an algorithm like this to select the first one available:
function grab(hash, keyPriority) {
var value;
keyPriority.some(function (key) {
if (hash.hasOwnProperty(key)) { // check if the property exists
value = hash[key];
return true; // break out of loop
}
});
return value;
}
usage:
grab({ c: 3, d: 4 }, ['a', 'b', 'c', 'd']) // 3
You can modify this to work by truthy values, or undefined/null by changing hash.hasOwnProperty(key) to hash[key] or hash[key] != null respectively.
If you are fine with using a bit of JQuery then the following code snippet should do the job I guess.
$.each(JSONObj, function(key, value){
if (!(value === "" || value === null)){
myValue = value;
return false; //to break the loop once a valid value is found
}
});
This will assign the first valid value to your variable myValue and will also exit the loop once a valid value is found.

Shorthand code for multiple "or" statements inside the if statement? [duplicate]

I have a group of strings in Javascript and I need to write a function that detects if another specific string belongs to this group or not.
What is the fastest way to achieve this? Is it alright to put the group of values into an array, and then write a function that searches through the array?
I think if I keep the values sorted and do a binary search, it should work fast enough. Or is there some other smart way of doing this, which can work faster?
Use a hash table, and do this:
// Initialise the set
mySet = {};
// Add to the set
mySet["some string value"] = true;
...
// Test if a value is in the set:
if (testValue in mySet) {
alert(testValue + " is in the set");
} else {
alert(testValue + " is not in the set");
}
You can use an object like so:
// prepare a mock-up object
setOfValues = {};
for (var i = 0; i < 100; i++)
setOfValues["example value " + i] = true;
// check for existence
if (setOfValues["example value 99"]); // true
if (setOfValues["example value 101"]); // undefined, essentially: false
This takes advantage of the fact that objects are implemented as associative arrays. How fast that is depends on your data and the JavaScript engine implementation, but you can do some performance testing easily to compare against other variants of doing it.
If a value can occur more than once in your set and the "how often" is important to you, you can also use an incrementing number in place of the boolean I used for my example.
A comment to the above mentioned hash solutions.
Actually the {} creates an object (also mentioned above) which can lead to some side-effects.
One of them is that your "hash" is already pre-populated with the default object methods.
So "toString" in setOfValues will be true (at least in Firefox).
You can prepend another character e.g. "." to your strings to work around this problem or use the Hash object provided by the "prototype" library.
Stumbled across this and realized the answers are out of date. In this day and age, you should not be implementing sets using hashtables except in corner cases. You should use sets.
For example:
> let set = new Set();
> set.add('red')
> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false
Refer to this SO post for more examples and discussion: Ways to create a Set in JavaScript?
A possible way, particularly efficient if the set is immutable, but is still usable with a variable set:
var haystack = "monday tuesday wednesday thursday friday saturday sunday";
var needle = "Friday";
if (haystack.indexOf(needle.toLowerCase()) >= 0) alert("Found!");
Of course, you might need to change the separator depending on the strings you have to put there...
A more robust variant can include bounds to ensure neither "day wed" nor "day" can match positively:
var haystack = "!monday!tuesday!wednesday!thursday!friday!saturday!sunday!";
var needle = "Friday";
if (haystack.indexOf('!' + needle.toLowerCase() + '!') >= 0) alert("Found!");
Might be not needed if the input is sure (eg. out of database, etc.).
I used that in a Greasemonkey script, with the advantage of using the haystack directly out of GM's storage.
Using a hash table might be a quicker option.
Whatever option you go for its definitely worth testing out its performance against the alternatives you consider.
Depends on how much values there are.
If there are a few values (less than 10 to 50), searching through the array may be ok. A hash table might be overkill.
If you have lots of values, a hash table is the best option. It requires less work than sorting the values and doing a binary search.
I know it is an old post. But to detect if a value is in a set of values we can manipulate through array indexOf() which searches and detects the present of the value
var myString="this is my large string set";
var myStr=myString.split(' ');
console.log('myStr contains "my" = '+ (myStr.indexOf('my')>=0));
console.log('myStr contains "your" = '+ (myStr.indexOf('your')>=0));
console.log('integer example : [1, 2, 5, 3] contains 5 = '+ ([1, 2, 5, 3].indexOf(5)>=0));
You can use ES6 includes.
var string = "The quick brown fox jumps over the lazy dog.",
substring = "lazy dog";
console.log(string.includes(substring));

How to shorten my conditional statements

I have a very long conditional statement like the following:
if(test.type == 'itema' || test.type == 'itemb' || test.type == 'itemc' || test.type == 'itemd'){
// do something.
}
I was wondering if I could refactor this expression/statement into a more concise form.
Any idea on how to achieve this?
Put your values into an array, and check if your item is in the array:
if ([1, 2, 3, 4].includes(test.type)) {
// Do something
}
If a browser you support doesn't have the Array#includes method, you can use this polyfill.
Short explanation of the ~ tilde shortcut:
Update: Since we now have the includes method, there's no point in using the ~ hack anymore. Just keeping this here for people that are interested in knowing how it works and/or have encountered it in other's code.
Instead of checking if the result of indexOf is >= 0, there is a nice little shortcut:
if ( ~[1, 2, 3, 4].indexOf(test.type) ) {
// Do something
}
Here is the fiddle: http://jsfiddle.net/HYJvK/
How does this work? If an item is found in the array, indexOf returns its index. If the item was not found, it'll return -1. Without getting into too much detail, the ~ is a bitwise NOT operator, which will return 0 only for -1.
I like using the ~ shortcut, since it's more succinct than doing a comparison on the return value. I wish JavaScript would have an in_array function that returns a Boolean directly (similar to PHP), but that's just wishful thinking (Update: it now does. It's called includes. See above). Note that jQuery's inArray, while sharing PHP's method signature, actually mimics the native indexOf functionality (which is useful in different cases, if the index is what you're truly after).
Important note: Using the tilde shortcut seems to be swathed in controversy, as some vehemently believe that the code is not clear enough and should be avoided at all costs (see the comments on this answer). If you share their sentiment, you should stick to the .indexOf(...) >= 0 solution.
A little longer explanation:
Integers in JavaScript are signed, which means that the left-most bit is reserved as the sign bit; a flag to indicate whether the number is positive or negative, with a 1 being negative.
Here are some sample positive numbers in 32-bit binary format:
1 : 00000000000000000000000000000001
2 : 00000000000000000000000000000010
3 : 00000000000000000000000000000011
15: 00000000000000000000000000001111
Now here are those same numbers, but negative:
-1 : 11111111111111111111111111111111
-2 : 11111111111111111111111111111110
-3 : 11111111111111111111111111111101
-15: 11111111111111111111111111110001
Why such weird combinations for the negative numbers? Simple. A negative number is simply the inverse of the positive number + 1; adding the negative number to the positive number should always yield 0.
To understand this, let's do some simple binary arithmetic.
Here is how we would add -1 to +1:
00000000000000000000000000000001 +1
+ 11111111111111111111111111111111 -1
-------------------------------------------
= 00000000000000000000000000000000 0
And here is how we would add -15 to +15:
00000000000000000000000000001111 +15
+ 11111111111111111111111111110001 -15
--------------------------------------------
= 00000000000000000000000000000000 0
How do we get those results? By doing regular addition, the way we were taught in school: you start at the right-most column, and you add up all the rows. If the sum is greater than the greatest single-digit number (which in decimal is 9, but in binary is 1) we carry the remainder over to the next column.
Now, as you'll notice, when adding a negative number to its positive number, the right-most column that is not all 0s will always have two 1s, which when added together will result in 2. The binary representation of two being 10, we carry the 1 to the next column, and put a 0 for the result in the first column. All other columns to the left have only one row with a 1, so the 1 carried over from the previous column will again add up to 2, which will then carry over... This process repeats itself till we get to the left-most column, where the 1 to be carried over has nowhere to go, so it overflows and gets lost, and we're left with 0s all across.
This system is called 2's Complement. You can read more about this here:
2's Complement Representation for Signed Integers.
Now that the crash course in 2's complement is over, you'll notice that -1 is the only number whose binary representation is 1's all across.
Using the ~ bitwise NOT operator, all the bits in a given number are inverted. The only way to get 0 back from inverting all the bits is if we started out with 1's all across.
So, all this was a long-winded way of saying that ~n will only return 0 if n is -1.
You can use switch statement with fall thru:
switch (test.type) {
case "itema":
case "itemb":
case "itemc":
case "itemd":
// do something
}
Using Science: you should do what idfah said and this for fastest speed while keep code short:
THIS IS FASTER THAN ~ Method
var x = test.type;
if (x == 'itema' ||
x == 'itemb' ||
x == 'itemc' ||
x == 'itemd') {
//do something
}
http://jsperf.com/if-statements-test-techsin
(Top set: Chrome, bottom set: Firefox)
Conclusion :
If possibilities are few and you know that certain ones are more likely to occur than you get maximum performance out if || ,switch fall through , and if(obj[keyval]).
If possibilities are many, and anyone of them could be the most occurring one, in other words, you can't know that which one is most likely to occur than you get most performance out of object lookup if(obj[keyval]) and regex if that fits.
http://jsperf.com/if-statements-test-techsin/12
i'll update if something new comes up.
If you are comparing to strings and there is a pattern, consider using regular expressions.
Otherwise, I suspect attempting to shorten it will just obfuscate your code. Consider simply wrapping the lines to make it pretty.
if (test.type == 'itema' ||
test.type == 'itemb' ||
test.type == 'itemc' ||
test.type == 'itemd') {
do something.
}
var possibilities = {
"itema": 1,
"itemb": 1,
"itemc": 1,
…};
if (test.type in possibilities) { … }
Using an object as an associative array is a pretty common thing, but since JavaScript doesn't have a native set you can use objects as cheap sets as well.
if( /^item[a-d]$/.test(test.type) ) { /* do something */ }
or if the items are not that uniform, then:
if( /^(itema|itemb|itemc|itemd)$/.test(test.type) ) { /* do something */ }
Excellent answers, but you could make the code far more readable by wrapping one of them in a function.
This is complex if statement, when you (or someone else) read the code in a years time, you will be scanning through to find the section to understand what is happening. A statement with this level of business logic will cause you to stumble for a few seconds at while you work out what you are testing. Where as code like this, will allow you to continue scanning.
if(CheckIfBusinessRuleIsTrue())
{
//Do Something
}
function CheckIfBusinessRuleIsTrue()
{
return (the best solution from previous posts here);
}
Name your function explicitly so it immediately obvious what you are testing and your code will be much easier to scan and understand.
You could put all the answers into a Javascript Set and then just call .contains() on the set.
You still have to declare all the contents, but the inline call will be shorter.
Something like:
var itemSet = new Set(["itema","itemb","itemc","itemd"]);
if( itemSet.contains( test.type ){}
One of my favorite ways of accomplishing this is with a library such as underscore.js...
var isItem = _.some(['itema','itemb','itemc','itemd'], function(item) {
return test.type === item;
});
if(isItem) {
// One of them was true
}
http://underscorejs.org/#some
another way or another awesome way i found is this...
if ('a' in oc(['a','b','c'])) { //dosomething }
function oc(a)
{
var o = {};
for(var i=0;i<a.length;i++) o[a[i]]='';
return o;
}
of course as you can see this takes things one step further and make them easy follow logic.
http://snook.ca/archives/javascript/testing_for_a_v
using operators such as ~ && || ((),()) ~~ is fine only if your code breaks later on. You won't know where to start. So readability is BIG.
if you must you could make it shorter.
('a' in oc(['a','b','c'])) && statement;
('a' in oc(['a','b','c'])) && (statements,statements);
('a' in oc(['a','b','c']))?statement:elseStatement;
('a' in oc(['a','b','c']))?(statements,statements):(elseStatements,elseStatements);
and if you want to do inverse
('a' in oc(['a','b','c'])) || statement;
Just use a switch statement instead of if statement:
switch (test.type) {
case "itema":case "itemb":case "itemc":case "itemd":
// do your process
case "other cases":...:
// do other processes
default:
// do processes when test.type does not meet your predictions.
}
Switch also works faster than comparing lots of conditionals within an if
For very long lists of strings, this idea would save a few characters (not saying I'd recommend it in real life, but it should work).
Choose a character that you know won't occur in your test.type, use it as a delimiter, stick them all into one long string and search that:
if ("/itema/itemb/itemc/itemd/".indexOf("/"+test.type+"/")>=0) {
// doSomething
}
If your strings happen to be further constrained, you could even omit the delimiters...
if ("itemaitembitemcitemd".indexOf(test.type)>=0) {
// doSomething
}
...but you'd have to be careful of false positives in that case (e.g. "embite" would match in that version)
For readability create a function for the test (yes, a one line function):
function isTypeDefined(test) {
return test.type == 'itema' ||
test.type == 'itemb' ||
test.type == 'itemc' ||
test.type == 'itemd';
}
then call it:
…
if (isTypeDefined(test)) {
…
}
...
I think there are 2 objectives when writing this kind of if condition.
brevity
readability
As such sometimes #1 might be the fastest, but I'll take #2 for easy maintenance later on. Depending on the scenario I will often opt for a variation of Walter's answer.
To start I have a globally available function as part of my existing library.
function isDefined(obj){
return (typeof(obj) != 'undefined');
}
and then when I actually want to run an if condition similar to yours I'd create an object with a list of the valid values:
var validOptions = {
"itema":1,
"itemb":1,
"itemc":1,
"itemd":1
};
if(isDefined(validOptions[test.type])){
//do something...
}
It isn't as quick as a switch/case statement and a bit more verbose than some of the other examples but I often get re-use of the object elsewhere in the code which can be quite handy.
Piggybacking on one of the jsperf samples made above I added this test and a variation to compare speeds. http://jsperf.com/if-statements-test-techsin/6 The most interesting thing I noted is that certain test combos in Firefox are much quicker than even Chrome.
This can be solved with a simple for loop:
test = {};
test.type = 'itema';
for(var i=['itema','itemb','itemc']; i[0]==test.type && [
(function() {
// do something
console.log('matched!');
})()
]; i.shift());
We use the first section of the for loop to initialize the arguments you wish to match, the second section to stop the for loop from running, and the third section to cause the loop to eventually exit.

Form handling and validation in pure JavaScript

My intention is to get your thoughts and criticism about the script below, as regards the algorithm's design, performance and cross-browser compatibility.
I have just started getting into JavaScript having missed out on its awesomeness for quite a while. My background and experience is in developing C/C++/PHP based RESTful backends.
In order to understand the language and the right way of using it, I decided to do something which I am sure has been done many times before. But learning to use a new language and paradigm often entails pain anyway.
This is my attempt to create a normal form processing and validation script/ function.
In order to reduce complexity and keep code simple/clean, I decided to use HTML5 Custom Data Attributes (data-*) to assign metadata for each element in the form:
Data-Required: True or False. If set to true, this parameter makes the form-field required and so it cannot be empty. A value set to false indicates that the field is optional. Default is false.>
Data-Type: Type of validation to be performed. Examples include 'email', 'password', 'numbers' or any other 'regexp'.
A fairy simple example of such a form would be:
<form action="postlistings" id="postlistings" enctype='multipart/form-data' method="post" class="postlistings">
<ul class="login-li">
<li>
<input class="title" name="title" type="title" id="title" data-required="true" data-type="title"></a>
</li>
<li>
<textarea name="body" id="elm1" class="elm1" name="elm1" data-type="body" data-required="true" >
</textarea>
</li>
<li>
<span class="nav-btn-question">Add Listing</span>
</li>
</ul>
</form>
Reminder: This is my first piece of JavaScript code.
The idea is to call Form while passing the form name to retrieve and validate all the field values in one loop for performance. The validation involves two steps as can be guessed from the Data-* attributes described above:
i. Check for required form fields.
In case the values fail to meet step 1 requirement, an error message from configuration is pulled for the specific form value. Thus, for all values that fail to meet this requirement, an array of error messages are collected and passed on to the View.
ii. Perform respective validations.
Validations are only performed if all the values passed step 1. Otherwise, they follow the same steps as indicated in 1 above.
function Form(){
var args = Array.prototype.slice.call(arguments),
formName = args[0],
callback = args.pop(),
userError = [{type: {}, param: {}}],
requiredDataParam = 'required',
typeDataParam = 'type',
form = document.forms[formName],
formLength = form.length || null,
formElement = {id: {}, name: {}, value: {}, required: {}, type: {}};
function getFormElements(){
var num = 0;
var emptyContent = false;
for (var i = 0; i < formLength; i += 1) {
var formField = form[i];
formElement.id[i] = inArray('id', formField) ? formField.id : null;
formElement.name[i] = inArray('name', formField) ? formField.name : null;
formElement.value[i] = inArray('value', formField) ? formField.value : null;
formElement.required[i] = getDataAttribute(formField, requiredDataParam);
formElement.type[i] = getDataAttribute(formField, typeDataParam);
if (formElement.required[i] === true){
if(!formElement.type[i]) {
error('Validation rule not defined!');
}
else if (!formElement.value[i]) {
userError[num++] = {'type': 'required', 'param': form[i]};
emptyContent = true;
}
}
if (emptyContent === false) {
// Perform validations only if no empty but required form values were found.
// This is so that we can collect all the empty
// inputs and their corresponding error messages.
}
}
if (userError) {
// Return empty form errors and their corresponding error messages.
}
return formElement;
};
// Removed the getFormParam function that was not used at all.
return {
getFormElements: getFormElements
}
};
Two outside functions that are used in the JS script above (from JQuery source):
var inArray = function(elem, array){
if (array.indexOf){
return array.indexOf(elem);
}
for (var i = 0, length = array.length; i < length; i++){
if (array[i] === elem){
return i;
}
}
return -1;
}
// This is a cross-platform way to retrieve HTML5 custom attributes.
// Source: JQuery
var getDataAttribute = function(elem, key, data) {
if (data === undefined && elem.nodeType === 1) {
data = elem.getAttribute("data-" + key);
if (typeof data === "string") {
data = data === "true" ? true :
data === "false" ? false :
data === "null" ? null :
!CheckType.isNaN ? parseFloat(data) :
CheckType.rbrace.test(data) ? parseJSON(data) :
data;
}
else {
data = undefined;
}
}
return data;
}
An example of Config Error messages can be set as follows:
var errorMsgs = {
ERROR_email: "Please enter a valid email address.",
ERROR_password: "Your password must be at least 6 characters long. Please try another",
ERROR_user_exists: "The requested email address already exists. Please try again."
};
As I post this for your review, please ignore any styling conventions that I might not have followed. My intention is to get your expert reviews on anything I should be doing different or could do better concerning the code itself, and the algorithm.
Besides the styling conventions, all criticism and questions are welcome.
First I'd like to clear up a common misconception. Forgive me if you already understand this clearly; maybe it will be helpful for someone else.
Learning and using jQuery or a similar library does not preclude or conflict with learning the JavaScript language. jQuery is simply a DOM manipulation library which takes away many of the pain points of using the DOM. There's plenty of room to learn and use JavaScript, the language, even if you use a library to abstract away some of the DOM details.
In fact, I would argue that using the DOM directly is likely to teach bad JavaScript coding habits, because the DOM is very much not a "JavaScript-ish" API. It was designed to work identically in JavaScript and Java and potentially other languages, and so it completely fails to make good use of the features of the JavaScript language.
Of course as you said, you're using this as a learning exercise; I just don't want you to fall into the trap that I've seen many people fall into of thinking, "I don't want to learn jQuery, because I want to learn JavaScript instead!" That's a false dichotomy: you have to learn JavaScript in either case, and using jQuery for the DOM doesn't interfere with that at all.
Now some details...
While it's OK to quote property names in an object literal and when you reference the properties, it's customary - and more readable - not to quote them when they are valid JavaScript names. e.g. in your formElement object
formElement = { id: {}, name: {}, value: {}, required: {}, type: {} };
(there was a missing semicolon at the end there too)
and where you use the names you can do:
formElement.id[i] = ...
formElement.name[i] = ...
etc.
Don't run your loops backwards unless the program logic requires it. It doesn't make the code faster except possibly in the case of an extremely tight loop, and it makes it unclear whether you're just prematurely optimizing or actually need the backwards loop.
Speaking of optimization, that loop has several inArray() calls. Since each of those loops through an array, that could be more of a performance impact than the outer loop. I imagine these arrays are probably pretty short? So performance wouldn't matter at all anyway, but this is something to think about in cases where you have longer arrays and objects. In some cases you can use an object with property names and values for a faster lookup - but I didn't look closely enough at what you're doing to suggest anything.
In any case, you're using inArray() wrong! But not your fault, that is a ridiculously named function in jQuery. The name clearly suggests a boolean return value, but the function returns the zero-based array index or -1 if the value is not found. I strongly recommend renaming this function as indexOf() to match the native Array method, or arrayIndex(), or some such.
That same loop has form[i] repeated numerous times. You could do this at the top of the loop:
var field = form[i];
and then use field throughout, e.g. field.id instead of form[i].id. This is generally faster, if it matters (which it probably doesn't here), but more importantly it's easier to read.
Do not use strict boolean comparisons like if( foo === true ) and if( bar === false) unless you really need to - and those cases are rare. The code sends a signal to the reader that there is something going on that's different from the usual boolean test. The only time these particular tests should be used is when you have a variable that may contain a boolean value or may contain some other type of value, and you need to distinguish which is which.
A good example of a case where you should use tests like these is an optional parameter that defaults to true:
// Do stuff unless 'really' is explicitly set to false, e.g.
// stuff(1) will do stuff with 1, but stuff(1,false) won't.
function stuff( value, really ) {
if( really === false ) {
// don't do stuff
}
else {
// do stuff
}
}
That specific example doesn't make a lot of sense, but it should give you the idea.
Similarly, an === true test could be used in a case where need to distinguish an actual boolean true value from some other "truthy" value. Indeed, it looks like this line is a valid case for that:
if (formElement['required'][i] === true){
given that if (formElement['required'][i] comes from the getDataAttribute() function which may return a boolean or other type.
If you are just testing for truthiness, though - and this should be most of the time - simply use if( foo ) or if( ! foo ). Or similarly in a conditional expression: foo ? x : y or !foo ? x : y.
The above was a long-winded way of saying that you should change this:
if (empty_content === false) {
to:
if (!empty_content) {
Your getFormParam() function goes to some work to convert an undefined result to null. There is usually no reason to do this. I don't see any place where that function is called, so I can't advise specifically, but in general you'd be testing for truthiness on something like this, so null and undefined would both be treated as false. Or in cases where you do need to distinguish null/undefined from other values (say, an explicit false), you can easily do it with != null or == null. This is one case where the "looser" comparison performed by == and != is very useful: both null and undefined evaluate the same with these operators.
You asked to ignore coding style, but one little suggestion here: You have a mix of camelCaseNames and names_with_underscores. In JavaScript, camelCaseNames are more idiomatic for function and variable names, with PascalCaseNames for constructor functions. Of course feel free to use underscores where they make more sense, for example if you're writing code that works with database columns in that format you may want your variable names to match the column names.
Hope that helps! Keep up the good work.
Update for your new code
I'm having a bit of trouble following the logic in the code, and I think I know part of the reason. It's a combination of naming conventions and inside-out objects.
First, the name formElement is really confusing. When I see element in JavaScript, I think of either a DOM element (HTMLElement) or an array element. I'm not sure if this formElement represents one or the other or neither.
So I look at the code to figure out what it's doing, and I see it has id:{}, name:{}, ... properties, but the code later treats each of those as an Array and not an Object:
formElement.id[i] = ...
formElement.name[i] = ...
formElement.value[i] = ...
formElement.required[i] = ...
formElement.type[i] = ...
(where i is an integer index)
If that code is right, those should be arrays instead: id:[], name:[], ....
But this is a red flag. When you see yourself creating parallel arrays in JavaScript, you're probably doing it wrong. In most cases you're better off replacing the parallel arrays with a single array of objects. Each of the objects in that array represents a single slice through all your parallel arrays, with a property for each of the previous arrays.
So, this object (where I've made the correction from {} to [] to match its current use):
formElement = { id: [], name: [], value: [], required: [], type: [] };
should be:
formInfo = [];
and then where you have the code that goes:
formElement.id[i] = ...;
formElement.name[i] = ...;
formElement.value[i] = ...;
formElement.required[i] = ...;
formElement.type[i] = ...;
It should be:
var info = {
id: ...,
name: ...,
value: ...,
required: ...,
type: ...
};
formInfo.push( info );
and adjust the rest of the code to suit. For example:
formElement.required[i]
would be:
formInfo[i].required
or even simpler since it's in the same function:
info.required
And note: I'm not saying info and formInfo are great names :-) they are just placeholders so you can think of a better name. The main idea is to create an array of objects instead of a set of parallel arrays.
One last thing and then I'm out of time for now.
That getDataAttribute() function is a complicated little piece of work. You don't need it! It would be simpler would just call the underlying function directly where you need it:
var info = {
...
required: formField.getAttribute('data-required') === 'true',
type: formField.getAttribute('data-type')
};
This also gives you full control of how the attributes are interpreted - as in the === 'true' test above. (This gives you a proper boolean value, so when you test the value later you don't have to use === true on it.)
On a stylistic note, yes, I did hard code the two 'data-xxxx' names right there, and I think that's a better and more clear way to do it.. Don't let your C experience throw you off here. There's no advantage to defining a string "constant" in this particular case, unless it's something that you want to make configurable, which this isn't.
Also, even if you do make a string constant, there's a minor advantage to having the complete 'data-whatever' string instead of just 'whatever'. The reason is that when somebody reads your HTML code, they may see a string in it and search the JS code for that string. But when they search for data-whatever they won't find it if the data- prefix is automagically prepended in the JS code.
Oh, I forgot one last thing. This code:
function Form(){
var args = Array.prototype.slice.call(arguments),
formName = args[0],
callback = args.pop(),
is working way too hard! Just do this instead:
function Form( formName, callback ) {
(and keep the var for the remaining variable declarations of course)
I cannot add comments yet so here is a little tip. I would separate the getFormElements() into smaller private functions. And I would add the errorMsgs to the Form function.
But for a first script in JavaScript, it is very impressive. This is actually the real reason I respond. I think it deserves more upvotes, and I would be very interested in a JS ninja responding to this question.
Good luck!

Categories

Resources