Tips & Tricks: add new structure’s field into web content
January 15, 2011 42 Comments
During my everyday work some people asked me: „Is it possible to add new type of structure’s field. If yes, how can I do it?”. In my opinion the fastest way to achieve this result is overriding few files in ext plugin or hook. We decided to use hook. In this post We show you the example, how we did it. Our new field is a datetime component.
Firstly, we should know in liferay there are two places where we can add and edit structure’s fields, So we must check our new functionality two times:
- CP -> Web content -> Structures (tab) -> Add or edit structure
- CP -> Web content -> add or edit web content -> change structure
Files which are responsible to render and serve structure’s fields in first point:
- html/portlet/journal/js/main.js
- html/portlet/journal/edit_article_content_xsd_el.jsp
- html/portlet/journal/edit_structure_xsd_el.jsp
In second point:
- html/portlet/journal/edit_article_structure extra.jspf
- html/portlet/journal/edit_article_extra.jsp
Ok, let’s get started.
1. Create a new hook and configure that it can override jsps files. For example:
<hook> <custom-jsp-dir>/custom_jsps</custom-jsp-dir> </hook>
2. Copy html/portlet/journal/js/main.js into custom_jsps folder. In this step we must register new model’s field (below registerFieldModel method’s declaration).
registerFieldModel('Date', 'date', 'DateField', true);
Next we add new field’s type to map called „model” in function _fieldInstanceFactory()
var model = { 'boolean': Journal.FieldModel.Boolean, 'document_library': Journal.FieldModel.DocumentLibrary, 'image': Journal.FieldModel.Image, 'image_gallery': Journal.FieldModel.ImageGallery, 'link_to_layout': Journal.FieldModel.LinkToPage, 'list': Journal.FieldModel.List, 'multi-list': Journal.FieldModel.MultiList, 'selection_break': Journal.FieldModel.SelectionBreak, 'text': Journal.FieldModel.Text, 'text_area': Journal.FieldModel.TextArea, 'text_box': Journal.FieldModel.TextBox, 'date': Journal.FieldModel.Date };
3. Copy html/portlet/journal/edit_article_content_xsd_el.jsp and add code inside journal-article-component-container. We compare our new type with elType variable.
<c:if test='<%= elType.equals("date") %>'> <%@ include file="/html/portlet/journal/edit_article_content_xsd_date_el.jspf" %> </c:if>
Ok, it looks fine, but what’s is in edit_article_content_xsd_date_el.jspf“? This is the main point of this tutorial. In this file we define „look and feel” our new component. For example:
<aui:fieldset> <% Calendar cal = CalendarFactoryUtil.getCalendar(timeZone, locale); if (elContent != null && !elContent.equals("")) { DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd H:m"); Date date = (Date)formatter.parse(elContent); cal.setTime(date); } %> <aui:field-wrapper> <div> <liferay-ui:input-date dayParam="dateDay" dayValue="<%= cal.get(Calendar.DATE) %>" disabled="<%= false %>" firstDayOfWeek="<%= cal.getFirstDayOfWeek() - 1 %>" monthParam="dateMonth" monthValue="<%= cal.get(Calendar.MONTH) %>" yearParam="dateYear" yearValue="<%= cal.get(Calendar.YEAR) %>" yearRangeStart="<%= cal.get(Calendar.YEAR) %>" yearRangeEnd="<%= cal.get(Calendar.YEAR) + 5 %>" /> <liferay-ui:input-time amPmParam="dateAmPm" amPmValue="<%= cal.get(Calendar.AM_PM) %>" hourParam="dateHour" hourValue="<%= cal.get(Calendar.HOUR) %>" minuteParam="dateMinute" minuteValue="<%= cal.get(Calendar.MINUTE) %>" minuteInterval="<%= 1 %>" /> </div> </aui:field-wrapper> </aui:fieldset>
You must know that elContent variable contains our field’s value (date in this example).
4. Copy html/portlet/journal/edit_structure_xsd_el.jsp and add our new type into select’s list.
<option <%= elType.equals("date") ? "selected" : "" %> value="date"><liferay-ui:message key="date" /></option>
Okey, let’s deploy our hook and look into add structure form. What are we seeing? Oh, our new field’s type.
What’s next? We must allow to edit structure directly in edit article form.
5. Let’s look inside edit_article_structure_extra.jspf file and copy them. Then we define „look and feel” our new field inside journalFieldModelContainer div by adding new div with dataType=”<type>” attribute.
<div dataType="date"> <% String elContent = ""; %> <%@ include file="/html/portlet/journal/edit_article_content_xsd_date_el.jspf" %> </div>
6. In file html/portlet/journal/edit_article_extra.jsp we add to menu (lists of field’s types) our new component because we want allow „drag and drop” functionality. So we create new div’s tag with dataType=”<type>” attribute inside journalComponentList div.
<div dataType="date"> <liferay-ui:message key="date" /> </div>
We have new component with great design but how we save selected date and time value. Let’s see main.js file. There is getContent method where there are a lot of “if” conditions. So, we must write a new one:
if (type == 'date') { var dateContents = componentContainer.all('select'); var date = [Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN, Number.NaN]; if (dateContents) { dateContents.each( function(select, selectIndex, selects) { var options = select.all('option'); if (options) { options.each( function(option, optionIndex, options) { if (option.get('selected')) { //name selecta, zawierajacego wybrany item var selectName = select.get('name'); var selectedValue = option.val(); if (selectName.match(/dateDay$/)) { date[0] = selectedValue; } else if (selectName.match(/dateMonth$/)) { date[1] = selectedValue; } else if (selectName.match(/dateYear$/)) { date[2] = selectedValue; } else if (selectName.match(/dateHour$/)) { date[3] = selectedValue; } else if (selectName.match(/dateMinute$/)) { date[4] = selectedValue; } else if (selectName.match(/dateAmPm$/)) { date[6] = selectedValue; } } }, select ); } } ); } content = instance.getDateAsString(date); }
Also we must define the „getDateAsString” function
getDateAsString: function(stringDateArray) { if (stringDateArray.length != 7) { return ''; } var intDateArray = new Array(stringDateArray.length); var notNaNExist = false; for (var i = 0; i < stringDateArray.length; i++) { intDateArray[i] = parseInt(stringDateArray[i], 10); if (intDateArray[i] != NaN) { notNaNExist = true; } } if (!notNaNExist) { return ''; } var day = intDateArray[0]; var month = intDateArray[1]; var year = intDateArray[2]; var hour = intDateArray[3]; var minute = intDateArray[4]; var second = 0; var ampm = intDateArray[6]; if (!isNaN(month)) { month = month + 1; } if (ampm == 1 && !isNaN(hour)) { hour = hour + 12; } if (month < 10) { month = '0' + month; } if (day < 10) { day = '0' + day; } if (hour < 10) { hour = '0' + hour; } if (minute < 10) { minute = '0' + minute; } if (second < 10) { second = '0' + second; } return year + '-' + month + '-' + day + ' ' + hour + ':' + minute; }
I know that my code is not optimal. This post is only tutorial how to write new fields of structure. If you have better idea how resolve this problem just write to me.
If you want download all source files or download ready to deploy war file click links:
* I wrote this hook using trunk version of liferay CE (rev. 70450)