Rewrite javascript functions for IE - javascript

I have some functions that either kill the page or fail silently in IE. I can't figure out how to rewrite them. I'd prefer not to have to add a bunch of plugins but I do have jQuery.
The variables in question are arrays of objects. How would you write the following?
// 1. Get only the newly added user / group
var new_students = new_enrollee_list.filter(function( new_enrollee ){
return ! current_enrollee_list.some(function( current_enrollee ){
return new_enrollee.id === current_enrollee.id && new_enrollee.type === current_enrollee.type;
});
});
// 2. Remove students from current list
current_enrollee_list.splice(0, current_enrollee_list.length, ...new_enrollee_list);

For the spread syntax you should be able to workaround by making one array from all the arguments and using Function.apply:
So this
current_enrollee_list.splice(0, current_enrollee_list.length, ...new_enrollee_list);
becomes
current_enrollee_list.splice.apply(current_enrollee_list, [0, current_enrollee_list.length].concat(new_enrollee_list));
Since you are using IE9 some and filter should work fine.

Related

Reset JS array and set dynamic key array with one line of code?

EDIT
The following question How do I empty an array in JavaScript? does not answer the question i.e. I get how to set / reset an array but that was not my question. I want to to do is:
set the key with a dynamic value e.g. UUID and,
then set element[0] (the above) to true in one line of code.
QUESTION
Code base: Angular 1.5x, lodash 4.x
Seems like a basic question but cannot find an answer. Is it possible using JS, Angular or lodash to reset an array and set it at the same time per the example below. As per the example below the array will keep pushing unless I set reset it. Why you ask, example if I'm using a UUID as key in the HTML e.g.
HTML
<li id="{[{contactus.app_id}]}"
ng-show="view.state[item.uuid]"
ng-repeat="item in collection.items track by item.uuid">
</li>
JS (Angular)
NOTE: this code works and I could also use replace $scope.view.state.length = 0; with $scope.view.state = []; but my question is more along the lines of reseting an array, adding a dynamic key and setting it to true all in one line of code. The complexity is in the dynamic key.
$scope.view = {state:[]};
$scope.setViewState = function (id) {
// how can I collapse the following 2 lines of code into one
$scope.view.state = [];
$scope.view.state[id] = true;
};
You can use computed properties to do this in one line:
$scope.view.state = {[id]: true};
However, this will only work on the latest browsers (Chrome, Firefox, Safari 7.1+, and Edge) and if you have any build steps that need to parse your code they will also need to be upgraded to understand the syntax as well.
If you need older browser support (or if your build tools cannot be upgraded) then you'll need to do it in two steps. However, you can hide those steps in a function:
function initialState(key, value) {
var result = {};
result[key] = value;
return result;
}
then you can simply do:
$scope.view.state = initialState(id, true);

Restricted JavaScript Array Pop Polyfill not working

