« Back

Expandos - What are they? And how do they help me? (Liferay Portal 5.0.1+)

Company Blogs April 24, 2008 By Ray Augé Staff

Updated: Wed May 28 10:53:09 EDT 2008.

See the follow-up article here.

In Javascript, "expando" means "to attach additional properties to an object".

That's a little bit of hint as to what Expandos are in Liferay.

In Liferay the Expando service is a "generic" service which allows you to dynamically define a collection of data. This data can be

  • typed (boolean, Date, double, int, long, short, String, and arrays of all those)
  • associated with a specific entity (e.g. 'com.liferay.portal.model.User')
  • arranged into any number of "columns"
  • available to plugins
  • accessed from Velocity templates
  • accessed via Liferay's JSON API through AJAX

 

The service also provides all the CRUD methods you typically need (Create/Retreive/Update/Delete).

You often need a way to add some custom field to Users or other portal entities and sometimes you need to do it fast and with minimal effort... it might even be temporary. Well, Expando is comming to the rescue.

To demonstrate how Expando works, I'll take you through a sample application which runs completely in as a velocity Journal Template.

So first off, lets get the required details out of the way.

Step 1) Create a Journal Structure. The structure is a requirement of any Journal Template because the connection between a Journal Article and a Journal Template is a function of the Journal Structure.

A very basic Structure is all we need in this case:

<root>
	<dynamic-element name='content' type='text'></dynamic-element>
</root>

Step 2) Create our Template

Our template "Language Type" will be VM (for Velocity), and we'll disable template caching by unckecking "Cacheable".

We'll start simple and work our way through the code. The first few lines will get some utility objects that we'll use later, and set a title for our app.

#set ($locale = $localeUtil.fromLanguageId($request.get("locale")))
#set ($dateFormatDateTime = $dateFormats.getDateTime($locale))

<h1>First Expando Bank</h1>

Step 3) We create our Article.

On the Article tab we essentially click "Add Article", give it a "Name", choose the Structure we created in Step 1) and click "Save".

The result should be something like this:

Now, the first task when using Expando is to define our table. Expando lets you do this programatically and with very little code.

Give our table a name.

...
<h1>First Expando Bank</h1>

#set ($accountsTableName = "AccountsTable")

Check to see if the table exists, and if not, create it.

...
#set ($accountsTableName = "AccountsTable")

#set ($accountsTable = $expandoTableLocalService.getTable($accountsTableName, $accountsTableName))

#if (!$accountsTable)
#set ($accountsTable = $expandoTableLocalService.addTable($accountsTableName, $accountsTableName))
#end

We now have a table and we want to add columns to it. Since we're building a Bank app fields we need are firstName, lastName, and balance. We'll also keep track of the last time the account was updated, modifiedDate. The account number will be automitically generated and will represent the primary key for our table. In Expando, this primary key doesn't require a standalone column.

Also, we don't want this process to happen every time, so we'll only do it when the table is first created.

...
#if (!$accountsTable)
	#set ($accountsTable = $expandoTableLocalService.addTable($accountsTableName, $accountsTableName))

	#set ($accountsTableId = $accountsTable.getTableId())

#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "firstName", 15)) ## STRING
#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "lastName", 15)) ## STRING
#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "balance", 5)) ## DOUBLE
#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "modifiedDate", 3)) ## DATE
#end

Notice how we specified, for each column, an integer as the last parameter. These integer constants are defined in com.liferay.portlet.expando.model.ExpandoColumnConstants, and include the types I mentioned above.

Now that we have our table and columns setup, we want to add some logic that will detect and handle the various operations of our application. These are the CRUD operations we need for a complete app.

Let's start with some request handling and param setup.

...
#set ($renderUrl = $request.get("render-url"))
#set ($namespace = $request.get("portlet-namespace"))
#set ($cmd = $request.get("parameters").get("cmd"))

#set ($firstName = '')
#set ($lastName = '')
#set ($balance = 0.0)

I won't go into much detail regarding the request handling abilitites of Journal Templates. Suffice it to say that it supports it. (I'll cover that in another Blog entry.)

Determine whether we have been passed an account number. As I mentioned earlier, Expando "records" have a primary key. That field is called "classPK", so we will keep that name to re-inforce the concept.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

Now we're going to check and see what operation (if any) we were asked to perform.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
...
#elseif ($cmd.equals("delete"))
...
#elseif ($cmd.equals("edit"))
...
#end

Adding/Updating an account is our first operation. In this case, we get the params from the request.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
	#set ($firstName = $request.get("parameters").get("firstName"))
#set ($lastName = $request.get("parameters").get("lastName"))
#set ($balance = $getterUtil.getDouble($request.get("parameters").get("balance")))
#set ($date = $dateTool.getDate())
#elseif ($cmd.equals("delete")) ... #elseif ($cmd.equals("edit")) ... #end

Do some form input checking (this one just does a basic check).

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
	#set ($firstName = $request.get("parameters").get("firstName"))
	#set ($lastName = $request.get("parameters").get("lastName"))
	#set ($balance = $getterUtil.getDouble($request.get("parameters").get("balance")))
	#set ($date = $dateTool.getDate())

	#if (($cmd.equals("add") && !$firstName.equals("") && !$lastName.equals("") && $balance >= 50) || ($cmd.equals("update") && !$firstName.equals("") && !$lastName.equals("")))
...
#else
Please fill the form completely in order to create an account. The minimum amount of cash required to create an account is $50.
#end
#elseif ($cmd.equals("delete")) ... #elseif ($cmd.equals("edit")) ... #end

So now, if we're ok, store the data.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
	#set ($firstName = $request.get("parameters").get("firstName"))
	#set ($lastName = $request.get("parameters").get("lastName"))
	#set ($balance = $getterUtil.getDouble($request.get("parameters").get("balance")))
	#set ($date = $dateTool.getDate())

	#if (($cmd.equals("add") && !$firstName.equals("") && !$lastName.equals("") && $balance >= 50) || ($cmd.equals("update") && !$firstName.equals("") && !$lastName.equals("")))
		#if ($classPK <= 0)
#set ($classPK = $dateTool.getDate().getTime())
#end

#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "firstName", $classPK, $firstName))
#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "lastName", $classPK, $lastName))
#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "balance", $classPK, $balance))
#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "modifiedDate", $classPK, $date))

#if ($cmd.equals("update"))
Thank you, ${firstName}, for updating your account with our bank!
#else
Thank you, ${firstName}, for creating an account with our bank!
#end
#else Please fill the form completely in order to create an account. The minimum amount of cash required to create an account is $50. #end #elseif ($cmd.equals("delete")) ... #elseif ($cmd.equals("edit")) ... #end

Before we can continue we need to do some cleanup.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
	#set ($firstName = $request.get("parameters").get("firstName"))
	#set ($lastName = $request.get("parameters").get("lastName"))
	#set ($balance = $getterUtil.getDouble($request.get("parameters").get("balance")))
	#set ($date = $dateTool.getDate())

	#if (($cmd.equals("add") && !$firstName.equals("") && !$lastName.equals("") && $balance >= 50) || ($cmd.equals("update") && !$firstName.equals("") && !$lastName.equals("")))
		#if ($classPK <= 0)
			#set ($classPK = $dateTool.getDate().getTime())
		#end

		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "firstName", $classPK, $firstName))
		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "lastName", $classPK, $lastName))
		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "balance", $classPK, $balance))
		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "modifiedDate", $classPK, $date))

		#if ($cmd.equals("update"))
			Thank you, ${firstName}, for updating your account with our bank!
		#else
			Thank you, ${firstName}, for creating an account with our bank!
		#end
	#else
		Please fill the form completely in order to create an account. The minimum amount of cash required to create an account is $50.
	#end

	#set ($classPK = 0)
#set ($firstName = '')
#set ($lastName = '')
#set ($balance = 0.0)
#elseif ($cmd.equals("delete")) ... #elseif ($cmd.equals("edit")) ... #end

The next operation to handle is Deleting an account.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
	...
#elseif ($cmd.equals("delete"))
	#if ($classPK > 0)
#set ($V = $expandoRowLocalService.deleteRow($accountsTableName, $accountsTableName, $classPK))

Account deleted!

#set ($classPK = 0)
#end
#elseif ($cmd.equals("edit")) ... #end

That's it... Pretty simple? Sure is! Next, the Edit operation.

...
#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

#if ($cmd.equals("add") || $cmd.equals("update"))
	...
#elseif ($cmd.equals("delete"))
	...
#elseif ($cmd.equals("edit"))
	Editting...

#if ($classPK > 0)
#set ($firstName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "firstName", $classPK, ""))
#set ($lastName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "lastName", $classPK, ""))
#set ($balance = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "balance", $classPK, 0.0))
#end
#end

So, we retrieved the data stored in the requested account and put them into some placeholder variables.

Finally, we're ready to display some UI elements. We'll have two different views, a table listing the accounts, and a form for adding/editing accounts.

...
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
...
#else
...
#end

Ok, so when we show the table we need some frillies like a "Create Account" button and some column headers.

