Trying to find the way how to share properties of multiple objects to a single object.
For example: I have the app object and the homepage object. The idea is very simple, I'd like to share the properties of the homepage object with the app object and be able to do something like this:
const app = {...}
const homepage = {
clickOnSignIn() {...}
}
app.clickOnSignIn()
I know that I can simply merge this two objects, but this is not a solution for me. The plan is to add more pages in the future and still be able to use the dot notion on the app object.
Update:
I discovered another possible solution for this problem, but I'm not sure if it's the correct way of doing things.
Created the Homepage object with some methods on this object inside /pages/homepage.js file.
export const HomePage = {
clickOnSignUpButton() {},
clickOnSignInButton() {},
};
There's one more object and I called it the LoginPage.
export const LoginPage = {
fillUpSignInForm() {},
clickForgotPassword() {},
};
I exported these files inside /pages/index.js
export * from "./home.js";
export * from "./login.js";
Then I simply imported these pages as a namespace inside my app.js file and created a few getter functions.
import * as pages from "./pages/index.js";
export class App {
constructor(client) {
if (!App.instance) {
this.client = client;
App.instance = this;
}
return App.instance;
}
static get pages() {
return pages;
}
static getPage(pageName) {
return pages[pageName];
}
}
First of all, it works as excepted. Using this approach, I have access to all my pages thru my App class and now, I can do something like this:
const app = new App("some-client");
const { HomePage } = app.pages;
HomePage.clickOnSignUpButton();
I'm currently working on a ReactJS project and I'm trying to come up with an effective way to namespace my code. The most obvious solution I have is to do the following:
let API = {
init: function() {
// Do some type of initialization here
}
makeACall: function() {
// Make a call
}
};
Previously, I'd make a file of functions each of which were exported like:
export function makeACall() {
// Make a call
}
And then I'd import it into another file using: import { makeACall } from 'utils/api';
However the problem here is that I may have another function in the imported file called makeACall. So what I'd like to do is namespace it. Question is: how do I import individual functions from an object/namespace or is this not possible?
Basically I'd like to patch functions that another function calls so that I can confirm they've been called. Is this possible in js? (I'm using Mocha/Chai/Sinon for testing).
A simplified version of what I'd like to do:
// in render.js
export helper = function() {}
export default = function() {
helper()
}
// in the render-test.js
import render, { helper } from 'render'
// what I'd like to to:
helper = sinon.spy()
render()
assert(helper.called, true)
It's possible, but it does require some rewriting, most notably the way you're calling helper in render.js.
Here's an example that works:
// render.js
export function helper() {}
export default function() {
exports.helper()
}
// render-test.js
import render, * as renderFuncs from 'render'
...
sinon.spy(renderFuncs, 'helper');
render()
assert(renderFuncs.helper.called)
The reason you need to call exports.helper() instead of just helper() is because the latter is a local reference to the helper function, which you can't access from the test file.
The exports object is accessible from the test file (it's given the name renderFuncs), so Sinon can change the reference to helper and wrap it with a spy.
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.
In my Aurelia view I have a script tag from where I want to call a function from my view-model:
Page.html:
<template>
...
<script>
function beginEdit(args) {
console.log(args);
console.log(args.primaryKeyValue);
//In this place I want to call the sayHello() fuction
};
</script>
</template>
Page.js:
import {inject} from 'aurelia-framework'
import {HttpClient} from 'aurelia-http-client';
import {Router} from 'aurelia-router';
export class Licigrid{
constructor(){...}
activate(){...}
...
sayHello()
{
alert("Hello");
}
}
I tried to use inside my script tag ${sayHello();} but this calls the function immidiately when the page has loaded and not when the user has entered in the beginEdit() function.
I have reproduced this piece of code in Plunker.
Note that I am using a package that calls the beginEdit function by itself, so unfortunately I can not use .trigger(), .delegate(), or .call() inside my html tag which would solve my problem.
My question is: is there a solution to call the sayHello() function from my script tag?
The beginEdit function needs access to the App view model instance. We can use Aurelia's DI container to retrieve this:
var app = container.get(App);
app.sayHello();
Problem is we don't have access to the container within the beginEdit function. We can use a small hack to access the container:
var container = document.body.aurelia.container;
We also need the App constructor function because it's the key to retrieving the app instance from the container. We can use the System loader to load the module containing the App constructor function:
System.import('app').then(function(module) {
var App = module.App;
});
All together it looks like this:
function beginEdit() {
System.import('app').then(function(module) {
var App = module.App, // get the constructor function
container = document.body.aurelia.container, // get the container
app = container.get(App); // get the instance of App (the viewmodel)
app.sayHello();
});
}
Here's the working plunkr:
http://plnkr.co/edit/9c0oJmifjW5pXXdD06Hg?p=preview