Theming and Localization of Wijmo Controls in Vue Applications
Vue is a JavaScript application framework similar to Angular and React, but considerably lighter. Despite its tiny footprint, Vue is a powerful and flexible framework. Wijmo's library of JavaScript UI Components is also compact, robust, and flexible. The two libraries are a great match.
Wijmo features over 40 locales, referred to as cultures, and 25 different built-in themes. By default, Wijmo formats and parses data using the American English culture. If your application targets other cultures, include references to the appropriate Wijmo culture files.
Below we'll create a sample Vue application with Wijmo controls. Then we will outline how to change Wijmo themes and cultures in a Vue application using both static and dynamic methods.
Creating a Sample Vue Application with Wijmo Controls
Use Vue CLI to create a project with default settings:
vue create wijmo-sample
cd wijmo-sample
Details about creating a Vue CLI application creation can be found here.
Add Wijmo to the project:
yarn add @grapecity/wijmo.vue2.all
How to Modify the Vue CLI Project
- Create the file "src/data.js" which forms random sample data for Wijmo controls:
export const countries = [
'US',
'Germany',
'UK',
'Japan',
'Italy',
'Greece',
];
export const data = [];
for (let i = 0; i < countries.length; i++) {
data.push({
country: countries[i],
downloads: Math.round(Math.random() * 20000),
sales: Math.random() * 10000,
expenses: Math.random() * 5000
})
}
- Create the component "src/components/WijmoSample.vue" file which contains several Wijmo controls with random sample data:
<template>
<div class="wijmo-sample">
<h1>Wijmo controls sample</h1>
<p>
<strong>ComboBox:</strong><br />
<wj-combo-box :itemsSource="countries" />
</p>
<p>
<strong>InputNumber:</strong><br />
<wj-input-number :value="1234.5678" :step="1" />
</p>
<p>
<strong>InputDate:</strong><br />
<wj-input-date />
</p>
<p>
<strong>Calendar:</strong><br />
<wj-calendar />
</p>
<p>
<strong>FlexGrid:</strong><br />
<wj-flex-grid :itemsSource="data">
<wj-flex-grid-column binding="country" header="Country" />
<wj-flex-grid-column binding="downloads" header="Downloads" />
<wj-flex-grid-column binding="sales" header="Sales" format="c0" />
<wj-flex-grid-column binding="expenses" header="Expenses" format="c0"
/>
</wj-flex-grid>
</p>
</div><
</template>
<script>
import * as dataSource from '../data';
import "@grapecity/wijmo.styles/wijmo.css";
import "@grapecity/wijmo.vue2.input";
import "@grapecity/wijmo.vue2.grid";
export default {
name: 'WijmoSample',
data: function () {
return {
data: dataSource.data,
countries: dataSource.countries,
};
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.wijmo-sample {
width: 50em;
margin: 3em auto;
}
.wj-calendar {
width: 25em;
}
</style>
- Modify the standard Vue template (src/App.vue) file by replacing the unnecessary component with the previously created WijmoSample component:
<template>
<div id="app">
<WijmoSample />
</div>
</template>
<script>
import WijmoSample from './components/WijmoSample.vue'
export default {
name: 'App',
components: {
WijmoSample,
}
}
</script>
-
Remove unused "src/assets" folder and "src/components/HelloWorld.vue" file
-
Run the sample application.
yarn serve
- Navigate to http://localhost:8080/ in the browser. View the Wijmo controls with tne default theme and locale.
Use this sample application as a base for the demonstration of static and dynamic methods.
Using the Static Method in Vue Applications
Import the required css or js files to statically use a built-in theme or locale.
Built-in themes are located in themes folder of @grapecity/wijmo.styles module.
- Apply a built-in theme: modify src/components/WijmoSample.vue file by replacing import of default them by importe of another buil-in them. For example, to apply organic theme:
// replace this line // import "@grapecity/wijmo.styles/wijmo.css"; // by import "@grapecity/wijmo.styles/themes/wijmo.theme.organic.css";
Built-in locales are located in @grapecity/wijmo.cultures
module.
- Apply built-in locale: import the appropriate js-file after loading Wijmo. For example, to load the Japanese locale add the next line as the last line among imports:
// import Japanese locale
import "@grapecity/wijmo.cultures/wijmo.culture.ja";
As a result <script> import"
part of _src/components/WijmoSample.vue
file looks like:
<script>
import * as dataSource from '../data';
import "@grapecity/wijmo.styles/themes/wijmo.theme.organic.css";
import "@grapecity/wijmo.vue2.input";
import "@grapecity/wijmo.vue2.grid";
import "@grapecity/wijmo.cultures/wijmo.culture.ja";
export default {
name: 'WijmoSample',
data: function () {
return {
data: dataSource.data,
countries: dataSource.countries,
};
}
}
</script>
Here's what the application should look:
Using the Dynamic Method in Vue Applications
Dynamic theming and localization require:
- Publishing resources (themes and locales)
- Loading required ones on the fly (for example, on changing of theme or locale selectors)
Resource Publication
The most efficient resource publication method is webpack configuration of Vue project. We configure webpack to achieve next objectives:
- Resources automatically copied from GrapeCity modules to their specified subfolder of dist folder on every build
- Subfolder names aren't hard coded (can be easily changed in the future)
- The application works when deployed in non-root
- List of available themes and locales dynamically formed in accordance with the content of appropriate folders
To achieve these objectives, we create vue.config.js file in the root of the project with the content:
const path = require('path');
const glob = require('glob');
// public folder name for wijmo themes styles
const wijmoThemesPublicFolder = 'themes';
// public folder name for wijmo cultures
const wijmoCulturesPublicFolder = 'cultures';
// resources source folders
const wijmoThemesSrcFolder = path.resolve('./node_modules/@grapecity/wijmo.styles/themes');
const wijmoCulturesSrcFolder = path.resolve('./node_modules/@grapecity/wijmo.cultures');
// list of available themes
const themes = glob
.sync('wijmo.theme.*.css', { cwd: wijmoThemesSrcFolder })
.map(file => file.replace(/^wijmo\.theme\.(.+)\.css$/, '$1'))
.sort();
themes.unshift('default');
// list of available cultures
const cultures = glob
.sync('wijmo.culture.*.js', { cwd: wijmoCulturesSrcFolder })
.map(file => file.replace(/^wijmo\.culture\.(.+)\.js$/, '$1'))
.sort();
module.exports = {
chainWebpack: config => {
// configure DefinePlugin
config.plugin('define').tap(definitions => {
// define process.env variables to use in application at runtime
const env = definitions[0]['process.env'];
env.WIJMO_THEMES_PUBLIC_FOLDER = JSON.stringify(wijmoThemesPublicFolder);
env.WIJMO_CULTURES_PUBLIC_FOLDER = JSON.stringify(wijmoCulturesPublicFolder);
env.WIJMO_THEMES = JSON.stringify(themes);
env.WIJMO_CULTURES = JSON.stringify(cultures);
return definitions;
});
// configure CopyWebpackPlugin
config.plugin('copy').tap(args => {
// copy wijmo themes
args[0].push({
context: wijmoThemesSrcFolder,
from: '*.css',
to: path.resolve('./dist/' + wijmoThemesPublicFolder),
});
// copy wijmo cultures
args[0].push({
context: wijmoCulturesSrcFolder,
from: '*.js',
to: path.resolve('./dist/' + wijmoCulturesPublicFolder),
});
return args;
})
}
}
Dynamic Resource Loading
Dynamically add one of these elements (<link>
for css, <script>
for js) to HTML in the document head as follows:
- css-file (id attribute added to identify previously added element for removal):
<link type="text/css" rel="stylesheet" href="public-path-to-css-file" id="css-resourse-id" />
- Js-file:
<script type="text/javascript" src="public-path-to-js-file" id="js-resourse-id"></script>
Loading a theme or locale requires an extra step to re-render the Wijmo controls. Re-render by using the invalidateAll static method of the Control Wijmo base class. The invalidateAll method should be called in the onload event of the added <link>
or <script>
element.
To simplify the manipulation of the elements, we create a universal function that handles the removal of the previous element and adds the new one.
Use this function as the WijmoSample component method:
addResource: function (resourceId, resourceLocation, isCulture) {
// remove previously applied resource
let element = document.getElementById(resourceId);
if (element) {
element.parentNode.removeChild(element);
}
// add element
if (resourceLocation) {
let element = null;
const publicPath = process.env.BASE_URL; // publicPath of app (https://cli.vuejs.org/guide/html-and-static-assets.html#the-public-folder)
if (isCulture) { // script
element = document.createElement('script');
element.type = 'text/javascript';
element.src = publicPath + resourceLocation;<
} else { // styleseet
element = document.createElement('link');
element.type = 'text/css';
element.rel = 'stylesheet';
element.href = publicPath + resourceLocation;
}
element.id = resourceId;
element.onload = () => {
// refresh all controls on page
Control.invalidateAll();
};
document.head.appendChild(element);
}
}
We use "_process.env.BASE_URL"_ value for cases when deploying the application in a non-root folder of the webserver.
Now resources may be loaded by calling "this.addResource" method:
// load theme css
this.addResource('theme-element-id', 'public-path-to-theme-css');
// load locale js
this.addResource('js-element-id', 'public-path-to-locale-js', true);
Names of public folders of themes and locales resources are accessable in _process.env.WIJMO_THEMES_PUBLIC_FOLDER_ and _process.env.WIJMO_CULTURES_PUBLIC_FOLDER_ variables, respectively (in accordance with the earlier defined webpack configuration in vue.config.js file).
Finally, we add two Wijmo ComboBoxes from which we select a theme and locale. Take the items for selectors from the "process.env" variable (see "vue.config.js" file). Event handlers apply default values for theme and locale to each selector.
The resulting WijmoSample code ("src/components/WijmoSample.vue" file) should look like:
<template>
<div class="wijmo-sample">
<h1>Wijmo controls sample</h1>
<hr />
<h2>Settings</h2>
<p>
<strong>Theme:</strong>
<wj-combo-box
:itemsSource="themes"
:initialized="themeComboboxInitialized"
:selectedIndexChanged="themeChanged"
/>
<br />
<br />
<strong>Culture:</strong>
<wj-combo-box
:itemsSource="cultures"
:initialized="cultureComboboxInitialized"
:selectedIndexChanged="cultureChanged"
/>
</p>
<hr />
<p>
<strong>ComboBox:</strong><br />
<wj-combo-box :itemsSource="countries" />
</p>
<p>
<strong>InputNumber:</strong><br /><
<wj-input-number :value="1234.5678" :step="1" />
</p>
<p>
<strong>InputDate:</strong><br />
<wj-input-date />
</p>
<p>
<strong>Calendar:</strong><br />
<wj-calendar />
</p>
<p>
<strong>FlexGrid:</strong><br />
<wj-flex-grid :itemsSource="data">
<wj-flex-grid-column binding="country" header="Country" />
<wj-flex-grid-column binding="downloads" header="Downloads" />
<wj-flex-grid-column binding="sales" header="Sales" format="c0" />
<wj-flex-grid-column binding="expenses" header="Expenses" format="c0"
/>
</wj-flex-grid>
</p>
</div>
</template>
<script>
import * as dataSource from '../data';
import "@grapecity/wijmo.styles/wijmo.css";
import "@grapecity/wijmo.vue2.input";
import "@grapecity/wijmo.vue2.grid";
import { Control } from "@grapecity/wijmo";
export default {
name: 'WijmoSample',
data: function () {
return {
data: dataSource.data,
countries: dataSource.countries,
// defined in vue.config.js
themes: process.env.WIJMO_THEMES,
cultures: process.env.WIJMO_CULTURES,
// initial values
defaultTheme: 'default',
defaultCulture: 'en',
};
},
methods: {
themeComboboxInitialized: function (combobox) {
// apply default theme
combobox.selectedValue = this.defaultTheme;
this.themeChanged(combobox);
},
themeChanged: function (combobox) {
// load theme css<
const themeStyleId = 'wijmo-theme';
const themeLocation = process.env.WIJMO_THEMES_PUBLIC_FOLDER // wijmo cultures public path (defined in vue.config.js)
+ '/wijmo.theme.' + combobox.selectedValue + '.css';
this.addResource(themeStyleId, combobox.selectedIndex && themeLocation);<
},
cultureComboboxInitialized: function (combobox) {<
// apply default culture
combobox.selectedValue = this.defaultCulture;
this.cultureChanged(combobox);
},
cultureChanged: function (combobox) {
const scriptCultureId = 'wijmo-culture';
const cultureLocation = process.env.WIJMO_CULTURES_PUBLIC_FOLDER // wijmo cultures public path (defined in vue.config.js)
+ '/wijmo.culture.' + combobox.selectedValue + '.js';
this.addResource(scriptCultureId, cultureLocation, true);
},
addResource: function (resourceId, resourceLocation, isCulture) {
// remove previously applied resource<
let element = document.getElementById(resourceId);
if (element) {
element.parentNode.removeChild(element);
}
// add element
if (resourceLocation) {
let element = null;
const publicPath = process.env.BASE_URL; // publicPath of app (https://cli.vuejs.org/guide/html-and-static-assets.html#the-public-folder)
if (isCulture) { // script
element = document.createElement('script');
element.type = 'text/javascript';
element.src = publicPath + resourceLocation;<
} else { // styleseet
element = document.createElement('link');
element.type = 'text/css';
element.rel = 'stylesheet';
element.href = publicPath + resourceLocation;
}
element.id = resourceId;
element.onload = () => {
// refresh all controls on page
Control.invalidateAll();
};
document.head.appendChild(element);
}
},
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
.wijmo-sample {
width: 50em;<
margin: 3em auto;
}
.wj-calendar {
width: 25em;
}
</style>
The resulting application:
The application theme or locale changes with every change of the corresponding selector value.
Thank you for reading. Let us know how this information helps with your applications.
Read more about creating a Vue project.
Download the source code | Live demo
Theming and localization of a Vue application based on Wijmo components are both rather simple. There are two methods for applying of build-in themes and locales: static and dynamic.
A static method is straightforward and maybe applicable when the required theme and locale are both known in advance.
The dynamic method is more complicated but permits the creation of applications that can change its appearance and culture-dependent formatting on the fly.
Happy coding! If you have questions or comments be sure to enter them below.