Replacing JHipster UI components (Part 2. Adding SideNavbar and customizing more components)
Antonio Ortiz Pola
Posted on September 19, 2019
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!
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 tothis
), awkward workarounds likestatic 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.
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')"> 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
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
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.
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!
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!
After admire our work for sometime, I want to compare how many parts did I touched to integrate my layout.
Checking the base files
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 randomjwtSecretKey
is generated each time you generate an application
Checking the source files we can see we did not touch many files
-
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 propertycurrentLanguage
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
After this simple changes, we can see JHipster UI is back!
Revert these changes to return to our Appwork layout
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.
Posted on September 19, 2019
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
September 19, 2019