Generating html directive - angularjs

I am creating a directive that will generate the HTML elements automatically according to data that received from the server.
Here is angularjs code:
(function () {
"use strict";
angular.module("workPlan").directive("myGenericFilter", ["$compile", "$http", "config", myGenericFilterData]);
function myGenericFilterData($compile, $http, config) {
var directive = {
restrict: "E",
scope: {
filterParams: "=filterparameters",
},
//templateUrl: config.baseUrl + "app/workPlan/templates/workPlanList.tmpl.html",
template: '<div></div>',
controller: "genericFilterDataController",
controllerAs: "filter",
link: function (scope, element) {
var parameters;
var el = angular.element('<span/>');
$http.post(config.baseUrl + "api/FilterConfigurations/", scope.filterParams).then(function (result) {
parameters = result.data;
angular.forEach(parameters, function (parameter, key) {
switch (parameter.ValueType) {
case 'Int32':
el.append('<div class="form-group">' +
'<div class="">' +
'<span class="view"></span>' +
'<input type="text"' + 'id =' + parameter.ObjectName + ' class="form-control">' +
'</div>' +
'</div>');
break;
case 'DateTime':
el.append('<div class="form-group">' +
'<span class="view"></span>' +
'<my-datepicker placeholder="dd/mm/yyyy" class=""></my-datepicker>' +
'</div>');
break;
}
});
});
$compile(el)(scope);
element.append(el);
}
}
return directive;
}
})();
As you can see I get data from server and according to parameter.ValueType appropriate case selected in the switch.
After the angular.forEach operator iterated on all items in the parameters variable and all DOM elements is loaded, all input HTML elements displayed except my-datepicker custom directive.
When I use F12 to Inspect Element I see the HTML code of all the elements(include my-datepicker).
Here how it's looks:
But in the view my-datepicker not displayed, here how it looks in view:
my-datepicker is custom directive that defined in common module that injected in all moduls in my project.
Any idea why my-datepicker not displayed in the view?

You compile el before the content of el is setted.
Remember that $http request is async then put
$compile(el)(scope);
element.append(el);
Into the callback
link: function (scope, element) {
var parameters;
var el = angular.element('<span/>');
$http.post(config.baseUrl + "api/FilterConfigurations/", scope.filterParams).then(function (result) {
parameters = result.data;
angular.forEach(parameters, function (parameter, key) {
switch (parameter.ValueType) {
case 'Int32':
el.append('<div class="form-group">' +
'<div class="">' +
'<span class="view"></span>' +
'<input type="text"' + 'id =' + parameter.ObjectName + ' class="form-control">' +
'</div>' +
'</div>');
break;
case 'DateTime':
el.append('<div class="form-group">' +
'<span class="view"></span>' +
'<my-datepicker placeholder="dd/mm/yyyy" class=""></my-datepicker>' +
'</div>');
break;
}
});
$compile(el)(scope);
element.append(el);
});
}

Related

How to make directive use the controller specified in directive attribute?

So I have a directive:
<directive data="user" templateUrl="./user.html" controller="UserController"></directive>
I want that directive to use the controller specified in "controller" attribute, as you see above.
Is it possible with AngularJS directives? Or should I do it other way, maybe with components?
My code currently looks like this:
app.directive('directive', function() {
var controllerName = "UserController"; // i want that to dynamicaly come from attribute
// check if controller extists:
var services = [];
app['_invokeQueue'].forEach(function(value){
services[value[2][0]] = true;
});
if (!services[controllerName]) controllerName = false;
return {
scope: { 'data' : '=' },
link: function (scope) {
Object.assign(scope, scope.data);
},
templateUrl: function(element, attr) {
return attr.templateurl;
},
controller: controllerName
}
});
You can do following (not exactly what you ask - it creates bunch of nested scopes, but should be sufficient):
.directive('directive', () => {
scope: { 'data' : '=' },
template: (elem, attrs) => {
return '<div ng-controller="' + attrs.controller + ' as vm"><div ng-include="' + attrs.template + '"></div></div>';
}
});
<directive data="user" templateUrl="./user.html" controller="UserController"></directive>
you may use $templateCache directly instead of ng-include
if you need controller/template/... to be dynamic, you need to observe/watch + dom manipulation + recompile stuff
Okay, so after analysing Petr's answer I post the working code using nested divs:
app.directive('directive', function() {
return {
scope: { 'data' : '=' },
link: function (scope) {
// this makes your fields available as {{name}} instead of {{user.name}}:
Object.assign(scope, scope.data);
},
template: function(element, attrs) {
var controllerName = attrs.controller;
var controllerString = controllerName + ' as vm';
// check if controller extists:
var services = [];
app['_invokeQueue'].forEach(function(value){
services[value[2][0]] = true;
})
if (!services[controllerName]) {
return '<div ng-include="\'' + attrs.templateurl + '\'"></div>';
} else {
return '<div ng-controller="' + controllerString + '"><div ng-include="\'' + attrs.templateurl + '\'"></div></div>';
}
}
}
});

