Angular performance when drag drop : various targets and highlighting - angularjs

I have, listing of many separate items (or tiles) in a list on my page. If the user drags a file over the an item in the list, I conditionally add the highlight class in the onDragEnter and remove the class in the onDragLeave. The highlight class essentially changes the background-color of the item.
Without using a $scope.$apply, the highlight takes a second or so before being reflected in the UI, so I am wrapping the highlight within the $scope.$apply.
Now when I move the file quickly between items, the performance becomes really slow (the highlighting and un-highlighting becomes very slow) due to the large number of digest cycles. Any ideas on how I can improve the performance but at the same time have the changes reflected in the UI pretty quickly.
I tried using $scope.$digest() instead of $scope.$apply but did not notice any improvement in the performance.
My code is:
function onDragEnter() {
$scope.$apply(function() {
$scope.highlight = true; // this controls if the highlight class is added to the item using ng-class
});
}
function onDragLeave() {
$scope.$apply(function() {
$scope.highlight = false;
});
}

In case this helps someone, using $scope.$evalAsync helped improve the performance significantly.
function onDragEnter() {
$scope.$evalAsync(function($scope) {
$scope.highlight = true; // this controls if the highlight class is added to the item using ng-class
});
}
function onDragLeave() {
$scope.$evalAsync(function($scope) {
$scope.highlight = false;
});
}

Related

HandsOnTable editor custom function

I'm using the autocomplete editor of HOT, but needed to have my own template of the option-list. I've been able to accomplish that, by removing the default display and replacing it with my own while doing a lazy load of its content. But I need to perform specific tasks on each of the options being clicked.
The issue is that I cannot find a way to have my <a ng-click='doSomething()'> or <a onclick = 'doSomething()'> tags to find my "doSomething" function.
I've tried the extend prototype of the autocomplete instance, have put my function out there on my controller to no avail. Is there any way I can insert a delegate function inside this editor that could be triggered from inside my custom-made template? (Using angularjs, HOT version 0.34)
Dropdown options cannot interpret HTML instead of Headers.
To perform action when an option is selected you can use Handsontable callback : AfterChange or BeforeChange
Here you can find all HOT callbacks https://docs.handsontable.com/0.34.0/tutorial-using-callbacks.html
This JSFiddle can help you http://jsfiddle.net/fsvakoLa/
beforeChange: function(source, changes){
console.log(source, changes)
},
afterChange: function(source, changes){
console.log(source, changes);
if(!source) return;
if(source[0][1] == 0){//if ocurs on col 0
let newsource = optionsWBS[source[0][3]];
cols[1] = {
type : 'dropdown',
source: newsource,
strict: false
};
hot.updateSettings({columns: cols});
hot.render();
};
}
Thanks, I actually needed actions specific to each area being clicked. What I did to make it work was this: while inserting the items for the list, I created the element and bound it to the function right away: liElement = document.createElement('li') .... liElement.onclick = doSomething(){} .... got it working this way ..

AngularJS - Changing an element's ng-enter animation

I have multiple states in my page that change the following element :
<header ng-view></header>
I decieded to add animation to the page's transition so and decided to go with ng.animate + animate.css. so I added the following class to the header :
.slideInLeft.ng-enter
{
transition-animation: slideInLeft 1s ease-in-out;
}
.slideOutRight.ng-leave
{
transition-animation: slideOutRight 1s ease-in-out;
}
and changed my header to this :
<header ng-view class='slideInLeft slideOutRight'></header>
This works quite well but the problem comes when I want to change my animations on the run. Since the user can go to the next page or the previous page so the class should change from 'slideInLeft slideOutRight' to 'slideOutLeft slideInRight'
I tried changing the class attribute directly but the new element (the one which is entering) stayed with the same classes as before.
I tried using ng-class='someVar' and giving 'someVar' the names of the classes but that didn't work either, I'm guessing angular didn't have a chance to update the UI since I changed the properites of 'someVar' right before using $state.go to the new page.
Someone has done it before/has an ideas how to solve this?
I managed to do it using :
<header ng-view ng-class="getAnimation()"></header>
By putting a function in the ng-class attribute, I could just make it return a scope variable. That scope variable is changed right before I go to the next page/previous page.
$scope.getAnimation = funtion()
{
return $scope.classVar;
}
$scope.nextClick = function()
{
$scope.classVar = "slideInLeft slideOutRight";
}
$scope.prevClick = function()
{
$scope.classVar = "slideOutLeft slideInRight";
}

When accordion is open or collapsed (transition ended)

is it possible to know or intercept when an accordion is open or closed with angular-ui-bootstrap, only when transition is ended?
So, when one accordion content is open i can refresh iScroll instance.
Looking at
https://github.com/angular-ui/bootstrap/blob/master/src/collapse/collapse.js
There does not seem to be any event triggered on collapseDone() or expandDone() that you can hook into.
The only way you can really do this is to watch when the class 'collapsing' exists (meaning collapsing is happening), then you know collapsing is over when that class goes away.
$scope.$watch(function() {
return $('.panel-collapse').hasClass('collapsing');
}, function(status) {
if ($scope.collapsing && !status) {
console.log('done collapsing');
}
$scope.collapsing = status;
});
Similar question: AngularJS - Find end of collapse animation

ng-animate: only add to the dom after the animation delay

