Angularjs Directive with Controller - angularjs

I am attempting to write a directive with it's own controller.
myApp.directive('imageUploadifive', function (createGal)
{
return {
restrict: 'A',
controller: function($scope)
{
//create gallery
$scope.created = createGal.createGal();
$scope.created.then(function(createGal){
$scope.gallery.id = createGal.created_id;
console.log($scope.gallery.id);//returning after the link function
});
$scope.gallery.galleryName = "New Image Gallery";
},
link: function($scope, element, attrs)
{
var id = $scope.gallery.id;
console.log(id);
$(element).uploadifive({
'uploadScript' : '/beta/images/upload',
'buttonClass' : 'uploadifive-button btn btn-primary',
'queueID' : 'imageGallery_queue',
'buttonText' : 'Select Files',
'fileSizeLimit' : 500,
'formData' : {
'galleryID' : id
},
'onError': function(errorType)
{
alert('There was a problem');
},
'onUpload': function()
{
}
});
}
};
});
This directive is called with a modal and the //create gallery is generating an id for the uploader js. What I don't understand is the link: function is running and returning undefined before the controller. Any guidance on this would be appreciated.
Thanks

It's not the linking function is called before the controller, it's because the $scope.created.then() is asynchronous, the callback function to set the id is called after the linking function.
To fix it, you need to call $scope.created.then() in the linking function instead:
link: function($scope, element, attrs) {
$scope.created.then(function(createGal) {
$scope.gallery.id = createGal.created_id;
$(element).uploadifive({
'uploadScript' : '/beta/images/upload',
'buttonClass' : 'uploadifive-button btn btn-primary',
'queueID' : 'imageGallery_queue',
'buttonText' : 'Select Files',
'fileSizeLimit' : 500,
'formData' : {
'galleryID' : $scope.gallery.id
},
'onError': function(errorType)
{
alert('There was a problem');
},
'onUpload': function()
{
}
});
});
}

Related

Include custom directives on DTColumnBuilder renderwidth

Is there a way for DTColumnBuilder.newColumn.renderWidth to include custom directives? Here is a draft code of what I want to achieve.
DTColumnBuilder.newColumn('reportStructureName').withTitle('Structure Name')
.renderWith((data, type, full) => {
return "<my-directive></my-directive>";
}),
You can $compile the cell content in the createdCell callback. Here is a very simple example, with a directive that does nothing but coloring the text red. Sorry for not using arrow functions :)
$scope.data = [
{ reportStructureName : "structurename1" },
{ reportStructureName : "structurename2" },
{ reportStructureName : "structurename3" },
{ reportStructureName : "structurename4" }
]
$scope.dtOptions = DTOptionsBuilder.newOptions()
.withOption('data', $scope.data)
.withPaginationType('full_numbers');
$scope.dtColumns = [
DTColumnBuilder.newColumn('reportStructureName')
.withTitle('Structure Name')
.renderWith(function(data, type, full) {
return "<my-directive>"+data+"</my-directive>";
})
.withOption('createdCell', function(td, cellData, rowData, row, col) {
$compile( td )( $scope ); //<--- here
})
]
Directive :
.directive('myDirective', function() {
return {
restrict: 'AE',
link: function (scope, element, attr, ctrl) {
angular.element(element).css('color', 'red')
}
}
})
demo -> http://plnkr.co/edit/aok6SyWZlLaQv8UsEVIf?p=preview

Access controller function from directive to update an HashMap

