AngularJS: Set element to class active by default - javascript

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

Related

Directive to externalize view not working

I'm using pageslide directive in order to display a lateral sliding menu when I click on an icon.
This is the usual way to do it, and its working :
<div class="navbar-header">
<span ng-controller="slideController as s">
<a class="navbar-brand" href="javascript:void(0)"
ng-click="s.toggle()"><i class="fa fa-bars fa-lg"></i></a>
<div pageslide ps-open="s.isActive" ps-side="left">
<div style="padding:20px">
<h2>Hello Pageslide</h2>
<p>Put here whatever I want in the lateral menu</p>
<a ng-click="s.toggle()" class="button" >Close</a>
</div>
</div>
</span>
<a class="navbar-brand text-danger" href="javascript:void(0)">Balrog</a>
</div>
But I would lilke to make the menu content in an external template, so I'm trying this :
<div class="navbar-header">
<span ng-controller="slideController as s">
<a class="navbar-brand" href="javascript:void(0)"
ng-click="s.toggle()"><i class="fa fa-bars fa-lg"></i></a>
<menu-item isActive="s.isActive"></menu-item>
</span>
<a class="navbar-brand text-danger" href="javascript:void(0)">Balrog</a>
</div>
with this as menu.html :
<div pageslide ps-open="s.isActive" ps-side="left">
<div style="padding:20px">
<h2>Hello Pageslide</h2>
<p>Put here whatever I want in the lateral menu</p>
<a ng-click="s.toggle()" class="button" >Close</a>
</div>
</div>
also, here is the related controller :
'use strict';
angular.module('BalrogApp').controller('slideController', function(){
this.isActive= false; // This will be binded using the ps-open attribute
this.toggle = function(){
this.isActive= !this.isActive
}
});
and the <menu-item> directive :
angular.module('BalrogApp').directive('menuItem', function () {
return {
restrict: 'E',
templateUrl: "views/menu.html",
scope: {
isActive: '='
},
controller: 'slideController',
controllerAs: 's',
bindToController: true
}
});
So it's not working this way, maybe it is because the main view (including the <menu-item>) is itself included in the main page thanks to <ng-view> ?
Or maybe the pageslide-directive requires the <div pageslide ...> and its parent element to be on the same file ?
I can tell from the console that toggle() is called and changes isActive in both cases but it's not opening the menu with the directive version.
And also, adding ng-controller="slideController as s" to the root <div> with the pageslide attribute didn't change anything.
So how can I make this work with the menu in another file ?
Did you try to replace <div ng-include="menu.html"></div> with <div ng-include="'menu.html'"></div>. According to https://docs.angularjs.org/api/ng/directive/ngInclude, you have to wrap string constants in single quotes.
I highly recommend you not use ng-include. You can get the same approach using directive instead.
For example:
Menu html
<div pageslide ps-open="vm.checked" ps-side="left">
<div style="padding:20px">
<h2>Hello Pageslide</h2>
<p>Put here whatever I want in the lateral menu</p>
<a ng-click="vm.toggle()" class="button" >Close</a>
</div>
</div>
Directive:
function Controller() {
}
Controller.prototype.toggle = function() {
this.checked = !this.checked;
}
angular
.module('BalrogApp')
.directive('menuItem', function() {
return {
templateUrl: 'urltomenu.html',
restrict: 'E',
scope: {
checked: '='
},
controller: Controller,
controllerAs: 'vm',
bindToController: true
}
})
Main template
<div class="navbar-header">
<span ng-controller="slideController as s">
<a class="navbar-brand" href="javascript:void(0)"
ng-click="s.toggle()"><i class="fa fa-bars fa-lg"></i></a>
<menu-item checked="s.checked"><menu-item>
</span>
<a class="navbar-brand text-danger" href="javascript:void(0)">Balrog</a>
</div>
This approach give you more power. All the logic is inside the directive controller and you could reuse this element each time. The scope attribute checked is the "bridge" between your main template if change outside also change inside and also the other way.

