I am trying to write my first custom datatype using the 'three class method' - inspired very heavily by Tim and Ove - and I have run into a brick wall...
Requirement : To store an array of values in a single field as parseable XML. I think the actual datatype works fine, shown in the class 'ClassTimeDatatype' below. It's rendering fine in the editor, and when I enter the first row of data and save, the values are persisted to the database. However, when I navigate back to that node in the CMS, the values are not coming back out of the database. I'm sure I'm missing something simple but I cant see what!
If someone who knows what they are doing could glance over this so see what I am missing it would be so useful.
/* Credits: * * Example Code and Setup: * Tim Geyssens (http://www.nibble.be) * * Template and Documentation: * Ove Andersen (http://www.eyecatch.no) * */ using System; using umbraco.cms.businesslogic.datatype;
namespace ClassTimeDatatype { public class DataType : AbstractDataEditor { private PrevalueEditor _prevalueeditor;
private ClassTimeDatatype _control = new ClassTimeDatatype();
public DataType() { base.RenderControl = _control; _control.Init += new EventHandler(ControlInit); base.DataEditorControl.OnSave += DataEditorControl_OnSave;
}
public void ControlInit(object sender, EventArgs e) { _control.LoadDataValue(base.Data.Value); }
public override Guid Id { get { return new Guid("68572280-7b8e-49ab-aa74-d2291f29f954"); } }
public override string DataTypeName { get { return "Class Times"; } }
public override umbraco.interfaces.IDataPrevalue PrevalueEditor { get { if (_prevalueeditor == null) { _prevalueeditor = new PrevalueEditor(this); } return _prevalueeditor; } }
_txtDateBegin = new List<TextBox>(); _txtDateEnd = new List<TextBox>(); _chkRoomList = new List<CheckBoxList>(); _chkWeekdayList = new List<CheckBoxList>(); _txtStartTime = new List<TextBox>(); _txtFinishTime = new List<TextBox>();
_dataValue = dataValue;
//Takes database value and configes current controls if (dataValue != null) { XmlDocument retDoc = new XmlDocument(); retDoc.LoadXml(dataValue.ToString());
internal object GetDataValue() { //Returns value from currently loaded controls for saving to database
StringBuilder sb = new StringBuilder();
sb.Append("<ClassTimes>"); for (int i = 0; i < _txtDateBegin.Count; i++) { if (!string.IsNullOrEmpty(_txtDateBegin[i].Text) || !string.IsNullOrEmpty(_txtDateEnd[i].Text) || !string.IsNullOrEmpty(getCheckBoxes(_chkRoomList[i])) || !string.IsNullOrEmpty(getCheckBoxes(_chkWeekdayList[i])) || !string.IsNullOrEmpty(_txtStartTime[i].Text) || !string.IsNullOrEmpty(_txtFinishTime[i].Text)) sb.AppendFormat("<ClassTime dateBegin=\"{0}\" dateEnd=\"{1}\" room=\"{2}\" weekDay=\"{3}\" startTime=\"{4}\" finishTime=\"{5}\"/>", _txtDateBegin[i].Text, _txtDateEnd[i].Text, getCheckBoxes(_chkRoomList[i]), getCheckBoxes(_chkWeekdayList[i]), _txtStartTime[i].Text, _txtFinishTime[i].Text); } sb.Append("</ClassTimes>");
return sb.ToString(); }
protected override void Render(System.Web.UI.HtmlTextWriter writer) { // (Prints out the prevalue editor control)
writer.WriteLine("<table>");
writer.Write("<tr>"); writer.Write("<th>Date Begin (yyyy-mm-dd)</th>"); writer.Write("<th>Date End (yyyy-mm-dd)</th>"); writer.Write("<th>Rooms</th>"); writer.Write("<th>Weekdays</th>"); writer.Write("<th>Start Time (hh:mm)</th>"); writer.Write("<th>End Time (hh:mm)</th>"); writer.Write("</tr>");
foreach (ListItem item in checkBoxList.Items) { item.Selected = valueList.Contains(item.Value); }
}
} }
using System; using System.Web.UI; using System.Web.UI.WebControls; using umbraco.BusinessLogic; using umbraco.cms.businesslogic.datatype; using umbraco.DataLayer; using umbraco.interfaces; using System.Collections.Generic; using System.Xml; using System.Text;
using System.Linq;
namespace ClassTimeDatatype { public class PrevalueEditor : PlaceHolder, IDataPrevalue { #region IDataPrevalue Members
However, I kind of thought that article (Nov 2008) is now slightly outdated. On 4.6 (?) or above, Umbraco recognises that you are trying to store XML data and no longer automatically wraps it in CDATA. My data is indeed being saved correctly to the database and is parseable via an XSL macro. However, it is not being retrieved properly in the CMS editor to make amendments or add new data - and just comes up blank.
Is base.Data.Value empty when you load the control?
If it helps, here is (almost) what I do in DataType Grid in uComponents:
private IList<MyObject> GetStoredValues() {
var list = new List<MyObject>();
var doc = new XmlDocument();
doc.LoadXml(Data.Value.ToString());
// Create and add XML declaration.
XmlDeclaration xmldecl = doc.CreateXmlDeclaration("1.0", null, null);
XmlElement root = doc.DocumentElement;
doc.InsertBefore(xmldecl, root);
// Get stored values from databaseif (root.ChildNodes.Count > 0)
{
foreach (XmlNode container in root.ChildNodes)
{
// Add the node values to a list
var m = new MyObject()
{
Id = int.Parse(container.Attributes["id"].Value),
Value = container.ChildNodes[0].InnerText // The first node
}
list.Add(m);
}
}
return list;
}
Creating a complex custom datatype
I am trying to write my first custom datatype using the 'three class method' - inspired very heavily by Tim and Ove - and I have run into a brick wall...
Requirement : To store an array of values in a single field as parseable XML. I think the actual datatype works fine, shown in the class 'ClassTimeDatatype' below. It's rendering fine in the editor, and when I enter the first row of data and save, the values are persisted to the database. However, when I navigate back to that node in the CMS, the values are not coming back out of the database. I'm sure I'm missing something simple but I cant see what!
If someone who knows what they are doing could glance over this so see what I am missing it would be so useful.
/* Credits:
*
* Example Code and Setup:
* Tim Geyssens (http://www.nibble.be)
*
* Template and Documentation:
* Ove Andersen (http://www.eyecatch.no)
*
*/
using System;
using umbraco.cms.businesslogic.datatype;
namespace ClassTimeDatatype
{
public class DataType : AbstractDataEditor
{
private PrevalueEditor _prevalueeditor;
private ClassTimeDatatype _control = new ClassTimeDatatype();
public DataType()
{
base.RenderControl = _control;
_control.Init += new EventHandler(ControlInit);
base.DataEditorControl.OnSave += DataEditorControl_OnSave;
}
public void ControlInit(object sender, EventArgs e)
{
_control.LoadDataValue(base.Data.Value);
}
public override Guid Id
{
get
{
return new Guid("68572280-7b8e-49ab-aa74-d2291f29f954");
}
}
public override string DataTypeName
{
get
{
return "Class Times";
}
}
public override umbraco.interfaces.IDataPrevalue PrevalueEditor
{
get
{
if (_prevalueeditor == null)
{
_prevalueeditor = new PrevalueEditor(this);
}
return _prevalueeditor;
}
}
void DataEditorControl_OnSave(EventArgs e)
{
base.Data.Value = _control.GetDataValue();
_control.LoadDataValue(base.Data.Value);
}
}
}
using System;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Text;
namespace ClassTimeDatatype
{
public class ClassTimeDatatype : Panel
{
// Datatype Controls
private List<TextBox> _txtDateBegin;
private List<TextBox> _txtDateEnd;
private List<CheckBoxList> _chkRoomList;
private List<CheckBoxList> _chkWeekdayList;
private List<TextBox> _txtStartTime;
private List<TextBox> _txtFinishTime;
private List<ListItem> roomsTemplate;
private List<ListItem> weekdaysTemplate;
private object _dataValue;
protected override void OnInit(EventArgs e)
{
roomsTemplate = new List<ListItem>();
roomsTemplate.Add(new ListItem("Studio 1", "Studio 1"));
roomsTemplate.Add(new ListItem("Studio 2", "Studio 2"));
roomsTemplate.Add(new ListItem("Studio 3", "Studio 3"));
weekdaysTemplate = new List<ListItem>();
weekdaysTemplate.Add(new ListItem("Monday", "Monday"));
weekdaysTemplate.Add(new ListItem("Tuesday", "Tuesday"));
weekdaysTemplate.Add(new ListItem("Wednesday", "Wednesday"));
weekdaysTemplate.Add(new ListItem("Thursday", "Thursday"));
weekdaysTemplate.Add(new ListItem("Friday", "Friday"));
weekdaysTemplate.Add(new ListItem("Saturday", "Saturday"));
weekdaysTemplate.Add(new ListItem("Sunday", "Sunday"));
base.OnInit(e);
}
internal void LoadDataValue(object dataValue)
{
try
{
this.Controls.Clear();
_txtDateBegin = new List<TextBox>();
_txtDateEnd = new List<TextBox>();
_chkRoomList = new List<CheckBoxList>();
_chkWeekdayList = new List<CheckBoxList>();
_txtStartTime = new List<TextBox>();
_txtFinishTime = new List<TextBox>();
_dataValue = dataValue;
//Takes database value and configes current controls
if (dataValue != null)
{
XmlDocument retDoc = new XmlDocument();
retDoc.LoadXml(dataValue.ToString());
foreach (XmlNode node in retDoc.SelectNodes("//ClassTime"))
{
addControlRow(node);
}
}
}
catch
{
}
finally
{
addControlRow();
}
}
internal object GetDataValue()
{
//Returns value from currently loaded controls for saving to database
StringBuilder sb = new StringBuilder();
sb.Append("<ClassTimes>");
for (int i = 0; i < _txtDateBegin.Count; i++)
{
if (!string.IsNullOrEmpty(_txtDateBegin[i].Text) || !string.IsNullOrEmpty(_txtDateEnd[i].Text) || !string.IsNullOrEmpty(getCheckBoxes(_chkRoomList[i])) || !string.IsNullOrEmpty(getCheckBoxes(_chkWeekdayList[i])) || !string.IsNullOrEmpty(_txtStartTime[i].Text) || !string.IsNullOrEmpty(_txtFinishTime[i].Text))
sb.AppendFormat("<ClassTime dateBegin=\"{0}\" dateEnd=\"{1}\" room=\"{2}\" weekDay=\"{3}\" startTime=\"{4}\" finishTime=\"{5}\"/>", _txtDateBegin[i].Text, _txtDateEnd[i].Text, getCheckBoxes(_chkRoomList[i]), getCheckBoxes(_chkWeekdayList[i]), _txtStartTime[i].Text, _txtFinishTime[i].Text);
}
sb.Append("</ClassTimes>");
return sb.ToString();
}
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
// (Prints out the prevalue editor control)
writer.WriteLine("<table>");
writer.Write("<tr>");
writer.Write("<th>Date Begin (yyyy-mm-dd)</th>");
writer.Write("<th>Date End (yyyy-mm-dd)</th>");
writer.Write("<th>Rooms</th>");
writer.Write("<th>Weekdays</th>");
writer.Write("<th>Start Time (hh:mm)</th>");
writer.Write("<th>End Time (hh:mm)</th>");
writer.Write("</tr>");
for (int i = 0; i < _txtDateBegin.Count; i++)
{
writer.Write("<tr>");
writer.Write("<th>"); _txtDateBegin[i].RenderControl(writer); writer.Write("</th>");
writer.Write("<th>"); _txtDateEnd[i].RenderControl(writer); writer.Write("</th>");
writer.Write("<th>"); _chkRoomList[i].RenderControl(writer); writer.Write("</th>");
writer.Write("<th>"); _chkWeekdayList[i].RenderControl(writer); writer.Write("</th>");
writer.Write("<th>"); _txtStartTime[i].RenderControl(writer); writer.Write("</th>");
writer.Write("<th>"); _txtFinishTime[i].RenderControl(writer); writer.Write("</th>");
writer.Write("</tr>");
}
writer.Write("</table>");
}
private void addControlRow()
{
int count = _txtDateBegin.Count;
TextBox txtDateBegin = new TextBox();
txtDateBegin.ID = string.Format("txtDateBegin_{0}", count);
txtDateBegin.CssClass = "umbDateTimePicker";
_txtDateBegin.Add(txtDateBegin);
Controls.Add(txtDateBegin);
TextBox txtDateEnd = new TextBox();
txtDateEnd.ID = string.Format("txtDateEnd_{0}", count);
txtDateEnd.CssClass = "umbDateTimePicker";
_txtDateEnd.Add(txtDateEnd);
Controls.Add(txtDateEnd);
CheckBoxList chkRoomList = new CheckBoxList();
chkRoomList.ID = string.Format("chkRoomList_{0}", count);
chkRoomList.Items.AddRange(roomsTemplate.ToArray());
_chkRoomList.Add(chkRoomList);
Controls.Add(chkRoomList);
CheckBoxList chkWeekdayList = new CheckBoxList();
chkWeekdayList.ID = string.Format("chkWeekdayList_{0}", count);
chkWeekdayList.Items.AddRange(weekdaysTemplate.ToArray());
_chkWeekdayList.Add(chkWeekdayList);
Controls.Add(chkWeekdayList);
TextBox txtStartTime = new TextBox();
txtStartTime.ID = string.Format("txtStartTime_{0}", count);
_txtStartTime.Add(txtStartTime);
Controls.Add(txtStartTime);
TextBox txtFinishTime = new TextBox();
txtFinishTime.ID = string.Format("txtFinishTime_{0}", count);
_txtFinishTime.Add(txtFinishTime);
Controls.Add(txtFinishTime);
}
private void addControlRow(XmlNode node)
{
int count = _txtDateBegin.Count;
TextBox txtDateBegin = new TextBox();
txtDateBegin.ID = string.Format("txtDateBegin_{0}", count);
txtDateBegin.Text = node.Attributes["dateStart"].Value;
_txtDateBegin.Add(txtDateBegin);
Controls.Add(txtDateBegin);
TextBox txtDateEnd = new TextBox();
txtDateEnd.ID = string.Format("txtDateEnd_{0}", count);
txtDateEnd.Text = node.Attributes["dateEnd"].Value;
_txtDateEnd.Add(txtDateEnd);
Controls.Add(txtDateEnd);
CheckBoxList chkRoomList = new CheckBoxList();
chkRoomList.ID = string.Format("chkRoomList_{0}", count);
chkRoomList.Items.AddRange(roomsTemplate.ToArray());
selectCheckBoxes(chkRoomList, node.Attributes["room"].Value);
_chkRoomList.Add(chkRoomList);
Controls.Add(chkRoomList);
CheckBoxList chkWeekdayList = new CheckBoxList();
chkWeekdayList.ID = string.Format("chkWeekdayList_{0}", count);
chkWeekdayList.Items.AddRange(weekdaysTemplate.ToArray());
selectCheckBoxes(chkWeekdayList, node.Attributes["weekday"].Value);
_chkWeekdayList.Add(chkWeekdayList);
Controls.Add(chkWeekdayList);
TextBox txtStartTime = new TextBox();
txtStartTime.ID = string.Format("txtStartTime_{0}", count);
txtStartTime.Text = node.Attributes["startTime"].Value;
_txtStartTime.Add(txtStartTime);
Controls.Add(txtStartTime);
TextBox txtFinishTime = new TextBox();
txtFinishTime.ID = string.Format("txtFinishTime_{0}", count);
txtFinishTime.Text = node.Attributes["finishTime"].Value;
_txtFinishTime.Add(txtFinishTime);
Controls.Add(txtFinishTime);
}
private string getCheckBoxes(CheckBoxList checkBoxList)
{
StringBuilder sb = new StringBuilder();
foreach (ListItem item in checkBoxList.Items)
{
if (item.Selected) sb.AppendFormat("{0},", item.Value);
}
return sb.ToString();
}
private void selectCheckBoxes(CheckBoxList checkBoxList, string fieldValue)
{
string[] valueList = fieldValue.Split(new string[] { "," }, StringSplitOptions.RemoveEmptyEntries);
foreach (ListItem item in checkBoxList.Items)
{
item.Selected = valueList.Contains(item.Value);
}
}
}
}
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using umbraco.BusinessLogic;
using umbraco.cms.businesslogic.datatype;
using umbraco.DataLayer;
using umbraco.interfaces;
using System.Collections.Generic;
using System.Xml;
using System.Text;
using System.Linq;
namespace ClassTimeDatatype
{
public class PrevalueEditor : PlaceHolder, IDataPrevalue
{
#region IDataPrevalue Members
// Referenced datatype
private readonly BaseDataType _datatype;
public PrevalueEditor(DataType dataType)
{
_datatype = dataType;
SetupChildControls();
}
private void SetupChildControls()
{
}
public Control Editor
{
get
{
return this;
}
}
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
}
public void Save()
{
_datatype.DBType = (DBTypes)Enum.Parse(typeof(DBTypes), DBTypes.Ntext.ToString(), true);
SqlHelper.ExecuteNonQuery("delete from cmsDataTypePreValues where datatypenodeid = @dtdefid", SqlHelper.CreateParameter("@dtdefid", _datatype.DataTypeDefinitionId));
}
protected override void Render(HtmlTextWriter writer)
{
base.Render(writer);
}
#endregion
public static ISqlHelper SqlHelper
{
get
{
return Application.SqlHelper;
}
}
}
}
This should help you: http://www.nibble.be/?p=51
Thanks Daniel -
However, I kind of thought that article (Nov 2008) is now slightly outdated. On 4.6 (?) or above, Umbraco recognises that you are trying to store XML data and no longer automatically wraps it in CDATA. My data is indeed being saved correctly to the database and is parseable via an XSL macro. However, it is not being retrieved properly in the CMS editor to make amendments or add new data - and just comes up blank.
Does that make sense?
Is base.Data.Value empty when you load the control?
If it helps, here is (almost) what I do in DataType Grid in uComponents:
Comment author was deleted
If you are running at least version 4.6 of umbraco I wouldn't touch the "3 class method" anymore and simply go for the usercontrol wrapper
Since you can also have settings + xml properties using that
Some details:
http://www.nibble.be/?p=99
http://www.nibble.be/?p=100
http://stream.umbraco.org/video/1533917/tim-geyssens-master-of
is working on a reply...