...
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
	<input type="button" value="Create Account" onClick="self.location = '${renderUrl}&${namespace}cmd=edit';" />

<br /><br />

<table class="lfr-table">
<tr>
<th>Account Number</th>
<th>First Name</th>
<th>Last Name</th>
<th>Balance</th>
<th>Modified Date</th>
<th><!----></th>
</tr>
#else ... #end

Here we're going to add the calls to get the number of, and list of all the existing accounts.

...
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
	<input type="button" value="Create Account" onClick="self.location = '${renderUrl}&${namespace}cmd=edit';" />
	
	<br /><br />

	<table class="lfr-table">
	<tr>
		<th>Account Number</th>
		<th>First Name</th>
		<th>Last Name</th>
		<th>Balance</th>
		<th>Modified Date</th>
		<th><!----></th>
	</tr>

	#set ($rowsCount = $expandoRowLocalService.getRowsCount($accountsTableName, $accountsTableName))
#set ($rows = $expandoRowLocalService.getRows($accountsTableName, $accountsTableName, -1, -1))
#else ... #end

Iterate through the list.

...
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
	<input type="button" value="Create Account" onClick="self.location = '${renderUrl}&${namespace}cmd=edit';" />
	
	<br /><br />

	<table class="lfr-table">
	<tr>
		<th>Account Number</th>
		<th>First Name</th>
		<th>Last Name</th>
		<th>Balance</th>
		<th>Modified Date</th>
		<th><!----></th>
	</tr>

	#set ($rowsCount = $expandoRowLocalService.getRowsCount($accountsTableName, $accountsTableName))
	#set ($rows = $expandoRowLocalService.getRows($accountsTableName, $accountsTableName, -1, -1))

	#foreach($row in $rows)
#set ($currentClassPK = $row.getClassPK())

...
#end

#if ($rowsCount <= 0)
<tr>
<td colspan="5">No Accounts were found.</td>
</tr>
#end

</table>

# of Accounts: ${rowsCount}
#else ... #end

Let's draw each table row (the accounts).

...
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
	<input type="button" value="Create Account" onClick="self.location = '${renderUrl}&${namespace}cmd=edit';" />
	
	<br /><br />

	<table class="lfr-table">
	<tr>
		<th>Account Number</th>
		<th>First Name</th>
		<th>Last Name</th>
		<th>Balance</th>
		<th>Modified Date</th>
		<th><!----></th>
	</tr>

	#set ($rowsCount = $expandoRowLocalService.getRowsCount($accountsTableName, $accountsTableName))
	#set ($rows = $expandoRowLocalService.getRows($accountsTableName, $accountsTableName, -1, -1))

	#foreach($row in $rows)
		#set ($currentClassPK = $row.getClassPK())

		<tr>
<td>${currentClassPK}</td>

#set ($currentFirstName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "firstName", $currentClassPK, ""))
<td>${currentFirstName}</td>

#set ($currentLastName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "lastName", $currentClassPK, ""))
<td>${currentLastName}</td>

#set ($currentBalance = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "balance", $currentClassPK, 0.0))
<td align="right">${numberTool.currency($currentBalance)}</td>

#set ($currentModifiedDate = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "modifiedDate", $currentClassPK, $dateTool.getDate()))
<td>${dateFormatDateTime.format($currentModifiedDate)}</td>

<td>
<a href="${renderUrl}&amp;${namespace}cmd=edit&amp;${namespace}classPK=${currentClassPK}">Edit</a> |
<a href="${renderUrl}&amp;${namespace}cmd=delete&amp;${namespace}classPK=${currentClassPK}">Delete</a>
</td>
</tr>
#end #if ($rowsCount <= 0) <tr> <td colspan="5">No Accounts were found.</td> </tr> #end </table> # of Accounts: ${rowsCount} #else ... #end

Well, that was a mouthfull, but it should be pretty familliar design pattern. The general theme here is "ease of use". It didn't take much time or anything too tricky to get at the data.

The final piece of code is of course the input form.

...
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
	...
#else
	<form action="$renderUrl" method="post" name="${namespace}fm10">
<input type="hidden" name="${namespace}classPK" value="${classPK}" />
<input type="hidden" name="${namespace}cmd"
#if ($classPK > 0)
value="update"
#else
value="add"
#end
/>

<table class="lfr-table">
<tr>
<td>First Name:</td>
<td>
<input type="text" name="${namespace}firstName" value="${firstName}" />
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<input type="text" name="${namespace}lastName" value="${lastName}" />
</td>
</tr>
<tr>
<td>Balance:</td>
<td>
<input type="text" name="${namespace}balance" value="${numberTool.format($balance)}" />
</td>
</tr>
</table>

<br />

<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="self.location = '${renderUrl}'" />
</form>
#end

Two significant items to note are ${renderUrl} and ${namespace}. Remember that we're running within the context of a portlet, which means that we have to get a base url from the portal (we can't just use any old url). The code we wrote earlier got us an url from the request. Secondly, we need to namespace any parameter we're going to post back to that url so that the container knows to which portlet it belongs (there might be more that one portlet on the page).

Now you have a result which should look something like this:

Creating/Editting an account:

List with 4 accounts:

Here's the whole template with comments.

#set ($locale = $localeUtil.fromLanguageId($request.get("locale")))
#set ($dateFormatDateTime = $dateFormats.getDateTime($locale))

<h1>First Expando Bank</h1>

##
## Define the "name" for our ExpandoTable.
##

#set ($accountsTableName = "AccountsTable")

##
## Get/Create the ExpandoTable to hold our data.
##

#set ($accountsTable = $expandoTableLocalService.getTable($accountsTableName, $accountsTableName))

#if (!$accountsTable)
	#set ($accountsTable = $expandoTableLocalService.addTable($accountsTableName, $accountsTableName))

	#set ($accountsTableId = $accountsTable.getTableId())

	##
	## Create an ExpandoColumn for each field in the form.
	##

	#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "firstName", 15)) ## STRING
	#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "lastName", 15)) ## STRING
	#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "balance", 5)) ## DOUBLE
	#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "modifiedDate", 3)) ## DATE
#end

##
## Do some request handling setup.
##

#set ($renderUrl = $request.get("render-url"))
#set ($namespace = $request.get("portlet-namespace"))
#set ($cmd = $request.get("parameters").get("cmd"))

#set ($firstName = '')
#set ($lastName = '')
#set ($balance = 0.0)

##
## Check to see if a classPK was passed in the request.
##

#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

##
## Check if we have received a form submission?
##

#if ($cmd.equals("add") || $cmd.equals("update"))
	##
	## Let's get the form values from the request.
	##
	
	#set ($firstName = $request.get("parameters").get("firstName"))
	#set ($lastName = $request.get("parameters").get("lastName"))
	#set ($balance = $getterUtil.getDouble($request.get("parameters").get("balance")))
	#set ($date = $dateTool.getDate())

	##
	## Validate the params to see if we should proceed.
	##

	#if (($cmd.equals("add") && !$firstName.equals("") && !$lastName.equals("") && $balance >= 50) || ($cmd.equals("update") && !$firstName.equals("") && !$lastName.equals("")))
		##
		## Check to see if it's a new Account.
		##
		
		#if ($classPK <= 0)
			#set ($classPK = $dateTool.getDate().getTime())
		#end

		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "firstName", $classPK, $firstName))
		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "lastName", $classPK, $lastName))
		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "balance", $classPK, $balance))
		#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "modifiedDate", $classPK, $date))

		##
		## Show a response.
		##
		
		#if ($cmd.equals("update"))
			Thank you, ${firstName}, for updating your account with our bank!
		#else
			Thank you, ${firstName}, for creating an account with our bank!
		#end

	#else
		Please fill the form completely in order to create an account. The minimum amount of cash required to create an account is $50.
	#end

	#set ($classPK = 0)
	#set ($firstName = '')
	#set ($lastName = '')
	#set ($balance = 0.0)

#elseif ($cmd.equals("delete"))
	##
	## Delete the specified Row.
	##
	
	#if ($classPK > 0)
		#set ($V = $expandoRowLocalService.deleteRow($accountsTableName, $accountsTableName, $classPK))

		Account deleted!

		#set ($classPK = 0)
	#end
#elseif ($cmd.equals("edit"))
	##
	## Edit the specified Row.
	##
	
	Editting...

	#if ($classPK > 0)
		##
		## Get the account specific values
		##

		#set ($firstName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "firstName", $classPK, ""))
		#set ($lastName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "lastName", $classPK, ""))
		#set ($balance = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "balance", $classPK, 0.0))
	#end
#end
	
