How to get route parameter in aurelia custom component - javascript

I'm creating a custom component in aureliajs framework and injecting it a Router instance:
#inject(Router)
export class Pagination {
constructor(router) {
this.router = router;
}
}
It will be used with list view-models in order to setup some basic pagination. Therefore I need to read current page number from an active route (that would look like: orders/:pageNum. I'm not sure however how to do it? I mean - I know it should probably be placed in Pagination attached method, but how to get to this :pageNum param?

Create a bindable property in your custom element. Take the page number from the active route and bind it to the custom element. For instance:
import { bindable } from 'aurelia-framework';
#inject(Router)
export class Pagination {
#bindable page;
constructor(router) {
this.router = router;
}
}
Usage:
<pagination page.bind="page"></pagination>
To get the page number in the active route, use the activate() hook:
activate(params) {
this.page = params.pageNum;
}

You can use router.currentInstruction.params and router.currentInstruction.queryParams.
You can also observe the router to be notified when the route changes:
let sub = this.bindingEngine.propertyObserver(
this.router, 'currentInstruction').subscribe(currentInstruction => {
console.log(currentInstruction);
});

Related

Vue.js - Helper function that accesses the app instance

Let's say I want to make a utility function that redirects users to a route if they aren't authenticated.
This function resides in a utils.js file; it isn't declared in any one component.
// utils.js
export function redirectIfNotAuthenticated (app, path) {
if (!app.$store.state.isAuthenticated) {
app.$router.push(path)
}
}
this function is then imported into single-file components and called like:
redirectIfNotAuthenticated(this, '/login').
I think having an app parameter and having to explicitly pass this from the component is ugly. Is there a better way to have a stateless helper function access the scope of the calling component in vue? As in, is there a way to bind functions that don't live inside of a .vue file to the calling instance of the vue app?
To handle redirection, instead of putting logic inside component, you can use vue-router's navigation guards to handle on top level instead:
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
if (!isAuthenticationRequired(to)) { // your logic to ignore authentication for custom routes, e.g. login
next();
} else if (!isAuthenticated()) { // your logic which needs access to Vuex's store
next('login');
} else {
next();
}
})
Then, if you create a store object and add it to your Vue app instance, like in the default example of Vuex , you can refer to the object directly without any need to access the Vue app, since same store object is used:
// utils.js
import store from '../your_path/your_file.js';
export function isAuthenticated() {
return store.state.isAuthenticated;
}
The best practice in this situation is to use router navigation guards :
const router = new VueRouter({ ... })
router.beforeEach((to, from, next) => {
next(vm=>!vm.$store.state.isAuthenticated)
//vm refers to the vue instance
})

emit event with arguments from child to parent with ember 3.6

I would like emit one event from child to parent but with some argument.
Child component javascript
import Component from '#ember/component';
export default Component.extend({
tagName: 'li',
actions: {
favoriteWasClicked() {
const organization = this.get('organization');
// organization.id value equal to 'facebook' here in my app
// we're gonna send its id to parent component "orgs" like an argument of clicked method
this.clicked(organization);
}
}
});
Parent component .hbs template
{{child-component
clicked=(action "favoriteClicked" value="organization.id")
}}
Parent component javascript controller file
import Controller from '#ember/controller';
export default Controller.extend({
actions: {
favoriteClicked(orgId) {
console.log(orgId);
}
}
});
I'm getting undefined in console.log(orgId); Why? What am i missing
Thank you!
You simply need to change organization.id to just id. What I mean is; you need to do the following:
{{child-component
clicked=(action "favoriteClicked" value="id")
}}
You send the event from the child component with an organization; which simply contains an id; but not organization.id; so it must be just id for the value property in the action passing within parent's template.
I prepared an ember twiddle for you to illustrate the solution I proposed in action. Hope that helps.

Interfacing between view-models in Aurelia

