CyberStore Ecommerce 2023 Documentation
HotSpotDiagramDisplay Widget

A hot spot diagram display widget. Introduced in v2.16. 

 Remarks

The HotSpotDiagramDisplay widget is designed to provide a complete hot spot diagram experience for the shopper This includes the presentation of the diagram and Hot Spots themselves as well as the interactive popup that will show when one clicks a hot spot to display details Item details including description, photo, the Web Price and the ability to add to cart (when either are available to the Shopper).

 

When Hot Spot Diagrams are created in the Management Console in the Hot Spot Diagram Maintenance screen. Once setup, they can be linked to Categories and Items for display in your online catalog. When diagrams are rendered, the widget with draw on top of them the configured shapes at the hot spot locations. Each Hot Spot is linked to an Item, and so when a Hot Spot shape is clicked, a popup will display the specific Item with it's photo 1 image, pricing (if configured to show) and an add to cart button (if configured to be available). 

 Widget Option Usage

Widget options provide a way to customize widgets in various ways. Widget options are sent to the widget when loaded with the LoadWidgetControl.

       

The widget determines the correct Hot Spot Diagram to use by receiving the diagram's ID from the HSP variable in the query string (e.g. https://www.yourdomain.com/ecommerce/CatalogHotSpots.aspx?HSP=12345). You can override this behavior by setting a value to the diagram_ID option using the ID of the Hot Spot Diagram.

By default, the size of the diagram is the original image size of the diagram graphic, however a custom size can be specified using the width option which will set the width in pixels while proportionally scaling the height as well.

The HotSpotDiagramListing widget supports the use of various shapes for the Hot Spot overlays. The shape option allows for choosing between the following: 'circle' (default) ,'triangle', 'square', 'pentagon', 'octagon', or 'star'. In all cases the X and Y coordinates established for the Hot Spot location is the center of the shape selected.

Popup Options

The size of the Item popup that appears when any Hot Spot is clicked by the shopper can be controlled by setting pixel dimensions using the popupWidth, popupHeight, popupMinWidth, popupMinHeight, popupMaxWidth and popupMaxHeight options. The default width value is 300, and height is 400.

A title bar is displayed by default at the top of the popup showing the Stock Code of the Item and providing an optional close icon. To hide the title bar, set the popupShowTitle option to false. The close icon can also be shown if desired by setting the popupShowCloseButton option to false.

By default, clicking outside of the popup, or hitting the Esc key will close the popup, to disable this behavior, set the value of the popupCloseOnOutsideClick option to false

To display the popup as a modal type popup shading the page beneath, set the popupModal option to true.

The popup position relative to the mouse click can also be set using the popupAtPosition and popupMyPosition options. These options each accept position pairs representing horizontal and vertical relative position to the mouse click. Valid values are 'left top' (default), 'left bottom', 'right top', 'right bottom'. In addition a specified number pixels offset from the mouse click can be set using the popupOffsetX (default 10) and popupOffsetY (default 0) options.

The Add to Cart button displayed within an individual popup can have its text changed using the options addToCartButtonText (default 'Add to Cart') and addedToCartButtonText (default, 'Added to Cart').

Additionally, the addedToCartMessageTimeout enables you to dictate the number of milliseconds before the Added to Cart message is displayed after clicking the Add to Cart button and the Item is successfully added. When this option is not set with a value it defaults to 1500, or 1.5 seconds.

Similarily, the addToCartErrorMessageTimeout enables you to dictate the number of milliseconds before an error message is displayed when after clicking the Add to Cart button and the Item is not successfully added. When this option is not set with a value it defaults to 3500, or 3.5 seconds.

CSS Class Options

The default CSS Class used for the diagram name's <p> tag is 'diagramName', but it can be customized using the diagramNameCSSClass option.

The default CSS Class used for the diagram headline's <p> tag is 'diagramHeadline', but it can be customized using the diagramHeadlineCSSClass option.

The default CSS Class used for the diagram description text's <p> tag is 'diagramDescription', but it can be customized using the diagramDescriptionCSSClass option.

Setting CSS Classes for styling of elements within the popup can be done with the options: hotSpotItemNameCSSClass (default 'popupName'), hotSpotItemPhotoCSSClass (default 'popupPhoto'), hotSpotItemShortDescriptionCSSClass (default 'popupShortDescription'), hotSpotMinQtyCSSClass (default 'popupMinQty'), and hotSpotItemPriceCSSClass (default 'WebPrice').

The Add to Cart button is styled by default using the CSS Class of 'btnCart btnHotSpot' but can be customized using the addToCartButtonCSSClass option.

 Widget Conventions
 Example

See an example of how to load and configure this widget in SitePages.config.

       
<!-- Example 1:
     Options set with default values. 
     Diagram determined by evaluating URL of the Hot Spot Page in page address 
     e.g https://www.yourdomain.com/ecommerce/CatalogHotSpot.aspx?HSP=12345
-->
<Control src="LoadWidgetControl.ascx" Name="HotSpot Display"
    FileLocation="HotSpotDiagramDisplay.html"
    Options="
        shape: 'circle',
        popupShowTitle: true,
        popupWidth: 300,
        popupHeight: 400,
        popupMinWidth: null,
        popupMinHeight: null,
        popupMaxWidth: null,
        popupMaxHeight: null,
        popupEnableDrag: true,
        popupEnableResize: false,
        popupShowCloseButton: true,
        popupCloseOnOutsideClick: true,
        popupModal: false,
        popupAtPosition: 'left top',
        popupMyPosition: 'left top',
        popupOffsetX: 10,
        popupOffsetY: 0,
        addToCartButtonText: 'Add to Cart',
        addedToCartButtonText: 'Added to Cart',
        addedToCartMessageTimeout: 1500,
        addToCartErrorMessageTimeout: 3500,
        addToCartErrorMessageText: null,
        diagramNameCSSClass: 'diagramName',
        diagramHeadlineCSSClass: 'diagramHeadline',
        diagramDescriptionCSSClass: 'diagramDescription',
        hotSpotItemNameCSSClass: 'popupName',
        hotSpotItemPhotoCSSClass: 'popupPhoto',
        hotSpotItemShortDescriptionCSSClass: 'popupShortDescription',
        hotSpotMinQtyCSSClass: 'popupMinQty',
        hotSpotItemPriceCSSClass: 'WebPrice',
        addToCartButtonCSSClass: 'btnCart btnHotSpot'" 
/>

<!-- Example 2: Options set with default values. 
        diagram_ID - force diagram to use ID of 1234
        shape - use star for the hot spot shape
        addToCartButtonText - use text of 'Buy Now'
        popupModal - treat the hot spot popup as a modal window 
-->
<Control src="LoadWidgetControl.ascx" Name="HotSpot Display"
    FileLocation="HotSpotDiagramDisplay.html"
    Options="
        diagram_ID: 1234,
        shape: 'star',
        popupModal: true,
        addToCartButtonText: 'Buy Now'"
/>
 Widget Source

The following is the source code for this widget.

Developer's Note:

To create a custom version of the widget, copy all of the code below into a file of the same name and place it into your Site's widgets folder (e.g., ../YourSiteFolder/Widgets). The CyberStore page engine will then override the default source with your customized version.

<div id="divWrapper_HotSpotContent">
    <p id="pName_HotSpotDiagram" />
    <p id="pHeadline_HotSpotDiagram" />
    <p id="pDescription_HotSpotDiagram" />
</div>
<div id="divWrapper_HotSpotDiagram" class="HotSpotDiagram">
    <canvas id="cnvsDiagram" resize></canvas>
</div>
<div id="divWrapper_HotSpotPopup">
    <div class="popup"></div>
</div>
    
<script type="text/javascript" src="/ecommerce/js/paperjs/paper-full.min.js"></script>
    