ui boostrap typeahead directive doesn't work when wrapped in a directive and loaded dynamically

I've a directive that creates an input text element and uses ui bootstrap directive to attach typeahead functionality to the input field.
This input field is dynamically appended to one of the field on the form on dom ready event. I've to do this since, I don't have access to edit/modify html page generated by server. i.e - Dynamically add a typeahead field using angularjs and bootstrap angularjs as well.
I'm using ui boostrap - v0.12.0, angularjs version - v1.2.26 and jquery - v1.8.3
Problem: the directive is not working (or may be not correctly compiled or access scope) in IE 11, whereas works perfectly in chrome browser without any problem. I can see the appended elements on form load with no errors or exceptions on the console, however no typeahead magic.
here's the what i've -
// added required js references
// initialize angular app
var typeAheadApp = angular.module("typeAheadApp", ['smart-table', 'ui.bootstrap']);
controller:
typeAheadApp.controller('TypeaheadCtrl', ['$scope', '$http', '$compile', function ($scope, $http, $compile) {
$scope.getCategoriesSize = 1;
$scope.categorylkp = getCategoryField().val();
$scope.getCategories = function (val) {
return $http({
url: "/some/data/source/url",
method: 'GET',
headers: {
"Accept": "application/json;odata=verbose"
}
}).then(function (response) {
$scope.getCategoriesSize = response.data.d.results.length;
return response.data.d.results.map(function (item) {
return item.categoryName;
});
}, function (ex) {
alert("ERROR!!");
});
};
$scope.selectedCategory = function (item, model, label) {
getCategoryField().val(label);
};
$scope.updateCategory = function (setVal) {
getCategoryField().val(setVal);
};
}]);
directive:
typeAheadApp.directive('categoryLookup', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var typeAheadTemplate = angular.element('<div class="form-inline">' +
'<input id="categorylkpTxt" type="text" ng-model="categorylkp" ng-change="updateCategory(categorylkp)" typeahead="category for category in getCategories($viewValue)" typeahead-on-select="selectedCategory($item, $model, $label)" typeahead-min-length="3" typeahead-loading="loadingCategories" style="width: 345px;" autocomplete="off">' +
'</div>' +
'<div ng-show="loadingCategories">' +
'<i class="icon-refresh"></i> Loading...' +
'</div>' +
'<div ng-show="!getCategoriesSize">' +
'<i class="icon-remove"></i> No Results Found ' +
'</div>');
var compiled = $compile(angular.element('<div>').append(typeAheadTemplate).html())(scope);
element.append(compiled);
}
}
}]);
init function:
function initTypeAhead(){
var typeAheadField = getCategoryField(); // some field on the form
typeAheadField.parent().append('<div id="typeAheadEl"><div ng-controller="TypeaheadCtrl"><div id="category-lookup" class="custom-typeahead" category-lookup></div></div></div>');
// manual bootstrapping the angular
angular.bootstrap($('#typeAheadEl'), ['typeAheadApp']);
}
angular.element(document).ready(function() {
initTypeAhead();
});
Any advise or comments ?
Thanks in advance!
I'd start fixing it from category lookup directive as it looks rather messy, you are compiling in link method what should be in template
typeAheadApp.directive('categoryLookup', function () {
return {
restrict: 'A',
template: '<div class="form-inline">' +
'<input id="categorylkpTxt" type="text" ng-model="categorylkp" ng-change="updateCategory(categorylkp)" typeahead="category for category in getCategories($viewValue)" typeahead-on-select="selectedCategory($item, $model, $label)" typeahead-min-length="3" typeahead-loading="loadingCategories" style="width: 345px;" autocomplete="off">' +
'</div>' +
'<div ng-show="loadingCategories">' +
'<i class="icon-refresh"></i> Loading...' +
'</div>' +
'<div ng-show="!getCategoriesSize">' +
'<i class="icon-remove"></i> No Results Found ' +
'</div>',
controller: 'TypeaheadCtrl'
}
});
and then init function
function initTypeAhead(){
var typeAheadField = getCategoryField(); // some field on the form
typeAheadField.parent().append('<div id="typeAheadEl"><div id="category-lookup" class="custom-typeahead" category-lookup></div></div>');
// manual bootstrapping the angular
angular.bootstrap($('#typeAheadEl'), ['typeAheadApp']);
}
angular.element(document).ready(function() {
initTypeAhead();
});

