Getting CurrentPage ContentLink (page id) in Dojo Widget

Vote:
 

Hi!

Im developing a custom editor widget that lets editors create simple polygons on a map. This information should be saved on the current page so that the shapes can be rendered on the page in view mode. 

However, since saving data to the current property via Dojo almost never work automatically (I mean, really? Why should it be so troublesome?), I thought I could call a web service that saves the shapes instead. That solution requires that the current page id (contentlink) is sent to the service along with the data. 

So the question is - how to access the current page id from the widget? 

It seems it can, somehow, be achived using this: https://world.episerver.com/documentation/Javascript-Library/?documentId=cms/7/epi/cms/_ContentContextMixin 

But I think this documentation lacks some good solid examples on how to actually use the mixin within the widget. 

I also found this post: https://world.episerver.com/Modules/Forum/Pages/Thread.aspx?id=87472, but to me it seems strange that the suggested answer was marked as the correct one - it does not answer the original question.

Has anyone got any idea how to accomplish this? It really should be simple. 

#196000
Edited, Aug 15, 2018 13:59
Vote:
 

Hi,

I would instead try to figure out how to save the data to the property instead. Can you perhaps post the widget code so I can have a quick look? And how are you saving the data, as JSON?

#196048
Aug 17, 2018 8:47
Vote:
 

This is the widget script. In the function _onAddedPolygon I call the API to save the property value. However, it does not always seems to trigger a page save (ie, the publish button does not appear). This I think is related to the focus of the control beeing lost or something along these lines. I have seen it before but never fully understood have to circumvent it. 

define([
    "dojo/on", // To connect events
    "dojo/_base/declare", // Used to declare the actual widget
    "dojo/_base/config", // Used to check if client code debugging is enabled
    "dojo/aspect", // Used to attach to events in an aspect-oriented way to inject behaviors

    "dijit/registry", // Used to get access to other dijits in the app
    "dijit/WidgetSet", // To be able to use 'byClass' when querying the dijit registry
    "dijit/_Widget", // Base class for all widgets
    "dijit/_TemplatedMixin", // Widgets will be based on an external template (string literal, external file, or URL request)
    "dijit/_WidgetsInTemplateMixin", // The widget will in itself contain additional widgets,
    "dijit/Dialog",
    "dijit/form/TextBox",
    "dijit/form/Textarea",
    "dijit/form/Button",
    "dojox/form/DropDownSelect",

    "epi/epi", // For example to use areEqual to compare property values
    "epi/shell/widget/_ValueRequiredMixin", // In order to check if the property is in a readonly state
    "epi/shell/widget/dialog/LightWeight", // Used to display the help message
    "epi-cms/widget/_HasChildDialogMixin",
    "epi-cms/_ContentContextMixin",

    "dojo/i18n!./nls/Labels", // Localization files containing translations

    'xstyle/css!./WidgetTemplate.css',

    "googlemapseditor/Async!https://maps.googleapis.com/maps/api/js?v=3.exp&libraries=places,drawing&key=AIzaSyCCkZI3w943tyqyZCkbbEB2Pl1W0eH1WPc" // Use async module to load external asyncronously loaded script for Google Maps
],
    function (
        on,
        declare,
        config,
        aspect,

        registry,
        WidgetSet,
        _Widget,
        _TemplatedMixin,
        _WidgetsInTemplateMixin,
        Dialog,
        TextBox,
        Textarea,
        Button,
        DropDownSelect,

        epi,
        _ValueRequiredMixin,
        LightWeight,
        _HasChildDialogMixin,
        _ContentContextMixin,

        localized
    ) {
        return declare([_Widget, _TemplatedMixin, _WidgetsInTemplateMixin, _ValueRequiredMixin, _ContentContextMixin], {

            intermediateChanges: true,
            // The Google Maps object of this widget instance
            map: null,

            // The map marker of this widget instance
            marker: null,

            // Load HTML template from the same location as the widget
            templateString: dojo.cache("advancedgooglemapseditor", "WidgetTemplate.html"),

            // Localizations able to be accessed from the template
            localized: localized,

            // Event used to notify EPiServer that the property value has changed
            onChange: function (value) {

            },

            // Saves all shape coodinates the user has drawn
            shapes: null,

            // Saves the objects them self (for cleanup)
            trueShapes: null,

            marker: null,
            helpDialog: null,
            infoDialog: null,
            textarea: null,
            pageId: null,

            // Dojo event fired after all properties of a widget are defined, but before the fragment itself is added to the main HTML document
            postCreate: function () {

                // Call base implementation of postCreate, passing on any parameters
                this.inherited(arguments);

                // When the editor switches tabs in the UI we trigger a map resize to ensure it aligns properly
                // Note: byClass requires dot notation
                var that = this;
                registry.byClass("dijit.layout.TabContainer").forEach(function (tab, i) {
                    aspect.after(tab, "selectChild", function () {
                        that.alignMap();
                    });
                });

                // Display help when help icon is clicked
                on(this.helpIcon, "click", function (e) {
                    e.preventDefault();

                    if (!that.helpDialog) {

                        if (config.isDebug) {
                            console.log('Creating help dialog for Google Maps editor');
                        }

                        that.helpDialog = new LightWeight({
                            style: "width: 540px",
                            closeIconVisible: true,
                            showButtonContainer: false,
                            onButtonClose: function () {
                                that.helpDialog.hide();
                            },
                            _endDrag: function () {
                                // HACK Avoid EPiServer bug, "Cannot read property 'userSetTransformId' of null" when close icon is clicked
                            },
                            title: that.localized.help.dialogTitle,
                            content: that.localized.help.dialogHtml
                        });
                    }

                    if (that.helpDialog.open) {
                        that.helpDialog.hide();
                    } else {
                        that.helpDialog.show();
                    }
                });
            },

            // Dojo event triggered after 'postCreate', for example when JS resizing needs to be done
            startup: function () {
                this.initializeMap();

                var that = this;

                // HACK Give EPiServer UI some time to load the view before resizing the map to ensure it aligns even after being hidden/displayed
                // TODO Replace with event/aspect for when the edit view changes in the UI
                setTimeout(function () {
                    that.alignMap();
                }, 250);
            },

            // Dojo event triggered when widget is removed
            destroy: function () {
                this.inherited(arguments); // Important to ensure inherited widgets are destroyed properly, failure to do this risks memory leaks

                // Clean up Google Maps (as much as possible, but there is a known issue with Google Maps: https://code.google.com/p/gmaps-api-issues/issues/detail?id=3803)
                if (this.marker) {
                    this.marker.setMap(null);
                }

                if (this.map && this.map.parentNode) {
                    google.maps.event.clearListeners(this.map, 'rightclick');
                    this.map.parentNode.removeChild(this.map);
                    this.map = null;
                }
            },

            // Checks if the current property value is valid (invoked by EPiServer)
            isValid: function () {
                return true;
            },

            // Checks if the current value is valid coordinates
            hasCoordinates: function () {
                return true;
            },

            // Setter for value property (invoked by EPiServer on load)
            _setValueAttr: function (value) {
                if (value === this.value) {
                    return;
                }

                this._set("value", value);

                // If the value set is empty then clear the coordinates
                if (!value) {
                    //this.clearCoordinates();
                    this.clearAllShapes();
                    return;
                }

                var location;
                if (value) {

                    if (this.shapes == null)
                        this.shapes = [];

                    if (this.trueShapes == null)
                        this.trueShapes = [];

                    var json = JSON.parse(value);

                    for (var i = 0; i < json.length; i++) {
                        var shapePath = json[i].points;
                        var type = json[i].type;
                        var text = json[i].text;

                        if (shapePath.length > 1) {
                            if (type == 'polygon')
                                var shape = new google.maps.Polygon({ path: shapePath });
                            else if (type == 'polyline')
                                var shape = new google.maps.Polyline({ path: shapePath });

                            shape.setMap(this.map);

                            var pointsWrapper = new Object();

                            pointsWrapper.points = shapePath;
                            pointsWrapper.type = type
                            pointsWrapper.text = text

                            this.trueShapes.push(shape);
                            this.shapes.push(pointsWrapper);
                        }
                        else {

                            var pos = {
                                lat: json[i].points[0].lat,
                                lng: json[i].points[0].lng
                            };

                            var shape = new google.maps.Marker({
                                position: pos
                            });

                            var polygonPoints = [];

                            polygonPoints.push(shape.getPosition());

                            var pointsWrapper = new Object();

                            pointsWrapper.points = polygonPoints;
                            pointsWrapper.type = type
                            pointsWrapper.text = text

                            shape.setMap(this.map);

                            this.trueShapes.push(shape);
                            this.shapes.push(pointsWrapper);
                        }
                    }
                }
            },

            _onAddedPolygon: function () {
                var c = getCurrentContent();

                console.log(c);
                if (!this._started) {
                     return;
                 }

                 var value = JSON.stringify(this.shapes);

                 this.focus();
                 this._set("value", value);
                 this.onChange(value);
            },

            clearAllShapes: function () {
                // Clear the layers from the map
                for (var i = 0; i < this.trueShapes.length; i++) {
                    var shape = this.trueShapes[i];
                    shape.setMap(null);
                    shape = null;
                }

                // Delete the arrays
                trueShapes = null;
                shapes = null;

                // Null the EPiServer value
                this._set("value", null);
                this.onChange(null);

                //// Call the backend api
                //$.ajax({
                //    type: "POST",
                //    url: "/Templates/Extern/Pages/Special/AdvancedMapSaver.aspx",
                //    data: { "pageId": this.pageId, "shapesData": "" },
                //    success: function (d) {
                //        console.log(d);
                //    },
                //});
            },

            saveShapes: function () {
                var value = JSON.stringify(this.shapes);

                // Call the backend api
                $.ajax({
                    type: "POST",
                    url: "/Templates/Extern/Pages/Special/AdvancedMapSaver.aspx",
                    data: { "pageId": this.pageId, "shapesData": value },
                    success: function (d) {
                        console.log(d);
                    },
                });

            },

            // Setup the Google Maps canvas
            initializeMap: function () {

                if (config.isDebug) {
                    console.log('Initializing map for Google Maps editor');
                }

                defaultCoordinates = new google.maps.LatLng(59.3791034, 13.4994304);

                // Render the map, but disable interaction if property is readonly
                var mapOptions = {
                    zoom: 11,
                    disableDefaultUI: true,
                    center: defaultCoordinates,
                    disableDoubleClickZoom: this.readOnly,
                    scrollwheel: !this.readOnly,
                    draggable: !this.readOnly
                };

                var drawingManager = new google.maps.drawing.DrawingManager({
                    drawingMode: google.maps.drawing.OverlayType.MARKER,
                    drawingControl: true,
                    drawingControlOptions: {
                        position: google.maps.ControlPosition.TOP_CENTER,
                        drawingModes: ['marker', 'polygon', 'polyline',]
                    },
                });

                // Load the map
                this.map = new google.maps.Map(this.canvas, mapOptions);

                drawingManager.setMap(this.map);

                var that = this;

                google.maps.event.addListener(drawingManager, 'overlaycomplete', function (event) {
                    if (that.shapes == null)
                        that.shapes = [];

                    if (that.trueShapes == null)
                        that.trueShapes = [];

                    if (event.type == 'polygon' || event.type == 'polyline') {

                        var polygon = event.overlay;
                        var polygonPoints = [];
                        var pointsWrapper = new Object();

                        for (var i = 0; i < polygon.getPath().getLength(); i++) {
                            var p = polygon.getPath().getAt(i);
                            polygonPoints.push(p);
                        }

                        pointsWrapper.points = polygonPoints;

                        pointsWrapper.type = event.type;
                        pointsWrapper.text = that.descriptionTextArea.value;
                        pointsWrapper.heading = that.descriptionTextbox.value;
                        pointsWrapper.color = that.colorDropDown.value;

                        that.widgetForm.reset();

                        that.shapes.push(pointsWrapper);
                        that.trueShapes.push(polygon);

                        that._onAddedPolygon();

                    }

                    if (event.type == 'marker') {
                        marker = event.overlay;
                        var polygonPoints = [];
                        var pointsWrapper = new Object();
                        polygonPoints.push(marker.getPosition());
                        pointsWrapper.points = polygonPoints;
                        pointsWrapper.type = 'marker';
                        pointsWrapper.heading = that.descriptionTextbox.value;
                        pointsWrapper.text = that.descriptionTextArea.value;
                        pointsWrapper.color = that.colorDropDown.value;
                        that.widgetForm.reset();
                        that.shapes.push(pointsWrapper);
                        that.trueShapes.push(marker);
                        that._onAddedPolygon();
                    }
                });

                var searchBox = new google.maps.places.SearchBox(this.searchTextbox.textbox);

                // Remove Google Maps Searchbox default placeholder, as it won't recognize the placeholder attribute placed on the Textbox dijit
                this.searchTextbox.textbox.setAttribute('placeholder', '');

                google.maps.event.addListener(searchBox, 'places_changed', function () {
                    var places = searchBox.getPlaces();

                    if (places.length == 0) {
                        return;
                    }
                    // Return focus to the textbox to ensure autosave works correctly and to also give a nice editor experience
                    that.searchTextbox.focus();

                    var mapOptions = {
                        zoom: 15,
                        center: places[0].geometry.location,
                    };

                    that.map.setOptions(mapOptions);
                });

            },

            // Triggers a map resize, for example when the map is hidden/displayed to ensure it aligns properly
            alignMap: function () {
                var center = this.map.getCenter();
                google.maps.event.trigger(this.map, "resize");
                this.map.setCenter(center);
            },         
        });
    });
