• Keep up, Get ahead

    Join over 14,000 subscribers and 50,000 readers per month who get the latest updates and expert content from across the community.

Typescript design patterns for SharePoint Framework Part 3 – Builder

Builder pattern builds a complex object using simple objects and using a step by step approach. This type of design pattern comes under creational pattern as this pattern provides one of the best ways to create an object.

A Builder class builds the final object step by step. This builder is independent of other objects.

For this pattern, we have taken an existing example https://www.tutorialspoint.com/designpattern/builderpattern.htm and translated it to Typescript. Data Access implementation details are left to the reader.

The idea on this example is to show how you can build a Complex object from single objects, a Meal from (burger, fries, soda). Suppose you have a Sharepoint List for Burgers, another list for Sodas, another one for desserts, and you want to build different Meals (Menus), so this would be a perfect sample.

UML

This is more or less the diagram of the classes were are coding below.

Diagram of coding

Diagram of coding

Project structure

We have created a component with all the needed class, lets discuss them one by one.

Components

Components

IItem.ts

This interface is the one that every item needs to implement to come with a common structure for all products.

import IPacking from "./IPacking";

interface IItem {  
    name(): string;
    packing(): IPacking;
    price(): number;
}

export default IItem;  

Ipacking.ts

This interface is the one that all packaging will use, eg: Bottle, Wrapper, etc, its the way to define common behavior and properties for each product packing.

interface IPacking {  
    pack(): string;
}

export default IPacking;  

Bottle.ts

This is one type of packing, it implements the IPacking interface.

import IPacking from "./IPacking";

class Bottle implements IPacking {  
    public pack(): string {
       return "Bottle";
    }
}

export default Bottle;  

Wrapper.ts

import IPacking from "./IPacking";

class Wrapper implements IPacking {  
    public pack(): string {
       return "Wrapper";
    }
}

export default Wrapper;  

Burger.ts

This is an abstract class from which all our specific burgers need to implement, its there to have a common structure for name, packing and pricing.

import IItem from "./IItem";  
import Wrapper from "./Wrapper";  
import IPacking from "./IPacking";

abstract class Burger implements IItem {  
    public name(): string {
        throw new Error("Method not implemented.");
    }

    public packing(): IPacking {
        return new Wrapper();
    }

    public abstract price(): number ;

}

export default Burger;  

ChickenBurger.ts

import Burger from "./Burger";

class ChickenBurger extends Burger {  
    public price(): number {
        return 15;
    }

    public name(): string {
        return "Chicken Burger";
    }
}

export default ChickenBurger;

VegBurger.ts

import Burger from "./Burger";

class VegBurger extends Burger {  
    public price(): number {
        return 11;
    }

    public name(): string {
        return "Veg Burger";
    }
}

export default VegBurger;

Colddrink.ts

import IItem from "./IItem";  
import IPacking from "./IPacking";  
import Bottle from "./Bottle";

abstract class ColdDrink implements IItem {  
    public name(): string {
        throw new Error("Method not implemented.");
    }
    public packing(): IPacking {
        return new Bottle();
    }

    public abstract price(): number ;

}

export default ColdDrink;

Coke.ts

import ColdDrink from "./ColdDrink";

class Coke extends ColdDrink {  
    public price(): number {
       return 2.5;
    }

    public name(): string {
        return "Coca Cola";
    }
}

export default Coke;

Pepsi.ts

import ColdDrink from "./ColdDrink";

class Pepsi extends ColdDrink {  
    public price(): number {
       return 1.5;
    }

    public name(): string {
        return "Pepsi Cola";
    }
}

export default Pepsi;

Meal.ts

This class will represent a full meal behavior, here we have the methods to add items to the Meal, get the cost and show the items belonging to the Meal.

import IItem from "./IItem";

class Meal {  
    private items: IItem[];

    public addItem(item: IItem): void {
        this.items.push(item);
    }