<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
	##
	## Now we're into the display logic.
	##
	
	<input type="button" value="Create Account" onClick="self.location = '${renderUrl}&${namespace}cmd=edit';" />
	
	<br /><br />

	<table class="lfr-table">
	<tr>
		<th>Account Number</th>
		<th>First Name</th>
		<th>Last Name</th>
		<th>Balance</th>
		<th>Modified Date</th>
		<th><!----></th>
	</tr>

	##
	## Get all the current records in our ExpandoTable. We can paginate by passing a
	## "begin" and "end" params.
	##

	#set ($rowsCount = $expandoRowLocalService.getRowsCount($accountsTableName, $accountsTableName))
	#set ($rows = $expandoRowLocalService.getRows($accountsTableName, $accountsTableName, -1, -1))

	#foreach($row in $rows)
		##
		## Get the classPK of this row.
		##

		#set ($currentClassPK = $row.getClassPK())

		<tr>
			<td>${currentClassPK}</td>

			#set ($currentFirstName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "firstName", $currentClassPK, ""))
			<td>${currentFirstName}</td>
		
			#set ($currentLastName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "lastName", $currentClassPK, ""))
			<td>${currentLastName}</td>
		
			#set ($currentBalance = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "balance", $currentClassPK, 0.0))
			<td align="right">${numberTool.currency($currentBalance)}</td>
		
			#set ($currentModifiedDate = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "modifiedDate", $currentClassPK, $dateTool.getDate()))
			<td>${dateFormatDateTime.format($currentModifiedDate)}</td>
		
			<td>
				<a href="${renderUrl}&amp;${namespace}cmd=edit&amp;${namespace}classPK=${currentClassPK}">Edit</a> |
				<a href="${renderUrl}&amp;${namespace}cmd=delete&amp;${namespace}classPK=${currentClassPK}">Delete</a>
			</td>
		</tr>
	#end

	#if ($rowsCount <= 0)
		<tr>
			<td colspan="5">No Accounts were found.</td>
		</tr>
	#end

	</table>

	# of Accounts: ${rowsCount}
#else
	##
	## Here we have our input form.
	##

	<form action="$renderUrl" method="post" name="${namespace}fm10">
	<input type="hidden" name="${namespace}classPK" value="${classPK}" />
	<input type="hidden" name="${namespace}cmd"
	#if ($classPK > 0)
		value="update"
	#else
		value="add"
	#end
	/>

	<table class="lfr-table">
	<tr>
		<td>First Name:</td>
		<td>
			<input type="text" name="${namespace}firstName" value="${firstName}" />
		</td>
	</tr>
	<tr>
		<td>Last Name:</td>
		<td>
			<input type="text" name="${namespace}lastName" value="${lastName}" />
		</td>
	</tr>
	<tr>
		<td>Balance:</td>
		<td>
			<input type="text" name="${namespace}balance" value="${numberTool.format($balance)}" />
		</td>
	</tr>
	</table>

	<br />
	
	<input type="submit" value="Save" />
	<input type="button" value="Cancel" onclick="self.location = '${renderUrl}'" />
	</form>
#end

<br /><br />
Threaded Replies Author Date
This is awesome Ray. Thanks for the... Jerry Niu April 24, 2008 10:08 AM
Thanks Jerry! Ray Augé April 24, 2008 10:55 AM
Wow... Now that's cool. James Min April 24, 2008 3:26 PM
Nice feature! In (since) which version is that... Tobias Käfer April 24, 2008 9:48 PM
It's currently in trunk, and should be in 5.0.2. Ray Augé April 24, 2008 9:54 PM
Ray, I would update the title of the blog post... Alvaro Del Castillo May 28, 2008 2:34 AM
It's updated now. Thanks Alvaro! Ray Augé May 28, 2008 8:07 AM
Great work Ray, thanks! I was searching for a... Gabor Nagy June 13, 2008 12:38 AM
Hello Gabor, The API has changed slightly.... Ray Augé June 13, 2008 5:47 AM
Thank you Ray! It sounds good! We waiting for... Gabor Nagy June 13, 2008 7:50 AM
Thanks for the post. I initially approached... b v j July 17, 2008 3:34 PM
It is possible in Journal to create reusable VM... Ray Augé July 19, 2008 12:46 PM
Perfect - thank you. b v j July 19, 2008 9:40 PM
Thanks for this, Ray! I have just gotten... Joshua Asbury August 12, 2008 11:05 AM
Hi Ray! I am using UserGroupLocalService in VM... nidhi singh December 12, 2008 4:19 AM
I'm trying to use this feature to display data... Thomas Kellerer December 19, 2008 6:53 AM
Try: #set ($accountsTable =... Ray Augé December 19, 2008 7:21 AM
Great that worked, thanks a lot Thomas Kellerer December 20, 2008 12:08 PM
Hi Ray, great! On question, do the attribute... Jonas Yuan December 29, 2008 6:26 PM
If there exists a localization for the... Ray Augé December 30, 2008 7:11 AM
Hi Ray, Thank you! How about custom types in... Jonas Yuan February 14, 2009 6:04 PM
Why don't you just store a reference to one of... Ray Augé February 14, 2009 9:45 PM
Hi, I tried this again with 5.2.1, and it does... Thomas Kellerer February 13, 2009 6:21 AM
I found my error. Apparently the table type has... Thomas Kellerer February 16, 2009 3:11 AM
Hi Ray, I read this blog, and create new table... nidhi singh January 21, 2009 9:46 PM
When you set the className of the table to... Ray Augé February 6, 2009 5:29 AM
HI RAY, iam trying expandos thing in portal... Auditya manikanta Vadrevu February 5, 2009 11:20 PM
The API has changed slightly. (I have not... Ray Augé February 6, 2009 5:33 AM
After getting it to work with 5.2 I have a... Thomas Kellerer February 16, 2009 3:43 AM
I'll have to test this. Can you file a bug... Ray Augé February 16, 2009 6:32 AM
Thanks for the answer. It's hard do create a... Thomas Kellerer February 16, 2009 7:19 AM
This is very cool.. we are hoping to use it to... Chris Whittle February 17, 2009 5:33 AM
nevermind I missed the structure.... Heres the... Chris Whittle February 17, 2009 12:14 PM
nevermind I missed the structure.... Heres the... Chris Whittle February 17, 2009 12:14 PM
Not exactly sure what you mean. Can you clarify? Ray Augé February 27, 2009 7:40 AM
im confused to set pagination, could you... delang j February 27, 2009 12:08 AM
hi ray, why it didnt works on my office's... delang j February 27, 2009 12:20 AM
hmm... Which version of the portal. We had a... Ray Augé February 27, 2009 7:42 AM
problem solved. i just playing with the... delang j February 28, 2009 7:23 AM
what about to display 10 latest data only delang j March 2, 2009 5:59 PM
Apparently (and logically), Users' and... Peter Mesotten March 4, 2009 2:18 AM
The service layer will automatically know what... Ray Augé March 4, 2009 5:17 AM
I'll have to audit all the services which have... Ray Augé March 4, 2009 5:18 AM
Thanks alot for your quick response. I'm using... Peter Mesotten March 4, 2009 8:00 AM
I'd have to say just before the comment: //... Ray Augé March 4, 2009 8:43 AM
This is exactly what I'm looking for! I get... Max Gabrielsson March 9, 2009 8:03 AM
To start, which version of the portal are you... Ray Augé March 9, 2009 8:38 AM
I use 5.2.1. Max Gabrielsson March 9, 2009 8:47 AM
Great! You can reduce all that code by alot... Ray Augé March 9, 2009 9:18 AM
This is the API that "Custom Attributes" are... Ray Augé March 9, 2009 9:19 AM
Many thanks for your answer Ray! But I still... Max Gabrielsson March 10, 2009 7:14 AM
It should be! We're doing that very thing in... Ray Augé March 10, 2009 8:06 AM
Hi! I meet same problem in Liferay 5.2.3 - I'm... Alexey Kakunin September 15, 2009 12:45 PM
Probably, problem in fact, Expando has... Alexey Kakunin September 15, 2009 12:52 PM
uups - sorry! Exando has local services as... Alexey Kakunin September 15, 2009 2:39 PM
Hi Alexey, I'm also trying to add an attribute... Sven Ehlert October 9, 2009 6:36 AM
Hi Ray, brilliant blog thread! I have a simple... Harry Ostreicker January 11, 2010 9:46 AM
Thank you Ray, awesome explanation, great work.... Ricardo Javier Barbieri March 19, 2009 12:43 PM
Sorry the sample code perfectly works with... Ricardo Javier Barbieri March 25, 2009 1:20 PM
Great, got my User-Expando working :) One... Erik Mellegård April 3, 2009 7:17 AM
Hi Erik M, expandoColumn.getColumnId() gives,... Emilie R June 22, 2009 7:55 AM
Seems like gr8 feature. I am trying to set... Nagendra Kumar Busam August 11, 2009 1:08 AM
hi, expando working well in journal when... Auditya manikanta Vadrevu August 11, 2009 10:03 PM
First, let me say this is a great article. I... Shawn G September 2, 2009 1:30 PM
Hey Shawn, Thanks for the nice feedback, I... Ray Augé September 2, 2009 1:44 PM
Yes, I did. Later I found that this appears to... Shawn G September 9, 2009 7:23 AM
Oh dear! Expando-tables are nice and easy to... Milan Jaroš September 22, 2009 7:52 AM
Excellent how-to Ray, as always. In your... Jeff Williams October 2, 2009 10:40 AM
Hi Ray, brilliant blog thread! I have a simple... Harry Ostreicker January 11, 2010 9:47 AM
very nice article.. i got a noob question.. how... willard largueza macay January 13, 2010 7:45 PM
sorry wrong post.. but i did manage to create a... willard largueza macay January 13, 2010 7:50 PM
HI willard, Can u give complete template code... DarshanKumar N Bhatia May 18, 2010 8:49 PM
See the follow-up post which has full... Ray Augé May 18, 2010 8:50 PM
Am sorry if am late here. There are couple of... Ayorinde Afolayan June 16, 2010 1:27 AM
i did a test to see the values of the sent... Ayorinde Afolayan June 16, 2010 3:05 AM
okay i was able to fix the problem. For anybody... Ayorinde Afolayan June 16, 2010 3:54 AM
Hey Ayo, glad you finally solved it! Sadly... Ray Augé June 16, 2010 5:33 AM
Thanks Ray for the quick response. Am really... Ayorinde Afolayan June 16, 2010 7:40 AM
Or do we already have a solution addressing my... Ayorinde Afolayan June 16, 2010 7:43 AM
Yeah, using the Web Form Portlet should not be... Ray Augé June 16, 2010 8:07 AM
Thanks. Ayo Ayorinde Afolayan June 17, 2010 12:34 AM
very nice article.. i got a noob question.. how... willard largueza macay January 13, 2010 7:47 PM
Can anyone explain why we doing the following... Danny N May 18, 2010 12:02 PM
Ah, that was just a quick hack to get a long... Ray Augé May 18, 2010 12:11 PM
[...] Super ! Merci beaucoup pour ces... Anonymous July 13, 2011 7:14 AM
Thanks Ray, always looking forward to your blogs! Suraj Bihari November 15, 2011 1:42 PM
Hello, I am trying to pull values from a custom... Jonathan Mattox January 29, 2014 9:29 AM
Hy Raymond can you please take a look at this... Mohamed hammouda April 25, 2014 1:16 AM
[...] i did a test to see the values of the... Anonymous July 9, 2014 5:14 AM

