View source | Discuss this page | Page history | Printable version   

Retail:Developers Guide/How-to/Update To WebPOS New Skin

Contents

Introduction

The following guide is intended to provide information and examples of changes that should be applied to the JS and CSS code in order to continue displaying properly the UI after upgrading from 19Q4 or lower versions.

Remove styling declared in JS

As a previous step required to make your custom module skinnable, all the styling declared in the JS (or inline CSS) should be moved to CSS files. In order to do that, three steps should be followed:

Inline styles have maximum specificity value, are difficult to override and are not reusable. Due to that, they are not allowed anymore. HowToUpgradeCSS inline.png

BEM

New CSS classes should be named using BEM CamelCase approach.

Example:

HowToUpgradeCSS BEM example.png

In order to provide the specific CSS class names from the code moved from JS, the following rules must be followed:

Example:

HowToUpgradeCSS BEM in real js.png

PS: All new components must include a name. Components without name ARE NOT ALLOWED.

CSS files structure

Additionaly, some improvements can be made inside the CSS file structure: File & Folder header

/***********************   ***********************/
/*                 File: [...]                   */
/*               Folder: [...]                   */
/***********************   ***********************/

Component information

/*
# Component: [...]
# Inherits: [...]
*/

CSS class inheritance

/* Inherits: [...] */

Example:

/***********************   ***********************/
/*          File: ob-terminal-component.js       */
/*             Folder: source/component/         */
/***********************   ***********************/
 
/*
# Component: OB.UI.Terminal
*/
.obUiTerminal {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
}
 
.obUiTerminal-headerContainer {
  height: 0px;
}
 
.obUiTerminal-mainContainer-containerLoading {
/* Inherits: [OB.UI.LoadingScrim] */
}

Get rid of "floats" of your CSS code

While doing the code migration from JS to CSS, some code enhacements could be also made: Replace CSS floats by FlexBox or CSS Grid:

Explore css utils ofered by mobile core

Our main css file obmobc-main.css, which is located in mobile core offers many utils which can be reused simply adding them to components as css classes. Explore that file to find all of them. Below are listed a short selection of utils

 
.u-clearBoth {
  clear: both;
}
.u-hiddeComponent {
  visibility: hidden;
}
.u-showComponent {
  visibility: visible;
}
.u-displayNone {
  display: none;
}
.u-displayBlock {
  display: block;
}
.u-displayInlineFlex {
  display: inline-flex;
}

Use custom properties

Custom properties are a kind of cssConstants which can be overwriten by other skins. If your components uses this properties instead of "inline" values, your component will changed without any effort when a new skin is installed. Custom properties are defined in mobile.core module (obmobc-main.css). It is highly recommended to make use of them. Below are listed a short selection of utils.

 
  /*Definition*/
  --color-primary: #4d545c;
  --color-on-primary: #e8e8e8;
  --color-secondary: #6cb33f;
  --color-on-secondary: #ffffff;
 
  /*Usage*/ 
.obUiFormElementIntegerEditor-btnQtyMinus {
  /*Inherits: [OB.UI.Button]*/
  margin: 0px;
  width: 50px;
  height: 50px;
  font-size: var(--font-xxlarge);
  background-color: var(--color-primary);
  color: var(--color-on-primary);
}

If your project has a stylesheet you can create your own custom properties.

Adapt JS+CSS to use new components

Prior to 20Q1 there were no official CSS API, so custom modules could contain wide variety of JS+CSS code because at the end the developer built it following his own criteria. Because of this, it is difficult to write a detailed guide like "all 'A' components should be replaced by 'Z' components" could not be provided, because 'A' could be 'A' or 'B', 'C', 'D' or whatever kind of structure the developer could have built. The best way to determine which components need to be updated is by performing a visual inspection after the upgrade. All the new components you can reuse or inherit from are detailed in this link:

Some real examples of JS+CSS code migration will be provided below to help you in the upgrade of your custom code.