    public getCost(): number {
        let cost: number  = 0;
        for(let item of this.items) {
            cost+= item.price();
        }

        return cost;
    }

    public showItems(): string {
        let returnStr: string;
        for(let item of this.items) {
            returnStr +="Item:" + item.name;
            returnStr +=", Packing:" + item.packing().pack();
            returnStr +=", Price: " + item.price();
        }
        return returnStr;
    }
}

export default Meal;  

MealBuilder.ts

Mealbuilder its just the class that uses the classes explained before to construct any type of meal, for sake of simplicity, we created only 2 meals here.

import Meal from "./Meal";  
import VegBurger from "./VegBurger";  
import Coke from "./Coke";  
import ChickenBurger from "./ChickenBurger";

class MealBuilder {  
    public prepareVegMeal(): Meal {
        let meal: Meal= new Meal();
        meal.addItem(new VegBurger());
        meal.addItem(new Coke());
        return meal;
    }

    public prepareNonVegMeal(): Meal {
        let meal: Meal= new Meal();
        meal.addItem(new ChickenBurger());
        meal.addItem(new Coke());
        return meal;
    }
}

export default MealBuilder;  

ITypescriptDesignPatterns03BuilderProps.ts

We created a selectedMeal string property to take the decision on which meal to build.

export interface ITypescriptDesignPatterns03BuilderProps {  
  description: string;
  selectedMeal: string;
}

TypescriptDesignPatterns03Builder.tsx

This is our component class, here we have a constructor and in the constructor we call the setMeal method, with the selected meal option as a parameter, and then we can define which meal to prepare. Once the meal is prepared, in the render method we can use the showItems method

import * as React from "react";  
import styles from "./TypescriptDesignPatterns03Builder.module.scss";  
import { ITypescriptDesignPatterns03BuilderProps } from "./ITypescriptDesignPatterns03BuilderProps";  
import { escape } from "@microsoft/sp-lodash-subset";  
import MealBuilder from "./MealBuilder";  
import Meal from "./Meal";  
import { IPropertyPaneConfiguration } from "@microsoft/sp-webpart-base/lib/propertyPane/propertyPane/IPropertyPane";  
import {  
  PropertyPaneDropdown
} from "@microsoft/sp-webpart-base";
import Version from "@microsoft/sp-core-library/lib/Version";

export default class TypescriptDesignPatterns03Builder extends React.Component<ITypescriptDesignPatterns03BuilderProps, {}> {

  private mealBuilder: MealBuilder ;
  private items: string;
  private meal: Meal;

  constructor(props: ITypescriptDesignPatterns03BuilderProps, state: any) {
    super(props);
    this.setMeal(props.selectedMeal);
    this.mealBuilder = new MealBuilder();
  }

  public render(): React.ReactElement<ITypescriptDesignPatterns03BuilderProps> {
    return (
        <div className={styles.typescriptDesignPatterns03Builder}>
          <div className={styles.container}>
            <div className={`ms-Grid-row ms-bgColor-themeDark ms-fontColor-white ${styles.row}`}>
              <div className="ms-Grid-col ms-lg10 ms-xl8 ms-xlPush2 ms-lgPush1">
                <span className="ms-font-xl ms-fontColor-white">Welcome to Burger Company!</span>
                <p className="ms-font-l ms-fontColor-white">You have selected the following.</p>
                  <span className={styles.label}>{this.meal.showItems()}</span>
              </div>
            </div>
          </div>
        </div>
      );
    }

  protected get dataVersion(): Version {
    return Version.parse("1.0");
  }

  private setMeal(selectedMeal: string): void {
     if(selectedMeal === "VegMeal") {
        this.meal = this.mealBuilder.prepareVegMeal();
     }
     if(selectedMeal === "NonVegMeal") {
      this.meal = this.mealBuilder.prepareNonVegMeal();
   }
  }
}

And finally

TypescriptDesignPatterns03BuilderWebPart.ts

