Skip to content

Commit

Permalink
Merge pull request react-bootstrap#427 from react-bootstrap/collapsab…
Browse files Browse the repository at this point in the history
…le-updates

[changed] CollapsableMixin state tracking
  • Loading branch information
mtscout6 committed Mar 10, 2015
2 parents b2e86f6 + befed83 commit 37c3947
Show file tree
Hide file tree
Showing 10 changed files with 449 additions and 95 deletions.
37 changes: 37 additions & 0 deletions docs/examples/CollapsableParagraph.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
var CollapsableParagraph = React.createClass({
mixins: [CollapsableMixin],

getCollapsableDOMNode: function(){
return this.refs.panel.getDOMNode();
},

getCollapsableDimensionValue: function(){
return this.refs.panel.getDOMNode().scrollHeight;
},

onHandleToggle: function(e){
e.preventDefault();
this.setState({expanded:!this.state.expanded});
},

render: function(){
var styles = this.getCollapsableClassSet();
var text = this.isExpanded() ? 'Hide' : 'Show';
return (
<div>
<Button onClick={this.onHandleToggle}>{text} Content</Button>
<div ref="panel" className={classSet(styles)}>
{this.props.children}
</div>
</div>
);
}
});

var panelInstance = (
<CollapsableParagraph>
Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
</CollapsableParagraph>
);

React.render(panelInstance, mountNode);
4 changes: 4 additions & 0 deletions docs/src/ComponentsPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,10 @@ var ComponentsPage = React.createClass({
<h3 id="panels-accordion">Accordions</h3>
<p><code>&lt;Accordion /&gt;</code> aliases <code>&lt;PanelGroup accordion /&gt;</code>.</p>
<ReactPlayground codeText={fs.readFileSync(__dirname + '/../examples/PanelGroupAccordion.js', 'utf8')} />

<h3 id="panels-collapsable">Collapsable Mixin</h3>
<p><code>CollapsableMixin</code> can be used to create your own components with collapse functionality.</p>
<ReactPlayground codeText={fs.readFileSync(__dirname + '/../examples/CollapsableParagraph.js', 'utf8')} />
</div>

<div className="bs-docs-section">
Expand Down
1 change: 1 addition & 0 deletions docs/src/ReactPlayground.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ var Badge = require('../../lib/Badge');
var Button = require('../../lib/Button');
var ButtonGroup = require('../../lib/ButtonGroup');
var ButtonToolbar = require('../../lib/ButtonToolbar');
var CollapsableMixin = require('../../lib/CollapsableMixin');
var Carousel = require('../../lib/Carousel');
var CarouselItem = require('../../lib/CarouselItem');
var Col = require('../../lib/Col');
Expand Down
6 changes: 5 additions & 1 deletion src/BootstrapMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ var BootstrapMixin = {
}

return classes;
},

prefixClass: function(subClass) {
return constants.CLASSES[this.props.bsClass] + '-' + subClass;
}
};

module.exports = BootstrapMixin;
module.exports = BootstrapMixin;
166 changes: 107 additions & 59 deletions src/CollapsableMixin.js
Original file line number Diff line number Diff line change
@@ -1,101 +1,149 @@
var React = require('react');
var TransitionEvents = require('./utils/TransitionEvents');
var TransitionEvents = require('react/lib/ReactTransitionEvents');

