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: "="
}
};
});
Related
This question follows on a previous question I had about getting these directives to work:
Previous question about dynamically generating a grid
My Html looks like this:
<div ng-grid ng-collection="entries" ng-collection-headings="headings" ng-button-click="theAction(inp)">
<div ng-checkbox-column></div>
</div>
It generates Html that looks like this:
<div ng-grid="" ng-collection="entries" ng-collection-headings="headings" ng-button-click="theAction(inp)" class="ng-isolate-scope">
<table class="table table-bordered">
<thead>
//Truncated for brevity
</thead>
<tbody>
<tr id="item0" ng-repeat="item in ngCollection" class="ng-scope">
<td ng-checkbox-column="">
<label>
<input type="checkbox" ng-model="item.checked" ng-click="tempButtonClicked()" class="ng-pristine ng-untouched ng-valid"> From the checkbox directive.
</label>
</td>
</tr>
//Truncated for brevity
</tbody>
</table>
</div>
The problem I am having is that the ng-click events of the ng-checkbox-column directive cannot be assigned from the myCtrl controller outside of the ng-grid directive. This is because I created an isolate scope for the ng-grid directive and the ng-checkbox-column directive is sharing the scope of the ng-grid directive.
Is there a way around this?
What options do I have to get this to work?
Is my only option to use events?
The reason I created an isolate scope for the ng-grid directive is so that it can be used more than once in the same controller. Therefore I cannot share the scope of the myCtrl controller.
Here is a plunker showing the problem:
Plunker with working dynamic grid, but limited ng-click
Looking at the plunker, you can click on the buttons to see where the click happens in relation to the "scopes".
I would like the checkboxes to fire their own events and update that status without having to go through the ng-grid directive.
Currently I am handling the ng-click with this:
ng-click='tempButtonClicked()'
and in the ng-checkbox-column controller:
$scope.tempButtonClicked = function () {
var val = "From the checkbox directive";
$scope.buttonClicked(val);
};
where $scope.buttonClicked(val); is a reference to a function defined in the controller of the ng-grid directive:
$scope.buttonClicked = function (inp) {
if (typeof inp != 'undefined')
inp = inp + ", through the grid directive.";
else
inp = "From the grid directive.";
$scope.ngButtonClick({ inp: inp });
};
which in turn, binds it to a function specified through an isolate scope variable defined in the ng-grid directive as:
scope: {
ngButtonClick: "&"
},
I hope what I'm explaining makes sense and someone can shed some more light on the subject.
What I would like to have:
Is a way to bind a function to the ng-checkbox-column directive so that I can call a function in the myCtrl controller when it is clicked.
The reason for this is that I may have multiple column templates in a grid and perform different actions depending on which column is clicked.
With the current way it works, I'll have to define x-amount of functions in the ng-grid directive to use with the templates, which seems like a sloppy way of doing it.
I would LOVE to be able to do this:
<div ng-checkbox-column ng-click-action="someDefinedFunctionInMyCtrl()"></div>
which generates this:
<td ng-checkbox-column="">
<label>
<input type="checkbox" ng-model="item.checked" ng-click="someDefinedFunctionInMyCtrl()"> From the checkbox directive.
</label>
</td>
Got it working using an expression as an attribute and ng-transclude:
http://plnkr.co/edit/3XmsE3d44v8O0AxOoUeF?p=preview
JS:
.directive("ngGrid", function () {
return {
[...]
transclude: true, //added transclude
template: function (element, attrs) {
var children = element.html();
children = children.trim().replace(/div/g, "td");
var htmlText = [
"<input type='button' ng-click='buttonClicked()'",
" value='From the grid directive' />",
"<table class='table table-bordered'>",
"<thead><tr>",
"<th ng-repeat='heading in ngCollectionHeadings'>{{heading}}</th>",
"</tr></thead>",
//added ng-transclude
"<tbody><tr id='item{{$index}}'",
" ng-repeat='item in ngCollection' ng-transclude>",
children,
"</tr></tbody>",
"</table>"
].join('');
return htmlText;
},
[...]
};
})
.directive("ngCheckboxColumn", function () {
return {
restrict: "A",
scope: {
//expression as an attribute
myClick: '&'
},
[...]
controller: function ($scope, $element) {
$scope.tempButtonClicked = function () {
var val = "From the checkbox directive";
//call the expression (val will be available in the expression)
$scope.myClick({val: val});
};
}
};
})
HTML:
<div ng-controller="myCtrl">
<div ng-grid ng-collection="entries"
ng-collection-headings="headings" ng-button-click="theAction(inp)">
<!-- added the my-click expression (use val here) -->
<div ng-checkbox-column my-click="ctrl.theAction(val)"></div>
</div>
<p id="btnClickVal">{{actionVal}}</p>
<input type="button" ng-click="theAction(fromParent)"
value="From the parent controller" />
</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 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.
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