Skip to content

Commit

Permalink
feat(Collapse): added bs-collapse component for collapsible content
Browse files Browse the repository at this point in the history
  • Loading branch information
simonihmig committed Sep 22, 2015
1 parent 0fe2b43 commit 794b52d
Show file tree
Hide file tree
Showing 8 changed files with 331 additions and 0 deletions.
1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ bower_components/
tests/
tmp/
dist/
docs/

.bowerrc
.editorconfig
Expand Down
231 changes: 231 additions & 0 deletions addon/components/bs-collapse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
import Ember from 'ember';

export default Ember.Component.extend({

classNameBindings: ['collapse','in','collapsing'],
attributeBindings: ['style'],

/**
* Collapsed/expanded state
*
* @property collapsed
* @type boolean
* @default true
* @public
*/
collapsed: true,

/**
* True if this item is expanded
*
* @property active
* @protected
*/
active: false,

collapse: Ember.computed.not('transitioning'),
collapsing: Ember.computed.alias('transitioning'),
in: Ember.computed.and('collapse', 'active'),

/**
* true if the component is currently transitioning
*
* @property transitioning
* @type boolean
* @protected
*/
transitioning: false,

/**
* @property collapseSize
* @type number
* @protected
*/
collapseSize: null,

/**
* The size of the element when collapsed. Defaults to 0.
*
* @property collapsedSize
* @type number
* @default 0
* @public
*/
collapsedSize: 0,

/**
* The size of the element when expanded. When null the value is calculated automatically to fit the containing elements.
*
* @property expandedSize
* @type number
* @default null
* @public
*/
expandedSize: null,

/**
* Usually the size (height) of the element is only set while transitioning, and reseted afterwards. Set to true to always set a size.
*
* @property resetSizeWhenNotCollapsing
* @type boolean
* @default true
* @private
*/
resetSizeWhenNotCollapsing: true,


/**
* The direction (height/width) of the collapse animation.
* When setting this to 'width' you should also define custom CSS transitions for the width property, as the Bootstrap
* CSS does only support collapsible elements for the height direction.
*
* @property collapseDimension
* @type string
* @default 'height'
* @public
*/
collapseDimension: 'height',

style: Ember.computed('collapseSize', function () {
var size = this.get('collapseSize'),
dimension = this.get('collapseDimension');
if (Ember.isEmpty(size)) {
return new Ember.Handlebars.SafeString('');
}
return new Ember.Handlebars.SafeString(`${dimension}: ${size}px`);
}),


/**
* Triggers the show transition
*
* @method show
* @protected
*/
show: function () {
var complete = function () {
this.set('transitioning', false);
if (this.get('resetSizeWhenNotCollapsing')) {
this.set('collapseSize', null);
}
this.sendAction('didShow');
};

this.sendAction('willShow');

this.setProperties({
transitioning: true,
collapseSize: this.get('collapsedSize'),
active: true
});

if (!Ember.$.support.transition) {
return complete.call(this);
}

this.$()
.one('bsTransitionEnd', Ember.run.bind(this, complete))
// @todo: make duration configurable
.emulateTransitionEnd(350)
;

Ember.run.next(this, function () {
if (!this.get('isDestroyed')) {
this.set('collapseSize', this.getExpandedSize('show'));
}
});
},

/**
* Get the size of the element when expanded
*
* @method getExpandedSize
* @param $action
* @returns number
* @private
*/
getExpandedSize: function($action) {
var expandedSize = this.get('expandedSize');
if (Ember.isPresent(expandedSize)) {
return expandedSize;
}

var collapseElement = this.$(),
prefix = $action === 'show' ? 'scroll' : 'offset',
measureProperty = Ember.String.camelize(prefix + '-' + this.get('collapseDimension'));
return collapseElement[0][measureProperty];
},

/**
* Triggers the hide transition
*
* @method hide
* @protected
*/
hide: function () {

var complete = function () {
this.set('transitioning', false);
if (this.get('resetSizeWhenNotCollapsing')) {
this.set('collapseSize', null);
}
this.sendAction('didHide');
};

this.sendAction('willHide');

this.setProperties({
transitioning: true,
collapseSize: this.getExpandedSize('hide'),
active: false
});

if (!Ember.$.support.transition) {
return complete.call(this);
}

this.$()
.one('bsTransitionEnd', Ember.run.bind(this, complete))
// @todo: make duration configurable
.emulateTransitionEnd(350)
;

Ember.run.next(this, function () {
if (!this.get('isDestroyed')) {
this.set('collapseSize', this.get('collapsedSize'));
}
});
},

_onCollapsedChange: Ember.observer('collapsed', function () {
var collapsed = this.get('collapsed'),
active = this.get('active');
if (collapsed !== active) {
return;
}
if (collapsed === false) {
this.show();
}
else {
this.hide();
}
}),

didInsertElement: function() {
this.set('active', !this.get('collapsed'));
},

_updateCollapsedSize: Ember.observer('collapsedSize', function() {
if (!this.get('resetSizeWhenNotCollapsing') && this.get('collapsed') && !this.get('collapsing')) {
this.set('collapseSize', this.get('collapsedSize'));
}
}),

_updateExpandedSize: Ember.observer('expandedSize', function() {
if (!this.get('resetSizeWhenNotCollapsing') && !this.get('collapsed') && !this.get('collapsing')) {
this.set('collapseSize', this.get('expandedSize'));
}
})


});
4 changes: 4 additions & 0 deletions app/components/bs-collapse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import Ember from 'ember';
import component from 'ember-bootstrap/components/bs-collapse';

