Using SelectBoxIt in AngularJS Directive - javascript

I want to implement SelectBoxIt in my angularJS directive. Here is my directive's template html snippet(lookup.template.html):
<select class="selectBoxit" ng-model="ngModel">
<option value="" disabled> {{placeholder}}</option>
<option value='{{a.LookupCode}}' ng-repeat='a in lookups'>{{a.Name}}</option>
</select>
Isolated scope directive:
.directive('lookup', function(){
return {
restrict: 'E',
scope: {
lookups: "=lookups",
ngModel: "=ngModel"
},
templateUrl: 'templates/lookup.template.html',
link: function (scope, element, attrs) {
scope.placeholder = attrs['placeholder'];
$(".selectBoxit").selectBoxIt().on('open', function()
{
//ScrollBar
$(this).data('selectBoxSelectBoxIt').list.perfectScrollbar();
});
}
}
});
Lookup Controller:
.controller('LookupController', function($scope){
$scope.schoolTypeCode = 'GNR';
$scope.schoolTypes = [{ Name: "Kinder Garten", LookupCode: "KG" },
{ Name: "Elemetary", LookupCode: "ELM" },
{ Name: "High School", LookupCode: "HSC" },
{ Name: "Preparatory", LookupCode: "PRP" },
{ Name: "General", LookupCode: "GNR" },
{ Name: "Distance", LookupCode: "DST" }];
});
and finally view using directive:
<lookup id="cboSchoolType" lookups="schoolTypes"
ng-model="SchoolTypeCode" placeholder="Select School Type"></lookup>
SelectBoxIt initialization worked fine but I noticed two problems. First, default value(ngModel) is not assigned and second, initialization is happenening before angular repeat finishes populating the options.This will result an empty select list and the following error message when it is clicked:
Uncaught TypeError: Cannot read property 'list' of undefined
So, are there any other ways to set the default value and trigger SelectBoxIt initialization after options are populated by angular repeat?
Thanks.

try the "dropdown" instead of "list"
so instead of
$(this).data('selectBoxSelectBoxIt').list.perfectScrollbar();
try this line in your event function
$(this).data('selectBoxSelectBoxIt').dropdown.perfectScrollbar();

Related

Update an angular treeview directive when a new object is added to the collection

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

Calling a function of parent controller with nested directives using isolated scope

I have a a treeview directive that I am trying to invoke a function from the parent controller and I can't seem to get the function to get called. I'm not sure if it's due to the structure of the treeview and nesting of child elements or what.
Within my html I have the directive declared as:
<div ng-controller="treeController as vm">
<tree src="myList" filter="doSomething()"></tree>
<a ng-click="clicked()"> link</a>
</div>
I declared in the directive an attribute/parameter filter which should call the doSomething() function within the main controller.
The main controller contains the following code (test to build the tree as well as invoke the function.
app.controller("treeController", ['$scope', function($scope) {
var vm = this;
$scope.doSomething = function () {
var item = data;
}
$scope.clicked = function () {
alert('clicked');
}
$scope.myList = {
children: [
{
name: "Event",
children: [
{
name: "Event Date",
children: [
{
name: "2008",
FilterType: '_eventStartDate',
Parent: '_event'
},
{
name: "2009",
FilterType: '_eventStartDate',
Parent: '_event'
}
]
},
{
name: "Event Attendee",
children: [
{
name: "Person 1",
FilterType: '_eventAttenddeeName',
Parent: '_Event'
},
{
name: "Person 2",
FilterType: '_eventAttenddeeName',
Parent: '_Event'
}
]
}
]
}]
};
}]);
The within my directive I declare the isolated scope, as well as the parameter filter (second app.directive) which I prefix with the model binding prefix '&'. Within the template I then call ng-click which should invoke the function doSomething() within the main controller. However... no dice.
app.directive('tree', function() {
//builds the tree
return {
restrict: 'E',
replace: true,
scope: {
t: '=src'
},
template: '<ul><branch ng-repeat="c in t.children" src="c"></branch></ul>'
};
});
app.directive('branch', function($compile) {
//directive that builds the children/branches
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&'
},
template: '<li><input type="checkbox" ng-click="filter()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
//test to call function within directive
//scope.doSomething = function(b) {
// alert('test');
//}
}
};
});
I posted a public jsFiddle with the working code sample as well
Any suggestions on what I missed?
Right now I am just trying to invoke the method however ultimately I will need to pass as a parameter the selected item back to the controller as well but for now I'm just trying to figure out why the function in my controller will not get called.
Thanks in advance
Update:
It was suggested to move the declaration of the filter from the branch to the tree directive.
I updated my code locally so the tree directive looked like the following:
app.directive('tree', function() {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&'
},
template: '<ul><branch ng-repeat="c in t.children" src="c"></branch></ul>'
};
});
Note: the filter parameter was removed from the secondary directive. There was no change in the output. The function within the controller was still not called.
your tree directive does not have filter method.your branch directive only has that property
<div ng-controller="treeController as vm">
<tree src="myList" filter="doSomething()"></tree>
<a ng-click="clicked()"> link</a>
</div>
app.directive('tree', function() {
//builds the tree
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&'
},
template: '<ul><branch ng-repeat="c in t.children" src="c" filter="doSomething()"></branch></ul>'
};
});
app.directive('branch', function($compile) {
//directive that builds the children/branches
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&'
},
template: '<li><input type="checkbox" ng-click="filter()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
//test to call function within directive
//scope.doSomething = function(b) {
// alert('test');
//}
}
};
});
Update:
Sundar's comments got me down the right path here is the updated directive the main issue for me was that I am working with nested directives so the nested item (which was making the function call) was out of scope of the controller to correct this included Sundar's changes but to get the nested directive to work I had to explicitly set the controller at the parent directive level. I realize this is not a good option if you needed a directive to be used in multiple areas of an app. However for me the directive is only used in one spot so this solution works. If anyone has any other suggestions or better approaches I'd appreciate them.
app.directive('tree', function() {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&'
},
controller:'treeController', //explicitly set the controller of the parent directive
template: '<ul><branch ng-repeat="c in t.children" src="c" filter="doSomething(data)"></branch></ul>'
};
});
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&'
},
template: '<li><input type="checkbox" ng-click="innerCall()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
scope.innerCall = function() {
scope.filter(scope.b);
}
}
};
});