This is awesome Ray. Thanks for the explanation and sample code!
Posted on 4/24/08 10:08 AM.
Thanks Jerry!
Posted on 4/24/08 10:55 AM in reply to Jerry Niu.
Wow... Now that's cool.
Posted on 4/24/08 3:26 PM.
Nice feature!
In (since) which version is that available?

Greats
Tobias
Posted on 4/24/08 9:48 PM.
It's currently in trunk, and should be in 5.0.2.
Posted on 4/24/08 9:54 PM in reply to Tobias S. Käfer.
Ray, I would update the title of the blog post to something like (Liferay Portal 5.0.2) because currently you can not use Expandos with any LRP released and the first experience from a developer trying it could be that it does not work. 5.0.2 is around the corner but ...

Really nice article in any case and a very useful feature,
Posted on 5/28/08 2:34 AM in reply to Ray Augé.
It's updated now.

Thanks Alvaro!
Posted on 5/28/08 8:07 AM in reply to Alvaro Del Castillo.
Great work Ray, thanks! I was searching for a solution extend dynamically Liferay tables and I found your solution. You show an example with new table, column, etc. But what about adding an expando column/value to an existing table? Is it possible? I tried to add an expando column 'foo' to user_ table and store value 'bar' for the specified user as the following way:

User user = UserLocalServiceUtil.getUserByEmailAddress(...);

ExpandoTable table = ExpandoTableLocalServiceUtil.addTable(PortalUtil.getClassNameId(user.getClass().­getName()), "user_");
if(table!=null) {
ExpandoColumn column = ExpandoColumnLocalServiceUtil.getColumn(table.getTableId(), "foo");
if(column!=null){
ExpandoValueLocalServiceUtil.addValue(column.getColumnId(), user.getUSerId(), user.getUserId(), "bar");
}
}

Is this correct? I'm afraid the second parameter of addValue (rowid) isn't correct, but what should I write there? And what about the third parameter (classPK), is that right? classPK is the primary key of the user?
If my code/idea is correct, I would like to ask you about deleting a "parent" record. I'll try to explain what I think :-) I put the value 'bar' into expando table for the user Joe Blogs. When I delete Joe Blogs from Liferay the 'bar' value from expando tables will not be deleted. Is this right? Should I delete manually expando records? There isn't a way to do this automatically? Do you have any idea? Or I misunderstood the whole expando-thing? :-)

Thanks,
Gabor
Posted on 6/13/08 12:38 AM.
Hello Gabor,

The API has changed slightly. Have a look at:

http://lportal.svn.sourceforge.net/svnroot/lportal/portal/trunk/portal-impl/s­rc/com/liferay/portlet/expando/service/impl/ExpandoValueLocalServiceImpl.java

you­'ll notice that the final API is a little more intuitive and easier to work with. Also, the final (stable) API will officially be available in the upcomming 5.0.2 release, due out next week. For example, you don't need to worry about rowIds as these are more for internal management than external (though it is available for use in iterating over table rows...).

Also, at least for users, any ExpandoValue associated directly with the User object and referenced by User's primary key will be deleted when the user is deleted, regardless of what table it's in... so you can have 100 tables associated with the User class and when a user is deleted... all the values associated with that user, in every table, go with it.

HTH!
Posted on 6/13/08 5:47 AM in reply to Gabor Nagy.
Thank you Ray!

It sounds good! We waiting for 5.0.2 :-)
However, I found a solution for delete problem. I added this line to portal/ext.properties:
value.object.listener.com.liferay.portal.model.User=com.li­feray.portal.model.UserListener,com.liferay.portal.model.MyUserListener

Then I created a new class MyUserListener and in onAfterRemove method:
User user = (User)model;
ExpandoValueLocalServiceUtil.deleteValues(PortalUtil.getClassNameId(­user.getClass().getName()), user.getUserId());

This removes all records belonging to the deleted user.

But, I'll take a look at the new API and rewrite my code with the official solution.
Posted on 6/13/08 7:50 AM in reply to Ray Augé.
Thanks for the post.

I initially approached this as an "oh carp, yet another thing to learn about Liferay". Well, I'm glad I read through your post. Journal templates are fantastic!

There was some initial confusion about default variables. I discovered the JT VM instances get initialized differently than regular portlet VM instances. For example, getting the user id was a bit difficult and I finally pulled it from the request tree. It would probably be beneficial to provide privileged based access to services instead of using hard filters in the boot properties file. Nonetheless, it was all a worthwhile experience that exposed some great functionality!

