Angular way to toggle a data-targeted submenu on click? - javascript

I'm trying to use an ng-click function to get the data-target attribute of a clicked target angular material md-button, so that when a topic is clicked in the sidenav, the submenu for it appears.
I've structured the navigation using md-list with md-items as I've seen suggested, but can't figure out a way to get this behaviour, especially with the .sidenav-submenu elements not being direct children of the md-button that's clicked on.
Not sure this would even be the best approach for achieving something like this, but I'm limited by angular material's lack of toggled sidenav submenu directive.
HTML:
<md-item>
<md-button id="sidenav-paymentsButton" data-ng-click="toggleSubmenu($event)" data-target="#paymentsSubmenu">
<md-content class="sidenav-link" md-ink-ripple="#3a455f" layout="row" layout-align="space-between center">
<div layout="row" layout-align="start center"><span class="sidenav-link-icon material-icons">money_off</span>
Payments
</div>
<div><span class="expandSubmenu material-icons">keyboard_arrow_down</span></div>
</md-content>
</md-button>
<md-list id="paymentsSubmenu" class="sidenav-submenu">
<md-item>
<md-button ui-sref="newPayment">
<md-content class="sidenav-link" md-ink-ripple="#3a455f" layout="row" layout-align="start center">
<span class="sidenav-link-icon material-icons">create</span>
New Payment
</md-content>
</md-button>
</md-item>
navController JS:
app.controller('navController', function($scope, $state, $document) {
angular.element('.sidenav-submenu').hide();
$scope.toggleSubmenu = function($event) {
var target = $event.target;
// Not sure what to do next
}
})

You shouldn't be doing DOM manipulation in your controller. You use directives for DOM manipulation. That being said, what you want doesn't require any sort of custom directive that is not already built into angularjs.
Consider the following unstyled markup:
<div data-ng-click="nav.selected = 'A'">
Your toggle for the "A" side nav.
</div>
<div data-ng-click="nav.selected= 'B'">
Your toggle for the "B" side nav.
</div>
<div ng-if="nav.selected === 'A'>
The "A" side nav.
</div>
<div ng-if="nav.selected === 'B'>
The "B" side nav.
</div>
Used in conjunction with the following controller:
app.controller('navController', function($scope) {
$scope.nav = {
selected: 'A'
};
});
You technically don't even need the controller for this example if you change the markup slightly, but it will be helpful in getting around the "dot problem" in case it pops up when moving this to your own example.

Related

Displaying one directive with multiple view options in angular

I have two modules wrapped inside of directives called 'bci-directive' and 'bcd-directive' that I use to display data inside of a primary module called 'core'. I am trying to give users a way to view these two modules in two different ways, one where they can view both at the same time, and one where they can see each separated by tabs -- the issue I am running into is that each of these directives has isolated scope so each pair of tags generates a new view which results in incorrect behavior in updating controller variables within the two sub-modules when users make edits. What I would like is to be able to a single view for each of these modules that can switch between the two view options(tab/all) while maintaining the same scope.
So far I've tried moving these views out of directives and into ng-includes with the same results, I wasn't able to find much more for suggestions while researching. Here is what I currently have for the front end:
<div ng-cloak="">
<md-toolbar class="md-primary">
<div class="md-toolbar-tools">
<!-- span tag to align buttons to right -->
<span flex=""></span>
<md-button ng-click="core.initiateSave()" class="md-raised">SAVE ALL</md-button>
<md-checkbox ng-model="core.tabView" aria-label="tabViewSwitch" class="md-warn">
Tab View
</md-checkbox>
</div>
</md-toolbar>
<div ng-show="core.tabView">
<md-content>
<md-tabs md-dynamic-height="" md-border-bottom="" class="md-primary md-hue-2">
<md-tab label="Details">
<bcd-directive></bcd-directive>
</md-tab>
<md-tab label="Items">
<bci-directive></bci-directive>
</md-tab>
</md-tabs>
</md-content>
</div>
<!-- View All: These are new views with new controllers that use same factory as above directives -->
<div ng-show="!core.tabView">
<bcd-directive></bcd-directive>
<bci-directive></bci-directive>
</div>
<div style="position:fixed;top:50%;left:50%">
<md-progress-circular md-mode="indeterminate" class="md-warn loader" md-diameter="60" ng-hide="!loading" ng-disabled="!loading"></md-progress-circular>
</div>
And the directives:
(function(){
'use strict';
angular
.module('app.bci')
.directive('bciDirective', bciDirective);
/* #ngInject */
function bciDirective(){
var directive = {
templateUrl: '../app/sf/bci/templates/bci.html',
controller: 'bciController',
controllerAs: 'bci',
scope: {},
bindToController: true,
restrict: 'E'
};
return directive;
}
})();
Is it possible to switch between these view options without having to instantiate the directives twice?
You can set the template property of your directive definition object to a function that will return your dynamic template:
restrict: "E",
replace: true,
template: function(tElement, attributes) {
return getTemplate(attributes.template);
}
And then assign that from the parent based on your condition where you can access the attributes through attributes.
Now your template is being determined before the compile phase, and you don't need to manually compile it.

