How to relate data (id's) to links for JavaScript? - javascript

I'm trying to write a fairly complex dynamic web page, using JQuery AJAX, and I am struggling with how to relate my links (<a ...>) with the the data their tied to, such as action names, and data element ids. I have pondered several different schemes, but I'm not sure I like any of them.
Building it into onclick, which means I have to configure it in the link generation.
<a onlick="func('abc', 123)">...</a>
Inserting it into the id of the link, which means parsing it out in JavaScript.
<a id="link_abc_123">...</a>
Putting the link in a div with hidden input elements...
<div>
<a>...</a>
<input type="hidden" name="action" value="abc"/>
<input type="hidden" name="id" value="123"/>
</div>
Is there a best practice or a commonly accepted way of structuring this data?

Best practice should always be, to strictly separate Code.
That means, you shouldn't include any Javascript into your backend-source code. So personally I'm a big fan of either putting the necesarry data into the elements (your last example) when using a template-engine, or sending just the necesarry data on a separate request (JSON for instance) to the client.
Using jQuery, it's a very convinient way to create data- attributes, where you can store any information, while jQuery will translate the values from those attributes into the data expandos. For instance:
<div id="test" data-foo='bar' data-test='{"cool": "stuff"}'>Just a div</div>
When selecting that element with jQuery var $test = $('#test'), you can access:
$test.data('foo') // === 'bar'
$test.data('test').cool // === 'stuff'
Read more: http://api.jquery.com/data/

With HTML5, you have the luxury of using data-* attributes - for example:
...
Which jQuery actually has support for - calls to $('a').data() will include the data-* values in it.

For simple things, I use a function like:
function getIdStr(i, sDelim) {
if (typeof i != "string") {
var i = $(i).attr("id");
}
var arr = i.split(sDelim || /_|-/);
if (arr.length > 1) {
return arr[arr.length-1];
} else {
return "";
}
}
// usage
$(function(){
$(".data .action").click(function(){
doSomething(getIdStr(this)); // -> 123
});
});
For something heavier, you might try to attach a data to the topmost container.

i would go with the new Custom Data Attributes HTML5 brings along.
Just use this <a data-action="foo" data-id="bar">...</a>
Also, jQuery already has support to get these data-attributes

You can add a custom property to the input and access it in javascript.
eg
<input type="hidden" name="action" value="abc" yourproperty='<%= Eval("YourDataID") %>'/>

Related

Getting value of <a> link?

I am having troubles pulling the value from a link. It's a CSS formatted page and I would really prefer to use a <a> than a <button>.
<button value="1" onclick="showDetails(this.value)">This works</button>
<a value="2" onclick="showDetails(this.value)">This doesn't work</a>
};
xmlhttp.open("GET","getdetails.php?q="+str,true);
xmlhttp.send();
How can I get the value of <a> when it is clicked and not having it go somewhere?
Only a select few elements, like <input>s, <textarea>s, and <button>s can have value properties:
console.log(
document.querySelector('a').value
);
<a value="val">a</a>
If you have to use the value attribute, use the getAttribute method instead of dot notation:
console.log(
document.querySelector('a').getAttribute('value')
);
<a value="val">a</a>
Another option would be to use data attributes instead, which would be more appropriate than value="s when working with an <a>:
console.log(
document.querySelector('a').dataset.value
);
<a data-value="val">a</a>
(also make sure to attach your event handlers properly using Javascript if at all possible - inline handlers are generally considered to be pretty poor practice - try using addEventListener)
To use addEventListener, select your a, and call addEventListener on it. For example, if your <a> has an id of details:
const details = document.querySelector('#details');
details.addEventListener('click', () => {
showDetails(details.dataset.value);
});
function showDetails(str) {
console.log('showing details for ' + str);
}
<a id="details" data-value="thevalue">click for details</a>
You can write a Javascript function to get the value from the link as follows:
function showDetails(a){
let value = a.getAttribute("value");
// view value
console.log(value)
}
<!--<button value="1" onclick="showDetails(this)">Button link</button>-->
<a value="2" onclick="showDetails(this)">Anchor link</a>
Your issues has 2 parts:
#1: Use of correct attribute
You should not use the value attribute in <a> tag, as it's not a valid attribute for HTML standard; try to use data-val instead. Attributes starting with data- allow us to store extra information on standard, semantic HTML elements without other hacks.
Example:
<a data-val="2" onclick="showDetails(this)">Test</a>
For the JS function, it can be written as:
function showDetails(obj) {
console.log(obj.dataset.val); // 2
}
References: https://developer.mozilla.org/en-US/docs/Learn/HTML/Howto/Use_data_attributes
#2: Prevent the <a> gets redirected
The initial choice of using <a> is incorrect, as <a> is designed to: (1) redirect to other page via hyperlink specified; or (2) go to certain section in the page using anchor name.
However, you can still stop the redirection using JavaScript (though not suggested). Edit your onclick function as below:
<a data-val="2" onclick="showDetails(this); return false;">Test</a>
Note the return false is added.
p.s. for better coding standard, you should separate JS from HTML structure.
Anchor tag does not have a value attribute, you can read about it in mdn. If you need to assign a value to an anchor tag you can use custom data attribute data-*. In order to get values from data-* in your javascript function can try something like this
const anchor = document.querySelector('a');
const str = anchor.data.value;
And then use it in your ajax logic.
const url = 'getdetails.php';
const params = `q=${str}`;
const http = new XMLHttpRequest();
http.open('GET', `${url}?${params}`, true);
// ... other logic

