Refactor snackbar.
parent
ef4135d7b5
commit
2089a682f7
|
@ -7,38 +7,43 @@ Actions should undo the committed action or retry it if it failed for example.
|
|||
Actions should not be to close the snackbar.
|
||||
By not providing an action, the snackbar becomes a **toast** component.
|
||||
|
||||
## To include an MDL **snackbar** component:
|
||||
## Basic Usage:
|
||||
|
||||
1. Create a `<div>` element to contain the snackbar. This element should have no content of its own and be a direct child of the `<body>`.
|
||||
Start a snackbar with a container div element.
|
||||
On that container define the `mdl-js-snackbar` and `mdl-snackbar` classes.
|
||||
It is also beneficial to add the aria live and atomic values to this container.
|
||||
|
||||
Within the container create a container element for the message.
|
||||
This element should have the class `mdl-snackbar__text`.
|
||||
Leave this element empty!
|
||||
Text is added when the snackbar is called to be shown.
|
||||
|
||||
Second in the container, add a button element.
|
||||
This element should have the class `mdl-snackbar__action`.
|
||||
It is recommended to set the type to button to make sure no forms get submitted by accident.
|
||||
Leave the text content empty here as well!
|
||||
Do not directly apply any event handlers.
|
||||
|
||||
You now have complete markup for the snackbar to function.
|
||||
All that is left is within your JavaScript to call the `showSnackbar` method on the snackbar container.
|
||||
This takes a [plain object](#data-object) to configure the snackbar content appropriately.
|
||||
You may call it multiple consecutive times and messages will stack.
|
||||
|
||||
## Examples
|
||||
|
||||
All snackbars should be shown through the same element.
|
||||
|
||||
#### Markup:
|
||||
|
||||
```html
|
||||
<body>
|
||||
<div>
|
||||
</div>
|
||||
</body>
|
||||
```
|
||||
|
||||
2. Add the `mdl-js-snackbar` class to the container.
|
||||
|
||||
```html
|
||||
<div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-js-snackbar">
|
||||
<div aria-live="assertive" aria-atomic="true" aria-relevant="text" class="mdl-snackbar mdl-js-snackbar">
|
||||
<div class="mdl-snackbar__text"></div>
|
||||
<button type="button" class="mdl-snackbar__action"></button>
|
||||
</div>
|
||||
```
|
||||
|
||||
> Note: In this example there are a few aria attributes for accessibility. Please modify these as-needed for your site.
|
||||
|
||||
3. Use JavaScript to trigger a snackbar.
|
||||
|
||||
```JavaScript
|
||||
var notification = document.querySelector('.mdl-js-snackbar');
|
||||
var data = {
|
||||
message: 'Message Sent'
|
||||
};
|
||||
notification.MaterialSnackbar.showSnackbar(data);
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### Snackbar
|
||||
|
||||
```js
|
||||
|
@ -56,9 +61,34 @@ notification.MaterialSnackbar.showSnackbar(data);
|
|||
|
||||
```js
|
||||
var notification = document.querySelector('.mdl-js-snackbar');
|
||||
notification.MaterialSnackbar.showSnackbar({message: 'Image Uploaded'});
|
||||
notification.MaterialSnackbar.showSnackbar(
|
||||
{
|
||||
message: 'Image Uploaded'
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
## CSS Classes
|
||||
|
||||
### Blocks
|
||||
|
||||
| MDL Class | Effect | Remarks |
|
||||
|-----------|--------|---------|
|
||||
| `mdl-snackbar` | Defines the container of the snackbar component. | Required on snackbar container |
|
||||
|
||||
### Elements
|
||||
|
||||
| MDL Class | Effect | Remarks |
|
||||
|-----------|--------|---------|
|
||||
| `mdl-snackbar__text` | Defines the element containing the text of the snackbar. | Required |
|
||||
| `mdl-snackbar__action` | Defines the element that triggers the action of a snackbar. | Required |
|
||||
|
||||
### Modifiers
|
||||
|
||||
| MDL Class | Effect | Remarks |
|
||||
|-----------|--------|---------|
|
||||
| `mdl-snackbar--active` | Marks the snackbar as active which causes it to display. | Required when active. Controlled in JavaScript |
|
||||
|
||||
## Data Object
|
||||
|
||||
The Snackbar components `showSnackbar` method takes an object for snackbar data.
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
|
||||
@import "../variables";
|
||||
@import "../mixins";
|
||||
|
||||
.mdl-snackbar {
|
||||
position: fixed;
|
||||
|
@ -29,10 +30,12 @@
|
|||
display: flex;
|
||||
font-family: $preferred_font;
|
||||
will-change: transform;
|
||||
transform: translate3d(0,-50px,0) rotateZ(0deg);
|
||||
transform: translate3d(0, -50px, 0) rotateZ(0deg);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
@media(max-width: $snackbar-tablet-breakpoint - 1) {
|
||||
width: 100%;
|
||||
left:0;
|
||||
left: 0;
|
||||
min-height: 48px;
|
||||
max-height: 80px;
|
||||
}
|
||||
|
@ -40,33 +43,41 @@
|
|||
min-width: 288px;
|
||||
max-width: 568px;
|
||||
}
|
||||
&.is-active {
|
||||
&--active {
|
||||
max-height: 48px;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
.mdl-snackbar__text {
|
||||
padding: 14px 24px;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mdl-snackbar__action {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $snackbar-action-color;
|
||||
text-transform: uppercase;
|
||||
padding: 14px 24px;
|
||||
@include typo-button();
|
||||
overflow: hidden;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
&__text {
|
||||
padding: 14px 24px;
|
||||
vertical-align: middle;
|
||||
color: white;
|
||||
}
|
||||
|
||||
&__action {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: $snackbar-action-color;
|
||||
text-transform: uppercase;
|
||||
padding: 14px 24px;
|
||||
@include typo-button();
|
||||
overflow: hidden;
|
||||
outline: none;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
|
||||
&::-moz-focus-inner {
|
||||
border: 0;
|
||||
}
|
||||
&:not([aria-hidden]) {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,8 +26,20 @@
|
|||
*/
|
||||
var MaterialSnackbar = function MaterialSnackbar(element) {
|
||||
this.element_ = element;
|
||||
this.textElement_ = this.element_.querySelector('.' + this.cssClasses_.MESSAGE);
|
||||
this.actionElement_ = this.element_.querySelector('.' + this.cssClasses_.ACTION);
|
||||
if (!this.textElement_) {
|
||||
throw new Error('There must be a message element for a snackbar.');
|
||||
}
|
||||
if (!this.actionElement_) {
|
||||
throw new Error('There must be an action element for a snackbar.');
|
||||
}
|
||||
this.active = false;
|
||||
this.init();
|
||||
this.actionHandler_ = undefined;
|
||||
this.message_ = undefined;
|
||||
this.actionText_ = undefined;
|
||||
this.queuedNotifications_ = [];
|
||||
this.setActionHidden_(true);
|
||||
};
|
||||
window['MaterialSnackbar'] = MaterialSnackbar;
|
||||
|
||||
|
@ -43,54 +55,32 @@
|
|||
SNACKBAR: 'mdl-snackbar',
|
||||
MESSAGE: 'mdl-snackbar__text',
|
||||
ACTION: 'mdl-snackbar__action',
|
||||
ACTIVE: 'is-active'
|
||||
ACTIVE: 'mdl-snackbar--active'
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the internal snackbar markup.
|
||||
* Display the snackbar.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
MaterialSnackbar.prototype.createSnackbar_ = function() {
|
||||
this.snackbarElement_ = document.createElement('div');
|
||||
this.textElement_ = document.createElement('div');
|
||||
this.snackbarElement_.classList.add(this.cssClasses_.SNACKBAR);
|
||||
this.textElement_.classList.add(this.cssClasses_.MESSAGE);
|
||||
this.snackbarElement_.appendChild(this.textElement_);
|
||||
this.snackbarElement_.setAttribute('aria-hidden', true);
|
||||
MaterialSnackbar.prototype.displaySnackbar_ = function() {
|
||||
this.element_.setAttribute('aria-hidden', 'true');
|
||||
|
||||
if (this.actionHandler_) {
|
||||
this.actionElement_ = document.createElement('button');
|
||||
this.actionElement_.type = 'button';
|
||||
this.actionElement_.classList.add(this.cssClasses_.ACTION);
|
||||
this.actionElement_.textContent = this.actionText_;
|
||||
this.snackbarElement_.appendChild(this.actionElement_);
|
||||
this.actionElement_.addEventListener('click', this.actionHandler_);
|
||||
this.setActionHidden_(false);
|
||||
}
|
||||
|
||||
this.element_.appendChild(this.snackbarElement_);
|
||||
this.textElement_.textContent = this.message_;
|
||||
this.snackbarElement_.classList.add(this.cssClasses_.ACTIVE);
|
||||
this.snackbarElement_.setAttribute('aria-hidden', false);
|
||||
this.element_.classList.add(this.cssClasses_.ACTIVE);
|
||||
this.element_.setAttribute('aria-hidden', 'false');
|
||||
setTimeout(this.cleanup_.bind(this), this.timeout_);
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Remove the internal snackbar markup.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
MaterialSnackbar.prototype.removeSnackbar_ = function() {
|
||||
if (this.actionElement_ && this.actionElement_.parentNode) {
|
||||
this.actionElement_.parentNode.removeChild(this.actionElement_);
|
||||
}
|
||||
this.textElement_.parentNode.removeChild(this.textElement_);
|
||||
this.snackbarElement_.parentNode.removeChild(this.snackbarElement_);
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the internal snackbar markup.
|
||||
* Show the snackbar.
|
||||
*
|
||||
* @param {Object} data The data for the notification.
|
||||
* @public
|
||||
|
@ -122,7 +112,7 @@
|
|||
if (data['actionText']) {
|
||||
this.actionText_ = data['actionText'];
|
||||
}
|
||||
this.createSnackbar_();
|
||||
this.displaySnackbar_();
|
||||
}
|
||||
};
|
||||
MaterialSnackbar.prototype['showSnackbar'] = MaterialSnackbar.prototype.showSnackbar;
|
||||
|
@ -145,39 +135,35 @@
|
|||
* @private
|
||||
*/
|
||||
MaterialSnackbar.prototype.cleanup_ = function() {
|
||||
this.snackbarElement_.classList.remove(this.cssClasses_.ACTIVE);
|
||||
this.snackbarElement_.setAttribute('aria-hidden', true);
|
||||
if (this.actionElement_) {
|
||||
this.element_.classList.remove(this.cssClasses_.ACTIVE);
|
||||
this.element_.setAttribute('aria-hidden', 'true');
|
||||
this.textElement_.textContent = '';
|
||||
if (Boolean(this.actionElement_.getAttribute('aria-hidden'))) {
|
||||
this.setActionHidden_(true);
|
||||
this.actionElement_.textContent = '';
|
||||
this.actionElement_.removeEventListener('click', this.actionHandler_);
|
||||
}
|
||||
this.setDefaults_();
|
||||
this.actionHandler_ = undefined;
|
||||
this.message_ = undefined;
|
||||
this.actionText_ = undefined;
|
||||
this.active = false;
|
||||
this.removeSnackbar_();
|
||||
this.checkQueue_();
|
||||
};
|
||||
|
||||
/**
|
||||
* Clean properties to avoid one entry affecting another.
|
||||
* Set the action handler hidden state.
|
||||
*
|
||||
* @param {boolean} value
|
||||
* @private
|
||||
*/
|
||||
MaterialSnackbar.prototype.setDefaults_ = function() {
|
||||
this.actionHandler_ = undefined;
|
||||
this.message_ = undefined;
|
||||
this.actionText_ = undefined;
|
||||
MaterialSnackbar.prototype.setActionHidden_ = function(value) {
|
||||
if (value) {
|
||||
this.actionElement_.setAttribute('aria-hidden', 'true');
|
||||
} else {
|
||||
this.actionElement_.removeAttribute('aria-hidden');
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize the object.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
MaterialSnackbar.prototype.init = function() {
|
||||
this.setDefaults_();
|
||||
this.queuedNotifications_ = [];
|
||||
};
|
||||
MaterialSnackbar.prototype['init'] = MaterialSnackbar.prototype.init;
|
||||
|
||||
// The component registers itself. It can assume componentHandler is available
|
||||
// in the global scope.
|
||||
componentHandler.register({
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<button id="demo-show-snackbar" class="mdl-button mdl-js-button mdl-button--raised" type="button">Show Snackbar</button>
|
||||
<div id="demo-snackbar-example" class="mdl-js-snackbar"></div>
|
||||
<div id="demo-snackbar-example" class="mdl-js-snackbar mdl-snackbar">
|
||||
<div class="mdl-snackbar__text"></div>
|
||||
<button class="mdl-snackbar__action" type="button"></button>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
<button id="demo-show-toast" class="mdl-button mdl-js-button mdl-button--raised" type="button">Show Toast</button>
|
||||
<div id="demo-toast-example" class="mdl-js-snackbar"></div>
|
||||
<div id="demo-toast-example" class="mdl-js-snackbar mdl-snackbar">
|
||||
<div class="mdl-snackbar__text"></div>
|
||||
<button class="mdl-snackbar__action" type="button"></button>
|
||||
</div>
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
|
|
|
@ -25,7 +25,7 @@ var menuStamps = [];
|
|||
// 'MaterialRadio',
|
||||
'MaterialRipple',
|
||||
// 'MaterialSlider',
|
||||
'MaterialSnackbar',
|
||||
// 'MaterialSnackbar',
|
||||
// 'MaterialSwitch',
|
||||
'MaterialTabs',
|
||||
// 'MaterialTextfield',
|
||||
|
|
|
@ -16,25 +16,49 @@
|
|||
|
||||
describe('MaterialSnackbar', function () {
|
||||
|
||||
function createSnackbarMarkup() {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'mdl-js-snackbar mdl-snackbar';
|
||||
var text = document.createElement('div');
|
||||
var action = document.createElement('button');
|
||||
action.type = 'button';
|
||||
action.classList.add('mdl-snackbar__action');
|
||||
text.classList.add('mdl-snackbar__text');
|
||||
el.appendChild(text);
|
||||
el.appendChild(action);
|
||||
return el;
|
||||
}
|
||||
|
||||
it('should be globally available', function () {
|
||||
expect(MaterialSnackbar).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should expose public methods', function() {
|
||||
var el = createSnackbarMarkup();
|
||||
componentHandler.upgradeElement(el);
|
||||
var methods = [
|
||||
'showSnackbar'
|
||||
];
|
||||
methods.forEach(function(item) {
|
||||
expect(el.MaterialSnackbar[item]).to.be.a('function');
|
||||
});
|
||||
});
|
||||
|
||||
it('should be upgradable', function() {
|
||||
var el = document.createElement('div');
|
||||
var el = createSnackbarMarkup();
|
||||
componentHandler.upgradeElement(el, 'MaterialSnackbar');
|
||||
expect($(el)).to.have.data('upgraded', ',MaterialSnackbar');
|
||||
});
|
||||
|
||||
it('should reveal showSnackbar to widget', function() {
|
||||
var el = document.createElement('div');
|
||||
var el = createSnackbarMarkup();
|
||||
componentHandler.upgradeElement(el, 'MaterialSnackbar');
|
||||
expect(el.MaterialSnackbar.showSnackbar).to.be.a('function');
|
||||
});
|
||||
|
||||
it('should throw an error if not provided data', function() {
|
||||
expect(function() {
|
||||
var el = document.createElement('div');
|
||||
var el = createSnackbarMarkup();
|
||||
componentHandler.upgradeElement(el, 'MaterialSnackbar');
|
||||
el.MaterialSnackbar.showSnackbar();
|
||||
}).to.throw('Please provide a data object with at least a message to display.');
|
||||
|
@ -42,7 +66,7 @@ describe('MaterialSnackbar', function () {
|
|||
|
||||
it('should throw an error if not provided a message', function() {
|
||||
expect(function() {
|
||||
var el = document.createElement('div');
|
||||
var el = createSnackbarMarkup();
|
||||
componentHandler.upgradeElement(el, 'MaterialSnackbar');
|
||||
el.MaterialSnackbar.showSnackbar({});
|
||||
}).to.throw('Please provide a message to be displayed.');
|
||||
|
@ -50,7 +74,7 @@ describe('MaterialSnackbar', function () {
|
|||
|
||||
it('should throw an error if not provided actionText with an actionHandler', function() {
|
||||
expect(function() {
|
||||
var el = document.createElement('div');
|
||||
var el = createSnackbarMarkup();
|
||||
componentHandler.upgradeElement(el, 'MaterialSnackbar');
|
||||
el.MaterialSnackbar.showSnackbar({
|
||||
message: 'Test message',
|
||||
|
@ -59,4 +83,22 @@ describe('MaterialSnackbar', function () {
|
|||
}).to.throw('Please provide action text with the handler.');
|
||||
});
|
||||
|
||||
it('should throw an error if not constructed with a text area in the markup', function() {
|
||||
expect(function() {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'mdl-js-snackbar mdl-snackbar';
|
||||
componentHandler.upgradeElement(el);
|
||||
}).to.throw('There must be a message element for a snackbar.');
|
||||
});
|
||||
|
||||
it('should throw an error if not constructed with a text area in the markup', function() {
|
||||
expect(function() {
|
||||
var el = document.createElement('div');
|
||||
el.className = 'mdl-js-snackbar mdl-snackbar';
|
||||
var textArea = document.createElement('div');
|
||||
textArea.className = 'mdl-snackbar__text';
|
||||
el.appendChild(textArea);
|
||||
componentHandler.upgradeElement(el);
|
||||
}).to.throw('There must be an action element for a snackbar.');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue