jQuery attribute contains JavaScript - javascript

After hours of searching without result, I turn to you
The question is simple, with jQuery can I put JavaScript in an attribute?
example
<div id="MyDiv" data-func="
console.log('#MyDviv[data-func] called')
"></div>
The ultimate goal is: I have an object that has an action to do and I would like before I do this to call a function that checks can proceed.
Example
<div id="MyDiv" data-action="next" data-func="
()=>{
var valid = true
/* ... */
return valid===true;
}
"></div>
code in the library
$('#MyDiv').on('MyEvent',function() {
/* ... */
var buttonAct = $(this).attr('data-action');
var validator = $(this).attr('data-func');
var valid=false;
/*
code that executes the validator and puts the result in valid
*/
if (valid)
buttonNextPrev (buttonAct);
});
Solutions like eval and things like that are not allowed, I need something that works like event example onclick

As Quentin notes in their answer, there is no way of putting arbitrary JavaScript into an HTML attribute and executing it without using eval or something equivalent.
There are workarounds, however.
For instance, if you can write the functions you want ahead of time, and name them in a memorable way, and could place the names of the functions in the attributes. Here's an example of doing that:
const other = {
doThisToo: (me) => {
me.style.backgroundColor = 'white';
}
};
document.getElementById('example').addEventListener('click', function () {
this.style.color = 'red';
const fn = this.getAttribute('data-other');
if (other[fn]) {
other[fn](this);
}
});
<button id="example" data-other="doThisToo">Example</button>
Essentially, this is just putting the name of "some other function to call" in the attribute value and using that name to look up the function in an object other that has the functions as properties. One could just use globally-scoped functions as well, but I prefer to pollute the global namespace as little as possible.

solutions like eval and things like that are not allowed
Then no, it isn't possible.
Any solution that would pull text from an HTML attribute and execute it as JavaScript would be like eval (even if you did it at one-step removed from eval itself such as with the Function constructor function).

Related

How can I avoid using eval() for the base object

As I understand it, eval() can be harmful. And it's annoying seeing all the warnings in my JSLint.
I've got a number of functions that are identical for my Wishlist / Shopping Cart. So I'd like to make it dynamic and only have one function of each.
Instead of cart.addItem() and wish.addItem(), I want cartWish.addItem(type).
Inside of cartWish.addItem() I need to access cart.data or wish.data, depending on the type argument.
How can I do this without resorting to eval(type).data?
I tried this[type].data and it didn't seem to work right.
What's the difference between
cart.addItem(...);
wish.addItem(...)
and
cartWish.addItem("cart", ...);
cartWish.addItem("wish", ...);
Seems like the same number of lines of code, and then all you've done is obfuscate what you are really doing. Maybe create a function that takes either a cart or wish object and assume they have the same interface:
function addItem(x, data) {
x.addItem(data);
}
var cart = ...
var wish = ...
addItem(cart, {...});
addItem(wish, {...});
Another option is to create a class:
function Item(type) {
this.type = type;
}
Item.prototype.add = function add(...) {
// ...
};
var cart = new Item("cart");
var wish = new Item("wish");
cart.add(...)
wish.add(...)
It was poor programming on my part. I didn't know that JavaScript tended to make references of objects instead of copies.
So doing...
var myReference=(type == "cart") ? cart.data : wish.data;
myReference[0].name="Bob Dole's Grill";
... will actually change cart.data[0].name outside of the function. And it will do so without making a copy of the cart object in memory.
Note: You could also just pass in the object by reference into the function, but I'm not sure if I can, because I'm sometimes invoking this function from a KnockoutJS click event.

html "data-" attribute as javascript parameter

