I am populating a Bootstrap Accordion with content (from Orders) from an array in Angular.
My problem is, that while it technically works, all the accordion buttons are displayed as active (pressed), although the body itself is already collapsed (as intended).
Here's the Orders component:
<div class="accordion" id="accordionOrders">
<div class="accordion-item" *ngFor="let order of orders">
<order [order]="order"></order>
</div>
</div>
The Order Component takes the specific order details and displays them. Here is the Order components template:
<h2 class="accordion-header" [id]="'heading'+order.orderId">
<button class="accordion-button" type="button" data-bs-toggle="collapse"
[attr.data-bs-target]="'#collapse'+order.orderId" aria-expanded="false"
[attr.aria-controls]="'collapse'+order.orderId">
Order #{{ order.orderId }}, ordered at {{ order.orderDate | date:'hh:mm' }}
</button>
</h2>
<div [id]="'collapse'+order.orderId" class="accordion-collapse collapse"
[attr.aria-labelledby]="'heading'+order.orderId"
data-bs-parent="#accordionOrders">
<div class="accordion-body">
Here goes the content
</div>
</div>
I wondered if the nesting of a Angular component within the Accordion structure might cause the problem, but it works - except for the active buttons. When I nested a HTML element at the same position in Codepen, everything works fine as well.
I am a novice and just can't figure out what I am overseeing.
Related
I've built a widget in ServiceNow that displays a user's onboarding steps and when a step is clicked upon, a div appears below that lists out all of the tasks that they need to complete. While this works fine, I think it would look slicker if instead of having a div appear below the onboarding steps with a line divider, that the list of tasks would take the place of the entire onboarding steps div. Then maybe have a back button that takes them back to the onboarding steps with some fade in/fade out animation. If possible, I would like this to be done without jquery. Can someone provide some guidance or direct me towards an example?
My HTML so far looks like this:
<div class="container" ng-if="!c.data.loading && c.stage.length>0">
<div class="parent">
<div class="child" ng-repeat="item in c.stage track by $index">
<div class="at-work-process position-relative overflow-hidden text-center">
<div class="at-work-process-text">
<span class="at-work-process-step" ng-class="{inactive: item.workflow_order>c.currentOrder}">Step</span>
<span class="at-work-process-number" ng-class="{inactive: item.workflow_order>c.currentOrder}">{{$index+1}}</span>
<span class="at-work-process-number-text" ng-class="{inactive: item.workflow_order>c.currentOrder}">{{item.workflow_stage}}</span>
<div class="at-separator-thick" ng-class="{active_stage: item.currentStage, completed_stage: item.incompleteTotal==0 && item.workflow_order<c.currentOrder}"></div>
</div>
<div ng-click="showDetails(item);" class="at-work-process-details" ng-class="{inactive: item.workflow_order>c.currentOrder}">
<div ng-if="item.incompleteTotal>0 && item.workflow_order<=c.currentOrder" class="full-height">
<i class="material-icons" style="color:#e53935;">assignment_late</i>
<span ng-if="item.incompleteTotal>1">Incomplete <br/> {{item.incompleteTotal}} Tasks Require Your Attention</span>
<span ng-if="item.incompleteTotal==1">Incomplete <br/> {{item.incompleteTotal}} Task Require Your Attention</span>
</div>
<div ng-if="item.incompleteTotal==0 && item.workflow_order>c.currentOrder" class="full-height">
<i class="material-icons" style="color:#78B4F3;">assignment</i>
<span>Not Started</span>
</div>
<div ng-if="item.incompleteTotal==0 && item.workflow_order<c.currentOrder" class="full-height">
<i class="material-icons" style="color:#43A047;">assignment_turned_in</i>
<span>Complete</span>
</div>
</div>
</div>
</div>
</div>
//am hoping the below div would take the place of the above div instead of showing up underneath it//
<div ng-if="active_tasks.length>0" ng-class="taskClass" class="text-center tasksDiv">
<md-divider></md-divider>
<h3>{{active_workflow}} Tasks:</h3>
<ul style="list-style:none; padding-left:0; display:inline-block">
<li ng-repeat="tasks in active_tasks track by $index" style="display:flex; align-items:center; padding-bottom:0.5rem;">
<i class="material-icons" style={{tasks.style}}>{{tasks.icon}}</i>
<a ng-if="tasks.url.indexOf('table2')!=-1" ng-click="c.enroll(tasks)" href="javascript: void(0)" ng-class="{completed_inactive: tasks.state==3}">{{tasks.short_description}}</a>
<a ng-if="tasks.url.indexOf('table1')!=-1" ng-click="c.tableNew()" href="javascript: void(0)"ng-class="{completed_inactive: tasks.state==3}">{{tasks.short_description}}</a>
<a ng-if="tasks.hr_task_type=='url' && tasks.url.indexOf('c.')==-1" href="{{tasks.url}}" target="_blank" ng-class="{completed_inactive: tasks.state==3}">{{tasks.short_description}}</a>
<a ng-if="tasks.url.indexOf('launchWith')!=-1" ng-click="c.launchWith()" href="javascript: void(0)" ng-class="{completed_inactive: tasks.state==3}">{{tasks.short_description}}</a>
<a ng-if="tasks.hr_task_type!='url'" ng-click="review(tasks)" href="javascript: void(0)" target="_blank" ng-class="{completed_inactive: tasks.state==3}">{{tasks.short_description}}</a>
</li>
</ul>
</div>
</div>
I would attach an ng-if to the first div that works on the opposite condition of the second div.
So in this case, your second div has ng-if="active_tasks.length>0" as its condition for display. So you could set an ng-if="active_tasks.length<=0" on the first div so that any time the second div is displayed, the condition will be false for the first div. Then just remove the divider in the second div (since you don't want that anymore). Now when you perform an action on the first div and active_tasks gets populated, the first one will be destroyed and the second one will be created.
Note that in this case, you're using ng-if which will create/destroy DOM elements. You can also use ng-show to show/hide the DOM elements if you anticipate the user going back and for a lot (so that they're created up front and just hidden instead of being recreated each time the user changes the state)
Your html is slightly hard to follow, so I'm not sure which blocks you would like to hide/show, but you can do this very simply. Just create a variable, showTaskList on your scope and set it to true/false. Then on the parent div for the onboarding list do, ng-if="!showTaskList" and on the parent div for the task list, ng-if="showTaskList".
When the person clicks the button to switch between the task list or the onboarding steps, just switch this bool to the opposite, showTaskList = !showTaskList.
I want to create a custom element that's going to work like an accordion container for other elements. I'm planning on using the Bootstrap 4 Collapse. I want to be able to place a variable number of other custom elements within it so using slots is not enough.
For example if I knew that there will be 3 elements placed in the accordion I would put three slots in accordion.html and then use it like this:
<accordion>
<first-custom-element slot="first-element"></first-custom-element>
<second-custom-element slot="second-element"></second-custom-element>
<third-custom-element slot="third-element"></third-custom-element>
</accordion>
The thing is, I don't know how many elements will need to placed inside the accordion because I want to make it more generic and reusable so I can use it in multiple pages in my application. What I want is a way to read everything placed inside the <accordion> tags and create slots for each one of these elements the fly. Is there such a functionality in Aurelia or should go for a custom implementation?
Split out the items from the overall accordion element, like this:
accordion.html
<template>
<div id="accordion" role="tablist" aria-multiselectable="true">
<slot></slot>
</div>
</template>
accordion-item.html
<template bindable="panelTitle, headingId, itemId">
<div class="panel panel-default">
<div class="panel-heading" role="tab" id="${headingId}">
<h4 class="panel-title">
<a data-toggle="collapse" data-parent="#accordion" href="#${itemId}" aria-expanded="true" aria-controls="${itemId}">
${panelTitle}
</a>
</h4>
</div>
<div id="${itemId}" class="panel-collapse collapse in" role="tabpanel" aria-labelledby="${headingId}">
<slot></slot>
</div>
</div>
</template>
Usage
<template>
<require from="accordian.html"></require>
<require from="accordian-item.html"></require>
<accordian>
<accordian-item panel-title="Panel 1 Title" heading-id="headingOne" item-id="collapseOne">
<accordian item 1 content>
</accordian-item>
<accordian-item panel-title="Panel 2 Title" heading-id="headingTwo" item-id="collapseTwo">
<accordian item 2 content>
</accordian-item>
</accordian>
</template>
If you need only one accordion, juste use one slotin your template:
<template>
<slot></slot>
</template>
Don't put slot property in your inside elements and the whole content will be inserted at the <slot> position.
<accordion>
<first-custom-element></first-custom-element>
<second-custom-element></second-custom-element>
<third-custom-element></third-custom-element>
...
</accordion>
So, I've got two product boxes placed next to each other:
<div class="container product-box" data-toggle="collapse" href=".container-name-desc-1#info-14005" aria-expanded="true">
<div class="product-side-content product-side-content-selected">
<div class="product-side-content-name">Name 1</div>
<div class="product-icon icon-arrow-down"></div>
<div class="container-name-desc-1 text-right collapse" id="info-14005" aria-expanded="false" style="height: 0px;">
<p>
Some very very long description of product
</p>
<a class="btn btn-success product-add" product-id="14005" product-price="10.45">CHOOSE</a>
</div>
</div>
</div>
and second
<div class="container product-box" data-toggle="collapse" href=".container-name-desc-2#info-14005" aria-expanded="true">
<div class="product-side-content product-side-content-selected">
<div class="product-side-content-name">Name 2</div>
<div class="product-icon icon-arrow-down"></div>
<div class="container-name-desc-2 text-right collapse" id="info-14005" aria-expanded="false" style="height: 0px;">
<p>
Some very very long description of product
</p>
<a class="btn btn-success product-add" product-id="14005" product-price="10.45">CHOOSE</a>
</div>
</div>
</div>
As you can see in code above, there are two classess for containers which have product description inside paragraph tag:
first is container-name-desc-1 and second container-name-desc-2 and both container has same id info-14005.
Pay attention to href attributes in the main container.
When I click on first product box arrow icon to expand/collapse, to show the product description, it works normally, but when I'm clicking on second product box arrow icon, the first product box description container expands/collapses again.
This is how it looks:
I think it has to do with same id name, so info-14005, but classess are different.
Does this mean, that browser pays attention to CSS specificity values when user specifies the href destination to some element's class/id inside, whether it's linking to child of same container or element of some other container?
In my example hrefs for both product boxes are linked to same id but different classess, so in theory clicking on one product box's arrow should expand/collapse only this particular box description container, right?
Or is this have something to do with the fact that IDs have higher specificity than classess, and last value of every href is id #info-14005, so browser somehow decides to collapse/expand only the first element which has this id assigned to itself?
I don't understand it, if anybody could answer the question?
I want to create an accordion with nested levels. So I have used Angular's accordion directive to create nested levels. But the last level contains a list which should get displayed one by one on new line. But what happens in my case all sub categories gets listed in one line.
I saw in the console that all sub categories are getting listed in one panel body. They should get separate class for each of the sub categories. Any one know how to do that? This is code of my accordion:
<div style='padding-top:50px;'>
<accordion>
<accordion-group heading="Title">
<accordion close-others="oneAtATime">
<accordion-group heading="Category">
<span>Subcat1</span>
<span>Subcat2</span>
</accordion-group>
</accordion>
</accordion-group>
</accordion>
</div>
Directive accordion-group is replacing following code:
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="panel-title">
<a href class="accordion-toggle" ng-click="toggleOpen()" accordion-transclude="heading">
<span ng-class="{\'text-muted\': isDisabled}">{{heading}}</span>
</a>
</h4>
</div>
<div class="panel-collapse" collapse="!isOpen">
<div class="panel-body" ng-transclude>
</div>
</div>
</div>
So whatever I am printing inside <accordion-group></accordion-group> is getting placed in div having class panel-body. But as I am printing Subcat1 and Subcat2 inside accordion-group is going in one div panel-body. Like this:
<div ng-transclude="" class="panel-body">
<span class="ng-scope">Subcat1</span>
<span class="ng-scope">Subcat2</span>
</div>
But I want separate panel-body div for each of Subcat. Like
<div class="panel-body"><span>Subcat1</span></div>
<div class="panel-body"><span>Subcat1</span></div>
For more details see this plunker
Sorry for my english..:P
A <span> is defined as an inline element and will on its own not introduce any line breaks.
To achieve the output on multiple lines, you would either want to use elements that do that on their own, or make use of custom styles, usually via CSS.
If you really have a list of subcategories, you could for example use an ordered list <ol>.
Simple example:
<accordion>
<accordion-group heading="Title">
<accordion close-others="oneAtATime">
<accordion-group heading="Category">
<ol>
<li>Subcat1</li>
<li>Subcat2</li>
</ol>
</accordion-group>
</accordion>
</accordion-group>
Updated Plunker: Link
But this has nothing to do with your tags angularjs or the angular-ui accordion, it's an issue of plain HTML.
I have page with various accordions on. Some of these accordions reference content in another accordion on the page, my question is how do I make it so that the in the first accordion expands the other accordion.
I cant seem to get it to work.
I have set the link up as Test
And on the first <p> under the <div class="panel-body"> as <p id="SubTitle2">Content</p> but this does nothing at all.
Am I putting it in the wrong place or am I going about it completely wrong?
Below is my complete code
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<a class="accordion-toggle collapsed" data-parent="#accordion" href="#collapseOne" data-toggle="collapse">Heading 1</a>
</h3>
</div>
<div class="panel-collapse collapse" id="collapseOne">
<div class="panel-body">
<p>Test</p>
</div>
</div>
</div>
</div>
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<a class="accordion-toggle collapsed" data-parent="#accordion" href="#collapseTwo" data-toggle="collapse">Heading 2</a>
</h3>
</div>
<div class="panel-collapse collapse" id="collapseTwo">
<div class="panel-body">
<p id="SubTitle2">Content</p>
</div>
</div>
</div>
</div>
If you're inclined to use jQuery for this, you could do the following:
$('#SubTitle2').click(function(){
$('#collapseTwo').collapse('show');
});
...having the anchor setup something like this:
Test
You can test it out here: http://www.bootply.com/jQMOup05vG
by using data-target you can achieve this as
<a data-toggle="collapse" data-target="#id_to_expand">click</a>
hope this will help you
Managed to get it working by mixing the answers from Nasir Mahmood and webeno.
Basically I mixed and matched both solutions and it works. My code is now:
<p><a class="collapsed"
href="#SubTitle2"
data-toggle="collapse"
data-target="#collapseTwo">Test
</a></p>
As you are not able to mark responses as answer I will add a comment on them both
None of the answers submitted previously are acceptable for my situation. I don't know ahead of time which links exist on the page, and which links will necessitate expanding collapsed sections. I also have nested collapsible sections. Also, I want the expansion to occur if someone links into the page from another document. So I settled on a solution that detects dynamically what sections must be opened. When a click on a link I care about happens, the handler:
Finds all parents of the target which are collapsed sections. ($(target).parents(".collapse:not(.in)").toArray().reverse();)
Starting with the outermost parent, it requests that the element be expanded. (These are the calls to next(). The call $parent.collapse('show'); is what shows a parent.)
Once an element is expanded, it expands the next parent (closer to the link target). (if (parents.length)...)
Until finally it requests that the target be scrolled into view. (target.scrollIntoView(true);)
I've initially tried walking the DOM tree in the reverse order, from innermost parent to outermost, but I got strange results. Even compensating for event bubbling, the results were inconsistent. The request for scrolling to the target is necessary as it is likely that the browser will have scrolled the window before the sections are expanded.
Here's the code. win and doc are set to the Window and Document instance that actually hold the DOM tree being processed. Since frames may be used, just referring to window and document ain't okay. The root variable is an Element that holds the part of the DOM I care about.
function showTarget() {
var hash = win.location.hash;
if (!hash)
return;
var target = doc.getElementById(hash.slice(1));
if (!target)
return;
var parents =
$(target).parents(".collapse:not(.in)").toArray().reverse();
function next(parent) {
var $parent = $(parent);
$parent.one('shown.bs.collapse', function () {
if (parents.length) {
next(parents.shift());
return;
}
target.scrollIntoView(true);
});
$parent.collapse('show');
}
next(parents.shift());
}
win.addEventListener('popstate', showTarget);
$(root).on('click', 'a[href]:not([data-toggle], [href="#"])',
function (ev) {
setTimeout(showTarget, 0);
});
showTarget();
Notes:
The selector a[href]:not([data-toggle], [href="#"]) limits event listening only to those a elements that are actually hyperlinks into the rest of the document. Sometimes a is used for other purposes. For instance, those a elements that have a data-toggle attribute or have href="#" are not used for navigating through the page.
setTimeout(showTarget, 0) allows the default action for a click on a hyperlink to happen (i.e. the hash changes) first, and then showTarget is called. This works everywhere except FF. A few tests show that showTarget won't see the change on FF unless the timeout is raised. 0ms does not work, 1ms did not, and 10ms works. At any rate, I'd rather not hardcode some FF-specific value that may change in the future so we also listen on popstate` to catch those cases that would not be caught on FF.
The explicit call to showTarget() is necessary for cases when the page is loaded with a URL that has a hash.
I've tried an implementation that listened only on popstate but it proved unreliable due to how Chrome and FF differ in how they generate popstate. (Chrome generates it whenever a link is clicked, even if the hash does not change. FF generates it only when the hash changes.)
The code above has been tested in Chrome (39, 38, 36), FF (31), and IE (10, 11).
you have set panel-group id=accordion twice. Thats not good.
Wrap your two panels into one panel-group-block with one id:
<div class="panel-group" id="accordion">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<a class="accordion-toggle collapsed" data-parent="#accordion" href="#collapseOne" data-toggle="collapse">Heading 1</a>
</h3>
</div>
<div class="panel-collapse collapse" id="collapseOne">
<div class="panel-body">
<p>Test</p>
</div>
</div>
</div>
<!-- SNIP
</div>
<div class="panel-group" id="accordion">
SNAP -->
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">
<a class="accordion-toggle collapsed" data-parent="#accordion" href="#collapseTwo" data-toggle="collapse">Heading 2</a>
</h3>
</div>
<div class="panel-collapse collapse" id="collapseTwo">
<div class="panel-body">
<p id="SubTitle2">Content</p>
</div>
</div>
</div>
</div>