Multiple actions in edit mode Liferay MVC - liferay

I am following the following example: https://github.com/liferay/liferay-blade-samples/blob/7.1/maven/apps/configuration-action/src/main/java/com/liferay/blade/samples/configurationaction/MessageDisplayConfigurationAction.java
I have multiple submit buttons in my form. I'd like to handle them in multiple methods. This is possible in Spring MVC Portlet. For example, you can do this: Attach an onlclick event of the button to a function that looks like this:
function <portlet:namespace />addGroup(){
var url = "<portlet:actionURL portletMode='edit'><portlet:param name='action' value='addGroup'/></portlet:actionURL>";
submitForm(document.<portlet:namespace />fm, url);
}
And in the code we can do like this:
#RequestMapping("EDIT")
#ActionMapping(params = "action=addGroup")
public void handleAddGroup(ActionRequest actionRequest, ActionResponse response) throws ResearchLibraryException, Exception {
PortletPreferences preferences = actionRequest.getPreferences();
// Add something to preferences
preferences.store();
}
How to do the same thing in Liferay MVC. I would like to use multiple methods. Right now, I can do only one method and switch based on the condition and identify the different clicks.

This shouldn't be much different with Liferay MVC portlets.
You should invoke a Liferay RenderCommand or ActionCommand.
Check out https://portal.liferay.dev/docs/7-0/tutorials/-/knowledge_base/t/mvc-render-command or https://portal.liferay.dev/docs/7-0/tutorials/-/knowledge_base/t/mvc-action-command
For example, the action command will be its own OSGi component:
#Component(
immediate = true,
property = {
"javax.portlet.name=your_portlet_name_YourPortlet",
"mvc.command.name=/your/jsp/action/url"
},
service = MVCActionCommand.class
)
public class YourMVCActionCommand extends BaseMVCActionCommand {
// implement your action
}
with this URL generation:
<portlet:actionURL name="/your/jsp/action/url" />

In addition to Jan's good answer: Even the portlet spec allows you to have multiple action methods with a standardized annotation. This has been made even easier with LiferayMVC portlets, in that you can even omit the annotation and just use the method name as action name:
// this is the action method defined by the portlet spec (2.0)
#ProcessAction(name="doSomething")
public void doSomething(ActionRequest request, ActionResponse response) {
log.info("doSomething");
}
// this works in Liferay MVC, the method name is the action name
public void doSomethingElse(ActionRequest request, ActionResponse response) {
log.info("doSomethingElse");
}
You'll generate the URLs like this, for example in a JSP:
<%# taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<portlet:actionURL name="doSomething" var="doSomethingURL" />
do something
<br/>
<portlet:actionURL name="doSomethingElse" var="doSomethingElseURL" />
do something else
On top of that, you can create ActionCommands and RenderCommands, as Jan mentions. This will break up the functionality even more, into individual classes. IMHO that's preferable (as it makes them all independent of each other and smaller)
A condition to get this functionality is to not override processAction, as its default implementation tries to find annotated methods, or methods with the required interface.

Related

ASP.NET MVC Core - Displaying an Enum property in a View (NOT related to a DropDownList)

I want to display an Index style View featuring an HTML table that I populate from my #model, a List<MyViewModel>.
The MyViewModel class has a property called Status, which is a custom enum I've defined like so:
public enum MarketingEmailStatus
{
Queued,
Sending,
Sent
}
When I attempt to display that property in my View like so ...
...
#foreach (var item in Model)
{
<tr>
<td>
#Html.DisplayForModel(modelItem => item.Status)
</td>
...
I get an error (both in Intellisense and at run time):
Cannot convert lambda expression to type 'object' because it is not a delegate type
I tried to do item.Status.ToString() to no avail.
No doubt, I could probably take an alternative approach (i.e., just change the ViewModel to represent Status as a string and resolve this prior to sending to the view), but this feels like such a simple scenario that it should be possible. And yet after an hour of searching I've not found any solutions (other than those related to DropDownLists for which there seems to be some #Html helper methods available.)
I see two problems with directly using #item.Status:
It won't respect the "display values" of those enums e.g. instead of
"Code Sample", it would show "CodeSample".
It doesn't take into account the localization.
This looks like a very promising solution for such scenarios, in my opinion.
You can obviously just use the extension methods if point 2 is not your concern as below:
public static class EnumExtensions
{
public static string GetDisplayName(this Enum enumValue)
{
FieldInfo fi = enumValue.GetType().GetField(enumValue.ToString());
var displayAttribute = fi.CustomAttributes.FirstOrDefault(c => c.AttributeType == typeof(DisplayAttribute));
if (displayAttribute == null) return enumValue.ToString();
return displayAttribute.NamedArguments.FirstOrDefault(a => a.MemberName == "Name").TypedValue.Value.ToString();
}
}
In ASP.NET Core, especially in my own web application — which we both somehow have been upgrading constantly since ASP.NET Core 2.0 days — what I did was just used the variable name from Model and did a # on it, ASP.NET Core 2.0 took care of the rest. It is quite surprising to me, as to why you are still using these Html helpers, and not the best of what ASP.NET has to offer. :-) And yes, for select element, there is a special ASP.NET Core tag helper, which takes the model and in the case of a simple text display you can do #propertyName and it will be properly written for you.
Let me share the necessary model content with you,
public class Foo {
// Other properties
public int Views { get; set; }
public PostType PostType { get; set; }
// Other properties.
}
The enum is like this,
public enum PostType { Article, Video, CodeSample, Ebook, Other }
Now, on the View page I do the following after capturing the Model,
<!-- Content here. -->
<p>#post.PostType has #post.Views views.</p>
<!-- Content here. -->
And there, it automatically prints the string containing the enum value.
As you can see, the last line in the paragraph shows Article has 6 views. Which was the response for the item that I had selected. Rest of the content in the image can be ignored.
Now, as to just give an idea of a table, this would perfectly work — and it does work for my dashboard panel in the web app.
<table>
<tr>
<th>Id</th>
<th>Title</th>
<th>Status</th>
</tr>
#foreach (var item in Model) {
<tr>
<td>#item.Id</td>
<td>#item.Name</td>
<td>#item.Status</td>
</tr>
}
</table>
There is no need to bring in lambdas for such a simple task — I mean, no need for #Html helper, because Razor is enough and smart too.