#196049
Edited, Aug 17, 2018 9:02
Vote:
 

Hi again,

First of all, inherit from _HasChildDialogMixin and let me know the result. I have found this to be helpful in many cases with widget focus problems. And I think intermediateChanges property should be set to false in your case as well. It's helpful on text box widgets etc where you want to fire changes on every keystroke.

#196054
Aug 17, 2018 10:47
Vote:
 

I did as you suggested, included the mixin in the declare function, and changed intermediateChanges to false. It did not solve the issue. The value is not saved to the page. 

#196132
Aug 20, 2018 10:55
Vote:
 

This solved the problem: https://world.episerver.com/documentation/developer-guides/CMS/user-interface/Context-sensitive-components/

Summary: one should use the _ContextMixin to enable automatic saving. It can also be used to get some information about the current page. 

#196265
Aug 23, 2018 13:52
Vote:
 

Or maybe not. It workes perfectly for a while but suddenly I'm back to square one. However, the mixin gives access to the page id, so I wil use this in my crude API where I call the server backend and lets it save the value to the page. It's ugly, but since EPiServer never have adressed these issues I feel I have no choice. 

If anyone is interested, the code that extracts the page id looks as follows:

pageId: function () {
        var fullId = this.getCurrentContext().id;

         if (fullId.includes("_"))
              return fullId.split("_")[0];

          return fullId;
 }
#196266
Aug 23, 2018 14:50
This topic was created over six months ago and has been resolved. If you have a similar question, please create a new topic and refer to this one.