I am using angularjs tab and pane example components
angular.module('components', []).
directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0) $scope.select(pane);
panes.push(pane);
}
},
template:
'<div class="tabbable">' +
'<ul class="nav nav-tabs">' +
'<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+
'{{pane.title}}' +
'</li>' +
'</ul>' +
'<div class="tab-content" ng-transclude></div>' +
'</div>',
replace: true
};
}).
directive('pane', function() {
return {
require: '^tabs',
restrict: 'E',
transclude: true,
scope: { title: '#' },
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
},
template:
'<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +
'</div>',
replace: true
};
})
SomePage.html
<tabs>
<pane title="Datos Generales">
<div ng-include src="'/resources/js/page/running/admin/templates/update-data.html'"></div>
</pane>
<pane title="LocalizaciĆ³n">
<div ng-include src="'/resources/js/page/running/admin/templates/update-location.html'"></div>
</pane>
<pane title="Datos Contacto">
<div ng-include src="'/resources/js/page/running/admin/templates/update-contacts.html'"></div>
</pane>
<pane title="Variantes">
<div ng-include src="'/resources/js/page/running/admin/templates/update-variants.html'"></div>
</pane>
</tabs>
In the second pane there is a GoogleMap. I would like to refresh the google map when the second pane is selected.
I don't know how to get selected pane in the controller. I've tried $scope.panes but is undefined
Here are three ways you could solve your problem:
define a method on your controller, specify that method in an attribute on the tabs element, use the '&' syntax on the tabs directive's isolate scope definition to enable the directive to call the specified method when a pane is selected.
define an object on your controller's scope with a "selectedPane" property. Use the '=' sytnax on the tabs directive's isolate scope definition to enable two-way databinding. Set this property whenever a pane is selected.
have the tabs directive $emit an event, and have your controller listen for it using $on.
Update: #qopuir asked for more details about option 1.
Suppose the controller method is something like the following:
$scope.paneChanged = function(pane) {
$scope.selectedPane = pane
...
}
On the tabs element, we'll specify this function with attribute pane-changed:
<tabs pane-changed="paneChanged(selectedPane)">
Then, the tabs directive can use the '&' syntax to call this method:
.directive('tabs', function() {
return {
restrict: 'E',
transclude: true,
scope: { paneChanged: '&' },
controller: function($scope, $element) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
$scope.paneChanged({selectedPane: pane});
}
Related
I coded two nested directives, the outer one is wrapping all inner ones to build a nav. But seems the ng-transclude all inner directives. I have debugged a while, but can't figure it out. Below are the directives and the html snippet;
angular.module('custom.directives', [])
.directive('navTabs', ['$log', function($log){
$log.info('instantaiting directive navTabs...');
var _directive = {
scope: true,
restrict: 'EA',
replace: true,
transclude: true,
templateUrl: '/jsex/ab/templates/tabs.html',
controller: ['$scope', function($scope) {
$scope.currentTab = 0;
$scope.tabs = [];
$scope.selectTab = function(tab) {
$scope.currentTab = tab;
}
return $scope;
}]
};
$log.info('finish instantaiting directive navTabs');
return _directive;
}])
.directive('navTab', ['$log', '$filter', function($log, $filter){
$log.info('instantaiting directive navTab...');
var _directive = {
scope: true,
restrict: 'EA',
replace: true,
transclude: true,
require: '^navTabs',
template: '<div ng-show="showTab()" ng-transclude></div>',
link: function(scope, element, attrs, navTabs) {
var tabId = navTabs.tabs.length;
$log.info("tabId = " + tabId + " - " + attrs.tabHeading);
navTabs.tabs.push(attrs.tabHeading);
$log.info($filter('json')(navTabs.tabs));
scope.showTab = function() {
return tabId == scope.currentTab;
};
}
};
$log.info('finish instantaiting directive navTab');
return _directive;
}]);
<nav-tabs>
<nav-tab class="tab-content" tab-heading="First Tab">
<h1> First Tab</h1>
<p>Simple tab 1
</nav-tab>
<nav-tab class="tab-content" tab-heading="Second Tab">
<h1> Second Tab</h1>
<p>Simple tab 2
</nav-tab>
<nav-tab class="tab-content" tab-heading="Third Tab">
<h1> Third Tab</h1>
<p>Simple tab 3
</nav-tab>
</nav-tabs>
Thanks.
I'm relatively new to AngularJS and working on creating tabs in a page. Till now I have resolved my problems with angularjs by searching a lot on internet but I can't resolve this. Hope anyone can help me with ideas and better knowledge of angularjs.
I have two custom directives tabset and tab. 'Tabset' is the directive to maintain the tabs and 'tab' is for a single tab.
app.directive('tabset', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'tabset.html',
bindToController: true,
scope: {},
controller: function($scope){
$scope.tabs = [];
this.addTab = function(tab) {
$scope.tabs.push(tab);
}
console.log("In tabset controller");
},
link : function(scope){
console.log("In the tabset link");
}
}
});
//Custom Directive for the tab controls
app.directive('tab', function() {
return {
restrict: 'E',
transclude: true,
template: '<h2>Welcome to Stackoverflow</h2> <div role="tabpanel" ng-transclude></div>',
require : '^tabset',
scope: {},
link : function(scope, elem, attr, tabsetCntrl) {
tabsetCntrl.addTab(scope);
console.log("In the tab link");
}
}
});
I call these directives in my HTML page as shown below:
<tabset>
<tab>
This is one tab
</tab>
<tab>
This is another tab
</tab>
</tabset>
But, when I run the code, the link function of the tab directive is not running. The 'require : ^tabset' option gets the controller from the tabset, but the link function of the tab directive is not working.
Try adding controllerAs: '$ctrl' to your tabset directive.
Like:
angular.module('app').directive('tabset', function() {
return {
restrict: 'E',
transclude: true,
templateUrl: 'tabset.html',
bindToController: true,
controllerAs: '$ctrl', // <---- HERE
scope: {},
controller: function($scope){
$scope.tabs = [];
this.addTab = function(tab) {
$scope.tabs.push(tab);
}
console.log("In tabset controller");
},
link : function(scope){
console.log("In the tabset link");
}
}
});
Tested
Further info found by checking the error seen in console here
Prudhvee, take a look at this demo i did to understand the making of angular tabs using nested directives.
app.directive('tabset', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
controller: [ "$scope", function($scope) {
var panes = $scope.panes = [];
$scope.select = function(pane) {
angular.forEach(panes, function(pane) {
pane.selected = false;
});
pane.selected = true;
}
this.addPane = function(pane) {
if (panes.length == 0) $scope.select(pane);
panes.push(pane);
}
}],
template:
'<div class="tabbable">' +
'<ul class="nav nav-tabs">' +
'<li ng-repeat="pane in panes" ng-class="{active:pane.selected}">'+
'{{pane.title}}' +
'</li>' +
'</ul>' +
'<div class="tab-content" ng-transclude></div>' +
'</div>',
replace: true
};
});
app.directive('tab', function() {
return {
require: '^tabset',
restrict: 'E',
transclude: true,
scope: { title: '#' },
link: function(scope, element, attrs, tabsCtrl) {
tabsCtrl.addPane(scope);
},
template:
'<div class="tab-pane" ng-class="{active: selected}" ng-transclude>' +
'</div>',
replace: true
};
})
http://plnkr.co/edit/BJWWw2?p=preview
Left side of the page sometimes have just info, sometimes it has side menu. When there is side menu, the content of main section depends on the option clicked.
Since the same kind of layout is used in multiple places, I created nested directives for the link/info side menu.
I have problem with the link option. Once link clicked, it calls the controller function to update the ng-include location of the main content holder. Controller does update the variable that holds the ng-include link, but ng-include does not include new template.
HTML:
Left Side
<left-side hasmenu='hasMenu' update-tab='updateTab' options='menuOptions'></left-side>
Main Content area:
<div id="tabContentWrapper" class="left two-third-screen-tab " ng-include="tab">
Controller:
var myStatsController = angular.module('myStatsController', []);
myStatsController.controller('StatsCtrl',
['$scope', '$rootScope',function ($scope, $rootScope){
//default tab
$scope.tab = 'stats/views/stats.html';
$scope.updateTab = function(newTab){
$scope.tab=newTab;
};
//all tabs
$scope.menuOptions =[{'text':'Stats','link':'stats/views/stats.html','isTab':true,'isFullScreen':false},
{'text':'Summary','link':'stats/views/summary.html','isTab':true, 'isFullScreen':false}
}]);
Directives:
1) Left Side directive
HTML:
<div class="sideMenu left">
<div ng-switch="hasmenu" class="sideMenuNavSection">
<side-menu ng-switch-when="true" update-tab="updateTab(tab)" options='options'></side-menu>
<side-info ng-switch-when="false" options='options'></side-info>
</div>
<home-button></home-button>
Directive code:
//left side area
myDirectives.directive('leftSide', function () {
return {
restrict: 'AE',
replace: true,
scope:
{
options: "=",
hasmenu:"=",
updateTab:"&"
},
templateUrl: 'shared/leftSide.html'
};
});
2) Directive that choose what to display link or info sections:
//side navigation
myDirectives.directive('sideMenu', function ($compile) {
return {
restrict: 'AE',
scope: {
options: "=",
updateTab:"&"
},
link: function (scope, element, attrs) {
for (var item in scope.options) {
if (scope.options[item].isTab === true) {
var link = scope.options[item];
element.append('<tab-link update-tab="'+attrs.updateTab+'" fullscreen = "' + link.isFullScreen + '" linktext="' + link.text + '" options="' + link.link + '"></tab-link>');
}
else {
// info section - not relevant for question
}
$compile(element.contents())(scope);
}
}
};
});
Finally Directive that constructs tabLink and calles controller function updateTab(newTab) to update $scope.tab for ng-include
myDirectives.directive('tabLink', function ($compile) {
return {
replace: true,
restrict: 'AE',
scope: {
options: "#",
linktext: "#",
fullscreen: "#",
updateTab:"&"
},
template: '<div class="infoText narrow "><div class="sideMenuItem bold">{{linktext}}</div><div class="greenArrow"></div></div>',
link: function (scope, element, attr) {
var tab = document.getElementById('tabContentWrapper');
tab = angular.element(tab);
element.on('click', function (event) {
//calls controller function
scope.updateTab()(scope.options);
$compile(tab.contents())(scope);
});
}
};
});
Thanks
Because you are initiating this thing with a normal element on click, It doesn't invoke the digest cycle. Add $scope.$apply() after $scope.tab=newTab;
Hello I also wanted to change the template dynamically and I found the one way of doing that as follows:
1) set ng-include value as follows:
<div ng-controller="mainCtrl">
<div id="tabContentWrapper" class="left two-third-screen-tab " ng-include="getTab()">
</div>
2) define function "getTab()" in your controller as follows:
app.controller("mainCtrl", function(){
$scope.getTab = function (){
// you can have different conditions here for e.g.
if(x){
return "x template path";
}else if (y){
return "y template path";
}
// Or you can just return path directly as "/return "path of the template";/"
}
});
This should not be too hard a thing to do but I cannot figure out how best to do it.
I have a parent directive, like so:
directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
replace: true,
transclude: true,
template: '
<div class="editable-fieldset" ng-click="edit()">
<div ng-transclude></div>
...
</div>',
controller: ['$scope', function ($scope) {
$scope.edit = ->
$scope.editing = true
// ...
]
};
});
And a child directive:
.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
'<div>
<label>' + attrs.label + '</label>
<p>{{ model.' + attrs.field + ' }}</p>
...
</div>'
},
require: '^editableFieldset'
};
});
How can I easily access the model and editing properties of the parent directive from the child directive? In my link function I have access to the parent scope - should I use $watch to watch these properties?
Put together, what I'd like to have is:
<editable-fieldset model="myModel">
<editable-string label="Some Property" field="property"></editable-string>
<editable-string label="Some Property" field="property"></editable-string>
</editable-fieldset>
The idea is to have a set of fields displayed by default. If clicked on, they become inputs and can be edited.
Taking inspiration from this SO post, I've got a working solution here in this plunker.
I had to change quite a bit. I opted to have an isolated scope on the editableString as well because it was easier to bind in the correct values to the template. Otherwise, you are going to have to use compile or another method (like $transclude service).
Here is the result:
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.myModel = { property1: 'hello1', property2: 'hello2' }
});
myApp.directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
transclude: true,
replace: true,
template: '<div class="editable-fieldset" ng-click="edit()"><div ng-transclude></div></div>',
link: function(scope, element) {
scope.edit = function() {
scope.editing = true;
}
},
controller: ['$scope', function($scope) {
this.getModel = function() {
return $scope.model;
}
}]
};
});
myApp.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
scope: {
label: '#',
field: '#'
},
template: '<div><label>{{ label }}</label><p>{{ model[field] }}</p></div>',
require: '^editableFieldset',
link: function(scope, element, attrs, ctrl) {
scope.model = ctrl.getModel();
}
};
});
HTML:
<body ng-controller="Ctrl">
<h1>Hello Plunker!</h1>
<editable-fieldset model="myModel">
<editable-string label="Some Property1:" field="property1"></editable-string>
<editable-string label="Some Property2:" field="property2"></editable-string>
</editable-fieldset>
</body>
You can get access to parent controller by passing attribute in child directive link function
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.$scope.editing = true;
}
I have a form based on twitter bootstrap, each field have it's own configuration
// controller (the template shows this in ng-repeat
$scope.fields = [{name:"f1", label:"Field 1", with_button: false},
{name:"f2", label:"Field 2", with_button: true}]
I'm trying to make a "conditional directive" that customize the template according to "field.with_button"
// Without button
<div class="controls">
<input type="text" id="i_{{field.name}}">
</div>
// With button
<div class="controls">
<div class="input-append">
<input type="text" id="i_{{field.name}}">
<span class="add-on">bt</span>
</div>
</div>
I searched a lot and didn't find any solution, I tried to create only one div and put contents inside with a compiler function but it didn't parse, and if I call $apply it crashes.
How could I make this directive?
wrong My last try:
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls">{{innerContent}}</div>',
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
$scope.$eval('$scope.innerContent = \'<input type="text" id="input_{{field.name}}" placeholder="{{field.name}}" class="input-xlarge">\'');
}]
};
});
//<ss-field field="{{field}}"></ss-field>
You can use the $http and $compile services to do what you're after.
http://plnkr.co/edit/Xt9khe?p=preview
This plnkr should demostrate what needs to be done, but basically:
Use $http to load the template depending on the condition.
Compile the loaded template against the current scope with $compile.
angular.module('mymodule',[]).directive('ssField', ['$http', '$compile', function($http, $compile) {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls"></div>',
link: function(scope, element, attrs) {
var template;
var withButtonTmpl = 'with_button.html';
var withoutButtonTmpl = 'without_button.html';
if (scope.field.with_button) {
$http.get(withButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
} else {
$http.get(withoutButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
}
}
};
}]);
You can change the directive to be more robust so the URLs aren't directly embedded in the directive for re-usability, etc., but the concept should be similar.
Just to further expand on Cuing Vo's answer here is something similar to what I use(without using external partials and additional $http calls):
http://jsfiddle.net/LvUdQ/
myApp.directive('myDirective',['$compile', function($compile) {
return {
restrict: 'E',
template: '<hr/>',
link: function (scope, element, attrs, ngModelCtrl) {
var template = {
'templ1':'<div>Template 1</div>',
'templ2':'<div>Template 2</div>',
'default':'<div>Template Default</div>'
};
var templateObj;
if(attrs.templateName){
templateObj = $compile(template[attrs.templateName])(scope);
}else{
templateObj = $compile(template['default'])(scope);
}
element.append(templateObj);
}
};
}]);
However Im not quite sure its by the bible from performance perspective.
In AngularJS, directly manipulate the DOM must only be a last resort solution. Here, you can simply use the ngSwitch directive :
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template:
'<div class="controls" data-ng-switch="field.with_button">' +
'<input type="text" id="i_{{field.name}}" data-ng-switch-when="false">' +
'<div class="input-append" data-ng-switch-default>' +
'<input type="text" id="i_{{field.name}}">' +
'<span class="add-on">bt</span>' +
'</div>' +
'</div>',
};
});