Add an action button to DataTables column header in AngularJS - angularjs

I am trying to add a button to the heading of a column in angular-datatables, which when clicked will run a function. I have tried doing something like this.
DTColumnBuilder.newColumn('name').withTitle(function() {
return '<span>Name</span><button ng-click="runme()">Click me</button>'
})
In the same controller, runme() function is defined as:
$scope.runme = function() {
console.log('clicked');
}
But this is not triggered, it only sort the column data, no matter where i clicked on entire header section.

When you are using this approach you'll need to $compile the content of the <thead> (and anything else injected by DataTables you would like AngularJS to be aware of).
A good place to invoke $compile is in the initComplete callback :
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('initComplete', function() {
$compile(angular.element('thead').contents())($scope)
})
demo -> http://plnkr.co/edit/D72WPqkE3g2UgJTg
Remember to inject $compile to your controller, see for example Working with $compile in angularjs. (Lousy google does not even bother to fix the errors in their docs, so https://docs.angularjs.org/api/ng/service/$compile does not work).
Note: You could also go with static <table> markup
<table>
<thead>
<tr>
<th><span>Name</span><button ng-click="runme()">Click me</button></th>
</tr>
</thead>
<tbody></tbody>
</table>
Then AngularJS will connect $scope.runme to the ng-click, and only if you need additional bindings in the dynamic content inserted by DataTables, a $compile is needed.

Related

My AngularJS view is able to access it's controller's scope, but ng-repeat doesn't work

I'm building a MEAN app and used yeoman to scaffold the client side code. I have a controller and view called movies.
Here is the movie controller:
'use strict';
/**
* #ngdoc function
* #name clientApp.controller:MoviesCtrl
* #description
* # MoviesCtrl
* Controller of the clientApp
*/
angular.module('clientApp')
.controller('MoviesCtrl', function () {
this.movies = [
{
title: 'A New Hope',
url: 'http://youtube.com/embed/1g3_CFmnU7k'
},
{
title: 'The Empire Strikes Back',
url: 'http://youtube.com/embed/96v4XraJEPI'
},
{
title: 'Return of the Jedi',
url: 'http://youtube.com/embed/5UfA_aKBGMc'
}
];
});
Here is the movie view:
<table class="table table-striped">
<thead>
<th>Title</th>
<th>URL</th>
</thead>
<tbody>
<tr ng-repeat="movie in movies">
<td>{{movie.title}}</td>
<td>{{movie.url}}</td>
</tr>
</tbody>
</table>
{{movies}}
When I view the page in my browser, the ng-repeat doesn't work (nothing is printed out), but where I explicitly print out {{movies}} in the view, I see the movies json that I defined in the controller. So, I know that the view has access to the controller's scope.
Why isn't ng-repeat working even when the view has access to the controller's scope?
In order to use this.movies, you need to use controller AS syntax (MovieCtrl as ctrl). Even so, you may run into problems if you don't save controller object (e.g., vm = this)
The way you have it now, you need to assign to $scope instead of this
ng-repeat creates a new child scope, but your parent scope doesn't have the movies object, so you would have to inject $scope into the controller and specify it how it's normally done (e.g. $scope.movies = ... ) .
For this case when you want to use the "this" keyword, you just need to use "MoviesCtrl as moviesCtrl" in your markup and then use "moviesCtrl.movies" to get the object in your ng-repeat.
The reason of why {{movie}} seems to work is most likely because of the way interpolation works.

angular edit details using REST($resource) Service

I have a table with result set and there is edit option existing in each row. After clicking on the 'edit' link, i am able to populating the content on the fields.
But how do i edit the existing content using REST Service.
<table class="table-bordered table">
<thead>
<tr>
<th>id</th>
<th>Director</th>
<th>genre</th>
<th>releaseYear</th>
<th>title</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="result in results | orderBy:'_id'">
<td>{{result._id}}</td>
<td>{{result.director}}</td>
<td>{{result.genre}}</td>
<td>{{result.releaseYear}}</td>
<td>{{result.title}}</td>
<td>Edit | Delete</td>
</tr>
</tbody>
</table>
controller
$scope.saveContact = function(){
//save or edit contact
};
I have created in plunker.
content EDIT using REST API
I put it into work. You were close to the solution.
Here is the working plunker
What i did :
1 - I changed your way of getting the element for the form.
HTML
//I give the complete object instead of the id
ng-click="edit(result)"
Controller
//I prefer to pass the entire object
$scope.edit = function(result){
$scope.cineresultsFrm = angular.copy(result);
};
Service
I just removed the service. It wasn't useful anymore.
2 - I used the method of the ressource on your object
Controller
$scope.saveContact = function(){
//The function given to $update() is the .then() function
$scope.cineresultsFrm.$update(function(){
//If the update succeed i update the whole list. Could be done a better way but if you don't have any performance issues that will do the job.
$scope.getMovies();
});
};
I also changed the way you handle the "then" in the promise. But this is just a matter of taste.
$scope.getMovies = function(){
$scope.movieResults = movie.query(function() {
$scope.results = $scope.movieResults;
});
};
$scope.getMovies();
Hope it helped you

Dynamically create ng-table directive from another directive on same element

I'm trying to create wrapper directive for ng-table directive. My wrapper should instantiate ng-table directive on same element as the first directive is applied to and add some custom configuration to ng-table.
I am using following code to create ng-table directive.
angular.module('main')
.directive('mkTable', function($compile) {
return {
'link': function ($scope, element, attributes) {
element.removeAttr('mk-table'); // Must remove attribute because of recursion
element.attr('ng-table', 'tableParams');
$compile(element)($scope);
}
}
})
It does create ng-table (you can see it by pagination) but it doesn't display any data. If you check console output you can see that getData() function is called.
I presume that problem is in compiling child elements (tr, td) and bounding it to new ng-table scope, but I was not able to find the solution.
Demo: http://plnkr.co/1aEAdr2ugl39WG9Ay0vN
I think the problem is ng-repeat on tr element is being compiled couple of times, so I did a little naughty trick :) -insert "fake" to break Angular binding-
<tr fake-ng-repeat="user in $data">
<td data-title="'Name'">{fake{user.name}}</td>
<td data-title="'Age'">{fake{user.age}}</td>
</tr>
Then in the directive remove all "fake(s)" before recompiling:
element.html(element.html().replace(/fake-?/g, ''));
Demo.
Although it's working, I believe it's dirty trick.
After a lot of experimenting I figured it out. Solution is to use compile function insted of link.
angular.module('main')
.directive('mkTable', function($compile) {
return {
compile: function(element, attributes) {
element.removeAttr('mk-table');
element.attr('ng-table', 'tableParams');
var compileFn = $compile(element);
return function($scope, element, attributes) {
compileFn($scope);
}
}
}
})
Updated demo: http://plnkr.co/vL4kg0KVp4GYEDpOlIrm