Lets say I have this:
<div data-uid="aaa" data-name="bbb", data-value="ccc" onclick="fun(this.data.uid, this.data-name, this.data-value)">
And this:
function fun(one, two, three) {
//some code
}
Well this is not working but I have absolutely no idea why. could someone post a working example please?
The easiest way to get data-* attributes is with element.getAttribute():
onclick="fun(this.getAttribute('data-uid'), this.getAttribute('data-name'), this.getAttribute('data-value'));"
DEMO: http://jsfiddle.net/pm6cH/
Although I would suggest just passing this to fun(), and getting the 3 attributes inside the fun function:
onclick="fun(this);"
And then:
function fun(obj) {
var one = obj.getAttribute('data-uid'),
two = obj.getAttribute('data-name'),
three = obj.getAttribute('data-value');
}
DEMO: http://jsfiddle.net/pm6cH/1/
The new way to access them by property is with dataset, but that isn't supported by all browsers. You'd get them like the following:
this.dataset.uid
// and
this.dataset.name
// and
this.dataset.value
DEMO: http://jsfiddle.net/pm6cH/2/
Also note that in your HTML, there shouldn't be a comma here:
data-name="bbb",
References:
element.getAttribute(): https://developer.mozilla.org/en-US/docs/DOM/element.getAttribute
.dataset: https://developer.mozilla.org/en-US/docs/DOM/element.dataset
.dataset browser compatibility: http://caniuse.com/dataset
If you are using jQuery you can easily fetch the data attributes by
$(this).data("id") or $(event.target).data("id")
The short answer is that the syntax is this.dataset.whatever.
Your code should look like this:
<div data-uid="aaa" data-name="bbb" data-value="ccc"
onclick="fun(this.dataset.uid, this.dataset.name, this.dataset.value)">
Another important note: Javascript will always strip out hyphens and make the data attributes camelCase, regardless of whatever capitalization you use. data-camelCase will become this.dataset.camelcase and data-Camel-case will become this.dataset.camelCase.
jQuery (after v1.5 and later) always uses lowercase, regardless of your capitalization.
So when referencing your data attributes using this method, remember the camelCase:
<div data-this-is-wild="yes, it's true"
onclick="fun(this.dataset.thisIsWild)">
Also, you don't need to use commas to separate attributes.
HTML:
<div data-uid="aaa" data-name="bbb", data-value="ccc" onclick="fun(this)">
JavaScript:
function fun(obj) {
var uid= $(obj).attr('data-uid');
var name= $(obj).attr('data-name');
var value= $(obj).attr('data-value');
}
but I'm using jQuery.
JS:
function fun(obj) {
var uid= $(obj).data('uid');
var name= $(obj).data('name');
var value= $(obj).data('value');
}
you might use default parameters in your function
and then just pass the entire dataset itself, since the
dataset is already a DOMStringMap Object
<div data-uid="aaa" data-name="bbb" data-value="ccc"
onclick="fun(this.dataset)">
<script>
const fun = ({uid:'ddd', name:'eee', value:'fff', other:'default'} = {}) {
//
}
</script>
that way, you can deal with any data-values that got set in the html tag,
or use defaults if they weren't set - that kind of thing
maybe not in this situation, but in others, it might be advantageous to put all
your preferences in a single data-attribute
<div data-all='{"uid":"aaa","name":"bbb","value":"ccc"}'
onclick="fun(JSON.parse(this.dataset.all))">
there are probably more terse ways of doing that, if you already know
certain things about the order of the data
<div data-all="aaa,bbb,ccc" onclick="fun(this.dataset.all.split(','))">

Moving inline code into function, with object name generation

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.

Setting properties on anonymous DOM elements through JavaScript?

