Conditional Animation for AngularJS - angularjs

Trying to get conditional animation to work. This method works for just two classes (left and right) but gets weird with three classes.(sometimes it will fade, sometimes it will use left/right) It also gets unwieldy with more classes. Any tips?
I have whatever I want to animate (in this case it would be ng-view) with these classes already defined.
ng-class="{animateLeft: goingLeft, animateRight: goingRight, fadeIn: fading}"
I add these functions to ng-click() on my navigation buttons.
$scope.goRight = function(){
$scope.goingRight = true;
$scope.goingLeft = false;
$scope.fading = false;
};
$scope.goLeft = function(){
$scope.goingRight = false;
$scope.goingLeft = true;
$scope.fading = false;
};
$scope.fadeIn = function(){
$scope.goingRight = false;
$scope.goingLeft = false;
$scope.fading = true;
};

I figured out what was being weird. This method actually does work. You just need to understand how angular animates things step by step.
To animate something in angular you assign it 4 CSS classes.
1)The state it should start in when it ENTERS. (e.g. off to the side and invisible)
2)The state it should end in when it ENTERS. (e.g. going to the middle and visible)
3)The state it should start in when it EXITS. (e.g. start from the middle and visible)
4)The state it should end in when it EXITS. (e.g. go to the side and invisible)
This means that there are TWO objects on "stage" as the same time as the animation is occurring (one is leaving while the other is entering). When an object enters the stage, it's already programmed to leave in a certain way. When the other objects comes onto the stage, that objects leaves.
My problem was that I wanted my view to conditionally LEAVE as well as conditionally ENTER. I could call the ENTER animation properly but because the LEAVE conditional was already set BEFORE knowing how I'd call enter the way it leaves cannot be changed.
I changed my code so that the pages animate into the stage based on a condition (e.g. animate in from arrow direction you click) but made them fade to 0 on leave instead of leave in the opposite direction. Previously the animations were weird because I would click on RIGHT, the page would animate in from the right, but then when I wanted to fade to another page the previous page would leave to the LEFT which was confusing.
Just remember that the page your animating get both its enter AND leave classes set when you call it.

Related

How do I return focus to an element when the entire page changes?

I have a complicated setup. My application is driven by a set of "rules" which dictate what the user interface is. The UI is rendered by looping through the rules and creating the individual dropdowns. Initially, everything renders properly. However, once a user makes a change to the UI, other rules may be affected. In my application, an api call is made, which then returns a modified set of rules. In the attached plunker, I've simplified things such that only the new set of rules is applied, which causes the page to re-render. The problem is that my users would like to be able to tab between all of the entries on the page and make changes. However, once the page is re-rendered, the currently selected page element is now gone nothing has the focus. I've tried to put the focus back on the proper element by tracking a common Id, but to no avail.
Using either of these doesn't seem to work.
var el = document.getElementById(focusId);
el.focus();
angular.element(el).focus();
I've also tried using the autofocus attribute on the dropdown that I want to have focus, but that didn't work either. I'm using angularjs 1.2. Any ideas are appreciated.
http://plnkr.co/edit/ND9PKqULIOlixWR4XChN?p=preview
If you want to assign auto focus dynamically to a element on the DOM from angular you can use this:
var myEl = angular.element(document.querySelector('select'));
myEl.attr('autofocus',"attr val");
You can also pass in an id like: angular.element(document.querySelector('#focusId'));
You can look here for a prior answer which may be of some more help!
-Cheers!
Problem here is, you are trying to focus the element before the blur event completes. So you need to execute the focus code after blur event. Async execution of your focus code would solve the problem. You can use either setTimeout or $timeout.
setTimeout(function(){
angular.element('#'+focusId).focus();
/* //or
var el = document.getElementById(focusId);
el.focus();
*/
});
or
$timeout(function(){
angular.element('#'+focusId).focus();
/* //or
var el = document.getElementById(focusId);
el.focus();
*/
});
dont forgot to inject $timeout to your controller if you are using second code. Hope this helps :)

Angular - Prevent event looping?

