Auto-scrolling multiple chat logs using AngularJS - angularjs

In this simplified example, I have:
$scope.channels = [channel1, channel2, ...]
and a channel is an object containing:
chatLog: ["msg1", "msg2", ...]
In the .html, I have something similar to:
<div ng-repeat="channel in channels" ng-show="channel.isActiveWindow">
<div class="chatlog" style="height:500px;">
<div ng-repeat="msg in channel.msgLog">
<div>{{msg}}</div>
</div>
</div>
</div>
I'd like to implement automatic scrolling when a new message is added to channel.chatLog. What would be a proper way to do this with AngularJS? I can compute where to scroll to, but I need an event to fire when the chatLog is appended.
I was thinking of setting up a watch on the chatLog of every channel, but this becomes way too manual and I need to be careful about removing the watch as well. Another way would be to not rely on watches but make sure every place in the code that will append to a chatLog will also fire the event and provide some info about which channel the event relates to.
Also, when this event is fired, is there a way to access the div with the class "chatlog" in the code above for the given channel without assigning it a unique 'id' and looking it up by that?
Thanks in advance :)

You could create a chatlog directive and add it to your markup like this:
<div chatlog ng-repeat="msg in channel.msgLog">
Then in the linking function of your directive you can do something like:
link: function(scope, element, attrs) {
if (scope.$last) {
// this will run each time the channel.msgLog array changes
// more precisely, every time the last element is linked
}
}

Related

ng-class not updating inside of ng-repeat when array is changed

The code below is from a chat interface in an Angular application. The user will select the users they wish to send the message to by clicking on user bubbles which will add those users to an array selectedChatUsers
Lists out the users. Highlights the ones that are currently selected.
<div ng-repeat="user in PlayerController.chatUsers | orderBy:['type','name']"
class="chat-recipient"
ng-class="{'selected-recipient' : PlayerController.selectedChatUsers.indexOf(user) >= 0 }"
ng-click="PlayerController.selectRecipient(user)">
<div class="chat-recipient-name" ng-bind="user.name"></div>
<div class="chat-recipient-icon"></div>
</div>
Another feature of the application is the ability to click on a chat bubble from a sent message, and have the selectedChatUsers array be populated with the "to" property, which is an array of users that this message was sent to.
This functionality works, but the ng-class directive that should show those users are selected, does not function.
function chatReply(message){
/* one attempt was to try and manipulate the array rather than copy the information directly. This attempt had no more success. I'm including it here just to show that it was tried */
/****
self.selectedChatUsers.length = 0;
angular.forEach(message.to, function(recipient){
self.selectedChatUsers.push(recipient);
});
****/
/* Also tried wrapping the whole thing in $timeout and $scope.$apply, this also doesn't update the ng-class in the view */
$timeout(function() {
$scope.$apply(function(){
self.selectedChatUsers = angular.copy(message.to);
});
});
}
Suggestions on how I can make the ng-class in the view be updated when selectedChatUsers is changed?
Both class and ng-class will not work together, always apply class:
class="chat-recipient"
ng-class="{'selected-recipient' : condition }"
remove class and change it to:
ng-class="condition == true ? 'chat-recipient selected-recipient' : 'chat-recipient'"

Using contenteditable with ng-model inside ng-repeat?