I was trying to call a controller function from a directive in order to update a counter inside an hash-map.
After reading a few solutions, I ended up doing this:
'use strict';
var dragDropApp = angular.module('dragDropApp', []);
dragDropApp.controller('DragDropCtrl', function($scope) {
$scope.itemCount = {
'item1' : {
'count' : 0
},
'item2' : {
'count' : 0
},
'item3' : {
'count' : 0
}
};
//this.updateItemCounter = function(itemId) {
// $scope.itemCount[itemId].count++;
//}
$scope.updateItemCounter = function(itemId) {
$scope.itemCount[itemId].count++;
}
}
dragDropApp.directive('droppable', function() {
return {
restrict : 'A',
scope : {
drop : '&', // parent
bin : '=' // bi-directional scope
},
controller : 'DragDropCtrl',
link : function(scope, element, attrs, DragDropCtrl) {
var el = element[0];
el.addEventListener('drop', function(e) {
var item = document.getElementById(
e.dataTransfer.getData('Text')).cloneNode(true);
//DragDropCtrl.updateItemCounter(item.id);
>>>> scope.$parent.updateItemCounter(item.id); <<<<
return false;
}, false);
}
}
});
It works and does what I want, but I don't know if this approach is correct. Is it?
I've also tried to use the controller to access the function updateItemCounter, but the hash-map does not get updated, having the same values every time I call the function.
In html you should set attributes like this:
<div droppable bin="something" drop="updateItemCounter(itemId)"></div>
In directive if you want to call your controller's function you should call it like this. Note that property name of passed object must be the same as used in html
link : function(scope, element, attrs, DragDropCtrl) {
...
scope.updateItemCounter({itemId: item.id});
...
}
if you wold like to pass more arguments you should use in html:
<div droppable drop="updateItemCounter(itemId, param2, param3)"></div>
and call:
scope.updateItemCounter({itemId: item.id, param2: 'something', param3: 'something'});
Read these answers:
https://stackoverflow.com/a/27997722/1937797
https://stackoverflow.com/a/23843218/1937797

Image loading issue in custom directive

I want the image URL received from server side in my custom directive.
The directive is used to create a canvas.
Seems the directive is loaded and the image URL is undefined. As it takes the time to get the URL from the server side.
Or maybe how did I get $rootScope data in my directive link function.
Edit:
The following is the directive:
app.directive('logocanvasdirective',['$rootScope','$templateRequest','$compile', function($rootScope,$templateRequest,$compile) {
return {
template: "<canvas id='logo' width='500' height='500'/>",
scope: true,
link: function($scope, element, attrs) {
var canvas1 = document.getElementById('logo'),
context1 = canvas1.getContext('2d');
make_base1();
function make_base1()
{
base_image1 = new Image();
base_image1.src =scope.variable; //How do I use this?
base_image1.onload = function() {
context1.drawImage(base_image1, 0, 0);
}
}
}
};
}]);
I want the image.src = $scope.variable which is receive from server side in my controller.
How do I do that ?
You need to use $watch since you are getting the src from an asynchronous AJAX call:
app.directive('logocanvasdirective',['$rootScope','$templateRequest','$compile', function($rootScope,$templateRequest,$compile) {
return {
template: "<canvas id='logo' width='500' height='500'/>",
scope: {
imgSrc: '='
},
link: function($scope, element, attrs) {
var canvas1 = document.getElementById('logo'),
context1 = canvas1.getContext('2d');
make_base1();
function make_base1()
{
base_image1 = new Image();
base_image1.src = scope.imgSrc;
base_image1.onload = function() {
context1.drawImage(base_image1, 0, 0);
}
}
scope.$watch('imgSrc', function(newValue) {
if (newValue) {
make_base1();
}
});
}
};
}]);
And pass the $scope.variable to your directive:
<logocanvasdirective img-src="variable" />
Or
<div logocanvasdirective img-src="variable"></div>

Adding a new data model to Malhar-Angular-Dashboard

Im' working on the Malhar Angular Dashboard, based on this github project https://github.com/DataTorrent/malhar-angular-dashboard.
As per the documentation in the link post just above, under the 'dataModelType' heading 1/2 way down:
`The best way to provide data to a widget is to specify a dataModelType in the Widget Definition Object (above). This function is used as a constructor whenever a new widget is instantiated on the page.`
And when setting up the Widget Definition Objects, there are various options to choose from :
templateUrl - URL of template to use for widget content
template - String template (ignored if templateUrl is present)
directive - HTML-injectable directive name (eg. "ng-show")
So when I add my own widget definition column chart, I attempt to use the 'template' option; however it does NOT inject the {{value}} scope variable I'm setting.
Using the original datamodel sample widget def, it works fine using the 'directive' option. If I mimic this method on my column chart definition then it works ! But it doesn't work using the template option.
Here's the 'widgetDefinitions' factory code :
(function () {
'use strict';
angular.module('rage')
.factory('widgetDefinitions', ['RandomDataModel','GadgetDataModel', widgetDefinitions])
function widgetDefinitions(RandomDataModel, GadgetDataModel) {
return [
{
name: 'datamodel',
directive: 'wt-scope-watch',
dataAttrName: 'value',
dataModelType: RandomDataModel // GOTTA FIGURE THIS OUT !! -BM:
},
{
name: 'column chart',
title: 'Column Chart',
template: '<div>Chart Gadget Here {{value}}</div>',
dataAttrName: 'value',
size: {width: '40%',height: '200px'},
dataModelType: ColumnChartDataModel
},
];
}
})();
and here are the factories:
'use strict';
angular.module('rage')
.factory('TreeGridDataModel', function (WidgetDataModel, gadgetInitService) {
function TreeGridDataModel() {
}
TreeGridDataModel.prototype = Object.create(WidgetDataModel.prototype);
TreeGridDataModel.prototype.constructor = WidgetDataModel;
angular.extend(TreeGridDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
//this.treeGridOptions = {};
this.updateScope('THIS IS A TreeGridDataModel...'); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return TreeGridDataModel;
});
'use strict';
angular.module('rage')
.factory('ColumnChartDataModel', function (WidgetDataModel) {
function ColumnChartDataModel() {
}
ColumnChartDataModel.prototype = Object.create(WidgetDataModel.prototype);
ColumnChartDataModel.prototype.constructor = WidgetDataModel;
angular.extend(ColumnChartDataModel.prototype, {
init: function () {
var dataModelOptions = this.dataModelOptions;
this.limit = (dataModelOptions && dataModelOptions.limit) ? dataModelOptions.limit : 100;
this.treeGridActive = true;
var value = 'THIS IS A ColChartDataModel...';
//$scope.value = value;
this.updateScope(value); // see WidgetDataModel factory
},
updateLimit: function (limit) {
this.dataModelOptions = this.dataModelOptions ? this.dataModelOptions : {};
this.dataModelOptions.limit = limit;
this.limit = limit;
},
destroy: function () {
WidgetDataModel.prototype.destroy.call(this);
}
});
return ColumnChartDataModel;
});
and finally the directives:
'use strict';
angular.module('rage')
.directive('wtTime', function ($interval) {
return {
restrict: 'A',
scope: true,
replace: true,
template: '<div>Time<div class="alert alert-success">{{time}}</div></div>',
link: function (scope) {
function update() {
scope.time = new Date().toLocaleTimeString();
}
update();
var promise = $interval(update, 500);
scope.$on('$destroy', function () {
$interval.cancel(promise);
});
}
};
})
.directive('wtScopeWatch', function () {
return {
restrict: 'A',
replace: true,
template: '<div>Value<div class="alert alert-info">{{value}}</div></div>',
scope: {
value: '=value'
}
};
})
.directive('wtFluid', function () {
return {
restrict: 'A',
replace: true,
templateUrl: 'app/views/template2/fluid.html',
scope: true,
controller: function ($scope) {
$scope.$on('widgetResized', function (event, size) {
$scope.width = size.width || $scope.width;
$scope.height = size.height || $scope.height;
});
}
};
});
I'd like to know why ONLY the directive option will update the wigdet's data and not the template option.
thank you,
Bob
I believe I see the problem. The dataAttrName setting and updateScope method are actually doing something other than what you're expecting.
Look at the makeTemplateString function here. This is what ultimately builds your widget's template. You should notice that if you supply a template, the dataAttrName does not even get used.
Next, take a look at what updateScope does, and keep in mind that you can override this function in your own data model to do what you really want, a la:
angular.extend(TreeGridDataModel.prototype, {
init: function() {...},
destroy: function() {...},
updateScope: function(data) {
// I don't see this "main" object defined anywhere, I'm just going
// off your treegrid.html template, which has jqx-settings="main.treeGridOptions"
this.widgetScope.main = { treeGridOptions: data };
// Doing it without main, you could just do:
// this.widgetScope.treeGridOptions = data;
// And then update your treegrid.html file to be:
// <div id="treeGrid" jqx-tree-grid jqx-settings="treeGridOptions"></div>
}
});

Load Angular Directive Template Async

I want to be able to load the directive's template from a promise. e.g.
template: templateRepo.get('myTemplate')
templateRepo.get returns a promise, that when resolved has the content of the template in a string.
Any ideas?
You could load your html inside your directive apply it to your element and compile.
.directive('myDirective', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
//Some arbitrary promise.
fetchHtml()
.then(function(result){
element.html(result);
$compile(element.contents())(scope);
}, function(error){
});
}
}
});
This is really interesting question with several answers of different complexity. As others have already suggested, you can put loading image inside directive and when template is loaded it'll be replaced.
Seeing as you want more generic loading indicator solution that should be suitable for other things, I propose to:
Create generic service to control indicator with.
Manually load template inside link function, show indicator on request send and hide on response.
Here's very simplified example you can start with:
<button ng-click="more()">more</button>
<div test="item" ng-repeat="item in items"></div>
.throbber {
position: absolute;
top: calc(50% - 16px);
left: calc(50% - 16px);
}
angular
.module("app", [])
.run(function ($rootScope) {
$rootScope.items = ["One", "Two"];
$rootScope.more = function () {
$rootScope.items.push(Math.random());
};
})
.factory("throbber", function () {
var visible = false;
var throbber = document.createElement("img");
throbber.src = "http://upload.wikimedia.org/wikipedia/en/2/29/Throbber-Loadinfo-292929-ffffff.gif";
throbber.classList.add("throbber");
function show () {
document.body.appendChild(throbber);
}
function hide () {
document.body.removeChild(throbber);
}
return {
show: show,
hide: hide
};
})
.directive("test", function ($templateCache, $timeout, $compile, $q, throbber) {
var template = "<div>{{text}}</div>";
var templateUrl = "templateUrl";
return {
link: function (scope, el, attr) {
var tmpl = $templateCache.get(templateUrl);
if (!tmpl) {
throbber.show();
tmpl = $timeout(function () {
return template;
}, 1000);
}
$q.when(tmpl).then(function (value) {
$templateCache.put(templateUrl, value);
el.html(value);
$compile(el.contents())(scope);
throbber.hide();
});
},
scope: {
text: "=test"
}
};
});
JSBin example.
In live code you'll have to replace $timeout with $http.get(templateUrl), I've used the former to illustrate async loading.
How template loading works in my example:
Check if there's our template in $templateCache.
If no, fetch it from URL and show indicator.
Manually put template inside element and [$compile][2] it.
Hide indicator.
If you wonder what $templateCache is, read the docs. AngularJS uses it with templateUrl by default, so I did the same.
Template loading can probably be moved to decorator, but I lack relevant experience here. This would separate concerns even further, since directives don't need to know about indicator, and get rid of boilerplate code.
I've also added ng-repeat and run stuff to demonstrate that template doesn't trigger indicator if it was already loaded.
What I would do is to add an ng-include in my directive to selectively load what I need
Check this demo from angular page. It may help:
http://docs.angularjs.org/api/ng.directive:ngInclude
````
/**
* async load template
* eg :
* <div class="ui-header">
* {{data.name}}
* <ng-transclude></ng-transclude>
* </div>
*/
Spa.Service.factory("RequireTpl", [
'$q',
'$templateCache',
'DataRequest',
'TplConfig',
function(
$q,
$templateCache,
DataRequest,
TplConfig
) {
function getTemplate(tplName) {
var name = TplConfig[tplName];
var tpl = "";
if(!name) {
return $q.reject(tpl);
} else {
tpl = $templateCache.get(name) || "";
}
if(!!tpl) {
return $q.resolve(tpl);
}
//加载还未获得的模板
return new $q(function(resolve, reject) {
DataRequest.get({
url : "/template/",
action : "components",
responseType : "text",
components : name
}).success(function(tpl) {
$templateCache.put(name, tpl);
resolve(tpl);
}).error(function() {
reject(null);
});
});
}
return getTemplate;
}]);
/**
* usage:
* <component template="table" data="info">
* <span>{{info.name}}{{name}}</span>
* </component>
*/
Spa.Directive.directive("component", [
"$compile",
"RequireTpl",
function(
$compile,
RequireTpl
) {
var directive = {
restrict : 'E',
scope : {
data : '='
},
transclude : true,
link: function ($scope, element, attrs, $controller, $transclude) {
var linkFn = $compile(element.contents());
element.empty();
var tpl = attrs.template || "";
RequireTpl(tpl)
.then(function(rs) {
var tplElem = angular.element(rs);
element.replaceWith(tplElem);
$transclude(function(clone, transcludedScope) {
if(clone.length) {
tplElem.find("ng-transclude").replaceWith(clone);
linkFn($scope);
} else {
transcludedScope.$destroy()
}
$compile(tplElem.contents())($scope);
}, null, "");
})
.catch(function() {
element.remove();
console.log("%c component tpl isn't exist : " + tpl, "color:red")
});
}
};
return directive;
}]);
````

Resources