Why can't I access the new DOM element created by Angular?

HTML:
<div class="list-group link-list" ng-show="linksForPerson">
<a href="" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}">
<h4 class="list-group-item-heading">[[ link.engine.name ]]</h4>
<p class="list-group-item-text">[[ link.engine.base_url ]]</p>
<p class="list-group-item-text" ng-show="link.user_sync_id">[[ link.user_sync_id ]]</p>
<p class="list-group-item-text" ng-show="link.group_sync_id">[[ link.group_sync_id ]]</p>
</a>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
Controller:
appModuleLightDashboard.controller('ManageLinksController',
function($scope, $http, $timeout) {
$scope.addLink = function(event) {
$scope.linksForPerson.push({});
// Error: [$rootScope:inprog] http://errors.angularjs.org/1.3.0-rc.1/$rootScope/inprog?p0=%24apply
$('.link-list .list-group-item').eq(-2).trigger('click');
// But this works ---- why?
// $timeout( function(){$('.link-list .list-group-item').eq(-2).trigger('click')} , 0);
}
});
I have changed the interpolate symbol to [[]] as it conflicts with Django
The problem:
A new list item will be created when the user clicks on the "Add a new link". I wanted to select this new list item automatically.
But it looks like I couldn't select that new DOM element created by Angular ( i.e. $('.link-list .list-group-item') doesn't return the new one ), unless I wrap the code with $timeout. Anyone knows why?
Also, please advise if there is a more Angular way to achieve it:)
Your question is "why". The answer is because at the moment you are trying to use jQuery to find the element, it hasn't yet been added to the DOM. That doesn't happen until the digest cycle runs.
$timeout works because the function call is now deferred until after the next digest cycle. The problem with that solution is that there are cases where the DOM still won't yet have been modified.
Looking in more detail, this will have several failure modes. The error you are showing is sent because you are actually triggering a click in the second to last element already added, and you are doing it from inside of a digest cycle. If you already have two or more items added to the collection, this triggers angular's ng-click on the second to last one (which happens to not be the one you think), which assumes it is called outside of a digest cycle and calls $apply, which fails with the error you see because it's actually inside of a digest cycle.
The "angular way" to achieve what you want is to use a directive.
.directive('triggerClick', function($parse) {
return {
restrict: 'A',
link: function(scope, elem, attr) {
var fn = $parse(attr['triggerClick']);
if(scope.$last) { //or some other logic
fn(scope);
}
}
}
})
div class="list-group link-list" ng-show="linksForPerson">
<a href="" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}" trigger-click="showLinkDetail(link)">
<h4 class="list-group-item-heading">[[ link.engine.name ]]</h4>
<p class="list-group-item-text">[[ link.engine.base_url ]]</p>
<p class="list-group-item-text" ng-show="link.user_sync_id">[[ link.user_sync_id ]]</p>
<p class="list-group-item-text" ng-show="link.group_sync_id">[[ link.group_sync_id ]]</p>
</a>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
This works because the link function of the directive will be called after the node has been constructed and added to the DOM. Note the addition of "trigger-click" to your ng-repeat element.
elem in the directive is a jQuery object wrapped around the instance of the ng-repeat item. Angular will call the link function for every instance of the directive, which in this case is every instance of the ng-repeat.
Even more "angular" would be to not use a click event at all. You don't include the implementation of showLinkDetail, but rather than trigger a click, just call it in your controller.
As a general "angular" rule, anything that looks like jQuery should only happen in a directive.
EDIT: With more info on what you need, you can do this without need to do any DOM manipulation at all (no directives).
appModuleLightDashboard.controller('ManageLinksController',
function($scope, $http, $timeout) {
$scope.activeLink = undefined;
$scope.addLink = function(event) {
$scope.activeLink = {};
$scope.linksForPerson.push($scope.activeLink);
}
$scope.showLinkDetail = function(link){
$scope.activeLink = link
}
$scope.isSelectedLink = function(link){
return $scope.activeLink === link;
}
});
<div class="list-group link-list" ng-show="linksForPerson">
<a href="" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}">
<h4 class="list-group-item-heading">[[ link.engine.name ]]</h4>
<p class="list-group-item-text">[[ link.engine.base_url ]]</p>
<p class="list-group-item-text" ng-show="link.user_sync_id">[[ link.user_sync_id ]]</p>
<p class="list-group-item-text" ng-show="link.group_sync_id">[[ link.group_sync_id ]]</p>
</a>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
you should not put your "add new link" inside the div with ngShow because when the linksForPerson array is empty, you will not be able to add a new link . Also, putting it outside the div will ease up every other manipulation (based on what you want to achieve"
linksForPerson is an array, use ng-show="linksForPerson.length" instead
you should initialize your arrays before pushing anything into it $scope.linksForPerson=[]
use of ng-bind is a better alternative to {{}} or [[]]
I refactored your code.
// ---- controller
appModuleLightDashboard.controller('ManageLinksController',
function($scope, $http, $timeout) {
var activeLink;
// you should initiate your array
$scope.linksForPerson = [];
$scope.isSelectedLink = function (link) {
return activeLink === link;
};
$scope.addLink = function(event) {
activeLink = {
engine: {
name : "engine" + ($scope.linksForPerson.length + 1),
base_url : " someUrl"
}
};
$scope.linksForPerson.push(activeLink);
};
});
and html (note use of ng-bind)
<div ng-controller="ManageLinksController">
<div class="list-group link-list" ng-show="linksForPerson.length">
<a href="#" class="list-group-item" ng-repeat="link in linksForPerson" ng-click="showLinkDetail(link)" ng-class="{active: isSelectedLink(link)}">
<h4 class="list-group-item-heading" ng-bind="link.engine.name"></h4>
<p class="list-group-item-text" ng-bind="link.engine.base_url"></p>
<p class="list-group-item-text" ng-show="link.user_sync_id" ng-bind="link.user_sync_id"></p>
<p class="list-group-item-text" ng-show="link.group_sync_id" ng-bind="link.group_sync_id"></p>
</a>
</div>
<span class="glyphicon glyphicon-plus"></span> Add a new link
</div>
here's jsfiddle for you to play with

Angular UI Typeahead - Prevent dropdown close on select

I wanted to be able to show the whole list after a selection. The way I wanted to do that is by placing the selection in the placeholder and clearing the input's model.
On the typeahead-on-select event, I save the value that was selected and I set the model to be "". I expected the dropdown to appear just like if the input is empty, but it doesn't.
<input type="text" ng-model="myModel" data-min-length="0"
typeahead="item for item in items | filter:$viewValue"
placeholder="{{currentModel}}"
ng-blur="validateSelection()"
typeahead-on-select="onSelect($item, $model, $label)">
When I clear the input's model, typeahead doesn't detect the change in the model. If I then type 1 character and erase it, I get the whole list.
Angular v1.2.9
Angular Bootstrap v0.10.0
Any help would be appreciated, even a different approach.
EDIT:
How do I either prevent the dropdown closing after select or make typeahead detect the change in my model?
I do something similar. I add a button to the right of the typeahead so it looks like a dropdown menu and clicking the button makes the typeahead choices show up. You could do the equivalent of the button click I use to make it happen. You'll have to modify the code below to your needs. This comes from a directive I made
var which = 'idOfTypehead'; // This is actually a variable, I just set it here
// it is the id="XXX" from my typeahead
$("#"+which).focus();
var e = jQuery.Event('keydown', {which: 40 });
$timeout(function() {
$("#"+which).trigger(e);
},0);
} ;
I could solve this just adding a ng-click function stoping the propagation to the modal content. In this way ng-click="dropdownMenuClick($event)".
<header class="top-header clearfix" data-ng-controller="headerController">
<!--modal search panel-->
<li class="dropdown top-bar-item search-panel" ng-show="searchCallback">
<a href="javascript:;" class="dropdown-toggle" toggle="dropdown">
<i class="glyphicon glyphicon-search"></i>
<span>Search modal panel</span>
</a>
<div class="dropdown-menu with-arrow panel panel-dark" style="width: 330px;">
<div class="panel-heading">
<i class="glyphicon glyphicon-search"></i> <span>{{currentSearchTitle}}</span>
</div>
<div ng-click="dropdownMenuClick($event)">
<div class="panel-body">
<div class="row">
<input type="text"
placeholder="Type your word"
ng-model="result"
typeahead="item as item.Name for item in buildings($viewValue)"
typeahead-on-select='onSelect($item, $model, $label)'
class="form-control">
</div>
</div>
</div>
<div class="panel-footer text-right">
<a href="javascript:;" class="" toggle="dropdown" ng-click="searchCallback(seachFilter)">
<i class="glyphicon glyphicon-search"></i>
<span>Search</span>
</a>
</div>
</div>
</li>
<!--modal search panel-->
</header>
In the controller:
(function () {
'use strict';
angular.module('app')
.controller('headerController', [
'$scope', '$http', '$routeParams', 'logger', '$modal', 'appConfig',
function ($scope, $http, $routeParams, logger, $modal, appConfig) {
$scope.dropdownMenuClick = function ($event) {
$event.preventDefault()
};
}
]);
}).call(this);