Here is my issue:
I am using ng-repeat to make a list of spans.
Each span has the contenteditable attribute and ng-model directive.
Everything works as expected (including two-way data binding), until I try to add a new item to the list.
<div ng-repeat="item in list">
<span ng-model="item.text" contenteditable></span>
</div>
<button ng-click="addItemToList"></button>
The methods look like this:
$scope.addItemToList = function () {
$scope.list.push({text: 'dummy text'});
}
or
$scope.addItemToList = function () {
$scope.list.splice(1, 0, {text: 'dummy text'});
}
When adding the new item to the list (push or splice), the DOM updates, but the last item is initialised empty, with no text whatsoever. The last item in the model list also empties out, even if I specifically push an element with text in it.
After a few tests, I've noticed that this only happens if the list's length is bigger after modifying it:
if I try to replace/modify/remove (not add) an element in the list, it works well.
I believe this has to do with the way contenteditable elements initialise in the DOM (I think they initialise empty, and somehow, the model empties out as well).
Has anyone encountered this problem before? If yes, how did you solve it / what workaround have you found?
Based on the angular docs related to ngModelController, it seems that there is not built-in support for two-way data binding with contenteditable elements, seeing as in the plunkr example they wrote their own contenteditable directive. You might be able to use a modified version of that as a workaround.
It looks to be a similar problem as this question and the contenteditable directive there looks similar to the contenteditable directive in the angular docs example.
I also found this directive on github that might accomplish what you are trying to do.
Edit: I did a new version of the plunk I posted in the comment above:
https://plnkr.co/edit/v3elswolP9AgWHDIPwCk
In this version I added a contenteditable directive that appears to be working correctly. It is basically a spin off of how the input[type=text] directive is written in angular, but I took out the places where it handles different types of input (since in this case it will just be text) and the places where it handles events that contenteditable elements don't even fire. I also changed it so that the $viewValue gets updated based on element.html() instead of element.val(). You might be able to use something like this as a workaround
The issue is old but that was the same problem for me today. (angular 1.5). My workaround was to add on blur update option: <td contenteditable data-ng-model="position.value" ng-model-options="{updateOn: 'blur'}"></td> somehow then model stopped getting be empty on initialize. I like using update on blur in many places (solves some performaces issues) so this is good for me, however it's some kind of trick.

Detect clicks on button array with a single method

Im working in a school project, a minesweeper. Will be 20x20, so it has 400 buttons. Its there a way to add an actionEvent/actionPerformed and implement a generalized method for the whole array? Or there is an easier way?
Maybe something like that (using jQuery for convenience but you can do it with Vanilla JS too):
Your HTML:
<div id="buttonsHolder">
<button data-num=1>1</button>
<button data-num=2>2</button>
<button data-num=3>3</button>
...
<button data-num=4>4</button>
</div>
Your JavaScript:
$('#buttonsHolder').on('click', 'button', (function(evt){
var buttonNum = $(this).attr('data-num');
// Now, buttonNum variable holds the button number has clicked.
});
Of course instead of use data-num atribute you can use whatever data you need.
You are placing the buttons inside a container (i suppose). Add a actionhandler to that to capture a click. And read the 'target' variable of the event, and see if that is a button

how to go from angular event to jquery selector

I know this is bad design but would like to introduce angular to a current project. I would like sayHello to be able to determine whether the element has the class 'is-a-favorite'
<div ng-click="sayHello(29, $event)" class="is-a-favorite" data-type="location" data-global-id="29" data-make-disappear="false"> </div>
$scope.sayHello=function(global_id,event){
//var selector=???
if(selector.hasClass('is-a-favorite')){
console.log("this is-a-favorite");
}
};
How would (or could) I get a reference to current DOM element to query via hasClass?
thx
The clicked element is available as $event.target, so you could check $($event.target).attr('class') or something similar.
EDIT: actually, what you'd want is to check $($event.target).hasClass('is-a-favorite')

How to get hold of template html in directive

I'm trying to create a simple time picker directive. http://plnkr.co/edit/VYGqhPbHf1yqXLpemGEP
When user click on input field I want to display the content of my template html as a dropdown below the input (will take care of css later) so that user can select a time from the list. I'm not sure how to get hold of the template (something like angular.element(template).show())
Thanks!
Edit: I managed to come up with this: http://plnkr.co/edit/zAplNKVfohXLbIzwjhy4?p=preview
Everything works except when I click any of the list, it does not set the correct model value.
Try the following:
Embed the the HTML for the date picker list
Hide the list from the html
If the input gets focus change the visibility.
Pseudo code:
HTML:
<ul ng-show="listVisible">
<li> .... </li>
</ul>
JavaScript
scope.listVisible = false;
element.$on('focus', function() {
scope.listVisible = true;
});
Do something similar in reverse.
I managed to get it working. I had to create a new scope for the dropdown element. http://plnkr.co/edit/zAplNKVfohXLbIzwjhy4?p=preview

Resources