How to create a skinnable custom field

From Sense/Net Wiki
Jump to: navigation, search
  •  
  •  
  •  
  •  
  • 100%
  • 6.3
  • Enterprise
  • Community
  • Planned

Overview

This tutorial introduces how to build a custom field control which has a unique look and metadata-set using client-side techniques. In this example we build an Education editor field control, the appearance of which depends on the chosen skin. Field data will be stored in JSON in a LongText field, which is modified with javascript.

Slider banner

Steps

1. Add the custom fieldcontrol template

There are two places in the Content Repository where fieldcontrol templates can be stored globally:

/Root/Global/fieldcontroltemplates/EducationEditor

or skin related:

/Root/Skins/mySkin/fieldcontroltemplates/EducationEditor

The custom template will be used from the skin which is applied on the actual page if defined there, or falls back to the globally defined template. Templates are stored in a folder, named exactly by the field name. Edit and browse templates can be created and stored here, named EditTemplate.ascx and BrowseTemplate.ascx. For an education editor field, we provide an edit template, which contains the markup of the field and all of it's values: school, start date and end date of the studies.

Please note that in the markup there must be a hidden textbox named InnerData to store the actual JSON. Please remember, that stored data in the JSON will not be searchable or queryable structured way, only as text content. If this is a required in the developing feature, the solution must be prepared to stored these data in separate fields.


<%@  Language="C#" %> 
<asp:TextBox ID="InnerData" runat="server" Style="display: none" CssClass="sn-educationlist-innerdata" /> 
 
<!-- hidden field to store JSON  --> 
<div id='sn-education-list'></div> 
<div class="new-education"> 
    <!-- School  --> 
    <div> 
        <label>School: </label> 
        <input type="text" class="sn-education-school" /></div> 
    <!-- From date  --> 
    <div> 
        <label>From: </label> 
        <input type="text" class="yearpicker sn-education-from" /> 
    </div> 
    <!-- Till date  --> 
    <div> 
        <label>Till: </label> 
        <input type="text" class="yearpicker sn-education-till"" /> 
    </div> 
</div>
<span class="addNewEducation sn-button" style="cursor: pointer; float: right;">Add</span> 
<sn:ScriptRequest ID="js" runat="server" Path="$skin/scripts/sn/SN.EducationEditor.js" /> 
<sn:CssRequest ID="css" runat="server" CSSPath="$skin/styles/SN.EducationEditor.css" /> 
<script> 
    $(function () { 
        //add new education item 
        $('.addNewEducation').on('click', function () { 
            Education.Add(); 
            $('.new-education input[type=text]').removeAttr('value'); 
        }) 
        //if education item egsists load them 
        if ($('.sn-educationlist-innerdata').val() !== '' || $('.sn-educationlist-innerdata').val() !== null) { 
            Education.Load(); 
        } 
    }); 
</script>

This is an ascx control, which contains the following:

  • markup of the template
  • Outer js and css calls
  • Script sniplet to run javascript actions defined in the outer JS file

By defining script- and css requests with a $skin-path, you can make sure that even these files will be used in a skin related way.

2. Add functionality with a JS file

In an outer JS file (see SN.EducationEditor.js in the markup above) we define all methods, that can be executed on this field. In case of the Education Editor field these are:

  • Create new education item,
  • Add education item to education list,
  • Add education item data to JSON
  • Newly created education items are added to the JSON, stored in a hidden TextBox
  • Create new education,
  • Load existing education items
  • Displaying existing education items, stored in the JSON
  • Delete education item
  • Deleting education items from the JSON
  • Edit existing education item,
  • Change data on save
  • Updating JSON with added education items
  • Populate JSON
// using $skin/scripts/jquery/jquery.js
 
var educationArray = [];
 
