Replacing an Associative Array Variable - javascript

Here is the following problem.
My javascript file contains the following code...
$(function() {
var names;
var names_hash = { };
// When an user types in a letter in the student name input field
$(".container").on("keyup", "#term", function(){
// Here we are submitting the form via AJAX. The form contains necessary
// Rails code to initiate the AJAX
$(this).parents(".student-search-form").submit();
});
$(".container").on("click", ".add_nested_fields", function() {
var term = $("#term").val();
console.log(names_hash);
});
});
Now when I enter a character into the input field with the id term the Rails controller action is called and responds with the following javascript.
FYI, #group_users_hash is just a Ruby hash and I have to call html_safe so it can be properly converted into a Javascript associative array(yes, I know it's really just an object).
names_hash = <%= #group_users_hash.html_safe %>;
console.log(names_hash);
So when AJAX finishes, I see that console.log(names_hash); has produced on my console
Object {1: "Jason"}
But when I click on the .add_nested_fields calling the on event, my console log displays
Object {}
I don't know why assoc_array isn't being updated. What's wrong with my code and how do I fix it?

It looks like your names_hash (which you redefine in your return anyways via var names_hash and then shadow any higher scoped variables) is not in the same scope as the the one in which you are priting it on click. Thanks to your wrapper function. You're going to need some way - via events or callbacks, etcetera - to access the variable within that scope and set it to the value you wish to use.
EDIT
A non-ideal but sample solution to further explain woudl be:
var names_hash = {};
$(function() {
$(".container").on("click", ".add_nested_fields", function() {
console.log(names_hash);
});
});
And then when your response comes in you simply:
names_hash = <%= #group_users_hash.html_safe %>;
And then you should be golden. The reason this is not an ideal is because you have your names_hash floating in global scope. It's best to "namespace" such as:
(function() {
// prevent redefining it if it's already defined via another file
var MyNamespace = window.MyNamespace || {};
MyNamespace.names_hash = {};
window.MyNamespace = MyNamespace;
})();
$(function() {
$(".container").on("click", ".add_nested_fields", function() {
console.log(MyNamespace.names_hash);
});
});
And from there you modify your response to:
MyNamespace.names_hash = <%= #group_users_hash.html_safe %>;
And viola. You've protected global scope and you've also publicized the functions you want to access outside of the wrapper function.

Related

Inject javascript into object function

I have an object function like this:
var batman = function () {
this.constructor.prototype.go = function(params){
......
}
}
When calling batman.go() I'm passing an object in with a few keys such as:
{
a:1,
b:2,
action:function(){..code to scan and inject into...}
}
My question is, how do I in batman.go() function, scan through the input param function code of 'action' and if a match is found, inject code into a certain place.
The code I am looking for is:
history.pushState({name:'homepage'},null,uri);
I want to inject so it looks like this:
history.pushState({id:an_id_variable,name:'homepage'},null,uri);
What is being inserted is:
id:an_id_variable
Use function.toString() to get the source of params.action, String.replace() to find and replace occurences of the snippet in question, and then the Function() constructor to dynamically create a new function with the amended source code:
var batman = function () {
this.constructor.prototype.go = function(params){
...
let newAction = new Function(params.action.toString().replace(
/history\.pushState\({name:'homepage'},null,uri\);/g,
`history.pushState({id:${an_id_variable},name:'homepage'},null,uri);`
));
//use newAction() however you like
}
}
It should be noted that if any end user has any amount of control over the content that can go in params.action, this would allow for completely arbitrary code injection by that user - but as pointed out in comments, arbitrary code can already be run on browsers via developer console. Just be aware of the security implications of a solution like this.
Also note that using the Function constructor binds the function to the global scope and it will lose any this context. You can bind it to an appropriate this context with function.bind() like this:
newAction = newAction.bind(params.bindTarget);
Then, when newAction executes, whatever params.bindTarget references will be this.

how to cleanly use JS variables in an onclick function that are set in the page load function

