How to Redraw Tables using Angular UI Tabs and UI Grid? - angularjs

I'm trying to migrate from jQuery Data Tables to Angular UI. My app uses the MySQL World schema and displays the data in 3 different tables. In jQuery, I'd 3 different pages, each launched from a home page. Here's a live example.
In Angular, I created 3 tabs, clicking on each of which is supposed to reload the page with a new grid and populate it with data. Problem is, the grid gets displayed alright on page load with the content on the first tab. On clicking the other tabs, page goes empty and nothing is rendered. Now the data returned is not insignificant, sometimes around 4k rows. However, the problem isn't a latency issue as I've confirmed by waiting several minutes.
I'm not a JS/CSS/HTML guy so most likely I'm missing something. That's why this question.
Edit:
Plnkr
Following is the code:
HTML:
<body>
<div id="selection-panel" class="selection-panel" ng-controller="HelloWorldCtrl">
<div>
<uib-tabset type="pills" justified="true">
<uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" select="update(tab.title)">
<div id="data-panel" class="data-panel" ui-grid="gridOptions"></div>
</uib-tab>
</uib-tabset>
</div>
</div>
<script src="js/app.js"></script>
</body>
JS:
(function() {
var app = angular.module('helloWorld', ['ui.bootstrap', 'ui.grid']);
app.controller('HelloWorldCtrl', ['$http', '$scope', function ($http, $scope) {
$scope.tabs = [
{ title: 'Countries' },
{ title: 'Cities' },
{ title: 'Languages' }
];
$scope.gridOptions = {};
$scope.gridOptions.data = [];
$scope.gridOptionsCountries = {
columnDefs: [
{ name: 'code'},
{ name: 'name'},
{ name: 'continent'},
{ name: 'region'},
{ name: 'population'}
]
};
$scope.gridOptionsCities = {
columnDefs: [
{ name: 'id'},
{ name: 'name'},
{ name: 'country'},
{ name: 'population'}
]
};
$scope.gridOptionsLanguages = {
columnDefs: [
{ name: 'country'},
{ name: 'language'}
]
};
$scope.update = function(title) {
if (title === "Countries") {
$scope.gridOptions = angular.copy($scope.gridOptionsCountries);
} else if (title === "Cities") {
$scope.gridOptions = angular.copy($scope.gridOptionsCities);
} else if (title === "Languages") {
$scope.gridOptions = angular.copy($scope.gridOptionsLanguages);
}
$http.get(title.toLowerCase()).success(function(data) {
$scope.gridOptions.data = data;
});
};
}]);
})();

I see 2 problems here:
You are creating/changing gridOptions dinamically. This is not the usual way of doing things and can bring many problems.
You are using grids inside of uib-tabs and this, like uib-modals, can have some annoying side effects.
I'd suggest you to address the first issue using different gridOptions (as you do when you create them) and then putting them inside your tabs array children, this way you can refer them from the html this whay:
<uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" select="update(tab.title)">
<div id="data-panel" class="data-panel" ui-grid="tab.gridOptions"></div>
</uib-tab>
The second problem is quite known and inside this tutorial they explain how to address it: you should add a $interval instruction to refresh the grid for some time after it's updated in order to let the tab take its time to load and render.
The code should be as follows:
$scope.tabs[0].gridOptions.data = data;
$interval( function() {
$scope.gridCountriesApi.core.handleWindowResize();
}, 10, 500);
Where gridCountriesApi are created inside of a regular onRegisterApi method.
I edited your plunkr, so you can see the whole code.

I can not get this to work in tabs, my guess is because you first use ng-repeat which creates a scope for each iteration, and then maybe the tabs itself creates a new scope and this causes a lot of headache with updates.
The quickest solution is just to move the grid outside of the tabs.
Here is the updated html.
HTML
<body>
<div id="selection-panel" class="selection-panel" ng-controller="HelloWorldCtrl">
<div>
<uib-tabset type="pills" justified="true">
<uib-tab ng-repeat="tab in tabs" heading="{{tab.title}}" select="update(tab.title)"></div>
</uib-tab>
</uib-tabset>
<!-- This is moved outside -->
<div id="data-panel" class="data-panel" ui-grid="gridOptions">
</div>
</div>
<script src="js/app.js"></script>
</body>