Education = {
    Add: function () {
        // create variables hold metadata of new education item
 
        var educationSchool = $('.new-education .sn-education-school').val();
        var educationFrom = $('.new-education .sn-education-from').val();
        var educationTill = $('.new-education .sn-education-till').val();
 
        // give metadata to functions
        Education.AddItemToList(educationSchool, educationFrom, educationTill);
        // a hidden textbox holds all metadata of all educations
        Education.AddItemToHiddenTextbox(educationSchool, educationFrom, educationTill);
    },
 
    // add new Education to Educationlist
    AddItemToList: function (educationSchool, educationFrom, educationTill) {
        // create newEducation markup
        var markup = '<div class="education" id="education-' + educationArray.length + '" data-contentid="' + educationArray.length + '">\
                        <div><label>School: </label><span class="value sn-education-school">' + educationSchool + '</span></div>\
                        <div><label>From: </label><span class="value sn-education-from">' + educationFrom + '</span></div>\
                        <div><label>Till: </label><span class="value sn-education-till"><span class="value">' + educationTill + '</span></div>\
                        <div class="edit-delete-education">\
                            <span class="sn-icon sn-icon-edit"><img src="/Root/Global/images/icons/16/edit.png" alt="edit" title="" class="sn-icon sn-icon16" /></span>\
                            <span class="sn-icon sn-icon-delete"><img src="/Root/Global/images/icons/16/delete.png" alt="delete" title="" class="sn-icon sn-icon16" /></span>\
                        </div>\
                    </div>';
        // add to educationlist
        $('#sn-education-list').append(markup);
        $('.sn-icon-delete').off('click');
        $('.sn-icon-edit').off('click');
 
        // remove this education on delete -- call edit function
        $('.sn-icon-delete').on('click', function (e) {
            var $this = $(e.target).closest('.education');
            Education.Delete($this.attr('id'), $this);
        });
 
        // edit this education on edit -- call edit function
        $('.sn-icon-edit').on('click', function (e) {
            var $this = $(e.target).closest('.education');
            Education.Edit($this.attr('id'), $this);
            $this.children('.edit-delete-education').hide();
        });
    },
 
    // add new education metadate to the hidden textbox
    AddItemToHiddenTextbox: function (educationSchool, educationFrom, educationTill) {
        // add metadata as JSON to hidden textbox
        var tb = $('.sn-educationlist-innerdata');
        var education = new Education.education(educationSchool, educationFrom, educationTill);
        if (tb.val() !== '') {
            educationArray = JSON.parse(tb.val());
        }
        else {
            educationArray = [];
        }
 
        educationArray.push(education);
        tb.val(JSON.stringify(educationArray));
    },
 
    // create this education with all metadata
    education: function (educationSchool, educationFrom, educationTill, id) {
        this.educationSchool = educationSchool;
        this.educationFrom = educationFrom;
        this.educationFrom = educationFrom;
        this.educationTill = educationTill;
        if (typeof id !== 'undefined')
            this.id = id;
        else
            this.id = 'education-' + educationArray.length;
    },
 
    // load this education to educationlist
    Load: function () {
        var tb = $('.sn-educationlist-innerdata');
        if (tb.val() !== '') {
            educationArray = JSON.parse(tb.val());
            Education.Populate();
            $.each(educationArray, function (i, item) {
                // markup for loaded education
                var markup = '<div class="education" id="education-' + i + '" data-contentid="' + i + '">\
                                <div><label>School: </label><span class="value sn-education-school">' + item.educationSchool + '</span></div>\
                                <div><label>From: </label><span class="value sn-education-from">' + item.educationFrom + '</span></div>\
                                <div><label>Till: </label><span class="value sn-education-til">' + item.educationTill + '</span></div>\
                                <div class="edit-delete-education">\
                                    <span class="sn-icon sn-icon-edit"><img src="/Root/Global/images/icons/16/edit.png" alt="edit" title="" class="sn-icon sn-icon16" /></span>\
                                    <span class="sn-icon sn-icon-delete"><img src="/Root/Global/images/icons/16/delete.png" alt="delete" title="" class="sn-icon sn-icon16" /></span>\
                                </div>\
                              </div>';
 
                $('#sn-education-list').append(markup);
            });
            $('.sn-icon-delete').on('click', function (e) {
                var $this = $(e.target).closest('.education');
                Education.Delete($this.attr('id'), $this);
            });
            $('.sn-icon-edit').on('click', function (e) {
                var $this = $(e.target).closest('.education');
                Education.Edit($this.attr('id'), $this);
                $this.children('.edit-delete-education').hide();
            });
        }
    },
 
    // remove this education on delete
    Delete: function (id, $education) {
        var tb = $('.sn-educationlist-innerdata');
        $.each(educationArray, function (i, item) {
            if (item.id === id) {
                educationArray.splice(i, 1);
                return false;
            }
        });
        $education.remove();
        Education.Populate();
        tb.val('');
        tb.val(JSON.stringify(educationArray));
    },
 
    // edit this education on edit -- modify markup to edit
    Edit: function (id, $education) {
        var $saveButton = $('<span class="ui-button-text-modify"><img src="/Root/Global/images/icons/16/tick.png" alt="edit" title="" class="sn-icon sn-icon16" /></span>');
        $education.find('span.value').each(function () {
            var $this = $(this);
            var $parent = $this.parent();
            var value = $this.text();
            var label = $parent.children('label').text().toLowerCase().replace(/\s/g, '');
            $this.remove();
            $parent.append('<input type="text" class="sn-button-' + label + '" value="' + value + '" />');
 
        });
 
        // add savebutton to save edited education
        $education.append($saveButton);
 
        // save modified meatadata on this button
        $saveButton.on('click', function (e) {
            var $education = $(e.target).closest('.education');
            Education.Change($education);
            $(this).remove();
        });
    },
 
    // change metadata of edited education on save
    Change: function ($education) {
        var tb = $('.sn-educationlist-innerdata');
        var educationSchool = $education.find('.sn-education-school').val();
        var educationFrom = $education.find('.sn-education-from').val();
        var educationTill = $education.find('.sn-education-till').val();
 
        var $saveButton = $education.find('.ui-button-text-modify');
 
        educationArray = JSON.parse(tb.val());
 
        var education = new Education.education(educationSchool, educationFrom, educationTill, $education.attr('id'));
 
        for (var i = 0; i < educationArray.length; i++) {
            if (educationArray[i].id === $education.attr('id')) {
                educationArray[i].educationSchool = education.educationSchool;
                educationArray[i].educationFrom = education.educationFrom;
                educationArray[i].educationTill = education.educationTill;
            }
        }
 
        tb.val('');
        tb.val(JSON.stringify(educationArray));
        $education.find('input[type=text]').each(function () {
            var $this = $(this);
            var $parent = $this.parent();
            var value = $this.val();
            $this.remove();
            $parent.append('<span class="value">' + value + '</span>');
        });
 
        $saveButton.remove();
        $education.find('.edit-delete-education').show();
    },
 
    // populate education array
    Populate: function () {
        $.each(educationArray, function (i, item) {
            item.id = 'education-' + i;
            $('#sn-education-list .education').eq(i).attr('id', item.id);
        })
 
    }
}

3. Specify appearance with a custom stylesheet

By having added a unique stylesheet even globally, or skin-related the appearance of the field can be influenced. If skin-related style-sheet is applied, appearance can be changed easily by changing the skin on the page.

Related links