angular directive scope inheritance/ isolation - angularjs

I have a directive to build dynamic table from a collection. It have two types of columns checkbox and dropdowns. However, the scope is not resolving correctly at directive level. They are getting as 'undefined'. Also I need to bind drop down options from the controller.
html
<div ng-app="myApp">
<div ng-controller="MyCtrl">
<ui-table source="students">
<check-col title="passed" field="passed"/>
<drop-down-col title="std" field="std" />
</ui-table>
</div>
</div>
Angular js
var myApp = angular.module('myApp',[]);
myApp.directive("uiTable", function ($compile) {
var generateTableHtml = function ($scope) {
// header
var html = "<table class='table table-condensed table-bordered table-responsive'><thead class='thead12'><tr class='active'>";
angular.forEach($scope.columns, function (col) {
debugger;
html += "<th scope='col'" +
(col.cssClass ? " class='" + col.cssClass + "'" : "") +
">" + col.title + "</th>";
});
html += "</tr></thead><tbody>";
// body
html += "<tr ng-repeat='item in dataSource'>";
angular.forEach($scope.columns, function (col) {
html += "<td" + (col.cssClass ? " class='" + col.cssClass + "'" : "") + ">";
if (col.type === ColumnType.Check) {
html += "<input type='checkbox' ng-model='item." + col.dataField + "'/>";
} else if (col.type === ColumnType.DropDown) {
html += "<select ng-model='item." +col.dataField + "' ng-options = 'option.Value for option in dataOptions'></select>";
}
html += "</td>";
});
html += "</tr>";
html += "</tbody></table>";
return html;
};
return {
restrict: "E",
replace: true,
transclude: true,
scope: {
dataSource: "=source"
},
controller: function ($scope) {
$scope.columns = [];
this.addColumn = function (col) {
$scope.columns.splice(0, 0, col);
};
},
template: "<div ng-transclude></div>",
compile: function () {
return function ($scope, $elem) {
$elem.html(generateTableHtml($scope));
$compile($elem.contents())($scope);
};
}
};
});
myApp.directive("checkCol", function () {
return {
require: "^uiTable",
restrict: "E",
replace: true,
scope: {
title: "#title",
cssClass: "#class",
dataField: "#field"
},
link: function ($scope, element, attrs, tableControl) {
$scope.type = ColumnType.Check;
tableControl.addColumn($scope);
}
};
});
myApp.directive("dropDownCol", function() {
return {
require: "^uiTable",
restrict: "E",
replace: true,
scope: {
title: "#title",
cssClass: "#class",
dataField: "#field"
},
link: function($scope, element, attrs, tableControl) {
$scope.type = ColumnType.DropDown;
tableControl.addColumn($scope);
}
};
});
//myApp.factory('myService', function() {});
var ColumnType = { Check: 1, DropDown: 2 };
myApp.controller('MyCtrl', function($scope) {
$scope.students = [{id:1, std:10, passed: true}, {id:2, std:9, passed: false}];
$scope.stds= [{Value: 10, Name: '10the std'},{Value: 9, Name: '9the std'},{Value: 8, Name: '8the std'}];
});
JSFIDDLE

Uprade the angular ver. to 1.2 and it works fine
`http://jsfiddle.net/HB7LU/18635/`

Related

Cannot access DOM inside ngRepeat inside custom directive

