Problem trying to select next item in JQuery - javascript

I'm trying to select the next element to add the class image-radio-selected with JQuery.
My html is like
<div id="photocall">
#foreach ($photocalls as $photocall)
<div class="d-inline-block mx-1">
<div style="background-image: url('')" class="photocallThumb image-radio-selected"></div>
</div>
#endforeach
<input>
</div>
Im trying to:
$( "#mybutton" ).on("click", function() {
var selected = $('.photocallThumb.image-radio-selected'); // working
selected.next('.photocallThumb').addClass('image-radio-selected'); // not working
});
After 2 hours, trying to solve, reading doc,
I'm more confused than when I started...
what am I doing wrong?

One method is you will need to get out of the parent div, then do a next for the parent.
$( "#mybutton" ).on("click", function() {
var selected = $('.photocallThumb.image-radio-selected');
selected.parent(".d-inline-block").next(".d-inline-block").find('.photocallThumb').addClass('image-radio-selected'); // not working
});
.image-radio-selected{border:1px solid #ff00aa;}
.mx-1{width:100px;height:100px;border:1px solid #000000;}
.d-inline-block{display:inline-block;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="photocall">
<div class="d-inline-block mx-1">
<div style="background-image: url('')" class="photocallThumb image-radio-selected"></div>
</div>
<div class="d-inline-block mx-1">
<div style="background-image: url('')" class="photocallThumb"></div>
</div>
<div class="d-inline-block mx-1">
<div style="background-image: url('')" class="photocallThumb"></div>
</div>
<div class="d-inline-block mx-1">
<div style="background-image: url('')" class="photocallThumb"></div>
</div>
<button type="button" id="mybutton">next</button>
</div>

JQuery's next method selects the next sibling of the selected element. However, since your photocallThumb div is inside a d-inline-block div, it has no siblings. You'd have to go back up a level, then find the next photocallThumb, maybe something like selected.parent().find('.photocallThumb').eq(0).
However, an even better pattern that will help you avoid bugs like these is called templating. Basically, on the client side, you have an html template string, and you pass it data that represent your current state. In your case, you'd pass it an array of javascript objects, each one with an image url and an isSelected boolean. Then, when your state changes, instead of using jquery to try to fix what's changed, you just rerender your template and replace your html element's content with the newly rendered template, and it's now magically in the correct state. This is the pattern favored by large frameworks like React and Angular.
Here's an example from lodash that renders a list of usernames:
// Use the "evaluate" delimiter to execute JavaScript and generate HTML.
var compiled = _.template(
`<% _.forEach(users, function(user) { %>
<li><%- user %></li>
<% }); %>`);
compiled({ 'users': ['fred', 'barney'] });
// => '<li>fred</li><li>barney</li>'

Related

Copy HTML from element, replace text with jQuery, then append to element

I am trying to create portlets on my website which are generated when a user inputs a number and clicks a button.
I have the HTML in a script tag (that way it's invisible). I am able to clone the HTML contents of the script tag and append it to the necessary element without issue. My problem is, I cannot seem to modify the text inside the template before appending it.
This is a super simplified version of what I'd like to do. I'm just trying to get parts of it working properly before building it up more.
Here is the script tag with the template:
var p = $("#tpl_dashboard_portlet").html();
var h = document.createElement('div');
$(h).html(p);
$(h).find('div.m-portlet').data('s', s);
$(h).find('[data-key="number"]').val(s);
$(h).find('[data-key="name"]').val("TEST");
console.log(h);
console.log($(h).html());
console.log(s);
$("div.m-content").append($(h).html());
<script id="tpl_dashboard_portlet" type="text/html">
<!--begin::Portlet-->
<div class="m-portlet">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<h3 class="m-portlet__head-text">
<span data-key="number"></span> [<span data-key="name"></span>]
</h3>
</div>
</div>
<div class="m-portlet__head-tools">
<ul class="m-portlet_nav">
<li class="m-portlet__nav-item">
<i class="la la-close"></i>
</li>
</ul>
</div>
</div>
<!--begin::Form-->
<div class="m-portlet__body">
Found! <span data-key="number"></span> [<span data-key="name"></span>]
</div>
</div>
<!--end::Portlet-->
</script>
I'm not sure what I'm doing wrong here. I've tried using .each as well with no luck. Both leave the value of the span tags empty.
(I've removed some of the script, but the variable s does have a value on it)
You have two issues here. Firstly, every time you call $(h) you're creating a new jQuery object from the original template HTML. As such any and all previous changes you made are lost. You need to create the jQuery object from the template HTML once, then make all changes to that object.
Secondly, the span elements you select by data-key attribute do not have value properties to change, you instead need to set their text(). Try this:
var s = 'foo';
var p = $("#tpl_dashboard_portlet").html();
var $h = $('<div />');
$h.html(p);
$h.find('div.m-portlet').data('s', s);
$h.find('[data-key="number"]').text(s);
$h.find('[data-key="name"]').text("TEST");
$("div.m-content").append($h.html());
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script id="tpl_dashboard_portlet" type="text/html">
<div class="m-portlet">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<h3 class="m-portlet__head-text">
<span data-key="number"></span> [<span data-key="name"></span>]
</h3>
</div>
</div>
<div class="m-portlet__head-tools">
<ul class="m-portlet_nav">
<li class="m-portlet__nav-item">
<i class="la la-close"></i>
</li>
</ul>
</div>
</div>
<div class="m-portlet__body">
Found! <span data-key="number"></span> [<span data-key="name"></span>]
</div>
</div>
</script>
<div class="m-content"></div>
In my case only this is working:
var template = $('template').clone(true, true); // Copies all data and events
var $h = $('<div />');
$h.html(template);
$h.find('.input-name').attr('value', "your value here"); // Note: .val("your value here") is not working
$('.list').prepend($h.html());

Container structure issue with JQuery - Containing containers

I'm finishing up a memory game for school and I'd really like the cards to flip with a CSS animation, which on it's own is pretty straight forward. However I'm pretty new to JavaScript and JQuery which is leading to some trouble with achieving the proper container structure I need to make the cards flip when they are clicked.
Presently the game pieces generate within the board as follows:
const generate=(cards)=>{
cards.forEach(function(card, i) {
$(".gameBoard")
.append($("<div>").addClass("front")//
.append($("<div>").addClass("back").append($("
<img>").attr("src", cards[i]))));
});
};
OR:
<div class="gameBoard>
<div class="front"></div>
<div class="back"><img src="cards"></div>
</div>
But in order for the animation to function properly both the front and back divs need to exist in the same container like this:
<div class="gameBoard>
<div class="flip">
<div class="front></div>
<div class="back"><img src="cards></div>
</div>
</div>
How can I add the div I need (.flip) but have it contain the front and back divs, not just append on to the other divs being generated within the .gameboard container.
Thanks.
It's much simpler to create your DOM using template literals rather than jQuery methods. That way you just describe the HTML as you're accustomed to.
const generate=(cards)=>{
cards.forEach(function(card, i) {
$(".gameBoard").append(`
<div class=flip>
<div class=front></div>
<div class=back><img src="${cards[i]}"</div>
</div>
`);
});
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js"></script>
<div class=gameBoard></div>
You'll notice the ${cards[i]}, which lets you perform string interpolation by executing at runtime the code in the braces.
Here's a vanilla JS version.
const generate=(cards)=>{
var gb = document.querySelector(".gameBoard");
cards.forEach(card =>
gb.insertAdjacentHTML("beforeend", `
<div class=flip>
<div class=front></div>
<div class=back><img src="${card}"</div>
</div>
`)
);
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<div class=gameBoard></div>
It also uses card instead of cards[i], and an arrow function for the callback.
And this one performs a single append.
const generate=(cards)=>{
document.querySelector(".gameBoard")
.insertAdjacentHTML("beforeend", cards.map(card =>
` <div class=flip>
<div class=front></div>
<div class=back><img src="${card}"</div>
</div>`).join(""));
};
generate([
"https://dummyimage.com/180x120/f00/fff.png&text=one",
"https://dummyimage.com/180x120/0f0/fff.png&text=two",
"https://dummyimage.com/180x120/00f/fff.png&text=three",
]);
<div class=gameBoard></div>

Why is this method still getting called after the first result is given?

I'm developing a MeteorJS app and I'm dynamically adding a data-target attribute for activating a modal and three of the additions are acting normal and producing correct results, yet the first target repeatedly calls the method which eats up CPU and RAM, and breaks what I'm trying to accomplish. The one fix I found is to add null/hidden tag that calls the method first but this doesn't fix the issue repeated calls that eat up CPU and RAM.
HTML:
{{#each getCategories}}
<div class="row hidden-sm hidden-md hidden-lg visible-xs-block">
<div class="col-xs-10">
<h2>{{this.category}}</h2>
</div>
<div class="col-xs-2">
<!--
Must have this null element for some odd reason
the first call to getUniqueID continuous runs and breaks
this functionality so this a quick fix
-->
<!-- <null style="display:none;">{{getUniqueID}}{{getUniqueID}}</null> -->
<span data-toggle="modal" data-target="#submit_{{getUniqueID}}"><i class="fa fa-plus-circle fa-3x" aria-hidden="true"></i></span>
{{>cardSumbitModal category=this.category id=getUniqueID}}
</div>
</div>
{{#each cards this.category}}
<div class="row hidden-sm hidden-md hidden-lg visible-xs-block">
<div class="col-xs-12">
{{>card id=_id color=../color}}
</div>
</div>
{{/each}}
{{/each}}
JS:
getUniqueID: function(){
var id = Session.get("roomNumber");
var count = Session.get("getUniqueID_CallCount");
id = id + count;
if(!Session.get("pairSet")){
Session.set("pairSet", true);
}else{
count++;
Session.set("pairSet", false);
}
Session.set("getUniqueID_CallCount",count);
return id;
}
Because helpers are designed to be reactive they can run much more often than you expect. You should setup the getCategories helper to map an array and include the unique id as a an extra column so it doesn't need to be computed with every document/row.

Find sibling with Javascript and Hammer.js

I have made a simple system which detects double taps. I want to show a heart icon when someone double taps on an image, just like on Instagram.
This is what my code looks right now:
var elements = document.getElementsByClassName('snap_img');
[].slice.call(elements).forEach(function(element) {
var hammertime = new Hammer(element),
img_src = element.getAttribute('src');
hammertime.on('doubletap', function(event) {
alert(img_src); // this is to test if doubletap works
// Some javascript to show the heart icon
});
});
This is what the HTML looks like:
<div class="snap_item">
<div class="snap_item_following_info">
<img class="snap_item_following_img" src="res/stat/img/user/profile/small/1.fw.png" alt="#JohnDoe" />
<a class="snap_item_following_name" href="#">#JohnDoe</a>
<div class="snap_too">
</div>
</div>
<img class="snap_img" src="res/stat/img/user/snap/43/2.fw.png" alt="#ErolSimsir" />
<div class="like_heart"></div>
<div class="snap_info">
<div class="snap_text">
LA is the shit...
<a class="snap_text_hashtah" href="#">#LA_city_trip</a>
</div>
<div class="snap_sub_info">
<span class="snap_time">56 minutes ago</span>
<div class="like inactive_like">
<div class="like_icon"></div>
<div class="like_no_active">5477</div>
</div>
</div>
</div>
</div>
So when the element 'snap_img' is double tapped, I need to get the element 'like_heart' which is one line below the snap_img element. How do I get that sibling element and fade it in with JQuery?
Like this
[].slice.call(elements).forEach(function(element) {
var hammertime = new Hammer(element),
img_src = element.getAttribute('src');
hammertime.on('doubletap', function(event) {
alert(img_src); // this is to test if doubletap works
$(element).next().text('♥').hide().fadeIn();
});
});
P.S. I've added that heart text, since the sibling was empty.
On the event handler, i would do $(element).parent().find('.like_heart').fadeIn(); So the code is not dependant on the element ordering.
(To clarify to selector: take the parent element which is the div.snap_item and find an element with class like-heart inside it)

Determining which element was clicked and conditionally choosing which method to call

I am attempting to use JQuery to make 3 thumbnails into buttons that each open up their own page element with details regarding the picture.
Right now I have succeeded in making it so that any thumbnail causes a page element (of the class "description") to scroll open and closed when any thumbnail (from the class "thumbnail") is clicked.
How do I check which thumbnail is clicked on so that I can open a different description corresponding to that specific thumbnail? (This is what I was attempting to do with the "select").
var main = function() {
$('.thumbnail').click(function(select) {
var description = $('.game-descriptions').children('.description');
if( description.is(":hidden")) {
description.slideDown("slow");
}
else
description.hide();
});
}
$(document).ready(main);
Use a data attribute to specify what the thumbnail click is targeting, example: data-target="#game-1", add IDs to your descriptions that match and use data() to use the attribute value of #game-1 a jQuery selector.
Here is a demo
JS
$('.thumbnail').click(function() {
var gameId = $(this).data('target');
$(gameId).slideToggle().siblings(':visible').slideToggle();
});
HTML
<img class="thumbnail" data-target="#game-1" />
<img class="thumbnail" data-target="#game-2" />
<div class="game-descriptions">
<div id="game-1" class="description"></div>
<div id="game-2" class="description"></div>
</div>
Any toggling like toggle(), slideToggle(), fadeToggle() handles the is hidden or is visible
jsFiddle
The parameter to the click function is a jQuery event object, which can be useful in adding some event handling logic. However, within the context of the handler, this refers to the element which triggered the click event, and is typically sufficient for any targeted logic.
Assuming the thumbnails and descriptions have similarly named IDs, for example, you can do something like this:
$(function () {
$('.thumbnail').click(function (event) {
var descId = this.id.replace("thumb", "desc");
var description = $('.game-descriptions').children('#' + descId);
// or simply $("#" + descId);
description.toggle("slow");
});
});
HTML
<div>
<div class="thumbnail" id="thumb-1">Thumb 1</div>
<div class="thumbnail" id="thumb-2">Thumb 2</div>
<div class="thumbnail" id="thumb-3">Thumb 3</div>
</div>
<div class="game-descriptions">
<div class="description" id="desc-1">Description One</div>
<div class="description" id="desc-2">Description Two</div>
<div class="description" id="desc-3">Description Three</div>
</div>
Your technique for targeting the correct 'description' will depend on your actual DOM structure, however.
Also note that I substituted the toggle method for your if statement, as the logic you have is equivalent to what it does (i.e. toggling object visibility).

Categories