Linking directly to both parent+child views/contro

2019-01-26 21:04发布

问题:

Consider this example:

The main router is located in

app.js

  • someparent/childroute1
  • someparent/childroute2
  • route3

"someparent" is the "base controller and view". It has some reusable html markup in the view, custom elements and bindings which is to be shared by all its "child views and controllers". The child views and controllers will access these.

Inside "someparent.html" there's (besides the shared markup) also a <router-view> tag, in which the child routes and pages should be rendered inside, but there's no navigation inside someparent.html.

From the main router/routes in app.js it should be possible to click a link and land - not on the parent/base class "someparent" itself, but directly on the children of the "someparent" "base/parent views and controllers", rendering both, when you click a link in the navigation menu of app.html built from the routes in app.js (and maybe routes in someparent.js injecting the child router in the parent or what?).

So essentially what I need is to achieve almost the same thing as basic routing - but as I mentioned I need to have multiple of these routes / pages as partials of a parent view/controller. I couldn't find any info on this from googling extensively for weeks, so hopefully someone in here will be able to understand what I ask, and have an idea of how to go about this in Aurelia, the right way?

回答1:

Create a class to contain your shared state and take a dependency on that class in your view-models. You can use the NewInstance.of resolver to control when shared state is created vs reused.

Here's an example: https://gist.run?id=4cbf5e9fa71ad4f4041556e4595d3e36

shared-state.js

export class SharedState {
  fromdate = '';
  todate = '';
  language = 'English';
}

shared-parent.js

import {inject, NewInstance} from 'aurelia-framework';
import {SharedState} from './shared-state';

@inject(NewInstance.of(SharedState)) // <-- this says create a new instance of the SharedState whenever a SharedParent instance is created
export class SharedParent {
  constructor(state) {
    this.state = state;
  }

  configureRouter(config, router){
    config.title = 'Aurelia';
    config.map([
        { route: ['', 'child-a'], moduleId: './child-a', nav: true, title: 'Child A' },
        { route: 'child-b', moduleId: './child-b', nav: true, title: 'Child B' },
    ]);

    this.router = router;
  }
}

note: if you use @inject(SharedState) instead of @inject(NewInstance.of(SharedState)), a single instance of SharedState will be shared with all components. This may be what you are looking for, I wasn't sure. The purpose of @inject(NewInstance.of(SharedState)) is to make sure the parent and it's children have their own SharedState instance.

child-a.js

import {inject} from 'aurelia-framework';
import {SharedState} from './shared-state';

@inject(SharedState)
export class ChildA {
  constructor(state) {
    this.state = state;
  }
}

child-b.js

import {inject} from 'aurelia-framework';
import {SharedState} from './shared-state';

@inject(SharedState)
export class ChildB {
  constructor(state) {
    this.state = state;
  }
}


回答2:

After better understanding the original question, I would propose the following solution, which takes advantage of the "Additional Data" parameter available in Aurelia's router. For more information, see http://aurelia.io/hub.html#/doc/article/aurelia/router/latest/router-configuration/4

app.js

configureRouter(config, router) {
  this.router = router;
  config.title = 'My Application Title';
  config.map([
    { route: ['', 'home'], name: 'home', moduleId: 'homefolder/home' },
    { route: 'someparent/child1', name: 'child1', moduleId: 'someparentfolder/someparent', settings: {data: 'child1'} },
    { route: 'someparent/child2', name: 'child2', moduleId: 'someparentfolder/someparent', settings: {data: 'child2'} },
    { route: 'someparent/child3', name: 'child3', moduleId: 'someparentfolder/someparent', settings: {data: 'child3'} }
  ]);
}

someparent.js

import {inject} from 'aurelia-framework';
import {Router} from 'aurelia-router';
@inject(Router)
export class SomeParent {
  constructor(router) {
    this.router = router;
  }
}

someparent.html

<template>
  <require from="./child1"></require>
  <require from="./child2"></require>
  <require from="./child3"></require>

  <h1>Some Parent Page Title</h1>

  <div if.bind="router.currentInstruction.config.settings.data == 'child1'">
    <h2>Child Component 1</h2>
    <child-component-one linkeddata.bind="child1"></child-component-one>
  </div>

  <div if.bind="router.currentInstruction.config.settings.data == 'child2'">
    <h2>Child Component 2</h2>
    <child-component-two linkeddata.bind="child2"></child-component-two>
  </div>

  <div if.bind="router.currentInstruction.config.settings.data == 'child3'">
    <h2>Child Component 3</h2>
    <child-component-three linkeddata.bind="child3"></child-component-three>
  </div>

</template>

Additional thoughts:

I tested the above and it works. Hopefully by using the route settings data parameters you can "get the message through" to the parent router as to which child you want displayed. Depending on your specific application, you may prefer to implement this as a sub-router, but simply binding/unbinding the individual child views as I've demonstrated above is a simple solution. It also shows you how to access the extra parameters you can supply with each route in Aurelia's router.



回答3:

I'm still relatively new to Aurelia (about 3 months) so there might be a more "expert" answer out there, but what you're trying to do is quite basic. Remember that Aurelia is based completely on components, to the point that every component is basically an element on a page. When rendering the "parent" view/controller, your "child" view/controllers are just elements on that parent page. So you only need to render the parent page and ensure that the child pages are linked correctly.

Router (in app.js):

configureRouter(config, router) {
  this.router = router;
  config.title = 'My Application Title';
  config.map([
    { route: ['', 'home'], name: 'home', moduleId: 'homefolder/home' },
    { route: 'someparent', name: 'someparentnamefornamedroutes-optional', moduleId: 'someparentfolder/someparent' },
  ]);
}

Parent ViewModel (in someparentfolder/someparent.js)

// imports go here, like: import { inject } from 'aurelia-framework';

// injects go here, like: @inject(MyClass)
export class SomeParent {
  child1 = {
    fname: "Debbie",
    lname: "Smith"
  };
  constructor() {

  }
}

Parent View (in someparentfolder/someparent.html)

<template>
  <require from="./child1"></require>
  <require from="./child2"></require>

  <h1>Some Parent Page Title</h1>

  <h2>Child Component 1</h2>
  <child-component-one linkeddata.bind="child1"></child-component-one>

  <h2>Child Component 2</h2>
  <child-component-two></child-component-two>

</template>

Child 1 ViewModel (in someparentfolder/child1.js)

import { inject, bindable, bindingMode } from 'aurelia-framework';
import { Core } from 'core';

@inject(Core)
export class ChildComponentOne { // use InitCaps, which will be translated to dash case by Aurelia for the element ref in SomeParent
  @bindable({ defaultBindingMode: bindingMode.twoWay }) linkeddata;
  constructor(core) {
    this.core = core;
  }
  attached() {
    // example calling core function
    var response = this.core.myCustomFunction();
  }
}

Child 1 View (in someparentfolder/child1.html)

<template>
  <h3>Hello, ${linkeddata.fname}</h3>
  <p>Here's something from core: ${core.value1}</p>
</template>

(Use same concepts for Child 2 ViewModel and View)

Navigation Directly to Child Components:

The above scenario has each of the child components "embedded" in the SomeParent page. However, if you want to simply open each Child Component as its own navigation router view (to open directly in your main <router-view></router-view> content window), just use a router definition like this:

configureRouter(config, router) {
  this.router = router;
  config.title = 'My Application Title';
  config.map([
    { route: ['', 'home'], name: 'home', moduleId: 'homefolder/home' },
    { route: 'someparent', name: 'someparent', moduleId: 'someparentfolder/someparent' },
    { route: 'someparent/child1', name: 'child1', moduleId: 'someparentfolder/child1' },
    { route: 'someparent/child2', name: 'child2', moduleId: 'someparentfolder/child2' },
    { route: 'someparent/child3', name: 'child3', moduleId: 'someparentfolder/child3' }
  ]);
}

One More Scenario:

Perhaps what you're looking for is a Singular class that contains a common place to store state and common functions that all of your components will access. I implemented this by creating a model called core.js. I've added those details above, and you would also create the following file in your project root (/src) folder.

Core Class (in /src/core.js):

// Some example imports to support the common class
import { inject, noView } from 'aurelia-framework';
import { HttpClient, json } from 'aurelia-fetch-client';
import { I18N } from 'aurelia-i18n';
import { EventAggregator } from 'aurelia-event-aggregator';

@noView // this decorator is needed since there is no core.html
@inject(EventAggregator, I18N, HttpClient)
export class Core {
  value1 = "Test data 1";
  value2 = "Test data 2";
  constructor(eventAggregator, i18n, httpClient) {
    // store local handles
    this.eventAggregator = eventAggregator;
    this.i18n = i18n;
    this.httpClient = httpClient;
  }

  myCustomFunction() {
    // some code here, available to any component that has core.js injected
  }
}

Notes on Binding:

I gave you an example of how to set up two-way binding to the child component with a JavaScript object. Aurelia is very flexible and you could do this a lot of different ways but this seems to be fairly standard. If you don't need the child to manipulate the data contained in child1, you could delete the parenthetical notes on the @bindable decorator so it would simply be @bindable linkeddata;

You can add multiple parameters to link more than one piece of data, or group them into an object or array.

Since I'm still a relatively new user, I remember going through all of this. Please let me know if you have any follow-up comments or questions.

And by all means, if there are any true experts watching this, please teach me as well! :-)