Angular scope not seen by children divs

This is part of my controller:
mbpMod.controller("bookController", function($scope,api) {
...
$scope.bookTable=new BookTable();
$scope.bookLabel="Book";
...
}
in my HTML page it works until it is like this:
<md-tab>
<md-tab-label class="mbpPanel">Book</md-tab-label>
<md-tab-body>
<div flex ag-grid="bookTable.table" class="ag-mbp lowtable" ng-controller="bookController"></div>
</md-tab-body>
</md-tab>
But it doesn't work when I do this:
<md-tab>
<div ng-controller="bookController">
<md-tab-label class="mbpPanel">{{bookLabel}}</md-tab-label>
<md-tab-body>
<div flex ag-grid="bookTable.table" class="ag-mbp lowtable" ></div>
</md-tab-body>
</div>
</md-tab>
I cannot put ng-controller directive in md-tab tag, as it will result in a double directive.
Any ideas?
The md-tab-directive is used to specify information about a tab and how the tab should look like.
When the directive is being compiled, the controller of the md-tabdirective is executed. If you have a look at the source code on Github, you will see that the controller checks the direct children of the md-tab directive to retrieve the md-tab-label and md-tab-body contents.
The working example you provided us has md-tab-label and md-tab-body as a child of the md-tab. In the broken example, only the div is a child of md-tab, so the controller is not able to find a label and the body for the tab.
If you move the ng-controller declaration outside of your md-tab definition, it should work.

"ng-click" not working out of "ion-content" in Ionic framework

I have an input box with the ng-model attribute in my ionic based application. The code inside the ion-content tag:
<ion-content class="padding">
<input ng-model="myNumber" type="number" placeholder="Enter number">
</ion-content>
And in the footer-bar I have this:
<div class="bar bar-footer bar-balanced" style="background-color: #00368C !important; color: #fff;">
<div class="title">
<button ng-click="submit(myNumber)" class="button button-clear">Submit</button>
</div>
</div>
The alert result is undefined.
NOTE: when I put the button inside the ion-content it works fine. (It means js codes works fine)
Any idea?
The reason behind your problem is, ion-content directive does create a child scope which is prototypically inherited from the parent scope. So by placing myNumber in input ng-model does get added inside the scope of ion-content, which is different that the controller myNumber number scope variable.
To make it working you need to follow dot rule while defining ng-model, so that prototypal inheritance rule will get follow(it works on reference type variable).
So create an new object inside a controller and do assign all ng-model variable into it. like below
$scope.model = {};
And then put all the properties you wanted to use on view like $scope.model.myNumber & while using it on view use it like model.myNumber
Markup
<ion-content class="padding">
<input ng-model="model.myNumber" type="number" placeholder="Enter number">
</ion-content>
<div class="bar bar-footer bar-balanced" style="background-color: #00368C !important; color: #fff;">
<div class="title">
<button ng-click="submit(model.myNumber)" class="button button-clear">Submit</button>
</div>
</div>
Detailed explanation can be found in this answer
More elegant way of doing it would be using controllerAs approach.
A better and simpler solution, use <ion-header-bar> or <ion-footer> and place all your pinned content in there - and then keep all your code in the same controller.
Referenced from: https://stackoverflow.com/a/51785866/2036221

How can I isolate/encapsulate $index in Angular using 2 ng-repeat directives on the same page?

I have a website built with Angular 1.4 with 2 slideshows. One in the header and one for a partner logo slider. They are in the same view when all it said and done.
Header
<div layout="row">
<div flex="100" flex-gt-sm="70" hide-xs class="header-slider center">
<img
ng-repeat="i in slides"
class="header-slide-animation"
src="{{i.img}}"
ng-hide="!isCurrentSlideIndex($index)"
ng-class="{'active':isCurrentSlideIndex($index)}"/>
</div>
And then here's the logo slider
<div class="partners-slider center">
<a ng-repeat="i in slides"
class="slide-animation"
href="{{i.href}}"
target="_blank"
ng-hide="!isCurrentSlideIndex($index)"
ng-class="{'active':isCurrentSlideIndex($index)}">
<img src="{{i.img}}" alt="{{i.title}}" />
</a>
I'm following this tutorial here http://onehungrymind.com/build-sweet-photo-slider-angularjs-animate/ and it uses TweenMax animate the sides.
Everything was working great with one slide show on the page but I have each split into separate directives being included on the same page and the problem is that $index is conflicting. The partner logo animation moves faster than the header slide show and also has a larger array so you can imagine the problem when the header's $index gets overwritten by the other and is out of range, etc... What can I do to isolate $index so the two slideshows don't step on each other?
It's not $index that is the problem, it is the shared scope whereby each directive is sharing the parent controller scope.
$index is actually isolated as it only exists in each child scope of ng-repeat
So whatever isCurrentSlideIndex($index) is doing in controller will be shared by both instances
The simple solution is to use an isolated scope in directive so both are separate instances.
Just move the methods used in controller to directive. You can then use one attribute in directive to receive the images in the isolated scope from the parent controller
Alright I found an article that helped me realize a solution to the problem. http://codeutopia.net/blog/2014/11/10/angularjs-best-practices-avoid-using-ng-repeats-index/
Overall the moral of the story is not to use $index because it can be problematic. I chose to modify the view to pass the image object and check for that against the current index.
HTML
<div layout="row">
<div flex="100" flex-gt-sm="70" hide-xs class="header-slider center">
<img
ng-repeat="j in headerSlides"
class="header-slide-animation"
src="{{j.img}}"
ng-hide="!isCurrentHeaderSlide(j)"
ng-class="{'active':isCurrentHeaderSlide(j)}"/>
</div>
This is what the JavaScript was
$scope.isCurrentSlideIndex = function (image) {
return $scope.currentIndex === index;
};
This is what I changed it to
$scope.isCurrentHeaderSlide = function (slide) {
return $scope.headerSlides[$scope.currentIndex] == slide;
};
And it works :-)

