AngularJS - ng-bind-html-unsafe inside a directive not working - angularjs

I am having the next problem. As you can see in my jsFiddle I am trying to use ng-bind-html-unsafe inside a template in my directive, and the attribute's value that I'm passing item{{itemColumn.field}} depends because is inside an ng-repeat. The thing is that I am using the ng-bind-html-unsafe in the columns that the attribute highlight is true, because the idea is to filter data (using the text input) and highlight the selection introduced by the user in the input. And as you can see, there is no value in those columns (because it seems that the binding is not working for some reason).
I have read about possible solutions and it one guy said that it can be fixed using $compile (which I'm actually using), so I have some time stuck in this with no idea on how to solve it.
Someone has faced something like this before? and can give me some ideas on how to solve the problem?
EDIT:
As Joachim suggests, I will provide more relevant code. In my template I have this
<td ng-repeat=\"itemColumn in gridOptions.gridColumnDefs \"
ng-show=\"itemColumn.visible | elementIsDefined : itemColumn.visible : true\" >
<div ng-switch on=\"itemColumn.highlight\"> " +
<span ng-switch-when=\"true\">
<div ng-bind-html-unsafe=\"item.{{itemColumn.field}} | highlight: {{gridOptions.searchInput}}\" ></div>
</span>
<span ng-switch-when=\"false\">{{item[itemColumn.field]}}</span>
</div>
</td>
I think my problem is related to the fact that I am trying to use a binding {{ }} inside the ng-bind-html-unsafe directive (Which i need). When the page renders, I got my div with the attributes as stated in the template, but the ng-bind-html-unsafe does not renders any HTML.

Just in case you need it, i found a way to make my issue disappear. In the link function inside my directive, I added the next functions:
scope.getValueToBind = function (item, subItem) {
return item[subItem];
};
scope.getFieldToFilter = function () {
var inputValue = scope.gridOptions.searchInput;
var returnValue = $("input[ng-model='" + inputValue + "']").val();
return returnValue;
};
And in my template I call this new functions instead of having a direct binding in the ng-bind-html-unsafe (which does not work at all with internal bindings). The functions return the values that I needed (as If i had a binding)
<td ng-repeat=\"itemColumn in gridOptions.gridColumnDefs \"
ng-show=\"itemColumn.visible | elementIsDefined : itemColumn.visible : true\" >
<div ng-switch on=\"itemColumn.highlight\">
<span ng-switch-when=\"true\"><div ng-bind-html-unsafe=\"getValueToBind(item,itemColumn.field) | highlight: getFieldToFilter()\" ></div>
</span>
<span ng-switch-when=\"false\">{{item[itemColumn.field]}}</span>
</div>
</td>
Here you can find the complete jsFiddle. If you type any letter/word that is inside the table, it will be highlighted.

Related

Programmatically Open Bootstrap UI Accordion Generated by Nested Ng-Repeat when Filtered Array is Not Empty

I have a Bootstrap-UI accordion group that generates individual accordion menus using ng-repeat. The content under each accordion is also generated using a nested ng-repeat.
<accordion close-others="false">
<span data-ng-repeat="category in categories">
<accordion-group is-open="filterText.length > 0">
<accordion-heading>{{category}}: {{reportList.length}} Items </accordion-heading>
<div>
<ul>
<li data-ng-repeat="report in reportList = (getReports($parent.$index) | filter: filterText)">{{report}}</li>
</ul>
</div>
</accordion-group>
</span>
</accordion>
The content generated by the second ng-repeat needs to be searchable. When the search is executed, accordions that contain matching values should open and display the matching values. I know that the outside ng-repeat sees the filtered array and its length because i can display the length value in the view (i.e. {{reportList.length}} updates when the filter executes).
The problem comes when i try to put the reportList.length value in the is-open attribute of an <accordion-group>. Using is-open="reportList.length" seems to pass the literal into the directive. In desperation, I tried using is-open="{{reportList.length}}" but that throws the expected syntax error.
Here's a plunker that shows what i have working as well commented out lines that show the way i think it should work (lines 22 & 23). Any suggestions are more than welcome.
Your binding is-open inside of an ng-repeat which creates a child scope for each item (category). You need to bind to $parent.filterText.length as filterText is not a category property.
What you bind the is-open to, Angular needs to be able to assign to it, not just evaluate it. It can evaluate an expression like "foo == 5" but it cannot assign to it.
What I do is create a variable and bind to that, then the accordion can change it, and I can change it, and everybody's happy.
$scope.accordionSettings = {
IsOpen: {
Section1: true,
Section2: false
}};
In the markup:
<div uib-accordion-group is-open="accordionSettings.IsOpen.Section1">
... more markup ...
<div uib-accordion-group is-open="accordionSettings.IsOpen.Section2">

Angular JS ng-repeat and the 'this'

i'm new in AngularJS, but did some jQuery before. i've got a problem to understand how to get the clicked element / it's parent to make some changes like change the text, an icon or a class in the item where i made the click.
the simple HTML:
<ul ng-controller="basketCtrl">
<li ng-repeat="item in item">
<button ng-click="addToBasket(Itemid,this,whatever)">
<i class="myBasketicon">
<span>Buy now</span>
</button>
</li>
</ul>
what i want to do:
$scope.addTobasket = function (id, elem, whatever){
// to some JSON-Server-stuff - that works perfect
// now my problems, :
//change this -> myBasketIcon -> myOKicon
//change this -> span text Buy now-> Thanks for buying
// give the this -> li an class => 'changed'
}
I really tried a lot, f.e with ng-model in the tags, arrays... search the web half the day... but didn't find anything that matches my problem.
Maybe it's just the way of thinking not the angular way... so please help :O)
Kind regard from Hamburg, Germany
Timo
You should be able to do this by changing a property (angular way), no need to access the element in the ng-click handler,and using ng-class and angular binding on that property.
<ul ng-controller="basketCtrl">
<li ng-repeat="item in items" ng-class="{'changed': item.added}">
<button ng-click="addToBasket(item)">
<i ng-class="{'myBasketicon':!item.added,'myOKicon':item.added }">
<span>{{item.added ? "Thanks for buying" : "Buy now"}}</span>
</button>
</li>
</ul>
and in your handler just do:
$scope.addTobasket = function (item){
item.added = true;
}
Most cases, whole purpose of using angular is to avoid DOM manipulation and let angular manage it, you just deal with the models/viewmodels and bindings.
You should add methods for the icon class and text that change their results based on the state of the object, or use custom a custom directive. You definitely don't want to be doing any DOM manipulation (changing text/classes etc) the way you would have done with jQuery.
For the method-based approach, something like this for your markup:
<li ng-repeat="item in item">
<button ng-click="addToBasket(item)">
<i ng-class="getClass(item)">
<span>{{getMessage(item)}}</span>
</button>
</li>
and on your controller:
.controller('ShoppingListCtrl', function($scope) {
$scope.getClass = function(item) {
return item.inBasket ? 'myOkIcon' : 'myBasketIcon';
};
$scope.getMessage = function(item) {
return item.inBasket ? 'Thanks for buying' : 'Buy now';
};
})
This could also be done with a custom directive which is a super powerful way to do things (and definitely worth figuring out) but may be overkill for just starting out. If you find you are adding a lot if methods for doing these sorts of things go with directives.

AngularJS's ng-model icm textarea

I'm trying to add some text to the last cursor place after clicking a button.
In the controller:
$scope.addEmoji = function(name){
var element = $("#chat-msg-input-field");
element.focus(); //ie
var selection = element.getSelection();
var textBefore = $scope.chatMsg.substr(0, selection.start);
var textAfter = $scope.chatMsg.substr(selection.end);
$scope.chatMsg = textBefore + name + textAfter;
}
$scope.updateChatMsg = function(chatMsg){
$scope.chatMsg = chatMsg;
}
$scope.sendChatMsg = function(){
var backend = $scope.convs[$scope.active.conv].backend.name;
$scope.view.addChatMsg($scope.active.conv, $scope.active.user, $scope.chatMsg,new Date().getTime() / 1000, backend);
Chat[backend].on.sendChatMsg($scope.active.conv, $scope.chatMsg);
$scope.chatMsg = '';
};
And then some HTML:
<div class="chat-msg-button" >
<button ng-click="view.toggle('emojiContainer')" ><img src="/apps/chat/img/emoji/smile.png"></button>
</div>
<form id="chat-msg-form" ng-submit="sendChatMsg()">
<div class="chat-msg-button" >
<button type="submit"><div class="icon-play"> </div></button>
</div>
<div id="chat-msg-input">
<textarea id="chat-msg-input-field" autocomplete="off" type="text" ng-model="chatMsg" ng-change="updateChatMsg(chatMsg)" placeholder="Chat message"></textarea>
<div>{{ chatMsg }}</div>
</div>
</form>
What I'm trying to achieve: a user types some text in the textarea => $scope.chatMsg gets the value of the textarea. Now the user press one of the button's => the name of the button is added to the latest cursor position. (it's no problem to find the latest cursor position)
The problem
There is a difference between the value of $scope.chatMsg, {{ chatMsg }} inside the div and the text in the textarea.
The contents of the textarea and the div stays always the same. But when pressing the button the name is added to $scope.chatMsg but the contents of the textarea isn't changed...
How can I solve this?
TIA
First of all, you're mixing jQuery with AngularJS, it doesn't look like you need jQuery here that much.
Also, your chat message is updated in 3 different functions, so you need some debugging to see which are fired.
In general:
To solve your issue, try some more debugging, do a
$scope.$watch($scope.chatMsg, function(){
console.log($scope.chatMsg);
});
this will watch all changes to chatMsg. Add console.log() to each of your functions and you can watch which is fired.
Also, rather than using {{ }} inside your div just use ng-bind since that text is the only item in your div, it's cleaner if your app crashes somewhere.
// change from
<div>{{ chatMsg }}</div>
// to
<div ng-bind="chatMsg "></div>
Update: after seeing your plunker, I modified it and came up with this: http://plnkr.co/edit/oNKGxRrcweiJafKCm9A5?p=preview
Your ng-repeat needs to be tracked by $index so that duplicates are displayed rather than crashing when someone creates the same message
I solved all problems. The plunkr form above works. So after investigating all scopes with the Angular chrome extension I saw that chatMsg was defined in another scope. Thus not in the scope I was trying to acces it from.
Via this question angularJS ng-model input type number to rootScope not updating I found a solution.
I added chatMsg to the fields object.

Duplicate attributes when using directive compile with transclude

JsFiddle of the issue: http://jsfiddle.net/UYf7U/
When using the angularJs transclude within a directives compile, it will duplicate any
attribute properties. I.e.
<a class="myClass">my link</a>
Will become
<a class="myClass myClass">my link</a>
Similarly, when using an ngClick
<a ng-click="myFunction()"> my link</a>
Will become
<a ng-click="myFunction() myFunction()"> my link</a>
The fiddle demonstrates this, and unfortunately it crashes. It's a stripped down version of what I'm trying to implement.
Is there a way around this? I've posted the issue to github to: https://github.com/angular/angular.js/issues/2576
When clicking on Hello the word "clicked" should appear in an alert.
This happens because myDirective is being initialized twice - first as part of your markup:
<div class="transcludeMe">
<div data-transclude-this="here">
<div class="myDirective"></div>
</div>
</div>
Second in the transcludeMe directive - since you do this in the compile stage of the directive initialization:
transcludeHere[0].innerHTML = clone[x].innerHTML
Since you use replace:true all attributes of the original element will get copied to the template element. If you remove this your example works, but you still be aware that myDirective is getting initialized two times: http://jsfiddle.net/tkzgG/
How important is it to you to specify the directive name as a class? This issue does not occur when the directives are used as elements directly.
See http://jsfiddle.net/smmccrohan/cfP3U/
Like thus, plus replacing the restrict: 'C' with restrict: 'E' in the directive definitions (and making some case changes to avoid an issue there):
<div ng-app="app">
<div ng-controller="ParentCtrl">
<transcludeme>
<div data-transclude-this="here">
<mydirective />
</div>
</transcludeme>
</div>
</div>
I found a different way to do multi-transclusion and that fixed my problem entirely, here's the updated fiddle for my problem being fixed: http://jsfiddle.net/UYf7U/1/
The code came from my previous question here: Multiple transclusions of separate html in an update that I did not see.
The fiddle will be out of date, but this is my final multi transclusion function. I've mode the logic into compile instead of the controller so that it can transclude dom that needs to have things like ng-repeat
.directive('multiTranscludeTo', function($rootScope){
return {
compile: function(tElement, tAttributes, transclude){
var baseScope = this;
transclude($rootScope, function(clone){
for (var x = 0; x < clone.length; x++){
var child = angular.element(clone[x]);
var viewName = child.attr('data-multi-transclude-from') || child.attr('multi-transclude-from');
if (viewName && viewName.split(" ")[0] == tAttributes["multiTranscludeTo"]){
tElement.html(clone[x].innerHTML);
}
}
});
}
}
})

Adding new form fields dynamically in ngView - Angularjs

I use routing to load different templates into a ngView. One of the templates has a simple controller which contains an array of contacts. What I'm trying to do here is as simple as by clicking a link (ngclick) call a function and add a new object into the array which I expect will be reflected in my UI.
It's something like this:
$scope.contacts = [{name='', email=''}];
<li ng-repeat="con in contacts">
<input type="text" ng-model="con.name"/>
<input type="email" ng-model="con.email"/>
</li>
<li>
<a href ng-click="addContact()">add</a>
</li>
$scope.addContact = function() {
$scope.contacts.push( {name='', email=''} ); //-- i can use either $scope or this to reference the array and works.
}
So, the UI renders well with the initial value, the addContact function is invoked on click and I see the value is pushed (length = 2) but then the function ends the array seems to be reset to one element (lenght = 1) after angularjs evaluation.
I'm not sure if this is occurring because I use ngView. I mean, I reviewed this example (http://code.angularjs.org/1.0.3/docs/api/ng.directive:ngController) and I don't see much differences of what I'm trying to do here, the diff is that I use routing with ngView.
fiddle: http://jsfiddle.net/fdDph/
Help is much appreciated.
In your Fiddle, you are resetting the array length to 1 in the ng-show:
<span ng-hide="contacts.length = 1">
Do this and it will work:
<span ng-hide="contacts.length == 1">
{name='', email=''} is wrong syntax btw, it should be {name:'', email:''}

Resources