Run into an issue where ng-repeat doesn't seem to be executing inside a custom directive. I think the problem is something to do with the transclusion/scope but I'm not really sure.
directive:
.directive('node', ['$compile', function($compile) {
return {
restrict: 'E',
scope: {
obj: '=obj',
},
transclude: true,
link: function(scope, element, attrs, ctrl, transclude) {
transclude(scope, function(clone, scope) {
console.log(clone);
var tmp = angular.element('<div></div>'), template;
tmp.append(clone);
if(scope.obj.hasSub) {
template =
'<div>' +
'<span>{{obj.name}}</span>' +
'<ul>' +
'<li ng-repeat="child in obj.children">' +
'<node data-obj="child" >' + tmp.html() + '</browser-node>' +
'</li>' +
'</ul>' +
'</div>';
} else {
template =
'<div>' +
tmp.html() +
'</div>'
}
element.html('').append($compile(template)(scope));
});
}
};
}]);
controller:
.controller('Main', ['$scope', function($scope) {
$scope.items = [
{'name': 'one', hasSub: true, children: [
{ 'name': 'one-one', items: { foo: 3, bar: 2 } }
] },
{'name': 'two', hasSub: true, children: [
{ 'name': 'two-one', items: { foo: 6, bar: 5 } }
] }
];
}]);
html:
<ul>
<li ng-repeat="item in items">
<node obj="item">
{{obj.name}}
<ul>
<li ng-repeat="(k,v) in obj.items">{{k}}: {{v}}</li>
</ul>
</node>
</ul>
The name of the item node displays fine, and {{obj.items}} will output the correct contents, but the ng-repeat seems to do nothing.
Plunker
There is no property items in the first level of your data. If you nest the repeater for items in your template and change to child.items it works fine
template =
'<div>' +
'<span>{{obj.name}}</span>' +
'<ul>' +
'<li ng-repeat="child in obj.children">' +
'<node data-obj="child" >' + tmp.html() + '</node>' +
'<ul>'+
'<li ng-repeat="(k,v) in child.items">{{k}}: {{v}}</li>'+
'</ul>'+
'</li>' +
'</ul>' +
'</div>';
DEMO
Related
I need a date picker which let me choose 'year' only in extjs.I already have multiple datepicker in my application, but for this i need to choose year only. Is there any way i can override css of datepicker(Extjs have component which shows month and year but not year only). I don't want to define custom component, i want to use datepicker by overriding it's css. I found one custom css example over stackoverflow.Please check it out here.
Ext.onReady(function() {
Ext.define('Ext.ux.OnlyYearPicker', {
xtype: 'onlyyearpicker',
extend: 'Ext.picker.Month',
afterRender: function(){
this.callParent();
this.el.setStyle({width: '106px',})
},
renderTpl: [
'<div id="{id}-bodyEl" data-ref="bodyEl" class="{baseCls}-body">',
'<div style="display: none; width:0px;" id="{id}-monthEl" data-ref="monthEl" class="{baseCls}-months">',
'<tpl for="months">',
'<div class="{parent.baseCls}-item {parent.baseCls}-month">',
'<a style="{parent.monthStyle}" role="button" hidefocus="on" class="{parent.baseCls}-item-inner">{.}</a>',
'</div>',
'</tpl>',
'</div>',
'<div id="{id}-yearEl" data-ref="yearEl" class="{baseCls}-years">',
'<div class="{baseCls}-yearnav">',
'<div class="{baseCls}-yearnav-button-ct">',
'<a id="{id}-prevEl" data-ref="prevEl" class="{baseCls}-yearnav-button {baseCls}-yearnav-prev" hidefocus="on" role="button"></a>',
'</div>',
'<div class="{baseCls}-yearnav-button-ct">',
'<a id="{id}-nextEl" data-ref="nextEl" class="{baseCls}-yearnav-button {baseCls}-yearnav-next" hidefocus="on" role="button"></a>',
'</div>',
'</div>',
'<tpl for="years">',
'<div class="{parent.baseCls}-item {parent.baseCls}-year">',
'<a hidefocus="on" class="{parent.baseCls}-item-inner" role="button">{.}</a>',
'</div>',
'</tpl>',
'</div>',
'<div class="' + Ext.baseCSSPrefix + 'clear"></div>',
'<tpl if="showButtons">',
'<div class="{baseCls}-buttons">{%',
'var me=values.$comp, okBtn=me.okBtn, cancelBtn=me.cancelBtn;',
'okBtn.ownerLayout = cancelBtn.ownerLayout = me.componentLayout;',
'okBtn.ownerCt = cancelBtn.ownerCt = me;',
'Ext.DomHelper.generateMarkup(okBtn.getRenderTree(), out);',
'Ext.DomHelper.generateMarkup(cancelBtn.getRenderTree(), out);',
'%}</div>',
'</tpl>',
'</div>'
]
});
Ext.define('Ext.form.field.Month', {
extend: 'Ext.form.field.Date',
alias: 'widget.monthfield',
requires: ['Ext.picker.Month', 'Ext.ux.OnlyYearPicker'],
alternateClassName: ['Ext.form.MonthField', 'Ext.form.Month'],
selectMonth: null,
createPicker: function() {
var me = this,
format = Ext.String.format;
return Ext.create('Ext.ux.OnlyYearPicker', {
pickerField: me,
ownerCt: me.ownerCt,
renderTo: document.body,
floating: true,
hidden: true,
focusOnShow: true,
minDate: me.minValue,
maxDate: me.maxValue,
disabledDatesRE: me.disabledDatesRE,
disabledDatesText: me.disabledDatesText,
disabledDays: me.disabledDays,
disabledDaysText: me.disabledDaysText,
format: me.format,
showToday: me.showToday,
startDay: me.startDay,
minText: format(me.minText, me.formatDate(me.minValue)),
maxText: format(me.maxText, me.formatDate(me.maxValue)),
listeners: {
select: {
scope: me,
fn: me.onSelect
},
monthdblclick: {
scope: me,
fn: me.onOKClick
},
yeardblclick: {
scope: me,
fn: me.onOKClick
},
OkClick: {
scope: me,
fn: me.onOKClick
},
CancelClick: {
scope: me,
fn: me.onCancelClick
}
},
keyNavConfig: {
esc: function() {
me.collapse();
}
}
});
},
onCancelClick: function() {
var me = this;
me.selectMonth = null;
me.collapse();
},
onOKClick: function() {
var me = this;
if (me.selectMonth) {
me.setValue(me.selectMonth);
me.fireEvent('select', me, me.selectMonth);
}
me.collapse();
},
onSelect: function(m, d) {
var me = this;
me.selectMonth = new Date((d[0] + 1) + '/1/' + d[1]);
}
});
Ext.create('Ext.form.field.Month', {
format: 'Y',
fieldLabel: 'Date',
renderTo: Ext.getBody()
});
});
is it possible with this example from Ben Foster to get all nodes closed by default (at the loading of the page) ? and to open each node with ng-click ?
http://jsfiddle.net/benfosterdev/NP7P5/
I have found a way to get selected node but I don't know how to combine it with ng-click and eventually ng-show or ng-hide:
ng-click='nodeSelected($event, category)'
and in controller
$scope.nodeSelected = function($event, category){
$event.stopPropagation();
console.log('This node is selected' + category);
}
Just found a similar example :
var gyroEditor = angular.module('gyroEditor', []);
gyroEditor.controller('Ctrl', function($scope) {
$scope.nodes = [
{
title: 'Computers',
categories: [
{
title: 'Laptops',
categories: [
{
title: 'Ultrabooks'
},
{
title: 'Macbooks'
}
]
},
{
title: 'Desktops'
},
{
title: 'Tablets',
categories: [
{
title: 'Apple'
},
{
title: 'Android'
}
]
}
]
},
{
title: 'Printers'
}
];
});
gyroEditor.directive('tree', function() {
return {
restrict: 'E',
replace: true,
scope: {nodes: '=nodes'},
templateUrl: 'tree.html',
controller: function($scope) {
console.log('tree ctrl');
}
};
});
gyroEditor.directive('treenode', function() {
return {
restrict: 'E',
replace: true,
scope: {node:'=node'},
templateUrl: 'treenode.html',
controller: function($scope) {
console.log('node ctrl');
}
};
});
gyroEditor.directive("recursive", function($compile) {
return {
restrict: "EACM",
priority: 100000,
compile: function(tElement, tAttr) {
var contents = tElement.contents().remove();
var compiledContents;
return function(scope, iElement, iAttr) {
if(!compiledContents) {
compiledContents = $compile(contents);
}
iElement.append(
compiledContents(scope,
function(clone) {
return clone; }));
};
}
};
});
.panel-left {
float: left;
width: 200px;
margin: 0 20px 20px 0;
}
.panel-editors {
float: right;
height: 100%;
width: 100%;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app=gyroEditor ng-controller=Ctrl>
<script type="text/ng-template" id="treenode.html">
<li ng-init="collapsed=true">
<a ng-click="collapsed=!collapsed"><i class="fa fa-{{((collapsed || !node.categories) ? '' : '-open')}}"></i> {{node.title}}</a>
<ol ng-if="!collapsed && node.categories && node.categories.length">
<recursive>
<treenode ng-repeat="c in node.categories" node=c>
</treenode>
</recursive>
</ol>
</li>
</script>
<script type="text/ng-template" id="tree.html">
<ol>
<treenode ng-repeat="n in nodes" node=n></treenode>
</ol>
</script>
<div class=panel-left>
<tree nodes=nodes></tree>
</div>
</div>
{{node.title}}
I'm an Angular noob. I'm structuring my panes as sets of tables and using directives to keep the template HTML abstract from the table implementation. The panes are getting transcluded correctly, but interior only contains the text not the table structure. Here's my code:
<!DOCTYPE html>
<html>
<head>
<script src= "angular.js"></script>
<script src= "angular-sanitize.js"></script>
</head>
<body ng-app="pp">
<div ng-controller="ppMain">
<ul class="menu">
<li ng-repeat="pageID in homePageList">{{pages[pageID].str}}</li>
</ul>
<div>
<div ng-repeat="pageID in pageList" ng-bind-html="pages[pageID].template">
</div>
</div>
</div>
<script>
var pp = {
ctl: {}
};
pp.mod = angular.module('pp', ['ngSanitize']);
pp.mod.directive({
'ppHeader': function () {
return ({
});
},
'ppGroup': function () {
return ({
template: '<table ng-transclude></table>',
transclude: true,
restrict: 'EA'
});
},
'ppRow': function () {
return ({
template: '<tr ng-transclude></tr>',
transclude: true,
restrict: 'EA'
});
},
'ppLabel': function () {
return ({
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
'ppValue': function () {
return ({
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
});
pp.ctl.main = pp.mod.controller('ppMain', ['$scope', function ($scope) {
$scope.curPage = 'page1';
$scope.pages = {
"page1": {
str: "page 1",
template:'\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>1</pp-value>\
</pp-row>\
</pp-group>\
'},
"page2": {
str: "page 2",
template:'\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>2</pp-value>\
</pp-row>\
</pp-group>\
'},
"page3": {
str: "page 3",
template:'\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>3</pp-value>\
</pp-row>\
</pp-group>\
'}
};
$scope.pageList = ["page1","page2","page3"];
$scope.homePageList = ["page2", "page3"];
}]);
</script>
</body>
</html>
Looking at the generated DOM with the debugger, I find no tables, only text.
That is not possible with ng-bind-html because ng-sanitize does not compile the html. It can only be used for static content. You would need to create a directive yourself for this.
Something like this:
.directive('compileHtml', function($compile) {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
var template = scope.$eval(attrs.compileHtml); //get the template by evaluating
elm.html(template); //set the html
$compile(elm.contents())(scope); //compile the contents
}
}
});
Also remember that you have child directives with no replace, which means you will be creating invalid html, table created by ppGroup will have ppRow as child which results in invalid html and it will push the directive content out of the table and compilation will not happen properly. So you would instead need to use the option replace:true on these directives.
However it will be really unsafe to compile the html dynamically though unless you know it is from a trusted source which is what $sce service used by ng-bind-html does. It sanitizes the boundhtml.
Demo
var pp = {
ctl: {}
};
pp.mod = angular.module('pp', ['ngSanitize']);
pp.mod.directive({
'ppHeader': function() {
return ({});
},
'ppGroup': function() {
return ({
template: '<table ng-transclude></table>',
transclude: true,
restrict: 'EA'
});
},
'ppRow': function() {
return ({
replace: true,
template: '<tr ng-transclude></tr>',
transclude: true,
restrict: 'EA'
});
},
'ppLabel': function() {
return ({
replace: true,
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
'ppValue': function() {
return ({
replace: true,
template: '<td ng-transclude></td>',
transclude: true,
restrict: 'EA'
});
},
}).directive('compileHtml', function($compile) {
return {
restrict: 'A',
link: function(scope, elm, attrs) {
var template = scope.$eval(attrs.compileHtml);
elm.html(template);
$compile(elm.contents())(scope);
}
}
});
pp.ctl.main = pp.mod.controller('ppMain', ['$scope',
function($scope) {
$scope.curPage = 'page1';
$scope.pages = {
"page1": {
str: "page 1",
template: '\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>1</pp-value>\
</pp-row>\
</pp-group>\
'
},
"page2": {
str: "page 2",
template: '\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>2</pp-value>\
</pp-row>\
</pp-group>\
'
},
"page3": {
str: "page 3",
template: '\
<pp-group>\
<pp-row>\
<pp-label>Page:</pp-label><pp-value>3</pp-value>\
</pp-row>\
</pp-group>\
'
}
};
$scope.pageList = ["page1", "page2", "page3"];
$scope.homePageList = ["page2", "page3"];
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.3/angular-sanitize.min.js"></script>
<div ng-app="pp">
<div ng-controller="ppMain">
<ul class="menu">
<li ng-repeat="pageID in homePageList">{{pages[pageID].str}}</li>
</ul>
<div>
<div ng-repeat="pageID in pageList" compile-html="pages[pageID].template">
</div>
</div>
</div>
</div>
I would like to have a directive which creates a div as title and a ul list under it.
I want the title to be set from attribute and a the list by a controller.
Here is a fiddle of my code
HTML:
<div ng-app="myModule">
<my-list caption="My List" ng-controller="ListController"></my-list>
</div>
JavaScript:
angular.module('myModule', []).
controller('ListController', ['$scope', function ($scope) {
$scope.items = [{
caption: 'Item 1'
}, {
caption: 'Item 2'
}, {
caption: 'Item 3'
}];
}]).directive('myList', [function () {
return {
restrict: 'E',
template: '<div>' +
'<div style="font-weight:bold;">{{caption}}</div>' +
'<ul>' +
'<li ng-repeat="item in items">{{item.caption}}</li>' +
'</ul>' +
'</div>',
scope: {
caption: '#caption'
},
link: function (scope, element) {
element.find('li').on('click', function (evt) {
alert($(this).html());
});
}
}
}])
How can I solve this issue?
fixed few things in your controller.
moved controller to div
<div ng-app="myModule" ng-controller="ListController">
<my-list caption="My List" list="items"></my-list>
</div>
fixed directive to receive list as a parameter
directive('myList', [function () {
return {
restrict: 'E',
template: '<div>' +
'<div style="font-weight:bold;">{{caption}}</div>' +
'<ul>' +
'<li ng-repeat="item in items" ng-click="onClick(item)">{{item.caption}}</li>' +
'</ul>' +
'</div>',
scope: {
caption: '#caption', items: '=list'
},
link: function (scope, element) {
scope.onClick= function(item){console.log(item);}
}
}
}])
there is one doubt though that i have.
controller myList to is tried to view or to directive?? in case it is tied to directive then
angular.module('myModule', []).
controller('ListController', ['$scope', function ($scope) {
$scope.items = [{
caption: 'Item 1'
}, {
caption: 'Item 2'
}, {
caption: 'Item 3'
}];
}]).directive('myList', [function () {
return {
restrict: 'E',
template: '<div>' +
'<div style="font-weight:bold;">{{caption}}</div>' +
'<ul>' +
'<li ng-repeat="item in items" ng-click="onClick(item)">{{item.caption}}</li>' +
'</ul>' +
'</div>',
scope: {
caption: '#caption'
},
link: function (scope, element) {
scope.onClick= function(item){console.log(item);}
},
controller: 'ListController'
}
}])
The problem (as the error message indicates) is that you specify multiple directives which request for an isolated scope.
Actually, if you want to specify a controller for your directive, you use the controller property of the Directive Definition Object:
<my-list caption="My List"></my-list>
.directive('myList', [function () {
return {
...
controller: 'ListController',
...
Updated fiddle
I have a boxcomponent that I'm creating with a tpl. So in initTemplate I set the template I want by doing
this.tpl = new Ext.Xtemplate( my_template ).
So in my_template, which is a bit long actually, I have a block which I wish to repeat more than once inside my_template.
What's the best way to achieve that? I wish I didn't have to copy the block over and over, specially if that block is long.
for example, let's say that my_template =
'<tpl for="value1">',
'<div class="c1">{a}</div>',
'<div class="c2">{b}</div>',
'</tpl>'
'<tpl for="value2">',
'<div class="c1">{a}</div>',
'<div class="c2">{b}</div>',
'</tpl>'
my data looks like:
data = {
value1: {
a: 'blah',
b: 'bleh'
}
value2: {
a: 'bloh',
b: 'bluh'
}
}
So, in my example, I'd like to know if there's a way to sort of call a function that will repeat
'<div class="c1">{a}</div>',
'<div class="c2">{b}</div>',
So I'd have
'<tpl for="value1">',
myFunction();
'</tpl>'
'<tpl for="value2">',
myFunction();
'</tpl>'
Now it is entirely different question, after you have edited it. Nevermind, the answer is yes, you can use member functions to generate the content. This is example from XTemplate docs. As you can see, anything returned from isBaby and isGirl methods below is rendered in the resulting html. You only need to set myFunction and then call it within the template: this.myFunction()
var tpl = new Ext.XTemplate(
'<p>Name: {name}</p>',
'<p>Kids: ',
'<tpl for="kids">',
'<tpl if="this.isGirl(name)">',
'<p>Girl: {name} - {age}</p>',
'<tpl else>',
'<p>Boy: {name} - {age}</p>',
'</tpl>',
'<tpl if="this.isBaby(age)">',
'<p>{name} is a baby!</p>',
'</tpl>',
'</tpl></p>',
{
// XTemplate configuration:
disableFormats: true,
// member functions:
isGirl: function(name){
return name == 'Aubrey' || name == 'Nikol';
},
isBaby: function(age){
return age < 1;
}
}
);
tpl.overwrite(panel.body, data);
And data:
var data = {
name: 'Don Griffin',
title: 'Senior Technomage',
company: 'Sencha Inc.',
drinks: ['Coffee', 'Water', 'More Coffee'],
kids: [
{ name: 'Aubrey', age: 17 },
{ name: 'Joshua', age: 13 },
{ name: 'Cale', age: 10 },
{ name: 'Nikol', age: 5 },
{ name: 'Solomon', age: 0 }
]
};
It depends if data you feed into the template also repeats - if it is an array. As you have <tpl for=".">, you can have <tpl for="innerArray"> so if your data looks similar to the following:
[
{item:'value', innerArray:[{
innerItem:'value1'
},{
innerItem:'value2'
}]}
]
it will work.
new Ext.XTemplate([
'<tpl for=".">',
'<div>',
'<tpl for="list">',
'{[ this.getTemplateIfDataPresent(values, "key_a") ]}',
'{[ this.getTemplateIfDataPresent(values, "key_b") ]}',
'</tpl> ',
'</div>',
'</tpl>'
].join(""), {
compiled: true,
templateStr: '<p> {0}: <b>{1}</b> </p>',
getTemplate: function(values, key) {
if (!values || !values[key]) {
return '-';
}
var result = String.format(this.templateStr, key, values.list[key]);
return result;
}
});