I have two views on the same page. View A’s view-model needs to call a method in view B’s view-model. Is that possible with Aurelia?
Also, is this the best approach? Would it be better to use the EventAggregator (pub/sub) to communicate between the view-models?
----- More Details -----
To be much more specific, there is a nav bar used in my app.html file like this:
<template>
<require from="nav-bar-view"></require>
<nav-bar-view></nav-bar-view>
<router-view></router-view>
</template>
View-models within the router-view need to be able to change the nav bar's title and button text.
My initial design was to use pub/sub to communicate the changes to the nav bar view-model. Since that seemed a bit messy and overcomplicated, I wanted to come up with a simpler approach.
My latest idea is to create a singleton NavBar class that is injected into the NavBarView class and the "consumer" view-models.
The following is a simplified version of the code:
nav-bar-view.html:
<template>
<div class="center">${navBar.title}</div>
</template>
nav-bar-view.js:
import {inject} from 'aurelia-framework';
import {NavBar} from 'nav-bar';
#inject(NavBar)
export class NavBarView {
constructor(navBar) {
this.navBar = navBar;
}
}
nav-bar.js:
import {singleton} from 'aurelia-framework';
#singleton()
export class NavBar {
constructor() {
this.title = '';
}
}
view.js (a consumer of the nav bar):
import {inject} from 'aurelia-framework';
import {NavBar} from 'nav-bar';
#inject(NavBar)
export class View {
constructor(navBar) {
this.navBar = navBar;
}
attached() {
this.navBar.title = 'This View's Title';
}
}
Again, this is much simpler than the actual code, but it servers to illustrate the idea.
I've tried it out and it works fine. Does this make sense? Is there a better way?
pub/sub would work but I suspect you're looking for something a little more targeted than that.
The preferred way to pass something into a custom element is via a bindable property. Assuming you have component-a and component-b and A needs to call a method on B's view-model. Here's what you could do:
get a reference to B's view-model so we can bind to it's properties and methods:
<component-b view-model.ref="b"></component-b>
Add bindable property to component A so we can give component A the reference to B's method.
import {bindable} from 'aurelia-framework';
export class ComponentA {
#bindable sayHello;
}
Bind component A's sayHello property to B's sayHello method.
<component-a say-hello.call="b.sayHello()"></component-a>
Here's a runnable example: https://gist.run/?id=91269472d4e6509e32123ca2a63dd9ca
Edit
Based on the updated information in the question, here's what I would recommend:
1. Create a class that contain's your nav-bar state
export class NavState {
title = 'some default title';
}
2. Take a dependency on the NavState in your nav-bar component
#inject(NavState)
export class NavBar {
constructor(state) {
this.state = state; // now you can bind to "state.title" in nav-bar.html
}
...
}
3. Take a dependency on the NavState in components that need to change the title.
#inject(NavState)
export class MyComponentThatChangesTheTitle {
constructor(state) {
this.state.title = 'something else';
}
...
}
This will be more flexible than passing around a component's viewmodel as state. For example, with this model, you can configure the title before the nav-bar is even instantiated.

Using vue-router, how can I make menu selected styles?

I'm using vue routers to create a single-page application. I have a navigation system where clicking on a menu item takes them to the corresponding page. For example:
var pageView = require('./components/pageView.vue')
// Define some components
var Boards_page = Vue.extend( {
template: '<p>Boards</p>'
})
var Fantheories = Vue.extend( {
template: '<p>Character</p>'
})
// The router needs a root component to render.
// For demo purposes, we will just use an empty one
// because we are using the HTML as the app template.
// !! Note that the App is not a Vue instance.
var App = Vue.extend({})
// Create a router instance.
// You can pass in additional options here, but let's
// keep it simple for now.
var router = new VueRouter({
history: true
})
// Define some routes.
// Each route should map to a component. The "component" can
// either be an actual component constructor created via
// Vue.extend(), or just a component options object.
// We'll talk about nested routes later.
router.map({
'/': {
component: pageView
},
'/boards': {
component: Boards_page
},
'/fantheories': {
component: Fantheories_page
}
})
// Now we can start the app!
// The router will create an instance of App and mount to
// the element matching the selector #app.
router.start(App, '#container')
How would I make it so that whatever URL I'm on the menu item has a different CSS style so it appears as if its selected?
The router option linkActiveClass determines the class that is applied to an active link. It defaults to v-link-active.
http://vuejs.github.io/vue-router/en/options.html

MarionetteJS: who is responsible for showing sub-applications?

