Reputation: 13200
I'm trying to figure out how to make flash messages in Aurelia. I've created a custom element flash-message
and required it in app.html
but the message doesn't update. It does display correctly if I set it to a default value.
app.html
<template>
<require from="./resources/elements/flash-message"></require>
<flash-message></flash-message>
</template>
flash-message.html
<template>
<div class="alert alert-success">${message}</div>
</template>
flash-message.js
import {bindable, bindingMode} from 'aurelia-framework';
export class FlashMessage {
@bindable({defaultBindingMode: bindingMode.twoWay}) message = 'Default';
setMessage(newValue) {
this.message = newValue;
}
}
object-detail.js
import {FlashMessage} from './resources/elements/flash-message';
export class ObjectDetail {
static inject() {return [FlashMessage]};
constructor(flash) {
this.flash = flash;
}
activate(params, routeConfig) {
this.flash.setMessage('Activate');
}
}
The activate()
code is being called as well as the setMessage()
method but the displayed messaged doesn't updated. What am I missing?
Upvotes: 1
Views: 950
Reputation: 13200
From LStarky's message, I discovered toastr which I wasn't aware of before so I ended up just using that library instead.
npm install toastr --save
aurelia.json (Under bundle -> dependencies)
{
"name": "toastr",
"path": "../node_modules/toastr",
"main": "toastr",
"resources": [
"build/toastr.min.css"
]
}
app.html
<require from="toastr/build/toastr.min.css"></require>
view-model.js
import toastr from 'toastr';
action() {
toastr.success('Toastr visible!');
}
Upvotes: 4
Reputation: 2777
Since you initially only require the template in app.html
without instantiating the class in app.js
, Aurelia treats it as a custom element, which means it has its own instance (not a singleton). You are basically working with two different instances of FlashMessage
, and therefore the properties of one is not reflected in the other.
If you want it to be instantiated as a singleton class, you'll also need to import the component in app.js
and inject it in the constructor so that it is treated like a component rather than a custom element.
app.js
import {FlashMessage} from './resources/elements/flash-message';
@inject(FlashMessage)
export class App {
constructor(flashMessage) {
this.flashMessage = flashMessage;
}
// ...
}
Confusion between Custom Element and Class/ViewModel
Since all class properties are considered public, you don't even need the setMessage(newValue)
method. You can update the message property from object-detail.js
like this:
this.flash.message = 'Activate';
Also, the @bindable
line is intended to be used so that you can instantiate it with the variable value in the HTML code, like this:
<flash-message message="Show this message"></flash-message>
If you don't plan to use it like this, I would skip the whole @bindable
line. Your flash-message.js
could be simplified to just this:
export class FlashMessage {
constructor() {
this.message = 'Default';
}
}
Use of Event Aggregator for Flash Messages
I implemented a Flash Message class with similar goals, using the Toastr 3rd party library (just because I liked the UI). But it's not hard to set it up any way you want. The best approach, I believe, to allowing any part of your app to set a flash message is to use Aurelia's Event Aggregator. The following code snippet might help you get it set up.
flash-message.js
import { inject } from 'aurelia-framework'
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator)
export class FlashMessage {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
this.eventAggregator.subscribe('ShowFlashMessage', this.showMessage);
}
showMessage(message) {
this.message = message;
// hide after 10 seconds
window.setTimeout(hideMessage, 10000);
}
hideMessage() {
this.message = "";
}
}
This is obviously simplified and doesn't handle multiple messages, or renewing the timer when a second message is posted, but it should be enough to get you started.
To set a message from another part of your app, you can simply first inject the eventAggregator and save in your constructor, and then publish a message like this:
this.eventAggregator.publish('ShowFlashMessage', "Record saved");
My Toastr Implementation in Aurelia:
Similar to what you did, I created a common class called FlashMessage
in a subfolder called common
in my src
folder.
//src/common/flash-message.js
import * as toastr from 'toastr';
import { inject } from 'aurelia-framework'
import { EventAggregator } from 'aurelia-event-aggregator';
@inject(EventAggregator)
export class FlashMessage {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
this.eventAggregator.subscribe('ewFlashSuccess', this.showSuccess);
this.eventAggregator.subscribe('ewFlashInfo', this.showInfo);
this.eventAggregator.subscribe('ewFlashWarning', this.showWarning);
this.eventAggregator.subscribe('ewFlashError', this.showError);
// Not sure why this is not working... if you figure it out, let me know.
toastr.options = {
positionClass: "toast-top-left",
showEasing: "swing",
hideEasing: "linear",
showMethod: "fadeIn",
hideMethod: "fadeOut",
preventDuplicates: true,
closeButton: true
}
}
showSuccess(message) {
toastr.success(message, null, {preventDuplicates: true, closeButton: true});
}
showInfo(message) {
toastr.info(message, null, {preventDuplicates: true, closeButton: true});
}
showWarning(message) {
toastr.warning(message, null, {preventDuplicates: true, closeButton: true});
}
showError(message) {
toastr.error(message, null, {preventDuplicates: true, closeButton: true});
}
}
Then, I injected and instantiated it in app.js
like this:
import { inject } from 'aurelia-framework';
import { FlashMessage } from './common/flash-message';
@inject(Core, FlashMessage)
export class App {
constructor(core, flashMessage) {
this.flashMessage = flashMessage;
}
// ...
}
I also had to require the CSS in app.html
like this:
<require from="toastr/build/toastr.min.css"></require>
All of this depends on having Toastr
properly installed (I installed it with npm install toastr --save
) and correctly required as a dependency in aurelia.json
(I'm using the CLI).
{
"name": "toastr",
"path": "../node_modules/toastr",
"main": "toastr",
"resources": [
"build/toastr.min.css"
]
},
Final thoughts
Please also see Ashley Grant's response for a better explanation of getting a handle on your ViewModel as well as a working GistRun to fix your immediate issues. Ashley is much more experienced than I am with Aurelia so if parts of my solution don't work, his most likely will! :-)
Upvotes: 5
Reputation: 10887
I would recommend getting a reference to the ViewModel of the custom element using view-model.ref="flash"
. Note that you won't be able to use this from the activate
callback though as no binding will have occurred at that point in the page lifecycle. I'm using the attached
callback in my example below.
Here's an example: https://gist.run?id=76ef47a5327a34560737f4ade1038305
app.html
<template>
<require from="./flash-message"></require>
<flash-message view-model.ref="flash"></flash-message>
</template>
app.js
export class App {
attached(params, routeConfig) {
this.flash.setMessage('Activate');
}
}
flash-message.html
<template>
<div class="alert alert-success">${message}</div>
</template>
flash-message.js
import {bindable, bindingMode} from 'aurelia-framework';
export class FlashMessage {
@bindable({defaultBindingMode: bindingMode.twoWay}) message = 'Default';
setMessage(newValue) {
this.message = newValue;
}
}
Upvotes: 4