Is it possible to create reusable VM libraries within the JT paradigm?
Posted on 7/17/08 3:34 PM.
It is possible in Journal to create reusable VM libraries. You simply place your library code in standalone JTs (JT's not associated with any JStructure). Then you can refer to these "library templates" directly from others using the VM #parse method. The only catch is that you must prepend the name of the Template with "$journalTemplatesPath".

e.g.
#parse ("$journalTemplatesPath/MY_VM_LIB")
Posted on 7/19/08 12:46 PM in reply to b v j.
Perfect - thank you.
Posted on 7/19/08 9:40 PM in reply to Ray Augé.
Thanks for this, Ray! I have just gotten around to playing with this functionality, and it's awesome. Question, though: is it possible in the current model to return only rows that have been created by the signed-in user? I am looking at:
public java.util.List<com.liferay.portlet.expando.model.ExpandoRow> getRows(
long tableId, int start, int end)
throws com.liferay.portal.SystemException;

and don't think I see a way to extract that data. I have modified your code slightly to add the current user ID as a column.

Thanks!
Posted on 8/12/08 11:05 AM.
Hi Ray!
I am using UserGroupLocalService in VM file.
I want to get userGroupId from the usergroup table which in database, but in UserGroupLocalService does'nt have any method for getting userGroupId.
$userGroupLocalService.getUserUserGroups()
how will i get?
please help me.
thanks
Posted on 12/12/08 4:19 AM in reply to Josh Asbury.
I'm trying to use this feature to display data that is saved to the database through a "Request Form".

I retrieved the generated table name (109_INSTANCE_98OX_201, basically the instance-id of the portlet) from the EXPANDOTABLE table, but when trying to access the table it fails.

Basically the

#set ($accountsTable = $expandoTableLocalService.getTable($accountsTableName, $accountsTableName))

does not work even though $accountsTableName contains the correct table name

I veried that the feature itself was working by creating a table manually through the API.

What am I missing?
Posted on 12/19/08 6:53 AM.
Try:

#set ($accountsTable = $expandoTableLocalService.getTable("com.liferay.portlet.webform.util.WebFormUtil­", $accountsTableName))
Posted on 12/19/08 7:21 AM in reply to Thomas Kellerer.
Great that worked, thanks a lot
Posted on 12/20/08 12:08 PM in reply to Ray Augé.
Hi Ray, great!

On question, do the attribute name and value support locale?

It was notified that portlet 139 Expando was applied on users and organizations. Logically, it should be possible to apply portlet 139 Expando on user groups, communities, roles, etc. Is it right?

Thanks

Jonas Yuan
Posted on 12/29/08 6:26 PM in reply to Ray Augé.
If there exists a localization for the attribute name it will be used. Thus you can localize the names by adding the key to the locale files. The values themselves are not localized.

Custom attributes can be applied to any entity generated via service builder, as such the Expando portlet can be used to manage those. No changes are required to the portlet, juswt create a portletURL as you find in the User and Organization management.
Posted on 12/30/08 7:11 AM in reply to Jonas Yuan.
Hi Ray,

I read this blog, and create new table user in expando it has 2 coulumn userid and points,
but i want to map userid of this table to userID of user table.
when we delete any user in liferay, same user is also deleted in expando user table.
how will i do
please reply
Posted on 1/21/09 9:46 PM.
HI RAY,

iam trying expandos thing in portal 5.1.1 but it is not performing well.
there is no error in code, i have copied the template as specifed.
when i click save the values are not saving. and there is no error in logs also .
what can be the problem?
Posted on 2/5/09 11:20 PM.
When you set the className of the table to 'com.liferay.portal.model.User' and the primaryKey to that of a given user, the data will be deleted when the user is deleted automatically.
Posted on 2/6/09 5:29 AM in reply to nidhi singh.
The API has changed slightly. (I have not updated the Post, sorry!)

Try:

$expandoValueServiceUtil.addValue(
$className, $tableName, $columnName, $classPK, $columnValue)
Posted on 2/6/09 5:33 AM in reply to auditya manikanta vadrevu.
Hi, I tried this again with 5.2.1, and it does not seem to work any longer.

I have a webform and data is submitted. So I looked up the generated table in the database (EXPANDOTABLE). The entry there gave me the tableanme: 1_WAR_webformportlet_INSTANCE_y0Ik_2

But the following code does not return a rowcount:

#set ($tableName = "1_WAR_webformportlet_INSTANCE_y0Ik_2")
#set ($tableType = "com.liferay.portlet.webform.util.WebFormUtil")
#set ($rowCount = $expandoRowLocalService.getRowsCount($tableType, $tableName))

what am I missing here?
Posted on 2/13/09 6:21 AM in reply to Ray Augé.
Hi Ray,

Thank you! How about custom types in custom attributes? That is, adding custom types (Image Gallery images and Document Library documents) in custom attributes (Expando).

http://issues.liferay.com/browse/LPS-2087

Any comments or suggestions?
Posted on 2/14/09 6:04 PM in reply to Ray Augé.
Why don't you just store a reference to one of those types?

e.g. a custom attribute called "favorite-pic" of type string stores an url to the image, or of type long, stores the primaryKey of the image, etc... All you need to provide is the selection and rendering logic. Both very easy to implement based on existing examples.
Posted on 2/14/09 9:45 PM in reply to Jonas Yuan.
I found my error. Apparently the table type has change from

com.liferay.portlet.webform.util.WebFormUtil

to

com.liferay.webform.util.WebFormUtil

Now it's working again
Posted on 2/16/09 3:11 AM in reply to Thomas Kellerer.
After getting it to work with 5.2 I have a problem with caching.

I can see four entries in the database but the template only shows 3 of them.

The "Cacheable" attribute is disabled for my VM template, but I even restarted Liferay but still can't see the new row.

Is there another level of caching? But after restarting the server all those caches should be empty, right?
Posted on 2/16/09 3:43 AM.
I'll have to test this.

Can you file a bug and provide a minimal test case? Thanks!
Posted on 2/16/09 6:32 AM in reply to Thomas Kellerer.
Thanks for the answer.

It's hard do create a test case (because some rows are show) but I'll try. Is there anything I can turn on in Liferay to debug that in my environment? e.g. log messages to find out what's going on?
Posted on 2/16/09 7:19 AM in reply to Ray Augé.
This is very cool.. we are hoping to use it to replace some of our sharepoint lists... my problem is when I copy the template you have and try to use it in a content it doesn't show.. all my other do... am I missing a step? (Using 5.1.2)
Posted on 2/17/09 5:33 AM in reply to Ray Augé.
nevermind I missed the structure.... Heres the rub now... It renders but when I hit the create button it thinks and the does nothing...
Posted on 2/17/09 12:14 PM in reply to Chris Whittle.
nevermind I missed the structure.... Heres the rub now... It renders but when I hit the create button it thinks and the does nothing...
Posted on 2/17/09 12:14 PM in reply to Chris Whittle.
im confused to set pagination, could you explain more details.
Posted on 2/27/09 12:08 AM.
hi ray,
why it didnt works on my office's computer(linux) but perfectly works on my personal computer(window) and my friend's computer(linux).

the table name,column name perfectly created but i cant insert any value.
Posted on 2/27/09 12:20 AM.
Not exactly sure what you mean. Can you clarify?
Posted on 2/27/09 7:40 AM in reply to Chris Whittle.
hmm... Which version of the portal. We had a small API change in a latter version of 5.1 (I know API changes are bad... but it was a sever limitation which incurred the change, I suspect that's the problem).
Posted on 2/27/09 7:42 AM in reply to delang j.
problem solved. i just playing with the codes.LOL.

what about pagination, im big confused how to implement it
Posted on 2/28/09 7:23 AM in reply to Ray Augé.
what about to display 10 latest data only
Posted on 3/2/09 5:59 PM.
Apparently (and logically), Users' and Organizations' Custom Attributes are implemented with Expandos. For a Project, we need custom attributes on other entities such as events and communities as well.

I've already managed to create an expandotable that refers to f.e. the CalEvent class and added fields to it. Now I want to include this extra fields in the form of the Create Event page. I found the <liferay-ui:custom-attribute-list /> JSP tag, but, f.e. for CalEvent, where do I put this tag, and will the service layer know what to do with the "extra" inputfields?
Posted on 3/4/09 2:18 AM.
The service layer will automatically know what to do with the input fields as far as collecting them and passing them along to the service implementation layer... But unfortunately, it appears that the operation to persist them was not called in the add/update methods, specifically w.r.t. CalEvent.

What version are you using? It's simple to patch, add these two lines later in the add/update methods:

ExpandoBridge expandoBridge = event.getExpandoBridge();
expandoBridge.setAttributes(serviceContext);
Posted on 3/4/09 5:17 AM in reply to Peter Mesotten.
I'll have to audit all the services which have support for this, but aren't persisting the custom attributes.
Posted on 3/4/09 5:18 AM in reply to Ray Augé.
Thanks alot for your quick response. I'm using 5.2.1.

I tried using <liferay-ui:custom-attribute-list ... /> in edit_event.jsp and it showed the expando fields, awesome! Values are not yet persisted like you said. But where exactly should I put your lines of code? I'm pretty new to Liferay development...

Thanks in advance! Very exciting this Expando stuff
Posted on 3/4/09 8:00 AM.
I'd have to say just before the comment:

// Social

But I've already committed the fix to the CalEvent service here

http://issues.liferay.com/browse/LPS-2317

and requested for backport to 5.2.x.
Posted on 3/4/09 8:43 AM in reply to Peter Mesotten.
This is exactly what I'm looking for!

I get "No ExpandoColumn exists with the primary key 0" when I use ExpandoColumnLocalServiceUtil.addColumn(...) and (Since I am not logged in during the startup hook) "PermissionChecker not initialized" when I use ExpandoColumnServiceUtil.addColumn(...)

Is there a way to create Custom Attributes during the startup hook?

Here is the code:

//in startupaction.java
addAttribute(myUser, "myStringAttribute", 15, "default");


//in myUtilClass.java
public static void addAttribute(User liferayUser, String name, int type, Serializable defaultValue)
throws PortalException {
//String className = liferayUser.getClass().getName();
String className = com.liferay.portal.model.User.class.getName();
try {
ExpandoTable table = null;
try {
table = ExpandoTableLocalServiceUtil.getDefaultTable(
className);
­ }
catch (NoSuchTableException nste) {
table = ExpandoTableLocalServiceUtil.addDefaultTable(
className);
­ }
ExpandoColumnLocalServiceUtil.addColumn(
table.getTableId(­), name, type, defaultValue);
}
catch (Exception e) {
if (e instanceof PortalException) {
throw (PortalException)e;
}
else {
}
}
}
Posted on 3/9/09 8:03 AM.
To start, which version of the portal are you using?
Posted on 3/9/09 8:38 AM in reply to Max Gabrielsson.
I use 5.2.1.
Posted on 3/9/09 8:47 AM in reply to Ray Augé.
Great! You can reduce all that code by alot using the convenience API called ExpandoBridge.

liferayUser.getExpandoBridge().addAttribute(name, type, defaultValue);

If you don't have a user at that time, use:

ExpandoBridge eb = new ExpandoBridgeImpl(User.class.getName(), 0);
eb.addAttribute(name, type, defaultValue);

Later to get/set those values for any user simply:

Object attr = someUser.getExpandoBridge().getAttribute(name);

someUser.getExpandoBridge().setAt­tribute(name, someValue);
Posted on 3/9/09 9:18 AM in reply to Max Gabrielsson.
This is the API that "Custom Attributes" are based on.
Posted on 3/9/09 9:19 AM in reply to Ray Augé.
Many thanks for your answer Ray!

But I still got the problem with the PermissionChecker not initialized.

After I create the appropriate role and user in the startupaction.java (on application.startup.events) i try the:

liferayUser.getExpandoBridge().addAttribute(name, type, defaultValue);

Which gives the PrincipalException: PermissionChecker not initialized.
Also the:
ExpandoBridge eb = new ExpandoBridgeImpl(User.class.getName(), 0);
eb.addAttribute(name, type, defaultValue); <------------- gives the same exception.

Is it possible to create custom attributes in the startupaction hook?
Posted on 3/10/09 7:14 AM in reply to Ray Augé.
It should be! We're doing that very thing in the project I'm currently tasked on.
Posted on 3/10/09 8:06 AM in reply to Max Gabrielsson.
Thank you Ray, awesome explanation, great work. Could you update the sample code for Liferay 5.2.2?
Posted on 3/19/09 12:43 PM.
Sorry the sample code perfectly works with Liferay 5.2.2
Posted on 3/25/09 1:20 PM in reply to ricardo javier barbieri.
Great, got my User-Expando working emoticon

One problem left: How can I set role-permissions to the newly created CustomAttribute from my class)?
I can read the available permissions with:
List<Permission> permissions = PermissionLocalServiceUtil.getRolePermissions(roleId,expandoColumn.getColumnId()­);

But when i trty to set permissions like this:
PermissionLocalServiceUtil.setRolePermissions(roleId,new String[]{"VIEW","UPDATE"},expandoColumn.getColumnId());

...I get the following exception (the primary key is the key of the expandoColumn):
13:40:03,013 ERROR [SearchPermissionCheckerImpl:112] com.liferay.portal.NoSuchResourceException: No Resource exists with
the primary key 19212
com.liferay.portal.NoSuchResourceException: No Resource exists with the primary key 19212

I hav a couple of lines above the setRolePermissions read the permissions from the ExpandoColumn

Any pointers in the right direction would be appreciated.
Posted on 4/3/09 7:17 AM.
Hi Erik M,

expandoColumn.getColumnId() gives, logically, the ID of the expandoColumn, which is not the resourceId !
expandoColumnId corresponds with the "primKey" (in the table "resource"). So you have to find the "link" between the primKey and the resourceId. But be careful, the type of primKey is String and not long !

To find the corresponding resourceId, try that :
Resource resource = ResourceLocalServiceUtil.getResource(companyId, ExpandoColumn.class.getName(), ResourceConstants.SCOPE_INDIVIDUAL, String.valueOf(expandoColumn.getColumnId()));
Posted on 6/22/09 7:55 AM in reply to Erik M.
Seems like gr8 feature.

I am trying to set ExpandoBridgeAttributes in ServiceContext for PortalLDAPUtil.java & trying to get those in another class UserLocalServiceImpl.java.

I was sucessful till setting & getting the attributes, where as when i m invoking ExpandoValueLocalServiceUtil.addValue i m getting following error

com.liferay.portlet.expando.NoSuchTableException: No ExpandoTable exists with the key {companyId=10301, classNameId=10045, name=phone}

My question

1) do we need to add the expandotable/expandocolumn related entries first before adding value from ExpandoValueLocalServiceUtil's addValue method? Please let me know sequence of steps needed to add a custom attribute