Here what we do is just to use our component and sending the parameter of the selected meal, which is just a normal dropdown with 2 hardcoded values.

import * as React from "react";  
import * as ReactDom from "react-dom";  
import { Version } from "@microsoft/sp-core-library";  
import {  
  BaseClientSideWebPart,
  IPropertyPaneConfiguration,
  PropertyPaneTextField,
  PropertyPaneDropdown
} from "@microsoft/sp-webpart-base";

import * as strings from "TypescriptDesignPatterns03BuilderWebPartStrings";  
import TypescriptDesignPatterns03Builder from "./components/TypescriptDesignPatterns03Builder";  
import { ITypescriptDesignPatterns03BuilderProps } from "./components/ITypescriptDesignPatterns03BuilderProps";  
import { ITypescriptDesignPatterns03BuilderWebPartProps } from "./ITypescriptDesignPatterns03BuilderWebPartProps";

export default class TypescriptDesignPatterns03BuilderWebPart extends  
  BaseClientSideWebPart<ITypescriptDesignPatterns03BuilderWebPartProps> {

  public render(): void {
    const element: React.ReactElement<ITypescriptDesignPatterns03BuilderProps > = React.createElement(
      TypescriptDesignPatterns03Builder,
      {
        description: this.properties.description,
        selectedMeal: this.properties.selectedMeal
      }
    );

    ReactDom.render(element, this.domElement);
  }

  protected get dataVersion(): Version {
    return Version.parse("1.0");
  }

  protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration  {
    return {
      pages: [
        {
          header: {
            description: "Header"
          },
          groups: [
            {
              groupName: "Group",
              groupFields: [
                PropertyPaneDropdown("meal", {
                  label: "Select meal",
                  options: [
                    { key: "Veg", text: "Veg" },
                    { key: "Nonveg", text: "Nonveg" }
                  ],
                  selectedKey: "Nonveg"
                })
              ]
            }
          ]
        }
      ]
    };
  }
}

Data source implementation is left to the reader.
This project is in my github repo: https://github.com/levalencia/TypescriptDesignPatterns03-Builder

About the Author: 

Luis Valencia, CTO at Software Estrategico, Medellin, Colombia, independent blogger and still a coder, after 17 years of experience in the field and regardless of my position, and mostly with SharePoint/Office Products, I still love to code, open Visual Studio and bring solutions to users and to the community its what makes me wake up every morning.

Feel free to contact me via twitter direct messages, @levalencia

Reference: 

Valencia, L (2018). Typescript design patterns for SharePoint Framework Part 3 – Builder. Available at: http://www.luisevalencia.com/2018/03/19/typescript-design-patterns-for-sharepoint-framework-part-3-builder/ [Accessed 12 November 2018]

Share this on...

Rate this Post:

Share:

Join our Mailing List!

Join the ESPC mailing list to receive the best, free content from across the SharePoint, Office 365 & Azure community, including our monthly newsletter and the latest conference announcements. You can unsubscribe at any time with one click.
  • Sign up to receive exclusive content and analysis from the SharePoint, Office 365 & Azure community, as well as the latest conference updates and offers.

  • Hidden
  • This field is for validation purposes and should be left unchanged.

Resource Centre Login - Content

Resource Centre Login - Content

  • Already a member? Simply Login

  • Become an ESPC Community Member today to access a wealth of SharePoint, Office 365 and Azure knowledge for free. New content is added daily to the online Resource Centre, across a variety of topics and formats from Microsoft MVP’s and industry experts. With over 2,500 eBooks, webinars, presentations, how to videos and blogs, there is something to suit everyone’s learning styles and career goals.

    (All Fields Required) ** Verification Email will be sent **
    Check your Spam/Junk/Clutter folder
  • ** Verification Email will be sent **
    Check your Spam/Junk/Clutter folder
  • This field is for validation purposes and should be left unchanged.

Scroll to top

STAY UP TO DATE - JOIN OUR MAILING LIST

opt-in