I have a functionality that I had running in the
window.addEventListener('load', function() {
var variable_name_1 = localStorage.getItem('var_1');
...
}
and I would like to move the functionality such that it only runs when the user clicks a button, in here:
function maketempuser() {
...
}
I can get the function to call when I want. But the function utilizes tons of variables from the load function. Is there a clean way to "globalize" these variables? Or must I find some way to add all these variables in the html:
<button ... onclick='maketempuser(variable_name_1, variable_name_2, ...);' >
NOTE: the javascript will run the same file, I just don't want it to keep re-running every time the user reloads the page since there is an ajax mysql insert that occurs because this page is one in a line of pages that enables a user to register.
To not pollute the global scope with a lot of variables (which can be overridden by other apps), I recommend you create an object with an app specific name, maybe something like this
var myAppVar = {};
window.addEventListener('load', function() {
myAppVar.var_1 = localStorage.getItem('var_1');
...
}
Just define them in global scope:
var variable_name_1;
window.addEventListener('load', function() {
variable_name_1 = localStorage.getItem('var_1');
...
}
This, however, is not a particularly healthy technique, since it's prone to name collisions. Best thing to do is have a custom object (cO, or with your initials, something unlikely to be used by anything else) and use it as a placeholder for all your custom vars:
var cS = {
var_1:null // or some default value...
};
window.addEventListener('load', function() {
cS.var_1 = localStorage.getItem('var_1');
...
}
Since localStorage is already global just retrieve the values you need in your handler from there.
function maketempuser() {
var variable_name_1 = localStorage.getItem('var_1');
}
No need to add anything extra to the global scope at all.

MVC - Construct javascript object in my index that will be available in my script file

I want to initialize in my index javascript object with urls as properties, I need to initialize it on my view because of the #Url.Action that available in my view. so it will look like this:
Index.cshtml:
window.onload = function () {
myUrls=new Object();
myUrls.url1='#Url.Action("MyAction1","MyControllerName")';
myUrls.url2='#Url.Action("MyAction2","MyControllerName")';
myUrls.url3='#Url.Action("MyAction3","MyControllerName")';
myUrls.url4='#Url.Action("MyAction4","MyControllerName")';
}
Now I have script in my Script folder and I want to access these urls in my script.
How can I achieve this? Can I initialize this object somehow in my script instead of my view?
Because you've done this inside a function, the myUrls variable is scoped to that function. Once the function ends, myUrls goes out of scope and is no longer available. To make it stay around, you have to make it global by either taking it out of the window.onload (which doesn't make any sense anyways for a static variable declaration), or simply declare the variable first in the global namespace.
<script>
var myUrls;
window.onload = function () { ... }
</script>
But again, like I said, you don't need the window.onload because you don't have to wait for the DOM to be ready to declare a variable. So just do:
<script>
var myUrls = {
url1: ...,
url2: ...,
...
}
</script>
You don't need to create a new object explicitly, just use the object notation { ... }.
Finally, since you're adding this to the global namespace, I seriously recommend that you create your own namespace:
<script>
var MyAwesomeAndUniqueNamespace = MyAwesomeAndUniqueNamespace || {};
MyAwesomeAndUniqueNamespace.myUrls = {
...
}
</script>

My own mini-framework is not compatible with some projects

I failed to create a mini-library with some useful functions that I have found over the Internet, and I want to use them easily by just including a file to the HTML (like jQuery).
The problem is that some vars and functions share the same name and they are causing problems.
Is there a better solution to this instead of giving crazy names to the vars/funcs like "bbbb123" so the odds that someone is working with a "bbbb123" var is really low?
I would put all of your functions and variables into a single object for your library.
var MyLibrary = {
myFunc: function() {
//do stuff
},
myVar: "Foo"
}
There are a few different ways of defining 'classes' in JavaScript. Here is a nice page with 3 of them.
You should take one variable name in the global namespace that there are low odds of being used, and put everything else underneath it (in its own namespace).
For example, if I wanted to call my library AzureLib:
AzureLib = {
SortSomething: function(arr) {
// do some sorting
},
DoSomethingCool: function(item) {
// do something cool
}
};
// usage (in another JavaScript file or in an HTML <script> tag):
AzureLib.SortSomething(myArray);
Yes, you can create an object as a namespace. There are several ways to do this, syntax-wise, but the end result is approximately the same. Your object name should be the thing that no one else will have used.
var MyLibrary = {
myFunc: function() { /* stuff */ }
};
Just remember, it's object literal syntax, so you use label : value to put things inside it, and not var label = value;.
If you need to declare things first, use a wrapping function to enclose the environment and protect you from the global scope:
var MyLibrary = (function() {
var foo = 'bar';
return {
myFunc: function() { /* stuff */ }
};
})(); // execute this function right away to return your library object
You could put all of your library's functions inside of a single object. That way, as long as that object's name doesn't conflict, you will be good. Something like:
var yourLib = {};
yourLib.usefulFunction1 = function(){
..
};
yourLib.usefulFunction2 = function(){
..
};

Problem accessing global javascript variables

My application has something like the following structure
window.object1;
window.object2;
$(document).ready(function() {
window.object1 = new type1object();
});
function type1object() {
//lots of code
this.property = 'property';
window.object2 = new type2object();
}
function type2object() {
//lots of code
this.property = new type3object();
}
function type3object() {
//lots of code
console.log(window.object1);
this.property = window.object1.property;
}
The problem is that whenever I try to access window.object1 from anywhere other than the document ready callback it comes back as undefined, this is even though when I inspect the DOM window.object1 is defined exactly as I expect it to be.
I've tried doing the same as above but using simple global variables instead (i.e. var object1 instead of window.object1) ... Tried declaring initial dummy values for object1 and object2 in various places... but run up against the same problem.
Does anyone know why I can't access my global variables globally?
You have to make sure you are evaluating window.object1 after initiating it.
That is, in your case, only after document.ready finished executing
If you look at this example below you can see that at click both are initialized.
<html>
<body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3/jquery.min.js"></script>
<script>
$(document).ready(function() {
window.object1 = new type1object();
window.object2 = new type2object();
//console.log(window.object1);
});
$(document).click(function(){
console.log(window.object1);
console.log(window.object2);
});
function type1object() {
}
function type2object() {
}
</script>
Since you are not setting the value of window.object1 until you are inside the document ready function, you wont be able to access it until it has run.
Nothing in your code shows that you couldn't just remove that document ready call altogether. It is generally reserved for waiting for elements to load in the dom, which it doesn't seem like you are doing. If you somehow do have elements that need to be waited on inside of code that isn't there, just put your script at the bottom of the page, right above the tag. This will do the equivalent of document ready.
writing the code really stripped out made the answer fall out - I was creating something that referenced object1 during the construction of object1.
So I changed it to this, so that the object exists (though with no content) before anything tries to reference it:
window.object1;
window.object2;
$(document).ready(function() {
window.object1 = new type1object();
window.object1.construct();
});
function type1object() {
//lots of code
this.construct = function() {
this.property = 'property';
window.object2 = new type2object();
};
}
function type2object() {
//lots of code
this.property = new type3object();
}
function type3object() {
//lots of code
console.log(window.object1);
this.property = window.object1.property;
}

Categories