Update ng-options hosted by a directive

I have a custom directive that displays a dropdownlist.
Is it possible to dynamically re-populate the ng-options from a datasource that comes from the controller that hosts the directive.
The datasource itself comes from a service.
Currently it works well from the initial array passed to the directive, but when I add new data (from the controller/service to this array I would like to update the item list.
Any help?
EDIT :
This is how I use my directive.
<select-item-obj-from-array datasource="ctrl.ActivityAddresses" ng-model="form.Activity.AddressID" name="AddressID" value="AddressID" label="City" .... />
My directive looks like:
app.directive('selectItemObjFromArray', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
var tpl = '';
tpl += "<div><div class=\"form-group clearfix\" >";
tpl += '<label for="' + attrs.name + '" class="col-lg-3 control-label">' + attrs.label + '</label>';
tpl += '<div class="col-lg-9">';
tpl += '<select ng-disabled="ngDisabled" name="' + attrs.name + '" ng-model="ngModel" chosen="datasource" ng-options="c.Name for c in datasource"></select>';
tpl += '</div>';
tpl += '</div>';
tpl += '</div>';
return tpl;
},
scope: {
ngModel: "=",
datasource: "="
},
link: function (scope, elem, attrs) {
var select = elem.find("select").eq(0);
select.chosen();
scope.$watch(function () {
return select[0].length;
},
function (newvalue, oldvalue) {
if (newvalue !== oldvalue) {
select.trigger("chosen:updated");
}
});
scope.$watch(attrs.ngModel, function () {
select.trigger('chosen:updated');
});
}
};
});
if my controller/service updated the ctrl.ActivityAddresses I don't know how to "reinvoke" the directive to update the dropdownlist..
You can broadcast from your service like this:
var broadcast = function() {
$rootScope.$broadcast('items.update');
};
Assuming that items is an array.
Then you can catch the broadcast in your controller or directive:
$scope.$on('items.update', function (event) {
//Do whatever you want with the items.
});
I think this is what you want? You don't need to change the ng-options directive for this.

How to pass multiple attribute in AngularJS directive