I'm creating a few specific functions for a compiler I'm working on, But certain restrictions within the compiler's nature will prevent me from using native JavaScript methods like Array.prototype.pop() to perform array pops...
So I decided to try and write some rudimentary pseudo-code to try and mimic the process, and then base my final function off the pseudo-code... But my tests seem to fail... based on the compiler's current behavior, it will only allow me to use array.length, array element assignments and that's about it... My code is below...
pop2 = function(arr) {
if(arr.length>0){
for(var w=undefined,x=[],y=0,z=arr.length;y<=z;y++){
y+1<z?(x[y]=arr[y]):(w=arr[y],arr=x);
}
}
return w;
}
Arr = [-1,0,1,2];
// Testing...
console.log(pop2(Arr)); // undefined... should be 2
console.log(Arr); // [-1,0,1,2]... should be [-1,0,1]
I'm trying to mimic the nature of the pop function but can't seem to put my finger on what's causing the function to still provide undefined and the original array... undefined should only return if an initial empty array is sent, just like you would expect with a [].pop() call...
Anyone have any clues as to how I can tailor this code to mimic the pop correctly?
And while I have heard that arr.splice(array.length-1,1)[0]; may work... the compiler is currently not capable of determining splice or similar methods... Is it possible to do it using a variation of my code?
Thanks in advance...
You're really over-thinking [].pop(). As defined in the specs, the process for [].pop() is:
Get the length of the array
If the length is 0
return undefined
If length is more than 0
Get the item at length - 1
Reduce array.length by 1
Return item.
(... plus a few things that the JavaScript engine needs to do behind the scenes like call ToObject on the array or ensure the length is an unsigned 32-bit integer.)
This can be done with a function as simple as the one below, there's not even a need for a loop.
function pop(array) {
var length = array.length,
item;
if (length > 0) {
item = array[length - 1];
array.length -= 1;
}
return item;
}
Edit
I'm assuming that the issue with the compiler is that Array.prototype.pop isn't understood at all. Re-reading your post, it looks like arrays have a pop method, but the compiler can't work out whether the variable is an array or not. In that case, an even simpler version of this function would be this:
function pop(array) {
return Array.prototype.pop.call(array);
}
Try that first as it'll be slightly faster and more robust, if it works. It's also the pattern for any other array method that you may need to use.
With this modification, it works:
http://jsfiddle.net/vxxfxvpL/1/
pop2 = function(arr) {
if(arr.length>0){
for(var w=undefined,x=[],y=0,z=arr.length;y<=z;y++){
if(y+1<z) {
(x[y]=arr[y]);
} else {
(w=arr[y],arr=x);
break;
}
}
}
return w;
}
Arr = [-1,0,1,2];
// Testing...
console.log(pop2(Arr)); // 2
The problem now is to remove the last element. You should construct the original array again without last element. You will have problems with this because you can't modify the original array. That's why this tasks are maded with prototype (Array.prototype.pop2 maybe can help you)

Remove element by value a-la Knockout

This is something of a two-part question that has to do with manipulating elements within an array of data in Angular. It seems like pretty universally the way to remove an element from an array in the ViewModel is
$scope.array.splice(index, 1);
This seems a little shaky to me, and I prefer how Knockout handles this with .remove and observable arrays: vm.array.remove(item).
I have found that you can do this which is a bit better:
$scope.array.splice($scope.array.indexOf(item), 1);
but it's more verbose and .indexOf may not work as you expect depending upon what item is.
Is there any construct for Angular that will allow you to easily remove an item from an array by its value?
Also based on this video from Egghead.io, it makes sense to remove dependencies within ViewModel methods and not rely on scope. Would it be preferred to pass in the array that you were removing the item from as well:
<input type=submit ng-click="remove(array, item)">
array.splice(array.indexOf(item), 1)
Or is there a reason to prefer using $scope (or the controller) within the remove method?
Unfortunately or Fortunately, Knockout does it the same way we are doing with Angular i.e. splice method
If you look at the source code of observableArray.remove(item) in knockout library -
'remove': function (valueOrPredicate) {
var underlyingArray = this.peek();
var removedValues = [];
var predicate = typeof valueOrPredicate == "function" && !ko.isObservable(valueOrPredicate) ? valueOrPredicate : function (value) { return value === valueOrPredicate; };
for (var i = 0; i < underlyingArray.length; i++) {
var value = underlyingArray[i];
if (predicate(value)) {
if (removedValues.length === 0) {
this.valueWillMutate();
}
removedValues.push(value);
underlyingArray.splice(i, 1);
i--;
}
}
if (removedValues.length) {
this.valueHasMutated();
}
return removedValues;
}
It does the same thing, it parse through the array and compare the given value and performs splice.
They have written reusable module for the same to make it easy to use for developers. I believe you can do the same by writing custom directive in your Angular code. You can use above code for a reference. It's just that Angular does not have any reusable directive for that... yet.. may be we can ask for a pull request after making one :-)
But your question is very good and one should have such reusable module.

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!

Adding custom remove method to Raphael JS Graffle Connection