Here is the my code

// class - PortalLDAPUtil.java
ServiceContext serviceContext = new ServiceContext();

// two custom attributes -telephoneNumber & postalCode
String telephoneNumber = LDAPUtil.getAttributeValue(attributes, userMappings.getProperty("phone"));
String postalCode = LDAPUtil.getAttributeValue(attributes, userMappings.getProperty("postalCode"));

Map<String, Serializable> expandoBridgeAttributes=new LinkedHashMap<String, Serializable>();
expandoBridgeAttributes.put("phone", telephoneNumber);
expandoBridgeAttributes.put("postalCode", postalCode);
serviceContext.setExpandoBridgeAttributes(expandoBridgeAttributes);

-­------------------------------------------------------------------------------
//­ class - UserLocalServiceImpl.java

for (Map.Entry<String, Serializable> entry : serviceContext.getExpandoBridgeAttributes().entrySet()) {
if (!entry.getKey().isEmpty() && !entry.getValue().equals("") ) {
if (entry.getKey().equals("phone")) {
ExpandoValueLocalServiceUtil.addValue(User.class.getName(),
"phone",­
"number_",
user.getPrimaryKey(),
entry.getValue().toString());
­ } else {
ExpandoValueLocalServiceUtil.addValue(User.class.getName(),
"address­",
"zip",
user.getPrimaryKey(),
entry.getValue().toString());
}
}
Posted on 8/11/09 1:08 AM.
hi,
expando working well in journal when creating and retreiving data through API only.
If we enter entries manually in expandotable,expandocolumn etc.., then the data is not retrievable through the expandfo API. Is there any other thing we must specify ...
Posted on 8/11/09 10:03 PM.
First, let me say this is a great article. I had a prototype up and running in no time, and the explanations of what was going on at each step were easy to follow.

However, I have a problem that has me completely stuck. I created two templates, one for admins to edit the list and one for guests to view it. The admin template updates as expected, but when Guests attempt to view the guest version, it seems to get "stuck" in cache and never updates. I can log in with minimal permissions and it appears file, but logging out goes back to the cached version.

What appears to be happening is that the first time the guest page renders, the list values at that time are cached for all eternity, or at least until the server restarts or I make a change to the page or template.

I've tried various settings for <expiration-cache>, but the Guest (i.e. not logged on) user always ignores them. It simply takes the initial rendering of the page and that's it.

Is there a better way to do this, or am I missing something? Basically I need power users to be able to edit records in the list, and the general public to be able to view it.

Thanks in advance,
Shawn
Posted on 9/2/09 1:30 PM.
Hey Shawn,

Thanks for the nice feedback, I appreciate it.

Onto your question; on the "Edit Template" view, did you _uncheck_ "Cacheable"?
Posted on 9/2/09 1:44 PM in reply to Shawn Gerlock.
Yes, I did. Later I found that this appears to be a function of the web content display portlet, by default it is set to cache for anonymous users. You can turn this off, but you would be turning it off for all web content display portlets so that is not an option for me. The workaround for me was to put it inside a Nested Portlet, since the Nested Portlet does not cache its content (in this case the web content display portlet) will not cache either.

Now I have another challenge - I need to be able to sort and eventually filter the list content. Basically what I want to do is have a list of tradeshows, sorted by start date and filtered showing only those events whose end dates have not passed. Is this possible using Expandos?

For sorting, I tried modifying the #foreach statement from this:

#foreach($row in $rows)

To this:

#foreach($row in $sorter.sort($rows, "EventStart"))

However this just produces an empty list, so I think I'm missing something somewhere. Any thoughts you might have would be appreciated.

Take care,
Shawn
Posted on 9/9/09 7:23 AM in reply to Ray Augé.
Hi! I meet same problem in Liferay 5.2.3 - I'm trying to add new attribute (in my case for BlogsEntry) in portlet StartUpAction (configured via hooks-propeties).
I'm doing very similar:
ExpandoBridgeImpl blogExpandoBridge = new ExpandoBridgeImpl(BlogsEntry.class.getName());
// shareWith
if (blogExpandoBridge.hasAttribute("shareWith")) {
_log.debug("attribute shareWith already exists");
}
else {
_log.info("adding shareWith attribute");
blogExpandoBridge.addAttribute("shareWith");
}

But got PermissionChecker is not initialized exception. I think it happens, because during adding new column Liferay tried to identify - is current user allowed to add expando columns, but, since this code exeucted in StartUpAction - no current user and as result - no any PermissionChecker.

Any solution for this?

BTW - Expando looks like a great solution which should resolve many problems for extending default liferay objects!
Posted on 9/15/09 12:45 PM in reply to Ray Augé.
Probably, problem in fact, Expando has services, but has no LocalServices - as far as I understand - one of the biggest difference - LocalServices did not do permission checking.

So, ExpandoServices always tried to do permission-checks, and in case they are called in (for example) startup action, since we do not have any user in context, permission checker is not initialized.

Having Expando LocalServices will allow to perform Expando operations without permission checking (supposing all permissions are already checked on top-level)
Posted on 9/15/09 12:52 PM in reply to Alexey Kakunin.
uups - sorry! Exando has local services as well, and, as I expected - their usage allow o perform exapndo attributes operations without checking permissions (for example in startup event).
Posted on 9/15/09 2:39 PM in reply to Alexey Kakunin.
Oh dear!
Expando-tables are nice and easy to use. We've used it in one case where was no need of developing special portlet. And this was the fastest solution. Thanx Ray.
BUT! But the WOL configuration of Jira user is the worst example. Why? ;o) BTW: Is it configurable after login name submit? Never mind we will develop another portlet for connection with Jira.
Posted on 9/22/09 7:52 AM.
Excellent how-to Ray, as always.