OB.UI.FormElement

New 'OB.UI.FormElement' component has been built, integrating in the same component the label, the icon (optional) and the component itself (input, select, checkbox, selector, ...).

Some migration examples:

From:

{
  kind: 'enyo.Input',
  type: 'text',
  name: 'username',
  classes: 'obObposLoginUiLogin-loginInputs-container2-username'
}
...
this.$.username.attributes.placeholder = OB.I18N.getLabel(
  'OBMOBC_LoginUserInput'
);

to:

{
  kind: 'OB.UI.FormElement',
  name: 'formElementUsername',
  classes:
    'obUiFormElement_dataEntry obObposLoginUiLogin-loginInputs-formElementUsername',
  coreElement: {
    kind: 'OB.UI.FormElement.Input',
    type: 'text',
    name: 'username',
    i18nLabel: 'OBMOBC_LoginUserInput',
    classes: 'obObposLoginUiLogin-loginInputs-formElementUsername-username'
  }
}

From:

{
  classes: 'obUiSearchProductCharacteristicHeader-container1-container3-container2',
  components: [
    {
      kind: 'OB.UI.CheckboxButton',
      name: 'crossStoreSearch',
      classes: 'obUiSearchProductCharacteristicHeader-container1-container3-container2-crossStoreSearch',
      i18nLabel: 'OBMOBC_CrossStoreSearch',
      tap: function() {
        if (this.checked) {
          this.unCheck();
        } else {
          this.check();
        }
        var searchProduct = this.owner.owner.$.searchProductCharacteristicHeader;
        searchProduct.categories.reset();
        searchProduct.loadCategories(null);
        return this;
      }
    }
  ]
}

to:

{
  kind: 'OB.UI.FormElement',
  name: 'formElementCrossStoreSearch',
  classes: 'obUiFormElement_dataEntry obUiSearchProductCharacteristicHeader-container1-container3-container2',
  newAttribute: {
    kind: 'OB.UI.FormElement.Checkbox',
    name: 'crossStoreSearch',
    classes: 'obUiSearchProductCharacteristicHeader-container1-container3-container2-crossStoreSearch',
    i18nLabel: 'OBMOBC_CrossStoreSearch',
    tap: function() {
      this.setChecked(!this.getChecked());
      var searchProduct = this.formElement.owner.owner.$.searchProductCharacteristicHeader;
      searchProduct.categories.reset();
      searchProduct.loadCategories(null);
      return this.formElement;
    }
  }
}

From:

{
  enyo.kind({
    kind: 'OB.UI.SmallButton',
    name: 'OB.UI.Customer',
    classes: 'obUiCustomer customerShipBill-obUiSmallButton-generic',
...
    renderCustomer: function(newCustomer) {
      this.setContent(newCustomer);
    },
    orderChanged: function(oldValue) {
      if (this.order.get('bp')) {
        this.renderCustomer(this.order.get('bp').get('_identifier'));
      } else {
        this.renderCustomer('');
      }
      this.order.on(
        'change:bp',
        function(model) {
          if (model.get('bp')) {
            this.renderCustomer(model.get('bp').get('_identifier'));
          } else {
            this.renderCustomer('');
          }
        },
        this
...
      )
    }
  });
}

to:

{
  enyo.kind({
    kind: 'OB.UI.FormElement.Selector',
    name: 'OB.UI.Customer',
    classes: 'obUiCustomer',
...
    renderCustomer: function(newCustomerId, newCustomerName) {
      this.setValue(newCustomerId, newCustomerName);
    },
    orderChanged: function(oldValue) {
      if (this.order.get('bp')) {
        this.renderCustomer(this.order.get('bp').get('id'), this.order.get('bp').get('_identifier')
        );
      } else {
        this.renderCustomer(null, '');
      }
      this.order.on(
        'change:bp',
        function(model) {
          if (model.get('bp')) {
            this.renderCustomer(this.order.get('bp').get('id'), this.order.get('bp').get('_identifier')
            );
          } else {
            this.renderCustomer(null, '');
          }
        },
        this
...
      )
    }
  });
}

