Replacing JHipster UI components (Part 1. Navbar)
Tags: jhipster, vue
Published: 2019-09-05 07:06:56

[In the first part](https://dev.to/antonioortizpola/separating-the-jhipster-layout-from-a-custom-ui-implementation-55i8) 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).

```html
<!--*** 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](https://thepracticaldev.s3.amazonaws.com/i/nam5lvqdjk3cx5xckzg9.png)

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

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/0qyryzwtgbfyv0mlpr6u.png)

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

```json
"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`


```typescript
...
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`

```html
<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](https://thepracticaldev.s3.amazonaws.com/i/y8s0uc9z1uy75mszw8gk.png)

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

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/d3fvvgfeuuacx51scg9r.png)

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](https://thepracticaldev.s3.amazonaws.com/i/mfvef0hsviq7i2u4pt9r.png)

- `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](https://thepracticaldev.s3.amazonaws.com/i/4oerr6hsphwzj498j727.png)

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`
1. Comment/uncomment the vendor.scss import from `main.ts` `// import '../content/scss/vendor.scss';`
1. 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:

```scss
// 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](https://thepracticaldev.s3.amazonaws.com/i/rty22vff8ayj44gxaqkl.png)

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()`.

```html
<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](https://thepracticaldev.s3.amazonaws.com/i/0ajq2nxtv4xf8agbfyvy.png)

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:

```javascript
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:

```javascript
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:

```javascript
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`

```javascript
@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](https://thepracticaldev.s3.amazonaws.com/i/r365s246yqhhg3tcrr0u.png)

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](https://thepracticaldev.s3.amazonaws.com/i/mms8hrt7u8rwe6th053d.png)
*Appwork starter*

![Appwork demo](https://thepracticaldev.s3.amazonaws.com/i/kagt5uch9yd5bze4am48.png)
*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](https://thepracticaldev.s3.amazonaws.com/i/8016h5zpkla9ba5a25l8.png)

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](https://thepracticaldev.s3.amazonaws.com/i/jjooje7wy5gojkqi6rbk.png)

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: <b-list-group> - 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](https://thepracticaldev.s3.amazonaws.com/i/kgyy4yg1sozmly2evjxq.png)

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`:

```javascript
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:

```javascript
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`

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

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

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/uxafxo6l8w7zrijhmeji.png)


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.

```javascript
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](https://thepracticaldev.s3.amazonaws.com/i/e3404bac1p04n4ye5sba.png)
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/2sx18xr41eh78pfku9pj.png)
![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/lvl9yj70h29xkjl4ed38.png)

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](https://thepracticaldev.s3.amazonaws.com/i/f8e8n9huwz2w8x6ock2i.png)

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!