Create and upload the notification extension
You can use an extension to add a Notify Me button to your Product Details widget, which is then displayed to shoppers when they view a product that is out of stock.
You must create and upload the notification extension in order to create the Notify Me element. To do so:
- Click the Settings icon.
- Click Extensions and display the Developer tab.
Before you develop an extension, you must generate an ID that you will include in your extension file.
- Click Generate ID, and you are prompted to name the extension.
- Name the extension Notify Me, and click Save.
Your extension ID is now generated and must be used in the extension’s
ext.json
file. - Edit the
ext.json
file and set theextensionID
property to the value generated in the previous steps. - Package all the files within your <extension-name> directory in a ZIP file.
- Open the Installed tab, and click Upload Extension to upload the extension to the administration interface.
- Select the extension ZIP file from your local file system.
- Publish your changes.
Create a Notify Me element
The Product Details widget is separated into elements, (see Fragment a Widget into Elements for more information) including the Notify Me element. To create an element to display the Notify Me button, you must create a template.txt
file providing the HTML rendering code for the element. The contents of the template.txt
file look similar to the following:
<!-- Notify Me Element -->
<!-- ko if: initialized() && $data.elements.hasOwnProperty('product-notify-me') -->
<div class="notify-me" id="cc-notify-me-container" data-bind="visible: $data.elements['product-notify-me'].showNotifyMe">
<a href="#" data-bind="click : $data.elements['product-notify-me'].notifyMe.bind(this)" id="CC-notifyMe-link">
<span id="CC-notifyMe-label" data-bind="widgetLocaleText: 'notifyMeText'"></span>
</a>
</div>
<!-- Popup -->
<div class="modal fade" id="CC-notify-me-dialog" tabindex="-1" role="dialog">
<div class="modal-dialog cc-modal-dialog" id="CC-notify-me-dialog-content" role="document">
<div class="modal-content">
<div class="modal-header CC-header-modal-heading">
<h3 data-bind="widgetLocaleText:'notifyDialogTitle'"></h3>
</div>
<div class="modal-body cc-modal-body">
<p data-bind="widgetLocaleText: 'notifyOutOfStock'"></p>
<p data-bind="widgetLocaleText: 'notifyInstructions'"></p>
<div class="form-group row">
<div class="controls col-md-12">
<label class="control-label inline" for="CC-login-input" data-bind="widgetLocaleText:'emailAddressText'"></label>
<span role="alert" class="text-danger" id="CC-notify-email-input-error" data-bind="validationMessage: user().emailAddress"></span>
<input type="email" class="col-md-5 form-control" id="CC-notify-email-input" aria-required="true"
data-bind="validatableValue: user().emailAddress, widgetLocaleText: {value: 'emailAddressText', attr:'placeholder'},
event: { blur: $data.user().emailAddressLostFocus.bind(this), focus: $data.user().emailAddressFocused.bind(this) }" />
</div>
</div>
<div id="CC-notify-me-footer" class="modal-footer CC-header-modal-footer">
<div class="center-block">
<button class="cc-button-primary" data-bind="widgetLocaleText: 'confirmText', click: $data.elements['product-notify-me'].confirm.bind(this)" />
<button class="cc-button-secondary" data-bind="widgetLocaleText: 'cancelText', click: $data.elements['product-notify-me'].cancel.bind(), event: {mousedown: $data.elements['product-notify-me'].handleMouseDown.bind($data, $parent), mouseup: $data.elements['product-notify-me'].handleMouseUp.bind($data, $parent)}" />
</div>
</div>
</div>
</div>
</div>
</div>
<!-- /ko -->
This file must be located in the NotifyMe/templates
folder.
Define the Notify Me element meta-data
The Notify Me element requires a manifest file, called element.json
, to define key properties. The contents of the element.json
file look similar to the following:
{
"inline" : true,
"supportedWidgetType":["productDetails"],
"translations" : [
{
"language" : "en",
"title" : "Notify Me",
"description" : "Element to allow setting of back in stock notifications"
}]
}
Define the Notify Me element.js
The Notify Me element requires an element.js
file, the contents of the which look similar to the following:
define(
//-------------------------------------------------------------------
// DEPENDENCIES
//------------------------------------------------------------------- ['jquery', 'knockout', 'navigation', 'ccConstants', 'ccRestClient', 'pubsub', 'notifier'],
// -------------------------------------------------------------------
// MODULE DEFINITION
// -------------------------------------------------------------------
function ($, ko, navigation, CCConstants, CCRestClient, pubsub, notifier) { "use strict";
return {
elementName: 'product-notify-me',
showNotifyMe: ko.observable(false),
onLoad: function(widget){
var self = this;
$.Topic(pubsub.topicNames.PRODUCT_VIEWED).subscribe(function(product){
self.setVisible(widget);
}); $.Topic(pubsub.topicNames.SKU_SELECTED).subscribe(function(product, sku, variant){
self.setVisible(widget);
});
widget.stockStatus.subscribe(function(newValue){
self.setVisible(widget);
});
$("[id^='CC-prodDetails-'").on("change", function(evt, data){
if ($(evt.target).find("option:selected").index() == 0){
//the "Select..." option has been selected, hide the notify-me
self.showNotifyMe(false);
}
});
//Set up subscriptions to user product notification events $.Topic(pubsub.topicNames.USER_PRODUCT_NOTIFICATION_SUCCESS).subscribe
(function(data){
notifier.sendSuccess(widget.WIDGET_ID,widget.translate('notifySuccess')); });
$.Topic(pubsub.topicNames.USER_PRODUCT_NOTIFICATION_FAILED).subscribe
(function(data){
notifier.sendSuccess(widget.WIDGET_ID,widget.translate('notifyFailed')); });
widget.confirmNotify = function(widget, email){
var inputData = {}, skuId = '';
inputData.siteId = widget.site().siteInfo.id;
inputData.productId = widget.product().id();
if (widget.selectedSku()){
skuId = widget.selectedSku().repositoryId;
}else{
//SKU for top level product
skuId = widget.product().stockStatus().catRefId || '';
}
inputData.skuId = skuId;
inputData.profileId = widget.user().repositoryId();
inputData.email = email;
inputData.locale = widget.locale();
inputData.expiryDate = "";
widget.user().createProductNotification(inputData);
$("#CC-notify-me-dialog").modal("hide");
}
},
confirm: function(widget, event){
if (widget.user().emailAddress.isValid()) {
var email = $("#CC-notify-email-input").val();
widget.confirmNotify(widget, email);
}
},
notifyMe: function(widget){
notifier.clearError(widget.WIDGET_ID);
if (!widget.user().loggedIn()){
widget.user().reset();
$("#CC-notify-email-input").val("");
$("#CC-notify-me-dialog").modal("show");
$('#CC-notify-me-dialog').on('shown.bs.modal', function () {
$("#CC-notify-email-input").focus();
});
$('#CC-notify-me-dialog').on('hidden.bs.modal', function() {
$("#CC-notifyMe-link").focus();
});
}else{
widget.confirmNotify(widget, widget.user().email());
}
},
cancel: function(){
$("#CC-notify-me-dialog").modal("hide");
},
setVisible: function(widget){
var hide = false;
//if a product has variants, but none are selected, don't show the
notify me option
if (widget.productVariantOptions &&
widget.productVariantOptions()){
if (!widget.selectedSku()){
hide = true;
}
//always allow options to be selected
if (widget.disableOptions){
widget.disableOptions(false);
}
}
this.showNotifyMe(!widget.stockStatus() && !hide);
},
/**
* Ignores the blur function when mouse click is up
*/
handleMouseUp: function(data) {
this.ignoreBlur(false);
data.user().ignoreEmailValidation(false);
return true;
},
/**
* Ignores the blur function when mouse click is down
*/
handleMouseDown: function(data) {
this.ignoreBlur(true);
data.user().ignoreEmailValidation(true);
return true;
}
};
}
);