Create a custom property editor in Umbraco

Umbraco User’s Guide

This post is helpful in following scenarios –

  • If you are looking to create a custom property editor in Umbraco
  • If you are looking for a way to populate a checkbox list in the content dynamically
  • If you are looking for a way to call an api in umbraco property editor
  • If you are looking for a way to populate a checkbox list with one of the document types dynamically

For this post, we are going to build a custom property editor that will populate the checkbox list with categories (added under home) using an api url.

Let’s say, we have a food website that have different categories like Healthy, Baking, Recipes, Dinner, Summer recipes, etc.
We also have articles related to all these different categories.

Categories under Home –

Content with categories as checkbox list

What is the problem?

Now the problem here is whenever a new category is added, the checkbox list for categories has to be updated manually as we have use the default checkbox list data type.

In order to fix this problem, we will create a custom property editor and use it to create the checkbox list dynamically. So whenever, a new category will be added, the checkbox list for categories will be updated automatically.

What is the solution?

Skills needed to build a Property Editor –

  • Umbraco BackOffice (Using ver 8.0.1 for this post)
  • HTML
  • CSS
  • Javascript (Optional)
  • Angular (Optional)

Files needed for a Property Editor –

  • Manifest
  • HTML file
  • Javascript file
  • CSS (Optional)

First, let’s create a manifest file.

{
  propertyEditors: [
    {
      alias: "CustomCheckBoxList",
      name: "Custom Checkbox list",
      editor: {
        view: "~/App_Plugins/CheckboxListing/checkboxlist.html",
        hideLabel: false,
        valueType: "STRING"
      },
	  prevalues: {
			fields: [				
				{
					label: "Api Url",
					description: "Add the API Url to get the list of items. api/{controller}/{Action}",
					key: "apiUrl",
					view: "textstring"
				}
			]
		}
    }
  ],
  javascript: [
    "~/App_Plugins/CheckboxListing/checkboxlist.controller.js"
  ]
}

Now, let’s add the HTML file

<div ng-controller="CheckboxList.Controller">
   
    <ul class="unstyled">
        <li ng-repeat="category in selectedItems">
            <label class="checkbox">
                <input type="checkbox" name="checkboxlist"
                       value="{{category.key}}"
                       ng-model="category.selected"/>
                {{category.val}}
            </label>
        </li>
    </ul>
</div>

Now, we have to create the Javascript file, which will contain the main logic of this property editor.

angular.module("umbraco")
    .controller("CheckboxList.Controller",
        function($scope, $http, assetsService) {
            var newItems = [];
            $scope.selectedItems = [];
         
            var categories = function() {
                return $http.get($scope.model.config.apiUrl)
                    .then(function(data) {
                        $scope.categories = data;
                        for (var i = 0; i < $scope.categories.data.length; i++) {
                            newItems.push({ id: $scope.categories.data[i].id, value: $scope.categories.data[i].name });
                        }
                        $scope.categories = newItems;
                    });
            }
            categories()
                .then(function() {
                    $scope.$on("formSubmitting",
                        function(ev, args) {
                            if ($scope.model.value === null || $scope.model.value === undefined) {
                                $scope.model.value = [];
                            }
                            var selectedCategories = "";

                            angular.forEach($scope.selectedItems,
                                function(value, key) {                                    
                                    var itemSelected = value.selected;
                                    var item = value.val;

                                    selectedCategories = itemSelected === true
                                        ? selectedCategories + item + ","
                                        : selectedCategories + "";
                                });
                            $scope.model.value = selectedCategories.replace(/(^,)|(,$)/g, "");

                        });


                    function setupViewModel() {
                        if ($scope.model.value === null || $scope.model.value === undefined) {
                            $scope.model.value = [];
                        }

                        for (var i = 0; i < $scope.categories.length; i++) {
                            var isSelected = $scope.model.value.includes($scope.categories[i].value);
                           
                            $scope.selectedItems.push({
                                selected: isSelected,
                                key: $scope.categories[i].id,
                                val: $scope.categories[i].value
                            });
                        }
                    }

                    setupViewModel();

                    $scope.$watch("selectedItems",
                        function(newVal, oldVal) {
                            $scope.model.value = [];

                            for (var x = 0; x < $scope.selectedItems.length; x++) {
                                if ($scope.selectedItems[x].checked) {
                                    $scope.model.value.push($scope.selectedItems[x].key);
                                }
                            }
                        },
                        true);


                    $scope.model.onValueChanged = function(newVal, oldVal) {
                        setupViewModel();
                    };
                });
        });


That’s it!! You are done with the coding part.

What’s Next?

Just rebuild your project and go to backoffice where you have to create a new data type for the categories.

Go to Settings – Right click on Data Types to create a new data type.
Select datatype that we just created – Custom Checkbox List

Now, you must be thinking that what is Api Url. Ok, so this is the url of the api you will create to get the categories. I am using categories for this post. However, you can use this field to add other api urls as well to populate the checkbox list with anything you want.

Now, we will add the newly created document type to our content page.

And when you load the content again, you will see the categories populated in the checkbox list.