I'm using the custom connection method (Raphael.fn.connection) added in the example found at: raphaeljs.com/graffle.html
My example is here: http://jsfiddle.net/WwT2L/ (scroll in the display window to see the effect)
Essentially, I've linked the graffle connection to the bubble so it stays with it as it scales. I'm hoping that I can have the connection switch to the next bubble as the user scrolls past a certain point.
To do this, I was thinking I would remove the connection and add another one, but as the connection method is not a native Raphael element, it doesn't have the built in remove method, and I'm having trouble adding the remove method to the prototype.
I've found some info about adding custom methods at this google group discussion
and I've tried:
this.connections[0] = this.r.connection(this.bubbles[0], this.unitConnector, "#fff", "#fff").__proto__.remove = function() {alert('working custom method');};
which seems to add a method to this instance of connection but I'm not sure what to have the method do and it seems like there should be a better way.
To recap... when we create a connection, we often use the following:
connections.push(
r.connection(r.getById(firstObjectId), r.getById(secondObjectId), '#fff')
);
What we're doing here is pushing (adding) a Raphael.connections object into a connections[] array, based on their Raphael object id's
To add a method/function to Raphael, one might use:
Raphael.fn.fnName = function (){ /* Your code here */ }
This creates a function in our Raphael namespace for use with our Raphael objects.
Below is the code i've created which does exactly what you require. I couldn't find a good resource out there for Raphael, but will surely be creating one soon, as I have done a lot of development with it.
Raphael.fn.removeConnection = function (firstObjectId, secondObjectId) {
for (var i = 0; i < connections.length; i++) {
if (connections[i].from.id == firstObjectId) {
if (connections[i].to.id == secondObjectId) {
connections[i].line.remove();
connections.splice(i, 1);
}
}
else if (connections[i].from.id == secondObjectId) {
if (connections[i].to.id == firstObjectId) {
connections[i].line.remove();
connections.splice(i, 1);
}
}
}
};
Just like in the create connections, two id's are provided. We must find these ID's in the array of connections we've pushed each connection set to. If you only have one connection, there is no need for array traversing, though this is a case less encountered.
We have two possible scenarios here - excluding the case of having found no connection for simplicity sake. It either finds that:
the connection objects from.id corresponds to the first provided paramenter firstObjectId. Then, the to corresponds to the second provided paramenter secondObjectId.
the connection objects from.id corresponds to the first provided paramenter secondObjectId. Then, the to corresponds to the second provided paramenter firstObjectId.
This method of checking covers all our bases, so no matter how the connection is interacted with (in my case the user clicks two objects to connect them, and delete their connection)
Once we've confirmed we have the two correct objects, we then remove the line from the DOM, using connections[i].line.remove(); as just removing the connection object from the array will leave it on the map.
Finally, we remove the specified connection object from the array, and the splice method leave us with an un-holy array (no holes in our array, that is ;) ) using connections.splice(i, 1);
Then,
this is what i am using to remove connections from connections array used with graffle example and so far i am having no issue with it. the question may be old but i stumbled upon on it searching the related solution, so when i had no luck i created mine and want to share with rest of the world.
//checks if the current object has any relation with any other object
//then remove all the to and from connections related to current object
for(var i =0 ; i<connections.length; i++){
if(connections[i].from.id == objectId || connections[i].to.id ==objectId ){
connections[i].line.remove();
}
}
//finds out which connections to remove from array and updates connections array
connections = $.grep(connections, function(el){
return el.line.paper != null;
})
the splice method was having issues with my case as if object has more than one connections (to, from) with multiple objects and every time i was using splice the main connections array length was changing as well as value of i was increasing, so i used jQuery grep method to update array based on removed lines. i hope this will help others too.
function removeShape(shape) {
//CONNECTIONS is my global structure.
var connections = [];
while (CONNECTIONS.length) {
var connection = CONNECTIONS.pop();
if (connection.from.id == shape.id || connection.to.id == shape.id)
connection.line.remove();
else
connections.push(connection);
}
shape.remove();
CONNECTIONS = connections;
}

Categories

Resources