I am building a quite simple Marionette application; I am not using Marionette.Module since it's deprecated and since I want to use ES2015 with Webpack.
I have just a couple of "pages": one is the Welcome screen and the other one is the Playground. Each one of those pages are Applications, plus there is a root application with a Layout with just three regions: header, main and footer.
The view (or layout) of those applications are meant to fill the main region of the root layout.
Now, whenever I want to show one of those sub-applications, I don't know exactly how (or, I am not finding the most satisfying way) to ask the Layout of the root application to showChildView of the view/layout of those sub-apps.
So far, I came up with two approaches (none of which is fantastic):
EDIT: I added another approach at the end of the Q
on the controller of each sub-application, trigger the event "application:show" passing in the view. The root application is listening to this event and showChildView as soon as it receives the message
whenever I start a sub-application, I pass it the root application instance. Whenever the sub-application needs to show itself, it will call the showChildView inside the main region
The first approach is weak, because it's totally asynchronous (fire and forget). I'd like to know when my application is shown (attached to the DOM) but relying again on another event seems cumbersome
The second approach is more robust but it sucks, because of course I don't want the sub-application's views to be responsible of their own rendering in the root layout. The root application knows better.
Some code follows to try to show both ideas:
// Approach #1
// In the root app
welcomeApp.on('app:show', (view) => {
rootApp.rootView.showChildView('main', view);
});
// In the Welcome sub-app
var Controller = {
show() {
app.trigger('app:show', new WelcomeView());
}
};
// Approach #2
// In the root application
const welcomeApp = new WelcomeApp({
rootApp: this
});
// In the Welcome sub-app
var Controller = {
show() {
app.rootApp.rootLayout.showChildView('main', new WelcomeView());
}
};
EDIT: 12 Jan.
OK, digging a bit more in the documentation I found what I think is the correct way to do that. The idea is that the root Application will listen for Commands from the subapplications.
So, in my root view I will have:
this.channel.commands.setHandler("application:show", function(view) {
this.rootView.showChildView('main', view);
}.bind(this));
In all the other subapplications, I will have (for example, in the WelcomeView):
getController() {
const greet = function () {
this.channel.commands.execute('application:show', new WelcomeView());
}.bind(this);
return {
greet: greet
};
}
Personally I wouldn't use multiple applications, this seems to be just getting around the issue of modules being removed. Have you tried using a single application with a LayoutView, a Region for each component or (module) and the base of the component either being a singleton JS object, or you could try Backbone.Service to split it up. If you are using multiple routes, you could have a look at Backbone.Routing, with each Route being the base 'controller' for the 'page'.
I find a brilliant architecture for large scale Marionette applications is James Kyle's Marionette Wires. This uses Service for reusable components, and routers/routes for different data types.
EDIT
Another way of architecting using services, but if you don't want the root application's regions show methods to be called from child components, would be to import the instantiated application into the child component and use the app.addRegions method to add regions within the child. EG
//app.js
import { Application } from 'backbone.marionette';
const App = Application.extend({
onStart() {
//doSomething();
}...
});
export const app = new App();
//submodule/service.js
import { Service } from 'backbone.service';
import { SubmoduleController } from './controller';
const SubmoduleService = Service.extend({
requests() {
show: 'show',
hide: 'hide'
},
setup(options = {}) {
this.el = options.el
},
start() {
this.controller = new SubmoduleController({
el: this.el
});
},
show() {
this.controller.show();
},
hide() {
this.controller.destroy();
}
});
export const submoduleService = new SubmoduleService();
//submodule/controller.js
import { Object as Controller, Region } from 'backbone.marionette';
import { View } from './view';
import { app } from '../app';
export const SubmoduleController = Controller.extend({
initialize(options = {}) {
this.el = options.el;
this._addRegions();
},
_addRegions() {
const region = new Region({
el: this.el
});
app.addRegions({
submoduleRegion: region
});
},
show() {
const view = new View();
app.submoduleRegion.show(view);
},
onDestroy() {
app.submoduleRegion.reset();
}
});
//main.js
import { submoduleService } from './submodule/service';
const config = {
submodule: {
el: '#submodule';
},
...
};
submoduleService.setup(config.submodule);
submoduleService.show();
A gotcha with doing it this way is the submodule's region needs it's element to exist in the DOM when it's region's show method is called. This can either be achieved by having a root view on the application, whose template contains all component root elems, or to have a root submodule - page or something.

Categories

Resources