Ajax beginner confused about DOM reference

I'm learning Ajax and I'm confused about something. In a tutorial, these two lines are included
document.myForm.time.value = ajaxRequest.responseText;
//code
<form name='myForm'>
Name: <input type='text' onChange="ajaxFunction();" name='username' /> <br />
Time: <input type='text' name='time' />
</form>
This code works. I try changing this code to the following
document.tree.innerHTML = document.tree.innerHTML + ajaxRequest.responseText;
//code
<div name='tree' id='tree'></div>
But I get an error "document.tree is undefined". Why?
The document object has a collection of all of the forms on the page, and you can access them by name, which is why document.myForm works. But this does not apply to all elements on the page -- forms are special.
To access your div by id, you can use
document.getElementById("tree")
so your code would become
document.getElementById("tree").innerHTML = document.getElementById("tree").innerHTML + ajaxRequest.responseText;
The reference document.tree tells the browser to look for:
<form name="tree">
What you're looking for instead is
document.getElementById("tree")
document._anything_ is a DOM0 model and it don't have support for all named elements, only forms, images, frames and something more. Now main model is DOM2. Main difference DOM2 from DOM0 is using methods like:
document.getElementById
document.getElementsByTagName
document.getElementsByClassName
instead of document tree walking:
document.anyChild._childOfAnyChild_
In your case better use document.getElementById("tree") instead of document.tree
Change:
document.tree
To:
document.getElementById("tree")
Or better yet, change:
document.tree.innerHTML = document.tree.innerHTML + ajaxRequest.responseText;
To:
var tree = document.getElementById("tree");
tree.innerHTML = tree.innerHTML + ajaxRequest.responseText;
The first method is a throwback to the "dom 0" days before the modern DOM existed. It works because you can reference forms directly by name, but you can't reference other DOM objects the same way. Regardless, this method should be avoided. The better way is to use document.getElementsById('tree'); or, with jQuery, $('#tree').
I strongly recommend reading https://eloquentjavascript.net/
The name attribute is largely used when dealing with forms. When you submit the form, the server will receive the form name as well as the name of controls within the form. Since forms are treated somewhat special, they can be accessed by name directly off the JavaScript "document" global.
Other HTML elements do not work the same way.
You can check it out using this Fiddle: http://jsfiddle.net/XLvwh/
You would need to use:
document.getElementById("tree").innerHtml = document.getElementById("tree").innerHtml + ajaxRequest.responseText;
I would suggest taking a look at jQuery. It makes dealing with DOM elements a lot easier. For instance, to do the same in jQuery would look like:
var treeHtml = $('#tree').html() + ajaxRequest.responseText;
$('#tree').html(treeHtml);
Check it out # http://jquery.com

Getting JSF-defined component with Javascript

I'm creating an interface using JSF, and I'd like the value of one text field to provide the default for a second if the second hasn't yet been set. The crucial code will look something like this:
<h:outputScript>
function suggestValue2() {
var value2 = document.getElementById('value2').value;
if (value2 == "") {
document.getElementById('value2').value = document.getElementById('value1').value;
}
}
</h:outputScript>
<h:inputText
id="value1"
onblur="suggestValue2();" />
<h:inputText
id="value2" />
The problem is this doesn't actually work. The actual IDs of those two input elements get prefixed with some JSF-generated values, which tanks the getElementById calls. What's the best way for me to accomplish what I'm trying to accomplish here?
Edit: I should note that this is going to appear inside a composite component, which could wind up appearing multiple times on a single page. JSF dynamically setting the actual ID represents desired behavior.
Bind the component to the view,
<h:inputText binding="#{input1}" ... />
so that you can just print its client ID elsewhere in the view by UIComponent#getClientId().
<h:outputScript>
var input1 = document.getElementById('#{input1.clientId}');
// ...
</h:outputScript>
As you mentioned that you're inside a composite component, it may be good to know that composite component's own client ID is already available via #{cc.clientId}. So the more recommended alternative would be:
<cc:implementation>
<h:outputScript>
var input1 = document.getElementById('#{cc.clientId}:input1');
// ...
</h:outputScript>
...
<h:inputText id="input1" ... />
...
</cc:implementation>
See also:
Integrate JavaScript in JSF composite component, the clean way
Jsf uses a concept "naming containers" which says the id need not be unique within a provided container. Provided the container has an Id. So if you are not giving an Id to the container jsf appends the unpredictable values before the element. With id for container it becomes containerid:elements id. And this can be used in JavaScript reliably