I need your help as I am still having trouble with events handling.
Here is the concept :
To contextualize, let's say that I have visuals items, that I call "components".
A component is defined by 2 javascript objects. One is containing data (like position x, y, width, height, color, etc...). The other one is the view exploiting a 3rd party library named draw2d.
Draw2d uses it's own x y width height color etc... properties. Which mean that I need to "bind" values to always keep them equals.
For this purpose, I just use events. There are 'on' events in draw2d letting me watch theses modifications. For the angular side, I just use the ng-model directive directly on my data bindings. When a data is updated, I update draw2d view, and when the view is edited, I update my data.
This part is working correctly as this is not hard to handle it.
I then tried to implement multiple selection editing. For that, I store in an array every selected component. When a value from the view or from the data is edited, I trig an event which let every selected component to set the same data to the same value. Again, it's working great.
Where i'm starting to struggle hard, it's when I want to edit the position. I don't want every components to be on top of each other. For that, when I edit multiple x or y attributes, instead of positioning elements, I calculate the offset so that every components move with this offset only. Here is where it's not okay. Of course, when an offset is set, others components updates, also trigging their new value, so I'm stuck in a hell loop.
I would like to know if you know any pattern to handle events so that, when you have 2 view and a model, you can stop an event from propagate.
Here is my propagation snippet :
scope.canvas.on('figureEdited', function (canvas, event) {
if (scope.selectedComponents.length > 1) {
var key = event.key;
var value = event.value;
var originComponent = event.origin;
for (var i = 0; i < scope.selectedComponents.length; i++) {
var selectedComponent = scope.selectedComponents[i];
if (selectedComponent !== originComponent) {
if (key !== 'x' && key !== 'y') {
selectedComponent.setData(key, value);
} else {
var offset = value - event.oldValue;
selectedComponent.setData(key, selectedComponent.getData(key) + offset, true);
}
}
}
}
});
the issue is caused by
selectedComponent.setData(key, selectedComponent.getData(key) + offset, true);
It retriggers a figureEdited event, making every component move again by this offset, infinitely (stopped by 10 digest iterations).
I hope this is clear, sorry for the long text and thanks a lot for your help :)
Alann.
If the setData call triggers new events synchronously you can ignore events triggered while in your loop by setting an ignore flag and checking it at the start of the function.
var ignoreEvent = false;
scope.canvas.on('figureEdited', function (canvas, event) {
if (ignoreEvent) return;
ignoreEvent = true;
//...
ignoreEvent = false;
});

Update scope half way through a scope method

EDIT: I've put together a JSFiddle: http://jsfiddle.net/U3pVM/18137/
I want the black to go up AND THEN the green to slide across... I do not know how to achieve this... Read on for more information:
Side note: The code is in Typescript (re: () => {} )
The following context shows a web page that has an overlaying div, and a couple of divs behind it. It also denotes the kind of animation that is occuring via a css class.
I have the following code (see references to diagram):
$scope.Resizing = true; // This prevents animations from happening (as a css class is linked up to it (see below))
$scope.MoveToPage( col, row ); // 2
$scope.$applyAsync(() => { // Update the view to remove the class and then start sliding up the overlay div
$scope.FirstPageTop = -$scope.PageMap.WindowInfo.Height; // 3
$scope.Resizing = false; // Re-enable animations // Finish
});
What it is supposed to do is:
When Resizing is true, it removes a css class that animates stuff.
It then runs the MoveToPage method that makes a position left equal a new number.. usually it would animate to the new position but in this instance i don't want it to (thus the Resizing attribute).
We can see the above expressed in my html:
<div class="column-wrap"
ng-class="{ 'column-wrap-animation' : !Resizing }"
ng-style="{ 'width': PageMap.ContainerWidth + 'px',
'left': PageMap.ColumnWrap.Left + 'px'}">
The procedure then applies the scope asynchronously (because if i use a simple apply, it throws an error) and as a callback to this method, I am then doing pulling an overlay off of the top of the page... and then setting Resizing back to false to re-apply animations.
The problem:
I need to slide off an overlay AFTER positioning the content underneath it. What is actually happening is, it slides the overlay off of the top, while the under page animates to where i've told it to go. How do i get around this css animation digestion problem...
Perhaps an even simpler question is, why might the class not be being removed when Resizing is set to false and actively i am telling it to update the scope?
Explaining my context:
You can apparently use setTimeout to deal with the $scope.$apply issue:
http://jsfiddle.net/U3pVM/18138/
$scope.Resizing = true;
$scope.MoveToPage( col, row );
setTimeout( function() {
$scope.FirstPageTop = -$scope.PageMap.WindowInfo.Height;
$scope.Resizing = false;
$scope.$apply() // This is important
}, 1);

