I have an issue, with the Animation One or Zero Items of the Animation page - http://facebook.github.io/react/docs/animation.html#animating-one-or-zero-items
My component shows a message initially. Then when it gets the next message, it fades out the "leaving" element in 1second. Because I set transitionLeaveTimeout to 1second as well, this element is removed after the fade out completes.
However, I set transitionEnterTimeout to be 1second (and then i tried 2second), however, the "entering" element is inserted as first child immediately on start of the "leaving". How come the "entering" element is not inserted AFTER the transitionEnterTimeout? Is there any way to make this happen?
The reason I ask is because if both are present, then the "leaving" element gets shifted down by line-height. I can't do CSS tricks like -100% line height and others as the CSS is a bit complex.
Here is a copy paste eample that shows this issue:
<html>
<head>
<script src="https://fb.me/react-with-addons-0.14.4.js"></script>
<script src="https://fb.me/react-dom-0.14.4.js"></script>
<script>
var MyStore = {};
function init() {
var TheThing = React.createClass({
displayName: 'TheThing',
getInitialState: function() {
return {
msg: this.props.msg
};
},
componentDidMount: function() {
MyStore.setState = this.setState.bind(this);
},
render: function() {
return React.createElement(React.addons.CSSTransitionGroup, {transitionName:'thing', transitionEnterTimeout:6000, transitionLeaveTimeout:3000},
React.createElement('div', {key:this.state.msg},
this.state.msg
)
);
}
});
ReactDOM.render(React.createElement(TheThing, {msg:'initial'}), document.getElementById('message'));
setTimeout(function() {
MyStore.setState({msg:'hi'})
}, 6000)
setTimeout(function() {
MyStore.setState({msg:'bye'})
}, 12000)
}
window.addEventListener('DOMContentLoaded', init, false);
</script>
<style>
.thing-enter,
.thing-appear {
opacity: 0;
}
.thing-enter.thing-enter-active,
.thing-appear.thing-appear-active {
opacity: 1;
transition: opacity 6s ease 3s;
}
.thing-leave {
opacity: 1;
}
.thing-leave.thing-leave-active {
opacity: 0;
transition: opacity 3s ease;
}
</style>
</head>
<body>
<div class="container">
<div id="message"></div>
</div>
</body>
</html>
Related
There are two buttons that toggle the layout of an item. When either button is clicked, I'd like the item to fade out, change, then fade back in.
Using ReactJS, I'm running into two problems:
Using componentDidUpdate to trigger the "fade back in" event causes a loop; changing the state re-triggers componentDidUpdate endlessly.
Using componentWillReceiveProps allows me to update the class on the element to have it fade out, but it also immediately changes the layout. I need to delay the change until it's invisible.
Thoughts? Have I constructed this wrong?
(The code is below, but something is broken in Stack's version that works in JSFiddle: https://jsfiddle.net/nathanziarek/69z2wepo/15009/)
var Hello = React.createClass({
getInitialState: function() {
return { visible: " x-hidden" };
},
render: function() {
return <div className={"hello" + this.state.visible}>Hello {this.props.name}</div>;
},
componentDidMount: function(){
this.setState({ visible: "" })
},
componentWillReceiveProps: function(){
// The item is changing, make it invisible
// Wait until it's done being invisible before changing
// anything?
this.setState({ visible: " x-hidden" })
},
componentDidUpdate: function(){
// The item has changed, make it visible
// Setting anything here causes this function
// to get called again, creating a loop
// this.setState({ visible: "" })
}
});
var Button = React.createClass({
render: function() {
return <button onClick={this.handleClick}>{this.props.label}</button>;
},
handleClick: function() {
React.render(
<span>
<Button label="Universe"/>
<Button label="World"/>
<Hello name={this.props.label} />
</span>,
document.getElementById('container')
);
}
});
React.render(
<span>
<Button label="Universe"/>
<Button label="World"/>
<Hello name="_______" />
</span>,
document.getElementById('container')
);
.hello {
opacity: 1;
transition: 300ms opacity linear;
-webkit-transition: 300ms opacity linear;
}
.x-hidden { opacity: 0 }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/JSXTransformer.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.1/react-with-addons.js"></script>
<script src="https://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
I would look into using React's CSSTransitionGroup add-on component. You'll have to do some restructuring, but this will give you the behavior you want without having to add a bunch of logic around setting CSS classes; React will take care of animating (in your case, fading) components that are entering/leaving the DOM.
Unrelated: I would also avoid re-rendering your entire component like how you're doing it in your button click event. This breaks the flow of React and will inevitably cause problems. Prefer changing state and pushing down new props.
Easiest way to do this is to have both on screen then to do the fade entirely with css, and essentially completely omit React from the process other than to place the appropriate classes.
If you're fading between them you need both sets of contents anyway. That, in turn, means there's no reason to not have these in the document, and that in turn means it's just CSS hide/show tomfoolery, and there's no need for any kind of React lifecycle stuff.
You're overcomplicating it by trying to have React handle way too much.
Supporting CSS:
#foo span {
opacity: 0;
position: absolute;
top: 0; left: 0; bottom: 0; right: 0;
transition: all 0.5s ease;
}
#foo span.show {
opacity: 1;
}
Then the relevant React:
var Foo = react.createComponent({
render: function() {
var showKey = this.props.showKey,
Things = {
a: 'Apple',
b: 'Bear',
c: 'Cold inevitable doom'
};
return (
<div id="foo">
{ Things.map(X,k) {
return <span class={(showKey === k)? 'show':undefined}>{X}</span>
}}
</div>
);
}
});
You'll find that as you switch that control's showKey, it will fade in and out its relevant contents as appropriate.
This question is more theoretical than practical but I have to ask it as a friend had a similar problem and I couldn't answer this for him.
I have a modal window that I use my extending the $modal object from AngularJS ui-bootstrap. Now when I open a modal I want to check if a class is present in the DOM (for no real reason, but I want to know if it is there). The class is determined in the service I created using $modal (see the options object). When I try and find the class using document.querySelectorAll('.photo-modal').length; I return a zero however I can see the class in the DOM when using Chrome Dev tools and the class is applied. My friends problem was similar but they could find the class originally but after closing the Modal window they would return a null or zero value. Look at this example. I open a modal window and set a 'window-class' property. The class is there but JavaScript cannot find it. I have tried using $timeout, a DOM traverse function but nothing returns the existence of my class in the DOM. Look at this example and notice the console.
angular.module('modalStuff', ['ui.bootstrap'])
.controller('MainCtrl', ['$scope', 'ModalService', function ($scope, ModalService) {
$scope.openModal = function () {
ModalService.profilePic();
};
}])
.factory('ModalService', ['$modal', function ($modal) {
var modal = angular.copy($modal);
modal.profilePic = function ( ) {
var options = {
template:'<div silly-directive>Content of the template.</div>',
windowClass: 'photo-modal'
};
return modal.open(options);
};
return modal;
}])
.directive('sillyDirective', [function () {
var findUpDom = function (elem, cssClassName) {
if (elem.classList.contains(cssClassName)) {
return elem;
} else {
while (elem.parentNode) {
elem = elem.parentNode;
if (elem && elem.classList.contains(cssClassName)) {
return true;
}
}
return false;
}
};
return {
link: function (scope, element) {
var myClass= document.querySelectorAll('.my-class').length;
var modalClass= document.querySelectorAll('.photo-modal').length;
console.log(myClass, modalClass);
//console.log(findUpDom(element[0],'photo-modal')); // Returns an error as undefined
}
};
}]
);
body {
font-family: "Comic Sans MS", "Lucida Sans Unicode", "Lucida Grande", sans-serif;
background-color: aliceblue;
}
.photo-modal {
background-color: blue;
}
.my-class {
background-color: lightBlue;
padding: 10px;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.0/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.13.2/ui-bootstrap-tpls.js"></script>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<title>JS Bin</title>
</head>
<body data-ng-app="modalStuff" data-ng-controller="MainCtrl">
<div class="my-class">
<button data-ng-click="openModal()">Open Me</button>
</div>
</body>
Now can anyone explain to me why the css class cannot be found? I also have the example here in a JSBin: https://jsbin.com/nuxipo/edit?html,css,js,console,output
I created a directive which makes elements resize automatically according to percents of window. But there are still problems I can't solve.
Here are the files:
autoresize.js:
angular.module('autoresize', [])
.directive('autoresize', function() { return {
scope:{theId:'#id', theClass:'#class', wFactor:'#w', hFactor:'#h'},
link: function(scope, elem, attrs) {
scope.onResize = function() {
if(scope.hFactor != 'w')
{
scope.height = Math.floor($(window).innerHeight()*scope.hFactor);
}
if(scope.wFactor != 'h')
{
scope.width = Math.floor($(window).innerWidth()*scope.wFactor);
}
if(scope.hFactor == 'w')
{
scope.height = scope.width;
}
if(scope.wFactor == 'h')
{
scope.width = scope.height;
}
$(elem).outerHeight(scope.height);
$(elem).outerWidth(scope.width);
}
scope.onResize();
angular.element($(window)).bind('resize', function() {
scope.onResize();
});
}
}
});
test.htm:
<!DOCTYPE html>
<html>
<head>
<title>Test</title>
<style type="text/css">
*
{
box-sizing: border-box;
}
html, body
{
margin: 0;
padding: 0;
}
#left, #right
{
display: inline-block;
}
#left
{
background: #101010;
}
#right
{
background: #777777;
}
#bottom
{
background: #AAAAAA;
}
</style>
</head>
<body>
<div ng-app="testApp" ng-controller="testCtrl">
<div id="content">
<div id="left" autoresize w="0.5" h="0.8"></div><div id="right" autoresize w="0.5" h="0.8"></div>
<div id="bottom" autoresize w="1" h="0.2">{{width}}</div>
</div>
</div>
<script src="vendor/angular.min.js"></script>
<script src="vendor/jquery-2.1.3.min.js"></script>
<script src="autoresize.js"></script>
<script>
angular.module('testApp', ['autoresize'])
.controller('testCtrl', function($scope, $http) {
});
</script>
</body>
</html>
The first problem is that when I open the webapp, the sizes don't init correctly: I must resize the window to get it work. Why doesn't the line scope.onResize() in the link function init everything correctly?
The second problem is that the scope variables "width" and "height" are not accessible in the element: in the example below, there isn't anything in the div "bottom". How do you get them accessible?
(Here you could think that CSS would be sufficient, however actually my app is a bit more complicated that the example below and CSS isn't sufficient)
Thanks a lot!
Ad 1)
The scope.onResize() does not work, because when the link function on a directive is called, the DOM element handled by that directive is not yet rendered (it will be, once the directive is done processing). Try to wrap that call in $timeout(), like this:
$timeout(function() { scope.onResize(); });
This will cause calling scope.onResize() during the next tick, which is after the directive finishes processing.
Ad 2)
See this question, on how to modify scope from within a directive.
You need call you scope.onResize(); method from load event of element, That would be efficient to initial kick off for setting css of elements
Add below method in directive & remove direct call of scope.onResize(); from link function
Code
element.on('load', function() {
scope.onResize();
});
I use Angular 1.3.1, ngAnimate with CSS transitions. And I want to know when the CSSÂ transition starts and completes.
I was expecting to be able to use module.animation but it does not work as I expect it (and as the docs suggest). I'm not able to get a notification when the transition completes.
I have created a Plunker: http://plnkr.co/edit/UGCmbBoiLT1xvhx7JGst.
This is my HTMLÂ code:
<body ng-controller="Controller as ctrl">
<h1>Angular Animation issue!</h1>
<div class="animation" id="resizeme" ng-class="ctrl.className"></div>
<label>
<input type="checkbox" ng-model="ctrl.className"
ng-true-value="'big'" ng-false-value="''">
Bigger!
</label>
This is the CSS code:
#resizeme {
width: 100px;
height: 100px;
background-color: red;
-webkit-transition: width linear 1s;
transition: width linear 1s;
}
#resizeme.big {
width: 200px;
}
And this is the JavaScript code:
var module = angular.module('app', ['ngAnimate']);
module.controller('Controller', function() {
this.className = '';
});
module.animation('.animation', function() {
return {
addClass: function(elemennt, className) {
console.log('addClass animation starts');
return function(cancel) {
console.log('addClass animation completes')
}
},
removeClass: function(elemennt, className) {
console.log('removeClass animation starts');
return function(cancel) {
console.log('removeClass animation completes')
}
},
}
});
Checking the checkbox runs the "addClass" animation. I do get the "addClass animation starts" message in the console, but I don't get the "addClass animation completes" message when the transition is complete.
The Javascript-defined Animations section of ngAnimate docs says the following for the function returned by enter, addClass and friends:
//this (optional) function will be called when the animation
//completes or when the animation is cancelled (the cancelled
//flag will be set to true if cancelled).
So I'd expect the function that outputs "addClass animation completes" to be called when the transition completes. But it's not called.
Does anyone know how I can get a notification when the transition completes?
PS: I know I could use the $animate service and the promise returned by $animate.addClass. But I'm trying to use ng-class (and other animation-compatible Angular directives), so I don't have access to the promise provided by the $animate service.
Hello I am building a small web app using meanjs, I have managed to put together a category selector via various tutorials, books etc. it should look like the ebay category selector but I am stuck on how to show the selected categories.
this is what I have so far jsfiddle
var app = angular.module('categories', []);
app.controller('MainCtrl', function($scope) {
$scope.choice = null;
$scope.opts = {
"Basic Materials": {
"Chemical":{
"BlahChemicals": ["abc123", "blahblah"],
"Resources": ["Minerals", "Metals"],
},
"Steel":{
"BlahTin": ["abcsteel", "blahyuck"],
"Exsteel": ["Minerals", "Metals"],
},
"Timber": ["Hardwood", "SoftWood"]
}
};
});
app.directive('catSelect', function($compile, $parse) {
var ddo = {
restrict: 'E',//only matches element name
scope: {config:'=config'},
replace: true,
controller: function($scope, $attrs, $element) {
$scope.selected = null; // a place to store the result of this iteration
// make an selections array to display the current set of keys
if (angular.isArray($scope.config)) {
// if it is an array, just copy
$scope.selections = angular.copy($scope.config);
} else if (angular.isObject($scope.config)) {
$scope.selections = [];
//if it is an object, extract the key's to an array
angular.forEach($scope.config, function(cat, key) {
$scope.selections.push(key);
});
}
$scope.$watch("selected", function(newCat, oldCat) {
if (newCat === oldCat || newCat === null) {return; } //nothing to do.
$scope.sub = $scope.config[newCat]; //make the current selection the root for the next
if ($scope.sub && angular.isObject($scope.sub)) { // is there an subselection to make?
$element.find("span").remove(); //remove previous added selects..
newSelect = '<cat-select config="sub" class="catSelectSubItem"></cat-select>';
$element.append($compile(newSelect)($scope));
}
});
},
template: '<span><select ng-model="selected" ng-options="cat for cat in selections"></span>',
};
return ddo;
});
The CSS
select {
display: inline;
margin-left: 0;
float: left;
height: 260px;
margin-left: 15px;
background: #FFF;
min-width: 15.0em;
}
The HTML
<!DOCTYPE html>
<html ng-app="categories">
<head>
<meta charset="utf-8" />
<title>AngularJS Categories</title>
<link rel="stylesheet" href="style.css" />
<script data-require="angular.js#1.0.x" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.1/angular.min.js" data-semver="1.0.8"> </script>
<script src="app.js"></script>
</head>
<body ng-controller="MainCtrl">
<div>
<cat-select config="opts"></cat-select>
</div>
</body>
</html>
So with any luck someone out there will be able to point me in the right direction
In order to retrieve the multiple selected categories you must keep track of them somehow.
I was able to make a quick edit on your jsFiddle so it stores the selected options in an array and shows that on the HTML... you can then use it on your controller to do whatever you need.
It probably could use some more optimization and defensive coding, but I guess you can do it yourself :)
Here is the fiddle, hope that helps:
http://jsfiddle.net/j8bq7rm1/1/
<div style="display: block;">{{selectedCategories}}</div>
<div>
<cat-select config="opts" selected-items="selectedCategories"></cat-select>
</div>
Thanks