actions in the header of an accordion component - ngx-admin

I would like to add actions in the header of the accordion component. The only problem is that if you click the action, the accordion will state chnages between collapsed and expanded.
example:
<nb-accordion>
<nb-accordion-item #primaryItem expanded="true">
<nb-accordion-item-header>
Dashboard
<nb-actions size="small">
<nb-action icon="search">Search</nb-action>
<nb-action icon="star"></nb-action>
<nb-action icon="star" status="warning"></nb-action>
</nb-actions>
</nb-accordion-item-header>
<nb-accordion-item-body>
item content
</nb-accordion-item-body>
</nb-accordion-item>
</nb-accordion>
Looks like this:
I can see in the api that there is a collapsedChange event, but this emits after the change.
AM i out of luck here? IS there a way to intercept and cancel the collapse event?
thanks in advance

so seems I'm the only one looking for this. So I built my own custom card to deliver me the functionality I want. you can take this and use it in your own angular project. its a collapsible card with header and footer sections as well as a section for adding actions.
car component:
<nb-card accent="{{accentColor}}" status="{{statusColor}}">
<nb-card-header>
<span class="float-left card-title">
<ng-content select="[slot=title]"></ng-content>
</span>
<nb-actions size="small" class="float-right">
<nb-action><button type="button" status="basic" nbButton size="small" (click)="toggleExpand()">
<nb-icon icon="{{expandedIcon}}"></nb-icon>{{ expandedText }}
</button></nb-action>
<!-- icon="{{expandedIcon}}" -->
</nb-actions>
<span class="float-right" *ngIf="hasActions">
<ng-content select="[slot=actions]"></ng-content>
</span>
</nb-card-header>
<nb-card-body *ngIf="expandedState" class="content-body">
<ng-content select="[slot=body]"></ng-content>
</nb-card-body>
<nb-card-footer *ngIf="expandedState && hasFooter">
<ng-content select="[slot=footer]"></ng-content>
</nb-card-footer>
</nb-card>
component .ts
import { Component, OnInit, Input } from '#angular/core';
#Component({
selector: 'app-content-card',
templateUrl: './content-card.component.html',
styleUrls: ['./content-card.component.scss'],
})
export class ContentCardComponent implements OnInit {
// STATUS OPTIONS: basic, primary, info, success, warning, danger, control
// ACCENT OPTIONS: basic, primary, info, success, warning, danger, control
#Input() hasActions: boolean;
#Input() hasFooter: boolean;
#Input() accentColor: string;
#Input() statusColor: string;
public readonly upIcon = 'arrowhead-up-outline';
public readonly downIcon = 'arrowhead-down-outline';
public readonly hideText = 'hide';
public readonly showText = 'show';
public expandedState = true;
public expandedText: string;
public expandedIcon: string;
constructor() {}
ngOnInit() {
this.expandedIcon = this.upIcon;
this.expandedText = this.hideText;
}
public toggleExpand(): void {
this.expandedState = !this.expandedState;
if (this.expandedState) {
this.expandedIcon = this.upIcon;
this.expandedText = this.hideText;
} else {
this.expandedIcon = this.downIcon;
this.expandedText = this.showText;
}
}
}
component .scss
.float-right {
float: right;
}
.float-left {
float: left;
}

Sorry if this is a little late - I ran into the same issue and in short, no there is no API or direct way to stop the accordion item from firing the expand/collapse whenever you click anywhere in the header component. The reason is because nebular is attaching a click event to all rendered html children of the accordion header that will fire the expand/collapse event. You can actually see the attached event if you inspect your elements and look at the attached event listeners.
One hacky way is the attempt to remove the attached event.
A second hacky way to get around this and the one I used was to fire the toggle for the accordion a second time after clicking. The end result is that the user does not see the accordion expand (since an expand + collapse = no ui change):
component.html
<nb-accordion>
<nb-accordion-item #primaryItem expanded="true" *ngFor="let item in items; let i = index;">
<nb-accordion-item-header>
Dashboard
<nb-actions size="small">
<nb-action icon="search" (click)="cancelAccordionToggle(i)">Search</nb-action>
<nb-action icon="star" (click)="cancelAccordionToggle(i)"></nb-action>
<nb-action icon="star" status="warning" (click)="cancelAccordionToggle(i)"></nb-action>
</nb-actions>
</nb-accordion-item-header>
<nb-accordion-item-body>
item content
</nb-accordion-item-body>
</nb-accordion-item>
</nb-accordion>
component.ts
// use the following hack for accordian so action items do not trigger toggle
#ViewChildren(NbAccordionItemComponent) listItems: QueryList<NbAccordionItemComponent>;
cancelAccordionToggle(index: number) {
const listItem: NbAccordionItemComponent = this.listItems.toArray()[index];
listItem.toggle();
}
If you don't want hack it like this, the alternative solution is to make your own like you did.

I added the following to my nbButton inside of <nb-accordion-item-header>
(click)="$event.stopPropagation()"
and it worked for me.

Related

Use scrollToAnchor from ViewportScroller in angular 6

