Permalink
Fetching contributors…
Cannot retrieve contributors at this time
409 lines (317 sloc) 17.6 KB

The article-browser-widget

In this step we are going to implement the article-browser-widget which displays a list of articles to the user and allows to select an individual article. To obtain the articles in the first place, we also create an activity. This part of the tutorial will allow you to learn how widgets safely share resources through the event bus, and how resources can be displayed to the user.

Creating an Activity

Our first task is to create a dummy-articles-activity that has one job: publish a resource that contains a list of articles. Each article in the list has a title, a price and an HTML description, plus a URL to a teaser image.

Because activities have no visual appearance, all we need to do is to create the activity using yo laxarjs:activity, allow configuration of an event bus topic, and implement the activity controller.

Create the activity using the Yeoman generator, again selecting the integration technology plain:

yo laxarjs2:activity dummy-articles-activity

For activities, you should generally use "plain" as they do not need fancy UI support in the first place. You will learn about another integration technology ("vue") in the next section. First, let us add the feature configuration option to the activity's descriptor:

"articles": {
   "required": [ "resource" ],
   "type": "object",
   "properties": {
      "resource": {
         "type": "string",
         "format": "topic",
         "axRole": "outlet"
      }
   }
}

We just defined a configurable event bus topic for our activity, in this case the name of a resource. The activity will publish a list of available shopping articles under this topic. Using the page configuration, we will use the topic to connect widgets and activities by assigning shared event bus topics. The format "topic" is used to check if the page configuration uses a valid event bus topic (containing just letters, numbers and dashes). Finally, "axRole" is a schema-property defined by LaxarJS: It indicates that this activity publishes the configurable resource.

Now, you can implement the activity controller:

import { articles } from './articles';

export const injections = [ 'axEventBus', 'axFeatures' ];
export function create( eventBus, features ) {
   eventBus.subscribe( 'beginLifecycleRequest', () => {
      const { resource } = features.articles;
      eventBus.publish( `didReplace.${resource}`, { resource, data: articles } );
   } );
}

The structure of the controller is very similar to that of the headline-widget created in the previous step: First, a static list of articles is imported. In an actual web shop, you would probably make a fetch request to a REST API to get the articles from a database. Note that the articles listing references [several images]. To follow along, either download them individually, clone the demo repository, or use images of your own choice and modify the articles.js accordingly.

Then, you have the two exports (injections and create) like in the headline-widget. This time the activity requests an additional injection: the event bus, allowing us to communicate with the rest of the page. Also, this time no onDomAvailable callback is returned, because an activity has no HTML template.

Your First Event

Once created, the activity subscribes to the event beginLifecycleRequest. This event is published by LaxarJS itself, and indicates that all widget and activity controllers have been created and have set up their own event bus subscriptions. The callback to subscribe is invoked when the corresponding event has been received. We use it to publish an event of our own: the didReplace event, which announces a new version of a resource to all interested subscribers. To identify which resource we have replaced, we specify its name using the second topic of our event (the part after the dot). As payload of our event, we also provide the configured resource name, along with the data imported from the articles list.

If you would like to know more about events, there is a manual on events as well as the event bus API reference. The format of the didReplace event and its payload is not arbitrary, it follows the so-called resource pattern, allowing widgets to collaborate without directly depending on another. The LaxarJS patterns documentation has more information on these patterns.

Adding the Activity to the Page

Each page in LaxarJS supports a special area "activities" as a natural container for these invisible helpers. Simply add the following area definition as a sibling to the existing "content" area:

"activities": [
   {
      "widget": "dummy-articles-activity",
      "features": {
         "articles": {
            "resource": "articles"
         }
      }
   }
],

Now you a have dummy-article-activity, configured to publish articles over the event bus using the "articles" topic. Of course, you would like to see these articles on screen. So, let us create an article-browser-widget.

Creating the article-browser-widget

This is what the final article-browser-widget will look like:

article-browser-widget

It displays a heading, followed by a table containing the list of available articles. In the image, the sixth row represents the currently selected article.

Again, start by creating the widget using the generator:

yo laxarjs2:widget article-browser-widget

This time, make sure to pick "vue" as the integration technology. Using this technology mean that your widget is a Vue.js component (saved as article-browser-widget.vue) at the same time. Compared to a "plain" widget, this should make it much simpler to synchronize HTML DOM and data.

