Skip to content

Commit

Permalink
feat(Nav): added components bs-nav and bs-nav-item for bootstrap navs
Browse files Browse the repository at this point in the history
  • Loading branch information
simonihmig committed Jul 1, 2016
1 parent d770cf6 commit dc66fe7
Show file tree
Hide file tree
Showing 16 changed files with 364 additions and 10 deletions.
59 changes: 59 additions & 0 deletions addon/components/bs-nav-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import Ember from 'ember';
import layout from '../templates/components/bs-nav-item';
import ComponentParent from 'ember-bootstrap/mixins/component-parent';

const { computed, LinkComponent } = Ember;

/**
Component for each item within a [Components.Nav](Components.Nav.html) component. Have a look there for examples.
@class NavItem
@namespace Components
@extends Ember.Component
@uses Mixins.ComponentParent
@public
*/
export default Ember.Component.extend(ComponentParent, {
layout,
classNameBindings: ['disabled', 'active'],
tagName: 'li',
ariaRole: 'presentation',

/**
* Render the nav item as disabled (see [Bootstrap docs](http://getbootstrap.com/components/#nav-disabled-links)).
* By default it will look at any nested `link-to` components and make itself disabled if there is a disabled link.
* See the [link-to API](http://emberjs.com/api/classes/Ember.Templates.helpers.html#toc_disabling-the-code-link-to-code-component)
*
* @property disabled
* @type boolean
* @public
*/
disabled: computed.gt('disabledChildLinks.length', 0),

/**
* Render the nav item as active.
* By default it will look at any nested `link-to` components and make itself active if there is an active link
* (i.e. the link points to the current route).
* See the [link-to API](http://emberjs.com/api/classes/Ember.Templates.helpers.html#toc_handling-current-route)
*
* @property active
* @type boolean
* @public
*/
active: computed.gt('activeChildLinks.length', 0),

/**
* Collection of all `Ember.LinkComponent`s that are children
*
* @property childLinks
* @private
*/
childLinks: computed.filter('children', function(view) {
return view instanceof LinkComponent;
}),


activeChildLinks: computed.filterBy('childLinks', 'active'),
disabledChildLinks: computed.filterBy('childLinks', 'disabled')
});
102 changes: 102 additions & 0 deletions addon/components/bs-nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import Ember from 'ember';
import layout from '../templates/components/bs-nav';

const { isPresent } = Ember;

/**
Component to generate [bootstrap navs](http://getbootstrap.com/components/#nav)
### Usage
Use in combination with the [Components.NavItem](Components.NavItem.html) component:
```hbs
{{#bs-nav type="pills"}}
{{#bs-nav-item}}
{{#link-to "foo"}}Foo{{/link-to}}
{{/bs-nav-item}}
{{#bs-nav-item}}
{{#link-to "bar"}}Bar{{/link-to}}
{{/bs-nav-item}}
{{/bs-nav}}
```
### Nav styles
The component supports the default bootstrap nav styling options "pills" and "tabs" through the `type`
property, as well as the `justified` and `stacked` properties.
### Active items
Bootstrap expects to have the `active` class on the `<li>` element that should be the active (highlighted)
navigation item. To achieve that just use the `link-to` helper as usual. If the link is active, i.e
it points to the current route, the `bs-nav-item` component will detect that and apply the `active` class
automatically. The same applies for the `disabled` state.
### Dropdowns
Use the [Components.Dropdown](Components.Dropdown.html) component with a `tagName` of "li" to integrate a dropdown into your nav:
{{#bs-nav type="pills"}}
{{#bs-nav-item}}{{#link-to "index"}}Home{{/link-to}}{{/bs-nav-item}}
{{#bs-dropdown tagName="li"}}
{{#bs-dropdown-toggle}}Dropdown <span class="caret"></span>{{/bs-dropdown-toggle}}
{{#bs-dropdown-menu}}
<li>{{#link-to "foo"}}Foo{{/link-to}}</li>
<li>{{#link-to "bar"}}Bar{{/link-to}}</li>
{{/bs-dropdown-menu}}
{{/bs-dropdown}}
{{/bs-nav}}
@class Nav
@namespace Components
@extends Ember.Component
@public
*/
export default Ember.Component.extend({
layout,

tagName: 'ul',
classNames: ['nav'],

classNameBindings: ['typeClass', 'justified:nav-justified', 'stacked:nav-stacked'],

typeClass: Ember.computed('type', function() {
let type = this.get('type');
if (isPresent(type)) {
return `nav-${type}`;
}
}),

/**
* Special type of nav, either "pills" or "tabs"
*
* @property type
* @type String
* @default null
* @public
*/
type: null,

/**
* Make the nav justified, see [bootstrap docs](http://getbootstrap.com/components/#nav-justified)
*
* @property justified
* @type boolean
* @default false
* @public
*/
justified: false,

/**
* Make the nav pills stacked, see [bootstrap docs](http://getbootstrap.com/components/#nav-pills)
*
* @property stacked
* @type boolean
* @default false
* @public
*/
stacked: false
});
15 changes: 15 additions & 0 deletions addon/initializers/bootstrap-linkto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Ember from 'ember';
import ComponentChildMixin from 'ember-bootstrap/mixins/component-child';

