I wish to make a dynamic table in AngularJS, but the problem is ng-click does not call the function.
Here is the fiddle : fiddle
Here is the code :
General template :
<div class="box">
<dynamic-table></dynamic-table>
</div>
Directive template :
<table class="table table-striped">
<thead>
<tr>
<th ng-repeat="column in columns" ng-bind="column.label"></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="content in data">
<td ng-repeat="column in columns">
<!-- Problem is here (column[function] displays 'displayCategory') -->
<a href ng-click="column[function]()">{{ content[column.field] }}</a>
</td>
</tr>
</tbody>
</table>
Directive code :
app.directive('dynamicTable', function () {
return {
restrict: 'E',
templateUrl:'/template/Directive/dynamic-table.html',
scope: true,
link: ['$scope', function($scope) {
$scope.updateCategory = function() {
console.log("WOW");
};
}]
};
});
When I display : column[function], it shows updateCategory. I don't understand why when I click on it, the function is not launched...
Have you got an idea ?
That's because column[function] returns a string, not a reference to the function itself. You should call the function directly, like:
<td ng-repeat="column in columns">
<!-- Problem is here (column[function] displays 'displayCategory') -->
<a href ng-click="updateCategory (column)">{{ column.field }}</a>
</td>
and inside the directive to have something like:
controller: ['$scope', function($scope) {
$scope.updateCategory = function(columnData) {
console.log(columnData.field);
};
}]
Check demo: JSFiddle.
First of all, you link function declaration is not correct:
link: ['$scope', function($scope) {
$scope.updateCategory = function() {
console.log("WOW");
};
}]
It is the format of controller function. Change it to:
link: function($scope) { ... }
Angular will do the injection for you.
Secondly, specify a dispatcher function on the scope. Inside the dispatcher, determine which function to call:
$scope.dispatcher = function (column) {
var fn = column.function;
fn && angular.isFunction($scope[fn]) && $scope[fn]();
};
And specify ng-click="dispatcher(column)" in the HTML.
Please see this fiddle as maybe it will suit your needs.
http://jsfiddle.net/tep78g6w/45/
link:function(scope, element, attrs) {
scope.updateCategory = function() {
console.log("WOW");
};
scope.doSomething = function(func) {
var test = scope.$eval(func);
if(test)
test();
}
}
}
Also, link function has parameters that are sent to it, this is not a place to use DI. Please see in the fiddle the correct approach. As far as the dynamically calling the function, I went with different approach and it works. The approach you took is not going to work because you need a way for the string to be a function, it needs to have a reference to a function.
Related
I am attempting to follow a JSFiddle, where a user can click on a <td> item, edit it, then eventually be able to save the changes.
The example uses ng-repeat and all others I have looked at do to where as I am not, I am using data passed from a resolve command in my route folder.
$stateProvider
.state('app.patents.patent', {
url: '/{patentId}',
component: 'patent',
resolve: {
patent: ['patents', '$stateParams', function(patents, $stateParams) {
return patents.find(function(patent){
return patent.id == $stateParams.patentId;
})
}]
}
})
}]);
I have attempted to use data-id (looked at How to retrieve the clicked ElementId in angularjs?), but with no success, as I assume you cannot use the same id twice and my desired functionality requires two elements that ng-show and ng-hide depending on the boolean value passed to them.
I have now got myself in a confused state, not sure which approach to take.
Question
How do I adapt my code that doesn't use ng-repeat to work with this JSFiddle? OR do you know another apporach I can take to achieve the same results?
<tr>
<th class="text-xs-right">Short Name</th>
<td>
<span data-id="123" ng-hide="$ctrl.shortTitle.editing" ng-dblclick="$ctrl.editItem(123)">{{$ctrl.patent.shortTitle}}</span>
<input type="text" data-id="123" ng-show="$ctrl.shortTitles.editing" ng-blur="$ctrl.doneEditing(123)" ng-model="$ctrl.patent.shortTitle"></input>
</td>
</tr>
angular.module('myApp').component('patent', {
templateUrl: 'p3sweb/app/components/patents/views/patent-item.htm',
controller: function() {
var vm = this;
vm.editItem = function (item) {
item.editing = true;
}
vm.doneEditing = function (item) {
item.editing = false;
};
});
As per my understanding regarding your question I have created a jsfiddle, have a look or you can create a jsfiddle with the issue you are facing for better understanding
JSFiddle
<!DOCTYPE html>
<div ng-app ng-controller="myCtrl" class="container">Double-click on the items below to edit:
<button type="button" ng-click="newItem()">Add item</button>
<table>
<tr ng-repeat="item in items">
<td>
<span ng-hide="item.editing" ng-dblclick="editItem(item)">{{item.name}}</span>
<input ng-show="item.editing" ng-model="item.name" ng-blur="doneEditing(item)" autofocus />
</td>
</tr>
</table>
</div>
You can create an array and connect each input to a specific index starting from 0 and then pass that index to your function call.
<tr>
<th class="text-xs-right">Short Name</th>
<td>
<span ng-hide="$ctrl.editing[1]" ng-dblclick="$ctrl.editItem(1)">{{$ctrl.patent.shortTitle}}</span>
<input type="text" data-id="123" ng-show="$ctrl.editing[1]" ng-blur="$ctrl.doneEditing(1)" ng-model="$ctrl.patent.shortTitle"></input>
</td>
</tr>
angular.module('myApp').component('patent', {
templateUrl: 'p3sweb/app/components/patents/views/patent-item.htm',
controller: function() {
var vm = this;
vm.editing=[];
vm.editItem = function (index) {
vm.editing[index] = true;
}
vm.doneEditing = function (index) {
vm.editing[index] = false;
};
});
Demo: http://jsfiddle.net/F7K63/381/
If I type onto page
<liberty-directive></liberty-directive>
That works fine.
However, I have database table with list of directive names.
So
<table>
<tr ng-repeat="lib in vm.liberty">
<td>{{lib.Tag}}</td>
</tr>
</table>
So this is the object with the directive tag
{{lib.Tag}} = <td class="ng-binding"><liberty-directive></liberty-directive></td>
Viewing source it "looks fine" , but copy and paste to this it is changing, how to prevent that?
To get it work, you have to compile your html in each iteration of ng-repeat (use $compile). For that, you can use a simple custom directive: (PLUNKER DEMO)
.directive('compileHtml', ['$compile', function($compile) {
return function(scope, element, attrs) {
element.html(attrs.compileHtml);
$compile(element.contents())(scope);
};
}]);
Then in your HTML use it like:
<tr ng-repeat="d in vm.data">
<td compile-html="{{d.htmlContent}}"></td>
</tr>
Controller:
function myCtrl() {
var vm = this;
vm.data = [
{ "htmlContent": "<my-custom-directive></my-custom-directive>" },
{ "htmlContent": "<div>Custom: <span my-attr-directive></span></div>" },
//... more items
];
}
Check this post if you want more examples.
I have a custom directive to format datetime. I use it in multiple views and it seems to work across the board, except for one instance. When request.created is passed to it (HTML further below), the first console.log(scope) indicates "date" is correctly set and passed to the directive. But console.log(scope.date) prints empty.
I all works fine other views and several "email.sendDate"s in the same view.
The value of request is retrieved from server and set by controller.
myApp.directive('friendlydate', function () {
function link(scope, element, attrs) {
console.log(scope);
console.log(scope.date);
var uglydate = scope.date;
//do stuff
scope.formatteddate = prettydate;
};
return {
restrict: 'E',
scope: {
date: '#'
},
link: link,
template: '{{formatteddate}}'
};
});
In my html, I have
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Created</dt>
<dd>
<friendlydate date="{{request.created}}"></friendlydate>
</dd>
<dt>IP Address</dt>
<dd>{{request.ipAddress}}</dd>
<dt>Browser</dt>
<dd>{{request.browser}}</dd>
</dl>
</div>
<table>
<tbody ng-repeat="email in request.emails">
<tr>
<td>
<friendlydate date="{{email.sendDate}}"></friendlydate>
</td>
<td>{{email.subject}}</td>
<td>{{emailStatus(email.status)}}</td>
<td><button class="btn btn-primary">view</button></td>
</tr>
</tbody>
</table>
Let me know if more info is required. I am on the verge of going nuts, please help.
Your data is retreived from server and set by controller.There might be a delay in your response If that is the case my solution works for you.
use ng-if in your view :
<friendlydate date="{{request.created}}" data-ng-if="request.created"></friendlydate>
Or you can use scope.$watch in your directive :
myApp.directive('friendlydate', function () {
function link(scope, element, attrs) {
scope.$watch(function () {
console.log(scope);
console.log(scope.date);
var uglydate = scope.date;
//do stuff
scope.formatteddate = prettydate;
});
};
return {
restrict: 'E',
scope: {
date: '#'
},
link: link,
template: '{{formatteddate}}'
};
});
I recommend you a different approach it will be more clean for the views and easy to maintain.
User a filter
angular.module('myApp.filters', [])
.filter('frieldyDate', function($moment, $translate) {
return function(value, format) {
if(format){
// you can pass parameters and return custom formats
// return formatWithFormat(value,format);
}
return friendlyFormat(value);
};
});
And your template will be
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>Created</dt>
<dd> {{request.created | friendlyDate: 'MM/YYY'}} </dd>
<dt>IP Address</dt>
<dd>{{request.ipAddress}}</dd>
<dt>Browser</dt>
<dd>{{request.browser}}</dd>
</dl>
</div>
<table>
<tbody ng-repeat="email in request.emails">
<tr>
<td>{{email.sendDate | friendlyDate}} </td>
<td>{{email.subject}}</td>
<td>{{emailStatus(email.status)}}</td>
<td><button class="btn btn-primary">view</button></td>
</tr>
</tbody>
</table>
Also y recommend you to use angular-moment for different date's format
I am writing some functions for check/uncheck all for table list and it is working fine,
Controller is,
invoiceApp.controller('itemController', ['$scope', 'itemService', '$route', function ($scope, itemService, $route) {
$scope.checkAllItem;
$scope.listItem = {
selected: []
};
$scope.checkUncheck = function () {
if ($scope.checkAllItem) {
$scope.listItem.selected = $scope.items.map(function (item) {
return item.id;
});
} else {
$scope.listItem.selected = [];
}
};
HTML TABLE,
<table id="dt_basic" class="table table-bordered table-hover" width="100%">
<thead>
<tr>
<th class="text-center" width="5%">
<input type="checkbox" name="checkbox-inline" ng-model="checkAllItem" ng-click="checkUncheck()">
<input type="checkbox" name="checkbox-inline" ng-click="uncheckAll()">
</th>
<th width="15%" ng-click="sort()">Name<i class="fa fa-sort small"></i></th>
<th width="65%">Description</th>
<th width="5%">Unit</th>
<th width="10%">Rate</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in items" data-toggle="modal" data-target="#itemModel" ng-click="getItem(item.id)" style="cursor: pointer">
<td class="text-center">
<input type="checkbox" checklist-model="listItem.selected" checklist-value="item.id">
</td>
<td><a>{{item.name}}</a></td>
<td>{{item.description}}</td>
<td>{{item.unit}}</td>
<td>{{item.rate}}</td>
</tr>
</tbody>
</table>
It is working fine,Here my problem is,In my project I have many tables in different pages,I have to copy past this same code (Talking about Controller only ) to everywhere.Is there any method to write it generally?
I tried with $routescope,
but It is not working with ng-model,Is there any method to implement the same?
You could turn it into a service then inject the service to whichever controller needs it. You can now also include other commonly used functions used to manipulate data in those. See,
http://jsbin.com/madapaqoso/1/edit
app.factory("toolService", function(){
return {
checkUncheck: function(listItem) {
listItem.selected = [];
}
}
});
I didn't add the added complexity of your function, but you get the idea.
Alternatively, use a directive. I show it in the jsbin as well. Though, I'd prefer a service since services are made for managing data and directives are more concerned with DOM editing and binding $watchers/events etc. Or perhaps you could persist the data with a service, then use a custom directive to handle all the clicks on the table.
I have written a custom directive
invoiceApp.directive('checkUncheck', function () {
return {
restrict: 'E',
replace: true,
template: '<input type="checkbox" name="checkbox-inline" ng-model="checkAllItem" ng-click="checkUncheck()">',
link: function (scope) {
//check/uncheck and delete
scope.checkAllItem;
scope.listItem = {
selected: []
};
scope.checkUncheck = function () {
if (scope.checkAllItem) {
scope.listItem.selected = scope.items.map(function (item) {
return item.id;
});
} else {
scope.listItem.selected = [];
}
};
}
};
});
In HTML,
<check-uncheck></check-uncheck>
Now I can share checkUncheck function with most of table view in my project.
I'm experimenting with AngularJS and NG-Table and cannot solve the following:
I'm displaying a collection of User objects from a Django app in an NG-Table. One of the properties of the model is a boolean indicating whether the object is active. In stead of displating true/false, I want to display a glyph from the Font Awesome set using an AngularJS directive.
From various samples I've got the following.
The module:
var main = angular.module("main", ["ngTable"]);
Retrieval of the objects to be displayed in the table:
main.factory('User', function ($http) {
return {
async: function() {
var promise = $http.get('api/v1/users').then(function (response) {
return response.data["objects"];
});
// Return the promise to the controller
return promise;
}
};
});
The controller and the directive to transform the boolean to the glyph:
main.controller("UsersCtrl", function ($scope, $filter, ngTableParams, User) {
User.async().then(function(data) {
$scope.tableParams = new ngTableParams({
page: 1,
count: 4,
sorting: {
name: 'asc'
}
},{
total: data.length, // length of data
getData: function ($defer, params) {
// use build-in angular filter
var orderedData = params.sorting() ? $filter('orderBy')(data, params.orderBy()) : data;
$defer.resolve(orderedData.slice((params.page() - 1) * params.count(), params.page() * params.count()));
}
});
});
}).directive('boolean', function () {
return {
restrict: 'E',
link: function (scope, elem, attrs) {
var userObject = scope.userObject;
if (userObject["active"]) {
console.log("active");
console.log(elem);
elem.html = "<i class='fa fa-check text-success fa-lg'></i>";
} else {
console.log("not active");
console.log(elem);
elem.html = "<i class='fa fa-times text-danger fa-lg'></i>";
}
}
}
});
Then in my HTML template:
<table ng-table="tableParams" class="table">
<tr ng-repeat="propertyObject in $data">
<td data-title="'Name'" sortable="'name'">
[[ userObject.name ]]
</td>
<td>
<boolean>[[ userObject.active ]]</boolean>
</td>
</tr>
</table>
Due to collision with Django template conventions I had to change the Angular's default double curly brackets to square brackets.
The table displays ok, but for my boolean directive which fails to display a glyph and still just shows true or false. By logging to the console I can inspect the actual objects and they appear correct. I'm obviously missing something but would appreciate any help as to what...
You are running into issue where you need to let ng-repeat complete it's digest before trying to manipulate the element html. There are several ways to do it, using attrs.$observe or $timeout.
Baasically what is happening is your code is firing before the element is rendered
For no more than what you are doing you could simply use ng-class and you won't need a directive
<table ng-table="tableParams" class="table">
<tr ng-repeat="propertyObject in $data">
<td data-title="'Name'" sortable="'name'">
[[ userObject.name ]]
</td>
<td>
<i class='fa fa-times fa-lg'
ng-class="{'text-danger':!userObject.active,'text-success':userObject.active}">
</i>
</td>
</tr>
</table>
Or you could really simplify the directive to only return the <i> as template using the ng-class