DEV Community

Cover image for Replacing JHipster UI components (Part 2. Adding SideNavbar and customizing more components)
Antonio Ortiz Pola
Antonio Ortiz Pola

Posted on

Replacing JHipster UI components (Part 2. Adding SideNavbar and customizing more components)

In the last part, I changed the top navbar with the same functionality as the JHipster one, it is time to customize it a little to add our own flavor.

Customization for Navbar

First, I am going to use flag-icon-css to make the language bar more descriptive. As their docs says, I simply run

npm install flag-icon-css

After installation, I need to copy the svg files to my content folder, so the styles can be load correctly.

node_modules/flag-icon-css/sass/flag-icon.scss

to

app/bjt/sass/flag-icon.scss

But I need to override the variables file, so I can update the path where the svg files are, to keep things simple I will customize my new flag-icon.scss

// Override paths for svgs
$flag-icon-css-path: '/content/bjt/vendor/flags' !default;
$flag-icon-rect-path: '/4x3' !default;
$flag-icon-square-path: '/1x1' !default;

@import '~flag-icon-css/sass/flag-icon-base';
@import '~flag-icon-css/sass/flag-icon-list';

And then in my bjt/bjt-style.scss

@import 'sass/flag-icon.scss';

Now, just to run a test, I can add somewhere the styles

<span class="flag-icon flag-icon-gr"></span>
<span class="flag-icon flag-icon-gr flag-icon-squared"></span>

And now we have some flags!

Alt Text

But not everything could be that good, soon I hit a wall, a very difficult one.

Vue.js and TypeScript super are not much of a friends

This next part is based in some search and my little knowledge of Vue, if there is something wrong or if there is a better way please let me know!

While in angular I could take advantage of inheritance and calling super to execute code already placed in the JHipster parent classes, in Vue.js it is not the case, as Ashraful Islam says:

So far (VueJS v2) we don't have class based component like react. *.vue has it's own syntax rather than traditional javascript.

Investigating a little more, I realized that this is not only true, it is like this by design and I do not think this will change soon, as many will know this is an old discussion in vue.js, with highlights like:

My opinion is that ES6 classes offer no practical advantages over plain object definitions except for syntax preferences.

or

I don't think I'll ever change Vue's default API to use classes.

And most important:

People who "just want classes" probably never actually tried to envision what the equivalent of Vue's current API would look like using classes ...

... Well, I have tried, and my conclusion is using only specced features from ES2017, there's simply no way to provide something that is elegant enough. You end up with tedious constructor calls (and don't forget super, binding instance methods to this), awkward workarounds like static get option () { return { ... }} or attaching static properties after the class declaration block, etc. etc.

https://github.com/vuejs/vue/issues/2371#issuecomment-330068362

While I do not entirely agree with yyx990803 opinion, I totally understand his point of view

... the standard today is not good enough to provide a decent equivalent of Vue's current API

There has been a lot of debate about the best approach on how to program user interfaces (functional, OOP, mutability/immutability..), I preffer to be a little more verbose to be more clear, with "type safe" and static code checking. At the same time I found vue.js more easy to develop and less ceremonial than angular (declare components, use modules if they are going to be used in other components), so, I get the fact that it has not the same design in mind, and there are always trade offs.

So vue.js preffers this

export default {
  props: {
    propA: {
      type: Number
    },
    propB: {
      default: 'default value'
    },
    propC: {
      type: [String, Boolean]
    }
  }
}

to this

import { Vue, Component, Prop } from 'vue-property-decorator'

@Component
export default class YourComponent extends Vue {
  @Prop(Number) readonly propA: number | undefined
  @Prop({ default: 'default value' }) readonly propB!: string
  @Prop([String, Boolean]) readonly propC: string | boolean | undefined
}

The problem is that JHipster generates the components with classes for angular and react, so trying to fit the same approach with vue.js seems the more sane approach, there is even an official component so you can declare your components as classes, but this is only a partial solution, it takes some annotations and conventions and transforms it into VueJs components with their respective properties.

This causes that, even if you are thinking that you are using classes to program your components, they are not real classes, they are "parsed" to work with vue.js, and, since it does not have classes, it also does not have the notion of super.

I do not know what could be best for JHipster, to change the classes to VueJs components, or leave it as is, so far this is the only major issue I found. It seems to me that it could be a barrier to hold the full VueJs design philosophy, but on the other hand, I am very used to classes and in this particular case it helped me to understand and be productive more quickly, so I will leave the decision to the reader.

In my case, I have to remember my second rule

adapt the layout to JHipster, not the other way

So, I decided that for now, I will change the name of the methods that I need to override to add an underscore _ before, so I do not run into problems, sadly, each modified component means one change to the code to switch between my UI and JHipster's one, it will also mean one more possible conflict when I update my JHipster, but it stills seems an easy task for now.

Back to our show!