I cannot figure out how to use the function ViewportScroller.scrollToAnchor(string anchor).
First of all - how do I define an anchor in my html? I may be confusing anchors, routerlinks and fragments.
My code which is based on fragments as of now:
export class ItemsOverviewPage implements OnInit {
public items: Item[];
constructor(private route: ActivatedRoute,
private scroller: ViewportScroller) {}
public async ngOnInit(): Promise<void> {
const fragment = await this.route.fragment.first().toPromise();
if (fragment !== undefined || fragment !== null) {
this.scroller.scrollToAnchor(fragment);
}
}
}
The html is something like
<ion-card mode="md"
*ngFor="let i of items"
routerDirection="forward"
id="{{ i.title) }}">
</ion-card>
How can I refer to the id? Or should I do an <a>...</a> around whatever I want to scroll to?
I am navigating to the page like:
this.router.navigate(['/items'], { fragment: item.title });
I don't think you can do it that way.
It seems that, when you are using a ngFor, the scrolling to the anchor gets called before the DOM is finalized.
So, in your ngOnInit you can get the fragment, but won't be able to find the anchor, as the ngFor has not completed yet.
One way way to do what you want, could be to use a route parameter rather than a fragment.You can retrieve the parameter in the ngOnInit of your ItemsOverviewPageComponent and store it in a variable (e.g. _fragment), and then scroll to the anchor in the ngAfterViewInit() hook using document.getElementById(this._fragment).scrollIntoView();
Another option, could be using navigationExtras.
Even better, if you need to pass data and potentially complex objects via routes, would be to set up a service that stores the data and then inject it into the components
For more information see ActivatedRoute, NavigationExtras

Vue: Event Methods- Confusion