ng-repeat ng-click when the click function has already been called earlier in the code

First off, I read the plethora of other questions and answers regarding ng-click, ng-repeat, and child and parent scopes (especially this excellent one.)
I think my problem is new.
I'm trying to call a function using ng-click within a table. The app allows for the sorting of Soundcloud likes. The problem is that when I try to call the ng click function using new data, it still tries to call the function using the old data. Let me explain better with the example:
Controller:
function TopListCtrl($scope, $http) {
$scope.sc_user = 'coolrivers';
$scope.getData = function(sc_user) {
var url = 'http://api.soundcloud.com/users/'+ $scope.sc_user +'/favorites.json?client_id=0553ef1b721e4783feda4f4fe6611d04&limit=200&linked_partitioning=1&callback=JSON_CALLBACK';
$http.jsonp(url).success(function(data) {
$scope.likes = data;
$scope.sortField = 'like.title';
$scope.reverse = true;
});
}
$scope.getData();
$scope.alertme = function(permalink) {
alert(permalink);
};
}
HTML
<div id="topelems">
<p id="nowsorting">Now sorting the Soundcloud likes of <input type=text ng-model="sc_user"><button class="btn-default" ng-click="getData(sc_user);">Sort</button></p>
<p id="search"><input ng-model="query" placeholder="Filter" type="text"/></p>
</div>
<table class="table table-hover table-striped">
<thead>
<tr>
<th>Song</th>
<th>Artist</th>
<th>Likes</th>
<th>Played</th>
<th>Tags</th>
</tr>
</thead>
<tr ng-repeat="like in likes.collection | filter:query | orderBy:sortField:reverse">
<td width="30%"><a href="{{ like.permalink_url }}">{{like.title}}</td>
(Trying to call it here) <td>{{like.user.username}}</td>
<td>{{like.favoritings_count}}</td>
<td>{{like.playback_count}}</td>
<td>{{like.tag_list}}</td>
</tr>
</table>
</div>
</body>
</html>
So I have that function getData. I use it to load the data in using the soundcloud api. I have a form at the top that uses getData to load a new user's Soundcloud data in. I also call the getData function in the controller so that there is an example on the page upon loading.
The problem is when I try to load a new user's data from a <td> I want to be able to click on the user to see and sort their likes.
How do I 'clear' the function or the global namespace (am I even refering to the right thing)? How can I reuse the getData function with a new variable?
Working Jsfiddle for this
In your getData function you have this line:
var url = 'http://api.soundcloud.com/users/'+ $scope.sc_user +'/favorites.json...
but you are passing in the variable sc_user to your getData function and should be using it like this (no $scope):
var url = 'http://api.soundcloud.com/users/'+ sc_user +'/favorites.json...
That being said... your initial data load fails because you are calling:
$scope.getData();
and not:
$scope.getData($scope.sc_user);

Apply an Angular directive only after data is loaded

I have a directive applied to a table, the outer div has a controller applied to it:
<div ng-controller="MyController">
<table ng-mydirective>
...
</table>
</div>
The controller loads some data and then uses ng-repeat to create some rows/columns in the table.
This is fine so far.
The directive needs to access columns possibly generated by the data, but the directive runs before the controller.
Is it possible to run a directive after the data is loaded/rendered? Or is the only way to achieve this by using $watch on the dom of the table?
<table ng-show="myDataSource.length>0" ng-mydirective>
...
</table>
or how about applying that within your directive's template itself, i.e hide the table until after the data source is populated. But in both cases the problem is that what if you display a table whose query returned 0 results? That means you directive won't show at all. Either way the best way to handle this would be inside the directive. I.e. hide the table unless your $http call executed successfully.
If your directive isn't using an isolate scope, it will automatically receive access to the data member in the controller scope. If you reference that data member in the template, Angular automatically sets up a $watch on it and you don't have to do anything else; otherwise you will need to add your own $watch. If you're using an isolate scope then it's a little more complicated but a similar pattern holds.
You have two ways for doing this
Easy one is using ng-if for requested data field. For example
<table ng-mydirective ng-if="nameOfTheDataSource !== undefined">
...
</table>
Also you can send an attribute to the directive and watch it until data arrives. This is more complicated but seemless to the template level:
Template
<table ng-mydirective ng-model="nameOfTheDataSource">
...
</table>
Directive
angular.module(....)
.directive('ng-mydirective', function() {
return{
scope: {
....
ngModel: '='
}
link: function(scope, element, attrs) {
var firstload = scope.$watch('ngModel', function(value) {
if (scope.ngModel !== undefined) {
// Do what is needed
}
// Deregister itself
firstload();
}
}
}
}

Resources