I need to use introjs to tour new features on an angular 6 app.
Inside the tour some step target items in ngFor in a child component of the component where I start introjs. The library is able to target child component html elements but seems to not be able to target element inside ngFor. The usage I want is simple I have a li that represent a card item I loop on as :
<div *ngFor="let source of sourcesDisplay; let idx = index" class="col-lg-5ths col-md-4 col-sm-6 col-xs-12">
<!-- item -->
<div class="item blue-light">
<div class="header">
<div class="categories">
<div class="truncat">{{source.categories.length > 0? source.categories : 'NO CATEGORY'}}</div>
</div>
<a routerLink="{{ organization?.name | routes: 'sources' : source.id }}" class="title">
<div class="truncat">{{source.name}}</div>
</a>
<div class="corner-ribbon blue-light" *ngIf="isARecentSource(source.createdAt)">New</div>
<div class="corner-fav" [class.is-active]="isSourceFavorite(source.id)"
(click)="toggleFavorite(source.id)"><em class="icon-heart-o"></em></div>
</div>
<a class="content" routerLink="{{ organization?.name | routes: 'sources' : source.id }}">
<div class="icon">
<em [ngClass]="source.icon? 'icon-'+source.icon : 'icon-cocktail3'"></em>
</div>
</a>
<div class="actions">
<ul class="buttons three">
<li class="icon-font" ><a (click)="openDashboardModal(source)"
[class.disable]="source.powerbi.length === 0" class="btn-effect"><em
class="icon-chart-bar"></em></a></li>
<li class="icon-font"><a
(click)="openDatalakeModal(source)" [class.disable]="source.datalakePath.length === 0"
class="btn-effect"><em class="icon-arrow-to-bottom"></em></a>
</li>
<li class="icon-font"><a routerLink="{{ organization?.name | routes: 'sources' : source.id }}"
class="btn-effect"><em class="icon-info-circle"></em></a></li>
</ul>
</div>
</div>
</div><!-- /item -->
And I want to target part of this card like a button for example as :
<li class="icon-font" id="step4{{idx}}">
<a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
<em class="icon-chart-bar"></em>
</a>
</li>
and then in my component :
const intro = IntroJs.introJs();
intro.setOptions({
steps: [
{
element: document.querySelector('#step40'),
intro: 'Welcome to the Data Portal ! Explore and download data accross any division. Let\'s discover the new home page.'
}]})
intro.start();
Is there something I am doing wrong ? is introjs not able to do it at all ? is there another lib that can do it?
Thank you in advance
The sections generated by the *ngFor loop may not be rendered yet in the AfterViewInit event. To detect the presence of these elements, you should use #ViewChildren and monitor the QueryList.changes event.
In the template, define a template reference variable #step instead of id:
<li class="icon-font" #step>
<a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
<em class="icon-chart-bar"></em>
</a>
</li>
In the code, retrieve the elements with #ViewChildren, and subscribe to the QueryList.changes event. You may have to convert the QueryList to an array to access an element with a specific index.
#ViewChildren("step") steps: QueryList<ElementRef>;
ngAfterViewInit() {
this.startIntroJs(); // Maybe already present
this.steps.changes.subscribe((list: QueryList<ElementRef>) => {
this.startIntroJs(); // Created later
});
}
private startIntroJs(): void {
if (this.steps.length > 40) {
const intro = IntroJs.introJs();
intro.setOptions({
steps: [
{
element: this.steps.toArray()[40].nativeElement,
intro: 'Welcome to the Data Portal...'
}
]
});
intro.start();
}
}
Related
I'm beginner in Emberjs, so i need to pass selected item in list to basket.
I have route catalog
<div class="flexbox">
{{left-menu-bar}}
<div class="main">
{{side-basket items=items}}
<div class="catalog-container">
<div class="container-inner">
{{#list-filter filter=(action 'filterByName') as |resultItems i|}}
<ul class="responsive-table">
<li class="table-header" >
<div class="col col-1">Наименование</div>
<div class="col col-2 radio-group">Год<i class="fas fa-angle-up angle angle-left radio" note="up" {{action 'sortColumn' value="target.note"}}></i><i class="fas fa-angle-down angle radio" data-value="down"></i></div>
<div class="col col-3">Количество<i class="fas fa-angle-up angle angle-left radio"></i><i class="fas fa-angle-down angle"></i></div>
<div class="col col-4">Цена<i class="fas fa-angle-up angle angle-left"></i><i class="fas fa-angle-down angle"></i></div>
<div class="col col-5">Примечание</div>
</li>
{{#each resultItems as |itemUnit|}}
{{item-list item=itemUnit gotItem=(action 'getBasketItem')}}
{{/each}}
</ul>
{{/list-filter}}
</div>
</div>
</div>
catalog.js controller
export default Controller.extend({
items: [],
actions: {
filterByName(param) {
if (param !== '') {
return this.get('store').query('item', {name: param})
}
}
getBasketItem(param){
if (param !== '') {
var item = this.get('store').query('item', {name: param});
//how to add item in items to use in side-basket
}
}
}
});
component item-list.hbs
<li class="table-row hvr-grow" {{action 'handleItem' item.name}}>
{{yield result}}
<div class="col col-1" data-label="Наименование">{{item.name}}</div>
<div class="col col-2" data-label="Год">{{item.year}}</div>
<div class="col col-3" data-label="Количество">{{item.quantity}}</div>
<div class="col col-4" data-label="Цена">{{item.cost}}</div>
<div class="col col-5" data-label="Примечание">{{item.info}}</div>
</li>
item-list.js
export default Ember.Component.extend({
selectedIndex : false,
actions: {
handleItem(param) {
let handledItem = this.get('gotItem');
handledItem(param);
}
}
});
and side-basket component with nested basket-list component
scheme
test
test with manual writing
how to realise this transfer?
In the long-run, you will find it easier if you move your data fetching to your route rather than the controller and just use query params in the controller. If you make this change, when you set your filter you can update the query params, which will in turn cause the router to refresh the model. The Ember docs explain this quite well on this page:
https://guides.emberjs.com/release/routing/query-params/
and you also need to look at this section:
https://guides.emberjs.com/release/routing/query-params/#toc_opting-into-a-full-transition
Note: this.get('store').query('item', {name: param}) in getBasketItem is returning a promise. If you change to fetching your data in the route using query params, you will be able to access your data via this.get('model') in your component and reference model in your route template - both of which will be the resolved data rather than a promise.
That should then simplify your code and make it easier to work with actual items in the controller and component. Rather than needing to fetch the item from the store in getBasketItem, you should be able to pass the item as a parameter and push it onto your items property with something like this.get('items').pushObject(selectedItem).
I am pretty close to having this app finished, but have one last hurdle. I am dynamically populating tabs and data via the WordPress Rest API and when I only had 2 tabs it worked wonderfully, but when I added tab 3 and 4 I ran into issues. When I click tabs 2-4 all tabs receive the "active" class instead of just the one that was clicked; thus also all 3 tabs content data also displays.
Here is the code:
var homeApp = angular.module('homeCharacters', ['ngSanitize']);
homeApp.controller('characters', function($scope, $http) {
$scope.myData = {
tab: 0
}; //set default tab
$http.get("http://bigbluecomics.dev/wp-json/posts?type=character").then(function(response) {
$scope.myData.data = response.data;
});
});
homeApp.filter('stripTags', function() {
return function(text) {
return text ? String(text).replace(/<[^>]+>/gm, '') : '';
};
});
<section class="characters" ng-app="homeCharacters" ng-controller="characters as myData">
<div class="char_copy">
<h3>Meet the Characters</h3>
<div class="char_inject" ng-repeat="item in myData.data" ng-show="myData.tab === item.menu_order">
<div class="copy_wrap">
<h3>{{ item.acf.team }}:</h3>
<h2>{{ item.acf.characters_name }} <span>[{{item.acf.real_name}}]</span></h2>
<p class="hero_type">{{ item.acf.hero_type }}</p>
<div class="description" ng-repeat="field in item.acf.character_description">
<p>{{field.description_paragraph}}</p>
</div>
Learn More
</div>
<div class="image_wrap">
<img src="{{ item.acf.homepage_full_image.url }}" />
</div>
</div>
</div>
<div class="char_tabs">
<nav>
<ul ng-init="ch.tab = 0">
<li class="tab" ng-repeat="item in myData.data" ng-class="{'active' : item.menu_order == myData.tab}">
<a href ng-click="myData.tab = item.menu_order">
<img src="{{ item.featured_image.source }}" />
<div class="tab_title_wrap">
<h3>{{ item.acf.characters_name }}</h3>
</div>
</a>
</li>
</ul>
</nav>
</div>
</section>
I would love any ideas! Thanks!
The code seems to work, see Fiddle. What are the values of menu_order? If they are the same for cases 2-4, then that would explain the behaviour.
I have 2 directives: wa-hotspots & wa-tooltips.
On ng-mouseover of wa-hotspots it takes that $index of wa-hotspot and sets the visibility and position of wa-tooltip via ng-class:on and ng-style="tooltipCoords" by matching indexes.
Note: Since wa-hotspots & wa-tooltips share the same collection page.hotspots and therefore they share teh same index via ng-repeat
Problem:
When you hover over wa-hotspots it sets the ng-style position to ALL of the elements in wa-tooltips. I only want it ot set the proper matching index. Since the visiblity is controlled by ng-class, This doesn't really matter but it seems like it's extra overhead that could be avoid.
Therefore:
Question:
How can I make sure that my ng-style isn't styling all the wa-tooltips on hover of wa-hotspots? But rather, style only the tooltip that matches the proper shared index?
<ul id="wa-page-{{page.pageindex}}" class="wa-page-inner" ng-mouseleave="pageLeave()">
<li wa-hotspots
<map name="wa-page-hotspot-{{page.pageindex}}">
<area ng-repeat="hotspot in page.hotspots"
class="page-hotspot"
shape="{{hotspot.areashape}}"
coords="{{hotspot.coordinatetag_scaled}}"
ng-mouseover="showTooltip($index, hotspot.coordinatetag_scaled)"
ng-mouseout="hideTooltip()">
</map>
</li>
<li class="tooltip-wrapper">
<ul class="tooltip">
<li wa-tooltips
ng-repeat="hotspot in page.hotspots"
ng-class="{on: hovered.index == $index}"
ng-mouseover="hovered.active == true"
ng-mouseout="hovered.active == false"
ng-style="tooltipCoords" hotspot="hotspot">
</li>
</ul>
</li>
</ul>
tooltip:
You need to make it per item like in your case - hotspot.tooltipCoords then set that variable by index.
you can do the check inside the expression function.
Heres a fiddle
<div ng-controller="MyCtrl">
<div ng-repeat="item in items" ng-style="isChecked($index)">
name: {{item.name}}, {{item.title}}
<input type="checkbox" ng-model="item.checked" />
</div>
</div>
...
$scope.isChecked = function($index){
var color = $scope.items[$index].checked ? 'red' : 'blue';
return {color:color};
}
Instead of
ng-mouseover="hovered.active == true"
ng-mouseout="hovered.active == false"
use
ng-mouseover="hotspot.class== 'active'"
ng-mouseout="hotspot.class== ''"
and after that you can use hotspot.class in ng-class ie:
ng-class="hotspot.class"
Please see demo below:
var app = angular.module('app', []);
app.controller('homeCtrl', function($scope) {
$scope.items = [{
id: 1
}, {
id: 2
}, {
id: 3
}, {
id: 4
}]
});
.red {
background-color: yellow;
}
p {
background-color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<div ng-controller="homeCtrl">
<p ng-repeat="i in items" ng-mouseover="i.class='red'" ng-class="i.class" ng-mouseout="i.class = ''">{{i.id}}</p>
</div>
</div>
Use the below one
<div class="col-md-4 col-xs-12 col-lg-4" ng-repeat="obj in items">
<button type="button" class="btn btn-sm pull-right" ng-class="obj.class" ng-click="obj.class='test'" >
Write a new class "test". Instead of click you can use the same in ngmouseover
I'm building a MEAN SPA and the current page I'm working on displays the users in the database. I'm pretty new to Angular so I'm still trying to wrap my head around it.
I have a parent container of which the content is controlled by an <ng-switch> and switches to show the relevant content depending on whether the user has clicked 'view all' or 'add new'. This works fine.
What I'm aiming to do now is when the user clicks on a user that's displayed in 'view-all', I want the content to switch to a view containing that users details where they can then go and edit the profile etc. What would be the best way to achieve this?
My HTML is set up like so:
Main staff view
<div class="staff" ng-controller="staffController">
<div class="side-menu">
<h2>Staff</h2>
<ul>
<li><a ng-click="tab='view-all'"><i class="fa fa-user"></i> View All</a></li>
<li><a ng-click="tab='add-new'"><i class="fa fa-plus"></i> Add New</a></li>
</ul>
</div>
<div class="page-content" ng-switch on="tab">
<div ng-switch-when="view-all" class="tab-content">
<staff-view-all></staff-view-all>
</div>
<div ng-switch-when="add-new" class="tab-content">
<staff-add-new></staff-add-new>
</div>
</div>
</div>
Directives:
.directive('staffViewAll', function () {
return {
restrict: 'E',
templateUrl: 'partials/staff/view-all.ejs'
}
})
.directive('staffAddNew', function () {
return {
restrict: 'E',
templateUrl: 'partials/staff/add-new.ejs'
}
})
view-all.ejs
<h2>View all staff</h2>
{{ users.length }} users in system
<ul>
<li ng-repeat="user in users"> <!-- click this and you see the singular view -->
<img ng-src="{{user.avatar}}?dim=100x100" />
<h3>{{user.username}}</h3>
<h4>{{user.email}}</h4>
</li>
</ul>
Use another ng-switch to switch to detailed view for the selected user.
Something like this: jsfiddle
<div ng-switch-when="list">
<ul>
<li ng-repeat="fruit in fruits">
{{fruit}}
</li>
</ul>
</div>
<div ng-switch-when="details">
<p>Details for {{ selectedFruit }}</p>
Back to list
</div>
Controller:
$scope.showDetail = function (fruit) {
$scope.selectedFruit = fruit;
$scope.moduleState = 'details';
}
$scope.showList = function()
{
$scope.moduleState = 'list';
};
I must not be doing something right. I have the following:
application.hbs
{{#view App.NavbarView}}{{/view}}
{{outlet}}
with the following template for Navbar
_navbar.hbs
<div class="toolbar">
<div class="row">
<div class="absolute top-left">
<button {{action "back"}} class="btn passive back"><i class="fa fa-play"></i></button>
</div>
{{#if hasTabs}}
<div class="small-centered columns">
<div class="tabs">
<ul>
{{#link-to 'stories' tagName="li" class="tab"}}<i class="fa fa-book"></i> Stories{{/link-to}}
{{#link-to 'mylevels' tagName="li" class="tab"}}<i class="fa fa-user"></i> My Levels{{/link-to}}
{{#link-to 'arcade.index' tagName="li" class="tab"}}<i class="fa fa-gamepad"></i> Arcade{{/link-to}}
</ul>
</div>
</div>
{{else}}
<div class="small-6 small-offset-3 columns">
<h2 class="title">{{ pageTitle App.currentPath }}</h2>
</div>
{{/if}}
{{#if currentUser.userName}}
<div class="absolute top-right">
<span class="user-hello">Welcome Back, <strong>{{ currentUser.userName }}</strong></span>
<button {{action "transitionAccount" currentUser._id}} class="square logged-in"><i class="fa fa-user"></i></button>
</div>
{{ else }}
<div class="absolute top-right">
<button {{action "transitionLogin"}} class="square logged-out"><i class="fa fa-user"></i></button>
</div>
{{/if}}
</div>
</div>
So all it is is a typical fixed navbar and in the middle of it I display what page you are on, if you happen to be on a page that has tabbed content, I show a tab container instead.
So I'm just using this.get('currentPath') in my App controller and comparing it against a group of route names to trigger true/false (I need an observer so it looks at the route change since the Navbar is in inline view at the Application level).
app.js
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath'),
tabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
console.log("ROUTE: ", route);
var tabs = group.indexOf(route) > -1 ? true : false;
return tabs;
}.observes('currentPath'),
// no idea what to do here
hasTabs: function() {
this.tabs();
}.property('tabs')
});
So, basically, no matter what, the tab UI is showing up, but I only want it to show up if that tabs observer is true. With some debugging I'm getting all the console output I would expect but I tried just doing {{#if tabs}} (just using the observer directly) and that always fires true (always shows the tabs UI). I assumed that's because it was an observer and not an actual controller property I could use in my template, so I tried just setting the hasTabs property and referencing the observer, but that doesn't seem to work. I realize I am fundamentally not understanding how this should work. Any thoughts?
If I understand your question correctly you should be able to just change your code to this (renamed tabs to hasTabs, removed previous hasTabs function. Changed from observes currentPath to be property of current path, removed the tabs variable assignment and replaced with the return, reduced the boolean conditional to the simple comparison operator). This is what I'd do, anyway. :) H2H
hasTabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
return group.indexOf(route) > -1;
}.property('currentPath')