export default component;
12 changes: 12 additions & 0 deletions tests/dummy/app/controllers/collapse.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Ember from 'ember';

export default Ember.Controller.extend({
collapsed: true,
notCollapsed: Ember.computed.not('collapsed'),

actions: {
toggle() {
this.toggleProperty('collapsed');
}
}
});
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Router.map(function() {
this.route('forms');
this.route('alert');
this.route('accordion');
this.route('collapse');
});

export default Router;
12 changes: 12 additions & 0 deletions tests/dummy/app/templates/collapse.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<h1>Collapse</h1>

{{#bs-button toggle=true active=notCollapsed action="toggle"}}{{#if collapsed}}Show{{else}}Hide{{/if}}{{/bs-button}}

<hr/>

{{#bs-collapse collapsed=collapsed}}
<div class="well">
<h2>Collapse</h2>
<p>This is collapsible content</p>
</div>
{{/bs-collapse}}
1 change: 1 addition & 0 deletions tests/dummy/app/templates/index.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
<li>{{#link-to "dropdown"}}Dropdown{{/link-to}}</li>
<li>{{#link-to "forms"}}Forms{{/link-to}}</li>
<li>{{#link-to "accordion"}}Accordion{{/link-to}}</li>
<li>{{#link-to "collapse"}}Collapse{{/link-to}}</li>
</ul>
69 changes: 69 additions & 0 deletions tests/integration/components/bs-collapse-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import {
moduleForComponent,
test
} from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';


moduleForComponent('bs-collapse', 'Integration | Component | bs-collapse', {
integration: true
});


test('collapse has correct default markup', function(assert) {
this.render(hbs`{{#bs-collapse}}<p>Just some content</p>{{/bs-collapse}}`);
assert.equal(this.$(':first-child').hasClass('collapse'), true, 'collapse has collapse class');
assert.equal(this.$(':first-child').hasClass('in'), false, 'collapse does not have in class');

});

test('expanded collapse has correct default markup', function(assert) {
this.render(hbs`{{#bs-collapse collapsed=false}}<p>Just some content</p>{{/bs-collapse}}`);
assert.equal(this.$(':first-child').hasClass('collapse'), true, 'collapse has collapse class');
assert.equal(this.$(':first-child').hasClass('in'), true, 'collapse has in class');
});



test('setting collapse to false expands this item', function(assert) {
this.set('collapsed', true);
this.render(hbs`{{#bs-collapse collapsed=collapsed}}<p>Just some content</p>{{/bs-collapse}}`);
this.set('collapsed', false);

assert.equal(this.$(':first-child').hasClass('collapsing'), true, 'collapse has collapsing class while transition is running');


var done = assert.async();

// wait for transitions to complete
setTimeout(() => {
assert.equal(this.$(':first-child').hasClass('collapse'), true, 'collapse has collapse class');
assert.equal(this.$(':first-child').hasClass('in'), true, 'collapse has in class');

done();
}, 500);


});

test('setting collapse to true collapses this item', function(assert) {
this.set('collapsed', false);
this.render(hbs`{{#bs-collapse collapsed=collapsed}}<p>Just some content</p>{{/bs-collapse}}`);
this.set('collapsed', true);

assert.equal(this.$(':first-child').hasClass('collapsing'), true, 'collapse has collapsing class while transition is running');

var done = assert.async();

// wait for transitions to complete
setTimeout(() => {
assert.equal(this.$(':first-child').hasClass('collapse'), true, 'collapse has collapse class');
assert.equal(this.$(':first-child').hasClass('in'), false, 'collapse does not have in class');

done();
}, 500);


});


0 comments on commit 794b52d

Please sign in to comment.