In my situation tab contents consume important time to be loaded. So if your case is that you don't want to update tab content everytime the tab is clicked, you can use this workaround:
In the HTML part I use select property to indicate which tab is pressed:
<tabset justified="true">
<tab heading="Tab 1" select="setFlag('showTab1')">
<div ng-if="showTab1">
...
</div>
</tab>
</tabset>
In the tab (*) container I used a switch to recognize which tab is pressed and broadcast the press action:
case 'showTab1':
$scope.$broadcast('tab1Selected');
break;
And in the controller part I listen the event and handle resizing:
// The timeout 0 function ensures that the code is run zero milliseconds after angular has finished processing the document.
$scope.$on('tab1Selected', function () {
$timeout(function() {
$scope.gridApi1.core.handleWindowResize();
}, 0);
});
Here is my Plunkr. Hope it helps.
(*) For current bootstrap version you should use and

Related

Multiple Data Bindings in Angular JS

I am new to Angular and would like to know if something like this is possible:
I'm calling data to each story using JSON. Once the story is clicked I would like it to be sent to Javascript, then passed to a different HTML div, the player div.
Is there a way to do that? Or is that not possible with Angular?
For example:
<body ng-app="app" ng-controller"storyCtrl">
<div id="story-1"
class="play_button"
data-id="{id}"
data-title="{title}"
data-storyurl="{fullUrl}"
data-program="{program}"
data-programurl="{program.fullUrl}"
data-type="audio"
data-src="{soundcloudLink}"
data-item-type="story"
data-pub-date="{addedOn}"
data-excerpt="{excerpt}">
</div>
<div id="story-2"
class="play_button"
data-id="{id}"
data-title="{title}"
data-storyurl="{fullUrl}"
data-program="{program}"
data-programurl="{program.fullUrl}"
data-type="audio"
data-src="{soundcloudLink}"
data-item-type="story"
data-pub-date="{addedOn}"
data-excerpt="{excerpt}">
</div>
<!--Here is where I would like the clicked data passed back to:-->
<div id="player">
{{story.Title}}
{{story.Program}}
</div>
</body>
How would I go about creating the variables so that they display the data from the clicked story? Is it possible using Angular's two way binding? I would like them to display the data-information as follows:
var app = angular.module('app', []);
app.controller('storyCtrl', function($scope) {
var story = [
{
Id: 'data-id',
Title: 'data-title',
Storyurl:'data-storyurl',
Program: 'data-program',
Programurl: 'data-programurl',
Type: 'data-type',
Src: 'data-src',
Pubdate: 'data-pub-date',
Excerpt: 'data-excerpt',
},
];
$scope.story = story;
});

AngularJS - What is the right way for this?

