I'm trying to write a generic table directive like this:
<h-table rows="customers">
<h-column field="Id">
<a ng-click="editCustomer(row.Id)">{{row.Id}}</a>
</h-column>
<h-column field="Name">
</h-column>
</h-table>
That will generate the following html:
<table>
<tr>
<th>Id</th>
<th>Name</th>
</tr>
<tr>
<td>
<a ng-click="editCustomer(1)">1</a>
</td>
<td>
Alexandre
</td>
</tr>
...
</table>
My h-table template is something like:
<script type="text/ng-template" id="hTableTemplate.html">
<div>
<div ng-transclude id="limbo" style="display: none"></div>
<table>
<tr>
<th ng-repeat="col in cols">{{col.field}}<th>
</tr>
<tr ng-repeat="row in rows">
<td ng-repeat="col in cols">
// Here I need to put the content of h-column directive
// if it exists, or just bind the value for the column
<span ng-bind-html="getContentOrValueFor(row, col)" />
</td>
</tr>
<table>
</div>
</script>
So I need to create two directives: h-table and h-column. The h-table directive uses a directive controller, that will be used by both directives.
The h-column directive will use this controller to add cols to the table and get value of a row/col.
So far, this is my directive's controller:
.controller("hTable", function ($scope, $element, $attrs, $compile) {
$scope.cols = [];
this.addCol = function (col) {
$scope.cols.push(col);
};
$scope.getContentOrValueFor = function (row, col) {
// MY PROBLEM IS HERE! I will explain below ...
return col.content && col.content.html() ? col.content.html() : row[col.field];
};
})
My h-column directive receives h-table's controller.
It uses transclude to get it content and save this content inside col object, to bind it after:
.directive("hColumn", function () {
return {
restrict: "E",
require: "^hTable",
transclude: true,
scope: {
field: "#",
},
link: function(scope, element, attrs, hTableController, transclude) {
var col = {};
col.field = scope.field;
col.content = transclude(); // <-- Here I save h-column content to bind after
hTableController.addCol(col);
...
}
};
})
And finally :) my h-table directive:
.directive("hTable", function () {
return {
restrict: "E",
scope : {
rows: "="
},
controller: "hTable",
require: "hTable",
replace: true,
transclude: true,
templateUrl: "hTableTemplate.html",
link: function(scope, element, attrs, hTableController) {
...
}
};
})
I need to put h-column's content inside the td tag. So, I call getContentOrValueFor function to get this content that was inside h-column's directive.
If there is no content, so I bind with the value for the field.
It works normally if the h-column's content is something like {{1+2+3}} (it will show me 6, that's ok).
But if this content is an html tag like:
test
I get the error "html.indexOf is not a function"
How can I achieve this??
Thats caused by not including ngSanatize i think. See : https://docs.angularjs.org/api/ng/directive/ngBindHtml
Related
I have one table. in that i have declared my custom directive
<table ng-show="dataset.length" ng-table="tableParams" class="table">
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="server in $data" ng-class-odd="'odd-row'" ng-class-even="'even-row'">
<td width="30" class="text-center">
<i class="ion-plus-round **toggle-icon**" group-row></i>
</td>
</tr>
</tbody>
</table>
While clicking on toggle-icon class
i need to generate one more tr data in next row.
custom directive is
app.directive('groupRow', function(){
return {
restrict: 'EA',
transclude: true,
controller: 'groupRowDirCtrl',
templateUrl: 'views/directives/templates/group-row.html',
link: function( scope, element, attrs, groupRowDirCtrl ) {
element.bind('click', function() {
$compile(el)(scope);
element.parent().parent().after(el);
});
}
};
})
.controller('scrollableTableviewDirCtrl',
function($scope) {
});
data have to fetch it from html page and append into next row.
How to do this?
if i understand you correctly, the on click function is in the directive.
so, i would add a service for the $data array that is being repeated, and on click add another item to that array by using the service.
like so
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, dataService) {
dataService.setData([1, 2, 3]);
$scope.data = dataService.getData();
});
app.directive('dir', function(dataService) {
return {
restrict: 'E',
replace: true,
template: '<tr><td ng-click="addMore()">One More</td></tr>',
link: function($scope, elem, attrs) {
$scope.addMore = function() {
dataService.addRow();
}
}
}
});
app.service('dataService', function() {
var _data = [];
var _service = {};
var _cb;
_service.getData = function() {
return _data;
}
_service.setData = function(data) {
_data = data;
}
_service.onUpdate = function(cb) {
_cb = cb;
}
_service.addRow = function( /* attibutes here */ ) {
_data.push({});
if (angular.isFunction(_cb)) _cb();
}
return _service;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myCtrl">
<table>
<tbody>
<dir ng-repeat="d in data"></dir>
</tbody>
</table>
</div>
</div>
So I am trying to make a directive that is encapsulated inside a Bootstrap popover. Almost everything is working - I have event listeners to know when buttons get clicked, links, everything is hunkey-dory - except for the fact that even though I am using ng-model to bind to the input in the innermost directive, nothing is getting updated. It's almost as if it's doing a one-way binding instead of a two-way. FieldValue on the model is always null (its initial state), and is never altered (even though it should be equal to whatever was put in the input field). I have looked through a ton of answers and nothing seems to address the issue.
Here is my code:
Page HTML
<table class="table table-bordered table-striped">
<tbody>
<tr ng-repeat="field in model.CustomFields">
<td>{{field.Title}}</td>
<td>
<a popover field-description="field.CustomField.Description" model-value="field.FieldValue" class="trigger">
<text-custom-field text-model="field"></text-custom-field>{{field.FieldValue || 'Empty'}}
</a>
</td>
</tr>
</tbody>
</table>
Popover Directive
(function() {
'use strict';
angular
.module('app.directives')
.directive('popover', popover);
popover.$inject = ['$compile'];
function popover($compile) {
var directive = {
link: link,
restrict: 'A',
scope: {
fieldDescription: '=',
modelValue: '='
}
};
return directive;
function link(scope, element, attrs) {
$(element).popover({
html: true,
placement: 'top',
title: scope.fieldDescription,
content: function () {
return $(this).parent().find('.js-popover-content').html() + '<button ng-click="submit()" type="submit" class="btn btn-primary btn-sm" style="margin-right: 5px;"><i class="glyphicon glyphicon-ok"></i></button><button type="button" class="btn btn-default btn-sm edit-cancel" ng-click="closePop()" style="margin-right: 5px;"><i class="glyphicon glyphicon-remove"></i></button>';
}
});
scope.closePop = function () {
console.log('close');
$('.trigger').popover('hide');
};
scope.submit = function () {
console.log('submit');
scope.$parent.$broadcast('submit');
};
scope.$parent.submitData = function (value) {
scope.modelValue = value;
console.log('submitted data');
scope.closePop();
};
$(element).on('click', function (e) {
$('.trigger').not(this).popover('hide');
$compile($('.popover.in').contents())(scope);
});
}
}
})();
Inner Directive
(function() {
'use strict';
angular
.module('app.directives')
.directive('textCustomField', textCustomField);
function textCustomField() {
var directive = {
templateUrl: 'url/templates/TextCustomField.html',
restrict: 'E',
transclude: true,
link: link,
scope: {
textModel: '='
}
};
return directive;
function link(scope, element, attrs) {
scope.$parent.$on('submit', function (event) {
console.log('heard submit event');
var value = scope.textModel.FieldValue;
console.log(value);
scope.$parent.submitData(value);
});
}
}
})();
Directive HTML
<div class="js-popover-content hide">
<div class="form-group" style="display: inline-flex;">
<input type="text" class="form-control" ng-model="textModel.FieldValue" style="width: {{textModel.CustomField.DisplaySize}}; margin-right: 5px;">
</div>
</div>
Does anyone have any ideas why this isn't working?
When I call a function, that provides a block of HTML, from within an ng-repeat, it is rendering as text, but I would like this to render the HTML. How should I approach this? :
<tr ng-repeat="person in data.people">
<td class="text-left">
{{getInitials(person.Firstname, person.Surname, person.IconColor )}}
</td>
</tr>
I have a function in a service which returns a block of HTML:
angular.module('myapp.services.global', [])
.factory('helperFunctions', function () {
return {
getInitials: function (firstname, lastname, iconColor) {
return "<div style='background-color:" + iconColor + "' class='userIconMedium'>" + firstname.charAt(0) + " " + lastname.charAt(0) + "</div>";
}
}
});
I mentioned that this is a perfect place for a directive however I realize that directives can be difficult to understand at first glance. Directives are used to link in with the DOM. Rendering HTML within Angular should usually be limited to directives.
You would modify your HTML to have the directive.
<tr ng-repeat="person in data.people">
<td class="text-left">
<div myapp-initials="person" ></div>
</td>
</tr>
Your angular module would no longer contain a factory definition but a directive definition.
angular.module('myapp.directives', [])
.directive('myappInitials', function () {
return {
restrict: 'A',
template: "<div style='background-color:{{myappInitials.IconColor}}' class='userIconMedium'>{{myappInitials.Firstname.charAt(0) + ' ' + myappInitials.Surname.charAt(0)}}</div>",
scope: {
myappInitials: "="
}
};
});
I have created a plunker to demonstrate how to use a directive in your situation.
Further to this, the above accepted answer is not quite correct. IE (including 11) does not support interpolation in style attributes. You must use ngStyle for that, e.g
ng-style="{'background-color': myAppInitials.IconColor}"
https://docs.angularjs.org/guide/ie
This is my working solution based on the directive kindly provided by #Joel above, though I'd prefer to include the ng-style element within the template of the directive but I am not yet sure whether this is possible:
<tr ng-repeat="person in data.people">
<td class="text-left">
<div ng-style="{'background-color':person.IconColor}" class="userIconMedium" myapp-initials="person"></div>
</td>
</tr>
The directive:
angular.module('myapp.directives', [])
.directive('myappInitials', function () {
return {
restrict: 'A',
template: "{{myappInitials.Firstname.charAt(0) + ' ' + myappInitials.Surname.charAt(0)}}",
scope: {
myappInitials: "="
}
};
});
I have a directive that is working fine in Chrome, but in IE9 it renders '{{myappInitials.IconColor}' into the HTML:
<tr ng-repeat="person in data.people">
<td class="text-left">
<div myapp-initials="person" ></div>
</td>
</tr>
The directive:
angular.module('myapp.directives', [])
.directive('myappInitials', function () {
return {
restrict: 'A',
template: "<div style='background-color:{{myappInitials.IconColor}}' class='userIconMedium'>{{myappInitials.Firstname.charAt(0) + ' ' + myappInitials.Surname.charAt(0)}}</div>",
scope: {
myappInitials: "="
}
};
});
There is a Plunker here to check.
Is this an Angular bug?
IE (including 11) does not support interpolation in style attributes. You must use ngStyle for that, e.g
ng-style="{'background-color': myAppInitials.IconColor}"
https://docs.angularjs.org/guide/ie
This is my working solution, though I'd prefer to include the ng-style element within the template of the directive but I am not yet sure whether this is possible:
<tr ng-repeat="person in data.people">
<td class="text-left">
<div ng-style="{'background-color':person.IconColor}" class="userIconMedium" myapp-initials="person"></div>
</td>
</tr>
The directive:
angular.module('myapp.directives', [])
.directive('myappInitials', function () {
return {
restrict: 'A',
template: "{{myappInitials.Firstname.charAt(0) + ' ' + myappInitials.Surname.charAt(0)}}",
scope: {
myappInitials: "="
}
};
});
i made a directive for filtering data. It uses scope: true and no transclusion. Somehow it only works inside an ng-switch. Maybe also other directives but i havent tried.
My html looks like this:
<my-filter source="{{foodArray}}" filter="reaction,category"> // source and filter are properties on the directive scope
<div>
<div class="span3">
<table class="table table-bordered">
<tr data-ng-repeat="item in filters.reaction"> // filters property of directive scope
<td><input type="checkbox" data-ng-model="item.isChecked" data-ng-change="filterCtrl.filterList()"></td>
<td>{{ item.value }}</td>
</tr>
</table>
</div>
</div>
<table>
<tbody>
<tr data-ng-repeat="food in filteredData"> // filteredData object on the directive scope
// interating through filteredData
</tr>
</tbody>
</table>
</my-filter>
here is my directive and its controller:
angular.module('myModule', [])
.directive('myFilter', function() {
return {
restrict: 'E',
scope: true,
controller: ["$scope", function ($scope) {
var filterCtrl = this;
$scope.filters = {};
filterCtrl.inputArray = [];
filterCtrl.filterList = function() {
/* some code where tmp array is created */
$scope.filteredData = tmp;
}
}],
controllerAs: 'filterCtrl',
link: function(scope, element, attrs, filterCtrl) {
filterCtrl.inputArray = angular.fromJson(attrs.source);
scope.filteredData = filterCtrl.inputArray;
// ...
}
}
});
FilteredData and filters are property of the directives scope. Now when i remove the ng switch around it the data is empty. Also the scope.source property can be an array or an object. When i remove the ng switch and give it an object as source it actually throws a syntax error: SyntaxError: Unexpected end of input
at Object.parse (native)
at Object.fromJson
Which it doesnt throw when i use an array.
Not sure what to make of this. If anybody had this problem before i would love to hear from you.