I am using jquery steps in my angularjs app. I have used a custom directive to init the jquery plugin. Now I need to validate all input once the finish button is clicked on the final steps of the form. In order to do that I know there is a option which needs to be set called onFinished. Now how do I call my controller method in this section?
app.directive('step', [function() {
return {
restrict: 'EA',
scope: {
stepChanging: '='
},
compile: function(element, attr) {
element.steps({
labels: {finish: "SUBMIT"},
headerTag: "h3",
bodyTag: "section",
transitionEffect: "slideLeft",
stepsOrientation: "vertical",
onFinished: function (event, currentIndex) {
console.log("submit button has been clicked");
$scope.validator(); //problem here
}
});
return {
//pre-link
pre:function() {},
//post-link
post: function(scope, element) {
//element.on('stepChanging', scope.stepChanging);
}
}
}
};
}])
you directive is isolated scope , it cannot access controller scope , if u want to pass functions you can use & in scope object of your directive like below
scope:{
validator:'&'
}
and in your directive pass this function as below
<step validator='validator'/>
To pass events from a directive with isolate scope to parent controller, use expression & binding.
During the directive compile phase, there is no scope for a function use.
Move the function to the postLink:
app.directive('step', function() {
return {
restrict: 'EA',
scope: {
//stepChanging: '=',
stepChanging: '<',
//USE expression binding
validator: '&'
},
//compile: function(element, attr) {
link: function postLink(scope,element,attrs) {
element.steps({
labels: {finish: "SUBMIT"},
headerTag: "h3",
bodyTag: "section",
transitionEffect: "slideLeft",
stepsOrientation: "vertical",
onFinished: function (event, currentIndex) {
console.log("submit button has been clicked");
//$scope.validator(); //problem here
scope.validator();
}
);
}
/*
return {
//pre-link
pre:function() {},
//post-link
post: function(scope, element) {
//element.on('stepChanging', scope.stepChanging);
}
}*/
}
};
});
Usage:
<step step-changing="vm.changing" validator="validator()">
</step>
Moving forward, avoid using two-way = binding. Instead use one-way < binding. It is more efficient and provides a better path for migrating to Angular 2+.
Also avoid closing element directives with /> it causes problems with some browsers. Instead use a closing tag </step>.
Related
I am creating drag and drop functionality by creating a <dragItem> directive and a <droptTraget> directive, but I don't understand yet how to work with the inner and out scope in this way.
Here are my directives. The events triggers the functions properly, I just want the on dragstart event to store a value of the drag element and the drop event to trigger the function testAddSet() which adds the drag value to my model.
drag
angular.module('app.directives.dragItem', [])
.directive('dragItem', function(){
return { // this object is the directive
restrict: 'E',
scope: {
excercise: '='
},
templateUrl: "templates/dragTile.html",
link: function(scope, element, attrs){
element.on('dragstart', function (event) {
var dataVar = element.innerText;
// It's here that I want to send a dataVar to the $scope
});
}
};
});
drop
angular.module('app.directives.dropTarget', [])
.directive('dropTarget', function(){
return { // this object is the directive
restrict: 'E',
scope: {
day: '='
},
templateUrl: "templates/calDay.html",
link: function(scope, element, attrs){
element.on('drop', function (event) {
event.preventDefault();
// It's here that I'd like to take the value from the drag item and update my model
testAddSet() // doesn't work
$parent.testAddSet() // doesn't work
});
element.on('dragover', function (event) {
event.preventDefault();
});
}
};
});
Since you are using isolate scope, you need to define an attribute for the function binding.
angular.module('app.directives.dropTarget', [])
.directive('dropTarget', function(){
return { // this object is the directive
restrict: 'E',
scope: {
day: '=',
//Add binding here
testAddSet: '&'
},
templateUrl: "templates/calDay.html",
link: function(scope, element, attrs){
element.on('drop', function (event) {
event.preventDefault();
//Invoke the function here
scope.testAddSet({arg: value, $event: event});
});
element.on('dragover', function (event) {
event.preventDefault();
});
}
};
});
In your template, connect the function using the directive attribute.
<drop-target test-add-set="fn(arg, $event)" day="x"></drop-target>
For more information on isolate scope binding, see AngularJS $compile Service API Reference - scope.
I recommend that the event object be exposed as $event since that is customary with AngularJS event directives.
$event
Directives like ngClick and ngFocus expose a $event object within the scope of that expression. The object is an instance of a jQuery Event Object when jQuery is present or a similar jqLite object.
-- AngularJS Developer Guide -- $event
I think the easiest way to get your cross-directive communication is to make a scope variable on the host page and then pass it double-bound ('=') to both directives. That way, they both have access to it as it changes.
I want to create a directive as a component, such that its not dependent on any controllers as such.
I have been trying to find out how to get a button click listener defined. But couldnt suceed yet.
angular.module('nestedDirectives', [])
.directive("parent", function () {
function linker(scope, element, attribute, controllers) {
console.log("linker called");
element.on("click", function clicked(event) {
console.log("clicked");
console.dir(this);
element.prepend("<h1>Hello</h1>");
});
}
return {
restrict: 'A',
template: '<div><h5>An Item</h5><button ng-click="clicked()">Click Me</button></div>',
link: linker,
scope: {}
}
})
In the template, no matter what i click the element.on("click") would get called. I want to call a clicked() method when button is clicked.
Here is the Plunker for the same.
The link function gets the scope (an isolated scope in your case) as the first argument, so you can do something like:
.directive("parent", function () {
function linker(scope, element, attribute, controllers) {
console.log("linker called");
//add the "clicked" function to your scope so you can reference with ng-click="clicked" in your template
scope.clicked = function() {
console.log("clicked");
console.dir(this);
element.prepend("<h1>Hello</h1>");
};
}
return {
restrict: 'A',
template: '<div><h5>An Item</h5><button ng-click="clicked()">Click Me</button></div>',
link: linker,
scope: {}
};
});
Here is your updated plunkr http://plnkr.co/edit/7mlcSB4phPO5EdEQqTj0
I have a custom directive:
.directive('test', function () {
return {
scope: {},
link: function (scope, element, attr) {
scope.$parent.$watch(attr.selectedItem, function(newValue, oldValue){
scope.selectedItem = newValue;
});
}
}
This will one way bind my directive's scope's selectedItem property to the value set in the attribute as such
<div test selectedItem="thePropertyOnTheController"></div>
But what if I want to two way bind? Is there an easy way to set this up without $watch'ing the directive's scope's selectedItem property and $parse'ing the attr.selectedItem expression and calling assign witht he parsed expression on scope.$parent?
$scope.thePropertyOnTheController might have some value like "Hello"
HTML
<div ng-repeat="photosets in userPhotoSetList">
<photosets photosetsarray="photosets.photosetDetail">
</div>
script :
.directive('photosets', function () {
return {
scope: {
photosetslist : "=photosetsarray"
},
link: function (scope, element, attr) {
console.log(scope.photosetslist);
//"Hello" is output
}
}
If you see photosetsarray="photosets.photosetDetail"" photosetsarray and
scope: {
photosetslist : "=photosetsarray" **//this name is same as assignee attr**
},
leftside variable name in html must = right side variable name in directive
Be careful with variable naming in these situations. Binding to an attribute that is declared as camel case in the directive cannot be accessed as such from the DOM.
.directive('test', function () {
return {
scope: {
item : "=selectedItem"
},
link: function (scope, element, attr) {
//do some stuff
}
}
So to correctly bind this attribute to a variable on the controller:
<div test selected-item="thePropertyOnTheController"></div>
I am trying to load a 'class' directive using ng-class. but my directive is never loaded when i do that. The directive is a multipurpose directive, and I don't want to create an isolated scope on this. it will be loaded only when required, based on ng-class conditions hence not using attribute or element directive. has anyone tried doing this and succeeded?
this directive is called using <div ng-class="someClass {{tooltip: enabled}}"></div>
here enabled is a scope variable.
app.directive('tooltip', ['$timeout', '$location', '$rootScope', function (timer, $location, $rootScope) {
return {
restrict: 'C',
transclude: true,
link: function (scope, element) {
var printContent = function () {
/* uses the content of .tooltip-content if it is a complex html tooltip,
otherwise
you can use the title attribute for plaintext tooltips
*/
var tooltipContent = $(element).find('.tooltip-content').html();
if (!tooltipContent) {
tooltipContent = $(element).attr('title');
}
$(element).tooltip({
content: tooltipContent,
items: "img, a, span, button, div",
tooltipClass: "tooltip",
position: { my: "left+30 top", at: "right top", collision: "flipfit" },
show: { effect: "fadeIn", duration: "fast" },
hide: { effect: "fadeOut", duration: "fast" },
open: function (event, ui) { $rootScope.tooltipElement = event.target; }
});
};
timer(printContent, 0);
}
};
}]);
Interesting issue. It seems that you don't want to use the ng-class directive since that doesn't recompile the content after adding the class. You'll likely want to create your own dynamic-class directive that actually recompiles when the value is true:
app.directive('dynamicClass', function($compile) {
return {
scope: {
dynamicClassWhen: '=',
dynamicClass: '='
},
link: function(scope, elt, attrs) {
scope.$watch('dynamicClassWhen', function(val) {
if (val) {
console.log(val);
elt.addClass(scope.dynamicClass);
$compile(elt)(scope);
}
});
}
};
});
You may need to modify this for the ability to remove the class and depending on if the $compile is sufficient for you or if you need to further manipulate the html, but this seems to be the right track for you. I made a fiddle with this in action.
Hope this helps!
I have a custom directive:
.directive('myDirective', function() {
return {
scope: {ngModel:'='},
link: function(scope, element) {
element.bind("keyup", function(event) {
scope.ngModel=0;
scope.$apply();
});
}
}
});
This works as planned, setting the variables to 0 on keyup, but it doesn't reflect the changes on the input themselves. Also when initialized, the values of the model are not in the input. Here is an example:
http://jsfiddle.net/prXm3/
What am I missing?
You need to put a watcher to populate the data since the directive creates an isolated scope.
angular.module('test', []).directive('myDirective', function () {
return {
scope: {
ngModel: '='
},
link: function (scope, element, attrs) {
scope.$watch('ngModel', function (val) {
element.val(scope.ngModel);
});
element.bind("keyup", function (event) {
scope.ngModel = 0;
scope.$apply();
element.val(0); //set the value in the dom as well.
});
}
}
});
Or, you can change the template to
<input type="text" ng-model="$parent.testModel.inputA" my-directive>
the data will be populated thought it will break your logic to do the event binding.
So it is easier to use the watcher instead.
Working Demo