How to create a skinnable custom field
- 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.
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.