Dogstudio / highway

Highway - A Modern Javascript Transitions Manager
https://highway.js.org/
MIT License
1.43k stars 92 forks source link

Custom renderer class properties #57

Closed joeRob2468 closed 5 years ago

joeRob2468 commented 5 years ago

Hey guys! I'm still new to the framework (moving over from Barba), and I'm running into a bit of an issue with my custom renderer class. I'm using v2.1.2, on the ES6 version of the library, and I'm using Webpack to compile and bundle my scripts.

I need to add a property to the renderer class that holds my main controller for my page scripts, so I can initialize and destroy them properly as pages are loaded and unloaded. However, if I add a constructor to the renderer, I get an Uncaught TypeError: Cannot set property 'mainController' of undefined error.

If I add a super() call before I set the property, I get this error: Uncaught TypeError: Cannot read property 'transition' of undefined at DefaultRenderer.Renderer (highway.js:2575) at new DefaultRenderer (DefaultRenderer.js:6), so obviously that doesn't work.

Do you know how I can extend the Highway.Renderer class and add class properties?

Here's my full custom renderer file:

import Highway from '@dogstudio/highway';
import MainController from './MainController';

class DefaultRenderer extends Highway.Renderer {
    constructor() {
        this.mainController = new MainController();
    }
    // Hooks/methods
    onEnter() {
        this.mainController.init();
    }
    onLeave() {
        this.mainController.destroy();
    }
    onEnterCompleted() {

    }
    onLeaveCompleted() {

    }
}

// Don`t forget to export your renderer
export default DefaultRenderer;
Anthodpnt commented 5 years ago

Hey @joeRob2468

Since you are extending Highway.Renderer which is a class with a constructor you should call super() in the constructor of your custom renderer like this:

class DefaultRenderer extends Highway.Renderer {
    constructor() {
        // Extension
        super();

        // Variables
        this.mainController = new MainController();
    }
}
joeRob2468 commented 5 years ago

Hey @Anthodpnt, thanks for taking a look at this! Here's what the constructor looks like:

constructor() {
    super();
    this.mainController = new MainController();
}

I'd previously tried that, and it causes a Uncaught (in promise) TypeError: Cannot read property 'transition' of undefined at DefaultRenderer.Renderer (highway.js:2575) error. However, I took a peek at the source code, and it looks like the Highway.Renderer class expects some properties to be passed into the constructor, so I added those parameters to the inherited class constructor as well, and it works!

For anyone else trying to extend the Highway.Renderer class to include class properties, just add a properties parameter to the constructor of your class, and pass those properties into the super() call. Here's a full working example of a custom renderer with class properties:

import Highway from '@dogstudio/highway';
import MainController from './MainController';

class DefaultRenderer extends Highway.Renderer {
    constructor(properties) {
        // pass properties into constructor of parent class
        super(properties);

        // initialize class variables 
        this.mainController = new MainController();
    }
    // Hooks/methods
    onEnter() {
        this.mainController.init();
    }
    onLeave() {
        this.mainController.destroy();
    }
    onEnterCompleted() {
        // [...]
    }
    onLeaveCompleted() {
        // [...]
    }
}

// Don`t forget to export your renderer
export default DefaultRenderer;
Anthodpnt commented 5 years ago

Nice, thanks @joeRob2468!

tiansial commented 5 years ago

How can we call the customized default renderer on Higway.Core?

Right now I'm calling it this way, but it's not working:

const H = new Highway.Core({
    renderers: {
        default: defaultRenderer,
        home: home,
        template_careers: careers,
        template_aboutus: about,
    },
    transitions: {
      default: Fade,
    },
  });
Anthodpnt commented 5 years ago

Hey @tiansial,

Nope, there is no default property for the renderers object in Highway.Core. You can have pages without any renderer attached to them. If so, Highway will use the default Highway.Renderer.

But if you like to have a default renderer that has pieces of code shared on multiple pages, you have to follow the example above and create a DefaultRenderer that will be extended by all other renderers that need the code in it.

Renderers are working this way because all pages don't need a renderer attached to them. It's thus easier to let you manage the inheritance of a DefaultRenderer and create the renderers you need that will extend this default one.

tiansial commented 5 years ago

I did follow the example and have a DefaultRenderer too, but the code in it is not being triggered on any page.

Is it enough to export the class in the end of the file?

export default DefaultRenderer;

Anthodpnt commented 5 years ago

You could follow your logic...

const H = new Highway.Core({
    renderers: {
        'home': HomeRenderer
        'default': DefaultRenderer
    }
});

... But then you have to define default as the value of data-router-view for all views that should use that renderer. Doing so, the renderer will be used but you won't be able to add extra code for a specific page.

<div data-router-wrapper>
    <div data-router-view="default">
        <!-- [...] -->
    </div>
</div>

For this reason, when you need to add extra code to DefaultRenderer for a specific view, you have to extend it and thus create another renderer that extends DefaultRenderer and add your extra code.

Let's say your contact page needs all the code from DefaultRenderer AND some extra code for this specific page. You would create a ContactRenderer that would extend DefaultRenderer to inherit all the code from DefaultRenderer and you could add some extra code for your contact page.

import DefaultRenderer from 'path/to/default.js'

class ContactRenderer extends DefaultRenderer {
    onEnter() {
        // Inherit the code from the `onEnter` method of `DefaultRenderer` if it exists
        super.onEnter();

        // Extra Code
        console.log('This is some extra code');
    }
}

export default ContactRenderer;

Finally, to use this ContactRenderer you have to attach it to a view name and use that name for the data-router-view HTML attribute like so:

const H = new Highway.Core({
    renderers: {
        'home': HomeRenderer,
        'contact': ContactRenderer,
        'default': DefaultRenderer
    }
});
<div data-router-wrapper>
    <div data-router-view="contact">
        <!-- [...] -->
    </div>
</div>

I hope that will help you @tiansial!

Best regards, Anthodpnt

tiansial commented 5 years ago

It did, thank you!