Dojo widget/Dijit best approach to set and get the value

Member since: 2007

So, I've developed my first dijit (yay) and have some questions.

The widget is a composite of other widget, and it looks like this http://i.imgur.com/conw2M1.png. As you can see I'm using the epi-cms/widget/ContentSelector widget. But I'm not able to drag n' drop to work. This is the code I have for the widget:

var contentRepositoryDescriptors = dependency.resolve("epi.cms.contentRepositoryDescriptors");

var contentSelector = new ContentSelector({
	title: "Key property",
	required: true,
	missingMessage: "You must select a page",
	showSearchBox: true,
	searchArea: contentRepositoryDescriptors["pages"].searchArea,
	roots: contentRepositoryDescriptors["pages"].roots,
	allowedTypes: this.params.allowedTypes|| [],
	allowedDndTypes: this.params.allowedDndTypes || []
});

Do I need to set something else besides allowedDndTypes?

Speaking of which, I'm using the AllowedTypes attribute and since I updated to EPiServer 8 it throws an exception! "AllowedTypesAttribute is only supported on ContentReference,ContentArea" :( I get that not all property types supports AllowedTypes, but is it necessary to throw an exception. Do I have to develop my own attribute now to be able to pass allowed types to my widget?

And what is the best way/approach to actually set and get the widget's data? Right now I'm setting the initial state of the widget in postCreate and then updating the value in _getValueAttr. It works, but is this the best approach?

This is the compelete code:

define([
	"dojo/_base/declare",
	"dojo/_base/lang",
	"dijit/registry",

	"dijit/_Widget",
	"dijit/_Container",

	"dijit/form/NumberTextBox",
	"dijit/form/CheckBox",
	"dijit/form/Button",
	"dijit/layout/ContentPane",
	"dojox/layout/TableContainer",
	"epi-cms/widget/ContentSelector",

	"epi/dependency"
], function (
	declare,
	lang,
	registry,

	_Widget,
	_Container,

	NumberTextBox,
	CheckBox,
	Button,
	ContentPane,
	TableContainer,
	ContentSelector,

	dependency
) {
	return declare("keyproperty.KeyPropertyEditor", [_Widget, _Container], {
		contentPane: null,
		postCreate: function () {
			if (this.value == null) {
				this.value = [];
			}

			contentPane = new ContentPane({
				style: "width: 450px;"
			});

			var newBtn = new Button({
				label: "Add",
				style: "margin: 0 0 10px 0;",
				iconClass: "epi-iconPlus epi-primary",
				onClick: lang.hitch(this, "_addRow")
			});

			contentPane.addChild(newBtn);
			contentPane.placeAt(this.containerNode);

			if (this.value != null) {
				for (var i = 0; i < this.value.length; i++) {
					this._addRow(this.value[i]);
				}
			}
		},
		isValid: function () {
			var isValid = true;
			var layouts = registry.findWidgets(contentPane.domNode);

			for (var i = 0; i < layouts.length; i++) {
				var widgets = registry.findWidgets(layouts[i].domNode);
				if (widgets.length === 4) {
					isValid = isValid && widgets[0].isValid(); // contentSelector
					isValid = isValid && widgets[2].isValid(); // ratingTextBox
				}
			}

			return isValid;
		},
		_addRow: function (val) {
			var layout = new TableContainer({
				cols: 4,
				showLabels: false,
				orientation: "vert"
			});

			var contentRepositoryDescriptors = dependency.resolve("epi.cms.contentRepositoryDescriptors");

			var contentSelector = new ContentSelector({
				title: "Key property",
				required: true,
				missingMessage: "You must select a page",
				showSearchBox: true,
				searchArea: contentRepositoryDescriptors["pages"].searchArea,
				roots: contentRepositoryDescriptors["pages"].roots,
				allowedTypes: this.params.allowedTypes|| [],
				allowedDndTypes: this.params.allowedDndTypes || []
			});

			var hbCheckBox = new CheckBox({
				label: "HP",
				title: "High performance"
			});

			var ratingTextBox = new NumberTextBox({
				label: "Rating",
				title: "Rating",
				style: "width: 100px;",
				required: true,
				missingMessage: "You must specify a rating",
				constraints: { min: 1, max: 7, places: 0 },
				invalidMessage: "You must specify a range between 1 and 7"
			});

			var removeBtn = new Button({
				label: "Remove",
				showLabel: false,
				iconClass: "epi-iconTrash",
				onClick: lang.hitch(this, "_removeRow")
			});

			if (val != null) {
				contentSelector.set("value", val.property);
				hbCheckBox.set("checked", val.isHighPerformance);
				ratingTextBox.set("value", val.rating);
			}

			layout.addChild(contentSelector);
			layout.addChild(hbCheckBox);
			layout.addChild(ratingTextBox);
			layout.addChild(removeBtn);

			contentPane.addChild(layout);
		},
		_removeRow: function (e) {
			var button = registry.getEnclosingWidget(e.srcElement);
			var layout = button.getParent();
			layout.destroyRecursive();
		},
		_getValueAttr: function () {
			var val = [];
			var layouts = registry.findWidgets(contentPane.domNode);

			for (var i = 0; i < layouts.length; i++) {
				var widgets = registry.findWidgets(layouts[i].domNode);
				if (widgets.length === 4) {
					var item = {
						Property: widgets[0].value || "",
						IsHighPerformance: widgets[1].checked || false,
						Rating: widgets[2].value || 0
					}

					val.push(item);
				}
			}

			return val;
		}
	});
});