Problem using dynamically added html-object from javascript

I have a problem with dynamically including an object-tag in my html.
We have a external service which we call to get some html-fragment, it includes an object-tag, a script and a simple html-form. I take that content and add it to a div in my page and then try to execute the script that uses the included object. When i debug using Firebug I can see that the code is correctly inserted in the page but the script gets an error when it tries to access the object. It seems to me that the object isn’t initialized. Let me show you some code to exemplify what I mean.
getFragment makes an ajax call using jQuery to get the content.
var htmlSnippet = RequestModule.getFragment( dto );
$('#plugin').html( htmlSnippet ).hide();
The included content in plugin-div looks like this
<div id="plugin" style="display: none; ">
Browser:
Chrome
<object name="signer" id="signer" type="application/x-personal-signer2"></object>
<form method="POST" name="signerData" action="#success">
<input name="nonce" value="ASyhs..." type="hidden">
<input name="signature" value="" type="hidden">
<input name="encodedTbs" value="U2l..." type="hidden">
<input name="provider" value="nexus-personal_4X" type="hidden">
<input type="submit" onclick="doSign()" value="Sign">
</form>
</div>
The javascript that tries to use the “signer” object looks like this:
function doSign(){
var signer2 = document.getElementById("signer");
retVal = signer2.SetParam('TextToBeSigned', 'some value...');
... and then some more
}
It’s when i call the signer2.SetParam method that I get an error saying
Object #<an HTMLObjectElement> has no method 'SetParam'
But when I use the original page where the content is loaded when the page loads the script works so I know that the ‘SetParam’ method exists on the object and that the script works. But somehow it doesn’t work when I dynamically add it to the page afterwards.
I’ve Googled this a lot the last couple of days with no luck.
Does anyone have any idea on how to get this to work?
Best regards,
Henrik
First of all Object tag is not fully supported in all browsers (Source)
Next, from my experience, jQuery (which heavily relies on document.createDocumentFragment) sometimes fails to attach/trigger events on dynamically created/cloned DOM nodes, which could explain why your object failed to initialize.
That said, to try and fix your problem, I suggest using native document.createElement and document.appendChild methods instead of jQuery.html. You can try document.innerHTML but if that fails, you can always go with the ones I mentioned earlier.
My suggestion is to either alter your service to replace:
<script type="text/javascript">
function addElement(parentid, tag, attributes) {
var el = document.createElement(tag);
// Add attributes
if (typeof attributes != 'undefined') {
for (var a in attributes) {
el.setAttribute(a, attributes[a]);
}
}
// Append element to parent
document.getElementById(parentid).appendChild(el);
}
addElement('plugin', 'object', {name:"signer",id:"signer",type:"application/x-personal-signer2"});
</script>
OR if you cannot change the content that is returned by the service, run this after you include the content onto your page:
<script type="text/javascript">
/*
* Goes through al the object tags in the element with the containerid id
* and tries to re-create them using the DOM builtin methods
*/
function reattachObjectTags(containerid) {
jQuery('#'+containerid+' object').each(function(){
var attrs = {}, el = this;
// We're insterested in preserving all the attributes
var saved_attrs = {}, attr;
for(var i=0; i < el.attributes.length; i++) {
attr = el.attributes.item(i);
if(attr.specified) {
saved_attrs[attr.nodeName]=attr.nodeValue;
}
}
this.parentNode.removeChild(this);
var new_element = document.createElement('object');
for (var a in saved_attrs) {
new_element.setAttribute(a,saved_attrs[a]);
}
document.getElementById(containerid).appendChild(new_element);
});
}
// Do your stuff
var htmlSnippet = RequestModule.getFragment( dto );
$('#plugin').html( htmlSnippet ).hide();
// reattach all the object elements in #plugin
reattachObjectTags('plugin');
</script>
THIS IS ALL UNTESTED -
I typed this off the top of my mind, since I don't have the means to fire up IE and test this.
For a jQuery solution, I think this should work:
$("input:submit").click(function(){
$("#signer").append('<param name="TextToBeSigned" value="some value ...">');
... and then some more
});
Might want to give the submit button a class or an id and use that as a selector, if you have multiple forms on that page though.
Hope this helps.
I've set up a test script here: https://dl.dropbox.com/u/74874/test_scripts/object.html
If you open up Firebug/Web Inspector, you'll see that the SetParam method is in-fact, not defined. I don't know what it's supposed to do, but it's not defined in either case. If you're trying to add <param> tags to your embed, you could use the DOM API to do that. There is some code in the test script that does that, but I'll paste it here anyway:
var obj_signer = document.getElementById('signer');
var obj_p = document.createElement('param');
obj_p.id = "myp2";
obj_p.name = "TextToBeSigned";
obj_p.value = "some value ...";
obj_p.setAttribute('valueType', 'ref');
obj_signer.appendChild(e);
Or be faster using jQuery:
$("#signer").append("<param id='myp2' name='TextToBeSigned' value='some value ...' valueType='ref'></param>");