<script type="text/javascript">
    
    $(function () {
        let Params = {
            Diagram_ID: (widgetOptions.diagram_ID != null)
                ? widgetOptions.diagram_ID
                : (queryString['HSP'] != null && queryString['HSP'] != '')
                    ? queryString['HSP']
                    : -1
        };
        MakeAJAXCall('Inventory.GetHotSpotsDiagramWithHotSpots', Params, drawMe);
    
    });
    
    function drawMe(data) {
        if (data) {
            let diagram = initDiagram(JSON.parse(data));
            let canvas = document.getElementById('cnvsDiagram');
            if ($('#pName_HotSpotDiagram')) {
                $('#pName_HotSpotDiagram').text(diagram.Name);
                SetAttribute(widgetOptions.diagramNameCSSClass || 'diagramName', 'class', 'pName_HotSpotDiagram');
            }
            if ($('#pHeadline_HotSpotDiagram')) {
                $('#pHeadline_HotSpotDiagram').text(diagram.Headline);
                SetAttribute(widgetOptions.diagramHeadlineCSSClass || 'diagramHeadline', 'class', 'pHeadline_HotSpotDiagram');
    
            }
            if ($('#pDescription_HotSpotDiagram')) {
                $('#pDescription_HotSpotDiagram').text(diagram.Description);
                SetAttribute(widgetOptions.diagramDescriptionCSSClass || 'diagramDescription', 'class', 'pDescription_HotSpotDiagram');
            }
            let diagramImage = new Image();
            diagramImage.src = diagram.ImagePath;
            diagramImage.onload = function () {
                if (diagramImage.width > $(window).width()) {
                    widgetOptions.width = $(window).width();
                }
                let scale = (widgetOptions.width || diagramImage.width);
                if (!isNaN(scale)) {
                    scale = scale / diagramImage.width;
                    diagram.Radius *= scale;
                }
                SetAttribute('width:' + ((diagramImage.width * scale) + (diagram.Radius * 2)) + 'px;height:' + ((diagramImage.height * scale) + (diagram.Radius * 2)) + 'px;background:url(\'' + diagram.ImagePath + '\'); background-position: ' + diagram.Radius + 'px ' + diagram.Radius + 'px;background-repeat:no-repeat;background-size:' + (diagramImage.width * scale) + 'px;', 'style', 'cnvsDiagram')
                paper.setup(canvas);
                for (let i = 0; i < diagram.HotSpots.length; i++) {
                    let hotSpot = diagram.HotSpots[i];
                    hotSpot.XCoords *= scale;
                    hotSpot.YCoords *= scale;
                    if (hotSpot.XCoords != null && hotSpot.YCoords != null) {
                        let fillPath = null;
                        fillPath = createHotSpotShape(fillPath, widgetOptions.shape || 'circle', diagram, hotSpot);
                        fillPath.id = hotSpot.Unique_ID + '_fillpath';
    
                        let fillOpacity = diagram.FillOpacity;
                        if (diagram.Fill) {
                            fillPath.fillColor = diagram.FillColor;
                            fillPath.opacity = diagram.FillOpacity;
                        } else {
                            fillPath.fillColor = diagram.FillColor;
                            fillPath.opacity = fillOpacity = 0;
                        }

                        if (diagram.HighlightFill) {
                            fillPath.onMouseEnter = function (event) {
                                $('html,body').css('cursor','pointer');
                                this.fillColor = (this.fillColor.toCSS(true) == diagram.SelectedFillColor)
                                    ? diagram.SelectedFillColor
                                    : diagram.HighlightFillColor;
                                this.opacity = (this.opacity == diagram.SelectedFillOpacity)
                                    ? diagram.SelectedFillOpacity
                                    : diagram.HighlightFillOpacity;
                            }
                            fillPath.onMouseLeave = function (event) {
                                $('html,body').css('cursor','auto');
                                this.fillColor = (this.fillColor.toCSS(true) == diagram.HighlightFillColor)
                                    ? diagram.FillColor
                                    : this.fillColor;
                                this.opacity = (this.opacity == diagram.HighlightFillOpacity)
                                    ? fillOpacity
                                    : this.opacity;
                            }
                        } else {
                            fillPath.onMouseEnter = function (event) {
                                $('html,body').css('cursor','pointer');
                            }
                            fillPath.onMouseLeave = function (event) {
                                $('html,body').css('cursor','auto');
                            }
                        }
                        if (diagram.SelectedFill) {
                            fillPath.onClick = function (event) {
                                this.fillColor = (this.fillColor.toCSS(true) == diagram.SelectedFillColor)
                                    ? diagram.FillColor
                                    : diagram.SelectedFillColor;
                                this.opacity = (this.opacity == diagram.SelectedFillOpacity)
                                    ? fillOpacity
                                    : diagram.SelectedFillOpacity;
                                showItemPopup(hotSpot, diagram, event, fillPath);
                            }
                        } else {
                            fillPath.onClick = function (event) {
                                showItemPopup(hotSpot, diagram, event, fillPath);
                            }
                        }
                    }
                }
    
                if (diagram.Border) {
                    for (let i = 0; i < diagram.HotSpots.length; i++) {
                        let hotSpot = diagram.HotSpots[i];
    
                        if (hotSpot.XCoords != null && hotSpot.YCoords != null) {
                            let borderPath = null;
                            borderPath = createHotSpotShape(borderPath, widgetOptions.shape || 'circle', diagram, hotSpot);
    
                            borderPath.id = hotSpot.Unique_ID + '_borderpath';
                            borderPath.strokeWidth = diagram.BorderWidth * scale;
                            borderPath.strokeColor = diagram.BorderColor;
                            borderPath.opacity = diagram.BorderOpacity;
                        }
                    }
                }
    
                paper.view.draw();
            };
        }
    }
    
    function createHotSpotShape(p, s, d, h) {
        switch (s.toLowerCase()) {
            case 's':
            case 'st':
            case 'star':
                p = new paper.Path.Star({
                    center: [h.XCoords, h.YCoords],
                    points: 5,
                    radius1: d.Radius,
                    radius2: d.Radius * 2
                });
                p.rotate(23);
                break;
            case 'q':
            case 'sq':
            case 'square':
                let width = d.Radius * 2;
                p = new paper.Path.Rectangle({
                    point: [h.XCoords - d.Radius, h.YCoords - d.Radius],
                    size: [width, width],
                });
                break;
            case 't':
            case 'tri':
            case 'triangle':
                p = new paper.Path.RegularPolygon({
                    center: [h.XCoords, h.YCoords],
                    sides: 3,
                    radius: d.Radius
                });
                break;
            case 'p':
            case 'pent':
            case 'pentagon':
                p = new paper.Path.RegularPolygon({
                    center: [h.XCoords, h.YCoords],
                    sides: 5,
                    radius: d.Radius
                });
                break;
            case 'o':
            case 'oct':
            case 'octagon':
                p = new paper.Path.RegularPolygon({
                    center: [h.XCoords, h.YCoords],
                    sides: 8,
                    radius: d.Radius
                });
                break;
            case 'c':
            case 'cir':
            case 'circle':
            default:
                p = new paper.Path.Circle({
                    center: [h.XCoords, h.YCoords],
                    radius: d.Radius
                });
        }
        return p;
    }
    
    function initDiagram(obj) {
        obj.BorderColor = cleanHexColor(obj.BorderColor);
        obj.FillColor = cleanHexColor(obj.FillColor);
        obj.HighlightFillColor = cleanHexColor(obj.HighlightFillColor);
        obj.SelectedFillColor = cleanHexColor(obj.SelectedFillColor);
        for (let i = 0; i < obj.HotSpots.length; i++) {
            obj.HotSpots[i].XCoords += obj.Radius;
            obj.HotSpots[i].YCoords += obj.Radius;
        }
        return obj;
    }
    
    let popup = null;
    
    function showItemPopup(item, diagram, event, thePath) {
        if (popup) {
            $('.popup').remove();
        }
        let $popupContainer = $('<div />')
            .addClass('popup')
            .appendTo($('#divWrapper_HotSpotPopup'));
        let requiredQty = (item.RequiredQuantity != null && item.RequiredQuantity > 0)
            ? item.RequiredQuantity
            : 1;
        // /alert(event)
        popup = $popupContainer.dxPopup({
            target: '#cnvsDiagram',
            contentTemplate: () => {
                return $('<div />').append(
                    [
                        $('<h3 />').append(item.Name)
                            .attr('class', widgetOptions.hotSpotItemNameCSSClass || 'popupName'),
                        $('<img />')
                            .attr('src', '/ecommerce/images/spacer.png')
                            .attr('id', item.Unique_ID + '_photo')
                            .attr('class', widgetOptions.hotSpotItemPhotoCSSClass || 'popupPhoto'),
                        $('<p />').append(item.ShortDescription)
                            .attr('class', widgetOptions.hotSpotItemShortDescriptionCSSClass || 'popupShortDescription'),
                        $('<span />').append('Min. Order Qty: ' + requiredQty)
                            .attr('class', widgetOptions.hotSpotMinQtyCSSClass || 'popupMinQty'),
                        $('<span />')
                            .attr('class', widgetOptions.hotSpotItemPriceCSSClass || 'WebPrice')
                            .attr('id', item.Unique_ID + '_webprice'),
                        $('<button />')
                            .attr('class', widgetOptions.addToCartButtonCSSClass || 'btnCart btnHotSpot').append(widgetOptions.addToCartButtonText || 'Add To Cart')
                            .attr('id', item.Unique_ID + '_button')
                            .attr('onclick', 'MakeAJAXCall(\'Cart.AddItemToCart\', {Item_ID:' + item.Item_ID + ', Qty:' + requiredQty + ', UOM:\'P\'}, VerifyAddToCart, \'' + item.Unique_ID + '_button\')')
                    ]
                )
                    .attr('class', 'popupDiv');
            },
            title: item.StockCode,
            showTitle: (widgetOptions.popupShowTitle != null)
                ? widgetOptions.popupShowTitle
                : true,
            showCloseButton: (widgetOptions.popupShowCloseButton != null)
                ? widgetOptions.popupShowCloseButton
                : true,
            closeOnOutsideClick: (widgetOptions.popupCloseOnOutsideClick != null)
                ? widgetOptions.popupCloseOnOutsideClick
                : true,
            width: widgetOptions.popupWidth || 300,
            height: widgetOptions.popupHeight || 400,
            minWidth: widgetOptions.popupMinWidth || null,
            minHeight: widgetOptions.popupMinHeight || null,
            maxWidth: widgetOptions.popupMaxWidth || null,
            maxHeight: widgetOptions.popupMaxHeight || null,
            dragEnabled: (widgetOptions.popupEnableDrag != null)
                ? widgetOptions.popupEnableDrag
                : true,
            resizeEnabled: (widgetOptions.popupEnableResize != null)
                ? widgetOptions.popupEnableResize
                : false,
            shading: (widgetOptions.popupModal != null)
                ? widgetOptions.popupModal
                : false,
    
            position: {
                at: widgetOptions.popupAtPosition || 'left top',
                my: widgetOptions.popupMyPosition || 'left top',
                of: $('#cnvsDiagram'),
                offset: (event.point.x + (diagram.Radius) + (widgetOptions.popupOffsetX || 10)) + ' ' + (event.point.y + (widgetOptions.popupOffsetY || 0)),
                collision: 'fit'
            },
            onShowing: () => {
                if (!((widgetOptions.showAddToCartButtonWhenUnableToAdd != null)
                    ? widgetOptions.showAddToCartButtonWhenUnableToAdd
                    : false) && !item.CanAddToCart) {
                    removeElement(item.Unique_ID + '_button');
                }
                MakeAJAXCall('Price.GetDisplayPriceByStockCode', { StockCode: item.StockCode, Quantity: requiredQty, UOM: 0 }, SetWebPrice, item.Unique_ID + '_webprice');
                MakeAJAXCall('Inventory.GetItemPhoto', { StockCode: item.StockCode }, SetAttribute, { attribute: 'src', element: item.Unique_ID + '_photo', setBlank: false });
            },
            onHiding: () => {
                if (diagram.Fill) {
                    thePath.fillColor = diagram.FillColor;
                    thePath.opacity = diagram.FillOpacity;
                } else {
                    thePath.fillColor = diagram.FillColor;
                    thePath.opacity = fillOpacity = 0;
                }
            }
        }).dxPopup("instance");
    
        popup.show();
    };
    var VerifyAddToCart = function (data, e) {
        let response = JSON.parse(data);
        if (response.Success) {
            let b = document.getElementById(e);
            b.innerText = (widgetOptions.addedToCartButtonText || 'Added to Cart');
            b.classList.add('added');
            setTimeout(function () { b.innerText = (widgetOptions.addToCartButtonText || 'Add to Cart'); b.classList.remove('added'); }, widgetOptions.addedToCartMessageTimeout || 1500);
        } else {
            DevExpress.ui.notify({
                message: widgetOptions.addToCartErrorMessageText || response.Message,
                position: 'LeftBottom',
                closeOnClick: true,
                closeOnOutsideClick: false,
                closeOnSwipe: true,
                shading: false,
                contentTemplate: function () {
                    return $('<img src=""' + SITE_PATHS.application + '/images/circle-remove.png"" class=""notify-closebutton""/>');
                }
            },
            'error', widgetOptions.addToCartErrorMessageTimeout || 10000);
        }
    }
</script>
    
<style>
#divWrapper_HotSpotContent{float:left;width:200px;height:200px;position:relative;z-index:1}.diagramName{font-weight:bold}.diagramHeadline{margin-left:6px}.diagramDescrition{display:block;width:180px;padding:10px}.popupName{margin-top:0;font-size:20px}.popupDiv{text-align:center}.popupPhoto{width:150px;border:1px solid gray;display:block;clear:both;margin:10px auto;padding:5px}.popupMinQty{margin-right:35px}.btnHotSpot,.btnHotSpot:hover{clear:both;margin:10px auto!important;display:block;float:none}
</style>