I'm working with angular js and bootstrap 3 and my app works like ... I have a view where you have several links which let you show a div with several tabs and select one of them. This works fine. But if I change the tab through a click over it and then I hide the view with the tabs when I make click on another click I show the view with the tabs, with the tab selected from the link, that's correct, but ... with the previous tab clicked.
So, how I can deselect the tab where I have been make click over it?
Edit 1:
I'm going to post several screenshots to try to explain better my problem.
Edit 2:
I add this plunker to show how it works my code and you can check that if you clic on a tab, if later returns clicking a button you don't select the correct tab. https://plnkr.co/edit/y22T01OwxgttDWM1mJeH
HTML:
<body ng-controller="MainCtrl as ctrl">
<button id="bTab1" ng-click="ctrl.buttonClicked($event)">
Tab 1
</button>
<button id="bTab2" ng-click="ctrl.buttonClicked($event)">
Tab 2
</button>
<button id="bTab3" ng-click="ctrl.buttonClicked($event)">
Tab 3
</button>
<div ng-show = "ctrl.show_tabs">
<div class = "row" style = "text-align: right; margin-top: 10px">
<button ng-click="ctrl.closeTab()">
Hide Tabs
</button>
</div>
<ul class="nav nav-tabs" id="myTab">
<li ng-class = "ctrl.active_pai"><a data-target="#pai" data-toggle="tab">PAI</a></li>
<li ng-class = "ctrl.active_pap"><a data-target="#pap" data-toggle="tab">PAP</a></li>
<li ng-class = "ctrl.active_ip"><a data-target="#ip" data-toggle="tab">IP</a></li>
</ul>
<div class="tab-content">
<div class="tab-pane" ng-class = "ctrl.active_pai" id="pai">Content PAI</div>
<div class="tab-pane" ng-class = "ctrl.active_pap" id="pap">Content PAP</div>
<div class="tab-pane" ng-class = "ctrl.active_ip" id="ip">Content IP</div>
</div>
</div>
</body>
Javascript:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
var self = this;
$scope.name = 'World';
self.show_tabs = false;
self.active_pai = "";
self.active_pap = "";
self.active_ip = "";
self.buttonClicked = function(event) {
self.show_tabs = true;
if (event.currentTarget.id == "bTab1"){
self.active_pai = "active";
self.active_pap = "";
self.active_ip = "";
}
if (event.currentTarget.id == "bTab2"){
self.active_pai = "";
self.active_pap = "active";
self.active_ip = "";
}
if (event.currentTarget.id == "bTab3"){
self.active_pai = "";
self.active_pap = "";
self.active_ip = "active";
}
};
self.closeTab = function(){
self.show_tabs = false;
}
});
Edit 3:
More problems:
In my code, I've got tabs and Bootstrap calendar and with the given solution works fine without bootstrap calendar, but If add bootstrap calendar, this doesn't work correctly.
I have modified my origina plunker and I have added a bootstrap calendar and change these libraries:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.2/ui-bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.2/ui-bootstrap-tpls.min.js"></script>
By these:
<script type="text/javascript" src="bootstrap.min.js"></script>
<script type="text/javascript" src="ui-bootstrap-tpls-0.14.3.min.js"></script>
The code of these libraries you've got on the plunker. Plus I have added the controller which manage the bootstrap calendar.
Ok, If we go to the plunker: https://plnkr.co/edit/PaSqa0jxQjz48pzcmBMa
We can see that we have a bootstrap calendar where I cannot select day greater than today + 1. That's correct! But, If I make a click on button "Tab 2", the Tab that we can see is not 2, it's 1. If I do the same with tab 3, I've got the same result. That's wrong. The correct functionality is If I make a click on button "Tab 2", we can see tab 2, for example.
Ok, If I change on the plunker these libraries ...
<script type="text/javascript" src="bootstrap.min.js"></script>
<script type="text/javascript" src="ui-bootstrap-tpls-0.14.3.min.js"></script>
By the given in the solution:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.2/ui-bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.2/ui-bootstrap-tpls.min.js"></script>
We can see that the tabs works correctly, but bootstrap calendar lets you to select days greater than today + 1. And this is wrong!
I would recommend using angular-ui-bootstrap tabs for this. It provides angular wrappers around most of the bootstrap functionality (mostly directives), so it makes these types of things much easier to write (and code is cleaner, as you'll see below). I modified your plunkr as minimally as possible, but changed it to make use of the ui-bootstrap tabs: https://plnkr.co/edit/qqvY2acsZWbkyFCCT7qr?p=info
New Controller:
app.controller('MainCtrl', function($scope) {
var self = this;
$scope.name = 'World';
self.buttonClicked = function(index) {
self.show_tabs = true;
self.active = index;
};
self.closeTab = function(){
self.show_tabs = false;
}
});
Html changes:
<button id="bTab1" ng-click="ctrl.buttonClicked(1)">
Tab 1
</button>
<button id="bTab2" ng-click="ctrl.buttonClicked(2)">
Tab 2
</button>
<button id="bTab3" ng-click="ctrl.buttonClicked(3)">
Tab 3
</button>
...
<div ng-show = "ctrl.show_tabs">
...
<uib-tabset active="ctrl.active">
<uib-tab index="1" heading="PAI">Content PAI</uib-tab>
<uib-tab index="2" heading="PAP">Content PAP</uib-tab>
<uib-tab index="3" heading="IP">Content IP</uib-tab>
</uib-tabset>
</div>
ctrl.active, which is passed into the active attribute on <uib-tabset> just represents the index of the currently opened tab, so just changing its value will change which tab is open/visible. There are some more attributes that can be used for these directives (you can see them on the page I linked to above), but this shows the basis of how these tab directives can be used. I haven't seen the issue you were describing above after these changes.
Related
I'm building an Single Page App where the paypal button is generated on ng-click from a button (Add products).
The problem I'm facing, is that if the user clicks this button several times, the app will generate several buttons one after the other.
This can very well happen as the user might click the button, but then go back and add an extra product, before finish the purchase.
How could I manage to remove all existing buttons before adding the new one?
The function looks like this:
$scope.formulari = function(){
paypal.Button.render({
env: 'production', // Or 'sandbox'
locale: 'es_ES',
style: {
label: 'paypal',
...
And after a few clicks, my initial HTML button <a id="paypal-button"></a> looks like this:
<a id="paypal-button">
<div id="xcomponent-paypal-button-6d3dcbc0c4" class="paypal-button paypal-button-context-iframe paypal-button-label-paypal paypal-button-size-large paypal-button-layout-horizontal" style=""></div>
<div id="xcomponent-paypal-button-46823018c3" class="paypal-button paypal-button-context-iframe paypal-button-label-paypal paypal-button-size-large paypal-button-layout-horizontal" style=""></div>
<div id="xcomponent-paypal-button-41aad29e14" class="paypal-button paypal-button-context-iframe paypal-button-label-paypal paypal-button-size-large paypal-button-layout-horizontal" style=""></div>
<div id="xcomponent-paypal-button-48d3247535" class="paypal-button paypal-button-context-iframe paypal-button-label-paypal paypal-button-size-large paypal-button-layout-horizontal" style=""></div>
</a>
Generating a button on click might not be the way you want to go with an AngularJs structure. Editing your DOM structure is more a jQuery thing and in general you don't want to mix the two (Some explanations of why: 1, 2).
An Angular way to pick this up would be the following (Explanation beneath snippet):
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope) {
$scope.articles = ['PC', 'Playstation', 'Xbox'];
$scope.cart = [];
$scope.addArticleToCart = function(article) {
$scope.cart.push(article);
}
$scope.clearCart = function() {
$scope.cart = [];
}
$scope.doPaypalThings = function() {
//REST API stuff
}
});
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.4/angular.min.js"></script>
</head>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<div ng-repeat="article in articles">
<button ng-click="addArticleToCart(article)">
{{article}}
</button>
</div>
<br>
<div ng-show="cart.length > 0">
<button id="paypal-button" ng-click="doPaypalThings()">
Paypal
</button>
</div>
<br>
<div>
In cart:
</div>
<div ng-repeat="item in cart">
{{item}}
</div>
<br>
<div>
<button ng-click="clearCart()">
Clear cart
</button>
</div>
</div>
</body>
</html>
With this method the button always exists, it just isn't visible until the requirements within the ng-show are met. In the example, the requirement is that there are items in the cart. You will notice that once the requirements are no longer met the button will disappear again.
An opposite of ng-show is ng-hide which can be used in the same way:
ng-hide="cart.length == 0" or ng-hide="cart.length < 1
If you're dead set on using your current method, you can check out this answer here, although it is not Angular.
May I ask if it's possible to trigger the click event of a dropdown menu programmatically on page load using AngularJS?
What I want to happen is that after loading the page, my navigation menu gets displayed automatically.
This is what I have so far:
<li class="menu-item" style="margin-top:15px">
<!-- Single button -->
<div class="btn-group open" uib-dropdown is-open="status.isopen">
<!-- Hamburger menu -->
<img ng-init="displayMainMenu()" id="nav-burger" uib-dropdown-toggle ng-disabled="disabled" ng-click="sMainMenu=true; isSubMenu=resetMenu(); getLinks(); bStopPropagation=true;" src="img/burger.png">
<!-- Main menu -->
<ul uib-dropdown-menu role="menu" aria-labelledby="single-button" ng-click="bStopPropagation && $event.stopPropagation()">
<!-- Main Menu -->
<li role="menuitem" class="main-menu-item" ng-repeat="link in links" ng-click="whatMenu(link.name); isSubMenu=false;" ng-show="isMainMenu">
<img id="{{link.icon}}">{{link.name}}<img class="navi-expand-icon">
</li>
<!-- End Main Menu -->
</ul>
</div>
</li>
And this is my Angular JS code:
$scope.displayMainMenu = function () {
var domElement = document.getElementById('nav-burger');
alert('before timeout'); // <-- This gets triggered
$timeout(function () {
angular.element(domElement).triggerHandler('click');
}, 0);
alert('after timeout'); // <-- This doesn't get triggered...
}
I have a feeling that I'm really close, however I couldn't figure out why it's not working.
Thank you in advance for your replies.
The ng-click on your image triggers this: ng-click="sMainMenu=true;"
However, to show your list items you have used isMainMenu ng-show="isMainMenu"
So I guess you made a typo.
Thank you for the clue Matheno! I did some more research about $timeout and finally got it to work by declaring $timeout in the controller:
app.controller('ctrlDropdown', function ($scope, $timeout) {
$scope.isMainMenu = true;
$scope.isSubMenu = false;
$scope.links = "";
$scope.subLinks = "";
$scope.selectedLink = "";
$scope.bStopPropagation = true;
...
}
My goal is to move the modal out of my source view, and move it into its own view, but for some reason, my modal does not show up. I have tried putting the modal into a directive, but its not working. I have moved the modal to the index page, but then the view changes when they modal opens.
Category.html
<section class="row">
<h1>{{ selectedCategory | uppercase}}</h1>
<div class="col-md-3 col-xs-12" ng-repeat="source in sources[selectedCategory]">
<a ng-href="#/explore/{{selectedCategory}}/{{source.name}}">
<section class="col-xs-2">
<img ng-src="assets/img/{{source.imagename}}" height="30" width="30">
</section>
<p class="col-xs-8">{{ source.name }}</p>
</a>
<div ng-if="!objectContains(addedSources,source.name)"><!-- Show this, if addesSources does not contains source.name -->
<section class="col-xs-2">
<!-- This part, is where i want the modal to be called. -->
<button class="tiny" data-toggle="modal" data-target="#myModal" ng-click="setUpModalData(source.name)">Add</button>
</section>
</div>
<div ng-if="objectContains(addedSources,source.name)"> <!-- Show this, if addesSources contains source.name -->
<section class="col-xs-2">
<button class="tiny secondary" ng-click="removeSource(source.name)">remove</button>
</section>
</div>
</div>
</section>
<div class="modal fade" id="myModal" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<!-- Modal content -->
</div><!-- modal-content -->
</div><!-- modal-dialog -->
</div><!-- myModal -->
Currently the controller does not open or close the modal, its only job is to provide the information shown in the modal. If you click on add for a particular source, the modal will open with that source name on the top.
I have tried doing what seems to work for other people, but i cant get it to work for me.
I want to be able to call this modal from different views. You can click add on the source List view (list of all sources), and the individual source view(details about one source). There will be an add button on both views, that will both call this modal.
I am using twitter bootstrap for the css.
Here is my Controller for this view.
.controller('CategoryController', ['$scope', '$http', '$routeParams', function($scope, $http, $routeParams){
$http.get('assets/js/Category.json').success(function(data) {
$scope.selectedCategory = $routeParams.simplename; //Get the the name of the url
$scope.sources = data; //set sources list, for view to iterate.
$scope.collectionList = {}; // List of all collections, and source under every collection
$scope.addedSources = {}; // object of sources, and the collection they're in. etc ("The Verge" : tech)
$scope.setUpModalData = function(simplename){
$scope.selectedSourceName = $scope.selectedSourceNameTitle =simplename;
$scope.selectedCollection = $scope.selectedCategory;
/* if the current category does not exist in the collection list,
* we will pre fill the form with the current category.
* Other wise we will set it, and it will not be pre pubulated.
*/
if(!($scope.selectedCategory in $scope.collectionList)){
$scope.collectionName = $scope.selectedCategory;
$scope.selectedCollection = 'createNewCollection';
}
}
$scope.removeSource = function(simplename){
var collectionNameHolder = $scope.addedSources[simplename]; //The collection the source is in.
delete $scope.collectionList[collectionNameHolder][simplename]; //delete the source from both lists.
delete $scope.addedSources[simplename]
}
$scope.arrayContains = function(array, element){
return (array.indexOf(element) > -1);
}
$scope.objectContains = function(object, element){
return (element in object);
}
});
}])
Can you put that in a simplified jsfiddle? I'm not sure about what you're trying to achieve and what's not working.
In any case, if you want to have a single modal for multiple views, you could do that with a service, which job would be to open or close the modal, or with events on rootScope which would tell the modal's controller that the modal should be displayed or hidden.
https://docs.angularjs.org/guide/services#creating-services
https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$broadcast
when placing a switch ( md-switch directive) within a tab, an aria error is thrown
ARIA: Attribute " aria-label ", required for accessibility, is missing on node: …
this happens regardless on whether an aria label is present or not. If the switch is moved outside of the tab, it works as expected.
plunker showing the issue
http://plnkr.co/edit/FmZAyLBpzhURbdZuuhQK?p=preview
<div ng-app="app" ng-controller="ctrl" >
<md-tabs md-selected="selectedIndex">
<md-tab id="tab1">Item One</md-tab>
</md-tabs>
<ng-switch on="selectedIndex" class="tabpanel-container">
<div role="tabpanel" id="tab1-content" ng-switch-when="0">
<div>
<md-switch aria-label="toggle" ng-model="data.switch">Switch : {{ toggle }}</md-switch>
</div>
</div>
</ng-switch>
</div>
<script>
var app = angular.module('app', ['ngMaterial']);
app.controller("ctrl", function ($scope) {
$scope.toggle = false;
$scope.selectedIndex = 0;
});
</script>
That was strange..
This issue seems to be fixed with the most current build of angular material (version 0.6.1-master-0767813).
Here's the plunker: http://plnkr.co/edit/chEaf9i50mIiThp0Jloq?p=preview
I just changed the scripts to the most current build:
<link rel="stylesheet" href="//rawgit.com/angular/bower-material/master/angular-material.css">
<script src="//rawgit.com/angular/bower-material/master/angular-material.min.js"></script>
Also you needed to change the ng-model to the toggle value. So instead of using
ng-model="data.switch"
You needed to use
ng-model="toggle"
pointing to $scope.toggle.
I'm trying to set a tab as active through the markup. For some reason when I set the active attribute on a tab it seems to mangle the state of the tabs. The page loads up fine and the tab that was set as active will be deactivated when clicking another tab. When I click back on the tag that was set with active="true" the previously selected tab will not be deselected.
...
<tab heading="Dynamic Title 1" active="true">Some Title 1</tab>
...
http://plnkr.co/edit/xzDbezXgkSMr6wokov6g?p=info
I switched to creating a variable that is set to true at init and plopped that into the active attribute. I'm hoping there's a better way to this though.
<tabset ng-init="startActive = true">
...
<tab heading="Dynamic Title 1" active="startActive">Some Title 1</tab>
...
</tabset>
http://plnkr.co/edit/mt5MQSZEl730fsMuMxg8
I don't want to define the tabs in js because this is a project that uses webforms and piping data from that to js might be worse than what I'm doing here. I change the page to be completely built with angular in which case piping data like the tab to be selected could be part of some config endpoint that would be hit on the controller's init. I'd rather not have to redesign a complete page to make this change but it seems like the most correct way to go. Any thoughts and tips would be appreciated.
I know this is quite old, but after wasting hours of my life, I came up with a super dirty hack, that does the trick (assuming I understood your question correctly and you have the same issue as me).
Problem Statement
Using UI Bootstrap Tabs, dynamically adding tabs based on list data and maintaining the active state outside of this data.
When using the Tabs of UI Bootstrap and generating tabs like this:
<tab ng-repeat="item in page.data.list" active="item.active">
UI Bootstrap will use the binding of the item to store the active state. If we omit the active attribute, UI Bootstrap will maintain it internally but then it becomes impossible to manipulate the active state from the outside, except for accessing it via one of these: $$ (which are the untouchables!)
Solution (The Hack)
Maintain the active state in another list:
<div ng-controller="parasample-tabs">
{{activeState}}
<tabset ng-show="page.data.list.length">
<tab ng-repeat="item in page.data.list" active="activeState[$index]">
<tab-heading>
<i style="cursor: pointer" class="glyphicon glyphicon-remove" ng-click="delTab($index)" prevent-default></i>
Item {{$index +1}}
</tab-heading>
{{item.text}} - {{item.transcript}} - {{item.active}}
</tab>
</tabset>
<!--
For me this problem arose because I like to use self-contained, self-managing data
from factories, hence I call addItem not on a controller
-->
<button ng-click="page.addItem()">Add Item</button>
</div>
Now for the controller, that is wrapped around that tabs and manages them, and their active state instead of writing it into my data:
app.controller('parasample-tabs', function ($scope) {
$scope.maxItems = 5;
$scope.activeState = [];
$scope.delTab = function (idx) {
var list = $scope.page.data.list;
if (list.length > 0) {
$scope.page.delItem(idx);
$scope.activeState.splice(idx, 1);
}
};
$scope.$watch(
"page.data.list.length",
function (newLen, oldLen) {
if (!newLen) return;
// new item => new tab, make active
if (newLen > oldLen)
$scope.activeState.push("true");
}
);
});
Now UI Bootstrap will access the array activeState and store the active state there. There is no need for initialisation, as that is taken care of.
When a new item is added to our data list, the watch will set the new tab as the active tab (thats my preference) and the rest of the list will be updated by UI Bootstrap.
When deleting however, it is not easily possible to determine which item was removed, so I had to wrap my page.delItem into the controller's delTab method.
Here is a fiddle to play with, too.
Let's hope that UI Bootstrap will allow for a different way to maintain the active state instead of having a two way binding in the active attribute. I like having an "active ID" variable.
Disclaimer: I am aware of how dirty this is and I only tested it in Chrome so far and it works nicely.
You're missing quite a few here. Here's a more extensible way:
var app = angular.module('myApp',[]);
app.controller('MyController', ['$scope',function($scope){
$scope.tab = 0;
$scope.changeTab = function(newTab){
$scope.tab = newTab;
};
$scope.isActiveTab = function(tab){
return $scope.tab === tab;
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE HTML>
<html ng-app="myApp">
<head>
<style type="text/css">
.active{
background-color:red;
}
</style>
</head>
<body ng-controller="MyController">
<div>
<div ng-class="{'active':isActiveTab(0)}" ng-click="changeTab(0)">Some Title 1</div>
<div ng-class="{'active':isActiveTab(1)}" ng-click="changeTab(1)">Some Title 2</div>
</div>
<br/>
<div ng-show="isActiveTab(0)">tab1</div>
<div ng-show="isActiveTab(1)">tab2</div>
<script type="text/javascript" src="js/angular-1.2.24.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
</body>
</html>
Initialization should always be in the controller.
Change the values using a controller function. Here, defined as 'changeTab()'
For checking active tabs, create a controller function to compare if the current value of $scope.tab is equal to the current tab.
I also added a bit of styling to impose which tab is active.