$scope not update from AngularJS directive - angularjs

I implemented custom directive for if select element, in which if there is only one item then it selects that item by default else it works as regular drop down which works fine.
My problem is I have two drop down as cascade. A Company list and a Department list. The Department list depends on a Company list. So when only one Company is selected by default then Department list will also update respectively by default.
For that I try to use ng-change but It does not access updated value it shows old value in log.
My angularjs code is below:
angular.module('TestApp', [
]);
angular.module('TestApp').directive('advanceDropdown', function () {
var directive = {}
directive.restrict = 'A';
directive.require = 'ngModel';
directive.scope = {
items: '=advanceDropdown',
model: '=ngModel',
key: '#key',
change: '&ngChange'
}
directive.link = function (scope, element, attrs, ctrl) {
scope.$watch('items', function (n, o) {
if (scope.items.length == 1) {
scope.model = scope.items[0][scope.key];
scope.change();
}
});
}
return directive;
});
(function () {
'use strict';
angular.module('TestApp').controller('TestCtrl', ['$scope', TestCtrl]);
function TestCtrl($scope) {
$scope.departmentList = [];
$scope.companyList = [];
$scope.designation = new Designation();
$scope.active = null;
$scope.BindDepartments = function () {
console.log('updated companyId:' + $scope.designation.CompanyId);
//Code here
//Load department base on selected company
//$scope.departmentList = data;
}
$scope.GetCompanyById = function (companyId) {
//Get company data by id
//recived from server
$scope.companyList = [{CompanyId:2,CompanyName:'xyz'}];
}
$scope.Init = function () {
$scope.active = null;
$scope.designation = new Designation();
$scope.GetCompanyById(2);
}
}
})();
function Designation() {
this.DesignationId = 0;
this.DesignationName = '';
this.DepartmentId = 0;
this.CompanyId = 0;
}
Designation.prototype = {
constructor: Designation
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.2/angular.min.js"></script>
<div ng-app="TestApp" ng-controller="TestCtrl">
<select ng-model="designation.CompanyId"
ng-options="dept.CompanyId as dept.CompanyName for dept in companyList"
ng-change="BindDepartments()"
advance-dropdown="companyList" key="CompanyId" required>
<option value="" disabled>--Select Company--</option>
</select>
</div>
In a BindDepartment() method $scope.designation.CompanyId value was not updated.
It Show log updated company id:0
I expect updated company id:2
Plunker demo

Avoid using isolate scope with the ng-model controller.
//directive.scope = {
// items: '=advanceDropdown',
// model: '=ngModel',
// key: '#key',
// change: '&ngChange'
//}
directive.scope = false;
Instead use the $setViewValue method to change the model value.
directive.link = function (scope, element, attrs, ctrl) {
scope.$watchCollection(attrs.advanceDropdown, function (n, o) {
if (n && n.length == 1) {
ctrl.$setViewValue(n[0]);
}
});
}
The $setViewValue method will automatically invoke the ng-change expression.

Related

Angularjs, Range.insertNode, ngClick : After $compile Angular isn't aware of element - ngClick not working

ngClick is still not responding even after $compile. The new element is being applied to the DOM and is accessible via jQuery and JS. I assume that the issue is with the range.insertNode function. What am I missing here?
Here's my directive:
.directive('selectText', [
'$rootScope',
'$compile',
'$window',
function ($rootScope, $compile, $window) {
return {
restrict: 'A',
scope: {
hlid: "=",
tu: "="
},
link: function (scope, element, attrs) {
element.on('mouseup', function () {
//console.log("Attrs: "+JSON.stringify(attrs));
if ($window.getSelection().toString()) {
var text = $window.getSelection().toString();
if(text == '') {
console.log("No selection");
return;
}
var selection = $window.getSelection();
var range = selection.getRangeAt(0);
var selectionContents = range.extractContents();
var clk = "edSel('hl_"+scope.hlid+"','"+attrs.id+"');";
// var span = $compile(angular.element('<hlight id="hl_'+scope.hlid+'" class="cr-pr noselect clickable" title="Text Selection" ng-click="'+clk+'">'+text+'</hlight>'))(scope);
var span = angular.element($compile('<hlight id="hl_'+scope.hlid+'" class="cr-pr noselect clickable" title="Text Selection" ng-click="'+clk+'">'+text+'</hlight>')(scope));
console.log(span);
range.insertNode(span[0]);
scope.tu.target = element.html();
//selection.removeAllRanges();
var arr = {};
arr.action = 'add';
arr.tuid = attrs.id;
arr.hlid = 'hl_'+scope.hlid;
arr.content = element.html();
scope.$emit('hlChange', arr);
scope.hlid++;
console.log(element.html());
var modal = UIkit.modal("#hl_modal");
modal.show();
}
});
scope.edSel = function(id,tuid) {
console.log('ID: '+id+" - tuID: "+tuid);
}
}
};
}])
Thanks for any help

Selecting single row on click in a data table

I have been trying to select one row that is clicked on but the problem I am facing is that the table being paginated shows the selected row on different pages and does not show on other pages.
I am simply removing and adding a css class on click on a row.
something like -
function rowClicked (){
var table = $('#test').DataTable();
$('#test tbody').on( 'click','tr', function () {
if ( $(this).hasClass('selected') ) {
$(this).removeClass('selected');
console.log('helloe 1');
}
else {
table.$('tr.selected').removeClass('selected');
$(this).addClass('selected');
console.log('helloe 3');
}
} );
}
You can user Angualr directive like that to your table <table select-row>
angular.module('myApp')
.directive('selectRow', selectRow);
function selectRow($compile) {
var directive = {
bindToController: true,
controller: testCtrl,
controllerAs: 'vm',
link: link,
restrict: 'EA',
scope: {}
};
return directive;
function link(scope, element, attrs, vm) {
var target, compileScope, newTd, template ;
target = angular.element($('table tr'));
compileScope = scope;
scope.arr = [];
angular.forEach(target, function(item) {
return scope.arr.push(item.textContent);
});
template = '<table><thead><tr><th></th><tbody><tr ng-class="selected[$index]" ng-repeat="t in arr track by $index" ng-click="selectRow($index)"><td >{{t}}</td></tr></tbody></tr></thead></table>';
template = angular.element(template);
$compile(template)(compileScope);
element.replaceWith(template);
// function to select row
scope.selectRow = function($index) {
scope.selected = [];
scope.index = $index;
scope.selected[$index] = scope.selected[$index] === 'selected' ? '' :'selected';
}
}
function testCtrl() {
}
}

Directive input value changing when calling directive multiple times

I have the following directive,
(function(){
angular.module("pulldownmodule")
.controller("pulldownCtrl",['pullDownServices','$scope',"multiselectDefaults","templates",function(pullDownServices,$scope,multiselectDefaults,templates){
//Local variables
_this = this;
var dropdownData = {};
var currentTemplate = {};
var firstTemplate;
//Validation function
function validateInput(){
console.log(_this.dropdownid);
if (_this.dropdownid) {
getPullDownData(_this.dropdownid,_this.filter);
}
//check if the dropdown ID is present
}
$scope.$watch('pulldownCtrl.dropdownid',function(newVal){
console.log(_this.dropdownid);
if (newVal) {
validateInput();
};
});
}])
.directive('pulldown', [function(){
return {
scope: {},
bindToController:{
dropdownid:"=",
filter:"=",
templatetype:"#"
},
controller:'pulldownCtrl',
controllerAs:'pulldownCtrl',
templateUrl: 'pulldown/html/dropDownDirective.html'
};
}]);
})()
I am calling the directive 2 times as follows
<div pulldown dropdownid="currentID" templatetype="template2" filter="customFilter"></div>
<div pulldown dropdownid="currentID2" templatetype="template2" filter="customFilter2"></div>
Passing the value of dropdownid in the controller as
$scope.currentID = 1;
$scope.currentID2 = 5;
The issue here is if i call the directive only 1 time everything works fine, but if i call it multiple times then i get the _this.dropdownid in $watch as the second directives value. Not sure what I'm doing wrong.
Probably i have to create a new instance using 'new'.
Directive HTML
Following is the major part of the directives HTML,
<select id="searchData" kendo-multi-select="pulldown" k-options="ddoptions" k-rebind="ddoptions" k-on-change="getChangevalue('searchData')"></select>
i'm using the kendo multiselect
As #hgoebl point out _this = this; it is kind of global (not application level) variable though you use in a function scope.
Use var _this = this;
//after assign "_this" is not accessible here
(function(){
//after assign "_this" is accessible here
angular.module("pulldownmodule")
.controller(...function(){
_this = this; //use var _this = this;
//...others code
});
}();
angular.module("pulldownmodule",[])
.controller("pulldownCtrl",['$scope',function($scope){
//Local variables
_this = this;
// Initialize your models here
$scope.currentID = '1';
$scope.currentID2 = '3';
var dropdownData = {};
var currentTemplate = {};
var firstTemplate;
//Validation function
function validateInput(){
console.log(_this.dropdownid);
if (_this.dropdownid) {
getPullDownData(_this.dropdownid,_this.filter);
}
//check if the dropdown ID is present
}
$scope.$watch('currentID', function (newVal) {
console.log($scope.currentID);
if (newVal) {
validateInput();
};
});
$scope.$watch('currentID2', function (newVal) {
console.log($scope.currentID2);
if (newVal) {
validateInput();
};
});
}])
.directive('pulldown', [function(){
return {
scope: {
dropdownid:"=",
filter:"=",
templatetype:"#"
},
template: '<select ng-model="dropdownid"> <option ng-repeat="a in [1,2,3,4,5]" value="{{a}}"> {{a}} </option> </select>',
link: function (scope,element, attr) {
scope.$watch("dropdownid", function (newVal) {
scope.dropdownid;
});
}
};
}]);

Decorating a directive by adding a function that will call the directive's controller

I use a directive that is declared like this :
(function (directives) {
var FilterDirective = (function () {
function FilterDirective() {
var directive = {};
directive.restrict = 'A';
directive.scope = true;
directive.controller = elasticui.controllers.FilterController;
directive.link = function (scope, element, attrs, filterCtrl) {
scope.$watch(element.attr('eui-filter') + " | euiCached", function (val) { return scope.filter.filter = val; });
var enabled = false;
var enabledAttr = element.attr('eui-enabled');
if (enabledAttr) {
scope.$watch(enabledAttr, function (val) { return scope.filter.enabled = val; });
enabled = scope.$eval(enabledAttr);
}
scope.filter = {
filter: scope.$eval(element.attr('eui-filter') + " | euiCached"),
enabled: enabled
};
filterCtrl.init();
};
return directive;
}
return FilterDirective;
})();
directives.FilterDirective = FilterDirective;
directives.directives.directive('euiFilter', FilterDirective);
})
The controller of the directive is :
(function (controllers) {
var FilterController = (function () {
function FilterController($scope) {
this.scope = $scope;
}
FilterController.prototype.init = function () {
var _this = this;
if (this.scope.filter.filter) {
var isEnabled = this.scope.filters.contains(this.scope.filter.filter);
if (!isEnabled && this.scope.filter.enabled) {
this.scope.filters.add(this.scope.filter.filter);
isEnabled = true;
}
}
this.scope.filter.enabled = isEnabled;
this.scope.$watch('filter.enabled', function (newVal, oldVal) {
if (newVal !== oldVal) {
_this.updateFilter();
}
});
this.scope.$watch('filter.filter', function (newVal, oldVal) {
if (!elasticui.util.EjsTool.equals(oldVal, newVal)) {
if (oldVal) {
_this.scope.filters.remove(oldVal);
}
_this.updateFilter();
}
});
};
FilterController.prototype.updateFilter = function () {
if (!this.scope.filter.filter) {
return;
}
if (this.scope.filter.enabled) {
this.scope.filters.add(this.scope.filter.filter);
}
else {
this.scope.filters.remove(this.scope.filter.filter);
}
};
FilterController.$inject = ['$scope'];
return FilterController;
})();
controllers.FilterController = FilterController;
})
Actually, the directive has a scope containing a filter object which contains two attributes filter : { enabled : boolean, filter : object} and the directive is used like this :
<label class="btn" ng-model="filter.enabled"
eui-filter="ejs.TermFilter('field','value')" btn-checkbox>
when the button is clicked the filter.enabled is set. My purpose is to add a behavior that will permit to change filter.enabled value via a function external to the directive.
The directive will look like this :
<label class="btn" ng-model="filter.enabled"
eui-filter="ejs.TermFilter('field','value')" eui-enable-fn="fn(somevariable)" btn-checkbox>
where fn will take the somevariable and set it to the filter.enabled.
Thanks in advance,
If you want to enable/disable a filter through the pressure of a button why not declare a filter with the property eui-enabled set to a custom toggling variable?
In other words it would result as:
HTML:
<label class="btn" eui-filter="..." eui-enabled="my_toggling_variable">
<button type=button ng-click="toggleVar()"></button>
JS:
myApp.controller('myCtrl', ['$scope', function($scope) {
$scope.my_toggling_variable = false;
$scope. toggleVar = function(){
$scope.my_toggling_variable = !$scope.my_toggling_variable;
};
}]);
Hope to have understood well the topic.

Directive for comparing two dates

I have used following code for directive which compares two dates (reference Custom form validation directive to compare two fields)
define(['./module'], function(directives) {
'use strict';
directives.directive('lowerThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
ctrl.$setValidity('lowerThan', false);
var validate = function(viewValue) {
var comparisonModel = $attrs.lowerThan;
/*if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
//console.log("It's valid because we have nothing to compare against");
ctrl.$setValidity('lowerThan', true);
}*/
// It's valid if model is lower than the model we're comparing against
//ctrl.$setValidity('lowerThan', parseInt(viewValue, 10) < parseInt(comparisonModel, 10) );
if(comparisonModel){
var to = comparisonModel.split("-");
var t = new Date(to[2], to[1] - 1, to[0]);
}
if(viewValue){
var from=viewValue.split("-");
var f=new Date(from[2],from[1]-1,from[0]);
}
console.log(Date.parse(t)>Date.parse(f));
ctrl.$setValidity('lowerThan', Date.parse(t)>Date.parse(f));
return viewValue;
};
ctrl.$parsers.unshift(validate);
ctrl.$formatters.push(validate);
$attrs.$observe('lowerThan', function(comparisonModel){
// Whenever the comparison model changes we'll re-validate
return validate(ctrl.$viewValue);
});
};
return {
require: 'ngModel',
link: link
};
}
]);
});
but when page is loaded first time it displays error message. i have tried using ctrl.$setValidity('lowerThan', false); to make it invisible first time. but it is not working.
Here is plunker for the same.
http://plnkr.co/edit/UPN1g1JEoQMSUQZoCDAk?p=preview
Your directive is fine. You're setting your date values inside the controller, and you're setting the lower date to a higher value, which means the dates are invalid on load. Your directive correctly detects that. If you don't want your directive to validate your data on load, than you'll need three things:
Remove the $attrs.$observe
Create and apply a higherThan directive to the other field
Tell your directive not to apply to the model value ($formatters array) but only to the input value ($parsers array).
PLUNKER
'use strict';
var app = angular.module('myApp', []);
app.controller('MainCtrl', function($scope) {
$scope.field = {
min: "02-04-2014",
max: "01-04-2014"
};
});
app.directive('lowerThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
var validate = function(viewValue) {
var comparisonModel = $attrs.lowerThan;
var t, f;
if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
ctrl.$setValidity('lowerThan', true);
}
if (comparisonModel) {
var to = comparisonModel.split("-");
t = new Date(to[2], to[1] - 1, to[0]);
}
if (viewValue) {
var from = viewValue.split("-");
f = new Date(from[2], from[1] - 1, from[0]);
}
ctrl.$setValidity('lowerThan', Date.parse(t) > Date.parse(f));
// It's valid if model is lower than the model we're comparing against
return viewValue;
};
ctrl.$parsers.unshift(validate);
//ctrl.$formatters.push(validate);
};
return {
require: 'ngModel',
link: link
};
}
]);
app.directive('higherThan', [
function() {
var link = function($scope, $element, $attrs, ctrl) {
var validate = function(viewValue) {
var comparisonModel = $attrs.higherThan;
var t, f;
if(!viewValue || !comparisonModel){
// It's valid because we have nothing to compare against
ctrl.$setValidity('higherThan', true);
}
if (comparisonModel) {
var to = comparisonModel.split("-");
t = new Date(to[2], to[1] - 1, to[0]);
}
if (viewValue) {
var from = viewValue.split("-");
f = new Date(from[2], from[1] - 1, from[0]);
}
ctrl.$setValidity('higherThan', Date.parse(t) < Date.parse(f));
// It's valid if model is higher than the model we're comparing against
return viewValue;
};
ctrl.$parsers.unshift(validate);
//ctrl.$formatters.push(validate);
};
return {
require: 'ngModel',
link: link
};
}
]);
<form name="form" >
Min: <input name="min" type="text" ng-model="field.min" lower-than="{{field.max}}" />
<span class="error" ng-show="form.min.$error.lowerThan">
Min cannot exceed max.
</span>
<br />
Max: <input name="max" type="text" ng-model="field.max" higher-than="{{field.min}}" />
<span class="error" ng-show="form.max.$error.higherThan">
Max cannot be lower than min.
</span>
</form>

Resources