Is it possible in Orchard to dynamically change HTML in a shape right before rendering?

This is the markup for Content.ThumbnailSummary.cshtml, a custom DisplayType I use to render ContentItems as clickable thumbnails with their contents absolutely positioned over them.
#using Orchard.Utility.Extensions;
#{
var contentTypeClassName = ((string)Model.ContentItem.ContentType).HtmlClassify();
}
<a class="content-item #contentTypeClassName thumbnail-summary">
#Display(Model.Header)
<div class="thumbnail-summary-inner">
#Display(Model.Content)
</div>
#Display(Model.Footer)
</a>
The problem is that out of the box most Parts and Fields get rendered as links or paragraphs containing links, and nested <a> tags mess up DOM rendering pretty badly in most browsers. A ThumbnailSummary should never contain any links.
I could create alternates for every field and part, or I could remove everything by default in placement and only add rules for specific cases as I need them. But that would be pretty tedious and defeats a lot of the benefits of placement, so I was hoping I could somehow strip or replace all <a> tags in code only for shapes with this DisplayType.
I've been looking in this direction but I'm not sure if it's viable:
public class Shapes : IShapeTableProvider
{
public void Discover(ShapeTableBuilder builder)
{
builder.Describe("Content")
.OnDisplaying(displaying =>
{
if (displaying.ShapeMetadata.DisplayType == "ThumbnailSummary")
{
// Do something here???
}
});
}
}
You are almost right, instead of a provider add a class that inherits from Orchard.DisplayManagement.Implementation.ShapeDisplayEvents or implement IShapeDisplayEvents yourself.
I've done this myself to remove certain functionality from admin area that cannot be disabled via feature or permission.
The code should look like this
public class MyShapeDisplayEvents : Orchard.DisplayManagement.Implementation.ShapeDisplayEvents
{
public override void Displayed(Orchard.DisplayManagement.Implementation.ShapeDisplayedContext context)
{
if (context.Shape is Orchard.DisplayManagement.Shapes.Shape)
{
Orchard.DisplayManagement.Shapes.Shape lShape = (Orchard.DisplayManagement.Shapes.Shape)context.Shape;
if (lShape.Metadata.Type == "Layout")
{
string lChildContent = context.ChildContent.ToHtmlString();
// do something with the content like removing tags
context.ChildContent = new System.Web.HtmlString(lChildContent);
}
...

Method to display list of available portlets in a portlet

Generally liferay has ADD option for displaying available portlets.
I want them to appear in a drop-down and that should be in a custom plugin-portlet, so I am searching in API which method is retrieving the available portlets, but I didn't find any.
Please help me in this as I am stuck with this and also on selecting from the drop-down the portlet should be added to the page.
The "Add...More" dialog is displayed by the dockbar portlet. You can find the implementation of the UI part of this in Liferay's source in portal-web/docroot/html/portlet/dockbar/add_panel.jsp, which also includes view_category.jsp in the same directory.
While this jsp code is not the prettiest, you'll easily find that PortletLocalService is the one where you find the relevant information, together with an actual sample of how to access the list of portlets by category, sort them according to the current user's locale etc.
As you're asking for more concrete pointers: In add_panel.jsp you can find:
for (PortletCategory curPortletCategory : categories) {
if (curPortletCategory.isHidden()) {
continue;
}
request.setAttribute(WebKeys.PORTLET_CATEGORY, curPortletCategory);
request.setAttribute(WebKeys.PORTLET_CATEGORY_INDEX, String.valueOf(portletCategoryIndex));
%>
<liferay-util:include page="/html/portlet/dockbar/view_category.jsp" />
<%
portletCategoryIndex++;
}
%>
and some excerpts from view_category.jsp:
<%
PortletCategory portletCategory = (PortletCategory)request.getAttribute(WebKeys.PORTLET_CATEGORY);
int portletCategoryIndex = GetterUtil.getInteger((String)request.getAttribute(WebKeys.PORTLET_CATEGORY_INDEX));
// ...
Set<String> portletIds = portletCategory.getPortletIds();
// ...
for (String portletId : portletIds) {
Portlet portlet = PortletLocalServiceUtil.getPortletById(user.getCompanyId(), portletId);
if ((portlet != null) && PortletPermissionUtil.contains(permissionChecker, layout, portlet, ActionKeys.ADD_TO_PAGE)) {
portlets.add(portlet);
// ... and so on
Hope this excerpt helps. See the rest of the file for what you can actually do with the resulting list. Also, Portlet's interface might help if you need more details.

How to hide portlet in liferay by using javascript

Actually on a single page I have 2 portlet but I want to hide the first portlet by clicking the submit button and only the second portlet should be visible I used the following code:
document.getElementById("portlet-id").style.visibility='none'
But after refreshing the page, again portlet is visible can anyone provide me the solution as to how I can proceed.
You can set the visibility of the portlet to false in the JSP by using the following code:
<%
renderRequest.setAttribute(WebKeys.PORTLET_CONFIGURATOR_VISIBILITY, Boolean.FALSE);
%>
This would hide your portlet from user's view.
Everytime your portlet is rendered you can check a parameter which was set in the request or session (your choice) to either show the portlet or not show the portlet, like:
<%
String paramFromRequestToHide = renderRequest.getParameter("hidePortlet");
// can also fetch from session: portletSession.getAttribute("hidePortlet");
if (paramFromRequestToHide .equals("YES")) { // you can use your favorite data-type
renderRequest.setAttribute(WebKeys.PORTLET_CONFIGURATOR_VISIBILITY, Boolean.FALSE);
} else {
renderRequest.setAttribute(WebKeys.PORTLET_CONFIGURATOR_VISIBILITY, Boolean.TRUE);
}
%>
Another method:
If you don't want to go with the above approach then you can combine your javascript approach with the parameter approach as follows:
<%
String paramFromRequestToHide = renderRequest.getParameter("hidePortlet");
if (paramFromRequestToHide .equals("YES")) {
%>
<aui:script>
Liferay.Portlet.ready(
/*
This function gets loaded after each and every portlet on the page.
portletId: the current portlet's id
node: the Alloy Node object of the current portlet
*/
function(portletId, node) {
document.getElementById(portletId).style.display = 'none';
// or alternatively using pure Alloy UI
// node.hide();
}
);
</aui:script>
<%
} else {
%>
<aui:script>
Liferay.Portlet.ready(
function(portletId, node) {
document.getElementById(portletId).style.display = 'block';
// or alternatively using pure Alloy UI
// node.show();
}
);
</aui:script>
<%
}
%>
In case you want to check-out Alloy UI API and some of the demos to learn Alloy UI since starting from Liferay 6.1 Alloy UI is the de-facto javascript library for liferay. Now Alloy UI has an official web-site with many helpful tutorials and examples.
Hope this gives you ample material to proceed :-)
You also can do like this :
If your portlet id is : callcenter_WAR_xyzyportlet
$('#p_p_id_callcenter_WAR_xyzyportlet_').css({display:'none'});

how do you pass in a collection to an MVC 2 partial view?

how do you pass in a collection to an MVC 2 partial view?
I saw an example where they used the syntax;
<% Html.RenderPartial("QuestionPartial", question); %>
this passes in only ONE question object..
what if I want to pass in several questions into the partial view and , say, I want to list them out.
How would I pass in SEVERAL questions?
Because your partial view will usually be placed in some other (main) view, you should strongly-type your main view to a composite ViewData object that looks something like this:
public class MyViewData
{
public string Interviewee { get; set }
// Other fields here...
public Question[] questions { get; set }
}
In your controller:
var viewData = new MyViewData;
// Populate viewData object with data here.
return View(myViewData);
and in your view:
<% Html.RenderPartial("QuestionPartial", Model.questions); %>
Then use tvanfosson's advice on the partial view.
Instead of passing question, why not pass a collection of questions, for instance List<QuestionType>?
Normally, you'd have an IEnumerable<Question> as a property in your view model -- in reality it might be a list or an array of Question objects. To use it in your partial, just pass that property of the view model as the model to the partial. The partial should be strongly typed to accept an IEnumerable<Question> as it's model.
<% Html.RenderPartial("QuestionPartial", Model.Questions ); %>
Partial:
<%# Page Language="C#"
MasterPageFile="~/Views/Shared/Site.Master"
Inherits="System.Web.Mvc.ViewUserControl<IEnumerable<Question>>" %>

Resources