I have a parent Vue which enables or disables "edit" mode. In non-edit mode all components are read only.
I've implemented this via a data object and all works fine.
I've split out some of the components in child components.
From the parent an $emit message is sent with the new edit mode state:
methods: {
toggleMode () {
this.editMode = !this.editMode
this.$emit('edit-mode-change', this.editMode)
}
Using Vue DevTools I can see the message is emitted.
However, I can't seem to receive it in my child component!I've looked a the docs, but none of the examples match this case. This is what I have currently (in the child component):
methods: {
onEditModeChange: function (mode) {
console.log('mode is', mode)
this.editMode = mode
}
Also tried:
events: {
onEditModeChange: function (mode) {
console.log('mode is', mode)
this.editMode = mode
}
Plus I'm getting an browser console error as follows:
[Vue warn]: Invalid handler for event "edit-mode-change": got false
(found in <Dimensions> at /home/anthony/Projects/Towers-Vue/src/components/assets/Dimensions.vue)
I'm sure I'm doing something basic wrong, but the docs don't reference the events: {} block, yet I've seen it on other code. Nor does it show how to implement a listener.
Thanks for taking the time to view this post, if you can point me in the right direction, it's much appreciated.
In Vue 2, events only flow laterally or up, not down.
What you really want is to simply pass a prop into your components.
In the parent JS:
toggleMode () {
this.editMode = ! this.editMode;
}
In the parent template:
<child-component-1 :editMode="editMode"></child-component-1>
...same for others...
Then simply accept editMode as a prop in each of your child components:
{
props: ['editMode']
}
You can now use editMode within your child's template. It'll track the parent's editMode, so no need for manual events/watchers.
The way vue2 works is by having a one-direction flow of the data, from parent to child, so in your parent component you can have
<template>
<child-component :isEditing="editMode"></child-component>
</template>
<script>
export default {
methods: {
toggleMode () {
this.editMode = !this.editMode
this.$emit('edit-mode-change', this.editMode)
}
}
}
and in child component you use props to get the data
Vue.component('child-component', {
props: ['isEditing'],
template: '<span>edit mode: {{ isEditing }}</span>'
})
we have cover the edit mode for the child. now if you want to send data from child to parent, then child needs to "emit" a signal to the parent, as props are "read only"
in child component you do at any point
someMethod() {
this.$emit('editDone', dataEdited);
}
and in your parent component you "intercept" the message using on:
<template>
<child-component
:isEditing="editMode"
#editDone="someParentMethod"></child-component>
</template>
Greetings!

Nativescript Switch prevent change event firing on initial binding

Hi my template is something like the below
<ListView [items]="modules">
<template let-item="item" >
<StackLayout orientation="vertical">
<Switch (checkedChange)="onSwitchModule(item.id,$event)" [checked]="item.active"></Switch>
</StackLayout>
</template>
</ListView>
My controller is
ngOnInit() {
this._moduleService.getUserModules()
.subscribe(
response=>{
this.modules = response.data;
}
)
}
onSwitchModule(itemId) {
console.log(itemID); //Gets called on initial true binding on switch checked
}
The onSwitchModule get called everytime the page loads with item.active is true on any item, how to handle this ?
NOTE: Beginner in Nativescript
What I did to overcome this is I watch for tap events instead of checkedChange:
<Switch (tap)="switchClicked" [checked]="item.active"></Switch>
and in the callback, you can get the current item from bindingContext:
function switchClicked(args) {
const item = args.object.bindingContext.item;
}
I ran into a similar issue: loading up settings data from an API, and having the checked event fire for the value I'd set from the api -- not desirable in my case. I didn't see a great way to prevent events from firing on the initial binding, so I decided to simply ignore events until I knew they were legit events from the user actually using the switch.
I did that by using a property switchReady to keep track of when you want to start recognizing change events. This pattern also keeps the toggle disabled until you're ready to start accepting changes. This makes use of Switch's isEnabled property, see docs here.
Markup
<Switch [checked]="currentSettings.pushTurnedOn" [isEnabled]="switchReady" (checkedChange)="onPushSettingChange($event)" row="0" col="1"></Switch>
Component
export class SettingsComponent implements OnInit {
currentSettings: Settings = new Settings(false)
switchReady: boolean = false
ngOnInit() {
this.getCurrentSettings()
}
public onPushSettingChange(args) {
let settingSwitch = <Switch>args.object
if (settingSwitch.isEnabled) {
// do something with the event/change
} else {
// we aren't ready to accept changes, do nothing with this change
return
}
}
getCurrentSettings() {
this.settingsService.loadCurrentSettings().subscribe(
() => {
this.currentSettings = this.settingsService.currentSettings
// we've applied our api change via data binding, it's okay to accept switch events now
this.switchReady = true
},
err => alert('There was a problem retrieving your settings.')
)
}
}

In angular2, how to make child image changes when parent image changes?

For example, we have two images, one is in parent component, the other is in child component. Click child image can make the parent image changes, which is easy to implement. But how can I change child image when click parent image?
I would see several ways to do that:
Create a custom event (using #Output) in the child component and fires the event to notify the parent component. See this link for more details: `Error: Output is not defined` when passing value outside a directive to parent directive
Inject the parent component instance into the child one. From the child component, directly update the property used for the image in the parent. See this answer for more details: Angular2 Exception: No provider for Service
Use a shared service with an EventEmitter property to make communicate parent and child components. See this link for more details: Delegation: EventEmitter or Observable in Angular2
Your plunker doesn't work so it's a little hard to figure out where your specific problem is. But the general case isn't too hard.
If you want to make the parent change when the child is clicked, you just need to use an output on the child. For example - clicking the child here alternates the image in the parent (working plunk)
import {Component, Output, EventEmitter} from 'angular2/core'
#Component({
selector: "child";
template:`
<div>
<h3> Child Image: </h3>
<img src="{{uri}}" (click)="imageClicked($event)" />
</div>
`
})
export class childComponent {
#Output() wasClicked: EventEmitter<Node> = new EventEmitter();
uri: string = "https://upload.wikimedia.org/wikipedia/commons/e/e5/Элемент_И-НЕ_%28100%29.PNG"
imageClicked(e) {
console.log("child component recieved click" )
this.wasClicked.emit(e)
}
}
#Component({
selector: "parent";
template:`
<div>
<h2> Parent Image: </h2>
<img src="{{uri}}" />
</div>
<child (wasClicked)="childClicked(e)"></child>
`,
directives: [childComponent]
})
export class parentComponent {
switch: boolean = false;
uri: string = "https://upload.wikimedia.org/wikipedia/commons/1/14/Элемент_Исключающее_ИЛИ_%28100%29.png"
childClicked(e){
console.log("parent recieved click event from child")
this.switch
? this.uri = "https://upload.wikimedia.org/wikipedia/commons/1/14/Элемент_Исключающее_ИЛИ_%28100%29.png"
: this.uri = "https://upload.wikimedia.org/wikipedia/commons/7/7a/Элемент_ИЛИ-НЕ_%28100%29.PNG"
this.switch = !this.switch
}
}
how to change child image when click parent image?
In general, when a parent wants to communicate something to a child, use a child input property. If you want to execute some logic, implement ngOnChanges() to be notified when an input property changes:
ngOnChanges(changes) {
console.log(changes);
}
I assume you want to do something in PauseBtnComponent when input propertyplayBtnStatus changes:
ngOnChanges(changes) {
console.log(changes);
if(changes.playBtnStatus.currentValue) { ... }
else { ... }
}
Plunker
Another option is to use #ViewChild, #ViewChildren, or #Query in the parent component to get references to the child(ren). Then you can call public method(s) on the child.

Angular ng-click on inputed later on dom

i'm changing a innter html of a dom element with a button. And when button is clicked i want to fire another controller function. Something like that. ... But is not working :).
$scope.addBtn = function() {
$('domtarget').html('<button ng-click="removeButton();"></button>');
}
$scope.removeBtn = function() {
$('domtarget').html('');
}
Please suggest fix :)
Do not modify DOM inside your controller, ever.
<div ng-show="showMe"></div>
<button ng-click="showMe = !showMe;anotherAction()">Switch</button>
<button ng-click="someOtherAction()">Switch2</button>
.
function SomeCtrl($scope) {
$scope.showMe=true;
$scope.anotherAction = function () {
alert("gotcha");
};
$scope.someOtherAction = function () {
$scope.showMe = !$scope.showMe;
$scope.anotherAction();
};
}
For hiding/showing an element conditionally, use ng-show or ng-hide.
For firing an event on click, use ng-click

Resources