So now, whenever a new category will be added, the checkbox list will be auto-populated with the new one.

Note: It is good to create a datatype like this before you actually create a content as later on if you delete and update a property in your content, you have to update the data associated as well which could be a complex task sometimes.

Upload images to Umbraco

It is easy to upload images to Media folder in Umbraco but there are some instances where you need to upload images to media folder programatically, via code.

Here is the code to do that. You can use this code to download and upload any image.


public class ImageService
{
  private readonly IUmbracoContextWrapper _umbCtxtHelper;
private readonly IMediaService _mediaService;

  public ImageService(IUmbracoContextWrapper umbCtxtHelper)
  {
     _umbCtxtHelper = umbCtxtHelper;
_mediaService = _umbCtxtHelper.MediaService;
  }  

  public int UploadMedia(string imageSrc)
  {
   IMedia newImage = null;
   try
   {
var mediaSvc = _mediaService;
     var mediaExtension = Path.GetExtension(imageSrc);
     var imageName = Guid.NewGuid() + mediaExtension;

     var image = DownloadImageFromUrl(imageSrc);

     const string rootPath = "~\\media";
     var fileName = HttpContext.Current.Server.MapPath(Path.Combine(rootPath, imageName));
     image.Save(fileName);

     // -1 is the root folderID, i.e. Media.
     // All images will be saved under Media in Umbraco
     newImage = mediaSvc.CreateMedia(imageName, -1, "Image");
     var buffer = System.IO.File.ReadAllBytes(HttpContext.Current.Server.MapPath("~\\media\\" + imageName));

     newImage.SetValue("umbracoFile", imageName, new MemoryStream(buffer));
     mediaSvc.Save(newImage);

     if (System.IO.File.Exists(HttpContext.Current.Server.MapPath("~\\media\\" + imageName)))
     {
       System.IO.File.Delete(HttpContext.Current.Server.MapPath("~\\media\\" + imageName));
      }
   }
   catch (Exception ex)
   {
     // log the exception
   }

   return newImage.Id;
  }

  private System.Drawing.Image DownloadImageFromUrl(string imageUrl)
  {
   System.Drawing.Image image = null;
   try
   {
     var webRequest = (HttpWebRequest)WebRequest.Create(imageUrl);
     webRequest.AllowWriteStreamBuffering = true;
     webRequest.Timeout = 30000;

     var webResponse = webRequest.GetResponse();
     var stream = webResponse.GetResponseStream();
     image = System.Drawing.Image.FromStream(stream);

     webResponse.Close();
   }
   catch (Exception ex)
   {
     // log the exception
   }
   return image;
  }
}

public interface IUmbracoContextWrapper
{
   IMediaService MediaService { get; }
}

If you want to create folders under Media and upload images to those in Umbraco. Read this post here

Extending uCommerce – Adding Custom Data

Custom data can be stored in uCommerce using Fluent NHibernate mappings.

Following things are required while adding custom data –

  • An entity class
  • A table to store the data in the database
  • A map to let uCommerce know how to store the data
  • A mapping tag to let uCommerce know that a particular DLLs contains maps

For complete tutorial, refer this doc – How to add custom data in uCommerce

After saving the data in the database, the next step is to create the UI, i.e. the grid.

Here is a sample code to create a grid in uCommerce – 





$(function ()
{
$('#sampleTable').dataTable(
{
"bPaginate": false,
"bFilter": false,
"bSort": false
});
});


<div class="propertyPane">
<table class="dataList" id="sampleTable" style="width:100%;vertical-align:top;">
<thead>
<tr>
<th><span>Name</span></th>
.. Add the headers</tr>
</thead>
<tr>
<td></td>
.. Add the data bindings</tr>
</table>
</div>

uCommerce is using Datatables plug-in for jQuery for its UI. Click here for more info

Get all URLs in Umbraco using SQL

Use the below SQL script to get all the Urls in your Umbraco project.

; WITH PathXml AS (

/* -- This gives nodes with their 'urlName' property
 -- not in recycle bin,
 -- level &amp;amp;amp;amp;gt; 1 which excludes the top level documents which are not included in the url */
 SELECT
 nodeId,
 cast([xml] as xml).query('data(//@urlName[1])').value('.', 'varchar(max)') AS Path
 FROM cmsContentXml x
 JOIN umbracoNode n ON x.nodeId = n.id AND n.trashed = 0 AND n.level > 1
)

SELECT
 u.id,
 u.path,
 '/' +
 /* This is to get the top level document */
 IsNull((SELECT
 pl.Path + '/'
 FROM PathXml pl
 WHERE ',' + u.path + ',' LIKE '%,' + CAST(pl.nodeId AS VARCHAR(MAX)) + ',%'
 ORDER BY CHARINDEX(',' + CAST(pl.nodeId AS VARCHAR(MAX)) + ',',
 ',' + u.path + ',')

FOR XML PATH('')),
 '') AS Url,
 u.text PageName
FROM umbracoNode u
WHERE nodeObjectType = 'C66BA18E-EAF3-4CFF-8A22-41B16D66A972' /* Document node */
AND trashed = 0
ORDER BY 3 /* Url */