I currently have an issue when I call ui-tinymce directive in a custom directive. The custom directive is used to load dynamically links from backend for tinymce advlink plugin (+ load tinymce options object associated with a key passed as an attribute to the directive).
Here is my controller :
module.controller('Ctrl', function ($scope) {
$scope.test = {
val: "gfsgfdgh"
};
});
Here is how I call the directive in HTML:
<tinymce-custom type="minimal" ng-model="test.val"></tinymce-custom>`
And here is my directive :
module.directive('tinymceCustom', function($location, TinyService, Module, GenerateurPage) {
return {
restrict: 'E',
replace: true,
require:"ngModel",
scope: {
ngModel: '='
},
link: function(scope, element, attrs, ngModel){
scope.loaded = {
modules: false,
pages: false,
tinymce: false
};
scope.tinyOptions = {};
var link_list = [];
var modules = [];
var pages = [];
Module.findByOrganisme({}, function (data) {
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
scope.loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
scope.loaded.pages = true;
initTiny();
});
function initTiny() {
if (!scope.loaded.modules || !scope.loaded.pages) {
return false;
}
scope.tinyOptions = TinyService.options(attrs.type);
console.log(scope);
scope.tinyOptions.link_list = link_list;
scope.loaded.tinymce = true;
}
},
template: '<div ng-if="loaded.tinymce"><textarea ui-tinymce="tinyOptions" ng-model="ngModel"></textarea></div>'
};
});
The problem is that the model passed to ui-tinymce directive is not updated when changing the text with the editor, and the text in the editor is not updated when the model from the controller is changed... BUT, the initial ngModel value is passed to ui-tinymce directive, so I think that is the data binding that is broken. Tried to watch it with $watch but nothing happens.
I can't figure how to fix it so I'm now looking for some help...
Thx
Finaly fixed it changing the approach :
<textarea tinymce-custom="minimal" ng-model="myVar"></textarea >
The final directive :
module.directive('tinymceCustom', function($location, $compile, $q, TinyService, Module, GenerateurPage) {
return {
restrict: 'A',
priority:999,
terminal:true, // prevent lower priority directives to compile after it
scope: true,
require: ['?ngModel'],
link: function(scope, el, attrs) {
// default is basic template
var type = attrs.tinymceCustom ? attrs.tinymceCustom : 'basic';
function loadTinyOptions(name) {
var loaded = {
modules: false,
pages: false,
tinymce: false
};
var link_list = [];
var deferred = $q.defer();
var initTiny = function() {
if (!loaded.modules || !loaded.pages) {
return false;
}
var tinyOptions = TinyService.options(name);
tinyOptions.link_list = link_list;
deferred.resolve(tinyOptions);
};
Module.findByOrganisme({}, function (data) {
var modules = [];
data.forEach(function(module) {
modules.push({title: module.libelle, value: "/modules/"+module.id});
});
link_list.push({title: "Modules", menu: modules});
loaded.modules = true;
initTiny();
});
GenerateurPage.findByOrganisme({}, function(data) {
var pages = [];
data.forEach(function(page) {
pages.push({title: page.titre, value: "/#/generateurPage/afficherPage?id=/"+page.id});
});
link_list.push({title: "Pages", menu: pages});
loaded.pages = true;
initTiny();
});
return deferred.promise;
}
loadTinyOptions(type).then(function(data) {
scope._tinyOptions = data;
el.removeAttr('tinymce-custom'); // necessary to avoid infinite compile loop
el.attr('ui-tinymce', '{{_tinyOptions}}');
$compile(el)(scope);
});
}
};
Hope this can help.
Related
I want to create a table directive, I wanted to know how to pass a callback to for example: "Calling a delete function that is not in the Controller", I was wanting to pass as a callback using parameters, but for some time I am hitting my head with it. When I can return the callback through the "link" I can not return the values, and the controller can not.
Imagem when I use link , but don't back values of callback
Image when I use Controller
View DashBoard
<div crud-table options='vm.options' callback='vm.myCallBack(response)'></div>
Controller DashBoard
(function() {
'use strict';
angular
.module('senhalivre')
.controller('DashboardMikrotikCtrl', DashboardMikrotikCtrl);
DashboardMikrotikCtrl.$inject = [];
function DashboardMikrotikCtrl() {
var vm = this;
vm.myCallBack = myCallBack;
vm.options = {
enabled : true,
msg : 'DashBoard',
// Configs etc.....
}
//////////////////
function myCallBack(response){
console.log('Callback !!! ');
console.log(response);
}
}
})();
Directive
(function() {
'use strict';
angular
.module('senhalivre')
.directive('crudTable', crudTable);
crudTable.$inject = [];
function crudTable() {
var directive = {
bindToController: true,
controller: crudTableCtrl,
templateUrl : './views/crud/crudTable.html',
controllerAs: 'vm',
link: link,
restrict: 'EA',
scope: {
options : '=',
callback : '&callback'
}
};
return directive;
////////////////
function link(scope, element, attrs, ctrl) {
scope.vm.callback({test : 'something'});
// Work
}
}
crudTableCtrl.$inject = [];
function crudTableCtrl() {
var vm = this;
vm.callback({test : 'something'});
// :(
}
})();
You need to provide the property 'response' in the parameter-object when calling the callback:
function link(scope, element, attrs, ctrl) {
scope.vm.callback({response: 'some response text'});
}
or
function crudTableCtrl() {
this.callback({response: 'something'});
}
I need to call a function in my directive when the value of variable in the parent controller changes. I tried adding a watch (I'm obviously doing it wrong) because nothing happens when the value changes. Here is the directive:
angular.module('ssq.shared').directive('checkboxPicklist', function() {
return {
restrict: 'E',
templateUrl: '/Scripts/app/Shared/directives/checkboxPicklist.html',
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
link: function (scope, element, attrs) {
scope.query = '';
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
var child = element.find('.dropdown-menu');
child.on({
'click': function (e) {
e.stopPropagation();
}
});
var selectedItemFn = function (item) {
return item.selected;
};
scope.getSelectedCount = function () {
return _.filter(scope.list, selectedItemFn).length;
};
scope.loadMore = function () {
scope.callMore();
};
scope.allSelected = function(list) {
var newValue = !scope.allNeedsMet(list);
_.each(list, function(item) {
item.selected = newValue;
scope.callbackFn({ object: item });
});
};
scope.allNeedsMet = function(list) {
var needsMet = _.reduce(list, function(memo, item) {
return memo + (item.selected ? 1 : 0);
}, 0);
if (!list) {
return (needsMet === 0);
}
return (needsMet === list.length);
};
function clearAll() {
_.each(list, function (item) {
item.selected = false;
})
}
}
};
});
Here is where I am trying to watch the variable:
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
Here is the function in my parent controller that changes the value of "clear"
$scope.clearFilters = function (clear) {
$scope.clear = true;
$scope.form.selected.services = [];
$scope.form.picked.areas = [];
$scope.form.certified.verifications = [];
$scope.form.subscribed.subscriptions = [];
$scope.form.OperatorBusinessUnitID = null;
$scope.form.OperatorBusinessUnitID = null;
};
I tried setting an attribute called "clearFilter" and assigning the variable to it, but the watch still doesn't trigger:
scope.$watch(attrs.clearFilter, function (value) {
if (value == true) {
this.clearAll();
}
});
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear-filter="clear">
</checkbox-picklist>
I'm not really sure if I am calling the function correctly. scope.$parent above does get the initial value of the variable from the parent scope, but once it changes, it never updates.
EDIT:What I have discovered is the normal scope.$watch('clear', function...) is not working it seems because the directive is in "ssq.shared" module which is injected in my my Main Module "myModule" (see below), so even though the page the directive is on uses my 'GeneralSearchCtrl', I cannot get the watch to work on the variable located in 'GeneralSearchCtrl'. If I use scope.$parent.clear I can see the value of the variable, but I cannot seem to set a watch on it.
My module injection code:
var app = angular.module('myModule', ['ui.bootstrap', 'checklist-model', 'ssq.shared', 'ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.pagination', 'ui.grid.selection', 'ui.grid.exporter', 'ui.grid.autoResize', 'ui.router', 'cgBusy', 'ui.mask', 'ngFileUpload', 'ngSanitize']);
The page where the directive lives uses:
<div ng-app="myModule" ng-controller="GeneralSearchCtrl">
I am unable to get a watch on the variable located in GeneralSearchCtrl.
Any assistance is greatly appreciated!!!!
Add a watch for the $scope value and call the function,
scope.$watch('clear', function(newValue, oldValue) {
if (newValue) {
this.clearAll();
}
});
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
This.clearAll() doesn't exist in the scope of your $watch function. Simply calling clearAll() should work better.
The signature of the watch function is not correct.
scope.$watch('clear', function (new, old) {}
As it turns out, the problem was that the directive had scope:{...} in its definition which stopped the "normal" scope.$watch('clear', function...) from working. I had to add clear: '=' to the scope list like so:
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
Then clear="clear" to the directive like so:
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear="clear">
</checkbox-picklist>
Then in the directive I had to add the watch like this for it work:
scope.$watch('$parent.clear', function (newValue, oldValue) {
if (newValue == true) {
clearAll();
alert('it works!');
}
})
I really hope this helps someone else as this was difficult for me to figure out. Happy coding!
Consider the initial app main state:
$stateProvider.state('main', {
url: '',
views: {
'nav#': {
templateUrl: baseTemplatesUrl + 'nav.main.html',
controllerAs: 'vm',
controller: 'mainNavController',
resolve: {
timelinesService: 'timelinesService'
}
},
'content#': {
template: ''
}
}
});
Inside the nav.main.html template I'm trying to use a simple slider directive:
<ul>
<li ng-repeat="t in vm.timelines">
<!-- ui-sref=".timeline({yearFrom: t.yearFrom, yearTo: t.yearTo})" -->
<a class="button tiny" ui-sref-active="active">{{t.text}}</a>
</li>
</ul>
<div simple-slider items="vm.timelines" on-item-clicked="vm.timelineClicked"></div>
The directive is defined as:
function simpleSlider() {
return {
restrict: 'EA',
templateUrl: baseTemplatesUrl + 'directive.simpleSlider.html',
replace: true,
scope: {
items: '=',
onItemClicked: '&'
},
link: function(scope, el, attr, ctrl) {
scope.curIndex = 0;
scope.next = function() {
scope.curIndex < scope.items.length - 1 ? scope.curIndex++ : scope.curIndex = 0;
};
scope.prev = function() {
scope.curIndex > 0 ? scope.curIndex-- : scope.curIndex = scope.items.length - 1;
};
scope.$watch('curIndex', function() {
scope.items.forEach(function(item) {
item.active = false;
});
scope.items[scope.curIndex].active = true;
});
},
};
}
The problem
On a page load the directive's $watch throws an exception saying that scope.items[scope.curIndex] is undefined, whereas the ng-repeat="t in vm.timelines" inside the nav.main.html template renders successfully.
Why the watch fails / how to pass vm.timelines to directive's scope?
The fiddle
https://jsfiddle.net/challenger/Le5p9aup/
Update 1. Regarding #Lex answer
The fiddle is working. But my setup differs from the given fiddle. In my setup the timelinesService returns the $http promise:
var service = {
getTimelines: function() {
return $http.get('/api/timelines');
}
};
then inside the navMainController the vm.timelines array gets populated:
vm.timelines = [];
timelinesService.getTimelines().then(function(response) {
vm.timelines = response.data.timelines;
}).catch(function(error) {
console.log(error);
});
Then, unless you click the directive's prev/next button, the $watch will fail:
// scope.items[scope.curIndex] is undefined
scope.items[scope.curIndex].active = true;
I have created a directive below:
html:
<div image-upload></div>
directive:
angular.module('app.directives.imageTools', [
"angularFileUpload"
])
.directive('imageUpload', function () {
// Directive used to display a badge.
return {
restrict: 'A',
replace: true,
templateUrl: "/static/html/partials/directives/imageToolsUpload.html",
controller: function ($scope) {
var resetScope = function () {
$scope.imageUpload = {};
$scope.imageUpload.error = false;
$scope.imageUpload['image_file'] = undefined;
$scope.$parent.imageUpload = $scope.imageUpload
};
$scope.onImageSelect = function ($files) {
resetScope();
$scope.imageUpload.image_file = $files[0];
var safe_file_types = ['image/jpeg', 'image/jpg']
if (safe_file_types.indexOf($scope.imageUpload.image_file.type) >= 0) {
$scope.$parent.imageUpload = $scope.imageUpload
}
else {
$scope.imageUpload.error = true
}
};
// Init function.
$scope.init = function () {
resetScope();
};
$scope.init();
}
}
});
This directive works fine and in my controller I access $scope.imageUpload as I required.
Next, I tried to pass into the directive a current image but when I do this $scope.imageUpload is undefined and things get weird...
html:
<div image-upload current="project.thumbnail_small"></div>
This is the updated code that gives the error, note the new current.
angular.module('app.directives.imageTools', [
"angularFileUpload"
])
.directive('imageUpload', function () {
// Directive used to display a badge.
return {
restrict: 'A',
replace: true,
scope: {
current: '='
},
templateUrl: "/static/html/partials/directives/imageToolsUpload.html",
controller: function ($scope) {
var resetScope = function () {
$scope.imageUpload = {};
$scope.imageUpload.error = false;
$scope.imageUpload['image_file'] = undefined;
$scope.$parent.imageUpload = $scope.imageUpload
if ($scope.current != undefined){
$scope.hasCurrentImage = true;
}
else {
$scope.hasCurrentImage = true;
}
};
$scope.onImageSelect = function ($files) {
resetScope();
$scope.imageUpload.image_file = $files[0];
var safe_file_types = ['image/jpeg', 'image/jpg']
if (safe_file_types.indexOf($scope.imageUpload.image_file.type) >= 0) {
$scope.$parent.imageUpload = $scope.imageUpload
}
else {
$scope.imageUpload.error = true
}
};
// Init function.
$scope.init = function () {
resetScope();
};
$scope.init();
}
}
});
What is going on here?
scope: {
current: '='
},
Everything works again but I don't get access to the current value.
Maybe I'm not using scope: { correctly.
in your updated code you use an isolated scope by defining scope: {current: '=' } so the controller in the directive will only see the isolated scope and not the original scope.
you can read more about this here: http://www.ng-newsletter.com/posts/directives.html in the scope section
I'm having trouble retrieving the value of an attribute passed in to a directive. My understanding of how directives work probably has something to do with this but it's my assumption that this is a scope related issue.
If you check the code below you'll see the attribute is being used as such:
display="contest.StyleBgImageMedia.filename"
This value of contest.StyleBgImageMedia.filename is a string and I've verified it exists by consoling it our from the controller. The problem is that when trying to access it within the directives link function I can't retrieve the value but only the attribute name.
This directive is used in the view like such:
<uploader class="pull-left" action="builder/uploadMediaFile" display="contest.StyleBgImageMedia.filename" data-file="style_bg_image_media_id"></uploader>
The full directive has been posted below. You'll see that I'm using $observe to render the value of the display attribute but this isn't working.
app.directive('uploader', function($rootScope) {
return {
restrict: 'E',
scope: {
action: '#',
display: '#display'
},
link: function(scope, elem, attrs) {
elem.find('.fake-uploader').click(function() {
elem.find('input[type="file"]').click();
});
scope.progress = 0;
attrs.$observe('display', function(value) {
if (value) {
scope.avatar = value;
}
});
scope.sendFile = function(el) {
var $form = jQuery(el).parents('form');
if (jQuery(el).val() === '') {
return false;
}
$form.attr('action', scope.action);
scope.$apply(function() {
scope.progress = 0;
});
$form.ajaxSubmit({
type: 'POST',
uploadProgress: function(event, position, total, percentComplete) {
scope.$apply(function() {
scope.progress = percentComplete;
});
},
error: function(event, statusText, responseText, form) {
$form.removeAttr('action');
},
success: function(responseText, statusText, xhr, form) {
var ar = jQuery(el).val().split('\\'),
filename = ar[ar.length-1];
$form.removeAttr('action');
scope.$apply(function() {
scope.avatar = filename;
});
var response = jQuery.parseJSON(responseText);
$rootScope.$broadcast('file-uploaded', {
'model': attrs.file,
'file': response.message
});
}
});
};
},
replace: false,
templateUrl: 'builder/views/partials/upload.php'
};
});
$observe doesn't work unless the attribute value contains interpolation.
So, you can change the attribute to be:
<uploader display="{{contest.StyleBgImageMedia.filename}}" ...>
Alternatively, you can use watch: scope.$watch('display', ...) with this isolated scope binding:
display: '='