Join 3,501 readers in helping fund MetaFilter (Hide)


JavaScript/jQuery Selectors
September 4, 2012 8:52 PM   Subscribe

JavaScript Filter: New to JavaScript and jQuery. Need help wrapping my head around selectors.

Scenario: I have a page that lists a bunch of films by their names (each film's name is inside of a <p> tag). Below each film's name is a drop-down list indicating your rating of the film. When you select a rating, I need JavaScript/jQuery to change the CSS class on *just* the tag wrapped around the film title in question's name.

Here's a screenshot, in case that helps.

Due to just lack of knowledge with JavaScript/jQuery, what I have so far is:
$('select').change(function () {

$('p').removeClass("film_title_unwatched").addClass("film_title_watched");

})
This obviously doesn't really work, because it changes the class on *every* tag on the page. I know about some of the jQuery selector filters and advanced selectors (e.g., $('p[attribute="value"]'), but I'm not really finding the right one for this situation.

I essentially need some way to find the specific <p> tag, and *only* change it.

Do I need to assign an arbitrary attribute to the <p> tag in question (like a ID value), which I would then filter by?

Note that I'll also obviously need a way for the user to undo their selection (to say, "Hey, actually, I never saw that film."), by choosing an option from the drop-down that will switch the <p> tag's class back to the original value. Therefore, it seems I need to some kind of if/then logic that determines which class to set on the <p> tag in question based on the value selected by the user.

Note that I'm generating the HTML via PHP, for what that is worth.
posted by JPowers to Computers & Internet (11 answers total) 5 users marked this as a favorite
 
It seems like you should be able to walk upwards from the select element that's calling you back to the containing div, then previous until you reach the preceding p with an appropriate class. Something like $(elt).closest('div').prevAll('p.movietitle').first(), but my jQuery is pretty rusty so that may not be quite right.
posted by hattifattener at 9:03 PM on September 4, 2012


So there's a few ways to do this, one is to try to climb up the DOM.
$('select').change(function () {
// (parent UL > Break Sibling > P .... - I think, you may have to adjust a bit)
$(this).parent().prev().prev().removeClass("film_title_unwatched").addClass("film_title_watched") 
})
Note on that one, you have an unnecessary break tag in there, removing it would make your DOM traversal easier. If you need more space under the "p" tag, add bottom margin to the "p" (note you can add extra classes to it if you need t
<p class="film_title_unwatched film-title">
Another way to do this would be to use some data attributes which are arbitrary values you can add.
<p data-id="somenumber">...

<select data-pid="somenumber">
...
</select>

Then do
$('select').change(function () {
// (parent UL > Break Sibling > P .... - I think, you may have to adjust a bit)
var pid = $(this).attr('data-pid')
// Note the quotes get a bit hectic here
$('p[value="'+pid+'"]').removeClass("film_title_unwatched").addClass("film_title_watched")
})

posted by bitdamaged at 9:08 PM on September 4, 2012


You need to traverse up the tree... but I don't know why the other people are making it so hard.
$('select').change(function () {
$(this).closest('.film_title_unwatched').removeClass("film_title_unwatched").addClass("film_title_watched");
})
Really, I'd make your CSS class something like "film-title" and then make the CSS so that .film-title is equivalent to your current .film_title_unwatched. Then add a new class, ".watched", and make the CSS for your current .film_title_watched equivalent to ".film-title.watched". Then the code is a little cleaner:
$('select').change(function () {
$(this).closest('.film-title').addClass("watched");
})

posted by sbutler at 9:21 PM on September 4, 2012


Wait, maybe I now see why everyone else is making it complicated. Is the select not inside the p tag? Then you'll just want to use .prev('.film_title_unwatched') instead of .closest(...).
posted by sbutler at 9:23 PM on September 4, 2012


prev only grabs the actual previous element in the DOM.

from the change

$(this).prevAll("p").first()

should also grab it.
posted by bitdamaged at 9:43 PM on September 4, 2012


n/m, my jQuery is apparently even rustier than hattifattener! I'd do what he suggested. Completed example.
posted by sbutler at 9:44 PM on September 4, 2012


This has all been extremely helpful. I've played around with all of the various options you guys have provided, and they all seem to work very well. Now I just need to decide what works best (there a few more issues at play here). Thanks to everyone!
posted by JPowers at 9:46 PM on September 4, 2012


Change the HTML and use .on()
posted by rhizome at 10:29 PM on September 4, 2012 [1 favorite]


Some comments:

There's most likely no reason for the div with class user_score_dropdown_list - it just wraps the dropdown. Just apply the class to the select element itself, or use css like
.film_title select {
}
...if the .file_title needs to be on the p tag for some reason. (sbutler's suggestion to toggle a single class .watched class rather than maintaining watched and unwatched classes is a good one and the above assumes that you've adopted it) Excessive divs are a common markup issue, some people are afraid to apply classes and style directly to display elements.

It looks like you're adding layout space with break tags. Always manage space with CSS when possible-the odds that you're going to want exactly the spacing that a break tag provides by default is slim, so you'll wind up either styling the break tag or manipulating space on the surrounding elements anyway in addition to the break-either of these is a bummer.

Design your markup so that your jQuery can select the required DOM element with one selector. If you find yourself chaining DOM manipulation jQuery calls just to identify the correct element, the code won't perform as well and will be harder to maintain-you don't want the next person along to have to chase through three or four jQuery calls just to see what element you want, let alone do anything with it, unless you really absolutely have to. I don't think you really want a p tag- probably a span and then a wrapper div around the section, so that you can use code along the lines of sbutler's to target the appropriate element cleanly and in a performant way. People often struggle with using divs appropriately, and this is a major appropriate use case: to sandbox an area of the markup to make dom manipulation easier for your js.

TL;DR: work to minimize markup, keep styling in the css, and design html to make js dom manipulation as easy as possible.
posted by Kwine at 10:33 PM on September 4, 2012


on lack of preview, rhizome has it right on, although you probably still want a span instead of a p unless there's some kind of weird circumstance.
posted by Kwine at 10:35 PM on September 4, 2012


Lots of great feedback for you in this thread. Here's my take.

The script:
$('.user_score_dropdown_list select').bind('change', function(){
	$(this)
		.parent('.user_score_dropdown_list')
		.prevUntil('.user_score_dropdown_list', 'p')
		.removeClass('film_title_unwatched')
		.addClass('film_title_watched');
	$(this)
		.parent('.user_score_dropdown_list')
		.slideUp();

});

$('a.oops').bind('click', function(){
	$(this).parent('p')
		.removeClass('film_title_watched')
		.addClass('film_title_unwatched')
		.nextUntil('p', '.user_score_dropdown_list')
		.slideDown();
});
This is with markup like this:
<p class="film_title_unwatched"><a href="">Vertigo</a> <a href="#" class="oops">I actually didn't see it.</a></p>
<br><br>
<div class="user_score_dropdown_list">
<select name="test" class="test">
	<option>I haven't seen it</option>
	<option>I absolutely loved it</option>
	<option>I thought it was great</option>
	<option>I thought it was good</option>
	<option>I thought it was OK</option>
	<option>I didn't like it</option>
</select>
</div>
Here's a fiddle: http://jsfiddle.net/artlung/AEKpE/.
posted by artlung at 5:00 AM on September 5, 2012


« Older I'm wondering what sort of pla...   |  What the Hell Happened to Me?... Newer »
This thread is closed to new comments.