OB.UI.Button

New 'OB.UI.Button' is an improved button class that allows render buttons having (optionally) both image and label, while at the same time has some rich skinnability capabilities. Most of the application buttons (OB.UI.ToolbarButton, OB.UI.ToolbarButtonTab, OB.UI.ActionButton, OB.UI.ModalDialogButton, ...) inherit from it.

Previously created buttons may continue working, although the styling will not match with the new skin. In order to adapt it the following properties (if exist) should be removed:

If there are custom classes defined, thouse ones would need to be adapted too. Here there is an example of a button with custom styling (icon on the left and label on the right):

Javascript:

{{
  enyo.kind({
    name: 'OB.UI.CustomButton',
    kind: 'OB.UI.Button',
    classes: 'obUiCustomButton',
...
  });
}

CSS:

.obUiCustomButton {
  font-weight: normal;
  font-size: var(--font-medium);
  text-decoration: none;
  height: 50px;
  text-align: left;
  color: var(--color-on-primary);
  background-color: var(--color-primary);
  border: none;
  text-overflow: clip;
  overflow: hidden;
  padding: 0px;
  margin: 0px;
}
 
.obUiCustomButton .obUiButton-components {
  grid-template-columns: 30px 1fr;
 
  margin: 0px;
  padding: 0px 10px;
}
 
.obUiCustomButton .obUiButton-components-icon {
  background-position: center center;
  background-repeat: no-repeat;
  filter: var(--color-on-primary_img);
  width: 100%;
  height: 100%;
}
 
.obUiCustomButton .obUiButton-components-label {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  word-break: break-word;
  overflow: hidden;
  text-overflow: ellipsis;
  color: var(--color-on-primary);
  padding: 0px 5px 0px 5px;
}
 
/* hover state */
.obUiCustomButton:not(.disabled):hover {
  background-color: var(--color-primary_hover);
}
 
.obUiCustomButton:not(.disabled):hover .obUiButton-components-icon {
  filter: var(--color-on-primary_hover_img);
  background-image: url('./../../org.openbravo.mobile.core/assets/img/iconActButtonDeleteLine.svg');
  background-size: auto 16px;
}
 
.obUiCustomButton:not(.disabled):hover .obUiButton-components-label {
  color: var(--color-on-primary_hover);
}
 
.obUiCustomButton .obUiButton-components:after {
  content: '';
  position: absolute;
  border-right: 0.5px solid var(--color-on-primary_disabled);
  height: calc(100% - 26px);
  width: calc(100% - 0.5px);
}
 
/* active state */
.obUiCustomButton:not(.disabled):active,
.obUiCustomButton.selected {
  background-color: var(--color-primary_active);
}
 
.obUiCustomButton:not(.disabled):active .obUiButton-components-icon,
.obUiCustomButton.selected .obUiButton-components-icon {
  filter: var(--color-on-primary_active_img);
}
 
.obUiCustomButton:not(.disabled):active .obUiButton-components-label,
.obUiCustomButton.selected .obUiButton-components-label {
  color: var(--color-on-primary_active);
}
 
.obUiCustomButton.selected .obUiButton-components-label:after {
  content: '';
  position: absolute;
  left: 20%;
  top: 81%;
  width: 60%;
  height: 6px;
  background-color: var(--color-secondary);
}
 
/* focus state */
.obUiCustomButton:not(:active):not(.disabled):focus {
  box-shadow: inset 0px 0 2px 1px var(--color-primary_focus);
  border-color: var(--color-primary_focus);
  border-style: solid;
}
 
.obUiCustomButton:not(:active):not(.disabled):focus .obUiButton-components-icon {
  filter: var(--color-on-primary_focus_img);
}
 
.obUiCustomButton:not(:active):not(.disabled):focus .obUiButton-components-label {
  color: var(--color-on-primary_focus);
}
 
/* disabled state */
.obUiCustomButton.disabled {
  background-color: var(--color-primary_disabled);
}
 
.obUiCustomButton.disabled .obUiButton-components-icon {
  filter: var(--color-on-primary_disabled_img);
}
 
.obUiCustomButton.disabled .obUiButton-components-label {
  color: var(--color-on-primary_disabled);
}


OB.UI.Modal

Both previous 'OB.UI.Modal' and 'OB.UI.ModalAction' has been merged into new 'OB.UI.Modal'

Previous implementations had only header and body, while new one has header, body and footer. In the following examples it is shown how the internal component structure has changed:

From:

enyo.kind({
  name: 'OB.UI.Modal',
  kind: 'OB.UI.Popup',
  classes: 'obUiModal',
  components: [
    {
      tag: 'div',
      classes: 'obUiModal-header',
      components: [
        {
          name: 'closebutton',
          tag: 'div',
          classes: 'obUiModal-header-closebutton',
          components: [
            {
              tag: 'span',
              ontap: 'hide',
              allowHtml: true,
              classes: 'header-closebutton-element1',
              content: '×'
            }
          ]
        },
        {
          name: 'header',
          classes: 'obUiModal-header-header'
        }
      ]
    },
    {
      tag: 'div',
      name: 'body',
      classes: 'obUiModal-body'
    }
  ],
...
});
 
enyo.kind({
  name: 'OB.UI.ModalAction',
  kind: 'OB.UI.Popup',
  classes: 'obUiModalAction',
  bodyContentClass: 'obUiModalAction-body',
  bodyButtonsClass: 'obUiModalAction-button',
  components: [
    {
      classes: 'obUiModalAction-header',
      components: [
        {
          name: 'headerCloseButton',
          classes: 'obUiModalAction-header-headerCloseButton',
          components: [
            {
              tag: 'span',
              ontap: 'hide',
              allowHtml: true,
              content: '&times',
              classes: 'header-headerCloseButton-element1'
            }
          ]
        },
        {
          name: 'header',
          classes: '  obUiModalAction-header-header'
        }
      ]
    },
    {
      classes: 'modal-dialog-body obUiModalAction-bodyParent',
      name: 'bodyParent',
      components: [
        {
          name: 'bodyContent',
          classes: 'obUiModalAction-bodyParent-bodyContent'
        },
        {
          name: 'bodyButtons',
          classes: 'obUiModalAction-bodyParent-bodyButtons'
        }
      ]
    }
  ],
...
});

to:

enyo.kind({
  name: 'OB.UI.Modal',
  kind: 'OB.UI.Popup',
  classes: 'obUiModal',
  components: [
    {
      classes: 'obUiModal-header',
      components: [
        {
          name: 'header',
          classes: 'obUiModal-header-header'
        },
        {
          name: 'closebutton',
          classes: 'obUiModal-header-closebutton',
          components: [
            {
              kind: 'OB.UI.ModalCloseButton',
              classes: 'obUiModal-header-closebutton-obUiModalCloseButton'
            }
          ]
        }
      ]
    },
    {
      name: 'body',
      classes: 'obUiModal-body',
      showing: false
    },
    {
      name: 'footer',
      classes: 'obUiModal-footer',
      showing: false
    }
  ],
...
});

In order to maintain visual coherence with the rest of the application, all the primary and secondary buttons of the popup should be moved to the 'footer'.

Except for specific situations all popups build in Openbravo mobile platform should be build using "OB.UI.Modal". If specific requirements need to be covered in a fully customized popup OB.UI.Popup can be used.

FROM:

 
 
enyo.kind({
  name: 'examplePopup',
  kind: 'OB.UI.Modal',
  bodyContent: {
    kind: 'examplePopupBodyContentKind',
    name: 'examplepopup-body'
  },
  bodyButtons: {
    kind: 'examplePopupBodyButtonsKind',
    name: 'examplepopup-buttons'
  },

TO:

 
enyo.kind({
  name: 'examplePopup',
  kind: 'OB.UI.Modal',
  body: {
    kind: 'examplePopupBodyKind',
    name: 'examplepopup-body'
  },
  footer: {
    kind: 'examplePopupFooterKind',
    name: 'examplepopup-footer'
  },

Buttons which represents the most common action should be marked as a default action

New utility function available to be used by modals

OB.UTIL.getPopupFromComponent(componentToCheck) which returns the popup component which is parent of the provided component

From:

 
//go up to reach parent
let parentComponent = this.parent.parent;
//go down to reach certain component
parentComponent.$.body.$.qtyKeypadBody.getQty();

To:

 
//go up to reach parent
let parentComponent = OB.UTIL.getPopupFromComponent(this);
//go down to reach certain component
parentComponent.$.body.$.qtyKeypadBody.getQty();

OB.UI.ModalSelector

The component OB.UI.ModalSelector has been adapted to use new 'OB.UI.Modal' component

In order to migrate it, the previous points should be followed for each one of the components (inputs, buttons, modal popup structure, ...). At the end of the process, the structure should remain like this.

Transform OB.UI.WindowView with toolbar to new Skin

The API of this component has not changed, so it should not require any change.

Bulbgraph.png   Tip: The developer can add a specific css class to the window container, it will help to define specific css classes
 
OB.MobileApp.windowRegistry.registerWindow({
  windowClass: 'OBAWO.Box.BoxProposalView',
+  containerCssClass: 'obawoBox',

The API of this component has not changed, so it should not require any change. It has been refactored to use css grid and by default, it generates a grid tempate-areas schema

 
  grid-template-areas:
    "obUiMultiColumn-leftToolbar obUiMultiColumn-rightToolbar"
    "obUiMultiColumn-leftPanel obUiMultiColumn-rightPanel";

if we want to implement the layout change when the screen changes from landscape to portrait then a media query should be used to modify the grid-template-area

 
  @media (max-aspect-ratio: 9/8) {
    .obawoBox .obUiMultiColumn-panels {
    grid-template-areas:
        'obUiMultiColumn-rightToolbar'
        'obUiMultiColumn-rightPanel';
    grid-template-columns: 100%;
    grid-template-rows: 72px calc(100% - 72px);
    }
  }

Same strategy will be used to switch between right and left side. In this case, when just left side is shown we will show the right toolbar on the top and below the left toolbar

 
@media (max-aspect-ratio: 9/8) {
.obawoBox .obUiMultiColumn-panels.showLeft {
    grid-template-areas:
        'obUiMultiColumn-rightToolbar'
        'obUiMultiColumn-leftToolbar'
        'obUiMultiColumn-leftPanel';
    grid-template-columns: 100%;
    grid-template-rows: 72px calc(100% - 72px);
}
}

It is required to define a grid template areas for the toolbar. When the toolbar is created if flag “ShowMenu” is enabled, then the button to open the main menu will be shown in the area called “obUiMainMenu”. Because of that, our grid-template-area should include an area with this name. Below component is defining the layout of a right toolbar with 2 small buttons and a container to show some info. (menu, back and information). Using the class defined above for my window (obawoBox) I'm creating a new css rule to define the structure desired for my toolbar.

 
.obawoBox .obUiMultiColumnToolbar-standardToolbar-toolbar {
  grid-template-areas: "obUiMainMenu back userinfo";
  grid-template-columns: 1fr 1fr 5fr;
  align-items: center;
}

Retrieved from "http://wiki.openbravo.com/wiki/Retail:Developers_Guide/How-to/Update_To_WebPOS_New_Skin"

This page has been accessed 2,544 times. This page was last modified on 14 October 2020, at 12:35. Content is available under Creative Commons Attribution-ShareAlike 2.5 Spain License.