Adding vendors

What you will learn

In this lesson, you will learn what vendors are. You will implement a VodService in a CustomBE vendor and create your custom models to retrieve contents.

Vendors overview

Vendors are used to implement framework abstraction to real use cases. For example, Dana exposes abstract services linked to TV domain’s functionalities needs like VodService, PvrService, SystemInfoService… In real use case, those services will probably need a different back-end or middleware / device connection according to projects or according to deployement countries of the same project. Thanks to abstraction at Dana level, your project will be able to use the same interfaces. Then, to add implementation, you will use vendors.

Note

It is common to use vendors for external dependencies like back-end or middleware.

When you created this project with create-dana-app, passed option --tutorial created a vendor for this tutorial. A folder named vendors has been created. In this folder you can create as many vendors as you need. Here, the one already is named customBE. The name is defined in the package.json, inside the customBE folder. In scripts/services, you will find the CustomBEVodService which is an implementation of AbstractVodService.

Caution

Take a look to CustomBEVodService JSDOC. There is an annotation @implementation above the class declaration. This is mandatory in Dana to declare implementations.

An abstract defines the public APIs, you should consider implementations are offering same public APIs. When defining an abstract, Dana will also associate an alias. As a result an AbstractXxxService, with an implementation MyXxxService, will be used with the alias XxxService. During profile resolution, alias will be resolved with the correct implementation or abstraction.

Retrieve data from a service

In the CatalogScreen, you are going to retrieve a catalog through the VodService. To begin, log the results to understand how it works.

Caution

Things to know :

  • services are singletons, you should not instanciate them,
  • to use services always use their alias not the final class name,
  • screens are responsible for retrieving data in prepareData methods,
  • screens are responsible for setting data in connectData methods,
  • results of prepareData is stored in the Screen’s property data

To use a service, declare it as a property of your class. Then, call getCatalog method, and log the result.

Caution

Keyword properties allow you to declare properties on the instance of your class. Properties can be classic JavaScript types (string, number, boolean…) but can also be Dana’s object. In that case use class keyword to indicate the class to use.

import $VodService from "@VodService";

/**
 * Catalog screen
 *
 * @name CatalogScreen
 * @class
 * @extends AppScreen
 * @screen
 * @property {VodService} vodService Instance of $VodService
 */
export default $AppScreen.declare("CatalogScreen", {
    properties: {
        vodService: {class: $VodService}
    },
    methods: {
        prepareData: function (context) {
            return this.vodService.getCatalogs();
        },
        connectData: function (context) {
            console.log(this.data);
        }
    }
});

Observe your debug console, an error as been thrown : You must implement AbstractVodService._getCatalogs. As project is not using any implementation of VodService, it is using abstraction. Looking at CustomBEVodService, you can see that there is an implementation of multiple protected methods including _getCatalogs. To use this implementation instead of abstraction, you should add this vendor to your profile.

{
    "base-lightning": {
        "base": {
            "name": "Lightning HTML5",
            "vendors": [
                "@dana/renderer-lightning-html5",
                "customBE"
            ]
        }
    },
    "base-css": {
        "base": {
            "name": "Css",
            "vendors": [
                "@dana/renderer-css",
                "customBE"
            ]
        }
    }
}

Caution

Declaration order of the vendors have importance. Implementations choices are based on this order, resulting last implementation override to be the choosen one.

Do not forget to launch back your profile to see expected result in the debug console :

> CatalogList
    > 0 : AbstractCatalog
    > 1 : AbstractCatalog

Retrieve custom contents

CustomBE is already implementing some of the methods you need in VodService. You are going to implement one of them: _getContents. This one should retrieve contents for a category, here movies. A JSON file is available to give you contents to parse. You need to transform those raw datas into Content. Using models is important to guarantee that all your implementations will return same structure of information.

Import JSON file and Content :

import contents from "contents.json!";
import $Content from "@Content";

Note

! is used to import JSON files. You do not need to write path to file, juste write its name.

Implement _getContents method :

_getContents: function (categoryId) {
    const result = contents.movies[categoryId].map(element => new $Content({data: element}));

    return Promise.resolve(result);
}

In CatalogScreen, update prepareData to call getContents with a category id part of the JSON. For example :

prepareData: function (context) {
    return this.vodService.getContents("forYou");
}

Observe in the debugger console what you retrieved :

> ContentList
    > AbstractContent
    > AbstractContent
    ...

As for the service, you can see there is no implementation for Content so abstract is used. If you take a look at properties of AbstractContent, you can observe title or description are null. To populate them based on information coming from the JSON, you need to create an implementation of Content. This one will do the mapping between keys in the JSON and keys on the model.

Create a new file CustomBEContent under vendors > customBE > scripts > services > model.

import $AbstractContent from "@AbstractContent";

/**
 *
 * @name CustomBEContent
 * @model
 * @extends AbstractContent
 * @implementation
 *
 * @property {number} id
 * @property {string} title
 * @property {string} description
 * @property {array} genres
 */

export default $AbstractContent.declare("CustomBEContent", {
    properties: /** @lends CustomBEContent.prototype */ {
        id: ({data}) => data.identifier,
        title: ({data}) => data.name,
        description: ({data}) => data.summary,
        genres: ({data}) => data.types
    }
});

Note

Properties in Model are using lazy loading. Unless you tried to access to property, its value is not calculated. Once binding function has been executed once, its value is stored for next access.

Observe the new logs in your debug console. You now have CustomBEContent instead of AbstractBEContent, and properties are well populated.

Summary

To summarize, during this lesson you have :

  • added a new vendor to your profiles,
  • created a custom model,
  • implemented a method of a service using your custom model

To go further on those concepts, you can browse the following documentation pages :

  • Vendor // TODO add vendors link
  • Property // TODO add property link