In the following example (http://jsfiddle.net/akanieski/t4chbuwq/1/) I have a directive that has an ngRepeat inside it... during the link stage I cannot access html inside the ngRepeat. Only thing inside shade-element is <!-- ngRepeat i in items --> What am I doing wrong?
Controller:
module.controller("MainCtrl", function($scope, $timeout) {
$scope.currentPanel = 1;
$timeout(function(){
$scope.items = [1,2];
}, 1000)
$scope.loadPanelOne = function() {
if (!$scope.$$phase) $scope.$apply(function(){$scope.loadingPanelOne = true;});
$timeout(function(){
$scope.loadingPanelOne = false;
}, 2000);
}
$scope.loadPanelTwo = function() {
if (!$scope.$$phase) $scope.$apply(function(){$scope.loadingPanelTwo = true;});
$timeout(function(){
$scope.loadingPanelTwo = false;
}, 2000);
}
$scope.loadPanel3 = function() {
if (!$scope.$$phase) $scope.$apply(function(){$scope.loadingPanel3 = true;});
$timeout(function(){
$scope.loadingPanel3 = false;
}, 2000);
}
$scope.loadPanel4 = function() {
if (!$scope.$$phase) $scope.$apply(function(){$scope.loadingPanel4 = true;});
$timeout(function(){
$scope.loadingPanel4 = false;
}, 2000);
}
});
Directives:
var module = angular.module("myApp", []);
module.directive('shadeSet', ['$rootScope', '$timeout', function($rootScope, $timeout) {
return {
restrict: "E",
scope: {
"mode": "=",
"currentPanel": "="
},
controller: function() {
},
link: function(scope, element, attributes) {
$rootScope.$on('panelOpening', function(ev, args) {
if (scope.mode === 'exclusive') {
element
.find('shade-panel')
.addClass('hide')
$('[id="' + args.id + '"]',element).removeClass('hide');
} else if (args.state === 'open') {
$('[id="' + args.id + '"]',element).removeClass('hide');
} else if (args.state === 'close') {
$('[id="' + args.id + '"]',element).addClass('hide');
} else {
$('[id="' + args.id + '"]',element).toggleClass('hide');
}
$rootScope.$broadcast('panelOpened', {id: args.id, state: 'open'});
})
if (scope.currentPanel) {
$rootScope.$broadcast('panelOpening', {id: scope.currentPanel, state: 'open'});
}
}
}
}])
module.directive('shadePanel', ['$rootScope', function($rootScope) {
return {
restrict: "E",
require: '^shadeSet',
scope: {
"id": "=",
"onOpen": "&",
"scrollOnOpen": "="
},
link: function(scope, element, attributes) {
$rootScope.$on('panelOpened', function(ev, args){
if (args.id === scope.id && scope.onOpen) scope.onOpen();
if (scope.scrollOnOpen) {
console.log("Scrolling to " + args.id)
$('html, body').animate({
scrollTop: $(element).offset().top
}, 500);
}
})
}
}
}])
module.directive('shadeToggle', ['$rootScope', function($rootScope) {
return {
restrict: "E",
require: '^shadeSet',
scope: {
target: "="
},
compile: function() {
return {
pre: function(scope, element, attributes) {
element.on('click', function() {
$rootScope.$emit('panelOpening', {id: scope.target});
});
scope.$on('$destroy', function() {
element.off('click');
});
}
};
}
}
}])
HTML
<div ng-controller="MainCtrl" ng-app="myApp">
<shade-set current-panel="currentPanel">
<div ng-repeat="i in items">
<shade-toggle target="1"><a href>Open One</a></shade-toggle>
<shade-panel id="1" class="hide" on-open="loadPanelOne()">
<span ng-show="loadingPanelOne">... Loading Panel 1 ...</span>
Hello World
</shade-panel>
</div>
</shade-set>
</div>

Angular Can't change ng-model form other directive

I need to change ng-model (currentPage) in pdfViewerToolbar directive by clicking on canvas in pdfViewer directive. The self.next function in controller is called when i clicking on canvas, but ng-model="currentPage" is not changed in pdfViewerToolbar directive.
app.angular.module("pdf").directive("pdfViewerToolbar", ["pdfDelegate", function(e) {
return {
restrict: "E",
template: '<div class="pdf_navigation" ng-show="navBar">\
<div class="pdf_buttons">\
<div ng-click="prev()" class="goPrevious" title="previous page"></div>\
<div ng-click="next()" class="goNext" id="goNext" title="next page"></div>\
<div ng-click="zoomOut()" class="zoomIn"></div>\
<div ng-click="zoomIn()" class="zoomOut"></div>\
</div>\
<div class="pdf_page">\
<span>Page</span>\
<input type="text" min=1 ng-model="currentPage" maxlength="4" ng-change="goToPage()" >\
<span>of {{pageCount}}</span>\
</div>\
</div>',
scope: {
pageCount: "=",
navBar: "=",
},
// controller: "PdfCtrl",
link: function(t, n, a) {
var o = a.delegateHandle;
t.currentPage = 1, t.prev = function() {
e.$getByHandle(o).prev(), r()
}, t.next = function() {
e.$getByHandle(o).next(), r()
}, t.zoomIn = function() {
e.$getByHandle(o).zoomIn()
}, t.zoomOut = function() {
e.$getByHandle(o).zoomOut()
}, t.rotate = function() {
e.$getByHandle(o).rotate()
}, t.goToPage = function() {
e.$getByHandle(o).goToPage(t.currentPage)
};
var r = function() {
t.currentPage = e.$getByHandle(o).getCurrentPage()
}
}
}
}])
app.angular.module("pdf").directive("pdfViewer", ["pdfDelegate", function(r) {
return {
restrict: "E",
template: '<div show-control class="pdf_doc"><pdf-viewer-toolbar ng-if="showToolbar" delegate-handle="{{id}}" page-count="pageCount" nav-bar="pdfNavigationBar"></pdf-viewer-toolbar><canvas ng-click="next()" id="pdf-canvas" ></canvas></div>',
scope: false,
controller: "PdfCtrl",
link: function(e, t, n) {
e.id = n.delegateHandle, e.showToolbar = e.$eval(n.showToolbar) || !1
var o = n.delegateHandle;
e.currentPage = 1,
e.next = function() {
r.$getByHandle(o).next(), s()
}
var s = function() {
e.currentPage = r.$getByHandle(o).getCurrentPage()
}
}
}
}]);
app.controller('PdfCtrl', [
'$scope',
'$element',
'$attrs',
'pdfDelegate',
'$log',
'$q', '$rootScope',
function($scope, $element, $attrs, pdfDelegate, $log, $q, $rootScope) {
// Register the instance!
var deregisterInstance = pdfDelegate._registerInstance(this, $attrs.delegateHandle);
// De-Register on destory!
$scope.$on('$destroy', deregisterInstance);
var self = this;
var url = $scope.$eval($attrs.url);
var headers = $scope.$eval($attrs.headers);
var pdfDoc;
$scope.pageCount = 0;
var currentPage = 1;
var angle = 0;
var scale = $attrs.scale ? $attrs.scale : 1;
var canvas = $element.find('canvas')[0];
var ctx = canvas.getContext('2d');
self.next = function() {
if (currentPage >= pdfDoc.numPages)
return;
currentPage = parseInt(currentPage, 10) + 1;
renderPage(currentPage);
console.log('currentPage'+currentPage);
};
return PDFJS
.getDocument(docInitParams)
.then(function (_pdfDoc) {
console.log('loaded');
$rootScope.loadPdf = $scope.pdfNavigationBar = true;
pdfDoc = _pdfDoc;
renderPage(1);
$scope.$apply(function() {
$scope.pageCount = _pdfDoc.numPages;
});
}, function(error) {
$log.error(error);
return $q.reject(error);
})
};
if(url) self.load();
}]);
You need to pass the currentPage variable as isolated directive like below.
return {
restrict: "E",
template: '<div class="pdf_navigation" ng-show="navBar">\
<div class="pdf_buttons">\
<div ng-click="prev()" class="goPrevious" title="previous page"></div>\
<div ng-click="next()" class="goNext" id="goNext" title="next page"></div>\
<div ng-click="zoomOut()" class="zoomIn"></div>\
<div ng-click="zoomIn()" class="zoomOut"></div>\
</div>\
<div class="pdf_page">\
<span>Page</span>\
<input type="text" min=1 ng-model="currentPage" maxlength="4" ng-change="goToPage()" >\
<span>of {{pageCount}}</span>\
</div>\
</div>',
scope: {
pageCount: "=",
navBar: "=",
currentPage: "="
},
...
...
...
and now pass that currentPage from pdfViewer directive template like below:
app.angular.module("pdf").directive("pdfViewer", ["pdfDelegate", function(r) {
return {
restrict: "E",
template: '<div show-control class="pdf_doc"><pdf-viewer-toolbar ng-if="showToolbar" delegate-handle="{{id}}" page-count="pageCount" nav-bar="pdfNavigationBar" current-page="currentPage" ></pdf-viewer-toolbar><canvas ng-click="next()" id="pdf-canvas" ></canvas></div>',
scope: false,
controller: "PdfCtrl",
and now define the variable in the scope of the controller PdfCtrl and access from the same.
app.controller('PdfCtrl', [
'$scope',
'$element',
'$attrs',
'pdfDelegate',
'$log',
'$q', '$rootScope',
function($scope, $element, $attrs, pdfDelegate, $log, $q, $rootScope) {
// Register the instance!
var deregisterInstance = pdfDelegate._registerInstance(this, $attrs.delegateHandle);
// De-Register on destory!
$scope.$on('$destroy', deregisterInstance);
var self = this;
var url = $scope.$eval($attrs.url);
var headers = $scope.$eval($attrs.headers);
var pdfDoc;
$scope.pageCount = 0;
$scope.currentPage = 1;
var angle = 0;
var scale = $attrs.scale ? $attrs.scale : 1;
var canvas = $element.find('canvas')[0];
var ctx = canvas.getContext('2d');
self.next = function() {
if ($scope.currentPage >= pdfDoc.numPages)
return;
$scope.currentPage = parseInt($scope.currentPage, 10) + 1;
renderPage($scope.currentPage);
console.log('currentPage'+$scope.currentPage);
};
In your pdfViewerToolbar directive scope is isolated, and if you want change something in that directive you sould pass it as scope element with two way databinding:"=".
scope: {
pageCount: "=",
navBar: "=",
currentPage:"="
}
and use your directive passing controller model like this
<pdf-viewer-toolbar page-count="ctrlModelPageCount" nav-bar="ctrlModelNavBar" current-page="ctrlModelCurrentPage"></pdf-viewer-toolbar>

Directive variable overwritting

I am working on angularjs directive, i made one JsBin, here I am using two arrays and each array selection is storing in two different variables name temp1 and temp2, the problem is when i select one array the other value changes to empty array and vice-versa.
HTML is this
<div ng-controller="ctrl">
<script type="text/ng-template" id="partials/checkbox.html">
<div ng-repeat="obj in data">
<input on-check type="checkbox" ng-model="checked" value="{{obj}}" click-checkbox="checkstatus(checked,obj)" checked-me="checked" />{{obj}}</div>
</script>
<check-boxes get-type="data"></check-boxes>
<check-boxes get-type="bestnights"></check-boxes>
</div></div>
Javascript code is
var app = angular.module('myApp', []);
app.controller('ctrl', function($scope) {
var data = [];
var bestnights = [];
var daysArray = [];
var getType;
var temp1, temp2;
$scope.$on($scope.getType, function() {
getType = $scope.getType;
if (getType == 'data') {
$scope.data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
temp1 = data;
daysArray = data;
}
if (getType == 'bestnights') {
$scope.data = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
temp2 = bestnights;
daysArray = bestnights;
}
});
$scope.checkstatus = function(checked, obj) {
var index = daysArray.indexOf(obj);
if (checked) {
if (index === -1) {
daysArray.push(obj);
}
}
if (!checked) {
daysArray.splice(index, 1);
}
var str = daysArray.toString();
console.log(temp1);
console.log(temp2);
};
});
app.directive('checkBoxes', function() {
return {
restrict: "EA",
scope: {
getType: "#"
},
controller: "ctrl",
templateUrl: "partials/checkbox.html",
link: function(scope, ele, attrs, dctrl) {
ele.bind('click', function() {
//console.log(scope.getType);
scope.$emit(scope.getType);
});
var defaultFunction = function() {
scope.$emit(scope.getType);
}();
}
};
});
app.directive('onCheck', function() {
return {
restrict: "A",
scope: {
clickCheckbox: "&",
value: "#",
checkedMe: "="
},
link: function(scope, ele, attrs) {
ele.bind('click', function() {
scope.clickCheckbox(scope.checkedMe, scope.value);
});
}
};
});
I think the reason why one of your variable is always null is because of scopes: A directive has its own scope.
Which means: Checkbox data has temp1 and temp2 as variables.
Checkbox bestnights has temp1 and temp2 as variables too.
HOWEVER there are not the same : data.temp1 != bestnights.temp1.
To find what are the values of your directives, do the following. In the html:
<div ng-controller="test">
<script type="text/ng-template" id="partials/checkbox.html">
<div ng-repeat="obj in data">
<input on-check type="checkbox" ng-model="checked" value="{{obj}}"
click-checkbox="checkstatus(checked,obj)" checked-me="checked" />{{obj}}
</div>
</script>
<check-boxes get-type="data" values="days"></check-boxes>
<check-boxes get-type="bestnights" values="months"></check-boxes>
<input type="button" ng-click="showValues()" value="Show values" />
</div>
In the js:
var app = angular.module('myApp', []);
app.controller('test', function($scope){
$scope.days = [];
$scope.months = [];
$scope.showValues = function(){
console.log('Values:');
console.log($scope.days);
console.log($scope.months);
};
});
app.controller('ctrl', function($scope) {
$scope.$on($scope.getType, function() {
if ($scope.getType == 'data') {
$scope.data = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
}
if ($scope.getType == 'bestnights') {
$scope.data = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'];
}
});
$scope.checkstatus = function(checked, obj) {
var index = $scope.values.indexOf(obj);
if (checked) {
if (index === -1) {
$scope.values.push(obj);
}
}
if (!checked) {
$scope.values.splice(index, 1);
}
};
});
app.directive('checkBoxes', function() {
return {
restrict: "EA",
scope: {
getType: "#",
values: "="
},
controller: "ctrl",
templateUrl: "partials/checkbox.html",
link: function(scope, ele, attrs, dctrl) {
ele.bind('click', function() {
scope.$emit(scope.getType);
});
var defaultFunction = function() {
scope.$emit(scope.getType);
}();
}
};
});
app.directive('onCheck', function() {
return {
restrict: "A",
scope: {
clickCheckbox: "&",
value: "#",
checkedMe: "="
},
link: function(scope, ele, attrs) {
ele.bind('click', function() {
scope.clickCheckbox(scope.checkedMe, scope.value);
});
}
};
});
I think it's best to separate the controller from your page and the controller from your directive. In my example, I created a controller named "test" for the page. I also added a new param to your directive which will contains the values.
Finally, for the example, I added a button to print the values.

directive with isolate scope and compilation wipes out mode

I have a directive that consists of a select dropdown, when the select is changed (im watching the modelid) i'm recompiling the template and setting the element to the compiled html. the bizarre thing is, the span shows the correct value where as the select text is incorrect.
what i am trying to do and if anyone can provide a better solution is have part of the template dynamic so when a select is changed, i want a different part loaded in (which would also need compiling as it would have binded fields)
can anyone see why its happening please?
Thx.
app.directive('editor', ['$http', '$compile', function ($http, $compile) {
function createTemplate(id) {
var template =
'<div><select class="form-control" ng-model="lettingsourceId" >' +
'<option ng-repeat="lettingsrc in lettingSources" value="{{lettingsrc.id}}">{{lettingsrc.description}}</option>' +
'</select></div>';
return template + "<span>" + id + "</span>";
}
return {
restrict: 'E',
scope: {},
link: function ($scope, $element, $attributes) {
$scope.values= [{ id: "1", description: 'testa' }, { id: "2", description: 'testb' }];
$scope.modelid= "1";
var html = createTemplate($scope.modelid);
$element.html($compile(html)($scope));
$scope.$watch('modelid', function () {
var html = createTemplate($scope.modelid);
$element.html($compile(html)($scope));
});
}
};
}]);
checkout this
app.directive('editor', ['$http', '$compile', function ($http, $compile) {
function createTemplate(id) {
var template =
'<div><select class="form-control" ng-model="' + id + '" >' +
'<option ng-repeat="lettingsrc in lettingSources" value="{{lettingsrc.id}}">{{lettingsrc.description}}</option>' +
'</select>';
return template + "<span>{{" + id + "}}</span></div>";
}
return {
restrict: 'E',
scope: {},
link: function ($scope, $element, $attributes) {
$scope.lettingSources= [{ id: "1", description: 'testa' }, { id: "2", description: 'testb' }];
$scope.modelid= "1";
var html = createTemplate('modelid');
var element = angular.element(html);
$element.append(element);
$compile(element)($scope);
// $scope.$watch('modelid', function () {
// var html = createTemplate($scope.modelid);
// // $element.html(html);
// // $compile(elm)($scope);
// });
}
};
}]);

How to pass multiple attribute in AngularJS directive

How to pass multiple attribute to a directive.
How to pass value 12 of click-to-edit1 inside below div like
<div click-to-edit="location.state" click-to-edit1=12></div>
and should be accessible in directive controller.please help me out.
Code:
App HTML:
<div ng-controller="LocationFormCtrl">
<h2>Editors</h2>
<div class="field">
<strong>State:</strong>
<div click-to-edit="location.state"></div>
</div>
<h2>Values</h2>
<p><strong>State:</strong> {{location.state}}</p>
</div>
App directive:
app = angular.module("formDemo", []);
app.directive("clickToEdit", function() {
var editorTemplate = '<div class="click-to-edit">' +
'<div ng-hide="view.editorEnabled">' +
'{{value}} ' +
'<a ng-click="enableEditor()">Edit</a>' +
'</div>' +
'<div ng-show="view.editorEnabled">' +
'<input ng-model="view.editableValue">' +
'Save' +
' or ' +
'<a ng-click="disableEditor()">cancel</a>.' +
'</div>' +
'</div>';
return {
restrict: "A",
replace: true,
template: editorTemplate,
scope: {
value: "=clickToEdit",
},
controller: function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
};
}
};
});
App controller:
app.controller("LocationFormCtrl", function($scope) {
$scope.location = {
state: "California",
};
});
Add new property inside directives scope:
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.location = {
state: "California",
};
});
app.directive("clickToEdit", function() {
var editorTemplate = '<div class="click-to-edit">' +
'<div ng-hide="view.editorEnabled">' +
' {{value}} ' +
'<a ng-click="enableEditor()">Edit</a>' +
'</div>' +
'<div>{{value1}}</div>' +
'<div ng-show="view.editorEnabled">' +
'<input ng-model="view.editableValue">' +
'Save' +
' or ' +
'<a ng-click="disableEditor()">cancel</a>.' +
'</div>' +
'</div>';
return {
restrict: "A",
replace: true,
template: editorTemplate,
scope: {
value: "=clickToEdit",
value1: "=clickToEdit1"
},
controller: function($scope) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
};
}
};
});
html:
<div class="field">
<strong>State:</strong>
<div click-to-edit="location.state" click-to-edit1="12"></div>
</div>
working example: http://plnkr.co/edit/e7oTtZNvLdu6w5dgFtfe?p=preview
you first tell the div that you want your directive on it.
<div click-to-edit value='location.state' value1='12'></div>
app.directive("clickToEdit", function() {
return {
restrict: "A",
scope : {
value : "=",
value1 : "="
},
link : function($scope) {
console.log("the first value, should be location.state value on the controller", $scope.value);
console.log("the second value, should be '12'", $scope.value);
}
}
it might seem more logic when you use the directive as a element.
but bottom line is that the diretive looks for attributes on the element, which you then can attach to the directive via the scope. '=' for two way binding, '#' for one way.

Resources