Not able to fetch scope value in back button method in Ionic

On same template ionic is unable to get the value in ion-nav-bar directive.
Problem - Whatever the amount I filled in textbox <input type="text" placeholder="70" ng-model="getamt"> in below mentioned code, I am able to get the same value at Getting Amt here: {{getamt}} ie.if I type 56 I am able to get 56 typed like Getting Amt here: 56 BUT I am expecting the same to print at Not updating Amt {{getamt}} but in this case I am not getting any value.
I need to send this value to my previous page..so I am trying to do this stuff.
Let me know how to fix this weird issue with ionic.
<ion-view>
<ion-nav-bar class="bar-stable">
<ion-nav-back-button class="button-clear" ng-click="myGoBack(getamt)">
<i class="icon ion-ios7-arrow-back"></i> Not updating Amt {{getamt}}
</ion-nav-back-button>
</ion-nav-bar>
<ion-content class="has-subheader" padding-bottom="true">
<div class="row">
<div class="col col-30">
<input type="text" placeholder="70" ng-model="getamt">
</div>
<div class="col col-30 item">
Getting Amt here: {{getamt}}
</div>
</div>
</ion-content>
</ion-view>
EDIT -
My controller code -
.controller('mystate2Ctrl', function($scope, $stateParams, $localstorage, $rootScope, $ionicHistory) {
console.log("----- mystate2Ctrl -----");
$scope.myGoBack = function(val){
console.log(val);
$ionicHistory.goBack();
};
$rootScope.$on( "$ionicView.leave", function( scopes, states ) {
if( states.stateName == "app.mystate1" ) {
console.log("In Ionic View blah blah code here");
}
});
The problem you're facing has to do with nested scopes and in particular with the way you use your "model".
The scope is not the model.
I would suggest you to watch this video from Miško Hevery where he talks about the problem you're having. At some point he says something like this:
Whenever you have ng-model there’s gotta be a dot in there somewhere.
If you don’t have a dot, you’re doing it wrong.
Anyway, if you want to fix your problem you should define a model in your controller.
The solution is to create one:
.controller('mystate2Ctrl', function($rootScope, $scope, $state) {
$scope.mymodel = { getamt: 0 };
});
Now you can reference your model in your views using the dot and your view should look something like this:
<ion-view view-title="my-app">
<ion-nav-buttons side="right" class="button-clear" ng-click="myGoBack(mymodel.getamt)">
<i class="icon ion-ios7-arrow-back"></i> Not updating Amt {{mymodel.getamt}}
</ion-nav-buttons>
<ion-content class="has-subheader" padding-bottom="true">
<div class="row">
<div class="col col-30">
<input type="text" placeholder="70" ng-model="mymodel.getamt">
</div>
<div class="col col-30 item">
Getting Amt here: {{mymodel.getamt}}
</div>
</div>
</ion-content>
</ion-view>
If you want to see how it works you can check my plunker.
Probably the best approach is the one John Papa suggests in his Angular guidelines.
If you read carefully the controllerAs View Syntax here he states why you should go for this approach:
Why?: It promotes the use of binding to a "dotted" object in the View
(e.g. customer.name instead of name), which is more contextual, easier
to read, and avoids any reference issues that may occur without
"dotting".
Why?: Helps avoid using $parent calls in Views with nested
controllers.
This is a second plunker with the controllerAs sample.
It seems like because, your Getting Amt here: {{getamt}} and Not updating Amt {{getamt}} in two different directives. So make sure they both can access mystate2Ctrl.
And try to define the variable in the controller itself.
.controller('mystate2Ctrl', function($scope, $stateParams, $localstorage, $rootScope, $ionicHistory) {
$scope.getamt = 0;
// your code
})
HTH

Categories

Resources