How to use Angular ui.bootstrap tabs with ui.router? - angularjs

Here's my initial implementation of ui.bootstrap tabs using ui.router:
<tabset>
<tab heading="Tab1" select="$state.go('home.tab1')">
<div ui-view="forTab1"></div>
</tab>
<tab heading="Tab2" select="$state.go('home.tab2')">
<div ui-view="forTab2"></div>
</tab>
</tabset>
Nicely simple, and it mostly works, but it has several problems.
First, it doesn't work if I enter a URL into the browser, e.g. ".../home/tab1". The issue I presume is that while ui.router may be routing to the correct view, it doesn't know to select that view's tab.
Second, if I try to leave the page for a state on a different page, it triggers an apparent bug in the tabs logic. As the tab set is being destroyed, it destroys the tabs one by one. When it destroys the currently selected tab, it appears to run the same logic that it would run if only that tab were being destroyed, not the entire tab set. This causes it to select one of the other not-yet-destroyed tabs, which makes ui.router go to that tab's state, which prevents me from leaving the page. (The fix presumably should be that destroying the tab set should not select any tabs during the destruction process.)
Third, some of the tab views may be complex, with many server hits during their construction. I don't want to destroy and recreate them every time I switch from one tab to another. I'd much rather have a tab view be created when that tab is first selected and then persist until I leave the page. Selecting a previously viewed tab should just make it visible again, not re-create it.
So, it looks like I need something more complex and sophisticated, but what? Thanks in advance for any suggestions.
Update: the ui.router FAQ turns out to have a question on how to implement tabs. It points to an "extras" package that looks promising. Hopefully there's a way to integrate with ui.bootstrap tabs. http://christopherthielen.github.io/ui-router-extras/#/sticky
Update: here are two implementations that work much better but still aren't quite right. The first one uses ui.bootstrap tabs:
<tabset>
<tab heading="Products" ui-sref=".products" active="$state.includes('home.products')"></tab>
<tab heading="Users" ui-sref=".users" active="$state.includes('home.users')"></tab>
</tabset>
<div ui-view="home.products" ng-show="$state.includes('home.products')"></div>
<div ui-view="home.users" ng-show="$state.includes('home.users')"></div>
The second is based on top.html from Chris Thielen's sticky-tabs example:
<div class="navbar navbar-default navbar-static-top" role="navigation">
<ul class="nav navbar-nav">
<li ng-class="{active: $state.includes('home.products')}" ><a ui-sref="home.products">Products</a></li>
<li ng-class="{active: $state.includes('home.users')}" ><a ui-sref="home.users">Users</a></li>
</ul>
</div>
<div class="tabcontent well-lg" ui-view="home.products" ng-show="$state.includes('home.products')" class="col-sm-6"></div>
<div class="tabcontent well-lg" ui-view="home.users" ng-show="$state.includes('home.users')" class="col-sm-6"></div>
Both of these avoid my first two problems above - going to a tab via URL works, and I can leave the page without triggering the tabs destroy bug (presumably because I'm not using "select=").
However, there's still a problem, and it happens with both versions. The home.users template has a ui-grid, and if I first go to the page with that tab selected, the grid is empty. I can see the client requesting the grid's data from the server, but it doesn't show it. If I go to the other tab and come back, then it shows the data (after re-fetching it). Worse, this means I can't make the home.users state sticky, because then it gets stuck in the no-data-showing mode.

The easiest way is to ignore all the fancy ui-bootstrap directives and just make use of the CSS. Then you can end up with something very simple:
<ul class="nav nav-tabs">
<li ui-sref=".state1" ui-sref-active="active"><a>State 1</a></li>
<li ng-repeat="type in viewModel.types"
ui-sref=".typeState({type:type})"
ui-sref-active="active"><a>{{type}}</a></li>
</ul>
This will give the appearance of the tabs while maintaining a correct active states. The trouble with using the <tab-header> directive is that you can't cleanly initialize the active state from the ui-router $state, so you get multiple tabs highlighted on page load.

You might consider having a look at this project, ui router plus ui bootstrap tabs, which declares itself as
Idiot-proof tab panes with route support using Angular.js + Bootstrap 3 + UI Router
I haven't tried it as yet, but certainly looks promising.

Just did this in a project of mine. This assumes that your routes are wired with ui.router. Hope it helps.
<uib-tabset active="active">
<uib-tab index="0" ui-sref ="tab1" heading="Tab1"></uib-tab>
<uib-tab index="1" ui-sref="tab2" heading="Tab2"></uib-tab>
</uib-tabset>

Related

Evaluate AngularJS expression within Material Design Lite's mdl-menu

I am building a tiny single-page-application using Angular for the functionality and mdl for the design. I wanted to have a dropdown select box and since mdl doesn't provide this out of the box I built one using mdl-menu. The problem is now that Angular seems to have troubles evaluating expressions I put inside this menu, maybe because the menu is only opened when you click the button? I tried using expressions within the list items and this gives the expression output but also the original expression, which I obviously don't want. I also tried to ng-repeat the list items and then they just don't show up.
<button id="demo-menu-lower-left"
class="mdl-button mdl-js-button mdl-button--icon">
<i class="material-icons">more_vert</i>
</button>
<ul class="mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect"
for="demo-menu-lower-left">
<li class="mdl-menu__item">{{5+5}}</li>
<li class="mdl-menu__item mdl-menu__item--full-bleed-divider">Another Action</li>
<li disabled class="mdl-menu__item">Disabled Action</li>
<li class="mdl-menu__item">Yet Another Action</li>
</ul>
Result pic (Look at the evaluation in the top left, but the original expression is the actual "clickable" action)
Thx in advance!
MDL's javascript (denoted by the mdl-js CSS classes) will probably fool around with the HTML before Angular can do anything, eventually creating HTML that gives strange results when 'parsed' by Angular. It would be best to ensure your HTML gets processed in a strict order: Angular first, MDL second.
If you need to work with the HTML after the initial loading and rendering of the page, it would help to inspect the HTML in the browser instead of relying on the source HTML. Any javascript code (including Angular) will have to work with this and not the HTML represented by the source.

Spring Security vS AngularJS -> Problems when using sec:authorize tag

I am creating a dynamic menu, all info of the menu I am getting from mysql database.
In my 'menu' database, there's a column called "role_access", where I can manipulate the roles. My intention is to fill this column in the database for each rule I have, then using the code below I could show/hide the menus depending if the client is USER or ADMIN.
<sec:authorize access="hasRole('{{roleAccess}}')">
<li class="menu-dropdown classic-menu-dropdown "><a
data-hover="megamenu-dropdown" data-close-others="true"
data-toggle="dropdown" href="javascript:;"> <span
ng-bind="menu.nome"></span>
</a>
<ul class="dropdown-menu pull-left">
<li ng-repeat="menu in menu.submenus"><span ng-bind="menu.nome"></span>
</li>
</ul></li>
</sec:authorize>
But, when loading the page, Spring Life's Cycle (loads everything before) is different from AngularJS (loads everything after page is loaded).
So, when using {{roleAcess}}, AngularJS is not loading my menu. If I force this roleAccess manually without the {{}}, it works.
Thanks in advance.

multiple ng-view wth ng-if

In my application the index page is like
<div ng-if="isLoggedIn>
<div ng-view>
</div>
<div ng-if="!isLoggedIn">
<div ng-if="type==='admin'">
<div ng-view>
</div>
</div>
<div ng-if="type!=='admin'">
<div>.....</div>
<div ng-view>
</div>
</div>
</div>
So basically i have different ng-views for different views. The problem here is when user is not logged in ,login page is displayed using ng-view.But upon login the ng-view initializes with login page again (may be becoz of $route.current is already set).
When user clicks log in button he/she is directed to home page and again redirected to login page. so no change is displayed on the page.
This issue can be solved using ng-show/hide insted of ng-if. But that creates another issue that controllers are called twice becoz of two ng-view in the dom and also element can't be referred using id becoz two elements are generated fro the same id. So this solution can't be used.
Is there any way of solving it?
Multiple & Nested ng-view won't work on the same page.
You need take a look at angular ui-router which would be great to use in your case, you could also use nested ui-view using angular ui-router
Or may be you can avoid ng-if using ui-router

Angular Tree Directive with Lazy Loading

We are kicking a new project using angularJS :) and one of the requirements is a tree control.
The tree control should support "Lazy Loading", So we could add nodes to it dynamically as we get more data using AJAX.
I saw the 2 directives below, but I do not think "Lazy Loading" is supported
so before I start developing it myself I am asking you folks :)
The 2 nice tree directives I saw:
https://github.com/eu81273/angular.treeview/blob/master/angular.treeview.js
https://github.com/nickperkinslondon/angular-bootstrap-nav-tree
Thanks, Avi
Because AngularJS loads directives before any logic, you cannot use them very well for recursive operations, else you probably wouldn't need a directive for one in the first place.
However, one very pleasant workaround is using ng-include, as this doesn't have the above limitation. And then making a tree is extremely simple.
Where you want to tree, you have something like
<ul class="tree-container">
<li style="margin: 5px; list-style: none;" ng-repeat="d in vm.features" ng-include="'tree_item_renderer.html'"></li>
</ul>
then the include looks something like this
<script type="text/ng-template" id="tree_item_renderer.html">
<span ng-click="d.expand = !d.expand;vm.getChildNodes(d)" ng-show="!d.expand && d.hasChildren"><i class="fa fa-fw fa-plus-circle"></i></span>
<span ng-show="d.expand && d.hasChildren" ng-click="d.expand = !d.expand;d.children=null" ><i class="fa fa-fw fa-minus-circle"></i></span>
<span ng-show="!d.hasChildren" style="margin-left:28px"></span>
<ul>
<li style="list-style:none;" ng-repeat="d in d.children" ng-model="d" ng-include="'tree_item_renderer.html'"></li>
</ul>
</script>
In the controller vm.getChildNodes(d) call you can get the children for the currently expanded node. I expand the nodes and then actually asynchronously do a count for each node over odata to determine if the node has children, so I can show if one of the children has children of its own, but of course you could more efficiently track that in the database if you have control over that (I think I will update my model for this myself).
Note that I've implemented it so that if you open and close and then open, it actually reloads the nodes. You don't have to do that of course, but it saves me from having to implement a reload/refresh button otherwise and it's not like users are going to open/close trees over and over because they have nothing better to do. ;)
The added advantage I have is that I implement user input for some (most) of the nodes, e.g. select them, enter a value for them. I've noticed it is more efficient if these only exist 'on demand' in angular.