Let's say I'm generating markup through server-side code. I'm generating a bunch of HTML tags but I want to add custom client-side behavior.
With JavaScript (if I had a reference to the DOM node) I could have written:
var myDOMNode = ...
myDOMNode.myCustomAttribute = "Hi!";
Now the issue here is that I don't want to qualify every element with an unique id just to initialize data. And it's really strange to me, that there's not an easier and unobtrusive way to attach client-side behavior.
If I'm remembing this correctly, this is valid IE stuff.
<div onload="this.myCustomAttribute='Hi!'"></div>
If I was able to do this, I should be able to access it's "data context" though the identifier 'myCustomAttribute', which is really what I want.
The following will work but not validate:
<div myattribute="myvalue"></div>
But if you are injecting it into the HTML with Javascript, then perhaps that's not concern for you. Otherwise, you can use something like jQuery to process the elements before adding them to the DOM:
$(elements).each(function(){
$(this).attr('myattribute','myvalue');
});
First off you should access custom attributes using the getAttribute and setAttribute methods if you want your code to work on other browsers than IE.
As to your event handler question that really depends on how you add the event handler.
Assigning a function directly to the elements onXXXX property would allow you access the the element via this.
If you use IE's attachEvent you can't use this, you can access the element that generated the event using event.srcElementbut that may be child element of the div. Hence you will need to test for the existance of myCustomAttribute and search up the ancestors until you find it.
I do appricate the input but I've finally figured this out and it's the way I go about initialization that has been the thorn in my side.
What you never wan't do is to pollute your global namespace with a bunch of short lived identifiers. Any time you put id="" on an element you're doing exactly that (same thing for any top level function). By relying on jQuery, HTML5 data and CSS there's a solution to my problem which I think is quite elegant.
What I do is that I reserve a CSS class for a specific behavior and then use HTML5 data to parameterize the behavior. When the document is ready, I query the document (using Query) for the CSS class that represents the behavior and initialize the client-side behavior.
I've been doing a lot of ASP.NET and within this context both the id="" and name="" belongs to ASP.NET and is pretty useless for anything else than internal ASP.NET stuff. What you typically find yourself doing is to get at a server-side property called ClientID you can refer to this from client-side JavaScript, it's a lot of hassle. They made it easier in 4.0 but fundamentally I think it's pretty much broken.
Using this hybrid of CSS, HTML5 data and jQuery solves this problem altogether. Here's an example of an attached behavior that uses regular expressions to validate the input of a textbox.
<input type="text" class="-input-regex" data-regex="^[a-z]+$" />
And here's the script:
$(function () {
function checkRegex(inp) {
if (inp.data("regex").test(inp.val()))
inp.data("good-value", inp.val());
else
inp.val(inp.data("good-value"));
}
$(".-input-regex")
.each(function () {
// starting with jQuery 1.5
// you can get at HTML5 data like this
var inp = $(this);
var pattern = inp.data("regex");
inp.data("regex", new RegExp(pattern));
checkRegex(inp);
})
.keyup(function (e) {
checkRegex($(this));
})
.change(function (e) {
checkRegex($(this));
})
.bind("paste", undefined, function (e) {
checkRegex($(this));
})
;
});
Totally clean, no funky id="" or obtrusive dependency.
In HTML5 there are HTML5 data attributes introduced exactly for the case.
<!DOCTYPE html>
<div data-my-custom-attribute='Hi!'></div>
is now corect, validating html. You can use any name starting with data- in any quantity.
There is jQuery .data method for interaction with them. Use .data( key ) to get, .data(key, value) to set data-key attribute. For example,
$('div').each(function () {
$(this).html($(this).data('myCustomAttribute')).data('processed', 'OK');
});
How about this?
<script>
function LoadElement(myDiv)
{
alert(this.myCustomAttribute);
}
</script>
<div onload="LoadElement(this)"></div>
not tested btw
Since you're trying to do this for multiple elements, you may try name attributes and getElementsByName.
<div name="handleonload">...</div>
window.onload = function () {
var divs = document.getElementsByName('handleonload');
for (var i = 0; i < divs.length; i += 1) {
divs[i].foo = 'bar';
}
};
Alternatively, you can use selectors, using libraries (such as jQuery and Prototype) and their respective iterators. This will also allow for you to search by other attributes (such as class).
Though, be cautious with your terminology:
obj.property = value;
<tag attribute="value">
<div style="width:100px;height:100px;border:solid black 1px" myCustomAttribute='Hi!' onclick="alert(myCustomAttribute);"></div>
The onload event is used for server side events. Its not part of the standard html element events.
Take a look at the following functions (especially the walk_the_dom one):
// walk_the_DOM visits every node of the tree in HTML source order, starting
// from some given node. It invokes a function,
// passing it each node in turn. walk_the_DOM calls
// itself to process each of the child nodes.
var walk_the_DOM = function walk(node, func) {
func(node);
node = node.firstChild;
while (node) {
walk(node, func);
node = node.nextSibling;
}
};
// getElementsByAttribute takes an attribute name string and an optional
// matching value. It calls walk_the_DOM, passing it a
// function that looks for an attribute name in the
// node. The matching nodes are accumulated in a
// results array.
var getElementsByAttribute = function (att, value) {
var results = [];
walk_the_DOM(document.body, function (node) {
var actual = node.nodeType === 1 && node.getAttribute(att);
if (typeof actual === 'string' &&
(actual === value || typeof value !== 'string')) {
results.push(node);
}
});
return results;
};
With the above two functions at hand, now we can do something like this:
some link
<script>
var els = getElementsByAttribute('dreas');
if (els.length > 0) {
els[0].innerHTML = 'changed text';
}
</script>
Notice how now I am making finding that particular element (which has an attribute called dreas) without using an id or a class name...or even a tag name
Looks like jQuery is the best bet for this one based on my searching. You can bind an object to a DOM node by:
var domNode = ...
var myObject = { ... }
$(domNode).data('mydata', mymyObj);
then you can call the data back up the same way, using your key.
var myObect = $(domNode).data('mydata');
I assume you could also store a reference to this within this object, but that may be more info then you really want. Hope I could help.

