How to create a restricted directive? - javascript

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

Related

Dynamically add AngularJS Directive to page to display and render the directive not working

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.

$watch() isn't updating the $scope

I have the following controller, and when I call $scope.remove() it makes a request to the usercart, which makes a request to the api. The api returns json object which has an object with an array of cart items.
The html on the page uses an ng-repeat to loop through the items, but the page isn't updating for some reason, and I can not figure out why.
// Main controller
app.controller('Checkout', function($scope, usercart){
$scope.cart = [];
$scope.$watch(function(){
return usercart.cart;
}, function(newVal, oldVal){
if(newVal !== oldVal){
$scope.cart = newVal;
}
}, true);
$scope.remove = function(domain){
usercart.remove(domain);
};
});
This service makes a request to the api and saves the cart data.
// User cart service
app.service('usercart', function(cart){
this.remove = function(domain){
// cart is an api service to make http requests
cart.removeDomain(domain).success(function(data){
this.cart = data.cart;
});
};
});
Here is a json response example:
{
"message":"Success",
"cart":[{
"domain":"asdfsadfsadf.org",
"years":2,
"amount":9
},{
"domain":"asdsmembers.cc",
"years":2,
"amount":24.95
},{
"domain":"asdsmembers.tv",
"years":2,
"amount":39.95
}]
}
Here is the html:
<tr ng-repeat="i in cart">
<td data-th="Product">
{{i.domain}}
</td>
<td data-th="Price">${{i.amount|number:2}}</td>
<td data-th="Quantity">
<select ng-model="i.years" ng-options="y.value as y.name for y in selYears" ng-disable="isInCart(i.domain)" ng-class="{disabled: isInCart(i.domain)}" ng-change="update(i.domain, 'years', i.years)"></select>
</td>
<td class="actions" data-th="" align="center">
<button class="btn btn-default btn-sm" style="background: #333;" ng-click="remove(i.domain)"><span class="glyphicon glyphicon-remove-circle" aria-hidden="true" style="color:#fff;"></span></button>
</td>
<td data-th="Subtotal" class="text-center">${{i.years * i.amount|number:2}}</td>
</tr>
Also when the page loads the table displays fine. It is just when I run the remove function.
I haven't tried but i believe here is the problem
cart.removeDomain(domain).success(function(data){
this.cart = data.cart;
});
Since this is a pointer to the caller of the callback function you will create cart property onto your cart api service. In order to circumvent this issue you should create variable called (by convention) self and assign this to it (at the begining of the usercart service):
var self = this;
after that, change your code into this:
cart.removeDomain(domain).success(function(data){
self.cart = data.cart;
});
To get better understanding you can go through this post
Watching your local $scope for a value you change in your singleton usercart definitely wouldn't work, unless you explicitely passed in that local scope. We can simplify this by ridding the $watch and resolving a promise we can return from our service instead. This allows for generic re-use and alleviates watchers from polluting our controllers. Observe the following changes...
app.service('usercart', function(cart) {
this.remove = function(domain) {
return cart.removeDomain(domain) // return promise
};
});
app.controller('Checkout', function($scope, usercart) {
$scope.remove = function(domain) {
usercart.remove(domain).then(function(data) { // resolve promise
$scope.cart = data.cart;
});
};
});

AngularJS and XML, how to render it?

I am working along DB guys, they are sending me the data thru XML, and depending the kind of element they specify is what I need to display in the view.
The code you will see is a dynamic table
<table>
<thead>
<tr>
<th ng-repeat="column in cols">
<span>{{column}}</span>
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in cols"
ng-init="isXX = column.indexOf('XX') === 0">
<span ng-if="!isXX">{{row[column]}}</span>
<button ng-if="isXX" class="btn btn-xs btn-blue"
ng-click="fillOpen()">
{{column.substring(3).replace('_', ' ')}}
</button>
</td>
</tr>
</tbody>
</table>
and here is what I have in the controller
ReportsFactory.pendingBets(reportParam).then(function(data) {
if (data.length) {
gridInfo = _.forEach(data, function(item) {return item;});
$scope.rows = gridInfo;
$scope.cols = Object.keys($scope.rows[0]);
}
}
as you can see here I have this ng-init
ng-init="isXX = column.indexOf('XX') === 0" where I am telling the app, if the property I am receiving comes with XX at the index, then display a button <button ng-if="isXX" ng-click="fillOpen()">...</button> but so far, I have some more props coming with XX at the beginning, so I need to do it more dynamic.
This is how my view looks so far
what I need to know, is how to read that XML, this is the XML printed in the Nodejs terminal
[{ BET: 57635034,
CUSTOMER: 181645,
SPORT: 'NFL',
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>',
XX_VIEW: '<element><element_type>BASIC_DROPDOWN</element_type><element_call>callThisFunction()</element_call><element_content><li>1</li><li>2</li><li>3</li><li>4</li></element_content></element>',
XX_CANCEL: '<element><element_type>BASIC_CHECKBOX</element_type><element_call/><element_content>1</element_content></element>'
}]
so, the first says
'XX_FILL OPEN': '<element><element_type>WAGER_ACTION_BUTTON</element_type><element_call>fillOpen(57635034)</element_call><element_content/></element>'
WAGER_ACTION_BUTTON should be a button
the second one says
BASIC_DROPDOWN that should be a dropdown and so on, so, how should I do in order to display the proper HTML element depending on what the XML says ?
Any suggestions ?
if I understood you correctly you want to dynamically render the xml or html content to your view... I assume that element and element type are directive you have or something.
use
ngBindHtml
e.g:
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="question.question.body"></span>
</div>
or you might need to use the trustAsHtml function
<div class="col-xs-offset-1 m-r-offset-8 p-t-offset-2 font-l-16">
<span mathjax-bind ng-bind-html="trustAsHtml(question.question.body)"></span>
</div>
$scope.trustAsHtml = function (val) {
return $sce.trustAsHtml(val);
};
this will take your string xml (html) code and render it...
you could always build a personalize directive and use $compile as well like:
app.directive('ngHtmlCompile',function ($compile) {
return function(scope, element, attrs) {
scope.$watch(
function(scope) {
// watch the 'compile' expression for changes
return scope.$eval(attrs.ngHtmlCompile);
},
function(value) {
// when the 'compile' expression changes
// assign it into the current DOM
element.html(value);
// compile the new DOM and link it to the current
// scope.
// NOTE: we only compile .childNodes so that
// we don't get into infinite loop compiling ourselves
$compile(element.contents())(scope);
}
);
};
});
and in the code just call the ng-html-compile... no need for $sce

Dynamic ng-click does not call function

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.

Where should I write general purpose controller function in angular.js?

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.

Categories

Resources