In your example, you make reference to pagination. Can you give an example?

thanks again -
Posted on 10/2/09 10:40 AM.
Hi Alexey,

I'm also trying to add an attribute in a Startup hook (Liferay 5.2.3) like in your example and I also got this PermissionChecker not initialized exception.

I'm not really getting your later comments with the Local service. Have you been able to fix your problem using local service? If so, would you mind posting your code here?

Thanks a lot!
Posted on 10/9/09 6:36 AM in reply to Alexey Kakunin.
Hi Ray, brilliant blog thread!

I have a simple question regarding posting data using the "web form" portlet and reading the entries using the velocity template approach.

My Web Form portlet instance works well and I can see all of the data in the Expando tables. But I'm trying to create a very simple way to read these values into a table with pagination using a velocity template.

Any ideas how I can accomplish this using the last portion of your code here?

Thanks!
-HarryO
Posted on 1/11/10 9:46 AM in reply to Sven Ehlert.
Hi Ray, brilliant blog thread!

I have a simple question regarding posting data using the "web form" portlet and reading the entries using the velocity template approach.

My Web Form portlet instance works well and I can see all of the data in the Expando tables. But I'm trying to create a very simple way to read these values into a table with pagination using a velocity template.

Any ideas how I can accomplish this using the last portion of your code here?

Thanks!
-HarryO
Posted on 1/11/10 9:47 AM.
very nice article.. i got a noob question.. how to make a custom query in expando using velocity? thanks
Posted on 1/13/10 7:45 PM in reply to Harry Ostreicker.
very nice article.. i got a noob question.. how to make a custom query in expando using velocity? thanks
Posted on 1/13/10 7:47 PM.
sorry wrong post.. but i did manage to create a pagination based in his example..

#set( $Integer = 0 )
#if($request.get("parameters").get("begin") != "")
#set ($begin = $Integer.parseInt($request.get("parameters").get("begin")))
#set ($end= $Integer.parseInt($request.get("parameters").get("end")))
#else
#set ($begin = 0)
#set ($end = 20)
#end

#set ($rowsCount = $expandoRowLocalService.getRowsCount($GlobeWidgetsTableName, $GlobeWidgetsTableName))

#set ($rows = $expandoRowLocalService.getRows($GlobeWidgetsTableName, $GlobeWidgetsTableName,$begin, $end))
Posted on 1/13/10 7:50 PM in reply to willard largueza macay.
Can anyone explain why we doing the following if classPK is undefined:

#if ($classPK <= 0)
#set ($classPK = $dateTool.getDate().getTime())
#end
Posted on 5/18/10 12:02 PM.
Ah, that was just a quick hack to get a long for use as a primary key. Any other way of getting a primaryKey would work, including maybe pulling from the counter service, which for a robust app, would probably be the best way.
Posted on 5/18/10 12:11 PM in reply to Danny N.
HI willard,

Can u give complete template code with pagination ,
so i can understand flow of it.
Posted on 5/18/10 8:49 PM in reply to willard largueza macay.
See the follow-up post which has full pagination

http://www.liferay.com/web/raymond.auge/blog/-/blogs/expandos-ii-refac­tor-of-a-previous-post-for-6-0
Posted on 5/18/10 8:50 PM in reply to DarshanKumar Bhatia.
Am sorry if am late here. There are couple of forms i was trying to create. Thought about creating a custom portlet(as i needed to also do an upload) but later stumbled upon this and find it kind of useful(never thought something as powerful as this is hidden within webcontents!). I copied the full example but doesn't seems to work.Though the table got created but that the best it does.
Also wouldn't mind any hint on incorporating file upload along with it.

Using liferay 5.2.3

Thanks for the time you spared to share such a wonderful tutorial


Ayo
Posted on 6/16/10 1:27 AM in reply to Ray Augé.
i did a test to see the values of the sent parameters but nothing got
displayed.So tried a 'get' method to see the parameters in the
url, i got this:
http://localhost:4040/web/guest/theme?_56_INSTANCE_9wJr_classPK=0&_56_INSTANCE_9­wJr_cmd=add&_56_INSTANCE_9wJr_firstName=myFirstName&_56_INSTANCE_9wJr_lastName=m­yLastName&_56_INSTANCE_9wJr_balance=4876

Still nothing got displayed. Below is the full source.




#set ($locale = $localeUtil.fromLanguageId($request.get("locale")))
#set ($dateFormatDateTime = $dateFormats.getDateTime($locale))

<h1>First Expando Bank</h1>

##
## Define the "name" for our ExpandoTable.
##

#set ($accountsTableName = "AccountsTable")

##
## Get/Create the ExpandoTable to hold our data.
##

#set ($accountsTable = $expandoTableLocalService.getTable($accountsTableName, $accountsTableName))

#if (!$accountsTable)
#set ($accountsTable = $expandoTableLocalService.addTable($accountsTableName, $accountsTableName))

#set ($accountsTableId = $accountsTable.getTableId())

##
## Create an ExpandoColumn for each field in the form.
##

#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "firstName", 15)) ## STRING
#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "lastName", 15)) ## STRING
#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "balance", 5)) ## DOUBLE
#set ($V = $expandoColumnLocalService.addColumn($accountsTableId, "modifiedDate", 3)) ## DATE
#end

##
## Do some request handling setup.
##

#set ($renderUrl = $request.get("render-url"))
#set ($namespace = $request.get("portlet-namespace"))
#set ($cmd = $request.get("parameters").get("cmd"))
#set ($lastName = $request.get("parameters").get("lastName"))
#set ($firstName = $request.get("parameters").get("firstName"))
#set ($balance = $request.get("parameters").get("balance"))

<br/>value of lastName: ${lastName}<br/>
<br/>value of firstName: ${firstName}<br/>
<br/>value of cmd: ${cmd}<br/>
<br/>value of balance: ${balance}<br/>

#set ($firstName = '')
#set ($lastName = '')
#set ($balance = 0.0)

##
## Check to see if a classPK was passed in the request.
##

#set ($classPK = $getterUtil.getLong($request.get("parameters").get("classPK")))

##
## Check if we have received a form submission?
##

#if ($cmd.equals("add") || $cmd.equals("update"))
##
## Let's get the form values from the request.
##

#set ($firstName = $request.get("parameters").get("firstName"))
#set ($lastName = $request.get("parameters").get("lastName"))
#set ($balance = $getterUtil.getDouble($request.get("parameters").get("balance")))
#set ($date = $dateTool.getDate())

##
## Validate the params to see if we should proceed.
##

#if (($cmd.equals("add") && !$firstName.equals("") && !$lastName.equals("") && $balance >= 50) || ($cmd.equals("update") && !$firstName.equals("") && !$lastName.equals("")))
##
## Check to see if it's a new Account.
##

#if ($classPK <= 0)
#set ($classPK = $dateTool.getDate().getTime())
#end

#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "firstName", $classPK, $firstName))
#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "lastName", $classPK, $lastName))
#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "balance", $classPK, $balance))
#set ($V = $expandoValueLocalService.addValue($accountsTableName, $accountsTableName, "modifiedDate", $classPK, $date))

##
## Show a response.
##

#if ($cmd.equals("update"))
Thank you, ${firstName}, for updating your account with our bank!
#else
Thank you, ${firstName}, for creating an account with our bank!
#end

#else
Please fill the form completely in order to create an account. The minimum amount of cash required to create an account is $50.
#end

#set ($classPK = 0)
#set ($firstName = '')
#set ($lastName = '')
#set ($balance = 0.0)

#elseif ($cmd.equals("delete"))
##
## Delete the specified Row.
##

#if ($classPK > 0)
#set ($V = $expandoRowLocalService.deleteRow($accountsTableName, $accountsTableName, $classPK))

Account deleted!

#set ($classPK = 0)
#end
#elseif ($cmd.equals("edit"))
##
## Edit the specified Row.
##

Editting...

#if ($classPK > 0)
##
## Get the account specific values
##

#set ($firstName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "firstName", $classPK, ""))
#set ($lastName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "lastName", $classPK, ""))
#set ($balance = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "balance", $classPK, 0.0))
#end
#end

<span style="display: block; border-top: 1px solid #CCC; margin: 5px 0px 5px 0px;"></span>

#if (!$cmd.equals("edit"))
##
## Now we're into the display logic.
##

<input type="button" value="Create Account" onClick="self.location = '${renderUrl}&${namespace}cmd=edit';" />

<br /><br />

<table class="lfr-table">
<tr>
<th>Account Number</th>
<th>First Name</th>
<th>Last Name</th>
<th>Balance</th>
<th>Modified Date</th>
<th><!----></th>
</tr>

##
## Get all the current records in our ExpandoTable. We can paginate by passing a
## "begin" and "end" params.
##

#set ($rowsCount = $expandoRowLocalService.getRowsCount($accountsTableName, $accountsTableName))
#set ($rows = $expandoRowLocalService.getRows($accountsTableName, $accountsTableName, -1, -1))