angular ui-router maintaining state between tabs

Would like to know the best way to preserve state between tabs. I use bootstrap tabs and angular ui-router. I've a google map in one of the tabs and don't want to reload the map when user selects that tab. Please advise.
Thanks
I think what you are looking for is discussed in this issue: https://github.com/angular-ui/ui-router/issues/63
They are mostly discussing iframes but I believe the same should hold for Google Maps. Unfortunately in the thread they decided that this isn't something that should be implemented in the core release. I haven't tried out the directive they provide (if I get a chance I'll let you know how it goes) but you may be able to get something working with that.
I have actually come across the exact problem you had. My solution was to use styled buttons as my tabs and ng-show for the map tab:
<div id="info-btns">
<button class="btn" ng-model="view" btn-radio="'info'">
Info
</button>
<button class="btn" ng-model="view" btn-radio="'map'" ng-click="loadMap()">
Map
</button>
</div>
<div class="content" ng-show="view != map">
<div ui-view="info"></div>
</div>
<div id="map-container" ng-show="view == 'map'">
<div id="map" class="content" sitemap>
</div>
</div>
ng-show simply uses display:none to hide the map and hence doesn't cause a refresh. You will need to trigger the map to load the first time it is not hidden otherwise it will render incorrectly, hence loadMap()
If I get a chance I'll set up a jsfiddle of this in practice.

Resources