I'm building an app with html/javascript.
I feel my code could be made cleaner and factorized with some javascript but don't know how to find this code (while keeping a very efficient page load performance).
<div id="deal-zone">
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"/></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal" >
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal" >
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
</div>
As you see it's really very very repetitive as I have 10 times the exact same block.
What I am trying to do is simply something like this and i lack the javascript chops for it:
function(){display in the html make 10 times repeatedly
<div class="deal">
<span class="beamer" data-toggle="modal" data-target="#myModal"></span>
</div>
}
Note: if you wonder by the way, javascript is dynamically giving each a certain position on the page.
$(document).ready(function(){
function getRandomInt(min, max) {
return Math.random() * (max - min + 1) + min;
}
$(".deal").each(function () {
var topPosition = getRandomInt(8, 70);
var leftPosition = getRandomInt(8, 92);
$(this).css({
"top": topPosition+"%",
"left": leftPosition+"%",
});
});
});
How can I do this ?
Here are some tips that might you.
If you want to use jQuery to generate the HTML for you, you can do the following:
var numBlocks = 10;
var $dealZone = $("#deal-zone");
var $toAppend = $('<div class="deal"><span class="beamer" data-toggle="modal" data-target="#myModal"></span></div>');
for(var c = 0; c < numBlocks; c++)
$dealZone.append($toAppend.clone());
JSFiddle to demonstrate (used <li>, sorry)
However, I just want to point out, that is not a great SEO way to do things. Dynamic HTML is not picked up so well by Search Engine crawlers, so it would look to the Search Engines that your content was missing.
Your first <span> tag has a closing slash. This is not valid HTML. Should be:
data-target="#myModal">
and not
data-target="#myModal"/>
It looks like your HTML is a list of some sort. Semantically, you might want to consider using a list structure with <ul> and <li> tags. You can still style these to remove the padding, margin, and bullets to display them any way you would like.
To simplify CSS classes, (using your current HTML) you could remove the class="deal" and class="beamer" from every child element. If you need this class for CSS styling, you can use the descendant selector (which targets all immediate children of the parent) as follows:
#deal-zone > div { /*styles here*/ } /* targets the current "deal" class */
#deal-zone > div > span { /*styles here*/ } /* targets the current ".beamer" class */
You could also use the descendant selector in your jQuery function. Use the following JavaScript to target the children without having the "deal" class:
$("#deal-zone > div").each(function () { ... }
In addition, you most likely don't need the repetitive data-toggle and data-target attributes on all of the children. Since they are all the same, you can assign these attributes to the parent and access them with the following jQuery:
var $dealZone = $('#deal-zone');
var data-toggle = $dealZone.attr('data-toggle');
var data-target = $dealZone.attr('data-target');
In summary, here is a working example in JSFiddle
Related
I have CSHTML code as follows. I need to toggle a class on click, which is given in the below Javascript code.
<div class="accordion bar-heading-padding" id="#accordion2" onclick="getclassofitag(this)">
<div class="accordion-group">
<div class="accordion-heading datalist" data-toggle="collapse" data-parent="#accordion2" href="##i.Id">
#*<i class="fa fa-chevron-up table-middle icon-width" aria-hidden="false"></i>*#
<i class="fa fa-chevron-down table-middle icon-width" aria-hidden="true"></i>
<h1 class="accordion-toggle table-middle">
#Html.Raw(#i.Name)
</h1>
</div>
<div id="#i.Id" class=" collapse out">
<div class="accordion-inner">
<img ng-src="#i.ImagePath" alt="logo" class="img-thumbnail img-wrap content-image" />
<h1>#Html.Raw(#i.Name)</h1>
<h3>#Html.Raw(#i.Title)</h3>
<br />
#Html.Raw(#i.BioDetail)
</div>
</div>
</div>
</div>
and the Javascript code for toggling class of the i element.
function getclassofitag(element) {
iTags = element.getElementsByTagName("i");
iTags.classList.toggle('fa-chevron-down');
itags.classList.toggle('fa-chevron-up');
}
I want to toggle the classes of the i element (<i) on click. What code do I have to write to do that? Where did I go wrong?
document.getElementsByTagName() returns an HTMLCollection object which contains a collection of DOM objects. The collection itself does not have a .classList property (like you are trying to do). That is a property of a DOM element, not the collection object. Thus, your iTags variable does not have a .classList` property on it.
So, you need to iterate the iTags HTMLCollection and use .classList on each individual DOM element.
function getclassofitag(element) {
var item, iTags = element.getElementsByTagName("i");
for (var i = 0; i < iTags.length; i++) {
item = iTags[i];
item.classList.toggle('fa-chevron-down');
item.classList.toggle('fa-chevron-up');
}
}
Note: I also declared your iTags variable as a local variable so you aren't using an implicit global (which is a bad practice).
hope this will solve your problem
$(document).ready(function () {
$('.collapse').on('shown.bs.collapse', function () {
$(this).parent().find(".fa-chevron-down").removeClass("fa-chevron-down").addClass("fa-chevron-up");
});
$('.collapse').on('hidden.bs.collapse', function () {
$(this).parent().find(".fa-chevron-up").removeClass("fa-chevron-up").addClass("fa-chevron-down");
});
});
I've code few line of jQuery for Hide/Show many elements on single click and it's working. But problem is; i've many more image class items, so my script going to long, my question is how to simplify or make short my script, i mean any alternatives or any new idea? please suggest.
HTML:
<div id="choose-color">
<span>
<i class="images-red" style="">Red Image</i>
<i class="images-blue" style="display: none;">Blue Image</i>
<i class="images-pink" style="display: none;">Pink Image</i>
<!-- many many images -->
</span>
<button class="red">Red</button>
<button class="blue">Blue</button>
<button class="pink">Pink</button>
</div>
JS: live demo >
$("button.red").click(function(){
$(".images-red").show();
$(".images-blue, .images-pink").hide();
});
$("button.blue").click(function(){
$(".images-red, .images-pink").hide();
$(".images-blue").show();
});
$("button.pink").click(function(){
$(".images-red, .images-blue").hide();
$(".images-pink").show();
});
Please suggest for short and simple code of my script. Thanks.
You can do it by adding just a common class to those buttons,
var iTags = $("#choose-color span i");
$("#choose-color button.button").click(function(){
iTags.hide().eq($(this).index("button.button")).show();
});
The concept behind the code is to bind click event for the buttons by using the common class. Now inside the event handler, hide all the i elements which has been cached already and show the one which has the same index as clicked button.
DEMO
For more details : .eq() and .index(selector)
And if your elements order are not same, both the i and button's. Then you can use the dataset feature of javascript to over come that issue.
var iTags = $("#choose-color span i");
$("#choose-color button.button").click(function(){
iTags.hide().filter(".images-" + this.dataset.class).show()
});
For implementing this you have to add data attribute to your buttons like,
<button data-class="red" class="button red">Red</button>
DEMO
This works
$("#choose-color button").click(function(){
var _class = $(this).attr('class');
$("#choose-color i").hide();
$(".images-"+_class).show();
});
https://jsfiddle.net/455k1hhh/5/
I know this might not be the prettiest solution, but it should do the job.
$("button").click(function(){
var classname = $(this).attr('class');
$("#choose-color span i").hide();
$(".images-"+classname).show();
});
You're making future extensibility a little difficult this way due to relying on class names but this would solve your immediate need:
<div id="myImages">
<i class="images-red" style="">Red Image</i>
<i class="images-blue" style="display: none;">Blue Image</i>
<i class="images-pink" style="display: none;">Pink Image</i>
<!-- Many many image -->
</div>
<div id="myButtons">
<button class="red">Red</button>
<button class="blue">Blue</button>
<button class="pink">Pink</button>
</div>
$("#myButtons button").click(function(){
var color = $(this).attr("class");
var imageClass = ".images-"+color;
$('#myImages').children("i").each(function () {
$(this).hide();
});
$(imageClass).show();
});
Here's a JSFiddle
Edit: Note how I wrapped the buttons and images in parent divs to allow you to isolate just the buttons/images you want to work with.
You can do the following using data-* attributes, because when you have more elements of the same color, using index of the button won't work. And simply using the whole class attribute won't work if you have to add more classes to the button in future.
$("button").click(function() {
var color = $(this).data('color');
var targets = $('.images-' + color);
targets.show();
$("span i").not(targets).hide();
});
.hidden {
display: none
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<br/>
<br/>
<div id="choose-color">
<span>
<i class="images-red">Red Image</i>
<i class="images-blue hidden">Blue Image</i>
<i class="images-pink hidden">Pink Image</i>
<!-- Many many image -->
</span>
<br/>
<br/>
<button data-color="red">Red</button>
<button data-color="blue">Blue</button>
<button data-color="pink">Pink</button>
</div>
It would make sense to have all images share a single class (.image for example). Then you just use a shared class for the button and the image; in this example I used the color name. Now, when any button is clicked, you can grab the class name of the image you want to show.
Give this a try:
$("button").click(function(){
$(".image").hide();
var className = $(this).attr("class");
$("." + className).show();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<br/><br/>
<div id="choose-color">
<span>
<i class="image red" style="">Red Image</i>
<i class="image blue" style="display: none;">Blue Image</i>
<i class="image pink" style="display: none;">Pink Image</i>
<!-- Many many image -->
</span>
<br/><br/>
<button class="red">Red</button>
<button class="blue">Blue</button>
<button class="pink">Pink</button>
</div>
You may try this:
<div id="choose-color">
<span>
<i class="images-red" style="">Red Image</i>
<i class="images-blue" style="display: none;">Blue Image</i>
<i class="images-pink" style="display: none;">Pink Image</i>
<!-- Many image -->
</span>
<br/><br/>
<button class="colour red" onclick="myFunction(this)">Red</button>
<button class="colour blue" onclick="myFunction(this)">Blue</button>
<button class="colour pink" onclick="myFunction(this)">Pink</button>
</div>
JS: see here
$(".colour").click(function(){
var colors = ["red", "blue", "pink"];
for (i = 0; i < colors.length; i++) {
if($(this).hasClass(colors[i])){
$(".images-"+colors[i]).show();
}else{
$(".images-"+colors[i]).hide();
}
}
});
I'd like to get the data-league values of all of those divs, and put them into an array.
What I want to do: Get all those values, save them, and then loop through them, and call a .click on those divs.
I don't know if there's an easier way to do this.
I guess it also has to loop through the divs where ID=128, and find the data-league values, right?
Honestly I'm completely stuck, so any help whatsoever would be appriciated.
Thank you.
Extra info: Basically the end result being, on click of a button, all those 'thumbnails' should open in a new tab. All tabs being different streams.
(See image)
In the example you provided the same id (128) has been used multiple times. This is not allowed. Iterating through the ids will not work.
This means you have to look for another possibility. Getting all div elements with a certain class would be possible, like in the example:
var divs = document.getElementsByClassName('videoPanel');
Now you can iterate through this collection, extract the values for the attribute data-league and save them somewhere, for example in an array:
var dataLeagueValues = [];
for (var i = 0; i < divs.length; i++) {
dataLeagueValues.push(divs[i].getAttribute('data-league'));
}
Now you have all values in the array dataLeagueValues. You can use them further in your script.
You could use the $('[attribute]') selector to select elements with data-league, you can then use .each(); to loop them.
The following example triggers a click on every element with a data-league attribute:
$('[data-league]').each(function() {
$(this).trigger('click');
});
You could also make the selector more specific to only match these elements by their shared class name videoPanel e.g.
$('.videoPanel[data-league]').each(...);
Will target all elements with a class of videoPanel and an attribute data-league
To match what you need the full example would be:
var leagues = [];
$('.videoPanel[data-league]').each(function() {
leagues.push($(this).attr('data-league'));
});
Following code snippet will your insert all data-league into an array.
var leagues=[];
$('.vedioPnnel').each(function() {
var league = $(this).data('league');
leagues.push(league);
});
console.log(leagues);
I propose to use the javascript forEach function like in the folowing snippet.
Alternatively you can use:
document.querySelectorAll('[id="127"]');
or
document.querySelectorAll('[data-league]')
function getAllDataLeague()
{
var leagues = [];
Array.prototype.forEach.call(document.getElementsByTagName('div'), function (value, index, traversed) {
var attrVal = value.getAttribute('data-league');
if (attrVal) {
leagues.push({'obj': value, 'data-league': attrVal});
}
});
return leagues;
}
var eleFound = getAllDataLeague();
// print result in the html body
document.body.innerHTML += '<p>To access elements: eleFound[index]["data-league"]</p>';
document.body.innerHTML += '<p>To trigger the click event: eleFound[index]["obj"].click()</p>';
document.body.innerHTML += '<p>Elements found:</p>';
for (var i = 0; i < eleFound.length; i++) {
document.body.innerHTML += '<div>data-league:' + eleFound[i]["data-league"] + '</div>';
}
<div class="game-listing-group">
<div class="bold game-listing-name">CS:GO</div><div class="videoPanel vu-channel-tab" id="127" data-channel="https://www-cdn.jtvnw.net/swflibs/TwitchPlayer.swf?channel=m0e_tv" data-league="1284">
<div class="video-thumbnail-con hidden-xs hidden-sm">
<img class="img-responsive video-thumbnail full-width" src="https://static-cdn.jtvnw.net/previews-ttv/live_user_m0e_tv-320x180.jpg">
</div>
<div class="videoPanelTextBg">
<p class="indexVideoPanelTitle">Dragon lore giveaway NOW</p>
<span class="indexVideoPanelGoldCount vu-league-gold pull-right" style="display: none;"></span>
</div>
<div class="indexVideoPanelGoldCount video-upcoming">
<i class="glyphicon glyphicon-time"></i>
<span class="vu-league-start"></span>
</div>
</div><div class="videoPanel vu-channel-tab" id="127" data-channel="https://www-cdn.jtvnw.net/swflibs/TwitchPlayer.swf?channel=freakazoid_tv" data-league="1296">
<div class="video-thumbnail-con hidden-xs hidden-sm videoPanelTextBgActive">
<img class="img-responsive video-thumbnail full-width" src="https://static-cdn.jtvnw.net/previews-ttv/live_user_freakazoid_tv-320x180.jpg">
</div>
<div class="videoPanelTextBg">
<p class="indexVideoPanelTitle">BIRTHDAY IN 24HOURS! #c9freakazoid</p>
<span class="indexVideoPanelGoldCount vu-league-gold pull-right" style="display: none;"></span>
</div>
<div class="indexVideoPanelGoldCount video-upcoming">
<i class="glyphicon glyphicon-time"></i>
<span class="vu-league-start"></span>
</div>
</div><div class="videoPanel vu-channel-tab" id="127" data-channel="https://www-cdn.jtvnw.net/swflibs/TwitchPlayer.swf?channel=gripex90" data-league="1301">
<div class="video-thumbnail-con hidden-xs hidden-sm">
<img class="img-responsive video-thumbnail full-width" src="https://static-cdn.jtvnw.net/previews-ttv/live_user_gripex90-320x180.jpg">
</div>
<div class="videoPanelTextBg">
<p class="indexVideoPanelTitle">Gripex - Most dedicated Lee sin! Top 50 Challenger</p>
<span class="indexVideoPanelGoldCount vu-league-gold pull-right" style="display: none;"></span>
</div>
<div class="indexVideoPanelGoldCount video-upcoming">
<i class="glyphicon glyphicon-time"></i>
<span class="vu-league-start"></span>
</div>
</div><div class="videoPanel vu-channel-tab" id="127" data-channel="https://www-cdn.jtvnw.net/swflibs/TwitchPlayer.swf?channel=phantoml0rd" data-league="1346">
<div class="video-thumbnail-con hidden-xs hidden-sm">
<img class="img-responsive video-thumbnail full-width" src="https://static-cdn.jtvnw.net/previews-ttv/live_user_phantoml0rd-320x180.jpg">
</div>
<div class="videoPanelTextBg">
<p class="indexVideoPanelTitle">Level 400 Gambler LOL - Cycled over 3,000,000 in s</p>
<span class="indexVideoPanelGoldCount vu-league-gold pull-right" style="display: none;"></span>
</div>
<div class="indexVideoPanelGoldCount video-upcoming">
<i class="glyphicon glyphicon-time"></i>
<span class="vu-league-start"></span>
</div>
</div></div>
have a look at the code
Html Code :
<div class="container content-rows" id="contentdisplay">
<div class="col-md-1" id="snocontent">abcd</div>
<div class="col-md-3" id="pgnamecontent">abcd</div>
<div class="col-md-3" id="cmpcontent">abcd</div>
<div class="col-md-2" id="datecontent">abcd</div>
<div>
<button onclick="createdivs()"><span class="glyphicon glyphicon-king" class="addtrainersbutton" id="addtrainersbutton" title="Add Trainers"></span></button>
<button onclick="edit_program()"><span class="glyphicon glyphicon-pencil" id="editprogrambutton" title="Edit Program"></span></button>
<span class="glyphicon glyphicon-user" id="assigntrainersbutton" title="Assign Trainers for the Program"></span>
<span class="glyphicon glyphicon-remove" id="deleteprogrambutton" title="Delete the Program"></span>
</div>
Javascript Code:
function createdivs() {
var i;
for (i = 0;i < 10;i++)
{
divc = "<div>.container.content-rows";
var list = document.createElement("divc");
document.body.appendChild(list);
list.className = "proglist";
}
}
I have a few questions, please clarify them or explain with code:
10 divc's are created but there are no other div elements inside them, only the first div has some content in it.. other divs are just created and they dnt even occupy space inside the webpage
I want the div to be aligned to the first div, ie., instead of document.body.appendChild I need something like document.div.appendChild, whereas the created divs should be appeneded with the first div..
Please let me know how can I get them with explanation.. Thank you in advance..
10 divc's are created but there are no other div elements inside them, only the first div has some content in it.. other divs are just created and they dnt even occupy space inside the webpage
Space isn't occupied because they don't have any content added to them. You add content to an element just like you do with document.body - appendChild() for example.
I want the div to be aligned to the first div, ie., instead of document.body.appendChild I need something like document.div.appendChild, whereas the created divs should be appeneded with the first div..
Elements have such a method:
function createdivs() {
var i;
for (i = 0; i < 10; i++) {
divc = "<div>.container.content-rows";
var divc = document.createElement("div");
divc.classList.add("container");
divc.classList.add("content-rows");
divc.classList.add("proglist");
divc.textContent = "I'm div #" + i;
document.getElementById('contentdisplay').appendChild(divc);
createNestedDivs(divc);
}
}
function createNestedDivs(selector) {
function appendToNode(node, content) {
// ideally content would also be a Node, but for simplicity,
// I'm assuming it's a string.
var inner = document.createElement('span');
inner.textContent = content;
node.appendChild(inner);
}
if (selector instanceof Node) {
appendToNode(selector, "inner");
return;
}
var selected = Array.prototype.slice.call(document.querySelectorAll(selector));
selected.forEach(function(el) {
appendToNode(el, "inner");
});
}
<div class="container content-rows" id="contentdisplay">
<div class="col-md-1" id="snocontent">abcd</div>
<div class="col-md-3" id="pgnamecontent">abcd</div>
<div class="col-md-3" id="cmpcontent">abcd</div>
<div class="col-md-2" id="datecontent">abcd</div>
<div>
<button onclick="createdivs()"><span class="glyphicon glyphicon-king" class="addtrainersbutton" id="addtrainersbutton" title="Add Trainers"></span>
</button>
<button onclick="edit_program()"><span class="glyphicon glyphicon-pencil" id="editprogrambutton" title="Edit Program"></span>
</button> <span class="glyphicon glyphicon-user" id="assigntrainersbutton" title="Assign Trainers for the Program"></span>
<span class="glyphicon glyphicon-remove" id="deleteprogrambutton" title="Delete the Program"></span>
</div>
The createNestedDivs() function takes care of if you want to append another child to each created element. You can pass it a Node or string and it will treat them as expected. createNestedDivs() works by the same principles as the modifications I made to createdivs(). The case for handling a Node is simple enough, but the string handling is a little less clear:
The Array.prototype.slice.call is necessary because document.querySelectorAll returns a NodeList, which isn't an array, and I can't use Array.prototype.forEach() on it.
Also, related reading here: http://www.w3.org/wiki/The_principles_of_unobtrusive_JavaScript
I am using Twitter Bootstrap to create collapsible sections of text. The sections are expanded when a + button is pressed. My html code as follows:
<div class="row-fluid summary">
<div class="span11">
<h2>MyHeading</h2>
</div>
<div class="span1">
<button type="button" class="btn btn-success" data-toggle="collapse" data-target="#intro">+</button>
</div>
</div>
<div class="row-fluid summary">
<div id="intro" class="collapse">
Here comes the text...
</div>
</div>
Is there a way to change the button to display - instead of + after the section is expanded (and change back to + when it is collapsed again)?
Additional information: I hoped there would be a simple twitter-bootstrap/css/html-based solution to my problem. All responses so far make use of JavaScript or PHP. Because of this I want to add some more information about my development environment: I want to use this solution inside a SilverStripe-based (version 3.0.5) website which has some implications for the use of both PHP as well as JavaScript.
try this. http://jsfiddle.net/fVpkm/
Html:-
<div class="row-fluid summary">
<div class="span11">
<h2>MyHeading</h2>
</div>
<div class="span1">
<button class="btn btn-success" data-toggle="collapse" data-target="#intro">+</button>
</div>
</div>
<div class="row-fluid summary">
<div id="intro" class="collapse">
Here comes the text...
</div>
</div>
JS:-
$('button').click(function(){ //you can give id or class name here for $('button')
$(this).text(function(i,old){
return old=='+' ? '-' : '+';
});
});
Update With pure Css, pseudo elements
http://jsfiddle.net/r4Bdz/
Supported Browsers
button.btn.collapsed:before
{
content:'+' ;
display:block;
width:15px;
}
button.btn:before
{
content:'-' ;
display:block;
width:15px;
}
Update 2 With pure Javascript
http://jsfiddle.net/WteTy/
function handleClick()
{
this.value = (this.value == '+' ? '-' : '+');
}
document.getElementById('collapsible').onclick=handleClick;
Here's another CSS only solution that works with any HTML layout.
It works with any element you need to switch. Whatever your toggle layout is you just put it inside a couple of elements with the if-collapsed and if-not-collapsed classes inside the toggle element.
The only catch is that you have to make sure you put the desired initial state of the toggle. If it's initially closed, then put a collapsed class on the toggle.
It also requires the :not selector, so it doesn't work on IE8.
HTML example:
<a class="btn btn-primary collapsed" data-toggle="collapse" href="#collapseExample">
<!--You can put any valid html inside these!-->
<span class="if-collapsed">Open</span>
<span class="if-not-collapsed">Close</span>
</a>
<div class="collapse" id="collapseExample">
<div class="well">
...
</div>
</div>
Less version:
[data-toggle="collapse"] {
&.collapsed .if-not-collapsed {
display: none;
}
&:not(.collapsed) .if-collapsed {
display: none;
}
}
CSS version:
[data-toggle="collapse"].collapsed .if-not-collapsed {
display: none;
}
[data-toggle="collapse"]:not(.collapsed) .if-collapsed {
display: none;
}
JS Fiddle
Add some jquery code, you need jquery to do this :
<script>
$(".btn[data-toggle='collapse']").click(function() {
if ($(this).text() == '+') {
$(this).text('-');
} else {
$(this).text('+');
}
});
</script>
All the other solutions posted here cause the toggle to get out of sync if it is double clicked. The following solution uses the events provided by the Bootstrap framework, and the toggle always matches the state of the collapsible element:
HTML:
<div class="row-fluid summary">
<div class="span11">
<h2>MyHeading</h2>
</div>
<div class="span1">
<button id="intro-switch" class="btn btn-success" data-toggle="collapse" data-target="#intro">+</button>
</div>
</div>
<div class="row-fluid summary">
<div id="intro" class="collapse">
Here comes the text...
</div>
</div>
JS:
$('#intro').on('show', function() {
$('#intro-switch').html('-')
})
$('#intro').on('hide', function() {
$('#intro-switch').html('+')
})
That should work for most cases.
However, I also ran into an additional problem when trying to nest one collapsible element and its toggle switch inside another collapsible element. With the above code, when I click the nested toggle to hide the nested collapsible element, the toggle for the parent element also changes. It may be a bug in Bootstrap. I found a solution that seems to work: I added a "collapsed" class to the toggle switches (Bootstrap adds this when the collapsible element is hidden but they don't start out with it), then added that to the jQuery selector for the hide function:
http://jsfiddle.net/fVpkm/87/
HTML:
<div class="row-fluid summary">
<div class="span11">
<h2>MyHeading</h2>
</div>
<div class="span1">
<button id="intro-switch" class="btn btn-success collapsed" data-toggle="collapse" data-target="#intro">+</button>
</div>
</div>
<div class="row-fluid summary">
<div id="intro" class="collapse">
Here comes the text...<br>
<a id="details-switch" class="collapsed" data-toggle="collapse" href="#details">Show details</a>
<div id="details" class="collapse">
More details...
</div>
</div>
</div>
JS:
$('#intro').on('show', function() {
$('#intro-switch').html('-')
})
$('#intro').on('hide', function() {
$('#intro-switch.collapsed').html('+')
})
$('#details').on('show', function() {
$('#details-switch').html('Hide details')
})
$('#details').on('hide', function() {
$('#details-switch.collapsed').html('Show details')
})
I liked the CSS-only solution from PSL, but in my case I needed to include some HTML in the button, and the content CSS property is showing the raw HTML with tags in this case.
In case that could help someone else, I've forked his fiddle to cover my use case: http://jsfiddle.net/brunoalla/99j11h40/2/
HTML:
<div class="row-fluid summary">
<div class="span11">
<h2>MyHeading</h2>
</div>
<div class="span1">
<button class="btn btn-success collapsed" data-toggle="collapse" data-target="#intro">
<span class="show-ctrl">
<i class="fa fa-chevron-down"></i> Expand
</span>
<span class="hide-ctrl">
<i class="fa fa-chevron-up"></i> Collapse
</span>
</button>
</div>
</div>
<div class="row-fluid summary">
<div id="intro" class="collapse">
Here comes the text...
</div>
</div>
CSS:
button.btn .show-ctrl{
display: none;
}
button.btn .hide-ctrl{
display: block;
}
button.btn.collapsed .show-ctrl{
display: block;
}
button.btn.collapsed .hide-ctrl{
display: none;
}
My following JS solution is better than the other approaches here because it ensures that it will always say 'open' when the target is closed, and vice versa.
HTML:
<a href="#collapseExample" class="btn btn-primary" data-toggle="collapse" data-toggle-secondary="Close">
Open
</a>
<div class="collapse" id="collapseExample">
<div class="well">
...
</div>
</div>
JS:
$('[data-toggle-secondary]').each(function() {
var $toggle = $(this);
var originalText = $toggle.text();
var secondaryText = $toggle.data('toggle-secondary');
var $target = $($toggle.attr('href'));
$target.on('show.bs.collapse hide.bs.collapse', function() {
if ($toggle.text() == originalText) {
$toggle.text(secondaryText);
} else {
$toggle.text(originalText);
}
});
});
Examples:
$('[data-toggle-secondary]').each(function() {
var $toggle = $(this);
var originalText = $toggle.text();
var secondaryText = $toggle.data('toggle-secondary');
var $target = $($toggle.attr('href'));
$target.on('show.bs.collapse hide.bs.collapse', function() {
if ($toggle.text() == originalText) {
$toggle.text(secondaryText);
} else {
$toggle.text(originalText);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<link href="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet"/>
<script src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/js/bootstrap.min.js"></script>
<a href="#collapseExample" class="btn btn-primary" data-toggle="collapse" data-toggle-secondary="Close">
Open
</a>
<div class="collapse" id="collapseExample">
<div class="well">
...
</div>
</div>
JS Fiddle
Other benefits of this approach:
the code is DRY and reusable
each collapse button stays separate
you only need to put one change into the HTML: adding the data-toggle-secondary attribute
I guess you could look inside your downloaded code where exactly there is a + sign (but this might not be very easy).
What I'd do?
I'd find the class/id of the DOM elements that contain the + sign (suppose it's ".collapsible", and with Javascript (actually jQuery):
<script>
$(document).ready(function() {
var content=$(".collapsible").html().replace("+", "-");
$(".collapsible").html(content));
});
</script>
edit
Alright... Sorry I haven't looked at the bootstrap code... but I guess it works with something like slideToggle, or slideDown and slideUp... Imagine it's a slideToggle for the elements of class .collapsible, which reveal contents of some .info elements. Then:
$(".collapsible").click(function() {
var content=$(".collapsible").html();
if $(this).next().css("display") === "none") {
$(".collapsible").html(content.replace("+", "-"));
}
else $(".collapsible").html(content.replace("-", "+"));
});
This seems like the opposite thing to do, but since the actual animation runs in parallel, you will check css before animation, and that's why you need to check if it's visible (which will mean it will be hidden once the animation is complete) and then set the corresponding + or -.
Easier with inline coding
<button type="button" ng-click="showmore = (showmore !=null && showmore) ? false : true;" class="btn float-right" data-toggle="collapse" data-target="#moreoptions">
<span class="glyphicon" ng-class="showmore ? 'glyphicon-collapse-up': 'glyphicon-collapse-down'"></span>
{{ showmore !=null && showmore ? "Hide More Options" : "Show More Options" }}
</button>
<div id="moreoptions" class="collapse">Your Panel</div>
Some may take issue with changing the Bootstrap js (and perhaps validly so) but here is a two line approach to achieving this.
In bootstrap.js, look for the Collapse.prototype.show function and modify the this.$trigger call to add the html change as follows:
this.$trigger
.removeClass('collapsed')
.attr('aria-expanded', true)
.html('Collapse')
Likewise in the Collapse.prototype.hide function change it to
this.$trigger
.addClass('collapsed')
.attr('aria-expanded', false)
.html('Expand')
This will toggle the text between "Collapse" when everything is expanded and "Expand" when everything is collapsed.
Two lines. Done.
EDIT: longterm this won't work. bootstrap.js is part of a Nuget package so I don't think it was propogating my change to the server. As mentioned previously, not best practice anyway to edit bootstrap.js, so I implemented PSL's solution which worked great. Nonetheless, my solution will work locally if you need something quick just to try it out.
You do like this.
the function return the old text.
$('button').click(function(){
$(this).text(function(i,old){
return old=='Read More' ? 'Read Less' : 'Read More';
});
});
Applied and working in Bootstrap 5.0.1.
Using simple jQuery
jQuery('button').on( 'click', function(){
if(jQuery(this).hasClass('collapsed')){
jQuery(this).html('+');
} else {
jQuery(this).html('-');
}
});
You can also use font awesome or HTML instead of +/- signs.