Thanks!

#119454 Mar 28, 2015 0:42
  • Member since: 2007

    This is the error I get when I drop a page in the content selector widget:

    Cannot deserialize the current JSON object (e.g. {"name":"value"}) into type 'EPiServer.Core.ContentReference' because the type requires a JSON string value to deserialize correctly. To fix this error either change the JSON to a JSON string value or change the deserialized type so that it is a normal .NET type (e.g. not a primitive type like integer, not a collection type like an array or List<T>) that can be deserialized from a JSON object. JsonObjectAttribute can also be added to the type to force it to deserialize from a JSON object. Path '[0].Property.isPreferredLanguageAvailable', line 1, position 45.

    #119456 Mar 28, 2015 0:54
  • Member since: 2014

    I've checked your source code sample and found that in the case of drag and drop, the widgets[0].value (line 143 in your javascript widget) will be an object rather than an integer value. I did modified your code to make it work with drag and drop. Please see the code below:

    Javascript widget:

    define([
        "dojo/_base/declare",
        "dojo/_base/lang",
        "dijit/registry",
    
        "dijit/_Widget",
        "dijit/_Container",
    
        "dijit/form/NumberTextBox",
        "dijit/form/CheckBox",
        "dijit/form/Button",
        "dijit/layout/ContentPane",
        "dojox/layout/TableContainer",
        "epi-cms/widget/ContentSelector",
    
        "epi/dependency"
    ], function (
        declare,
        lang,
        registry,
    
        _Widget,
        _Container,
    
        NumberTextBox,
        CheckBox,
        Button,
        ContentPane,
        TableContainer,
        ContentSelector,
    
        dependency
    ) {
        return declare("hemso.capifast.CustomContentSelector", [_Widget, _Container], {
            contentPane: null,
            postCreate: function () {
                if (this.value == null) {
                    this.value = [];
                }
    
                contentPane = new ContentPane({
                    style: "width: 450px;"
                });
    
                var newBtn = new Button({
                    label: "Add",
                    style: "margin: 0 0 10px 0;",
                    iconClass: "epi-iconPlus epi-primary",
                    onClick: lang.hitch(this, "_addRow")
                });
    
                contentPane.addChild(newBtn);
                contentPane.placeAt(this.containerNode);
    
                if (this.value != null) {
                    for (var i = 0; i < this.value.length; i++) {
                        this._addRow(this.value[i]);
                    }
                }
            },
            isValid: function () {
                var isValid = true;
                var layouts = registry.findWidgets(contentPane.domNode);
    
                for (var i = 0; i < layouts.length; i++) {
                    var widgets = registry.findWidgets(layouts[i].domNode);
                    if (widgets.length === 4) {
                        isValid = isValid && widgets[0].isValid(); // contentSelector
                        isValid = isValid && widgets[2].isValid(); // ratingTextBox
                    }
                }
    
                return isValid;
            },
            _addRow: function (val) {
                var layout = new TableContainer({
                    cols: 4,
                    showLabels: false,
                    orientation: "vert"
                });
    
                var contentRepositoryDescriptors = dependency.resolve("epi.cms.contentRepositoryDescriptors");
    
                var contentSelector = new ContentSelector({
                    title: "Key property",
                    required: true,
                    missingMessage: "You must select a page",
                    showSearchBox: true,
                    searchArea: contentRepositoryDescriptors["pages"].searchArea,
                    roots: contentRepositoryDescriptors["pages"].roots,
                    // I did modified you js code here to make the property be able to select any content type.
                    allowedTypes: ["episerver.core.icontentdata"],
                    allowedDndTypes: ["episerver.core.icontentdata"]
                });
    
                var hbCheckBox = new CheckBox({
                    label: "HP",
                    title: "High performance"
                });
    
                var ratingTextBox = new NumberTextBox({
                    label: "Rating",
                    title: "Rating",
                    style: "width: 100px;",
                    required: true,
                    missingMessage: "You must specify a rating",
                    constraints: { min: 1, max: 7, places: 0 },
                    invalidMessage: "You must specify a range between 1 and 7"
                });
    
                var removeBtn = new Button({
                    label: "Remove",
                    showLabel: false,
                    iconClass: "epi-iconTrash",
                    onClick: lang.hitch(this, "_removeRow")
                });
    
                if (val != null) {
                    contentSelector.set("value", val.property);
                    hbCheckBox.set("checked", val.isHighPerformance);
                    ratingTextBox.set("value", val.rating);
                }
    
                layout.addChild(contentSelector);
                layout.addChild(hbCheckBox);
                layout.addChild(ratingTextBox);
                layout.addChild(removeBtn);
    
                contentPane.addChild(layout);
            },
            _removeRow: function (e) {
                var button = registry.getEnclosingWidget(e.srcElement);
                var layout = button.getParent();
                layout.destroyRecursive();
            },
            _getValueAttr: function () {
                var val = [];
                var layouts = registry.findWidgets(contentPane.domNode);
    
                for (var i = 0; i < layouts.length; i++) {
                    var widgets = registry.findWidgets(layouts[i].domNode);
                    if (widgets.length === 4) {
                        // This is the main modified code
                        var objectContentLink;
                        if (typeof (widgets[0].value) != 'object')
                            objectContentLink = widgets[0].value;
                        else
                            objectContentLink = widgets[0].value.contentLink;
                        var item = {
                            Property: objectContentLink || "",
                            IsHighPerformance: widgets[1].checked || false,
                            Rating: widgets[2].value || 0
                        }
                        // End of modified code
    
                        val.push(item);
                    }
                }
    
                return val;
            }
        });
    });

    The Editor Descriptor:

    using EPiServer.Core;
    using EPiServer.Shell.ObjectEditing.EditorDescriptors;
    using EPiServerSite2.Models.Properties;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace EPiServerSite2.Business.EditorDescriptors
    {
        [EditorDescriptorRegistration(TargetType = typeof(IEnumerable<LineItem>), UIHint = "CustomContentSelector")]
        public class CustomContentSelectorEditorDescriptor : EditorDescriptor
        {
            public CustomContentSelectorEditorDescriptor()
            {
                ClientEditingClass = "hemso.capifast.CustomContentSelector";
            }
        }
    }

    The Property Class LineItem:

    using EPiServer.Core;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    
    namespace EPiServerSite2.Models.Properties
    {
        public class LineItem
        {
            public string Property { get; set; }
            public bool IsHighPerformance { get; set; }
            public int Rating { get; set; }
        }
    }

    The property class PropertyCustomContentSelector:

    using EPiServer.Core;
    using EPiServer.PlugIn;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Script.Serialization;
    
    namespace EPiServerSite2.Models.Properties
    {
        [PropertyDefinitionTypePlugIn(Description = "A property for list of content reference.", DisplayName = "Multi page property")]
        public class PropertyCustomContentSelector : PropertyLongString
        {
            public override Type PropertyValueType
            {
                get { return typeof(IEnumerable<LineItem>); }
            }
    
            public override object Value
            {
                get
                {
                    var value = base.Value as string;
                    if (value == null)
                    {
                        return null;
                    }
                    var serializer = new JavaScriptSerializer();
                    return serializer.Deserialize(value, typeof(IEnumerable<LineItem>));
                }
                set
                {
                    if (value is IEnumerable<LineItem>)
                    {
                        var serializer = new JavaScriptSerializer();
                        base.Value = serializer.Serialize(value);
                    }
                    else
                    {
                        base.Value = value;
                    }
                }
            }
    
            public override object SaveData(PropertyDataCollection properties)
            {
                return LongString;
            }
        }
    }


    After you deployed the code above, to create a multi-content selector property, you can use the following declaration on page:

    [Display(GroupName = SystemTabNames.Content, Order = 330)]
    [UIHint("CustomContentSelector")]
    [BackingType(typeof(PropertyCustomContentSelector))]
    public virtual IEnumerable<LineItem> SelectedContent { get; set; }
    

    Thanks,

    Linh Doan

    #120319 Apr 15, 2015 10:20
  • Member since: 2014

    I've corrected the display of drag and drop item with dndSourcePropertyName: "contentLink" in ContentSelector's constructor, and added your code to check for null widgets[0].value. The completed javascript file is below:

    define([
        "dojo/_base/declare",
        "dojo/_base/lang",
        "dijit/registry",
    
        "dijit/_Widget",
        "dijit/_Container",
    
        "dijit/form/NumberTextBox",
        "dijit/form/CheckBox",
        "dijit/form/Button",
        "dijit/layout/ContentPane",
        "dojox/layout/TableContainer",
        "epi-cms/widget/ContentSelector",
    
        "epi/dependency"
    ], function (
        declare,
        lang,
        registry,
    
        _Widget,
        _Container,
    
        NumberTextBox,
        CheckBox,
        Button,
        ContentPane,
        TableContainer,
        ContentSelector,
    
        dependency
    ) {
        return declare("hemso.capifast.CustomContentSelector", [_Widget, _Container], {
            contentPane: null,
            postCreate: function () {
                if (this.value == null) {
                    this.value = [];
                }
    
                contentPane = new ContentPane({
                    style: "width: 450px;"
                });
    
                var newBtn = new Button({
                    label: "Add",
                    style: "margin: 0 0 10px 0;",
                    iconClass: "epi-iconPlus epi-primary",
                    onClick: lang.hitch(this, "_addRow")
                });
    
                contentPane.addChild(newBtn);
                contentPane.placeAt(this.containerNode);
    
                if (this.value != null) {
                    for (var i = 0; i < this.value.length; i++) {
                        this._addRow(this.value[i]);
                    }
                }
            },
            isValid: function () {
                var isValid = true;
                var layouts = registry.findWidgets(contentPane.domNode);
    
                for (var i = 0; i < layouts.length; i++) {
                    var widgets = registry.findWidgets(layouts[i].domNode);
                    if (widgets.length === 4) {
                        isValid = isValid && widgets[0].isValid(); // contentSelector
                        isValid = isValid && widgets[2].isValid(); // ratingTextBox
                    }
                }
    
                return isValid;
            },
            _addRow: function (val) {
                var layout = new TableContainer({
                    cols: 4,
                    showLabels: false,
                    orientation: "vert"
                });
    
                var contentRepositoryDescriptors = dependency.resolve("epi.cms.contentRepositoryDescriptors");
    
                var contentSelector = new ContentSelector({
                    title: "Key property",
                    required: true,
                    missingMessage: "You must select a page",
                    showSearchBox: true,
                    dndSourcePropertyName: "contentLink",
                    searchArea: contentRepositoryDescriptors["pages"].searchArea,
                    roots: contentRepositoryDescriptors["pages"].roots,
                    // I did modified you js code here to make the property be able to select any content type.
                    allowedTypes: ["episerver.core.icontentdata"],
                    allowedDndTypes: ["episerver.core.icontentdata"]
                });
    
                var hbCheckBox = new CheckBox({
                    label: "HP",
                    title: "High performance"
                });
    
                var ratingTextBox = new NumberTextBox({
                    label: "Rating",
                    title: "Rating",
                    style: "width: 100px;",
                    required: true,
                    missingMessage: "You must specify a rating",
                    constraints: { min: 1, max: 7, places: 0 },
                    invalidMessage: "You must specify a range between 1 and 7"
                });
    
                var removeBtn = new Button({
                    label: "Remove",
                    showLabel: false,
                    iconClass: "epi-iconTrash",
                    onClick: lang.hitch(this, "_removeRow")
                });
    
                if (val != null) {
                    contentSelector.set("value", val.property);
                    hbCheckBox.set("checked", val.isHighPerformance);
                    ratingTextBox.set("value", val.rating);
                }
    
                layout.addChild(contentSelector);
                layout.addChild(hbCheckBox);
                layout.addChild(ratingTextBox);
                layout.addChild(removeBtn);
    
                contentPane.addChild(layout);
            },
            _removeRow: function (e) {
                var button = registry.getEnclosingWidget(e.srcElement);
                var layout = button.getParent();
                layout.destroyRecursive();
            },
            _getValueAttr: function () {
                var val = [];
                var layouts = registry.findWidgets(contentPane.domNode);
    
                for (var i = 0; i < layouts.length; i++) {
                    var widgets = registry.findWidgets(layouts[i].domNode);
                    if (widgets.length === 4) {
                        // This is the main modified code
                        if (widgets[0].value != null)
                        {
                            var objectContentLink;
                            if (typeof (widgets[0].value) != 'object')
                                objectContentLink = widgets[0].value;
                            else
                                objectContentLink = widgets[0].value.contentLink;
                            var item = {
                                Property: objectContentLink || "",
                                IsHighPerformance: widgets[1].checked || false,
                                Rating: widgets[2].value || 0
                            }
                            // End of modified code
    
                            val.push(item);
                        }
                    }
                }
    
                return val;
            }
        });
    });

    Regards,

    Linh Doan

    #120399 Edited, Apr 16, 2015 8:55
  • Member since: 2014

    For the [AllowedTypes] attribute, from EPiServer CMS 8 we have a breaking change and the attribute now is applicable for only ContentReference and ContentArea. You can check the document here:

    http://world.episerver.com/documentation/Items/Upgrading/EPiServer-CMS/8/Breaking-changes/

    Regards,

    Linh Doan

    #120404 Apr 16, 2015 10:07
  • Member since: 2007

    Thanks Linh!

    #120436 Apr 16, 2015 18:55
  • Hi,

    There's a bug of ContentSelector in EpiServer version 8.9.0, if I just only select a page or picture then autosave doesn't trigger. This work fine in 8.0.0.

    Thanks & best regards,

    An Le

    #123300 Jul 01, 2015 10:33
  • Member since: 2007

    Hi An,

    You should report that as a bug and not post it here in the thread. The product team doesn't read all the threads.

    #123301 Jul 01, 2015 10:37