1
0
mirror of https://github.com/twirl/The-API-Book.git synced 2025-05-13 21:26:26 +02:00
2023-08-14 17:41:39 +03:00

267 lines
6.8 KiB
JavaScript

/**
* @fileoverview
* In this example, we enhance the standard offer list with
* icons of the coffee shops and the offer view panel,
* with an additional business logic, exposing several additional
* controls and customizing the existing ones
*/
const {
SearchBox,
SearchBoxComposer,
OfferPanelComponent,
OfferPanelButton,
util,
dummyCoffeeApi
} = ourCoffeeSdk;
const { buildCloseButton } = OfferPanelComponent;
/**
* This is the factory method to create a customized
* “Place an order” button that augments the button
* look depending on the additional data fields
* in the assiciated offer
*/
const buildCustomCreateOrderButton = function (
offer,
container
) {
return OfferPanelComponent.buildCreateOrderButton(
offer,
container,
{
createOrderButtonUrl:
offer && offer.createOrderButtonIcon,
createOrderButtonText:
(offer &&
`Buy now for just ${offer.price.formattedValue}`) ||
"Place an Order"
}
);
};
/**
* This is the factory method to create a customized
* button that allows for making a phone call
*/
const buildCallButton = function (
offer,
container,
options
) {
return new OfferPanelButton("call", container, {
text: util.html`<a href="tel:${util.attrValue(
offer.phone
)}" style="color: inherit; text-decoration: none;">${
options.callButtonText
}</a>`
});
};
/**
* This is the factory method to create a customized
* button that allows for navigating back to the
* previous offer
*/
const buildPreviousOfferButton = function (
offer,
container
) {
return new NavigateButton(
"left",
offer.previousOfferId,
container
);
};
/**
* This is the factory method to create a customized
* button that allows for navigating to the next offer
*/
const buildNextOfferButton = function (offer, container) {
return new NavigateButton(
"right",
offer.nextOfferId,
container
);
};
/**
* This is a new implementation of the `IButton` interface
* from scratch. As “Back” and “Forward” buttons share little
* logic with the standard button (they do not have
* text or icon, feature a different design, etc.) it's
* more convenient to make a new class.
*/
class NavigateButton {
constructor(direction, targetOfferId, container) {
this.action = "navigate";
this.targetOfferId = targetOfferId;
this.events = new util.EventEmitter();
const button = (this.button =
document.createElement("button"));
button.innerHTML = direction === "left" ? "⟨" : "⟩";
button.className = direction;
container.classList.add("custom-control");
this.container = container;
this.listener = () =>
this.events.emit("press", {
target: this
});
button.addEventListener("click", this.listener);
container.appendChild(button);
}
destroy() {
this.button.removeEventListener("click", this.listener);
this.button.parentElement.removeChild(this.button);
this.container.classList.remove("custom-control");
}
}
/**
* This is the customization of the standard `OfferPanelComponent`
* class. In this custom implementation, the array of
* buttons is contructed dynamically depending on the data
* shown in the pannel.
*
* This is a bit of a shortcut (we should have a separate
* composer between a panel and its buttons). The full solution
* is left as an exercise for the reader.
*/
class CustomOfferPanel extends OfferPanelComponent {
show() {
const buttons = [];
const offer = this.currentOffer;
if (offer.previousOfferId) {
buttons.push(buildPreviousOfferButton);
}
buttons.push(buildCustomCreateOrderButton);
if (offer.phone) {
buttons.push(buildCallButton);
}
buttons.push(buildCloseButton);
if (offer.nextOfferId) {
buttons.push(buildNextOfferButton);
}
this.options.buttonBuilders = buttons;
super.show();
}
}
/**
* To work with the augmented panel we need
* an augmented composer:
* * Add the coffee chain icon to the
* “preview” data for the offer list
* * Use the enhanced offer panel instead
* of the standard one
* * Enrich the data for the panel needs
* with additional fields, such as
* the custom icon, phone, and the identifiers
* of the previous and next offers
*/
class CustomComposer extends SearchBoxComposer {
buildOfferPanelComponent(
context,
container,
currentOffer,
contextOptions
) {
return new CustomOfferPanel(
context,
container,
this.generateCurrentOfferFullView(
currentOffer,
contextOptions
),
{
...CustomComposer.DEFAULT_OPTIONS,
...this.generateOfferPanelComponentOptions(
contextOptions
)
}
);
}
generateOfferPreviews(offerList) {
const result = super.generateOfferPreviews(offerList);
return result === null
? result
: result.map((preview, index) => ({
...preview,
imageUrl: offerList[index].place.icon
}));
}
generateCurrentOfferFullView(offer, options) {
const offerFullView =
super.generateCurrentOfferFullView(offer, options);
if (offer) {
if (offer.place.phone) {
offerFullView.phone = offer.place.phone;
}
if (offer.place.icon) {
offerFullView.createOrderButtonIcon =
offer.place.icon;
}
if (this.offerList) {
const offers = this.offerList;
const index = offers.findIndex(
({ offerId }) => offerId === offer.offerId
);
if (index > 0) {
offerFullView.previousOfferId =
offers[index - 1].offerId;
}
if (index < offers.length - 1 && index >= 0) {
offerFullView.nextOfferId =
offers[index + 1].offerId;
}
}
}
return offerFullView;
}
performAction(event) {
if (event.action === "navigate") {
// NB: `event` itself contains an `offerId`
// However, this is the identifier of a currently
// displayed offer. With `navigate` buttons
// we need a different offer, the one we
// need to navigate ro
this.selectOffer(event.target.targetOfferId);
} else {
super.performAction(event);
}
}
}
CustomComposer.DEFAULT_OPTIONS = {
createOrderButtonText: "🛒Place an Order",
callButtonText: "☎️ Make a Call",
closeButtonText: "❌Not Now"
};
/**
* We're subclassing `SearchBox` to use our
* enhanced composer
*/
class CustomSearchBox extends SearchBox {
buildComposer(context, container, options) {
return new CustomComposer(context, container, options);
}
createOrder(offer) {
alert(`Isn't actually implemented (yet)`);
return super.createOrder(offer);
}
}
const searchBox = new CustomSearchBox(
document.getElementById("search-box"),
dummyCoffeeApi
);
searchBox.search("Lungo");