Skip to main content Skip to footer

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

  1. 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
  })
}
  1. 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>
  1. 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>

  1. Remove unused "src/assets" folder and "src/components/HelloWorld.vue" file

  2. Run the sample application.

yarn serve

  1. Navigate to http://localhost:8080/ in the browser. View the Wijmo controls with tne default theme and locale.

Theming and Localization of Wijmo Controls in Vue Applications

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:

Theming and Localization of Wijmo Controls in Vue Applications

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:

  1. Resources automatically copied from GrapeCity modules to their specified subfolder of dist folder on every build
  2. Subfolder names aren't hard coded (can be easily changed in the future)
  3. The application works when deployed in non-root
  4. 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>
      &nbsp;
      <wj-combo-box
        :itemsSource="themes"
        :initialized="themeComboboxInitialized"
        :selectedIndexChanged="themeChanged"
      />
      <br />
      <br />
      <strong>Culture:</strong>
      &nbsp;
      <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:

Theming and Localization of Wijmo Controls in Vue Applications

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.

Dmitry Yurusov

comments powered by Disqus