JavaScript: Dynamic Field Names

HI All,
I have a piece of javaScript that removes commas from a provided string (in my case currency values)
It is:
function replaceCommaInCurrency(myField, val)
{
var re = /,/g;
document.net1003Form.myField.value=val.replace(re, '');
}
'MyField' was my attempt to dynamically have this work on any field that I pass in, but it doesn't work, I get errors saying 'MyField' is not valid. I sort of get my, but I thought this was valid.
I am calling by using: onBlur="replaceCommaInCurrency(this.name, this.value);return false;"
this.name and this.value are passing in the right values...field name and its value.
How do I do this dynamically?
-Jason
You can use eval to make your code snippet work:
eval("document.net1003Form." + myField + ".value=val.replace(re, '');");
As mentioned below, the square brackets work (and don't suck like eval), stupid me for forgetting about those:
document.net1003Form[myField].value=val.replace(re, '');
Alternatively, try something like this:
function replaceCommaInCurrency(field){
var re = /,/g;
field.value = field.value.replace(re, '');
}
Which gets called like so:
onBlur="replaceCommaInCurrency(this); return false";
You should consider using a javascript toolkit for things like this. You could set a class like "currency" on each input, then use this snippet of jQuery based Javascript to handle everything:
$(function(){
$("input.currency").bind('blur', function(){
this.value = $(this).val().replace(',', '');
})
});
This code would fire on document ready, attach an event handler to each input with currency as its class, and then do the replacements. Note that you don't need a regex for replacement as well.
If you code it right into the markup like that, e.g. onblur="replaceCommaInCurrency(this)", the control originating the event gets passed as the parameter. Then you should be able to do something like:
myField.value = myField.value.replace(re, '');
with jQuery:
var jqField = $(myField);
jqField.val(jqField.val().replace(re, ''));
In general, you should be using a framework that will handle low level functionality like this, but the specific answer to your question is to use bracket notation for the field name:
function replaceCommaInCurrency( myField, val)
{
var re = /,/g;
document.net1003Form[myField].value=val.replace(re, '');
}
function removeCommaInCurrency(myField)
{
var re = /,/g;
myField.value=myField.value.replace(re, '');
}
-- and then call it like this:
<input type="text" name="..." onchange="removeCommaInCurrency(this);">
flatline and roenving's solution with ‘this’ is the cleaner approach, it also avoids the problems of ‘document.formname.fieldname’.
(Use ‘document.forms.formname’ to access a form without possible clashing on forms having the same name as members of the document object, and ‘forms.elements.fieldname’ to do the same with fields. Like all JavaScript object, object[namevariable] can also be used. Or, better, add IDs and use the unambiguous document.getElementById method.)
By moving binding into the script you can also remove the inline JavaScript of the onclick attribute, making the markup cleaner still:
<input type="text" class="number" name="something" />
...
<script type="text/javascript"> // external script is best, linked after all forms
function numberfield_bind() {
var inputs= document.getElementsByTagName('input');
for (var inputi= inputs.length; inputi-->0;)
if (inputs[inputi].className=='number')
inputs[inputi].onchange= numberfield_change;
}
function numberfield_change() {
this.value= this.value.split(',').join('');
}
numberfield_bind();
</script>

Categories

Resources