#foreach($row in $rows)
##
## Get the classPK of this row.
##

#set ($currentClassPK = $row.getClassPK())

<tr>
<td>${currentClassPK}</td>

#set ($currentFirstName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "firstName", $currentClassPK, ""))
<td>${currentFirstName}</td>

#set ($currentLastName = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "lastName", $currentClassPK, ""))
<td>${currentLastName}</td>

#set ($currentBalance = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "balance", $currentClassPK, 0.0))
<td align="right">${numberTool.currency($currentBalance)}</td>

#set ($currentModifiedDate = $expandoValueLocalService.getData($accountsTableName, $accountsTableName, "modifiedDate", $currentClassPK, $dateTool.getDate()))
<td>${dateFormatDateTime.format($currentModifiedDate)­}</td>

<td>
<a href="${renderUrl}&amp;${namespace}cmd=edit&amp;${namespace}classPK=${currentCla­ssPK}">Edit</a> |
<a href="${renderUrl}&amp;${namespace}cmd=delete&amp;${namespace}classPK=${currentC­lassPK}">Delete</a>
</td>
</tr>
#end

#if ($rowsCount <= 0)
<tr>
<td colspan="5">No Accounts were found.</td>
</tr>
#end

</table>

# of Accounts: ${rowsCount}
#else
##
## Here we have our input form.
##

<form action="$renderUrl" method="get" name="${namespace}fm10">
<input type="hidden" name="${namespace}classPK" value="${classPK}" />
<input type="hidden" name="${namespace}cmd"
#if ($classPK > 0)
value="update"
#else
value="add"
#end
/>

<table class="lfr-table">
<tr>
<td>First Name:</td>
<td>
<input type="text" name="${namespace}firstName" value="${firstName}" />
</td>
</tr>
<tr>
<td>Last Name:</td>
<td>
<input type="text" name="${namespace}lastName" value="${lastName}" />
</td>
</tr>
<tr>
<td>Balance:</td>
<td>
<input type="text" name="${namespace}balance" value="${numberTool.format($balance)}" />
</td>
</tr>
</table>

<br />

<input type="submit" value="Save" />
<input type="button" value="Cancel" onclick="self.location = '${renderUrl}'" />
</form>
#end

<br /><br />


Thanks
Posted on 6/16/10 3:05 AM in reply to Ayorinde Afolayan.
okay i was able to fix the problem. For anybody out there
who got stuck due to this kind of mess all you need do
is go to the Template edit page and uncheck the option
'Cacheable' and make sure it's a post submission you do in
the template.

That's it.

Ayo
Posted on 6/16/10 3:54 AM in reply to Ayorinde Afolayan.
Hey Ayo, glad you finally solved it!

Sadly regarding File upload, that is not currently supported by web content. For that you may need to use a
custom portlet as you first thought.

Sorry for getting your hopes up and hopefully (Advanced) Web Content can solve some other problems
for you.
Posted on 6/16/10 5:33 AM in reply to Ayorinde Afolayan.
Thanks Ray for the quick response. Am really grateful.
Now that the custom solution is the way, i would need it
to be generic and i think we already have something like that
talking about the Web-Form-Portlet. But it seems not to have
an upload option. Also i would be developing in the sdk mode
(not ext mode) and with this i won't have access to certain
services(the last time i tried i couldn't). So am thinking
of leveraging the WebService infracture provided(would
love to know your opinion on that please).

Though at the end of the day i would be modifying the already
available Web-Form-Portlet source code to create this new
generic custom solution.

Thanks once more Ray. We really do appreciate your hard work.


Ayo
Posted on 6/16/10 7:40 AM in reply to Ray Augé.
Or do we already have a solution addressing my issue?
Wouldn't mind a download link to it please.

Thanks.

Ayo
Posted on 6/16/10 7:43 AM in reply to Ayorinde Afolayan.
Yeah, using the Web Form Portlet should not be too hard to implement file upload. In fact it should be rather easy. The only trick is how to handle the files once you have them.

Not sure off the top of my head where the best persistence would be. Ideally it would be a special store in the DocLib engine.
Posted on 6/16/10 8:07 AM in reply to Ayorinde Afolayan.
Posted on 6/17/10 12:34 AM in reply to Ray Augé.
[...] Super ! Merci beaucoup pour ces précisions qui me seront très utiles ! Je vais partir sur les Expandos donc, d'autant plus que le nombre de documents dans le système ne sera pas très élevé (pas plus... [...] Read More
Posted on 7/13/11 7:14 AM.
Thanks Ray, always looking forward to your blogs!
Posted on 11/15/11 1:42 PM.
Hello, I am trying to pull values from a custom metadata set and display it as an additional column on the view/gridview along with Title, Size, Created Date. And I am having a hard time figuring out exactly what to do in my hook. I see bits and pieces of code all over the place, but I'm not sure exactly how to extend the classes to add this. I've been able to add a column to be seen on the gridview by adding an entry to the init.jsp file by adding the following around line number 151:

Before:
String defaultEntryColumns = "name,size";

After:
String defaultEntryColumns = "name,size,myNewColumn";

Then I found where you add a condition for it in view_entries.jsp, but when I output fileEntryTypeId in this if statement, I keep getting a "-1"

if (columnName.equals("myNewColumn")) {
//Logic code here
}

Also in view_entries.jsp I see the following code which gets the fileEntryTypeId, which is one of the keys I need to tap into getting the metadata since the DLFileEntry doesn't have a property for this, but I'm not sure how to tap into this to get what I need and post in in the view/gridview:

List results = null;
//List metaDataresults = null;
int total = 0;

if (fileEntryTypeId >= 0) {
Indexer indexer = IndexerRegistryUtil.getIndexer(DLFileEntryConstants.getClassName());

if (fileEntryTypeId > 0) {
DLFileEntryType dlFileEntryType = DLFileEntryTypeLocalServiceUtil.getFileEntryType(fileEntryTypeId);

dlFileEntry­TypeName = dlFileEntryType.getName();
}

Also, in edit_file_entry, I see the following code which looks like it pertains, but I don't understand how to use it (starting at line 334):

<%
if (fileEntryTypeId > 0) {
try {
List<DDMStructure> ddmStructures = dlFileEntryType.getDDMStructures();

for (DDMStructure ddmStructure : ddmStructures) {
Fields fields = null;

try {
DLFileEntryMetadata fileEntryMetadata = DLFileEntryMetadataLocalServiceUtil.getFileEntryMetadata(ddmStructure.getStructu­reId(), fileVersionId);

fields = StorageEngineUtil.getFields(fileEntryMetadata.getDDMStorageId());
­ }
catch (Exception e) {
}
%>

<%= DDMXSDUtil.getHTML(pageContext, ddmStructure.getXsd(), fields, String.valueOf(ddmStructure.getPrimaryKey()), locale) %>

I also see that the table "DDMContent" gets populated with two rows when you add a document that has metadata fields. One row is for the automatically extracted data, and the other has the custom data from my custom metadata set.

I am trying to implement the following class solutions:

public class MyDLAppLocalService implements DLAppLocalService {}

and

public class MyDLFileEntryMetadataLocalService implements
DLFileEntryMetadataLocalService {}

and

public class MyDLAppLocalServiceImpl extends DLAppLocalServiceWrapper {

public MyDLAppLocalServiceImpl(DLAppLocalService dlAppLocalService) {
super(dlAppLocalService);
// TODO Auto-generated constructor stub
}
}

and

public MyDLAppLocalServiceWrapper(FileEntry fileEntry){
super((DLAppService) fileEntry);
}
}
Here is my liferay-hook.xml:

<?xml version="1.0"?>
<!DOCTYPE hook PUBLIC "-//Liferay//DTD Hook 6.1.0//EN" "http://www.liferay.com/dtd/liferay-hook_6_1_0.dtd">

<hook>
<custom-jsp-dir>/META-INF/custom_jsps</custom-jsp-dir>
<service>
<service-type>com.liferay.portlet.documentlibrary.service.DLAppLocalService</ser­vice-type>
<service-impl>com.liferay.sample.hook.MyDLAppLocalServiceImpl</service-impl>
</se­rvice>
</hook>


I find all this stuff very confusing. I don't know what to do to which class and where. I see a lot of tutorials about modifying the User's classes, but not much about this and what posts I do find, they only give a tiny snippet of code. I have no idea where that snippet came from (which file and where in that file). I'm trying to figure out all this java and liferay stuff. I've been to the liferay training classes and I'm working through the books, along with Liferay In Action. I just don't see a very clear explanation from start to finish of how to do something like this can anyone please help me?

Thanks in advance emoticon
Posted on 1/29/14 9:29 AM.
Hy Raymond can you please take a look at this https://www.liferay.com/fr/community/forums/-/message_boards/message/37162018

It'­s about customs fields.

Thank you
Posted on 4/25/14 1:16 AM.
[...] i did a test to see the values of the sent parameters but nothing gotdisplayed.So tried a 'get' method to see the parameters in the url, i got this:... [...] Read More
Posted on 7/9/14 5:14 AM.