const { LinkComponent } = Ember;

export function initialize(/* application */) {
if (!ComponentChildMixin.detect(LinkComponent)) {
LinkComponent.reopen(ComponentChildMixin)
}
}

export default {
name: 'bootstrap-linkto',
initialize
};
1 change: 1 addition & 0 deletions addon/templates/components/bs-nav-item.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{yield}}
1 change: 1 addition & 0 deletions addon/templates/components/bs-nav.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{yield}}
1 change: 1 addition & 0 deletions app/components/bs-nav-item.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-bootstrap/components/bs-nav-item';
1 change: 1 addition & 0 deletions app/components/bs-nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from 'ember-bootstrap/components/bs-nav';
1 change: 1 addition & 0 deletions app/initializers/bootstrap-linkto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default, initialize } from 'ember-bootstrap/initializers/bootstrap-linkto';
14 changes: 14 additions & 0 deletions tests/acceptance/bs-nav-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { test } from 'qunit';
import moduleForAcceptance from '../../tests/helpers/module-for-acceptance';

moduleForAcceptance('Acceptance | bs nav');

test('link-to still works after reopening', function(assert) {
visit('/');
click('.nav li a:contains("Navs")');

andThen(function() {
assert.equal(currentURL(), '/navs');
assert.ok(find('.nav li a:contains("Navs")').hasClass('active'));
});
});
17 changes: 17 additions & 0 deletions tests/dummy/app/controllers/navs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Ember from 'ember';

const { computed, A } = Ember;

export default Ember.Controller.extend({
type: computed.oneWay('typeChoices.firstObject'),
stacked: false,
justified: false,
typeChoices: A([
{
id: 'pills'
},
{
id: 'tabs'
}
])
});
1 change: 1 addition & 0 deletions tests/dummy/app/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Router.map(function() {
this.route('collapse');
this.route('modal');
this.route('progress');
this.route('navs');
});