var CollapsableMixin = {

propTypes: {
collapsable: React.PropTypes.bool,
defaultExpanded: React.PropTypes.bool,
expanded: React.PropTypes.bool
},

getInitialState: function () {
getInitialState: function(){
var defaultExpanded = this.props.defaultExpanded != null ?
this.props.defaultExpanded :
this.props.expanded != null ?
this.props.expanded :
false;

return {
expanded: this.props.defaultExpanded != null ? this.props.defaultExpanded : null,
expanded: defaultExpanded,
collapsing: false
};
},

handleTransitionEnd: function () {
this._collapseEnd = true;
this.setState({
collapsing: false
});
},

componentWillReceiveProps: function (newProps) {
if (this.props.collapsable && newProps.expanded !== this.props.expanded) {
this._collapseEnd = false;
this.setState({
collapsing: true
});
componentWillUpdate: function(nextProps, nextState){
var willExpanded = nextProps.expanded != null ? nextProps.expanded : nextState.expanded;
if (willExpanded === this.isExpanded()) {
return;
}
},

_addEndTransitionListener: function () {
// if the expanded state is being toggled, ensure node has a dimension value
// this is needed for the animation to work and needs to be set before
// the collapsing class is applied (after collapsing is applied the in class
// is removed and the node's dimension will be wrong)

var node = this.getCollapsableDOMNode();
var dimension = this.dimension();
var value = '0';

if (node) {
TransitionEvents.addEndEventListener(
node,
this.handleTransitionEnd
);
if(!willExpanded){
value = this.getCollapsableDimensionValue();
}

node.style[dimension] = value + 'px';

this._afterWillUpdate();
},

_removeEndTransitionListener: function () {
var node = this.getCollapsableDOMNode();
componentDidUpdate: function(prevProps, prevState){
// check if expanded is being toggled; if so, set collapsing
this._checkToggleCollapsing(prevProps, prevState);

if (node) {
TransitionEvents.removeEndEventListener(
node,
this.handleTransitionEnd
);
}
// check if collapsing was turned on; if so, start animation
this._checkStartAnimation();
},

// helps enable test stubs
_afterWillUpdate: function(){
},

componentDidMount: function () {
this._afterRender();
_checkStartAnimation: function(){
if(!this.state.collapsing) {
return;
}

var node = this.getCollapsableDOMNode();
var dimension = this.dimension();
var value = this.getCollapsableDimensionValue();

// setting the dimension here starts the transition animation
var result;
if(this.isExpanded()) {
result = value + 'px';
} else {
result = '0px';
}
node.style[dimension] = result;
},

componentWillUnmount: function () {
this._removeEndTransitionListener();
_checkToggleCollapsing: function(prevProps, prevState){
var wasExpanded = prevProps.expanded != null ? prevProps.expanded : prevState.expanded;
var isExpanded = this.isExpanded();
if(wasExpanded !== isExpanded){
if(wasExpanded) {
this._handleCollapse();
} else {
this._handleExpand();
}
}
},

componentWillUpdate: function (nextProps) {
var dimension = (typeof this.getCollapsableDimension === 'function') ?
this.getCollapsableDimension() : 'height';
_handleExpand: function(){
var node = this.getCollapsableDOMNode();
var dimension = this.dimension();

var complete = (function (){
this._removeEndEventListener(node, complete);
// remove dimension value - this ensures the collapsable item can grow
// in dimension after initial display (such as an image loading)
node.style[dimension] = '';
this.setState({
collapsing:false
});
}).bind(this);

this._addEndEventListener(node, complete);

this._removeEndTransitionListener();
this.setState({
collapsing: true
});
},

componentDidUpdate: function (prevProps, prevState) {
this._afterRender();
_handleCollapse: function(){
var node = this.getCollapsableDOMNode();

var complete = (function (){
this._removeEndEventListener(node, complete);
this.setState({
collapsing: false
});
}).bind(this);

this._addEndEventListener(node, complete);

this.setState({
collapsing: true
});
},

_afterRender: function () {
if (!this.props.collapsable) {
return;
}
// helps enable test stubs
_addEndEventListener: function(node, complete){
TransitionEvents.addEndEventListener(node, complete);
},

this._addEndTransitionListener();
setTimeout(this._updateDimensionAfterRender, 0);
// helps enable test stubs
_removeEndEventListener: function(node, complete){
TransitionEvents.removeEndEventListener(node, complete);
},

_updateDimensionAfterRender: function () {
var node = this.getCollapsableDOMNode();
if (node) {
var dimension = (typeof this.getCollapsableDimension === 'function') ?
this.getCollapsableDimension() : 'height';
node.style[dimension] = this.isExpanded() ?
this.getCollapsableDimensionValue() + 'px' : '0px';
}
dimension: function(){
return (typeof this.getCollapsableDimension === 'function') ?
this.getCollapsableDimension() :
'height';
},

isExpanded: function () {
return (this.props.expanded != null) ?
this.props.expanded : this.state.expanded;
isExpanded: function(){
return this.props.expanded != null ? this.props.expanded : this.state.expanded;
},

getCollapsableClassSet: function (className) {
Expand Down
Loading

0 comments on commit 37c3947

Please sign in to comment.