loading...
Cover image for Replacing JHipster UI components (Part 1. Navbar)

Replacing JHipster UI components (Part 1. Navbar)

antonioortizpola profile image Antonio Ortiz Pola ・9 min read

In the first part I started working in a separation between JHipster and Appwork codes, in this point we have our App.vue completely separated from JHipster, trying to keep a Side by Side approach.

Replacing JHipster Layout

First I merge the app.vue code from Appwork to my new btj/App.vue, this is simple as adding the missing styles (I will leave the javascript for latter).

<!--*** JHipster part ***-->
<template>
    <div id="app">
        <ribbon></ribbon>
        <div id="app-header">
            <jhi-navbar></jhi-navbar>
        </div>
        <div class="container-fluid">
            <div class="card jh-card">
                <strong>
                Could it be is loading my layout and no JHipster?
                </strong>
                <router-view></router-view>
            </div>
            <b-modal id="login-page" hide-footer lazy>
                <span slot="modal-title" id="login-title" v-text="$t('login.title')">Sign in</span>
                <login-form></login-form>
            </b-modal>

            <jhi-footer></jhi-footer>
        </div>
    </div>
</template>

<!--*** Styles from appwork ***-->
<style src="@/vendor/styles/bootstrap.scss" lang="scss"></style>
<style src="@/vendor/styles/appwork.scss" lang="scss"></style>
<style src="@/vendor/styles/theme-corporate.scss" lang="scss"></style>
<style src="@/vendor/styles/colors.scss" lang="scss"></style>
<style src="@/vendor/styles/uikit.scss" lang="scss"></style>
<style src="./style.scss" lang="scss"></style>

<script lang="ts" src="./app.component.ts">
</script>

This creates a little problem, the layout is using the @ placeholder in the path, which is the base-path of the Appwork demo project, but in my project they are in src/main/webapp/app/bjt/vendor, I could change the paths with a replace, but a quick search shows at least 95 occurrences to replace and maintain :/.

Alt Text

So, searching for other solution I found the definition of @ in JHipster in the tsconfig.json file:

Alt Text

So, I came to the conclusion that it is fine to break my own rules and move the vendor folder from src/main/webapp/app/bjt/vendor to src/main/webapp/app/vendor, in exchange for leaving all the files as is, allowing me to update faster when new versions of Appwork are launched.

Other problem that I found, and that you will find sooner or latter is the mismatch of versions, in this case JHipster was using

"bootstrap-vue": "2.0.0-rc.11"

while appwork is in 2.0.0-rc.16, in this particular case I simply updated to the latest version and crossed my fingers that this will not break the JHipster layout, but hey, I am using Appwork, and I know the JHipster blueprint will update sooner or latter, so, always try to go with the latest stable versions.

Making that move I can start integrating the Appwork components, first adding the app-layout-navbar component to bjt/app.component.ts

...
import LayoutNavbar from '@/bjt/layout/LayoutNavbar.vue';

@Component({
  components: {
    ribbon: Ribbon,
    'jhi-navbar': JhiNavbar,
    'login-form': LoginForm,
    'jhi-footer': JhiFooter,
    'app-layout-navbar': LayoutNavbar // Appwork navbar
  }
})
export default class App extends Vue {
}

And now I can use it in my bjt/app.vue

<template>
    <div id="app">
        <ribbon></ribbon>
        <div id="app-header">
            <app-layout-navbar /> <!--Appwork component-->
            <jhi-navbar></jhi-navbar>
        </div>
        ...
    </div>
</template>
...

Then I can check the app page and feel a win:

Alt Text

But, hey!, something does not feel right...

Alt Text

The style looks different, not like the one from the demo. The answer is in two files, global.scss and vendor.scss, if I remove the code in both files, then the styles shows up fine, but, what are they and where they are coming from?

  • global.scss

Is where the main styles are located, here is where JHipster adds their custom styles for the default layout.

A quick search for the filename shows that the file is configured in two webpack configuration files.

Alt Text

  • vendor.scss

This file it is mentioned in the readme.md, from what I understand, it is suppose to contain custom imports and styles of third party plugins

Alt Text

So, again, after some thinking I resolved that I can change both webpack files (prod and dev) so I can replace global.scss with bjt/styles.scss, on the other hand, I do not need vendor.scss, since the styles for appwork layout are already included in the vendor folder.

Sadly this adds more steps to change between my app and the JHipster default app, now I have to:

  1. Replace the import in main.ts for app.vue or bjt/App.vue
  2. Comment/uncomment the vendor.scss import from main.ts // import '../content/scss/vendor.scss';
  3. Replace the global entry from both webpack.dev.js and webpack.prod.js from my implementation or the one from JHipster