Ember navbar UI condition based on currentPath

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

AngularJS: Load dialog for ng-repeat with large number of items

I am trying to come up with a way to display a load dialog while the items in ng-repeat are being rendered. The use case is as follows:
User clicks a toggle switch to display a list of items
A directive is shown that contains an ng-repeat of items
The list of items show up almost instantly on a PC. But on a mobile device it takes a few seconds for the list to show up. This can cause the user to tap the toggle switch multiple times resulting in the list being hidden and shown.
Here's my HTML mark-up:
<div jqm-theme="b">
<div style="" jqm-theme="a" jqm-textinput ng-model="projectFilter"
placeholder="Filter Project Areas...">
</div>
<div style="height:12px;"></div>
<ul jqm-listview style="padding-top: 12px;">
<project-display title="My Project Areas" filter="projectFilter" projects="myProjects"
select-project="selectProject" show="true">
</project-display>
<li jqm-li-entry jqm-theme="b" class="ui-icon-alt">
<div class="ui-grid-a">
<div class="ui-block-a">
<a class="projTitle">Toggle All Projects</a>
</div>
<div class="ui-block-b" style="text-align:right;">
<div jqm-flip mini="true" jqm-theme="d" ng-model="allSwitch.value"
on-label="On" on-value="1" off-label="Off" off-value="0"
default-value="0" ng-click="toggleAllProj()">
</div>
</div>
</div>
</li>
<project-display title="All Project Areas" filter="projectFilter" projects="projects"
select-project="selectProject" show="allSwitch.value">
</project-display>
</ul>
</div>
Here's the directive:
angular.module('myApp').directive('projectDisplay', ['$location', function($location) {
return {
restrict: 'E',
scope: {
title: '#',
filter: '=',
projects: '=',
selectProject: '=',
show: '='
},
templateUrl: 'template/directives/project-display.html'
};
}]);
Here's the directive's template:
<div ng-show="show">
<span style="margin-left:13px" jqm-li-divider>{{title}} ({{projects.length}})</span>
<ul jqm-listview style="padding-top: 12px;">
<li jqm-li-link jqm-theme="a" style="margin-left:13px" class="ui-icon-alt" ng-click="selectProject(project)"
ng-repeat="project in projects | filter: filter">
<a>{{project.title}}</a>
</li>
</ul>
</div>
I am using Angular JQM's $loadDialog which can be shown and hidden by explicitly calling the separate methods or it can also display until a promise is resolved.
Is there a way to monitor when all items within an ng-repeat have been rendered? I appreciate any help on this matter.
Thanks

Categories

Resources