I have a pretty simple directive, which basically contains child directives, which each call a function when clicked.
<yd-chart-selector>
<yd-chart-selector-item button-class="column" on-click="main.drawColumnChart()"></yd-chart-selector-item>
<yd-chart-selector-item button-class="web" on-click="main.drawSpiderChart()"></yd-chart-selector-item>
<yd-chart-selector-item button-class="line" on-click="main.drawLineChart()"></yd-chart-selector-item>
</yd-chart-selector>
getting this directive to work in it's simplest form, ie clicking an item calls the method, was simple to do.
i also wanted to set the clicked yd-chart-selector-item to active. again, doing that was simple, using a property on my VM.
however, when I set an item to active, the other items should become inactive.
so I figured that I would maintain a list of items on the parent, and use the parent to handle invoking the actions, and clearing the active of the items in it's list.
I have got this working, but it doesnt feel right to me.
app.directive('ydChartSelector', chartSelector);
function chartSelector() {
var directive = {
template: '<div class="form-group graph-type-select pull-right" ng-transclude=""></div>',
restrict: 'E',
bindToController: true,
link: link,
controller: controller,
controllerAs: 'vm',
transclude: true
};
return directive;
function controller() {
var vm = this;
vm.items = [];
vm.addItem = addItem;
vm.selectItem = selectItem;
function addItem(item) {
vm.items.push(item);
if(vm.items.length === 1) {
item.selected = true;
}
}
function selectItem(item) {
resetUi(vm);
item.selected = true;
item.onClick();
}
}
function resetUi(vm) {
vm.items.forEach(function(value) {
value.selected = false;
});
}
}
app.directive('ydChartSelectorItem', chartSelectorItem);
function chartSelectorItem() {
var directive = {
template: '<button type="button" class="btn graph-type-btn" ng-class="[vm.buttonClass, {selected: vm.selected}]" ng-click="vm.f(this)"></button>',
restrict: 'E',
link: link,
bindToController: true,
controller: controller,
controllerAs: 'vm',
require: '^ydChartSelector',
scope: {
onClick: '#',
buttonClass: '#'
}
};
return directive;
function link(scope, element, attrs, chartSelectorController) {
chartSelectorController.addItem(scope.vm);
}
function controller() {
var vm = this;
vm.selected = false;
vm.f = f;
function f($scope) {
$scope.$parent.$parent.vm.selectItem(vm);
}
}
}
the part that is bugging me is:
function f($scope) {
$scope.$parent.$parent.vm.selectItem(vm);
}
there must be a better way to do this?
I tried using '&' and '#' combinations for trying to bind to the parent, but could not get it right.
You should use isolated scope with & for passing method to directive & method should have on-click="main.drawLineChart()" function bracket so that the method will get call, Inside you controller you could call vm.onClick() to call method of directive element.
scope: {
onClick: '&',
buttonClass: '#'
}
Markup
<yd-chart-selector>
<yd-chart-selector-item button-class="column" on-click="main.drawColumnChart">
</yd-chart-selector-item>
<yd-chart-selector-item button-class="web" on-click="main.drawSpiderChart()">
</yd-chart-selector-item>
<yd-chart-selector-item button-class="line" on-click="main.drawLineChart()">
</yd-chart-selector-item>
</yd-chart-selector>
Related
I am using jCarousel for image thumbnail slider. but previously I was using directive for the same but now I changed my code to component. but now I am not able to use that link function and watch reload in component. because I am using first time component in agularjs.
//Previous code
directive('jblogcarousel', function() {
return {
restrict: 'A',
replace: true,
transclude: true,
scope: {
jitems: "="
},
templateUrl: '/templates/blog-carousel.html',
link: function link(scope, element, attrs) {
var container = $(element);
var carousel = container.find('.jcarousel');
carousel.jcarousel({
wrap: 'circular'
});
scope.$watch(attrs.jitems, function (value) {
carousel.jcarousel('reload');
});
container.find('.jcarousel-control-prev')
.jcarouselControl({
target: '-=1'
});
container.find('.jcarousel-control-next')
.jcarouselControl({
target: '+=1'
});
}
};
});
//Current code
.component('jCarousel', {
bindings: {
jitems: '='
},
templateUrl: '/templates/carousel.html'
})
From what I understood, in Angular 1.5 components bindings will bind the value to the controller.
So you can add a controller (with a $watch inside):
// bindings: { ... },
// templateUrl: '...',
controller: function ($scope) {
var vm = this;
console.log(vm.jitems); // vm.jitems should exist and be bound the value you passed to the component from the outside
// you should be able to watch this value like this
$scope.$watch(
function () { return vm.jitems; },
function (newValue) { console.log(newValue); }
);
}
Also, with components, you should in most situations use one way binding '<' instead of two-way binding '=', and use functions/events (binding '&') for the other direction.
I have created dx-tabs that are dynamically loaded in the view using custom directives. I am able to output the tabs but I cannot figure out how to bind the cooresponding tab-pane with the tab. I am thinking inside of my ji-Tabset directive in the addTab function I need to somehow create a variable that will loop through my scope.tabs array and then for each integer I need to create an instance of that variable. Then inside of my ji-tab directive I need to push both the text and the content to the scope.tabs array. Any thoughts?
Directive 1 - Ji-Tabset
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTabset', function () {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: function ($scope) {
var tabs = $scope.tabs = [];
$scope.select = function (args) {
var tab = args.itemData;
angular.forEach(tabs, function (tab) {
tab.selected = false;
});
tab.selected = true;
};
$scope.tabSettings = {
bindingOptions: { items: "tabs" },
onItemClick: $scope.select
}
this.addTab = function (tab) {
tabs.push(tab);
};
},
templateUrl: "FormTest/views/ji-Tabset.html",
};
});
}
Directive 2 - Ji-Tab
module FormTest {
angular
.module('FormTest') //Gets the FormTest Module
.directive('jiTab', function () {
return {
require: '^jiTabset',
restrict: 'E',
transclude: true,
scope: {},
templateUrl: 'FormTest/views/ji-Tab.html',
link: function (scope, element, attrs, tabsCtrl) {
scope.text = attrs.tabName;
tabsCtrl.addTab(scope);
}
};
});
}
Ji-Tabset templateUrl
<div dx-tabs='tabSettings'></div><div ng-transclude></div>
Ji-Tab templateUrl
<div ng-show="selected" ng-transclude></div>
Main view
<ji-tabset name="Tabs" label="testing">
<ji-tab tab-name="General">
<ji-button label="Button 1"></ji-button>
</ji-tab>
<ji-tab tab-name="Stats"></ji-tab>
<ji-tab tab-name="Stuff"></ji-tab>
<ji-tab tab-name="Other"></ji-tab>
<ji-tab tab-name="More stuff"></ji-tab>
</ji-tabset>
In case anyone ever runs into this, I was able to figure out the solution.
I had to make a few changes in my directives and use DevExpress' onItemClick event handler itemData. I had to add a view for ji-tab directive and transclude everything inside of it. And also loop through my tabs array with angular.foreach. I have updated the original post with the correct solution.
I have two directives which both have an isolated scope. I would like to require the parent directive in my child directive, and then be able to watch if a scope variable of the parent changes. I don't want to modify the variable in the child, I just want to be able to read it.
I want to be able to add different children which both have access to the list, but I don't want to have to bind the list to every child. What is missing in the example below, is a way to watch the list which gets bound to the parent. I am able to pass the original list in, but as soon as it updates, the child will have an outdated model.
Parent directive:
angular
.module('app.parent', [])
.directive('parent', parent);
function parent() {
var directive = {
restrict: 'EA',
transclude: true,
template: '<div>parent <pre>{{vm.list}}</pre><ng-transclude></ng-transclude> </div>',
scope: true,
controller: ParentController,
controllerAs: 'vm',
bindToController: {
config: "=",
list: "="
}
};
return directive;
function ParentController() {
var vm = this;
}
}
child directive:
angular
.module('app.parent.child', ['app.parent'])
.directive('child', child);
function child() {
var directive = {
restrict: 'EA',
require: ['^^parent', '^child'],
template: '<div>child<pre>{{vm.list}}</pre></div>',
scope: true,
controller: ChildController,
link: linkFunc,
controllerAs: 'vm',
bindToController: {
config: "="
}
};
return directive;
function ChildController() {
var vm = this;
}
function linkFunc(scope, element, attrs, ctrls) {
var parentController = ctrls[0];
var vm = ctrls[1];
vm.list = parentController.list;
}
}
I have made a Plunkr with the code above. I am looking for a nice pattern to solve the issue I am having. Both directives will have their own unique config object passed in with configurations specific to the directive.
You can create a watcher on the child directive's scope object, but rather than watching a scope item, you can pass in a function as the first parameter to $watch() and simply return a value/object that you would like to watch.
So for instance inside your child directive's linkFunc()
scope.$watch(function() {
return parentController.list;
}, function(newList) {
vm.list = newList;
});
Modified your plunkr: http://plnkr.co/edit/6WzT8PQJRH1b5KuU0twn?p=preview
Here is a directive that is loading new Template from file:
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html'
}
})
Next HTML-element calls this directive from the other HTML-Template (e.g. xxx.html):
<candidates-filter></candidates-filter>
There is next controller for this parent Template (xxx.html):
app.controller('candidatesController', function($scope, $location ){
$scope.addPeson = function() {
$location.url('/candidate/0');
};
});
Method addPerson() is not accessible inside the Directive's template AAAA.html, because
data-ng-click="addPerson()"
is not working there. How to change the Directive to make addPerson() method available inside the directive's template?
TEMPORARY Solution
I fixed this issue by next solution
.directive('candidatesFilter', function(){
return {
resctict: 'E',
replace: true,
templateUrl: 'views/directives/filters/AAAA.html',
controller: function(){
$('button.add').on('click',function(){
location.hash = '#/candidate/0';
});
}
}
})
If I understand the problem correctly:
You can pass a function into the directive for it to use
<candidates-filter></candidates-filter>
becomes
<candidates-filter add-candidate="addPerson()"></candidates-filter>
and the directive definition changed as follows:
.directive('candidatesFilter', function() {
return {
resctict: 'E',
replace: true,
scope: {
addCandidate: '&addCandidate'
}
templateUrl: 'views/directives/filters/AAAA.html'
link: function(scope, element, attrs) {
scope.someFunctionInDirective = function() {
scope.addCandidate();
}
};
}
})
Alternatively you can call it with the ng-click like normal from the button
Hope this helps clarify it?
I would like to do something like this
fiddle, making the text disappear and reappear with every click.
Problem is, it seem that with an isolated scope you can't have access to the controller scope. I solved it in the link function, handling there click event and setting my "showMe" flag using scope.$apply, like:
scope.$apply('showMe = false');
Is this the right way to go or there is some more elegant method?
Here you go (http://jsfiddle.net/66d0t7k0/1/)
Put your click handler in the link function and expose showMe to the scope
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-click=\"clickMe()\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
scope.clickMe = function () {
scope.showMe = !scope.showMe;
};
}
};
});
To expand on apairet's answer, since your directive is using an isolated scope, you could handle all of that in the template itself like so:
app.directive('example', function () {
return {
restrict: 'E',
template: '<p ng-show=\"showMe\">Text to show</p><button ng-init=\"showMe = false\" ng-click=\"showMe = !showMe\">Click me</button>',
scope: {
exampleAttr: '#'
},
link: function (scope) {
}
};
});
Another consideration is to use ng-if rather than ng-show as it doesn't render the element in the DOM until the expression evaluates to true.
You can hide the scope in the directive by setting scope: false
You can then put all your function in the main controller scope
angular.module('appMyApp').directive('appMyAppItem', function() {
return {
transclude: true,
templateUrl: 'link/to/url',
controller: 'appMyAppController',
scope: false
}
});
angular.module('appMyApp').controller('appMyAppController', ['$scope', function($scope){
$scope.showItem = true;
$scope.toggleItem = function(){
$scope.showItem = !$scope.showItem;
};
}]);
Hope this helps