AngularJS - FAQ inside a modal (bug?) - angularjs

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.

Related

Using a variable in ng-repeat and passing it to a controller

I have created an AngularJS app that contains two columns: one for a menu and a second for content (each link in the menu links to). The content and menu is in a table in DynamoDB that I am scanning with a Lambda function. The output of this function is being consumed as a JSON response with the following structure:
{
"body": [{
"course-content": "RL front matter",
"course-video": "https://123-course-videos.s3.amazonaws.com/vid_1.mp4",
"course-id": "rcl",
"course-title": "sml",
"course-lesson": "Lesson One"
}, {
"course-content": "RL2 front matter",
"course-video": "https://123-course-videos.s3.amazonaws.com/vid_2.mp4",
"course-id": "rcl2",
"course-title": "sml",
"course-lesson": "Lesson Two"
}, {
"course-content": "RL3 front matter",
"course-video": "https://123-course-videos.s3.amazonaws.com/vid_3.mp4",
"course-id": "rcl3",
"course-title": "sml",
"course-lesson": "Lesson Three"
}]
}
I (with the help of the great folks here) built the following controller that loops through the response and builds the menu:
controller
app.controller('menu', function($scope, $http) {
$http.get('api address').
then(function(response) {
$scope.navi = response.data.body;
$scope.selectCourse = function(course, index, path) {
console.log(path)
$scope.content = response.data.body[index]
console.log($scope.content)
};
});
});
menu built using ng-repeat
<div ng-repeat="nav in navi">
<ul><li>{{ nav['course-lesson'] }}
<button ng-click="selectCourse(nav, $index, '/content/' +
$index)">Select</button>
</li></ul>
</div>
This build the following menu:
Lesson One <button>
Lesson Three <button>
Lesson Two <button>
And I am using a second controller that consumes the content from the same api call:
app.controller('content', function($scope, $http) {
$http.get('api address').
then(function(response) {
$scope.content = response.data.body;
});
});
content is displayed in a route with a simple content.html template as follows:
app.config(function($routeProvider) {
$routeProvider
.when("/", {
templateUrl : "templates/main.html"
})
.when("/content/:id", {
templateUrl : "templates/content.html",
controller : "content"
});
Updated: Here is where I still need help:
How do I pass/use the $index variable in the menu controller to the content controller so the content updates as needed in the right template when I click on each button?
To help better understand the functionality:
Lesson One links to content for lesson one - lesson one content is
displayed in the content template.
List item Lesson Two links to content for lesson two - lesson two content is
displayed in the content template.
List item Lesson Three links to content for lesson three - lesson three content
is displayed in the content template.
Sorry for the long post, but I wanted to provide enough detail to help clarify any confusion.
To pass data to a controller from an ng-repeat element, use the ng-click directive:
<div class="col-4" ng-controller="menu">
<div ng-repeat="nav in navi">
<ul>
<li>
{{ nav['course-lesson'] }}
<button ng-click="selectCourse(nav, $index)">Select</button>
</li>
</ul>
</div>
Assign the function to scope:
$scope.selectCourse = function(course, index) {
console.log(course, index);
};
For more information, see
AngularJS ng-click Directive API Reference

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

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

Error: [ng:areq] Argument 'announcementCtrl' is not a function, got undefined

I am getting this error that 'announcementCtrl' is not a function? Can someone please tell me what I am doing wrong here. It clearly is defined so what's happening?
This is my javascript:
(function() {
var app = angular.module('announcementApp', []);
app.controller('announcementCtrl', function() {
this.announcements = announcementsArray;
});
var announcementsArray = [
{
type: 'UPDATE',
announcement: 'DISA Maps are almost complete! Look foward to reporting out at the project share next week.'
},
{
type: 'SHOUT-OUT',
announcement: 'Great work to Lawson and Patrick on Innovation Cell. We are gaining more strategic position and proving our value everyday.'
},
{
type: 'EVENT',
announcement: 'Dr. Chipley will be visiting 3/20/2015 to talk Cybersecurity'
}
];
})();
This is my HTML:
<div class="row row2" ng-app="announcementApp">
<section class="sub-box client-box">
<div class="announcements" ng-controller="announcementCtrl as announcements">
<div class="announcement-block" ng-repeat="eachAnnouncement in announcements">
<div class="event-highlight update"></div>
<div class="wrap">
<div class="announcement-description">{{eachAnnouncement.type}}</div>
<div class="announcement">{{eachAnnouncement.announcement}}</div>
</div>
</div>
</div>
</section>
</div>
Try to use $scope to save your values:
app.controller('announcementCtrl', function($scope) {
$scope.announcements = announcementsArray;
});
After i did that i receive the same error only when i comment my controller definition at all.
Also, clear cache of your browser.
Demo: http://plnkr.co/edit/oHHEtRgLpVbEcbao5U9o?p=preview
It looks like you mixed up the controllers 'handle' and the property you want to access. Since you are not injecting $scope ng-controller="announcementCtrl as announcements" declares announcements as a kind of 'handler' for this. Inside your controller you declare this.announcements to be your array. So it should be ng-repeat="eachAnnouncement in announcements.announcements" (first announcements = this in your controller, second = the array)
Look at the code example in the documentation: AngularJS: ngController
Edit: Maybe the answer was a bit confusing, but it works allright: Fiddle
I renamed your controller handle to 'ctrl' to make it more clear what was changed.

Conditionally apply hasDropdown directive on ng-repeated element

I'm working on a project where I use both angularJS and foundation, so I'm making use of the Angular Foundation project to get all the javascript parts of foundation working. I just upgraded from 0.2.2 to 0.3.1, causing a problem in the top bar directive.
Before, I could use a class has-dropdown to indicate a "top-bar" menu item that has a dropdown in it. Since the menu items are taken from a list and only some have an actual dropdown, I would use the following code:
<li ng-repeat="item in ctrl.items" class="{{item.subItems.length > 0 ? 'has-dropdown' : ''}}">
However, the latest version requires an attribute of has-dropdown instead of the class. I tried several solutions to include this attribute conditionally, but none seem to work:
<li ng-repeat="item in ctrl.items" has-dropdown="{{item.subItems.length > 0}}">
This gives me a true or false value, but in both cases the directive is actually active. Same goes for using ng-attr-has-dropdown.
this answer uses a method of conditionally applying one or the other element, one with and one without the directive attribute. That doesn't work if the same element is the one holding the ng-repeat so i can't think of any way to make that work for my code example.
this answer I do not understand. Is this applicable to me? If so, roughly how would this work? Due to the setup of the project I've written a couple of controllers and services so far but I have hardly any experience with custom directives so far.
So in short, is this possible, and how?
As per this answer, from Angular>=1.3 you can use ngAttr to achieve this (docs):
If any expression in the interpolated string results in undefined, the
attribute is removed and not added to the element.
So, for example:
<li ng-repeat="item in ctrl.items" ng-attr-has-dropdown="{{ item.subItems.length > 0 ? true : undefined }}">
angular.module('app', []).controller('testCtrl', ['$scope',
function ($scope) {
$scope.ctrl = {
items: [{
subItems: [1,2,3,4], name: 'Item 1'
},{
subItems: [], name: 'Item 2'
},{
subItems: [1,2,3,4], name: 'Item 3'
}]
};
}
]);
<div ng-app="app">
<ul ng-controller="testCtrl">
<li ng-repeat="item in ctrl.items" ng-attr-has-dropdown="{{ item.subItems.length > 0 ? true : undefined }}">
{{item.name}}
</li>
</ul>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
Ok, I made a directive. All <li> will need an initial attr of:
is-drop-down="{{item.subItems.length > 0}}"
Then the directive checks that value and for somereason its returning true as a string. Perhaps some onc can shed some light on that
app.directive('isDropDown', function () {
return {
link: function (scope, el, attrs) {
if (attrs.isDropDown == 'true')
{
return el.attr('has-dropdown', true); //true or whatever this value needs to be
}
}
};
});
http://jsfiddle.net/1qyxrcd3/
If you inspect test2 you will see it has a has-dropdown attribute. There is probably a cleaner solution, but this is all I know. I'm still new to angular.
edit I noticed a couple extra commas in my example json data..take note, still works, but they shouldn't be there.

AngularJS ngRepeat Not Updating Display Properly

I have have a page where I am using plupload to upload files and having a weird issue with a ng-repeat not updating properly. Here is the relevant code:
<div ng:app>
<div name="myForm" ng-controller="Ctrl">
<input ng-model="test" type="text" />{{test}}<div id="container" class="controls">
<div id="filelist">
<div ng-repeat="file in filesToUpload">{{file.name}} ({{file.size}}) <b>{{file.percent}}</b></div>
</div>
<br />
<a id="pickfiles" href="#">[Select files]</a>
</div>
</div>
</div>​
function Ctrl($scope) {
$scope.test = '';$scope.filesToUpload = [{id: 1, name: 'test', size: '123kb'}];
$scope.addItem = function(object) {
$scope.filesToUpload.push(object);
}
$scope.uploader = new plupload.Uploader({
runtimes : 'html5,flash,browserplus,gears',
browse_button : 'pickfiles',
container : 'container',
max_file_size : '10mb',
url : 'upload.php',
flash_swf_url : '/plupload/js/plupload.flash.swf'
});
$scope.uploader.init();
$scope.uploader.bind('FilesAdded', function(up, files) {
$scope.filesToUpload = [];
$.each(files, function(i, file) {
$scope.addItem({
id: file.id,
name: file.name,
size: plupload.formatSize(file.size)
});
});
console.log($scope.filesToUpload);
up.refresh(); // Reposition Flash/Silverlight
});
}​
Here is a trimmed down jsfiddle showing the issue happening:
http://jsfiddle.net/9HuUC/
To reproduce this issue do the following:
Click on [select files] and selects a few files (notice how you don't see the files displayed anywhere on the output)
Type any character into the input box (magically the files that you select know appear)
What would cause this type of behavior? I mean I know that the data is properly being set in $scope.filesToUpload because I have the console.log() there and even checked it in Batarang and it loods good there but for some reason something else needs to be updated for the display to be updated.
Interestingly enough, I have another ng-repeat that is working fine on the same page. I am wondering if it has anything to do with where the code is (being inside the FilesAdded event on the uploader).
The issue is due to the fact that the FilesAdded callback is executed outside the scope of AngularJS (it's called by the uploader), therefore the scope updates won't be triggered.
To solve this, just add the $scope.$apply call in the callback, encapsulating your existing code:
$scope.uploader.bind('FilesAdded', function(up, files) {
$scope.$apply( function() {
$scope.filesToUpload = [];
$.each(files, function(i, file) {
$scope.addItem({
id: file.id,
name: file.name,
size: plupload.formatSize(file.size)
});
});
console.log($scope.filesToUpload);
up.refresh(); // Reposition Flash/Silverlight
});
});
With this update, it's working in the fiddle. For reference see the AngularJS official documentation, $apply method of the scope object:
http://docs.angularjs.org/api/ng.$rootScope.Scope

Resources