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:

  1. Click the Settings icon.
  2. 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.

  3. Click Generate ID, and you are prompted to name the extension.
  4. 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.

  5. Edit the ext.json file and set the extensionID property to the value generated in the previous steps.
  6. Package all the files within your <extension-name> directory in a ZIP file.
  7. Open the Installed tab, and click Upload Extension to upload the extension to the administration interface.
  8. Select the extension ZIP file from your local file system.
  9. 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;
      }
    };
  }
);