angular-js reuseable directives with two way binding

I am trying to create a few different directives that will work as search / filter tools for different parts of my application.
For this purpose i have created the following directive code:
app.directive("lbFilterDivision", ['divisionService', function (divisionService) {
return {
restrict: "E",
templateUrl: 'tpl/directives/lb-filters/lbFilterDivision.html',
scope: {
model: '='
},
link: function (scope, element, attr) {
scope.divisions = [];
divisionService.getList().then(function (result) {
scope.divisions = result;
})
}
};
}]);
The template attached to this is:
<select class="form-control"
ng-model="model"
ng-options="item.id as item.name for item in divisions"
fix-select-null="">
<option value="" translate="FORMS.DIVISION_PLACEHOLDER"></option>
Okay first of all let me explain the main idea.
The idea is that you have a search variable that will be passed to the directive. Then the two way binding should notify up through the system.
So say for instance i have the following HTML:
<lb-filter-division model="search.division.id"></lb-filter-division>
<li ng-repeat="user in users | filter:search"> </li>
As you can see i set the model = to search.division.id which means every time i change selected variable it should update the search.division.id variable and filter the list.
Sadly this is not the case.
Can anyone see what ive done wrong?
Edit - I found the answer. Apprently there was a syntax error in my code. Im so sorry! i will leave this code here if someone gets the same idea as my self.
Here is a fiddle:
fiddle
Solved the problem.
If you wish to copy or are looking to solve the same issue i can refer to this fiddle i made:
var myApp = angular.module('myApp',[]);
function MyCtrl($scope) {
$scope.name = 'Superhero';
$scope.users = [
{id: 1, name: "div1", division:{id: 1, name: 'hello'}},
{id: 2, name: "div2", division:{id: 2, name: 'hello2'}},
{id: 3, name: "div3", division:{id: 3, name: 'hello3'}}
]
}
myApp.directive("lbFilterDivision", function () {
return {
restrict: "E",
scope: {
model: '='
},
template: '<select ng-model="model" ng-options="item.id as item.name for item in divisions"></select>',
link: function (scope, element, attr) {
scope.divisions = [{id: 1, name:'hello'},{id: 2, name:'hello2'},{id: 3, name:'hello2'}];
}
};
});
fiddle
Good luck!

How to fix unexpected arguments passing from a function being on ng-change for select list?

http://plnkr.co/edit/fFSoLmPFDBfNc2oOczZr?p=preview
Directive code
.directive('inputSelect', function() {
return {
templateUrl: 'someTemplate.html',
restrict: 'E',
scope: {
ngModel: '=',
ngChange: '&',
options: '='
}
};
})
Controller code
$scope.someFunction = function(name) {
console.log(name)
};
$scope.colors = [{
name: 'black',
shade: 'dark'
}, {
name: 'white',
shade: 'light',
notAnOption: true
}, {
name: 'red',
shade: 'dark'
}];
Template code
<select ng-model="ngModel" ng-change="ngChange()"
ng-options="option.name for option in options">
</select>
Code for directive usage
<input-select ng-model="someModel" ng-change="someFunction(someModel.name)" options="colors"></input-select>
So, the arguments being passed to someFunction() is being undefined or it contains correct value, the behaviour is unexpected and random.
Your template should call a method by passing a parameter in JSON format like ng-change="ngChange({someModel: ngModel})" from directive
Make sure while calling function from directive you should pass parameter with the key should be function parameter name as here it is someModel and then pass the value like here its ngModel
Markup
<input-select ng-model="someModel" ng-change="someFunction(someModel)" options="colors"></input-select>
Directive template
<select ng-model="ngModel" ng-change="ngChange({someModel: ngModel})"
ng-options="option.name for option in options">
</select>
Working Plunkr

Angular $compile on scope with ng-repeat doesn't work

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

Categories

Resources