I have a fiddle at http://jsfiddle.net/skwidgets/w2X9m/3/ to build out a group of check boxes. It works just as desired in the prototype and I can even include more then one group and they run independently. As I was satisfied I add the code to the app that I am building and the html in the template: '<>' of the directive does not appear. the model data that is in the handle bars in the template does how ever. I cannot seam to figure out why as there are not errors.
I also have this plunker that shows the code working
http://plnkr.co/edit/JE5dRDU4VyGd0UAbCtNu?p=preview
<div class="sqs" >
<pre>
{{survey | json}}
</pre>
<suvey-checkbox ng-model="Q1" ng-init="surveyQuestions = [
{ 'value':'value1', 'name': 'The unit' },
{ 'value': 'value2', 'name': 'Patient Threw Up'},
{ 'value':'value3', 'name': 'Unsafe for children' },
{ 'value':'value4', 'name': 'Actively dying, reached life goal'}]">
</suvey-checkbox>
<suvey-checkbox ng-model="Q2" ng-init="surveyQuestions = [
{ 'value':'value1', 'name': 'The unit' },
{ 'value': 'value2', 'name': 'Patient Threw Up'},
{ 'value':'value3', 'name': 'Unsafe for children' },
{ 'value':'value4', 'name': 'Actively dying, reached life goal'}]">
</suvey-checkbox>
</div>
And
var app = angular.module("app", []);
app.run(function($rootScope){
$rootScope.survey = [];
});
app.directive('suveyCheckbox', function ($rootScope) {
return {
restrict: 'E',
require: '^ngModel',
scope: {
ngModel: '#'
},
template: '{{ngModel}}<br /><label ng-repeat="surveyQuestion in surveyQuestions" class="checkbox">' +
'<input data-role="none" type="checkbox" name="selectedExclusion[]" value="{{surveyQuestion.value}}"' +
'ng-checked="surveyAnswers.indexOf(surveyQuestion.value) > -1" ng-click="togglesurveyAnswers(surveyQuestion.value)"> ' +
'{{surveyQuestion.name}} <br /></label>{{surveyAnswers}}',
link: function (scope, elem, attr) {
// selected exclusion
scope.surveyAnswers = [];
// toggle surveyAnswers for a given answer by name
scope.togglesurveyAnswers = function togglesurveyAnswers(surveyQuestion) {
var idx = scope.surveyAnswers.indexOf(surveyQuestion);
// is currently selected
if (idx > -1) {
scope.surveyAnswers.splice(idx, 1);
}
// is newly selected
else {
scope.surveyAnswers.push(surveyQuestion);
}
};
$rootScope.survey.push(scope.surveyAnswers);
}
}
});
I took the code from the prototype in the fiddler that is linked and that is not working for some reason.
The code will not work in newer versions of angular. In previous versions there was a bug that caused isolate scope to not be really isolate and leak out to other directives.
In angular 1.2.x isolate scope is truly isolate. This means that you cannot use anything in your template unless the directive either put it manually into the scope or it is included in the isolate scope definition (e.g. scope: { myVar: '='})
For example, in your case your template cannot access surveyQuestions because it was not defined in the private scope (ng-init will not be able to access the isolate scope inside of your surveyCheckbox directive.
If you want to be able to use surveyQuestions in your directive's template you need to pass it in to the isolate scope:
<survey-checkbox questions="surveyQuestions" ng-model="bedsideQ2" ng-init="surveyQuestions = [
{ 'value':'value1', 'name': 'The unit' },
{ 'value': 'value2', 'name': 'Patient Threw Up'},
{ 'value':'value3', 'name': 'Unsafe for children' },
{ 'value':'value4', 'name': 'Actively dying, reached life goal'}]">
</survey-checkbox>
And then add it to your isolate scope:
scope: {
ngModel: '#',
surveyQuestions: '=questions'
}
Related
I'm trying to build a feed of posts, where the post object specifies a type value, which then specifies a template for the post. I got a custom directive built but I'm having issues.
JS
myApp.controller('FeedCtrl', function ($scope, $http) {
$scope.posts = [
{'title': 'Post 1',
'type': 'post',
'snippet': 'Vim id noster platonem definitionem...',
}
];
});
myApp.directive('myPost', function () {
return {
restrict: 'E',
scope: {
title: '=',
type: '=',
snippet: '=',
},
templateUrl: function(elem, attr){
return './templates/'+attr.type+'.html'
}
};
});
HTML
<div ng-controller="FeedCtrl">
<div ng-repeat="post in posts">
<my-post title='post.title' type='post.type' snippet='post.snippet'></my-post>
</div>
</div>
doesn't work... but when I change the type attr to
<my-post type="post"></my-post>
it works and passes all of the scope data into the post.html template. What's the difference? Why doesn't it pass 'post.type' over to the directive, but it passes everything else just find?
Try this way:
<my-post title='post.title' type='{{post.type}}' snippet='post.snippet'></my-post>
And change this:
scope: {
title: '=',
type: '#',
snippet: '=',
}
The template is requested before the scope is initialized. You could remove the type from the isolate scope and change the HTML to:
<my-post title='post.title' type='{{post.type}}' snippet='post.snippet'></my-post>
as #karaxuna suggested.
I have a treeview directive credit to http://codepen.io/bachly/pen/KwWrzG for being my starting block. that I am trying to update when I add objects to the collection. I can update the object and insert the new objects but the treeview directive is never called once the $scoped item is updated.
Ultimately the data used will come from a service at this point I am just testing with mock data.
The original collection looks like this
$scope.myList = {
children: [
{
name: "Event",
children: [
{
name: "Event Date",
parent:"Event",
children: [
{
name: "2008",
filterType: '_eventStartDate',
parent: 'Event'
},
{
name: "2009",
filterType: '_eventStartDate',
parent: 'Event'
}
]
},
{
name: "Event Attendee",
parent: "Event",
children: [
{
name: "Person 1",
filterType: '_eventAttenddeeName',
parent: 'Event Attendee'
},
{
name: "Person 2",
filterType: '_eventAttenddeeName',
parent: 'Event Attendee'
}
]
}
]
}]
};
var TheOtherCollection = {
children: [
{
name: "A New Event",
children: [
{
name: "The Other Date",
parent: " A New Event",
children: [
{
name: "2010",
FilterType: '_eventStartDate',
Parent: '_event'
},
{
name: "2011",
FilterType: '_eventStartDate',
Parent: '_event'
}
]
}
]
}]
};
This generates a tree view with checkboxes using the following directive and html
app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&'
},
controller: 'treeController',
template: '<ul><branch ng-repeat="c in t.children track by $index" src="c" filter="doSomething(object, isSelected)"></branch></ul>'
};
});
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&',
checked: '=ngModel'
},
template: '<li><input type="checkbox" ng-click="innerCall()" ng-model="b.$$hashKey" ng-change="stateChanged(b.$$hashKey)" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var clicked = '';
var hasChildren = angular.isArray(scope.b.children);
scope.visible = hasChildren;
if (hasChildren) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (hasChildren) {
element.toggleClass('collapsed');
}
});
scope.stateChanged = function(b) {
clicked = b;
};
scope.innerCall = function() {
scope.filter({ object: scope.b, isSelected: clicked });
};
}
};
});
And then the html
<div ng-controller="treeController">
<tree src="myList" iobj="object" filter="doSomething(object, isSelected)"></tree>
<a ng-click="clicked()"> link</a>
</div>
When a checkbox is clicked the new collection is added to the existing one using lodashjs
ng-click event
$scope.doSomething = function (object, isSelected) {
if (isSelected) {
var item = object;
console.log(item);
nestAssociation(object, $scope.myList, TheOtherCollection);
}
}
which creates the new array and adds it within the children array
function nestAssociation(node, oldCollection, newAggregates) {
// var item = fn(oldCollection, node.parent);
var updatedArray = _.concat(oldCollection.children, newAggregates);
console.log(updatedArray);
if (updatedArray != null)
updateMyList(updatedArray);
}
I can see in the output I have a new object but I can't get the treeview to update. I have tried within the directive to add a $compile(element) on the click event in the directive but since the array is not built yet nothing changes.
Do I need to add a $watch to this directive and if so where or is there some other way I can get the directive to re-render and display the new nested collection?
Update
Base on some of the feedback and questions here is a little more detail around the question. The issue I am seeing is not in the directive as far as moving data around the issue is I cannot get the treeview to re-render once an array is added to the existing model.
The following link is a working plunker that shows the project as it currently works.
Running chrome dev tools I can see in the output the model is updated after a checkbox is selected
While I see the object is updated, the directive never updates to show the new array added to the object. This is the part that I need help understanding.
thanks in advance
You pass the function to the inner directives (which is the best practice), but you have access to scope.filter. Not doSomethingFunction. This one is undefined there.
filter="doSomething(object, isSelected)"
=>
filter="filter(object, isSelected)"
app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&'
},
controller: 'treeController',
template: '<ul>
<branch ng-repeat="c in t.children track by $index"
src="c" filter="filter(object, isSelected)">
</branch>
</ul>'
};
});
Next :
You can never access $$ variables in angularJS, because they are private. Maybe you should make one from your DB..., but the $$hashkey seems a easy solution though.
checked attribute might throw an error, because ngModel does not exist on your tree directive template. (put at least a ? before)
A checkbox can not have as model a $$hashkey.
Ng-change and ng-click will always be called at the same time, use the simplest one.
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&'
},
template: '<li><input type="checkbox" ng-click="innerCall(b.$$hashKey)" ng-model="isChecked" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
scope.isChecked = false;
var hasChildren = angular.isArray(scope.b.children);
scope.visible = hasChildren;
if (hasChildren) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (hasChildren) {
element.toggleClass('collapsed');
}
});
scope.innerCall = function(hash) {
if(scope.isChecked){
scope.filter({ object: scope.b, isSelected: hash });
}
};
}
};
});
UPDATE
You have the same treeController in your tree directive and in your index.html view.
This is what causes the view not to update!
I deleted the one in your directive, otherwise you'll have a controller for each child.
You saw the good console.log message in your controller, but it was in a controller for ONE directive.
You were not accessing the controller of the index.html.
Then I fixed the filter function communication between childs :
You forgot to communicate the filter function when you append new tree's :
element.append('<tree src="b" filter="filter({ object: object, isSelected: isSelected })"></tree>');
Also, in your parent directive template, you also need the hash to send parameters to the function :
filter="filter({ object: object, isSelected: isSelected })"
I edited your Plunker HERE without changing the code with the above comments I made.
(I'm not sure what you write is not what you want and because you did not comment I rather not change it so you still undertand your code fast)
But the view is updating now!
I think a little debug with what you want and the comments above should be enough.
EDIT 2
You forgot to return an object with the property chrilden. You returned an array, which caused the problem.
function updateMyList(data) {
var transformed = { children : data };
$scope.myList = transformed;
}
Here is a working PLUNKER.
Try $scope.$apply() after adding
i am newly using valdr in angualrjs . i m making validation directive using valdr for my application. i don't want to write addconstaints and addvalidator functions with json inside app.config file. i want to write that part inside the directive only ? can anyone solve this problem...?
var app = angular.module('app', ['valdr']);
app.config(function (valdrProvider) {
valdrProvider.addConstraints({
'Person': {
'firstname':
{
'size': {
'min': 3,
'max': 12,
'message':'firstname should be between 3 to 12 '
},
'required': {
'message': 'This field is required.'
}
},
'firstName':
{
'customValidator': {
'message': 'First name must be Hanueli.'
}
}
}
});
});
app.directive('info', function () {
return {
restrict: 'E',
templateUrl: 'tmpl.html',
require: '^?valdrProvider',
link: function ($scope, valdrProvider) {
$scope.Person = {};
$scope.$watch(valdrProvider.getConstraints, function (newContraints) {
$scope.constraints = newContraints;
})
}
};
});
I don't see how that could be useful but you can of course call valdrProvider.addConstraints() whenever you see fit. In link: function ($scope, valdrProvider) you get access to the provider.
The issue is with the naming. Angular provides two ways to configure providers.
1. during the configuration phase - here you have to append the "Provider" suffix.
2. during runtime - here you just get the service.
Change valdrProvider to valdr and you can addConstraints in your service/controller
I have a directive that represents a group of objects, let's call it people.
This directive has an ng-repeat in its template that repeats a child directive, e.g. person, which has an expression attribute personGreeting which should evaluate on its scope.
Both people and person use isolate scope.
How can I set up these directives such that I can expose personGreeting on the people directive and have it be evaluated within the scope of the person directive?
Here is an example:
angular.module('app', [])
.controller('ctrl', function($scope) {
$scope.myPeople = [{
id: 1,
name: 'Bob'
}, {
id: 2,
name: 'Steve'
}, {
id: 3,
name: 'Joe',
}]
})
.directive('people', function() {
return {
scope: {
peopleList: '=',
eachPersonGreeting: '&'
},
template: '<ul><person ng-repeat="currentPerson in peopleList" person-greeting="eachPersonGreeting(currentPerson)"></person></ul>'
}
})
.directive('person', function() {
return {
scope: {
personDetails: '=',
personGreeting: '&'
},
template: '<li>{{personGreeting(personDetails)}}</li>'
}
})
.directive('people2', function() {
return {
scope: {
peopleList: '=',
eachPersonGreeting: '#'
},
template: '<ul><person-2 ng-repeat="currentPerson in peopleList" person-greeting="{{eachPersonGreeting}}"></person-2></ul>'
}
})
.directive('person2', function() {
return {
scope: {
personDetails: '=',
personGreeting: '#'
},
template: '<li>{{personGreeting}}</li>'
}
})
<!DOCTYPE html>
<html ng-app="app">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.min.js"></script>
</head>
<body ng-controller="ctrl">
<h4>Here we are just using an in-line expression with ng-repeat, which works as you'd expect:</h4>
<ul>
<li ng-repeat="currentPerson in myPeople">Hello, {{currentPerson.name}}!</li>
</ul>
<h4>But what if we have custom directives, `people`, and `person`, and we want to let consumers of our `people` directive specify how each `person` should be greeted without making them override our directive's template, and also have data binding still work?</h4>
<h4>Unfortunately, this doesn't seem to work:</h4>
<people people-list="myPeople" each-person-greeting="'Welcome, ' + personDetails.name + '!'"></people>
<h4>Neither does this:</h4>
<people-2 people-list="myPeople" each-person-greeting="Welcome, {{personDetails.name}}!"></people-2>
</body>
</html>
I also started delving into the compile, link and controller functions for both of the directives, as well as the $interpolate service, and got it kind of working, but it got really weird and messy, and I couldn't get data binding working, so it felt like wasted effort. I feel like this should be simple, but it doesn't seem to be.
Is there an elegant way to do this?
How the greeting depends on the user itself?
Is there any way to create a service that knows how to produce customised greeting?
If not, what about Proxy object? https://www.youtube.com/watch?v=AIO2Om7B83s&feature=youtu.be&t=15m1s
I've discovered it yesterday and, from my perspective, it seems that it fits here. You should create proxy object for each guest, inject it to the guest directive and during the link phase put the parsed (done by angular) greeting into the that injected proxy object.
Also, I think that something you're doing can be done on much simpler way than setting the attribute from the outside and parsing that.
Okay I managed to put together a simple guestbook app using the same concept that works: http://plnkr.co/edit/R7s6xE?p=info
angular.module('guestbookApp', [])
.controller('guestbookCtrl', function($scope) {
$scope.latestGuests = [{
id: 1,
name: 'Bob'
}, {
id: 2,
name: 'Steve'
}, {
id: 3,
name: 'Joe',
}];
$scope.newGuest = {
name: ''
};
$scope.addGuest = function() {
$scope.latestGuests.push(angular.extend(angular.copy($scope.newGuest), {
id: $scope.latestGuests.length + 1
}));
$scope.newGuest.name = '';
};
})
.directive('guestList', function($parse) {
return {
scope: {
guests: '='
},
template: '<ul><li guest ng-repeat="currentGuest in guests | limitTo: -5" guest-details="currentGuest"></li></ul>',
controller: function($scope, $element, $attrs) {
this.greeting = function(scope) {
return $parse($attrs.greeting)(scope);
};
}
};
})
.directive('guest', function($parse) {
return {
scope: {
guestDetails: '='
},
template: '{{greeting}}',
require: '?^guestList',
link: function(scope, element, attrs, controller) {
var updateGreeting;
if (controller) {
updateGreeting = function() {
scope.greeting = controller.greeting(scope);
};
} else if (attrs.greeting) {
updateGreeting = function() {
scope.greeting = $parse(attrs.greeting)(scope);
};
}
scope.$watch('guestDetails.name', function() {
updateGreeting();
});
}
};
});
<!DOCTYPE html>
<html ng-app="guestbookApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.5/angular.js"></script>
</head>
<body ng-controller="guestbookCtrl">
<h4>Latest guests:</h4>
<guest-list guests="latestGuests" greeting="'Welcome, ' + guestDetails.name + '!'"></guest-list>
<h4>Type your name below to sign the guestbook:</h4>
<input type="text" ng-model="newGuest.name" />
<button ng-click="addGuest()" ng-disabled="!newGuest.name">Sign</button>
<div ng-if="newGuest.name">
<p guest guest-details="newGuest" greeting="'Hello, ' + guestDetails.name + '! Click \'Sign\' to sign the guestbook!'"></p>
</div>
</body>
</html>
If anyone has any suggestions on how this could be improved please let me know! I still feel kind of yucky about the use of $parse and $watch, but maybe it's unavoidable?
I have a directive that uses the $compile service to generate a template. It won't generate the select options using ng-options or ng-repeat, even though I clearly have the users array set in my scope. Why doesn't this work? This just gives me a blank <select> field with no options.
angular.module("myApp").directive("selectForm", [
'$compile',
function($compile) {
return {
restrict: 'C',
scope: true,
link: function(scope, element, attrs) {
scope.users = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" }
];
element.on('click', function(e) {
var selectHtml = $compile('\
<select\
class="col-lg-2 form-control"\
ng-options="user.id as user.name for user in users">\
</select>\
')(scope);
$(element).html(selectHtml);
});
}
}
}
]);
There is something to change in your code to make it work:
<select> works only when you also supply ng-model
Wrap your code inside scope.$apply.
Call element.off("click") to unsubscribe to the event to avoid flickering.
DEMO