I created a page, in which you can view lots of details on a user, in panels.
Every panel contains some information, received from a service, and sometimes has actions on it.
I have about 10 panels, and the viewer decides what he wants to see.
Is it right to make each of those panels a separate controller,
OR only panels with actions need to be in a separate controller, OR all the panels in one controller?
If every panel is a controller, and my code is this:
div class="panel panel-default">
<div class="panel-heading">Panel Heading</div>
<div class="panel-body" ng-controller="historyPanel" ng-include="'panels/history.html'"></div>
</div>
Is there a way for me to know if a panel is empty?
For example, I have an history panel, that I wanna show only when there is history to show, and I don't wanna show "No History", just wanna hide that panel.
But I do want to keep the panel code outside of the history.html view.
You should consider creating a directive for this. You can either create a single directive that contains all 10 panels, and provide an attribute to specify which panel to show, or you could create 10 different directives.
Something like:
<my-panel panel="history" ng-show="userPanel.history.display"></my-panel>
<my-panel panel="settings" ng-show="userPanel.settings.display"></my-panel>
<my-panel panel="friends" ng-show="userPanel.friends.display"></my-panel>
<my-panel panel="music" ng-show="userPanel.music.display"></my-panel>
Then your scope controller might have something like:
app.controller('MyController', function($scope) {
$scope.userPanel = {
history: { display: true },
settings: { display: true },
friends: { display: true },
music: { display: false }
}
});
This way, you could load data from a service which has the user preferences for which panels are displayed.
Try something like this, try not to inject $scope at all cost. :)
function PanelCtrl() {
'use strict';
var ctrl = this;
ctrl.panels = [{
"id": 0,
"name": "Lucia Oconnor",
"history": "my history"
}, {
"id": 1,
"name": "Stevenson Mcintosh",
"history": "my history"
}, {
"id": 2,
"name": "Spears Sims",
"history": "my history"
}];
}
function panel() {
'use strict';
return {
restrict: 'E',
scope: {},
bindToController: {
//Depends what youd like to do with the PanelCtrl
//'&': use a function on that ctrl
//'=': two way data binding
},
controller: 'PanelCtrl as ctrl',
templateUrl: './templates/panel.ng.html'
};
}
angular
.module('app', [])
.controller('PanelCtrl', PanelCtrl)
.directive('panel', panel);
This would be that template:
//also if static content might want to use--one time bindings.
// that would be ::panel.name
<div class="panel panel-default" id="panel_{{panel.id}}">
<div class="panel-heading">{{Panel.name}}</div>
<div class="panel-body">{{Panel.history}}</div>
</div>
This would be your html:
//be sure to include track by, major perf gains.
<div class="container" ng-repeat="panel in ctrl.panels track by panel.id">
<panel bind="here" ng-if="ctrl.history.length"></panel>
</div>

How do I use Angularjs's ng-style to change the background every 3 seconds?

Still learning angular here and could use some help after much research.
Very simple example I have going, I am simply applying the ng-style directive to the body to change the background as such
<body ng-app="myapp" ng-controller="mainController as main" ng-style="***need help here***">
controller
angular.module('mainCtrl', [])
.controller('mainController', function() {
// set the view model as this scope
var vm = this
vm.slides = [
{ images: 'assets/imgs/blurred-highway.jpg' },
{ images: 'assets/imgs/night-square.jpg' }
]
})
I have not had much luck with the ui.bootstrap carousel, thus I am looking to implement something even simpler; just want the full-screen background to change every 3 seconds infinitely cycling through the slides collection (will eventually have 6-7 photos), no additional controls needed.
help is much appreciated.
Thanks
You just have to make use of $interval.
Note that for the sake of the example, I replace your images with both colors: blue and red.
Here's the HTML code:
<body ng-app="myApp" ng-controller="mainController as main" ng-style="{'background-color': main.slide}">
//content
</body>
Here's the Javascript code:
angular.module('myApp', [])
.controller('mainController', function($interval) {
// set the view model as this scope
var vm = this
var slides = [
'blue' ,
'red'
];
vm.slide = slides[0];
$interval(function(){
if(vm.slide == slides[0])
vm.slide = slides[1];
else
vm.slide = slides[0];
}, 3000, 0);
});
Here's the corresponding Plunkr.
Thus, you just have to replace the colors and background-color by your images and background-image

Angularjs showing tab content from an array

