AngularJS error when setting class in directive - angularjs

I'm creating a directive that renders a list of content from a method passed in via an attribute. In addition to passing a method to get the data from, it accepts two attributes to set a class on the ul and li elements rendered in the list. When I set the class on the li element, it works fine. When I try to set a class on the ul, chaos ensues: the ng-repeat no longer works as expected and it throws an exception "undefined is not a function"
Here's a simplified version of the directive that has the issue.
var myApp = angular.module('myApp',[]).
directive('contentList',function(){
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
method:'&',
ulclass: '#',
liclass: '#'
},
template: '<div class="content-list-wrapper"> ' +
'<div class="content-loader" ng-show="loading"><i>Loading...</i></div>' +
'<ul class="content-list {{ulclass}}">' +
'<li class="content-list-repeat {{liclass}}" ng-repeat="item in items" ng-transclude></li>' +
'</ul>' +
'</div>',
link: function(scope,iElement,iAttrs){
scope.items = scope.method();
}
} ;
});
If you remove {{ulclass}} in the class attribute of the ul element, everything works. I'm baffled why it works for the li, but not the ul.
I've setup a jsfiddle at http://jsfiddle.net/woogychuck/wqU63/

Related

Default ngModel in angular directive not being set

I have this custom directive in angular that looks like this: (removed most of the code for brevity).
angular.module("SharedModule")
.directive('singleSelect', ['$ionicModal', singleSelect]);
function singleSelect($ionicModal) {
return {
restrict: 'E',
require: 'ngModel',
replace:true,
template: [
'<div class="item item-icon-right" ng-click="showItems($event)" >',
'{{text || headerText}}',
'<i class="icon ion-ios-arrow-right"></i>',
'</div>'
].join("")
,
scope: {
items: "=",
headerText: "#"
},
link: function (scope, element, attrs, ngModelController) {
if (ngModelController.$modelValue){ // i even tried $viewValue here
scope.selectedItemId = ngModelController.$modelValue.id;
scope.text = ngModelController.$modelValue.text;
}
scope.change = function (item) {
ngModelController.$setViewValue(item);
scope.text = ngModelController.$viewValue.text;
scope.close();
};
}
}
}
I set the default for the ng-model as:
<single-select ng-model='defaultValue' header-text='text when no default present'>
and setting the value for defaultValue to {id:"1",label:"foo"} in the scope.
I expect that my template shows a foo when rendering my directive template but it does not. Debugging shows that my ngModel values are set to NaN. I am sure i am missing something trivial here. What could it be?
Also, how could i display my ngModel's label value in the template without an intermediate scope property like the text i have used.
UDPATE: i have created a sample jsfiddle to explain the problem well.
I expect my div to display foo in the jsfiddle and not the default text.
if you replace you make you set your header text as follows foo will appear header-text={{defaultValue.text}} and remove ng-model, you can use angular's default templeting system
I've included a full JSfiddle: http://jsfiddle.net/0Lpy7gff/

Assign random ngModel to directive

I'm trying to assign a random string to ngModel. But I can't even seem to assign a regular string to it. In the code below, I'm trying to change the ngModel to "new", but in chrome, it's still showing me that the ngModel is "placeholder". What am I doing wrong?
app.directive("page", function(){
return {
restrict: 'E',
replace: true,
template: '<div ng-model="placeholder"></div>',
controller: function ($scope, $element) {
$scope.placeholder = "new";
}
}});
The plain div tag was just an example. What I actually have is a content editable div that I've bound to a textarea with a contenteditable directive. I made a button to allow me to add as many of these directives as I want, but when I add this directive, I'd like a new ng-model for each one, because that's what I'm using to save the content of each content editable div to a file.
This is the full example in case it might help someone else. I put the addPage directive to a button, which, when clicked, appends a new content editable div (which I'm calling a page). There is one more directive that I didn't include (contenteditable) because I got it from the bottom of the docs over here
app.directive("addPage", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){ angular.element(document.getElementById('container')).append($compile('<page></page>')(scope));
});
};
});
app.directive("page", function(){
return {
restrict: 'E',
replace: true,
// template: '<div class="page" contenteditable strip-br="true" ng-model="chapter"></div>',
template: function (elem, attr) {
var test="chapter.two"; //in reality, I will generate a random string to keep these divs unique
return '<div class="page" contenteditable strip-br="true" ng-model=' + test +'></div>'
}
}});
In my controller, I have this object that is used to store all the ng-model content
$scope.chapter = {};
I think you are approaching this a bit backwards. Starting with the model, you'd need an array to keep the values from all of the inputs / contenteditables.
.controller("MainCtrl", function($scope){
$scope.data = [{v: ""}]; // first element
$scope.addContentEditable = function(){
$scope.data.push({v: ""});
};
})
Then, you can bind to each element of that array easily, and add elements at will:
<button ng-click="addContentEditable()">Add</button>
<div ng-repeat="item in data" ng-model="item.v" contenteditable></div>
I'm not sure exactly how the page directive needs to be used here, but one way or another, the approach is the same as above.
If you just want to pass a string in you could use attributes and template(fn)
HTML
<div page="new">
Directive
app.directive("page", function () {
return {
restrict: 'E',
replace: true,
template: function (elem, attr) {
return '<div ng-model="' + attr.page + '"></div>';
}
}
});

attribute value in directive angularjs