Going into a full-depth explanation of Vue.js would take things too far, especially considering that there is a comprehensive Guide on Vue.js anyway. Let us quickly cover the basics:

  • a Vue.js component consists of an HTML template, a JavaScript object and optional CSS styles - much like a LaxarJS widget,

  • the template may contain bindings to render data from the JS object, and event handlers to trigger methods of the object,

  • the object contains the "business logic", interpreting events from the template (or from the event bus) in order to change the data for the template,

  • LaxarJS connects your components to the event bus, provides various injections, and takes care of theming as needed.

At first, your component is empty:

<template>
</template>

<script>
</script>

Let us now look at the features of the article-browser-widget widget, and implement the component accordingly.

The Features of the article-browser-widget

The widget configuration supports two features: display a list of articles and allow selection of an article.

Displaying a List of Articles

For the first feature articles, we allow to configure the name of the resource containing the articles. Here is an appropriate JSON schema that you can add to the properties of you widget descriptor features:

"articles": {
   "type": "object",
   "description": "Display a list of articles.",
   "required": [ "resource" ],
   "properties": {
      "resource": {
         "type": "string",
         "description": "Name of the data resource with articles.",
         "format": "topic",
         "axRole": "inlet"
      }
   }
}

Note that this closely resembles the configuration used for the dummy-articles-activity, only that you are using the role inlet this time. This indicates that your activity subscribes to the configured resource.

Because the widget would be useless without a list of articles to render, we define the resource property as required in the feature schema. The implementation of the vue component starts out like this:

<template>
<div>
   <h3><i class="fa fa-gift"></i> Articles</h3>
   <table v-if="articles.length"
      class="table table-hover table-striped">
      <thead>
         <tr>
            <th>Art. ID</th>
            <th>Article</th>
            <th>Price</th>
         </tr>
      </thead>
      <tbody>
         <tr v-for="article in articles">
            <td>{{ article.id }}</td>
            <td>{{ article.name }}</td>
            <td>{{ article.price }}</td>
         </tr>
      </tbody>
   </table>
</div>
</template>

<script>
export default {
   data: () => ({ articles: [] }),
   created() {
      this.eventBus.subscribe( `didReplace.${this.features.articles.resource}`, event => {
         this.articles = event.data;
      } );
   }
};
</script>

The template contains a headline, using the Font Awesome icon fa-gift. Font Awesome is automatically available when using the LaxarJS default.theme. The same goes for the Bootstrap CSS classes used here, causing table rows to use alternating colors.

A table renders the articles from the component data, one row per article. The Vue.js directive v-if is used to show the header row only if there are any articles. Another directive v-for is used to loop over the list of available articles, and render a table row for each of them. Vue.js binding expressions (sometimes also called mustache expressions, because they are surrounded by {{ }}) are used to render the properties of each article. The surrounding div is required because Vue.js requires that component templates have only a single direct child element.

The controller object has a data method to initialize the properties available for binding in the template. It also has a created method (much like the create function exported by the plain activity). The created method subscribes to didReplace-events for the configured resource and updates the component articles accordingly, which is then automatically reflected by the template. Make sure not to confuse the data method of the Vue.js component (defined by the Vue.js component API) with the data property of the didReplace event (defined by the LaxarJS resource pattern).

Note that being a widget controller, this object automatically has access to certain additional properties provided by LaxarJS: Most prominently, there are the eventBus (corresponding to the axEventBus injection seen in the dummy-articles-activity), and features (corresponding to axFeatures). For full information on the "vue" integration technology, consult the Vue.js adapter documentation.

Allowing the User to Select an Article

Now we'll cover the second feature of the article-browser-widget, called selection. For this you'll need to add another feature configuration schema, so that the widget can publish the currently selected article under a configurable name:

"selection": {
   "type": "object",
   "description": "Select an article.",
   "required": [ "resource" ],
   "properties": {
      "resource": {
         "type": "string",
         "description": "Name of the resource for the user selection",
         "format": "topic",
         "axRole": "outlet"
      }
   }
}

This will allow other widgets (created over the next steps) to display details on the currently selected article, and to add it to the shopping cart. Let us modify the component as follows:

<template>
   <!-- ... headline, table, thead ... -->
   <tr v-for="article in articles"
      @click="selectArticle( article )"
      :class="{ selected: article.id === selectedArticle.id }">
      <!-- cells -->
   </tr>
   <!-- ... closing tags ... -->
</template>

<script>
export default {
   data: () => ({
      selectedArticle: { id: null },
      articles: []
   }),
   created() {
      this.eventBus.subscribe( `didReplace.${this.features.articles.resource}`, event => {
         this.articles = event.data;
         this.selectArticle( null );
      } );
   },
   methods: {
      selectArticle( article ) {
         this.selectedArticle = article || { id: null };
         const { resource } = this.features.selection;
         this.eventBus.publish( `didReplace.${resource}`, { resource, data: article } );
      }
   }
};
</script>

Now, the component has another data attribute, the current selectedArticle. It exposes a method selectArticle to allow changing the selection, which also publishes the article over the event bus.

The template uses a method binding (@click) to update the selection when the user clicks a row. It also uses an attribute binding (:class) to visually highlight the currently selected article. Both of these are provided by Vue.js, and not specific to using the component as a LaxarJS widget.

Adding the article-browser-widget to our Application

Finally, we need to add our new widget to the "content" area of the home page. To recapitulate, here is the entire page configuration, with the headline-widget from the previous step along with the dummy-articles-activity and the article-browser-widget:

{
   "layout": "one-column",

   "areas": {
      "activities": [
         {
            "widget": "dummy-articles-activity",
            "features": {
               "articles": {
                  "resource": "articles"
               }
            }
         }
      ],
      "content": [
         {
            "widget": "headline-widget",
            "features": {
               "headline": {
                  "htmlText": "LaxarJS ShopDemo"
               }
            }
         },
         {
            "widget": "article-browser-widget",
            "features": {
               "articles": {
                  "resource": "articles"
               },
               "selection": {
                  "resource": "selectedArticle"
               }
            }
         }
      ]
   }
}

Most importantly, the dummy-articles-activity and the article-browser-widget share the same event bus topic ("articles"). This effectively connects the two artifacts, without their implementations knowing about each other, and without need for additional glue code. You can see that we also changed the headline text to "LaxarJS ShopDemo", but feel free to pick a title of your own.

The event wiring diagram with dummy-article-activity and article-browser-widget

Styling the Widget using SCSS

By following these steps, you have created your first interactive widget. But compared to the screenshot of the widget above, it does not look quite right. The reason is that there is no CSS styling yet!

While you styled the headline-widget using plain old CSS, let us go for something more sophisticated with this widget by using SCSS instead. The SCSS language is a superset of CSS, meaning that all CSS code is also valid SCSS. In addition, SCSS defines useful additions such as $variables, and .nested { .selectors {} }. We recommend using SCSS rather than CSS for any non-trivial styling. Since the goal of this tutorial is teaching LaxarJS rather than CSS or SCSS, go ahead and grab the prepared SCSS file for now, saving its contents under default.theme/scss/article-browser-widget.scss.

As briefly mentioned above, you could alternatively have put the styles into a <style type="scss"> section of your .vue component definition. However, using a dedicated file enables you to swap out the styles by changing the theme, which is explained in the final step of this tutorial.

Because LaxarJS only looks for regular CSS files (not SCSS) by default, you need to add some information to the widget descriptor:

   "styleSource": "scss/article-browser-widget.scss",

Then install the sass-loader and its dependencies for webpack:

npm install --save-dev sass-loader node-sass

Finally, the webpack configuration needs to be extended to let webpack process all .scss files with libsass.

For this, the following element needs to be added to the module.rules array in the webpack.config.js:

{
   test: /[/]default[.]theme[/].*[.]s[ac]ss$/,
   loader: 'sass-loader',
   options: require( 'laxar-uikit/themes/default.theme/sass-options' )
}

Restart the development server using Ctrl-C followed by npm start in the project directory.

You can remove the original .css file of the widget as it is no longer needed. To make best use of the additional style definitions, you'll need to add more CSS classes to your widget template. Refer to the full component template for details.

When visited in the browser, your application should allow to select among ten articles now, and highlight the article that was selected by clicking.

The Next Step

The next step is to add the article-teaser-widget in order to display a detailed preview of the selected article.

« Hello, World! | The article-browser-widget | The article-teaser-widget »