Moving inline code into function, with object name generation - javascript

I am customizing Denis Gritcyuk's Popup date picker.
This pop-up script uses inline Javascript in a href link, to set the selected date into the input field, in the parent window, that is was called for. An example URL looks like:
<a href="javascript:window.opener.document.formname.field.value='03-10-2011';
window.close();">3</a>
The input field name, (e.g. document.formname.field), is passed to the script as a string parameter.
I would like to add things done when that link is clicked (e.g. change background color of field, set flag, etc.). So while this DOES work, it's getting ugly fast.
<a href="javascript:window.opener.document.formname.field.value='03-10-2011';
window.opener.document.formname.field.style.backgroundColor='#FFB6C1';
window.close();">3</a>
How would I move these inline commands into a JS function? This would give me much cleaner URLs and code. The URL would now look something like
3
with a function like (this example obviously does NOT work):
function updateField (str_target, str_datetime) {
var fieldName = "window.opener" + str_target;
[fieldName].value = str_datetime;
[fieldName].style.backgroundColor = '#FFB6C1';
// Set flag, etc.
window.close();
}
So any suggestions on how this can be done, please?

I'd prefer to hide the dom path tracing back from the current window back to the opener. It's appropriate to bake that into the function since the function will always be used in the context of that child popup. Then your function call is cleaner and more readable. Obviously, replace "myField" with the ID of the field you're intending to update.
3
function updateField ( str_date, str_fieldname ) {
var fieldToUpdate = document.getElementById( str_fieldname );
fieldToUpdate.value = str_date;
fieldToUpdate.style.backgroundColor = '#FFB6C1';
// Set flag, etc.
window.close();
}

You're acessing the property incorrectly. Try:
function updateField (str_target, str_datetime) {
var fieldName = window.opener;
str_target = str_target.split('.');
for (var i = 0; i < str_target.length; i++)
fieldName = fieldName[str_target[i]];
fieldName.value = str_datetime;
fieldName.style.backgroundColor = '#FFB6C1';
// Set flag, etc.
window.close();
}
The bracket notation ([]) is only used for properties of objects, not objects themselves. If you found my post helpful, please vote for it.

You can build a string and evaluate it as code using the eval function, but I would recommend against it.
There are a couple of things wrong with your code:
You cannot use the [] operator in a global context, you have to suffix it on an object, so you can say window["opener"] and this will be equivalent to window.opener, but there is no such thing as simply ["window"]
When navigating nested properties, as in window.opener.document you cannot navigate multiple levels using the [] operator. I.e. window["opener.document"] is not allowed. You must use window["opener"]["document"] instead.

Related

Assign HTML elements ID to document.queryselector shorthand variables in a loop