I'm trying to use ng-animate with an ng-repeat (and ng-show) to fade out old content and replace it with new.
The problem I'm having is that during the remove and add animations, both the element(s) being added and the element(s) being removed have display:block.
I thought I could avoid this by using an animation-delay in the CSS, but that just delays the fade, and not the addition of the class that sets display on the element.
The result is a jerky transition.
This is my animation CSS (cut down):
.keyframe-fade.ng-enter,
.keyframe-fade.ng-move {
animation: 0.5s fade-in;
animation-delay: 1s;
animation-fill-mode: backwards;
}
.keyframe-fade.ng-leave {
animation: 0.5s fade-out;
}
But it's easier to demonstrate with this plunkr.
Any ideas?
NOTE: To be clear, the desired behaviour on the plunkr linked is that the coloured squares always take up the same space, i.e. they sit on the same line and the button doesn't move. If possible, I'd like to fix this without absolute positioning 'bodges' as the actual page I'm using this on is much more complex than the demo given.
The solution that I found for this is to augment the pure CSS animation with a very small amount of JavaScript.
To summarise the problem:
The entering element is added to the DOM with the ng-enter class at the same time that the leaving element is given the ng-leave class.
Though there is an animation delay, the entering element still takes up space
So this piece of javascript takes the element and adds ng-hide for the duration of the leave-animation, removing it afterwards.
.animation('.keyframe-fade', ['$timeout', function ($timeout){
return {
enter: function (element, done){
// Add ng-hide for the duration of the leave animation.
element.addClass('ng-hide');
$timeout(function(){
element.removeClass('ng-hide');
}, 500)
done();
}
}
}])
The duration is hard-coded here but I don't see any reason that you couldn't grab it from the element instead.
Improvements/suggestions welcomed.
Here's the original plunkr with the change.
This is awful, and for Angular 2+ but just for the record here's one idea.
I have two button elements, one for when the user has items in their shopping cart, and one for when they don't.
The easiest way by far is to put position: relative on the parent DIV and position: absolute on both the buttons. The main disadvantage is the parent DIV has to be sized manually, and things like centering becoming trickier.
If the intent is to delay adding to the DOM based on an Observable value, then I thought 'Why not just delay the observable value?' which will have the same end effect. This needs to be done only when the transition is from false > true though because you only want to hide it when it is coming into view. So I used a pipe to handle this.
<!-- This button for when item is IN the cart -->
<button [#cartIconAnimation] *ngIf="showCartIcon | delayTrue | async">View Cart</button>
<!-- This button for when item is NOT IN the cart -->
<button [#cartIconAnimation] *ngIf="showCartIcon | complement | delayTrue | async">Add to Cart</button>
This assumes showCartIcon is Observable<boolean>.
Then the pipes are as follows, and no delay is required on your animation criteria.
#Pipe({
name: 'delayTrue'
})
export class DelayTruePipe implements PipeTransform {
constructor() {}
transform(value: Observable<any> | any, delay: number): Observable<any> {
if (isObservable(value)) {
return value.pipe(distinctUntilChanged(), debounce((show) => show ? timer(delay || 500) : empty()));
} else {
throw Error('Needs to be an observable');
}
}
}
#Pipe({
name: 'complement'
})
export class ComplementPipe implements PipeTransform {
constructor() {}
transform(value: Observable<boolean> | any): Observable<any> {
if (isObservable(value)) {
return value.pipe(map(i => !i));
} else {
throw Error('Needs to be an observable');
}
}
}
Note: The delay used by the pipe must be greater than the time it takes for the previous item to disappear, or you'll have the same problem.
The complement pipe just inverts the boolean value.
This solution works, but it's hacky and the timing might be harder to get wrong and there may be race conditions as two different browser timers fire off at the same time. I'd only do something like this if you really can't use position: absolute.

Marionette CollectionView: how to check whether item is added because of "reset" or "add"?

I am using Marionette's CollectionView to render a list of items with ItemViews. Whenever a new item is added, I want to run a short fade-in animation. But not when the collection is rendered initially (or the collection is reset).
Before using Marionette, I handled the reset and add events slightly differently, but I can not figure out how to do this here. I looked at the source code and it seems that addItemView is responsible for adding the child view and both addChildView (called when add is triggered on the collection) and render (for reset events) call this method.
Maybe I am missing something obvious.
This is one way of doing it:
Include these functions in your CompositeView declaration:
onBeforeRender: function(){
this.onBeforeItemAdded = function(){};
},
onRender: function(){
this.onBeforeItemAdded = myAnimation;
}
This is similar to the solution I present in my book on Marionette (https://leanpub.com/marionette-gentle-introduction/)
How it works: Marionette triggers the "before:render" before it renders the entire collection, so you can set the the onBeforeItemAdded function to do nothing. Once the collection has been rendered, set that function to animate the new item view.
Since each time the collection view adds an item view it also triggers the "before:item:added", you can define an onBeforeItemAdded function that will automatically be called when that event is triggered. This matching happens thanks to triggerMethod.
This solution should solve your problem, without your needing to add flags on the model.
David Sulc answer is pretty hacky, fadeIn should be defined within item it self, not within parent view.
Another thing is that onBeforeItemAdded() is not mentioned in documentation, so it could be for internal use and may change over time.
What I suggest is to add following to parent view, note flag parentRendered:
itemViewOptions: function() {
return {
collection: this.collection,
parentRendered: this.rendered
};
},
onRender: function() {
this.rendered = true;
}
and use that flag in onShow function inside item view:
onShow: function() {
// show visual effect on newly added items
if (this.options.parentRendered) {
this.$el.css('opacity', 0).slideDown(200).animate(
{ opacity: 1 },
{ queue: false, duration: 400 }
);
}
else {
this.$el.show();
}
}
I think that your best choice is to bind your event after the CollectionView has been rendered.
myCollectionView.on( "render", function() {
this.on( "after:item:added", executeMyAnimation );
});

Resources