I am building my first Angular SPA. In a form I have three tabs, and want a tab to show only its contents and only when selected. The tab contents are in an array in the controller.
I have been playing around with different $scope configurations all evening, but can only make nothing, or the content of all 3 tabs appear at once. I can't isolate the content of one tab from the array in the view. I feel like I must be misunderstanding the $scope, but can't seem to find any documentation that fits the situation. Perhaps its completely the wrong way to go about it. In short, I would greatly appreciate any help or advice from you angular gurus out there :)
Thanks so much!! Here's the code:
I am keeping the tab content and functions as an array in the controller like this:
$scope.tabs = [
{
title: 'Free',
description: 'free',
price: 'free',
elem: 'free'
},{
title: 'Basic',
description: 'basic',
price: '9.97',
elem: 'basic'
},{
title: 'Unlimited',
description: 'Every service',
price: '19',
elem: 'unlimited'
}
];
$scope.currentTab = 'free';
$scope.onClickTab = function (tab) {
$scope.currentTab = tab.elem;
$('.tab-content').hide();
$('#'+tab.url).show();
}
$scope.isActiveTab = function(tabUrl) {
return tabUrl == $scope.currentTab;
}
I use ng-repeat to create the selection buttons:
<li class="tab-option" ng-repeat="tab in tabs"
ng-class="{active:isActiveTab(tab.elem)}"
ng-click="onClickTab(tab)">{{tab.elem}}
</li>
Below this, I want to display the price etc.. of the tab selected in a separate box. I see that {{tab.elem}} selects the correct part of the array, so I thought that {{tab.price}} would do the same.
But, when I use {{tab.price}} or any other {{tab.}} , like this:
<div>
<div class="spacer"></div>
<p>{{tab.description}}</p>
<p>{{tab.price}}</p>
</div>
I get nothing.
Thanks again for any help, advice or thoughts!
-Berkeley
The same functionality can be achieved in a different way
by the way we define onClickTab function.
Instead of storing $scope.currentTab as 'free' make it a reference to current tab
viz. $scope.currentTab=tab;
And then you can simply avoid the jquery selectors inside the function as it is not a good practise to do so.
In the content div where you need to show the price description use currentTab instead of tab.
Summarizing
<li class="tab-option" ng-repeat="tab in tabs"
ng-class="{active:isActiveTab(tab)}"
ng-click="onClickTab(tab)">{{tab.elem}}
</li>
Controller logic
//default one
$scope.currentTab=$scope.tabs[0];
$scope.isActiveTab = function(tabUrl) {
return tabUrl == $scope.currentTab;
}
$scope.onClickTab = function (tab) {
$scope.currentTab = tab.elem;
}
Content to be displayed by current tab
<div>
<div class="spacer"></div>
<p>{{currentTab.description}}</p>
<p>{{currentTab.price}}</p>
</div>
Always avoid hiding or showing elements in controller, as far as dom manipulations
are concerned make them using a directive.
Hope it helps.
I got it working!
The controller:
$scope.currentTab = $scope.tabs[0];
$scope.onClickTab = function (tab) {
$scope.currentTab = tab;
}
the view:
<div class="description">
<p class="text-center">{{currentTab.description}}</p>
<p class="text-center">{{currentTab.price}}</p>
</div>
This is all the code I needed. I used Naveen's advice of using the tab[order] to call it initially, but was struggling still with getting the comments to show up with the correct information only when selected.
I had been using $scope.currentTab = tab.elem; this doesn't work because it is trying to be too specific. By changing it to simply "tab" angular knew to treat all the tab's contents as the new $scope.currentTab so that when I call {{currentTab.description}} I get the correct response.
And with that I will close this question. Thanks so much!

AngularJS - FAQ inside a modal (bug?)

Currently i'm developing a webapp with AngularJS for a giant company, and i'm trying to have a simple FAQ inside a modal.
In my localhost the FAQ it's working just fine (very similar to the original FAQ in angular documentation), but when i write exactly the same code inside a modal i'm getting a console error:
TypeError: Object [object Object] has no method 'addGroup'
Important to state that inside the modal my $scope.oneAtATime = true; it's being ignored, so basically even if i force it to be true
<accordion close-others="true">
It's always false.
This addGroup method is on the AngularJS library code.
Any ideas?
The HTML:
<div class="modal__container__body">
<div id="faq_accordion" ng-controller="AccordionController">
<accordion close-others="true">
<accordion-group heading="{{faq.title}}" ng-repeat="faq in faqs">
{{faq.content}}
</accordion-group>
</accordion>
</div>
</div>
The controller
lobby.controller("AccordionController", ["$scope", function ($scope) {
$scope.oneAtATime = true;
$scope.faqs = [
{
title: "Q1?",
content: "A1"
},
{
title: "Q2?",
content: "A2"
},
{
title: "Q3?",
content: "A3"
},
{
title: "Q4?",
content: "A4"
}
];
}]);
Please notice that in the above code i'm forcing close-others to be true, directly in the html tab.
Help?
We had the same problem recently, change
<div id="faq_accordion" ng-controller="AccordionController">
to
<div id="faq_accordion" ng-controller="MyAccordionController">
That should fix it. You basically overwrote the plugin controller with your own. Don't forget to change the controller definition also, it's the part that's breaking it.

Resources