I am new to Javascript development.
I am trying to assign HTML elements IDs stored in an array to shorthands to be used in my function later.
So that instead of writing :
let addprop = document.querySelector(`#addprop`);
let readprop = document.querySelector(`#readprop`);
let editprop = document.querySelector(`#editprop`);
let footer = document.querySelector(`#footer`);
let association = document.querySelector(`#association`);
I can attribute elements ids that i store in an array like this :
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"] ;
arrayElements.forEach(el => { return(new Function (`${el} = document.querySelector("#${el}");`)()); });
Now, this bit of code works but from what I read here :
Execute JavaScript code stored as a string
This is probably not a good way to do it and also declares global variables.
One problem I encountered is that if I try to directly execute the assignment like this :
el = document.querySelector(`#${el}`);
Then the el value takes the value of the named access ID element (https://html.spec.whatwg.org/multipage/window-object.html#named-access-on-the-window-object) and breaks the code.
So I resorted to generate a string first then execute it.
I could simply assign each shorthand manually but I spent way too much time trying to make this work and am now left curious as to what would be a good solution or approach for this.
And would the scope limitations for loops simply forbid me to do this without using global variables ?
edit : switched the working code in one line
Possible answer :
1 - does it matter to declare global variables like that ? As these variables already exist globally because of browsers named access for elements IDs.
2 - By kiranvj's answer, a solution can be to store in an object structured as keys being the shortcuts and the full strings being the values, and calling the shortcuts with the object[key] method ; or using destructuring to assign the values to variable directly with :
const {addprop, readprop, editprop, idfooter, assocpatients} = elements;
I feel like I am missing something on this last one but it also seems to work.
In the end I will stick with my first code as condensing the function in one line seems to negate the risks of cross site scripting (?), and global values for the variables assigned though this method anyway already exist because of named access.
You can create a dictionary with all the elements with ID and then destroy it into your variables, ignoring the unused ones.
function getAllElementsWithId() {
let elements = {}
for (let el of document.querySelectorAll('[id]')) {
if (!(el.id in elements)) {
elements[el.id] = el
}
}
return elements
}
let { addprop, readprop, editprop, footer, association } = getAllElementsWithId()
This uses document.querySelectorAll (link to MDN) to get all elements with an ID. Notice that for big pages this could be a performance issue.
Also, what you would usually do is to add them into a container, in this case it seems like a dictionary.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"]
let elementsId = Object.fromEntries(arrayElements.map(id => [id, document.getElementById(id)]))
This uses Object.fromEntries (link to MDN) to generate the dictionary. Also I'm using document.getElementById (link to MDN) instead of document.querySelector so you don't need to add the hashtag before the id.
If you are concerned about global scope, you can try something like below. Use forEach instead of map . map also work but since you are not handling the return of map, forEach would be a better choice.
let arrayElements = ["addprop", "readprop", "editprop", "footer", "association"];
let elements = {};
arrayElements.forEach(el => elements[el] = document.querySelector(`#${el}`));
// access variables like elements.ID-NAME
console.log(elements);
<div id="addprop"></div>
<div id="readprop"></div>
Object destructing can be used if you know the object key name.
example : let {addprop} = element;
Another thing which you might be interested is Automatic global variables
This means a new variable (scoped to window) with the name of element id is created for all the elements in page. See the html5 spec. I would not recommend using it though.
So you don't have to call like document.querySelector('addprop')
addprop variable will have the DOM object.
See this example
// these works due to automatic global varaibles binding
alert(addprop);
console.log(addprop);
<div id="addprop">Some contents</div>

Using .append() in if statments?

I am trying to use .append() with if statements, I have a lot of them maybe 10. What I'm trying to do is add to a div if something happens. if A is less then 5 I want to add to the div, so on and so on. .append() works good for me if I put all of the things I want to add in one .append(). But if I try to do it separately it will not work for me. I don't know what I will be adding a head of time, it depends on user data so I can't add everything I want in one .append(). My code is long so I have put a fiddle below. I know i may have other issues with this code but, just asking about .append() or a way to add to my div like i want
if(k3a<5) {
msg3="need to work on q3"
var c = $('<p>'+msg3+'</p>')
$('#output1').append(c);
$output1.text(msg3);
}
if(k4a<5) {
msg4="need to work on q4"
var e = $('<p>'+msg4+'</p>')
$('#output1').append(e);
$output1.text(msg4);
}
if(k5a<5) {
msg5="need to work on q5"
var e = $('<p>'+msg5+'</p>')
$('#output1').append(e);
$output1.text(msg5);
}
I know I can do something like below, but I need to add them one by one if the condition is meet, not at once.
if (k1 < 10) {
msg1 = "This will not space like a want.<br/>";
msg2 = "I don know why not.<br/>";
msg3 = "How come.<br/>";
var e = $('<p>'+msg1+'</p>'+'<p>'+msg2+'</p>'+'<p>'+msg3+'</p>');
$('#output1').append(e);
}
http://jsfiddle.net/G24aQ/21/
This looks like you don't understand what the code is doing; (assuming $output1 is $('#output1)) current code is (just one part)
msg4="need to work on q4"; // set global variable `msg4`
var e = $('<p>'+msg4+'</p>'); // set local variable `e`
$('#output1').append(e); // append html to element
$output1.text(msg4); // re-set content of element as text
You most likely want just
var msg4="need to work on q4", // set local variable `msg4`
e = $('<p>'+msg4+'</p>'); // set local variable `e`
$('#output1').append(e); // append html to element
What is $output1? That should be a variable, but you don't assign anything to it. Also, there is no need to use $() to put HTML into just for the sake of assigning it to your c or e variable. $() gets content. You just need to make a String, like:
var c = '<p>'+msg3+'</p>';
although I would have var e, c above all your code, if I was going to reuse it, so you don't have to rewrite var, and I wouldn't store msg3 in a variable at all. My code would contain, something like:
var out1 = $('#output1');
if(k3a<5)out1.append('<p>need to work on q3</p>');
if(k4a<5)out1.append('<p>need to work on q4</p>');
if(k5a<5)out1.append('<p>need to work on q5</p>');
Your code is different on you jsFiddle page. For instance, k3a does not exist. Don't redefine var total in your code, either. There may be more problems with your code, but this should put you on the right path.
you may find it easier to define a small function to output the messages. something like
function showMessage( strMsg, targetID ) {
$('#'+targetID).empty().append("<p>" + strMsg + "</p>");
}
and call it using
showMessage( "message one", "output" );
showMessage( "message two", "output1" );
This way when you decide you want to display them in another fashion, you only have to change it in one place.

traverse of tree in javascript

I have a javascript variable like below:
var treeNode= [{"id":"T1"},{"id":"T2","children":[{"id":"T3"},{"id":"T4"},{"id":"T5","children":[{"id":"T6"}, {"id":"T7"},{"id":"T8"}]},{"id":"T9"},{"id":"T10"}]},{"id":"T11"},{"id":"T12"}];
node t3,t4,t5,t6,t7,t8,t9,t10 are the child of node t2
i have a link of deactivate on each node.on click on deactivate link make active and delete link .mentioned in image.
now i want to make same active and delete link on all child node of parent node.
for example T3,T4,T5,T6,T7,T8,T9,T10 are the child of T2.
if i click on T5 then this will work on T6,T7,T8.
I tried below recursive code.may be my approach is not right.please advice.
var objTreeNode = eval(treeNode);
trav(objTreeNode);
function trav(TreeNodeObj){
var i=0;
for (i=0;i<TreeNodeObj.length;i++){
if(!TreeNodeObj[i].children){
if(objID==TreeNodeObj[i].id){ // will get T2 if click on deactivate link of Item T2
document.getElementById('span_'+TreeNodeObj[i].id).innerHTML = 'Activate <a href="javascript:deleteNode(\'' + objID
+'\');">Delete</a>';
}
}
else{
childObj = TreeNodeObj[i].children;
trav(childObj)
}
}
}
There are a few silly things in your code, let me fix them:
1. "Eval is evil!"
var treeNode= [{"id":"T1"},{"id":"T2","children":[{"id":"T3"}]}];
var objTreeNode = eval(treeNode);
trav(objTreeNode);
Why would you call eval()?
Let's see what MDN has to say about this:
Don't use eval! It's dangerous and slow. There are safe (and fast!) alternatives to eval() for common use-cases.
So what is your "use-case"? Why do you call eval here? What is the "better" solution? If you read the whole documentation on MDN you can read that:
If the argument of eval() is not a string, eval() returns the argument unchanged.
So unless treeNode is a string var objTreeNode = eval(treeNode); basically equals to var objTreeNode = treeNode;
You can drop that whole eval() line and just use treeNode. It's already an object.
2. camelCase
function trav(TreeNodeObj) {
This is not an error just a convention: In JavaScript (and also in most languages with C-like syntax) the parameters of a function are written with lower camel case (first letter is lowercase, and every other word's first letter is uppercase).
function trav(treeNodeObj) {
3. objID is undefined
There is no objID variable defined in your code. Although it is possible that you have a global defined elsewhere at the given time, it is much safer to introduce it as a parameter in your function.
function trav(treeNodeObj, objID) {
4. What your code does with what and when
Let me just figure out what your code currently does:
Iterates over a given object's children property (which is hopefully an array).
If an element has no children
Check if the array item has a desired ID property, and change it's innerHTML
Else
Call the function on the children
So what it does: Changes the element with the given ID if it has no children.
What you need is: Change the element with the given ID and also it's children.
I just modified your function like this:
function trav(treeNodeObj, objID, activate) {
var i = 0;
for (i = 0; i < treeNodeObj.length; i++) {
var childrenActive = false;
if (objID === treeNodeObj[i].id || activate) { // will get T2 if click on deactivate link of Item T2
childrenActive = true;
document.getElementById('span_' + treeNodeObj[i].id).innerHTML = 'Activate Delete';
}
if (treeNodeObj[i].children) {
childObj = treeNodeObj[i].children;
trav(childObj, objID, childrenActive);
}
}
}
Since you need to change all the child elements I needed to introduce a cut. This is the activate parameter. If the activate parameter is true you don't need to check the ID anymore you know that we are iterating over the subelements of the element with the given ID, and therefore change the element anyway.
Also you need to change the elements even if they have child nodes, so I restructured the if-s.
I have also made a jsfiddle for you to test: http://jsfiddle.net/JZ52g/3/
You can change the id parameter at the function call.

JSON how find another value at the same index from a value in Javascript Object

A simple question I'm sure, but I can't figure it out.
I have some JSON returned from the server
while ($Row = mysql_fetch_array($params))
{
$jsondata[]= array('adc'=>$Row["adc"],
'adSNU'=>$Row["adSNU"],
'adname'=>$Row["adname"],
'adcl'=>$Row["adcl"],
'adt'=>$Row["adt"]);
};
echo json_encode(array("Ships" => $jsondata));
...which I use on the client side in an ajax call. It should be noted that the JSON is parsed into a globally declared object so to be available later, and that I've assumed that you know that I formated the ajax call properly...
if (ajaxRequest.readyState==4 && ajaxRequest.status==200 || ajaxRequest.status==0)
{
WShipsObject = JSON.parse(ajaxRequest.responseText);
var eeWShips = document.getElementById("eeWShipsContainer");
for (i=0;i<WShipsObject.Ships.length;i++)
{
newElement = WShipsObject.Ships;
newWShip = document.createElement("div");
newWShip.id = newElement[i].adSNU;
newWShip.class = newElement[i].adc;
eeWShips.appendChild(newWShip);
} // end for
}// If
You can see for example here that I've created HTML DIV elements inside a parent div with each new div having an id and a class. You will note also that I haven't used all the data returned in the object...
I use JQuery to handle the click on the object, and here is my problem, what I want to use is the id from the element to return another value, say for example adt value from the JSON at the same index. The trouble is that at the click event I no longer know the index because it is way after the element was created. ie I'm no longer in the forloop.
So how do I do this?
Here's what I tried, but I think I'm up the wrong tree... the .inArray() returns minus 1 in both test cases. Remember the object is globally available...
$(".wShip").click(function(){
var test1 = $.inArray(this.id, newElement.test);
var test2 = $.inArray(this.id, WShipsObject);
//alert(test1+"\n"+test2+"\n"+this.id);
});
For one you can simply use the ID attribute of the DIV to store a unique string, in your case it could be the index.
We do similar things in Google Closure / Javascript and if you wire up the event in the loop that you are creating the DIV in you can pass in a reference to the "current" object.
The later is the better / cleaner solution.
$(".wShip").click(function(){
var id = $(this).id;
var result;
WShipsObject.Ships.each(function(data) {
if(data.adSNU == id) {
result = data;
}
});
console.log(result);
}
I could not find a way of finding the index as asked, but I created a variation on the answer by Devraj.
In the solution I created a custom attribute called key into which I stored the index.
newWShip.key = i;
Later when I need the index back again I can use this.key inside the JQuery .click()method:
var key = this.key;
var adt = WShipsObject.Ships[key].adt;
You could argue that in fact I could store all the data into custom attributes, but I would argue that that would be unnecessary duplication of memory.

call JS function via url

i got a got a little embedded system that can be controlled via a webinterface.
the page looks like:
...
foo
...
is there a way to call this function just by http? like
http://<the-devices-ip>:80/javascipt:foo(bar) //wrong
thank you
You can do so by passing a querystring or a hash into the URL and execute a piece of JS which checks it during onload.
var query = window.location.search; // Gets '?foo=bar' from http://example.com/page.html?foo=bar
var hash = window.location.hash; // Gets '#foo' from http://example.com/page.html#foo
You only have to parse it further yourself or by using a 3rd party JS framework with plugin capabilities, like jQuery.
page1.html:
foo
page2.html:
<script type="text/javascript">
if(window.location.hash)
eval(window.location.hash)
</script>
I'm not saying it's a good idea. It might be helpful to document why you think you need to do this, there are probably better ways to accomplish whatever the actual goal is.
Note that doing this will not allow you to pass variables around. You need to have only static values in the javascript code executed on page2.html, or generate the href in page1.html dynamically.
Caveat: this will potentially open up your code to HTML and/or script injections. Filter rigorously.
I recently had to do something similar for a project that I was contracted out on. Like others, I used the hash portion of the URL to pass in JavaScript functions and parameters. However, the main difference was that I didn't do a simple eval of the entire string. I established a specific format to 1) narrow the amount of functions that could be executed, and 2) sanitize any input that the method required
The format, in full, is as follows:
http://somedomain.tld/path/?query=blah#specific.controller.object/method/['array', 'of', {json: 'arguments'}]
So, basically, you end up with the following string:
specific.controller.object/method/['array', 'of', {json: 'arguments'}]
I then wrote a parser to handle this string. Restrictions where exacted over what objects could be called by prepending with a sort of "namespace" object, in other words, calling it as part of a member of an existing, predetermined static object. For example, specific.controller.object, would be called as new com.project.specific.controller.object();. Here's something similar to my parser:
var data = location.hash.substr(1).split('/'),
controller = ("my.namespace." + data[0]).split("."),
// You can provide a default method if you want, my framework used `show`
method = data[1] || "show",
// must be an array for use with `apply`
params = data[2] || "[]";
// Parse the controller to find the appropriate object to instantiate.
// All objects are in reference to the global window object. Break
// them apart by their dot composition and step down through the object
// tree starting at window.
var composition = window;
for ( var i=0; i<controller.length; i++ ) {
composition = composition[ controller[i] ];
}
var obj = new composition;
// Handle the parameters. It may be the case that there "/" is present
// in the last argument. If so, add anything that was left out.
if ( data.length > 3 ) {
for ( var i=3; i<data.length; i++ ) {
params += '/' + data[i];
}
}
// Convert params from a string to an array.
// ***Possible injection point here***
params = dojo.fromJson(params);
// Make sure that the method runs in the proper context and
// pass it all of the parameters
obj[method].apply(obj, params);
Because the way the parser works, you required to provide parameters if none are needed, and in some cases, if you choose to allow default methods as I have, you don't have to specify which member on the object to call, which simplifies that construction of these URL's greatly.
Instead of using a static namespace object to restrict what objects could be instantiated, it would be trivial to use a white list of safe objects and methods.
foo represents the calling of a function, not the call to a url. There is no direct mapping between a url and a JavaScript function.

Categories

Resources