Variable Number of Slots in Aurelia - html

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>

Related

Bootstrap: Accordion buttons active, while body collapsed

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.

Polymer 2 migration issues

Recently tried to migrate a Polymer1 app to Polymer2 but I am really having a hard time to get stuff working. Here are some things I noticed when trying:
Do I really have to hand pick every paper/iron element for polymer2? I used to just install paper-elements and the like, this was a convenient way to simply get all components in a category, is there a similar way to get all paper-elements for polymer 2?
The Api for paper-toolbar specifies a justify property that would center the content, but this does not seem to work in the case below:
```
<paper-toolbar class="tall" justify="center" bottom-justify="center">
<div slot="top" class="title">Top Title</div>
<div slot="middle" class="title">Middle Title</div>
<div slot="bottom" class="title">Bottom Title</div>
</paper-toolbar>
```
The fullbleed class on the body does not work anymore. I've included the iron-layout-classes and iron-layout files, but these do not have any effect :(
Am I missing something?
Yes you have to pick every paper/iron elements.
Elements have dependencies and you just need to install few of them to have a whole lot of polymer elements, I don't see the advantage of downloading a pack of elements when you will just not use them all anyways.
And the reason why middleJustify or bottomJustify are not working in your case is because you are using a class="title". the toolbar will turn the slot title into a flex element, that means the element will stretch as far as possible to the right in the available space of its container. Because the title is taking all the available space justifying it to center will just fail (trying to center something that's all-contained in its container and it won't move).
If you want to justify to the center, try that :
<paper-toolbar class="tall" bottom-justify="center">
<div slot="top" class="title">Top Title</div>
<div slot="middle" class="title">Middle Title</div>
<div slot="bottom">Bottom Title</div> <!-- remove the class title, not recommended for a title -->
</paper-toolbar>
or
<paper-toolbar class="tall" justify="center" bottom-justify="center">
<div slot="top" class="title">Top Title</div>
<div slot="middle" class="title">Middle Title</div>
<div slot="bottom" class="title" style="text-align:center;">Bottom Title</div>
</paper-toolbar>

Elements appear on one line instead of separate lines

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.

Twitter Bootstrap 3 - Auto Expand An Accordion When A Link Clicked In Another Accordion

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>

Different Toolbar for every page in core-scaffold

I'm currently using core-scaffold for my basic app structure. However, i want to have a different toolbar for every page. I want to do something similar to this:
<core-scaffold>
<core-header-panel navigation flex mode="seamed">
<core-toolbar>
Veganolux
</core-toolbar>
<core-menu>
<paper-item icon="store" label="Restaurants" on-tap="{{showRestaurants}}"></paper-item>
<paper-item icon="settings" label="Settings" on-tap="{{showSettings}}"></paper-item>
<paper-item icon="info" label="About" on-click="{{showAbout}}"></paper-item>
</core-menu>
</core-header-panel>
<div tool>Restaurants</div>
<core-pages id="pages" selected="0">
<section id="pageRestaurants">
<div tool>Toolbar with buttons</div>
</section>
<section id="pageSettings">
<div tool>Toolbar without buttons</div>
</section>
<section id="pageAbout">
<div tool>Toolbar with other buttons</div>
</section>
</core-pages>
</core-scaffold>
This is however not possible, because the is not a direct child of core-scaffold. I know that I could add databinding to change the title, but it gets to complex if I add different buttons/etc. to the toolbar.
One option is to create an activePage variable that holds the currently selected page. Your on-tap event handlers would update the activePage variable. Then you could wrap sections of the core-header-panel in conditional templates like <template if="{{ activePage === 'restaurants' }}">.
The other option is to create multiple layout elements and wrap the page element in a layout like this http://erikringsmuth.github.io/app-router/#/layouts.