User Definable Attributes on HTML Elements?

What I need to do is be able to store some piece of data about an element.
For example, lets say I have a list item <li>, and I want to store some data about it in the element, like "This is element 1 from XYZ".
The only way I know how to do this (which I don't want to do if I can avoid) is this:
<li id='item1'>
Item 1
<!--This is element 1 from XYZ-->
</li>
<script>
// read it something like this
var e = document.getElementById('item1').innerHTML;
// then parse out the comment, etc.
// --
</script>
What I really want is something like this (which I am fairly certain is not possible):
// example
<li id='item1' userdata='This is element 1 from XYZ'>Item 1</li>
.. of course, I would like to be able to access it somehow via javasscript.
Alternatively, is there some other way to achieve this?
Thanks -
You can access your userdata="" attribute from JavaScript. Just do:
var theData = document.getElementById('item1').getAttribute('userdata');
If you want to do it the HTML5 way, then you would use attributes named data-*, e.g.:
<li id='item1' data-foo='This is element 1 from XYZ'>Item 1</li>
that way it will still be valid (i.e., it'll make you feel better for not using an invalid attribute). New browsers will support accessing the data-* attributes like so:
var theData = document.getElementById('item1').data.foo;
but I don't think that is implemented widely enough to rely upon yet.
If you do want to store the data in a comment (although I'd advise going the attribute route instead) you could do something like:
var e = document.getElementById('item1');
var n = e.firstChild;
while (n && n.nodeType != Node.COMMENT_NODE) {
n = n.nextSibling;
}
// now n.nodeValue will have the comment contents
(No guarantees about whether IE likes any of the above.)
You can't add arbitrary elements to HTML. Well you can but it won't be valid and beheaviour is undefined. For this kind of thing I tend to use jQuery. It has a data() call that can add arbitrary data to an element. I believe it encodes it in the class attribute but the implementation is not important.
You could do this yourself but why bother? It's easy to get wrong by putting the wrong characters in, not correctly escaping/unescaping data or inadvertently destroying informatino. Instead just do:
$("#item1").data({source: "Thsi is element 1 from XYZ"});
Since you can accept adding comments, a better solution would be to add a span element with the content you wanted..
<span class="data">.....</span>
you define your data class to have display:none and it is invisible ...
this way you can have access to it with the normal DOM traversing methods..
You can use setUserData() and getUserData() function
http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-setUserData
For example:
<html>
<head>
<script type="text/javascript">
function set(){
var a = document.getElementById("testElement");
a.setUserData("testData", "Some text", null);
}
function get(){
var a = document.getElementById("testElement");
alert(a.getUserData("testData"));
}
</script>
</head>
<body>
<span id="testElement"/>
<form>
<input type="button" value="setUserData" onclick="set()"/>
<input type="button" value="getUserData" onclick="get()"/>
</form>
</body>
If you don't need the HTML to be valid, you can make any attribute you want, and you can access it in Javascript by calling the getAttribute method.
You can add a nested invisible element with an id and your data as the innerText. Use the style attribute to make sure it's invisible.
But it all really depends on what you're trying to achieve. Could you elaborate more?
// If you want to include data in the html why not give it its own node?
.hiddendata{
display: none
}
<li id= 'item1'>
Item 1
<span class= "hiddendata"> This is element 1 from XYZ</span>
</li>
function readHiddenData(who){
var A= [], n= who.firstChild;
while(n){
if(n.className= 'hiddendata'){
A[A.length]= n.textContent || n.innerText || '';
}
n= n.nextSibling;
}
return A;
}
function showHiddenData(who){
var n= who.firstChild;
while(n){
if(n.className= 'hiddendata'){
n.style.display= "inline"
}
n= n.nextSibling;
}
}

Categories

Resources