So the old JavaScript aficionado and the young jQuery wizard in me are having a little disagreement. Sorry for the long setup, but the heart of the issue is whether to embed onClick code directly in my HTML or to go jQuery-style and and use bind() or click(). I find myself & myself disagreeing on this topic quite often, so I thought I would try generate some discussion on the issue. To explain the issue, the pattern below seems to bring this to the forefront most often.
Typical Example
I'm writing a search for a member directory. On my interface I have various filter criteria like "gender", "member since", and "has profile photo". A criteria looks like this ...
A user can select an option by clicking on the text (e.g. "Female") or choosing the radio button.
When a selection is made the appropriate radio button is selected the text is bold-ed
My html ends up looking something like ...
<div id="FilterContainer_GenderDIV">
<span id="FilterLabel_Gender">Gender:</span>
<span id="FilterSelection_Gender">Any</span>
<span id="FilterChicklet_Gender" class="sprite_MediumArrowDown inline" ></span>
<div id="FilterOptions_GenderDIV">
<input type="radio" id="GenderID" name="GenderID" value="1"/> <a href="" id="FilterOptionLink_CoupleGender_1" >Male</a><br />
<input type="radio" id="GenderID" name="GenderID" value="2"/> <a href="" id="FilterOptionLink_CoupleGender_2" >Female</a><br />
<input type="radio" id="GenderID" name="GenderID" value="0" checked="checked"/> <a href="" id="FilterOptionLink_CoupleGender_0" class="SearchSelectedChoice" >Any</a><br />
</div>
The issue really arises when a user clicks on the text link. At that point I need to know which radio set to change,which link text to bold, and take the newly selected text and change my header label. I see a few options for making this type of scenario work.
Options for making it work
jQuery Injection with Clever element names
I can use jQuery to bind to my elements in a generic fashion. $('#FinderBodyDIV input:radio').click(SearchOption_Click); Then sort out the appropriate ID & text with clever dom inspection. For example, name my hyperlink could be named GenderID_Link_1 where 1 is the ID I should select and GenderID tells me which radio set to change. I could use a combination of '.parents().find()and.siblings()` to find the radio next door and set it with the ID.
This is good because my binding code is simple and my jQuery is separated from my HTML
It's bad because my code functioning now really depends on a brittle HTML structure & naming.
Bind elements individually with eventData
An alternate option is to gather up my set of elements and for each individual element do a 'bind()' passing eventData.
var elements = $('#FinderBodyDIV input:radio');
elements.each ( FunctionWithLogicToBindPassingEventData );
This is satisfying because I've separate the logic for binding event data from a brittle HTML structure.
It's bad because I've simply moved the brittle piece to a new function
It's also bad because I've introduced slowed down (relatively) the binding (more DOM traversal).
Embed onClick code in the HTML
This is where my old JavaScript inclinations keep taking me. Instead of using jQuery to inject bindings, I change my link/radio button HTML to include click handlers. Something like ...
<input type="radio" id="GenderID" name="GenderID" value="1" onClick="SetGender(1,'Male')"/> Male<br />
This is satisfying because i know the values when I'm generating the HTML, so I've simplified my life.
I've removed a lot of dependency on my HTML structure (although not all).
On the down side, I've co-mingled my JS and HTML.
It feels dirty.
So what's a boy to do?
This seems like a fairly common scenario. I find it pops up in quite a few situations besides the one I've described above. I've searched tubes on the interweb, blogumentation, and technical articles on twitter. Yet, I don't see anyone talking about this issue. Maybe I just don't know how to phrase the issue. Whatever the reason, I don't feel good about any of the solutions I've come up with.
It really comes down to how do I associate a clicked element with it's related data--JSON, embedded in HTML names, or otherwise. So what's your take?
The pat answer is that embedding the onClick call in the input element goes against the unobtrusive javascript concept. However, I'm the kind of developer that'll go ahead and use it anyway if it gets the jorb done and the audience is not likely to have javascript disabled.
If I'm understanding the problem correctly, you need a way to do things jQuery-style without relying on html structure and such. Give your radio buttons a class name, for example, the "Male" radio button can have class="Male", and you can select it via jQuery easier.
Bonus: There are some instances where you may need to assign some element more than one class, for example, you are filtering by language and by country. So you can assign some element multiple classes like this:
$('#someElement').addClass('French').addClass('fr-FR');
And select it using either class later.
Related
I am looking to create a chart that generates a series of rows. Each row contains various buttons and text boxes. The row in question is this. Yes, I am using a bunch of divs instead of a list. No, I'm not going to change it at this time as I'm strictly hacking this stuff together so it works, not so it's semantically usable. This is an internal tool, intended for internal use only, and doesn't need to be perfectly semantic, nor does it need to be used by screen readers. I've also removed irrelevant information/names/labels/classes.
<div class="row">
<div class="rcColumn">
<button></button>
</div>
<div class="accColumn">
<input></input>
<button>Copy</button>
</div>
<div class="dollarColumn">
<input></input>
<button>Copy</button>
</div>
<div class="toolbar">
<button>Clear</button>
<button id="deleteLineButton" onclick="deleteLine();" title="Delete this line">X</button>
</div>
</div>
Yes, I'm aware it's not really good to use onclick in the HTML tags, but I am because again, whipping this up quick. I slowly make things more and more clean over time but for now I'm making it work. Don't kill me over it.
That said, the function I made is as follows:
function deleteLine() {
$("#deleteLineButton").parent().parent().remove();
}
Now, there are functions I created that make a new line, effective adding that HTML exactly underneath. It works fine. However, while deleteLine() appropriately deletes the topmost div in the HTML example, it always goes for the topmost line, no matter how many new lines I make.
I tried making an alternate version like so:
function deleteLine() {
$(this).parent().parent().remove();
}
But that doesn't work either. I also tried using "this" instead (with quotes), but still no dice. The delete line button no longer works, and I get no error in the console.
I don't want to use IDs or Classes because I might make an arbitrary amount of lines. How can I better select the appropriate row? Was I on the right track using 'this'?
Change the button id to a class since ids must be unique
<button class="deleteLineButton" title="Delete this line">X</button>
Then rather than using outdated inline onclick you can add a jQuery event listener that traverses to closest('.row') which is more understandable to read than chaining parent().parent()
$('.deleteLineButton').on('click', function(){
$(this).closest('.row').remove()
})
I want to make it so that everytime you click on an 'h2' tag, the 'input' inside gets selected and the 'h2' tag changes background, but if another 'h2' tag is clicked, the current highlight and 'input' selection changes accordingly.
problem is that I have 3 different that do the same and with my code all the 3 forms are affected rather one. How do i limit my changes to only be contained to that form. Here is some code for clarification
'
<form>
...
<h2 onclick="document.getElementById(1001).checked='True'
$('h2').removeClass('selected');
$(this).addClass('selected');
">
CONTENT
<input type="radio" name="radio" id="1001" value="1001" />
</h2>
...
</form>
I think this is what you need:
$("form h2").click(function() {
var form = $(this).closest("form");
$("#"+$(this).text().trim()).prop('checked', true);
form.find('h2').removeClass('selected');
$(this).addClass('selected');
});
All the changes are confined to elements within this form.
#Barmar has answered this from a code perspective. Let's see about helping pass some knowledge on. There are some non-code concepts that will help you avoid this problem in the future.
This is a common, and very frustrating, mistake with understanding how JavaScript and HTML work together. The JavaScript doesn't full "belong" to the h2 element when you put it in the onclick attribute, it just runs when you click it. The JavaScript can touch anything in the rest of the page. That's why $('h2').removeClass() is selecting every h2 element.
In general, you should do a few things to help your confusion.
Put your JavaScript in script blocks, or better yet separate
files, not inside HTML elements.
Use jQuery to only deal with the
one h2 at a time (as Barmar suggested).
Do some reading how how jQuery selectors work. jquery.com documentation is very good, it will be time well spent.
I've got an form with the ability to have an infinite number of user-added inputs.
As an example, imagine a job application with an "add references" section. There's a "+" button, as well as an "Remove" next to any already added reference.
Here's an example template
<script type="text/template" id="referenceTmpl">
<div>
<h2>Reference No. <%= index %></h2><a id="removeRef<%= index %>">Remove</a>
Name: <input type="text" name="references[<%= index %>].name" />
Email: <input type="text" name="references[<%= index %>].email" />
(...)
</div>
</script>
When any "reference" is removed, I'd like to renumber the others. As the form inputs may already contain unsaved data, I need to do so without fully re-rendering the template. I'd like to do this in a salable way (one that doesn't require too much extra code per input) as the solution may be used in a more complex application.
Feel free to assume I'm using jQuery if the solution could benefit from it.
Any ideas?
The best way to do this kind of thing without hacking into the code, is to use a model(list) for your so called "references".
You should try Backbone.js in combination with Underscore.js:
Backbone.js documentation
There is an TODO example application that does exactly what you want:
Application code-breakdown
Example application
It might take some time to set up, but when you are finished, you will get work done much quicker.
I'm trying to create a simple extension for personal use. It's partially from laziness, and partially from an urge to learn. I've never made extensions before, but I've been looking at the documentation. Now I just need to write the code. What I'm trying to do, is when the browser loads a certain page, to insert text into a specific form. The form is as follows
<div id="set_tags" class="advanced_option">
<label for="post_tags" class="inline_input_label" id="post_tags_label"
onclick="Element.remove($(this))"
style="left:8px; right:auto; text-align:left">tags</label>
<input id="post_tags" name="post[tags]" type="text"/>
</div>
I haven't worked much with javascript, so is there a way to add the text "Music" to this when the page is loaded?
You can use the onload function to start your function.
http://javascript.about.com/library/blonload.htm
Since you are new to javascript you may want to get familiar with unobtrusive javascript (http://www.onlinetools.org/articles/unobtrusivejavascript/chapter4.html) which I find is a better way to write javascript, as you can then easily comment out javascript and see how it works when that is disabled. But, it would be easier to learn this in the beginning.
To get the input tag you can use document.getElementById() which would be something like:
var elem = document.getElementById('post_tags');
Then, to add text to this field there should be a value property in your input definition above, and you would just do:
elem.value = "Music";
document.getElementById("post_tags_label").appendChild(
document.createTextNode("Music"));
I'm assuming that you want to put it at the end of the element post_tags_label.
This is really easy to do if you use GreaseMonkey. It's perfect for personal changes you want to make to web pages, etc.
As it's recommended, Javascript must be kept in a physically separate file (to be unobtrusive). So how do I access a particular element in particular page? should I detect those elements by id? that would mean 2 elements can't have the same id even if they are not located in the same page.
Well, for instance, using the Html helpers methods generates element's name + id from the model's properties. If I use the same model in several pages, many elements will have the same id. How can I target them in different pages. By the way, CSS work the same way.
EDIT
Let's say I've this
<% = Html.TextBoxFor(x => x.FirstName)%>
It will generates
<input type = "Text" name = "FirstName" id = "FirstName"/>
Let's say I've this textbox in 2 differen pages. If want, for instance, to disable the textbox located in page A, how do I do it knowing they are two of them in 2 different pages. How do I discriminate them from my external javascript file?
Thanks for helping
I suggest that for each page the uses the same model, you create a wrapper div
<div class="pageA">
// the model stuff here
<% = Html.TextBoxFor(x => x.FirstName)%>
</div>
<div class="pageB">
// the model stuff here
<% = Html.TextBoxFor(x => x.FirstName)%>
</div>
and then use Jquery selectors to get the correct element $(".pageA input[name='FirstName']") (not sure if this syntax is correct).
You cannot have multiple elements on the page with the same id. That isn't valid HTML.
So when you use the same HTML helper multiple times, you need to pass different names:
<%: Html.TextBox("Foo", Model.Foo) %>
<%: Html.TextBox("Bar", Model.Bar) %>
Correct me if i'm wrong, but are you saying, you have some elements with the same id, on multiple pages, that you want to attach different behaviour to? If so then this could help you out. if not, then what Craig said.
You can use more specific selectors, or give your selectors context
have a look at the documentations here: http://api.jquery.com/jQuery/
under this header:
jQuery( selector, [ context ] )
it explains a bit about selects and context. you should be able to use this and some creative page building to target the right element with your jQuery.
So you have two files, each with a text field with the id "FirstName". When you're script runs on Page A you want to disable the field, but not when your script runs on Page B.
Is the structure of the two pages identical? I suspect not if you're handling these fields differently. Use the context to your advantage. Like if the one on Page A is in a div with id "thisDiv" and the other is in a div with id "thatDiv" you could document.getElementById('thisDiv'). If you get an element then disable the field, if not do nothing.
If you want a more specific answer you're going to have to give us more context.
Well JavaScript may be kept in a separate file or not, but it is definitely included as part of the HTML send to the browser for a particular page. I Hope I've understood your question, but, generally if you have you JavaScript code in a file, lets say utils.js then in your html generated should include (probably within the <head> tag):
<script type="text/javascript" src="/path/to/utils.js"></script>
The script get included in the page, and when the browser encounters this, it loads and then runs the script, for that page. Therefore, it is not important what the ids for elements on different pages are.
Does that make sense, or have I completely misunderstood your question?
Update:
Ok, so based on your comments, I think I understand. You have
//Page 1
//When loaded, this input should flash blue via javascript for example
<input id="firstName" .../>
And
//Page 2
//When loaded, this input has some other fancy effect/behaviour
<input id="firstName" .../>
Well in this case, as far as I see, there are only 2 types of answers. Have two seperate external js files, one per page and this way you can change to your hearts content ...OR... have some sort of hidden field in your page that tells your script what page it is looking at (this seems hacky)
<input type="hidden" value="page1"/> //etc..