export default Router;
22 changes: 12 additions & 10 deletions tests/dummy/app/templates/application.hbs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
<ul class="nav nav-pills">
<li>{{#link-to "alert"}}Alert{{/link-to}}</li>
<li>{{#link-to "button"}}Buttons{{/link-to}}</li>
<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>
<li>{{#link-to "modal"}}Modals{{/link-to}}</li>
<li>{{#link-to "progress"}}Progress bars{{/link-to}}</li>
</ul>
{{#bs-nav type="pills"}}
{{#bs-nav-item}}{{#link-to "alert"}}Alert{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "button"}}Buttons{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "dropdown"}}Dropdown{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "forms"}}Forms{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "accordion"}}Accordion{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "collapse"}}Collapse{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "modal"}}Modals{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "progress"}}Progress bars{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "navs"}}Navs{{/link-to}}{{/bs-nav-item}}
{{/bs-nav}}

<div class="container">
{{outlet}}
</div>
20 changes: 20 additions & 0 deletions tests/dummy/app/templates/navs.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<h1>Navs</h1>

{{#bs-form model=this}}
{{bs-form-element controlType="select" label="Type" choices=typeChoices choiceLabelProperty="id" choiceValueProperty="id" property="type"}}
{{bs-form-element controlType="checkbox" label="Stacked" property="stacked"}}
{{bs-form-element controlType="checkbox" label="Justified" property="justified"}}
{{/bs-form}}

{{#bs-nav type=type.id justified=justified stacked=stacked}}
{{#bs-nav-item}}{{#link-to "alert"}}Alert{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "button"}}Buttons{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "dropdown"}}Dropdown{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "forms"}}Forms{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "accordion"}}Accordion{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "collapse"}}Collapse{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "modal"}}Modals{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "progress"}}Progress bars{{/link-to}}{{/bs-nav-item}}
{{#bs-nav-item}}{{#link-to "navs"}}Navs{{/link-to}}{{/bs-nav-item}}
{{/bs-nav}}

63 changes: 63 additions & 0 deletions tests/integration/components/bs-nav-item-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import Ember from 'ember';
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';
import BootstrapLinktoInitializer from 'ember-bootstrap/initializers/bootstrap-linkto';
import startApp from '../../../tests/helpers/start-app';

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

test('it has correct markup', function(assert) {
this.render(hbs`
{{#bs-nav-item}}
template block text
{{/bs-nav-item}}
`);

assert.equal(this.$().text().trim(), 'template block text', 'Shows block content');
assert.equal(this.$('li').length, 1, 'it is an list item');
assert.ok(!this.$('li').hasClass('active'), 'has not active class');
assert.ok(!this.$('li').hasClass('disabled'), 'has not disabled class');

});

test('can be disabled', function(assert) {
this.render(hbs`{{bs-nav-item disabled=true}}`);

assert.ok(this.$('li').hasClass('disabled'), 'has disabled class');
});

test('can be active', function(assert) {
this.render(hbs`{{bs-nav-item active=true}}`);

assert.ok(this.$('li').hasClass('active'), 'has active class');
});

test('active link makes nav item active', function(assert) {

let application = startApp();
BootstrapLinktoInitializer.initialize(application);

this.render(hbs`
{{#bs-nav-item}}
{{#link-to "application" active="foo"}}Test{{/link-to}}
{{/bs-nav-item}}
`);
assert.ok(this.$('li').hasClass('active'), 'has active class');
Ember.run(application, 'destroy');
});

test('disabled link makes nav item disabled', function(assert) {

let application = startApp();
BootstrapLinktoInitializer.initialize(application);

this.render(hbs`
{{#bs-nav-item}}
{{#link-to "application" disabled="foo"}}Test{{/link-to}}
{{/bs-nav-item}}
`);
assert.ok(this.$('li').hasClass('disabled'), 'has disabled class');
Ember.run(application, 'destroy');
});
31 changes: 31 additions & 0 deletions tests/integration/components/bs-nav-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { moduleForComponent, test } from 'ember-qunit';
import hbs from 'htmlbars-inline-precompile';

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

test('it has correct markup', function(assert) {
// Template block usage:
this.render(hbs`
{{#bs-nav}}
template block text
{{/bs-nav}}
`);

assert.equal(this.$().text().trim(), 'template block text', 'Shows block content');
assert.equal(this.$('ul').length, 1, 'it is an unordered list');
assert.ok(this.$('ul').hasClass('nav'), 'has nav class');
});

test('it supports bootstrap options', function(assert) {
// Template block usage:
this.render(hbs`
{{bs-nav justified=true stacked=true type="pills"}}
`);

assert.ok(this.$('ul').hasClass('nav-pills'), 'has pills class');
assert.ok(this.$('ul').hasClass('nav-justified'), 'has justified class');
assert.ok(this.$('ul').hasClass('nav-stacked'), 'has stacked class');
});

Loading

0 comments on commit dc66fe7

Please sign in to comment.