How to pass multiple attribute to a directive.
How to pass value 12 of click-to-edit1 inside below div like
<div click-to-edit="location.state" click-to-edit1=12></div>
and should be accessible in directive controller.please help me out.
Code:
App HTML:
<div ng-controller="LocationFormCtrl">
<h2>Editors</h2>
<div class="field">
<strong>State:</strong>
<div click-to-edit="location.state"></div>
</div>
<h2>Values</h2>
<p><strong>State:</strong> {{location.state}}</p>
</div>
App directive:
app = angular.module("formDemo", []);
app.directive("clickToEdit", function() {
var editorTemplate = '<div class="click-to-edit">' +
'<div ng-hide="view.editorEnabled">' +
'{{value}} ' +
'<a ng-click="enableEditor()">Edit</a>' +
'</div>' +
'<div ng-show="view.editorEnabled">' +
'<input ng-model="view.editableValue">' +
'Save' +
' or ' +
'<a ng-click="disableEditor()">cancel</a>.' +
'</div>' +
'</div>';
return {
restrict: "A",
replace: true,
template: editorTemplate,
scope: {
value: "=clickToEdit",
},
controller: function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
};
}
};
});
App controller:
app.controller("LocationFormCtrl", function($scope) {
$scope.location = {
state: "California",
};
});
Add new property inside directives scope:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.location = {
state: "California",
};
});
app.directive("clickToEdit", function() {
var editorTemplate = '<div class="click-to-edit">' +
'<div ng-hide="view.editorEnabled">' +
' {{value}} ' +
'<a ng-click="enableEditor()">Edit</a>' +
'</div>' +
'<div>{{value1}}</div>' +
'<div ng-show="view.editorEnabled">' +
'<input ng-model="view.editableValue">' +
'Save' +
' or ' +
'<a ng-click="disableEditor()">cancel</a>.' +
'</div>' +
'</div>';
return {
restrict: "A",
replace: true,
template: editorTemplate,
scope: {
value: "=clickToEdit",
value1: "=clickToEdit1"
},
controller: function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
};
}
};
});
html:
<div class="field">
<strong>State:</strong>
<div click-to-edit="location.state" click-to-edit1="12"></div>
</div>
working example: http://plnkr.co/edit/e7oTtZNvLdu6w5dgFtfe?p=preview
you first tell the div that you want your directive on it.
<div click-to-edit value='location.state' value1='12'></div>
app.directive("clickToEdit", function() {
return {
restrict: "A",
scope : {
value : "=",
value1 : "="
},
link : function($scope) {
console.log("the first value, should be location.state value on the controller", $scope.value);
console.log("the second value, should be '12'", $scope.value);
}
}
it might seem more logic when you use the directive as a element.
but bottom line is that the diretive looks for attributes on the element, which you then can attach to the directive via the scope. '=' for two way binding, '#' for one way.

How to Compile HTML String inside foreach loop

I am trying to compile HTML String directive which is place inside foreach loop. I even tried putting scope but still not working. My sample code looks like this:
hotelierApp.directive('maps', ['$translate', '$filter', '$compile',
function ($translate, $filter, $compile) {
return {
restrict: "A",
replace: true,
scope: true,
link: function (scope, element, attrs) {
_.each(scope.properties, function (property) {
var strHtml = $compile('<div><div class=\'hotelMarker\'>' +
'<div class=\'hotelIcon\'>' +
'<img alt=\'\' class=\'menuShadow\' src=\'{{PropertyThumbnail}}\' height=\'45\' width=\'45\'/>' +
'</div>' +
'<div class=\'hotelText\'>' +
'<span class=\'hotelHeader\'>{{PropertyName}} ' +
'</span>' +
'</div>' +
'</div></div>')(property);
return function () {
if (infoWindow) {
infoWindow.close();
}
infoWindow.setContent(strHtml[0].innerHTML);
infoWindow.open(map, markerOtherHotel, strHtml[0].innerHTML);
}
})(markerOtherHotel, property.PropertyID));
});
},
template: ''
}}]);
Please help
property is not a scope object and cannot be used with $compile in this way. Instead, you could do something like this:
var strHtml = $compile('<div><div class=\'hotelMarker\'>' +
'<div class=\'hotelIcon\'>' +
'<img alt=\'\' class=\'menuShadow\' src=\'' + property.PropertyThumbnail + '\'' +
' height=\'45\' width=\'45\'/>' +
'</div>' +
'<div class=\'hotelText\'>' +
'<span class=\'hotelHeader\'>' + property.PropertyName + ' ' +
'</span>' +
'</div>' +
'</div></div>')(scope);
See this Plunker for a working example.

Resources