Twitter Bootstrap accordion feature - javascript

Working with Bootstrap and JavaScript and I am using it as an accordion format - once clicked on the collapsed div it will open and show the items within the div based on the id.
Problem:
If the div doesn't contain any items i want it to open and show a message to the user:
"no items here"
How do I go about doing that? In the JavaScript ?
This is what I have:
View
<div class="accordion-body collapse state-loading" data-group-id="13" data-bind="attr: { 'id': 'GroupMember_' + Id(), 'data-type-id': ModelId() }" id="GroupMember_15" data-type-id="15">
<div class="accordion-inner no_border" data-bind="foreach: Children"></div><!--END: accordion-inner--></div>
</div>
If the Children are 0 i want it to open and have this text show: No items here
Javascript:
OnSuccess: function (data) {
var _groups = linq.From(options.groupData);
var _groupsToUpdate = _groups .Where(function (x) { return x.Id == options.groupId; });
if (_groupsToUpdate.Any()) {
_groupsToUpdate.First().Children = data.Items;
}
Not sure if i am missing anything else to share - let me know.
UPDATE
Div Layout:
<div class='accordion-group'>
<div class='accordion-heading'> Group 1 </div>
<div class='accordion-body'>
<div class='accordion-inner'>
<div class='element'>No items here</div>
</div>
</div>
</div>
I have to click on the 'accordion-heading' class in order to display the 'accordion-body' and get into the accordion-inner items

You'd need to bind to the show event on the accordion elements and perform your check there, from your classes I'm assuming your using Bootstrap v2.3.2:
$('.accordion .collapse').on('show', function () {
var $inner = $(this).find('.accordion-inner');
if($inner.is(':empty')){
$inner.html('No items here');
}
});
Demo fiddle
Note that the :empty selector is very picky, it will not work if there's any white space between the opening and closing tags of .accordion-inner.
You may also use if(!$.trim($inner.html())) to check if the element is empty or as #JL suggested check the length of the children elements just beware that text nodes are not treated like children, so a div with only text would be considered empty

Do you have jQuery installed? You can check if a <div> has children like this:
if ($('#divId').children().length == 0) {
$('#divId').append("no items here");
}
If you don't have jQuery:
if (!document.getElementById('divId').hasChildNodes()) {
document.getElementById('divId').innerHTML = "no items here";
}
Based on your edit, I think we're inspecting accordian-inner for children. If so, give it an ID and substitute that into our code. Note: You don't need a <div> to contain our "no items" message...the message will get printed with javascript (Plus, if you have a <div> there, then you've in effect added a child and the message no longer applies). Change your HTML to this:
<div class='accordion-group'>
<div class='accordion-heading'> Group 1 </div>
<div class='accordion-body'>
<div id='innerId' class='accordion-inner'>
<!-- Remove the 'element' div -->
</div>
</div>
</div>
Then:
if (!document.getElementById('innerId').hasChildNodes()) {
document.getElementById('innerId').innerHTML = "no items here";
}

Related

How to close template modals in handlebars?

I created an app that receives articles from news API. Each article is displayed in a card, which has a button "Open Modal".
This button opens a modal with the unique information that pertains to each respective article.
However, I am unable to close the modal once it's opened. I suspect it's because the modal is stuck in this state: modals.forEach((modal, index) => {modal.classList.toggle('open', index === openIndex);
Here is my current code:
{{!-- #each article --}}
<div class="row">
{{#each articles}}
<div class="col-12-sm col-6-md col-3-lg">
<div class="card m-2">
<div class="card-body">
<h5 class="card-title">{{title}}</h5>
<p class="card-text">{{description}}</p>
</div>
<img class="card-image" src="{{urlToImage}}" alt="Card image cap">
<button data-open-modal="{{#index}}">Open Modal</button>
</div>
</div>
{{/each}}
</div>
</div>
{{#each articles}}
<!-- The Modal -->
<div class="modal closed" id="Modal_{{#index}}">
<!-- Modal content -->
<div class="modal-content">
<span id="spm" class="close" >×</span>
<h2>{{title}}</h2>
<img src="{{urlToImage}}" alt="">
<p>{{content}}</p>
</div>
</div>
{{/each}}
<script>
//Store all modals and modal buttons in variables
const openModalButtons = document.querySelectorAll('[data-open-modal]');
const modals = document.querySelectorAll('.modal');
//Loop through all modal buttons and assign handler to each
openModalButtons.forEach(openModalButton => {
openModalButton.addEventListener('click', (event) => {
//Get index value from number clicked
const openIndex = Number(event.target.dataset.openModal); //Access dataset attribute to read and write
//Loop over each modal.
//Set modal class to open if index is equal to wanted index
modals.forEach((modal, index) => {
modal.classList.toggle('open', index === openIndex);
modal.classList.toggle('closed', index !== openIndex);
});
});
});
</script>
And here is what I tried adding to my script: (It gave no error but did nothing)
const span = document.querySelectorAll('.close');
let spanArr = Array.prototype.slice.call(span);
spanArr.forEach(spanArr => {
spanArr.addEventListener('click', (event) => {
const closeIndex = Number(event.target.dataset.closeModal);
spanArr[closeIndex].forEach(span => {
span.onclick = function() {
modal.style.display = "none";
}
});
});
});
I also tried adding event listeners to the spans, but I was unable to make it work. I am a beginner and this is my first time using handlebars, so thank you for any insight!
There are some issues here that perhaps are due to absent explanations on my part in https://stackoverflow.com/a/73738690/3397771.
First, the reason that const openIndex = Number(event.target.dataset.openModal); works is because there is a data-attribute called data-open-modal defined on each "open" button. It is that data-attribute that we are referencing with dataset.openModal and the value we will get back is the value on the right-hand-side of the equal sign in the attribute, the {{#index}} part.
However, the data-attribute approach is probably excessively complicated for our purposes here. We could, alternatively, use the index obtained in the forEach loop we use to iteratively add our click event listeners.
Next, there is no need for the spanArr[closeIndex].forEach(... loop inside our click handler. spanArr - despite its name - is not an arr(ay); it is a single span element.
The updated code becomes:
const span = document.querySelectorAll('.close');
span.forEach((spanArr, index) => {
spanArr.addEventListener('click', () => {
modals[index].style.display = "none";
});
});
Note: I have left the names of the variables as I found them, but they could and should be improved. For example, span does not communicate what purpose of these elements is or, for that matter, that it is a collection. closeButtons would be a better name. In fact, elements that behave like buttons should use the <button> element, not <span>, so as to be semantically correct and accessible.
I have created a new fiddle.

Sorting Child Elements Based on 'h2' Tag

I have an an example page containing several categories. Each category is wrapped in a .items class which contains an h2 title tag and several links. My goal is to sort each of those categories alphabetically based on the h2 tag.
I found several examples on how to do this, but they were in jquery. I want to do this only in javascript. I found some code that will sort divs but not by the divs's h2 tag.
HTML
<div id="mainContainer" class="column-container row">
<div class="item column">
<h2>Testimonials</h2>
Testimonial slider
</div>
<div class="item column">
<h2>Directories</h2>
Staff Directory
</div>
<div class="item column">
<h2>FAQ</h2>
</div>
<div class="item column">
<h2>Forms</h2>
Simple contact form - WIP
Online payment form using Network Merchants - WIP
Form with attachment
</div>
</div>
JavaScript
sortCategory('#mainContainer');
function sortCategory(s) {
Array.prototype.slice.call(document.body.querySelectorAll(s)).sort(function sort (ea, eb) {
var a = ea.textContent.trim();
var b = eb.textContent.trim();
if (a < b) return -1;
if (a > b) return 1;
return 0;
}).forEach(function(div) {
div.parentElement.appendChild(div);
});
}
How can I modify the javascipt code to sort each .item by the h2 tag?
Solution
With the help of others I figured it out and wanted to share the code. I also formatted the code to be easily read.
//****************************************
// Sort Categories Alphabetically
//****************************************
function sortCategory(elementContainer)
{
var allElements = document.body.querySelectorAll(elementContainer);
Array.prototype.slice.call(allElements).sort(byAlphabet).forEach(function(div)
{
div.parentElement.appendChild(div);
});
}
function byAlphabet(first, second)
{
var order = 0;
var first = first.querySelector('h2').textContent.trim();
var second = second.querySelector('h2').textContent.trim();
first > second ? order = 1 : order = -1;
return order;
}
//Call sortCategory function and pass in the container you want sorted
sortCategory('#mainContainer>.item');
Change ea.textContent.trim() to ea.querySelector('h2').textContent.trim()
and
change eb.textContent.trim() to eb.querySelector('h2').textContent.trim()
This will basically say check each div's first H2 element, rather than the div.
Hope I was helpful!

How to delete current element if previous element is empty in jquery?

I want to delete element with class "tehnicneinfo" but only if the element I'm checking ( with class "h2size") has no child. I have a bunch of those elements, generated by a plugin and I want to delete only the ones that have the next element without child. I wrote jquery code, but it delets all of my elements, not only the ones that have the next element without child. Here is my jquery code:
$('.news .h2size > div').each(function() {
var ul = $(this).find('ul');
if(!ul.length) $(this).remove();
var h1 = $('.news').find('.tehnicneinfo');
var h2size = $('.news').find('.h2size');
if(h2size.prev().is(':empty'))
{
h1.remove();
}
});
this code is inside $(document).ready(function(). Can you tell me what I'm doing wrong? The code is for something else also, so I'm having truble only from var h1 = $('.news').find('.tehnicneinfo'); this line on. Thanks in advance!
Html:
<div class="news">
<h1 class="tehnicneinfo">xxx</h1>
<div class="h2size">
<div id="xyxyxy">
.......
</div>
</div>
<h1 class="tehnicneinfo">yyy</h1>
<div class="h2size"></div>
....
</div>
That's the html, only that there is like 20 more lines that are the same, but with different values (not yyy and xxx). I would need to delete all 'yyy' (they are not all with same value).
You can use filter to filter the ones you want to remove then remove them
"I want to delete only the ones that have the next element without child"
$('.tehnicneinfo').filter(function(){
return !$(this).next().children().length;
// only ones with next sibling with no children
}).remove();
JSFIDDLE

CSS - Parent child selector not working

I have the following code:
.recipe
.ingredients
= f.simple_fields_for :ingredients do |ingredient|
= render 'ingredient_fields', f: ingredient
.row#links
.col-xs-12
= link_to_add_association "", f, :ingredients
%hr
I need to select the ingredients div using jquery in the format of $("#links")["closest"](".recipe > .ingredients") but this doesn't select anything.
It's frustrating though as $("#links")["closest"](".recipe > .row") will return the correct div.
Fiddle of what works and what I want: https://jsfiddle.net/yL6dr4s1/
According to jQuery documentation, closest method tries to find element matching the selector by testing the element itself and
traversing up through DOM.
It does not go through siblings of the element.
Based on your requirements, it seems like you want to traverse the tree for getting match in siblings. jQuery has siblings method to do that. So one solution would be to use siblings method like:
$("#links")["siblings"](".recipe > .ingredients")
Another soultion would be to get closest parent and then use children as answered by #mhodges
As for the query $("#links")["closest"](".recipe > .row"):
It works fine because closest method finds the match in the element itself.
Here is the example to showcase that:
$(document).ready(function() {
// Match found because it is parent
console.log($("#links")["closest"](".wrapper").length);
// No match found because element is sibling
console.log($("#links")["closest"](".row1").length);
// No match found because element is sibling
console.log($("#links")["closest"](".row3").length);
// Match found because it is element itself
console.log($("#links")["closest"](".row2").length);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="wrapper">
<div class="row1">
<span>Content1</span>
</div>
<div class="row2" id="links">
<span>Content2</span>
</div>
<div class="row3">
<span>Content3</span>
</div>
</div>
I am not sure of your requirements on using the exact selector/syntax you provided, but this selector works exactly how you want it to.
$(this).closest(".recipe").children(".ingredients").append('<br/><input type="text" value="Flour">');
Edit
This is the closest I could get:
$(this)["closest"](".recipe").children(".ingredients").append('<br/><input type="text" value="Flour">');
I don't think you can use the selectors in the way you propose.
As far as the DOM is concerned (and jQuery), the element defined by ingredient and the element defined by row are not related. You have to traverse up to the parent element, then back down to get to the child.
Here is a fiddle that hopefully demonstrates the issue.
If you can change it so that ingredient and row are both within the same parent div, you might have more luck with your test selector syntax.
When jQuery gets to buggy, doesn't have a certain option or just becomes to messy to use for a certain operation, it is good we also have access to good old plain javascript.
document.querySelector('#addToIngredients').addEventListener('click' , function(e) {
var recipe = getClosest(e.target,'recipe');
if (recipe) {
var ingred = recipe.querySelector('.ingredients');
ingred.innerHTML += '<br/><input type="text" value="Flour">';
}
});
function getClosest(elem,cls) {
var el = elem.parentNode;
while (el){
if (el.className.indexOf(cls) > -1) {
return el;
}
el = el.parentNode;
}
return false;
}
<div class="recipe">
<div class="ingredients">
<input type="text" value="Eggs"><br/>
<input type="text" value="Flour">
</div>
<div class="row">
<div class="col-xs-12">
Add to .ingredients
</div>
</div>
<hr/>
</div>
Of course they can be combined
$(function() {
$("#addToIngredients").on('click', function(e) {
var recipe = getClosest(e.target,'recipe');
if (recipe) {
var ingred = recipe.querySelector('.ingredients');
ingred.innerHTML += '<br/><input type="text" value="Flour">';
}
});
})

jQuery matching a single class against predefined list where element has multiple classes

I am trying to figure out if there is a way around doing .hasClass() multiple times to see if the active element I am working with has one of currently four specific classes, I am also trying to figure out the most optimized way to do this while the element(s) that are acting as the trigger (or the active element) has multiple classes in it mostly for styling purposes.
Example of the HTML:
<div class="form_row">
<div class="primary_row">1</div>
</div>
<div class="form_row">
<div class="primary_row subexists">1</div>
<div class="primary_row_sub">1a</div>
</div>
<div class="form_row">
<div class="primary_row subexists">1</div>
<div class="primary_row_sub subexists">1a</div>
<div class="secondary_row">2</div>
</div>
<div class="form_row">
<div class="primary_row subexists">1</div>
<div class="primary_row_sub subexists">1a</div>
<div class="secondary_row subexists">2</div>
<div class="secondary_row_sub">2a</div>
</div>
I am in the progress of currently building it up, so this is still a rough draft, but its safe to assume more classes will exist on various elements per the need. The Four main classes I am worried about are primary_row, primary_row_sub, secondary_row, secondary_row_sub. I am building a click handler like:
$('.form_row > div').click(function()
{
//code
});
in this click handler I want to be able to detect if the element clicked is one of the four mentioned above. Where if it is, I want to do something based on which. So determining which class is of the element clicked, rather than building four click handlers one for each type. I am hoping I can keep it optimized and contained to a single handler. Any ideas?
One option:
var classMap = {"one": function () { alert("one");},
"two": function () { alert("two");},
"three": function () { alert("three");}
}
, classes = "";
$('div').click(function (e) {
classes = this.className.split(" ");
for (key in classMap) {
if ($.inArray(key, classes) !== -1) {
classMap[key]();
}
}
});
http://jsfiddle.net/wp9X7/5/
if ($(this).is(".primary_row")) {
...
} elseif ($(this).is(".primary_row_sub")) {
...
} and so on

Categories