i have html markup that i need to use at many places so rather to copy paste it again and again but with different Headings
i thought to create a directive it looks like this
myModule.directive('row', function ($compile) {
return {
restrict: 'E',
template: '<div class="row">'+
'<div class="col-md-10 margin-top-10px font-18px">{{heading}}</div>'+
' <div class="col-md-2">'+
'<div class="margin-top-10px"><span ng-click="close()" class="close helvetica color-black">×</span></div>'+
'</div>'+
'</div>',
replace: true,
link: function (scope, element, attrs, ctrl) {
var test = attrs.heading;
}
};
});
and how i m trying to use it
<row heading="its my heading "></row>
i cant make it work .i dont want to create isolated scope or child scope i just wanted to pass value from attribute and when template gets replaced i want its interpolated value .
can some one help me how can id o this
Try with:
link: function (scope, element, attrs, ctrl) {
scope.heading = attrs.heading;
}
It might also be a good idea to set
scope: true
You have to actually attach the value from the attr object to the scope before it can be used in the template.
add
scope: {
heading: '='
}
to your directive's definition to create 2 way data-binding with that attribute. you would do this if you are passing in a scope variable.
or use #
scope: {
heading: '#'
}
if you are just passing in a string.
minimalist plunkr to demonstrate: http://plnkr.co/edit/U5OzlMiXaIsHEdb5gmRT

Angular: How to get content from a custom div

I want to get a the content from a custom div tag, I tried various ways to do it, not working well. Here is an example. The general item is to retrieve the content in the custom directive tags. and then bind them into the template. I hope some one can give me a suggestion or solution that does it, or does similar things
The html
<questions>
<qTitle> this is title</q-title>
<qContent> this is content <q-content>
</questions>
The angular js
var app = angular.module('app'[]);
app.directive('questions', function () {
return {
transclude: true;
template: "<div class='someCSSForTitle'>{{qTitle}}</div>"+
"<div class='someCSSForContent'>{{qContent}}</div>"
link:(scope, element, attrs)
scope.qTitle = element.find(qTitle).innerHTML
scope.qContent = element.find(qContent).innerHTML
}
}
});
First I'd advise you to read the AngularJS Guide. You didn't even copy-paste the structure correctly and you have javascript and even html errors.
Basic fixes:
HTML
<questions>
<q-title>this is title</q-title>
<q-content>this is content</q-content>
</questions>
Why do you mix qTitle and q-title?
As regarding JS:
app.directive('questions', function () {
return {
restrict: 'E',
replace: true,
template: "<div class='question'>{{title}}</div>", /* simplified */
link: function(scope, element, attrs) {
scope.title = "hallo";
console.log(element.html());
}
};
});
by default, restrict is set to 'A'. That means attributes. Your syntax is for elements.
replace set to true is not compulsory. However, because the browser doesn't understand your elements but does understand the content ("this is title"), it will print it.
the link function has to be a function. you had syntax errors there (same for transclude: you had something that you were not using followed by ";")
You can print the element to know the contents. If you do, you'll see that element in link is not question.
Now if you want to read the content, you can use a transclude function or create directives for each part and create the template separately. This seems simpler. Live example:
app.directive('questions', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
template: "<div class='question' ng-transclude></div>",
};
});
app.directive('qTitle', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
template: "<div class='title' ng-transclude></div>",
};
});
In this case you translude the contents to an inner div.
You can also define custom complex transclude functions in the compile phase but this doesn't seem necessary here.

AngularJS - Create dynamic action in Super Directive

I have a super directive made up of 2 directives. The second "child" directive is a add-new open dialog control:
Here is the plunker:
http://plnkr.co/edit/b6G2y3yqjhxpu059ZrWB
If you examine the super directive "selectAddNew", third line from the bottom, you will see this code:
<div txt-add-new text="{{text}}" action="openDialog(\'Front\')" style="display: inline-block"></div>
The problem is action="openDialog(\'Front\')" is hard coded.
The super directive's html is this:
<select-add-new select-model="$parent.selectedFrontAxle" text="add new"
select-phrase="Front Axle Type" preselected-filter="Front"
label-name="Front Axle" open-dialog="Front" <------ need to pass this value
select-options="axleTypes" var-ctrl="AxleTypesCtrl"></select-add-new>
I can live with the method OpenDialog, if I have too, but the paramater \'Front\', needs to get it's value from this part of the Html above:
open-dialog="Front"
Initially I tried this (making it an method action):
open-dialog="openDialog('Front')"
With this in my directive:
.directive('', function(){
..........
scope: {
open-dialog: "&"
},
......
},
template: .....
'<div txt-add-new text="{{text}}" action="openDialog()" style="display: inline-block">
......
};
But I found myself in an endless loop when reviewing the code in Chrome console
Mark I solved it, code below:
<select-add-new select-model="$parent.selectedFrontAxle" text="add new"
select-phrase="Front Axle Type" preselected-filter="Front"
label-name="Front Axle" dialog-param="openDialog('Front')"
select-options="axleTypes" var-ctrl="AxleTypesCtrl"></select-add-new>
.directive('selectAddNew', function () {
return {
replace: true,
restrict: "E",
scope: {
selectModel: "=",
selectOptions:"=",
labelName: "#",
preselectedFilter: "#",
selectPhrase: "#",
text: "#",
},
compile: function(tElement, attrs) {
var div = tElement.find('#ctrlId');
div.attr('ng-controller', attrs.varCtrl);
var div2 = tElement.find('#OpenWindow');
div2.attr('action', attrs.dialogParam);
},
template: '<div>' +
'<div class="local-label">{{labelName}}: </div>' +
'<name-value-select-control select-phrase="{{selectPhrase}}" selected-item="selectModel" preselected-filter="{{preselectedFilter}}" options="selectOptions"></name-value-select-control>' +
'<div id="ctrlId">' +
'<div id="OpenWindow" txt-add-new text="{{text}}" style="display: inline-block"></div>' +
'</div>' +
'</div>'
};

Resources