entry: {
  // global: './src/main/webapp/content/scss/global.scss', // JHipster
  global: './src/main/webapp/app/bjt/style.scss', // Mine
  main: './src/main/webapp/app/main'
}

While this adds a little more burden, I have to remember that JHipster is suppose to be the base of your application, sooner or later more things will be different, the idea is to keep to minimum the differences, but do not lost focus on developer experience and maintainability, at least replacing the file imports, and not the file itself, I can have less pain if I want to update JHipster or Appwork versions.

Now that bjt/style.scss is included from Webpack, I can proceed to remove it from bjt/App.vue, I also edited the file, so it can include the other styles itself, this way my bjt/App.vue is now clean of all global styles, and styles.scss ended up like this:

// Imports from App.vue
@import '@/vendor/styles/bootstrap.scss';
@import '@/vendor/styles/appwork.scss';
@import '@/vendor/styles/theme-corporate.scss';
@import '@/vendor/styles/colors.scss';
@import '@/vendor/styles/uikit.scss';

// Appwork original style.css file
@import '~bootstrap-vue/src/index.scss';

// Base
//
...

Now we can check our app page again and...

Alt Text

We have the appwork styles! Yehiii... but... again something does not feels good, the navbar is gray, not white, something is still missing in this puzzle.

After checking the navbar component, the differences are obvious, while JHipster uses TypeScript, Appwork is using plain Javascript files, also, the color is not being set because it uses a method called getLayoutNavbarBg().

<template>
  <b-navbar toggleable="lg" :variant="getLayoutNavbarBg()" class="layout-navbar align-items-lg-center container-p-x">

    <!-- Brand -->
    <b-navbar-brand to="/">Vue Starter</b-navbar-brand>

    <!-- Sidenav toggle -->
    <b-navbar-nav class="align-items-lg-center mr-auto mr-lg-4" v-if="sidenavToggle">
      <a class="nav-item nav-link px-0 ml-2 ml-lg-0" href="javascript:void(0)" @click="toggleSidenav">
        <i class="ion ion-md-menu text-large align-middle" />
      </a>
    </b-navbar-nav>

    <!-- Navbar toggle -->
    <b-navbar-toggle target="app-layout-navbar"></b-navbar-toggle>

    <b-collapse is-nav id="app-layout-navbar">
      <b-navbar-nav class="align-items-lg-center">
        <b-nav-item href="#">Link 1</b-nav-item>
        <b-nav-item href="#">Link 2</b-nav-item>
      </b-navbar-nav>
    </b-collapse>

  </b-navbar>
</template>

<script>
export default {
  name: 'app-layout-navbar',

  props: {
    sidenavToggle: {
      type: Boolean,
      default: true
    }
  },

  methods: {
    toggleSidenav () {
      this.layoutHelpers.toggleCollapsed()
    },

    getLayoutNavbarBg () {
      return this.layoutNavbarBg
    }
  }
}
</script>

So, time to practice my TypeScript skills, I create my new Frankenstein component, an hybrid between worlds, this will be my bjt-navbar component, the html part is not changed yet, but, following JHipster structure, I create a new class to contain the Javascript code, calling it bjt-navbar.component.ts.

This class also needs an extra change, since Appworks is using plain Javascript, it acces a property called this.layoutHelpers, this property is used in a lot of components.

Alt Text

Also, it is exporting a list of functions instead of a class, here my ignorance about Javascript comes to confuse me, I could not get an easy way to implement this as a class, on one hand I have the layout helper:

export default {
  get _layoutHelpers () {
    return window.layoutHelpers
  },

  _exec (fn) {
    return this._layoutHelpers && fn()
  },

  getLayoutSidenav () {
    return this._exec(() => this._layoutHelpers.getLayoutSidenav()) || null
  },

  getSidenav () {
    return this._exec(() => this._layoutHelpers.getSidenav()) || null
  },

  ...
}

I can see it is used in globals, which maintains the same structure:

import layoutHelpers from './layout/helpers.js'

export default function () {
  return {
    // Public url
    publicUrl: process.env.BASE_URL,

    // Layout helpers
    layoutHelpers,

    // Check for RTL layout
    get isRTL () {
      return document.documentElement.getAttribute('dir') === 'rtl' ||
             document.body.getAttribute('dir') === 'rtl'
    },

    ....
  }
}

But I could not find where the connection is made, how the component can simply access to the properties this this.layoutHelpers, so, the only way I found to use that classes was to implement a class field:

import { Component } from 'vue-property-decorator';
import JhiNavbar from "@/core/jhi-navbar/jhi-navbar.component";
import globals from "@/bjt/globals";

@Component
export default class BjtNavbar extends JhiNavbar {

  private globals = globals();
  private layoutHelpers = this.globals.layoutHelpers;

  public sidenavToggle = true;

  public toggleSidenav () {
    this.layoutHelpers.toggleCollapsed()
  }

  public getLayoutNavbarBg () {
    return this.globals.layoutNavbarBg
  }
}

It is also really important to notice one thing, my component is extending JhiNavbar, so I can access to all the properties, but this will come little latter, first, I need to check if the navbar style is fixed now, so, I simply replace the appwork component for my new one in bjt/app.component.ts

@Component({
  components: {
    ribbon: Ribbon,
    'jhi-navbar': JhiNavbar,
    'login-form': LoginForm,
    'jhi-footer': JhiFooter,
    'bjt-navbar': BjtNavbar // Look at me, I am the component now
  }
})
...

And finally, we have our custom navbar with the white background

Alt Text

Before proceed, I need to make a little change, I am working with the minimum Appwork starter layout, but to make the best of the layout, I need to be based on the more complete vue-demo example.

Appwork starter
Appwork starter

Appwork demo
Appwork demo

Luckily the appwork authors has been doing a great job organizing the code, there are not many differences between both projects in the base files (only more pages and a demo.css), so I just add the demo.css in my style.scss.

Alt Text

And then I just replace the content of my bjt/bjt-navbar.vue file with the content from the LayoutNavbar.vue from the appwork demo project.

Alt Text

As you can see, the styles are working fine, however the styles in the dropdown are weird, and the images are not loading.

The images are not much of a problem, I can just remove it for now, anyway, they are images that will not be in the final product, but the dropdown requires a more profound investigation.

The console shows that is not loading the vue component

[Vue warn]: Unknown custom element: - did you register the component correctly? For recursive components, make sure to provide the "name" option.

Here is where my knowledge again limits me a little, here is what I see, searching in Appwork demo project, I can see it is used multiple times:

Alt Text

But I can not find a place where the component is declared or initialized, I Know it is a component from vue-bootstrap, but no special config seems required.

On the other hand, in the JHipster project I found something interesting, JHipster is indeed, declaring each individual component from vue-bootstrap, in config-boostrap-vue.ts:

export function initBootstrapVue(vue) {
  vue.component('b-badge', bBadge);
  ..
  vue.directive('b-modal', bModalDirective);
}

I do not know why JHipster is doing this but not Appwork, but since I am using JHipster as base, I add a file in bjt/config/config-bootstrap-vue-bjt.ts with the components that JHipster is not adding:

import bDdDivider from 'bootstrap-vue/es/components/dropdown/dropdown-item';
import bDdItem from 'bootstrap-vue/es/components/dropdown/dropdown-item';
import bListGroup from 'bootstrap-vue/es/components/list-group/list-group';
import bListGroupItem from 'bootstrap-vue/es/components/list-group/list-group-item';

export function initBootstrapVueBjt(vue) {
  vue.component('b-dd-divider', bDdDivider);
  vue.component('b-dd-item', bDdItem);
  vue.component('b-list-group', bListGroup);
  vue.component('b-list-group-item', bListGroupItem);
}

And then I add my components initialization in main.ts

...
bootstrapVueConfig.initBootstrapVue(Vue);
bootstrapVueConfigBjt.initBootstrapVueBjt(Vue); // My components will be here
...

And finally, we have the navbar from Appwork in our project!!!

Alt Text

Now, thanks to extend the JhiNavbar in my bjt/bjt-navbar.component.ts, I can simply include the JHipster components in my layout with no problem.

export default class BjtNavbar extends JhiNavbar {

Then, it comes the most tedious part, merging the navbar from Appwork Demo to my JHipster project, but it is an easy task, and the results bring alive my new navbar menu.

Alt Text
Alt Text
Alt Text

I hope you can notice how the code did not change dramatically, it is just being replacing the text with the internacionalization utilities and the menus with the JHipster options.

Alt Text

You can also notice how there is no Entities menu, this is because entities will go in the left menu (not introduced yet), also the username is hard coded. Stay tuned for the third part where I will try to refine the top nabvar and insert the left menu from the Appwork demo.

Disclaimer

Sorry if I made some mistakes or do not follow the best practices in the post, again, I am mainly a back-end developer, and I am just learning Vue.js, so, if you find something that could be done in a better way, or if I did assumed something wrong about JHipster, please let me know!

Discussion

pic
Editor guide