To finally include my flags for the language, I need a simple change to the JhiNavbar, make the language protected so I can access the property in my class, and add an underscore to the created method, so I can replace it.

Alt Text

Then, I just need to add some methods to my bjt/bjt-navbar

public flagLanguages = {
  es: 'mx',
  en: 'us'
};
public flag: string;

created() {
  this._created();
  this.flag = this.flagLanguages[this.currentLanguage];
}

And the language drop-down now can use this to be a little more visual

<b-nav-item-dropdown no-caret :right="!isRTL" v-if="languages && Object.keys(languages).length > 1">
    <template slot="button-content">
        <i class="navbar-icon align-middle flag-icon flag-icon-squared" :class="'flag-icon-' + flag"></i>
        <span class="d-lg-none align-middle" v-text="$t('global.menu.language')">&nbsp; Language</span>
    </template>

    <b-dd-item v-for="(value, key) in languages" :key="`lang-${key}`" v-on:click="bjtChangeLanguage(key)"
                     :class="{ active: isActiveLanguage(key)}">
        <i class="flag-icon flag-icon-squared mr-1" :class="'flag-icon-' + flagLanguages[key]"></i>
        {{value.name}}
    </b-dd-item>
</b-nav-item-dropdown>

And now our language bar looks better

Alt Text

And as a last touch, lets make the username accessible also

public get username(): string {
  return this.$store.getters.account ? this.$store.getters.account.firstName : '';
}

And this is starting to look better

Alt Text

Implementing a SideNavBar in JHipster

From now on, we should be able to move a little easier, since we have resolved most of the common issues.

Checking the Appwork demo, the nice page uses the layout2, so I modify the bjt/App.vue file accordingly

<template>
    <div class="layout-wrapper layout-2" id="app">
        <ribbon></ribbon>
        <div class="layout-inner">
            <bjt-sidebar />

            <div class="layout-container">
                <bjt-navbar />

                <div class="layout-content">
                    <div class="router-transitions container-fluid flex-grow-1 container-p-y">
                        <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>
        </div>
        <div class="layout-overlay" @click="closeSidenav"></div>
    </div>
</template>

Then I create my bjt-sidebar.component.ts file based on the LayoutSidenav.vue component from Appwork.

Alt Text

As you can notice, this is nothing more than a parse to a class of the component. I also created the bjt-sidebar.component.ts with all the view code. And now we are almost done for our base project!

Alt Text

Then I repeat the procedure with the footer component, creating new i18n files if necessary for translation. After replacing the menus with the entities menu, you can say we have a JHipster App base with Appwork layout working!

Alt Text

Alt Text

After admire our work for sometime, I want to compare how many parts did I touched to integrate my layout.

Checking the base files

Alt Text

We have just 5 distinct files from one JHipster untouched project

  • webpack/webpack.dev.js: Just modified one line to load our styles instead the ones from JHipster
  • webpack/webpack.prod.js: Same as before
  • package.json: We installed some new libraries and updated one, this file is expected to change a lot when the system grows
  • .yo-rc.json: This file is only changed because a random jwtSecretKey is generated each time you generate an application

Checking the source files we can see we did not touch many files

Alt Text

  • bjt Folder is where all my custom code resides
  • i18n has some new files with my translations
  • core/jhi-navbar.component required two changes, make the property currentLanguage protected instead of private, and an underscore to avoid conflicts when overriding a method in the child class
  • main.ts has 3 changes, the import swap between the JHipster App and mine, the initialization of my custom components and a JHipster style commented to avoid conflicts with my styles
  • index.html Just some minor changes in head based on the index from Appwork

So, if we want to check our JHipster app, we can make this changes:

1.- Change webpack/webpack.dev.js and webpack/webpack.prod.js so it loads the JHipster styles

  entry: {
    // Change import for JHipster or BJT UX
    global: './src/main/webapp/content/scss/global.scss',
    // global: './src/main/webapp/app/bjt/style.scss',
    main: './src/main/webapp/app/main'
  }

2.- Change imports for App class in main.ts

// Change imports for JHipster or BJT UX
import App from './app.vue';
// import App from './bjt/App.vue';

3.- Uncomment vendor styles in main.ts

// Comment to activate BJT UX
import '../content/scss/vendor.scss';

4.- Change jhi-navbar.component.ts so it uses the created method instead the modified one _created

  // change lines to change between JHipster and BJT UX
  created() {
  // _created(): void {
    this.translationService().refreshTranslation(this.currentLanguage);
  }

As you can see, I added comments so it is easy to find all the parts to change with a search

Alt Text

After this simple changes, we can see JHipster UI is back!

Alt Text

Revert these changes to return to our Appwork layout

Alt Text

Ok, so now we have an easy to update base, almost completely separated from JHipster, we can switch between implementations relatively easy.

In the last part I am going to customize my entities, so they can be more than a simple crud and we can see some examples of side by side in the server side.

Top comments (0)