Toggling an element that appears twice on a page - javascript

I am working with an html component that appears twice on a page.
The issue I am having is that when I click one component to toggle it's content (like an accordion) both components toggle simultaneously.
How can I rewrite the JS to toggle each component when I click them separately, without giving the components different classes and rewriting the js twice?
This is what I have so far;
var bindEventsToUI = function () {
$(".details").click(function(e){
$(".content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
};

Try this:
$(".details").click(function(e) {
$(this).children(".content").slideToggle("slow");
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class='details'>Testing 123
<div class='content'>> Content 123</div>
</div>
<div class='details'>Testing 456
<div class='content'>> Content 456</div>
</div>

You will need to have a different identifier for each element. I would recommend an id:
var bindEventsToUI = function () {//element) {
$("#id1 .details").click(function(e){
$("#id1 .content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
$("#id2 .details").click(function(e){
$("#id2 .content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
//..and so on
};
Or, you could use the closest function:
var bindEventsToUI = function () {//element) {
$(".details").click(function(e){
$(this).closest(".content").slideToggle("slow", function() {
$(e.target).hide().siblings().show();
});
});
};

Related

How to use hide() in Javascript

I am trying to hide a div the id of which I stored in a variable named 'post_container_id'. My code is:
document.addEventListener('DOMContentLoaded', function() {
// Add event listener to following button
document.querySelectorAll('.post-edit').forEach(item => {
var itemid = item.getAttribute('id').slice(5,)
item.addEventListener('click', () => edit_post(itemid));
})
});
function edit_post(itemid) {
var post_container_id = `#post-container-${itemid}`;
(function(){
$(post_container_id).hide(1000);
});
};
This does not hide the div. It does not throw any error either. The function does get triggered (I checked it by logging to console). What am I doing wrong?
There is a mistake here:
(function(){
$(post_container_id).hide(1000);
});
You are just declaring the function, you should also call it:
(function(){
$(post_container_id).hide(1000);
})();
Also, the callback is useless in this case, you can just solve it as:
function edit_post(itemid) {
var post_container_id = `#post-container-${itemid}`;
$(post_container_id).hide(1000);
};
$("#hide").click(function(){
edit_post(1);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="post-container-1">secret</div>
<button id="hide">Click to hide</button>
You can also use vanilla JavaScript to hide/show the element directly by changing the style display property. As follows
function edit_post(itemid) {
const post_container_id = document.querySelectorAll(`#post-container-${itemid}`);
post_container_id.style.display = 'none';
};
Without jQuery,
document.getElementById(post_container_id).style.display = "none"

how to run all functions in one click

I want help in solving this problem
I want to run all functions in one click how can I do this
Is the code of jquery
/*global $*/
$(function () {
'use strict';
$(".click1 button").on("click", function click1() {
$(this).parent(".click1").css("background-color", "#f00");
});
$(".click2 button").on("click", function click2() {
$(this).parent(".click2").css("background-color", "#ff0");
});
$(".click3 button").on("click", function click3() {
$(this).parent(".click3").css("background-color", "#f0f");
});
$("clickAll").on("click", function () {
click1();
click2();
click3();
});
});
You can use trigger function.
/*global $*/
$(function () {
'use strict';
$(".click1 button").on("click", function click1() {
$(this).parent(".click1").css("background-color", "#f00");
});
$(".click2 button").on("click", function click2() {
$(this).parent(".click2").css("background-color", "#ff0");
});
$(".click3 button").on("click", function click3() {
$(this).parent(".click3").css("background-color", "#f0f");
});
$("clickAll").on("click", function () {
$(".click1 button").trigger("click");
$(".click2 button").trigger("click");
$(".click3 button").trigger("click");
});
});
Use .triggerHandler to fire the event without bubbling:
/*global $*/
$(function() {
'use strict';
var clickMe = function(event, parent, bgColor) {
$(event.target).parent(parent).css("background-color", bgColor);
};
var $click1Button = $(".click1 button");
$click1Button.on("click", function(event) {
clickMe(event, ".click1", "#f00");
});
var $click2Button = $(".click2 button");
$click2Button.on("click", function(event) {
clickMe(event, ".click2", "#ff0");
});
var $click3Button = $(".click3 button");
$click3Button.on("click", function(event) {
clickMe(event, ".click3", "#f0f");
});
$(".clickAll").on("click", function() {
$click1Button.triggerHandler("click");
$click2Button.triggerHandler("click");
$click3Button.triggerHandler("click");
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="click1"><button>click1</button></div>
<div class="click2"><button>click2</button></div>
<div class="click3"><button>click3</button></div>
<div class="clickAll"><button>clickAll</button></div>
Since the code executed in each handler is slightly the same, you can use event delegation to reduce the code required and moving what's related to the view in your html markup.
$(function(){
$("#common_ancestor").on('click', function (evt){
let $button = $(evt.target)
// Depending on your markup, you way use closest() or parents() instead of parent() here.
, $parent = $button.parent()
, clickSelector = '[class*="click"]'
;
// Test which button was triggered based on its parent
if ($parent.is('.clickAll')) {
$(this).find(clickSelector).each(function() {
this.style.backgroundColor = $(this).data('color');
})
} else if ($parent.is(clickSelector)) {
$parent[0].style.backgroundColor = $parent.data('color');
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="common_ancestor">
<div class="click1" data-color="#f00"><button>click1</button></div>
<div class="click2" data-color="#ff0"><button>click2</button></div>
<div class="click3" data-color="#f0f"><button>click3</button></div>
<div class="clickAll"><button>clickAll</button></div>
</div>
Pros on using event delegation:
You don't need to register listener again when injecting additionnal <div class="clickn" data-color="#ff0"><button>clickn</button></div> elements in "#common_ancestor" div.
Only one function object is created in memory.
Cons:
- If you have non trivial behavior or code that differs slightly between targes, your uniq handler might quickly become bloated, which might affect code readability a lot in the long term.

several javascript calls made into one

Hi I am very new to javascript and I have made a lot of buttons on a site which have different calls. They are all the same minus the results which are pretty much like hide.1 and show.2 etc. Its pretty big but there must be a way to make it smaller? Its no biggy just bad coding that i would like not to get in the habbit of doing. And the only reason for (a) after some of them is that the call doesnt works twice, so when i set it show in a full size screen and a mobile screen it does not work right? Thanks
The code is :
$(document).ready(function() {
$('#btn-continue').on('click', function() {
$('#loginbox').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next1').on('click', function() {
$('#1box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next2').on('click', function() {
$('#2box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back2').on('click', function() {
$('#2box').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next2a').on('click', function() {
$('#2box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back2a').on('click', function() {
$('#2box').hide();
$('#1box').show();
})
});
$(document).ready(function() {
$('#btn-next3').on('click', function() {
$('#3box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back3').on('click', function() {
$('#3box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next3a').on('click', function() {
$('#3box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back3a').on('click', function() {
$('#3box').hide();
$('#2box').show();
})
});
$(document).ready(function() {
$('#btn-next4').on('click', function() {
$('#4box').hide();
$('#5box').show();
})
});
$(document).ready(function() {
$('#btn-back4').on('click', function() {
$('#4box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-next4a').on('click', function() {
$('#4box').hide();
$('#5box').show();
})
});
$(document).ready(function() {
$('#btn-back4a').on('click', function() {
$('#4box').hide();
$('#3box').show();
})
});
$(document).ready(function() {
$('#btn-back5').on('click', function() {
$('#5box').hide();
$('#4box').show();
})
});
$(document).ready(function() {
$('#btn-back5a').on('click', function() {
$('#5box').hide();
$('#4box').show();
})
});
And the calls work like this :
<div class="col-sm-6 controls hidden-xs">
<div><button id='btn-back2' name ='back2' type='button' class='btn btn-success'>Back</button></div></div><div class="hidden-xs"><button id='btn-next2' name ='next2' type='button' class='btn btn-primary'>Next</button></div>
</div>
<div class="col-xs-6 controls hidden-sm hidden-md hidden-lg">
<div><button id='btn-back2a' name ='back2a' type='button' class='btn btn-success'>Back</button></div></div><div class="hidden-sm hidden-md hidden-lg"><button id='btn-next2a' name ='next2a' type='button' class='btn btn-primary'>Next</button></div
>
Just make it more generic. You can use classes or data attributes. There is a lot of different ways to do this.
jsfiddle - https://jsfiddle.net/vouvcedj/4/
There is still a lot of cleanup that can be done but this should be pretty clear as to what you can do.
JS
$(function() {
var handleFirstLast = function() {
if ($('.shown').is('.section:first')) {
$('#btn-back').hide();
} else {
$('#btn-back').show();
}
if ($('.shown').is('.section:last')) {
$('#btn-forward').hide();
} else {
$('#btn-forward').show();
}
}
handleFirstLast();
$('#btn-back').on('click', function(e) {
// find the currently shown section and get the previous
// https://api.jquery.com/next/
var $showing = $('.section.shown');
$showing.prev().removeClass('hidden').addClass('shown');
$showing.removeClass('shown').addClass('hidden');
handleFirstLast();
});
$('#btn-forward').on('click', function(e) {
var $showing = $('.section.shown');
$showing.next().removeClass('hidden').addClass('shown');
$showing.removeClass('shown').addClass('hidden');
handleFirstLast();
});
});
HTML
<div class="sections">
<div class="section shown">
section 1
</div>
<div class="section hidden">
section 2
</div>
<div class="section hidden">
</div>
</div>
<div class="actions">
<button id="btn-back">Back</button>
<button id="btn-forward">Forward</button>
</div>
Perhaps something like this would make sense:
$(document).ready(function() {
const buttons = [
['btn-continue', 'loginbox', '1box'],
['btn-next1', '1box', '2box'],
['btn-next2', '2box', '3box'],
['btn-back2', '2box', '1box'],
['btn-next2a', '2box', '3box'],
['btn-back2a', '2box', '1box'],
['btn-next3', '3box', '4box'],
['btn-back3', '3box', '2box'],
['btn-next3a', '3box', '4box'],
['btn-back3a', '3box', '2box'],
['btn-next4', '4box', '5box'],
['btn-back4', '4box', '3box'],
['btn-next4a', '4box', '5box'],
['btn-back4a', '4box', '3box'],
['btn-back5', '5box', '4box'],
['btn-back5a', '5box', '4box']
]
buttons.forEach(function(b) {
$('#' + b[0]).on('click', function() {
$('#' + b[1]).hide();
$('#' + b[2]).show();
});
});
});
This captures the repetitive data in a single structure, and replaces the repetitive code with a loop.
Note that this is entirely untested.
This refers to DRY (Don't Repeat Yourself) principle and it is one of the core principles in programming. If you find yourself repeating in code, means trouble, a small change can be catastrophic for you if you need to go to each place and implement it.
With DRY principle you write code once and reuse it via numerous techniques. A change can be applied once and to one place only.
When the DRY principle is applied successfully, a modification of any
single element of a system does not require a change in other
logically unrelated elements. Additionally, elements that are
logically related all change predictably and uniformly, and are thus
kept in sync.
You code can be changed to something like this:
var myNs = myNs || {};
$(document).ready(function() {
$(myNs.map).each(function(index, value) {
$(value.clickedElement).on('click', clickHandler.bind(this, value));
});
function clickHandler(value) {
$(value.hideElement).hide();
$(value.showElement).show();
}
});
What about the myNs? This is a configuration object, a namespace to load your configuration for elements. This can be a completely different JavaScript file, which of course is loaded before this particular script.
Example:
// This can be in a completely different file
var myNs = {
map: [{
clickedElement: '#btn-continue',
hideElement: '#loginbox',
showElement: '#1box'
}, {
clickedElement: '#btn-next1',
hideElement: '#1box',
showElement: '#2box'
}, {
clickedElement: '#btn-next2',
hideElement: '#2box',
showElement: '#3box'
}, {
clickedElement: '#btn-back2',
hideElement: '#2box',
showElement: '#1box'
},
...
]
};
You can take this a step forward and use the new ES6 modules or even AMD modules. Of course you need a module loader for that, like SystemJS, Webpack or RequireJS.
Check out the following plunk to peak on some code.

Attach component to dynamically created elements with Twitter Flight

Been looking to figure out how with Twitter Flight can attach to dynamic created elements.
Having the following HTML
<article>Add element</article>
And the following component definition
var Article = flight.component(function () {
this.addElement = function () {
this.$node.parent().append('<article>Add element</article>');
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
Once a new element is created, the click event doesn't fire. Here's the fiddle: http://jsfiddle.net/smxx5/
That's not how you should be using Flight imho.
Each component should be isolated from the rest of the application, therefore you should avoid this.$node.parent()
On the other hand you can interact with descendants.
My suggestion is to create an "Articles manager" component that uses event delegation.
eg.
http://jsfiddle.net/kd75v/
<div class="js-articles">
<article class="js-article-add">Add element</article>
<div/>
and
var ArticlesManager = flight.component(function () {
this.defaultAttrs({
addSelector: '.js-article-add',
articleTpl: '<article class="js-article-add">Add element</article>'
});
this.addArticle = function () {
this.$node.append(this.attr.articleTpl);
};
this.after('initialize', function () {
this.on('click', {
addSelector: this.addArticle
});
});
});
ArticlesManager.attachTo('.js-articles');
Try attaching Article to each new article added:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/smxx5/2/
var Article = flight.component(function () {
this.addElement = function () {
var newArticle = $('<article>Add element</article>');
this.$node.parent().append(newArticle);
Article.attachTo(newArticle);
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
The Article.attachTo('article'); at the end, that runs once on load, will only attach to existing article elements.
I hit this problem, and worked around is as follows...
Javascript: All thrown together for brevity, but could easily be separated.
(function(){
var TestComponent, LoaderComponent;
TestComponent = flight.component(function() {
this.doSomething = function()
{
console.log('hi there...');
};
this.after('initialize', function() {
this.on('mouseover', this.doSomething);
});
});
LoaderComponent = flight.component(function() {
this.attachComponents = function()
{
TestComponent.attachTo('.test');
};
this.after('initialize', function() {
// Initalise existing components
this.attachComponents();
// New item created, so re-attach components
this.on('newItem:testComponent', this.attachComponents);
});
});
LoaderComponent.attachTo('body');
}());
HTML: Note that one .test node exists. This will be picked up by Flight on initialization (i.e. not dynamic). We then add a second .test node using jQuery and fire off the event that the LoaderComponent is listening on.
<div class="test">
<p>Some sample text.</p>
</div>
<script>
$('body').append('<div class="test"><p>Some other text</p></div>').trigger('newItem:testComponent');
</script>
This is obviously a very contrived example, but should show that it's possible to use Flight with dynamically created elements.
Hope that helped :)

Jasmine doesn't seem to be calling my method

I am new to Jasmine and seem to be struggling to get what I think is a fairy standard kind of thing running.
I am loading an HTML file via a fixture and trying to call a click on an element on the dom. This I would expect result in the call to the method of the JS file I am trying to test. When I try and debug this in developer tools the method that should be called in my js file never hits a breakpoint. As such I assume that code is not being called and therfore does not toggle the expand/collapse class.
My test:
describe("userExpand", function () {
beforeEach(function () {
loadFixtures('user-expand.html');
//userControl();
//this.addMatchers({
// toHaveClass: function (className) {
// return this.actual.hasClass(className);
// }
//});
});
//this test works ok
it("checks the click is firing", function () {
spyOnEvent($('.expanded'), 'click');
$('.expanded').trigger('click');
expect("click").toHaveBeenTriggeredOn($('.expanded'));
});
//this doesn't
it("checks the click is changing the class", function () {
//spyOnEvent($('.collapsed'), 'click');
var myElement = $('.collapsed');
myElement.click();
expect(myElement).toHaveClass('.expanded');
});
Part of the fixture:
<div class="wrapper">
<div class="row group">
<div class="col-md-1" data-bordercolour=""> </div>
<div class="collapsed col-md-1"> </div>
<div class="col-md-9">None (1)</div>
The JS I am trying to test:
var userControl = function () {
"use strict";
var collapse = '.collapsed';
var expand = '.expanded';
var userList = $(".userList");
function toggleState() {
var currentControl = $(this);
if (currentControl.hasClass('all')) {
if (currentControl.hasClass('expanded')) {
toggleIcon(currentControl, collapse);
userList.find(".user-group-summary").hide()
.end()
.find(".user-group-info").show();
} else {
toggleIcon(currentControl, expand);
userList.find(".user-group-summary").show()
.end()
.find(".user-group-info").hide();
}
} else {
currentControl.parent().nextUntil('.group').toggle();
currentControl.toggleClass("expanded collapsed");
currentControl.parent().find(".user-group-summary").toggle()
.end()
.find(".user-group-info").toggle();
}
};
function toggleIcon(ctrl, currentState) {
var details = ctrl.closest('div.row').siblings('.wrapper');
details.find(currentState).toggleClass('expanded collapsed');
if (currentState === expand) {
details.find('.detail').hide();
} else {
details.find('.detail').show();
}
}
userList.on('click', '.expanded, .collapsed', toggleState);
$('[data-bordercolour]').each(function () {
$(this).css("background-color", $(this).data('bordercolour'))
.parent().nextUntil('.group')
.find('>:first-child').css("background-color", $(this).data('bordercolour'));
});
return {
toggleState: toggleState
};
}();
The code works fine in normal use so I am sure I am missing something obvious with the way Jasmine should be used. Any help would be appreciated.
Update:
I can make the togglestate method fire by using call in the test rather than triggering a click event:
it('checks on click of icon toggles that icon', function () {
var myElement = $('.collapsed');
userControl.toggleState.call(myElement);
expect(myElement).toHaveClass('expanded');
});
This seems a little strange as all the examples I can find are quite happy with click. Gets me off the hook but I would still like to know what I am missing.
It's hard to give a precise hint without the source code. Does click on .collapsed involve asynchronous action(s)? If so, wrapping the test in runs(...); waitsFor(...); runs(...); may solve the problem. Check the Jasmine introduction for how to do this.

Categories

Resources