Webshims - Show invalid form fields on initial load

(Follow on questions from Placeholder Hidden)
I'd like my form to validate existing data when it is loaded. I can't seem to get that to happen
I jQuery.each of my controls and call focus() and blur(), is there a better way than this? I tried to call ctrl.checkValidity(), but it wasn't always defined yet. When it was, it still didn't mark the controls.
I seem to have a timing issue too, while the focus and blur() fire, the UI does not update. It's as if the Webshims are not fully loaded yet, even though this fires in the $.webshims.ready event.
I also tried to call $('#form').submit(), but this doesn't fire the events as I expected. The only way I could make that happen was to include an input type='submit'. How can I pragmatically case a form validation like clicking a submit button would?
Here's a jsFiddle that demonstrates the problem. When the form loads, I want the invalid email to be marked as such. If you click the add button it will be marked then, but not when initially loaded. Why?
Focus and blur in the control will cause it to be marked.
BUT, clicking ADD will too (which runs the same method that ran when it was loaded). Why does it work the 2nd time, but not when initially loaded?
updateValidation : function () {
this.$el.find('[placeholder]').each(function (index, ctrl) {
var $ctrl = $(ctrl);
if( $ctrl.val() !== "" && (ctrl.checkValidity && !ctrl.checkValidity()) ) {
// alert('Do validity check!');
$ctrl.focus();
$ctrl.blur();
}
});
}
I see this in FF 17.0.5. The problem is worse in IE9, sometimes taking 2 or 3 clicks of ADD before the fields show in error. However, I get errors on some of the js files I've liked 'due to mime type mismatch'.
This has to do with the fact, that you are trying to reuse the .user-error class, which is a "shim" for the CSS4 :user-error and shouldn't be triggered from script. The user-error scripts are loaded after onload or as soon as a user seems to interact with an invalid from.
From my point of view, you shouldn't use user-error and instead create your own class. You can simply check for validity using the ':invalid' selector:
$(this)[ $(this).is(':invalid') ? 'addClass' : 'removeClass']('invalid-value');
Simply write a function with similar code and bind them to events like change, input and so on and call it on start.
In case you still want to use user-error, you could do the following, but I would not recommend:
$.webshims.polyfill('forms');
//force webshims to load form-validation module as soon as possible
$.webshims.loader.loadList(['form-validation']);
//wait until form-validation is loaded
$.webshims.ready('DOM form-validation', function(){
$('input:invalid')
.filter(function(){
return !!$(this).val();
})
.trigger('refreshvalidityui')
;
});

setting value of html select element in change handler doesn't work on ios6

I'm using an html select element as an active menu. When you select an item from it, it does an action and then in some cases, a side effect of the action is resetting the value of the menu to something else.
function onMenuChangeHandler() {
var menu = $('#menu');
var menuChoice = menu.val();
if (menuChoice == ...) {
...
menu.blur(); // ensure change handler doesn't get fired again
menu.val(OTHER_VALUE); // **
}
};
This works fine on the desktop in multiple browsers and works fine on iOS5. It inexplicably stopped working on iOS6. The result is that it acts as if the line marked ** above is not there.
It works fine in any case where I don't set the value.
FYI: There's another change in iOS6 which seems unrelated to this but mentioning it just in case. When the menu is selected, it now dismisses the picker immediately rather than leaving it open. This is how it works on every other platform. If you have more more than one select element, you still get the non-standard behavior.
I spent a lot of time trying to track this down and eventually discovered that the problem is that iOS6 sets the value of the control a second time after my change handler exits. If I put in alerts, I can clearly see that the control changes to the OTHER_VALUE (underneath the alert). Then when I dismiss the alert, the control reverts. (Without the alert, the change happens too fast to see.)
So here's a workaround:
function onMenuChangeHandler() {
var menu = $('#menu');
var menuChoice = menu.val();
if (menuChoice == ...) {
...
setTimeout(function() {
menu.blur(); // ensure change handler doesn't get fired again
menu.val(OTHER_VALUE);
}, 1);
}
};
Maybe someone else has a better answer or a better explanation.

Resources