Ember navbar UI condition based on currentPath - javascript

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')

Related

How to target an item inside ngFor with introjs

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();
}
}

how to add record to array

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).

Assign value to dynamically created scope variables

I'm trying to create a means to toggle dynamically created rows of information. I've tried using ng-init, and then passing it to a function, but I'm screwing up somewhere and I can't seem to wrap my head around how or if this is possible. The gap, I believe, is in getting the concatenated scope variable to be referenced elsewhere. I'm using Bootstrap 3 and AngularJS 1.5.
The HTML:
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{ equipment.equipId }}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': field{{ $index }}, 'glyphicon-triangle-right': !field{{ $index }}}"
data-ng-init="equipment['field' + $index] = true"
data-ng-click="toggleTaskEquip('field{{ $index }}')">
field{{ $index }}: I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="field{{ $index }}">
...stuff here...
</div>
</div>
The JS:
$scope.toggleTaskEquip = function(toggleBool)
{
if (toggleBool === true)
$scope.isTaskEquipOpen = false;
else if (toggleBool === false)
$scope.isTaskEquipOpen = true;
};
If I understand the problem correctly, you want to be able to toggle the boolean created in the ng-init with a click.
I think you need this:
<div class="container-fluid">
<div ng-controller="MyCtrl">
<div class="row" data-ng-repeat="equipment in task.equipment">
<div class="col-md-12">
<h4 class="green-text">
{{equipment.equipId}}
<small class="green-text">
<i class="glyphicon"
data-ng-class="{'glyphicon-triangle-bottom': isVisible, 'glyphicon-triangle-right': !isVisible}"
data-ng-init="isVisible = true"
data-ng-click="isVisible = !isVisible">I WANT THIS TO WORK</i>
</small>
</h4>
</div>
<div data-ng-show="isVisible">
...stuff here...
</div>
</div>
</div>
</div>
You don't even need the function toggleTaskEquip on the $scope.
JSFiddle here.
ng-repeat creates a new scope for each template instance, so you can just create a separate isVisible for each equipment with isVisible = true in the ng-init.

AngularJS: Set element to class active by default

I've created a custom tabbed element using the following code:
<div class="row step">
<div class="col-md-4 arrow active" ui-sref-active="active">
<a ui-sref="dashboard.create.key_elements" ui-sref-opts="{ reload: true }">
<span class="number">1</span>
<span class="h5">Key Elements</span>
</a>
</div>
<div class="col-md-4 arrow" ui-sref-active="active">
<a ui-sref="dashboard.create.questions" ui-sref-opts="{ reload: true }">
<span class="number">2</span>
<span class="h5">Questions</span>
</a>
</div>
<div class="col-md-4 arrow" ui-sref-active="active">
<a ui-sref="dashboard.create.publish" ui-sref-opts="{ reload: true }">
<span class="number">3</span>
<span class="h5">Publish</span>
</a>
</div>
</div>
As you can see I'm using ui-sref-active="active" to add a class of active to an element when it is clicked. My issue is getting the first element to display with a class of active when the page is first loaded as currently it only happens when an item is clicked. I've tried manually adding active to the first element but this seems to be ignored.
The problem is that the first route dashboard.create.key_elements is not the current route, so ui-router disables it as "active".
Solution:
Add another class in the CSS e.g. "newclassname" to have the same behavior of "active" class
Add ng-class to the first element conditioned to a variable in $scope and ng-click on the other elements so to disable it
In the JS:
$scope.firstActive = true;
$scope.changeFirst = function() {
$scope.firstActive = false;
};
EDIT:
Better yet, instead of dabbling with ng-click, you can simply inject the variable when you define the routes. E.g. from a snippet of my own code
.state('ordini', {
url: '/ordini/:pdv',
templateUrl: 'ordini/ordini.html',
controller: 'OrdiniController',
resolve : {
CartValue: ['$rootScope', '$stateParams', 'CartService', function($rootScope, $stateParams, CartService){
return CartService.getCartValue($rootScope.user.MachCode, $stateParams.pdv);
}]
}
See documentation

Why can't I change this emberjs model without an error?

I'm writing an ember app that has this settings model that I'm using to bypass routing because I want a single url. However, I get this error:
Error: Cannot perform operations on a Metamorph that is not in the DOM.
when I change any of the data in the model. I'm relatively new to ember, but from what I read of what's out there, you should be able to change a model just fine. Here's my html file:
<script type="text/x-handlebars" data-template-name="index">
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand" href="#"><img src="./img/MySpending.png"></a>
<div class="nav-collapse">
<ul id="navWrapper" class="nav">
{{#if model.isIndex}}
{{partial "indexNav"}}
{{/if}}
{{#if model.isMonthly}}
{{partial "monthlyNav"}}
{{/if}}
{{#if model.isYearly}}
{{partial "yearlyNav"}}
{{/if}}
</ul>
</div>
<div class='navbar_form pull-right'></div>
</div>
</div>
</div>
<div id="bodyWrapper" class="container" style="padding-top:60px;">
{{#if model.isIndex}}
{{partial "indexPage"}}
{{/if}}
{{#if model.isMonthly}}
{{partial "monthlyPage"}}
{{/if}}
{{#if model.isYearly}}
{{partial "yearlyPage"}}
{{/if}}
</div>
{{outlet}}
And each partial works fine initially.
It's when I change it in the three regular functions in my app.js that I have a problem. Here's my app.js:
App = Ember.Application.create();
App.Router.map(function() {
// put your routes here
});
App.settings=Ember.Object.extend({
isIndex: true,
isMonthly: false,
isYearly: false
});
Settings=App.settings.create();
//App.SettingsController = Ember.ObjectController.extend(Settings); Not sure whether to put this in or not
App.IndexRoute = Ember.Route.extend({
model: function() {
return Settings;
}
});
function goToMonthly(){
Settings.set('isIndex',false);
Settings.set('isMonthly',true);
Settings.set('isYearly',false);
}
function goToYearly(){
Settings.set('isIndex',false);
Settings.set('isMonthly',false);
Settings.set('isYearly',true);
}
function goToIndex(){
Settings.set('isIndex',true);
Settings.set('isMonthly',false);
Settings.set('isYearly',false);
}
So I'm not sure where the error is but if anyone can help,it'd be extremely appreciated.
There's a fundamental error in your approach here. Your Route/Controller are meant to handle the model for you, so by declaring your model outside of these objects, you're making more work for yourself and leading yourself to potential trouble.
I'm not seeing where your "goTo" functions are being called (I assume they're inside the partials, but if they're in that HTML and I missed it, that's my bad). But here's how they should be referenced:
App.SettingsController = Ember.ObjectController.extend({
actions:{
goToMonthly:function(){
this.set('isIndex', false);
this.set('isMonthly', true);
this.set('isYearly', false);
},
//Other functions go here
}
});
Then within your html, call these actions using handlebars:
<a {{action 'goToMonthly'}}> Click this to run the action </a>
Which ember version are you using? I tried it with ember 1.2 and 1.3 and your example is working fine.

Categories

Resources