AdoptOS

Assistance with Open Source adoption

ECM

Liferay Jenkins

Liferay - 11 hours 57 min ago

Hi,

 

today i have done integration of jenkins using liferay 6.2.

while setup jenkins need to be follow below steps:

  1. Download Jenkins software using below URL

URL : https://jenkins.io (select downloads button on navigation bar)

 

  1. Extract zip file and run Jenkins.exe file then put default password after installation

          File Name : Program Files (x86)\Jenkins\secrets\initialAdminPassword

  1.    Now setup your Jenkins server and install some required plugins after login with http://localhost:8080  (default url for Jenkins)
  2. Now setup admin account using jenkins server as below :

Then click on save and finish.

  1. Start jenkins as below :

You can see below screen after starting jenkins

 

  1. Click on Manage Jenkins -> Configure System (see below image)

  1. Configure Maven, Java and others:   As shown in below, Set JAVA_HOME path with system JDK path and set Maven, ANT also(use global tool configuration option)

 

 

 

You can configure other tools as well then click on save button.

 

 

 

  1. Set  SMTP server details for email communication as below(use configure system option)

 

Then click on save button.

 

SVN and Build Job Configuration

  1. Jenkins need to be configured with SVN, where your project plugin sdk is exists. In general, Liferay Plugin SDK will be created in SVN to hold Portlet, Theme, Layout, Hooks and EXT plugins. Liferay plugin SDK has robust support for ANT build scripts. In this section, We will look into SVN configuration and building whole plugin SDK.

                    In Jenkins home page, click on “New Item”. This action will create a project in Jenkins, technically  which would create workspace folder in JENKINS_HOME(JENKINS_HOME  is a jenkins folder  in user Documents folder in windows machine)  

 

  1. Give some name and select as free style project and click on “Advanced” under General Tab Options.need to be click on free style project then you can able see below screen

  1. Click on “Advanced Option” to configure custom workspace for your project in case if  you don’t want it to reside in JENKINS_HOME folder. Select User Custom Workspace and give complete path in Directory

             Ex. F:\Liferay Projects 2015\Liferay Jenkins\jenkins_workspace

Then click on save button.

  1. In above screen you can see source control management option there you can configure your source code repository

Then click on save button

 

  1. Jenkins provides below  choices on “When to take late code from SVN”.   Select Poll SVN option to take update from SVN when developers commits code and give time frame that to poll SVN. 

Then click on save button.

  1. Now we are done with  SVN configuration and In this step, we are about to configure Build actions and Post Build actions. build.xml file custom ant build script to build the liferay project as tar ball for clean deployment.
  1. In target section: give ant command “all”. Liferay SDK build.xml as “all, zip-portal, clean, deploy, war, ear, compile, build-service” targets.
  2. Build File: If you want to use liferay-plugin-sdk build file, then give the  workspace\liferay-plugin-sdk\build.xml file rather custom file. Make sure that you copy  build.{user-name}.properties file into that folder.
  3. Properties: Suppose, you might want to generate build with SVN Revision number. This is where you need to inject properties into build.xml file. 

Note : make sure that need to configure ANT first using global tool configuration Option        

  1. In general, you may have requirement to notify developers about SVN build status. Jenkins provides an option to notify developers through mail under “Post Build Actions”.

Click on save button.

  1. So once you deploy your portlet in will reflect your jenkin server.
  2. Now we have configured successfully jenkin server with Liferay.

Thanks,

 

Vikash Chandrol 2016-12-02T16:36:29Z
Categories: CMS, ECM

Fantastic Extension Points - And Where to Find Them

Liferay - 15 hours 40 min ago
One more year, it's been great to meet many of you at our events. This time I dedicated one of my workshops to the migration of 6.2 plugin projects to Liferay 7.0. I knew that some of you had already started planning this migration and you would find it helpful, so thanks for the good feedback. And thank you even more for all your questions, as they help us know what your concerns are and what you need to move towards Liferay's latest version.   Fortunatelly, it was easy to answer most of your questions. Our Documentation Team has made such a tremendous effort to cover the code migration topics, that I only had to point out the related section in our Developer Network. So kudos to the Docs Team!    In some cases, however, there's not just a single section I can refer you to. That's the case of the new extension mechanisms in Liferay, for instance, where you'll find dozens of guides each covering a particular use case. Motivated by this fact and by the many questions received during the workshops about the future of extensibility in Liferay, I decided to write this blog entry and guide you through some useful resources on this topic.   Hook Plugins in Liferay 6.2 Let's start with a quick review of what you already know: what hooks could you for you to customize Liferay Portal 6.2. If you want to refresh your knowledge, I recommend a quick reading of the Customizing Liferay Portal with Hooks section. To summarize, in Liferay 6.2 we used hooks for:
  • Overriding web resources
  • Customizing JSPs by extending the original
  • Overriding language properties
  • Overriding portal services
  • Creating model listeners
  • Overriding and adding struts actions
  • Extending indexers
  • Extending other plugins
Liferay 7.0 still supports legacy hook plugins. So first take your time to analyze the pros and cons of continuing with the plugin approach. Here you have a couple of readings on this subject: In this guide I'll cover the alternatives to hook plugins that Liferay 7.0 offers. As you'll discover, not only are the same customization cases covered in a simpler and more flexible way, but also new extension points, tools and samples are provided.   Extenssion Points in Liferay 7.0. If you're new to OSGi and the modular development in Liferay, let me suggest these articles before you continue reading: As you see, Liferay 7.0 is about modularity and keeping things small and simple. And the new extension mechanisms follow exactly this pattern. Let's review some of them:   Customizing the Portal UI A typical task that you face in Liferay projects is customizing Liferay Portal's default UI to match your requirements. In Liferay 6.2 this involved using hooks to change the appearance and behavior of the Dockbar and its sections. Liferay 7.0's UI provides new and much powerful ways to customize the main sections in the Product Navigation:     Furthermore, with Liferay 7.0 it is easier to customize some UI areas where previously hooks or themes were required: And don't forget about Application Display Templates, one of the easiest, fastest and most powerful ways to customize Liferay applications. With Liferay 7.0 you can customize the portal navigation without writing your own theme. And you can even extend the template context with your custom variables and functionality: Overriding JSPs Liferay applications, both in the core and in modules, often contain JSPs. In Liferay 7.0 you can leverage OSGi Fragments hosts to override these files: Customizing Portlet Behavior Moving a step down, Liferay also provides extension mechanisms in the control layer. For instance, you can override MVC commands with your own custom logic. If you're migrating from 6.2, take a look to this article about StrutsActionWrappers first. You can even replace the portlet that, by default, is "in charge" of certain actions - like displaying a web content - with yours:

Extending the Model Layer

Liferay's model layer is built with Service Builder. You can learn more about the Service Builder and the generated code in this article. The model layer can be customized and extended for many different purposes. For instance you may want to change how blog entries are created, add some extra fields to the indexed documents or execute your own logic after a web content is updated: Modifying Language Keys Another common use case in Liferay projects is modifiying or extending the default language keys in core and module applications:

Overriding Portal Properties

Last but not least, in case you need to override Liferay's default properties you can also leverage OSGi fragments (as the ones described for JSPs) for this purpose:

Tools and Samples

What you read in the previous section was just a summary of some Liferay's extension mechanisms. If you really want to learn about them, the best way is to get hands on some code. To make this task easier, Liferay provides code templates for the most common use cases. And this is not all. Developers love examples and replicable code, and we love developers so we have a whole repository full of sample modules for you: Finally, if you've read the articles I recommended at the beginning of this entry, you should know that all Liferay applications (including Web Content, Wiki, etc.) are now built in the same way as you'll build yours. This means you can also use the Liferay Portal repository as a source of samples for your own projects!   Some Final Notes If you read this far (I know, I know, it's been a long entry) then you have the docs, the tools and the samples to start writing extensions for Liferay 7.0. Please give it a try! Don't forget we're here to help you, so use the Forums to share any questions or issues you may find.   We'll also appreciate any feedback about these materials and how to improve them. And don't forget that you can contribute to the documentation, the tools and the samples yourself! Eduardo P. Garcia 2016-12-02T12:53:50Z
Categories: CMS, ECM

Javadocs for DXP Digital Enterprise, Liferay Portal CE, and apps

Liferay - Tue, 11/29/2016 - 11:21

Whether you're new to developing on Liferay DXP Digital Enterprise 7.0 and Liferay Portal CE 7.0 or you've developed on previous versions of Liferay Portal, the latest additions and improvements to Liferay's reference docs are for you.

New additions and improvements:

  • Reference docs for DXP Digital Enterprise, DXP apps, and CE apps
  • Improved Javadoc organization and aggregation
  • Module info in app Javadoc
  • Accessible Javadoc archives

As background, reference docs for previous versions of Liferay Portal and its built-in apps were grouped together, mirroring the product's monolithic codebase. However, Digital Enterprise 7.0 and Liferay Portal CE 7.0 comprise a small core set of modules and no applications.Applications that were previously built into Liferay Portal are now independent applications or in separate application suites. The respective reference docs now accompany DXP Digital Enterprise, Liferay Portal CE, and their apps.

Here’s the new reference documentation hierarchy on https://docs.liferay.com:

 

The digital-enterprise and portal folders contain subfolders for their releases. Their 7.0-latest subfolders link to reference docs for the latest 7.0 releases.

 

Figure 1: The *latest folders link to the latest version folder for the product.

 

Every Digital Enterprise release folder and Portal CE release folder has the following types of reference docs:

  • Javadoc

  • Taglib docs

  • Properties file defaults and descriptions

  • DTD Definitions

Reference docs for DXP and CE applications and applications suites reside in the dxp/apps/ and ce/apps/ folders.

Javadoc for an app’s modules is conveniently aggregated. The Javadoc overview page groups packages by module and shows each module’s bundle symbolic name and version, for developers to copy into their dependency files (e.g., build.gradle or pom.xml files).

Group name: com.liferay applies to all Liferay modules.

Figure 2: An app's Javadoc contains Javadoc for all packages in all the app's modules. The overview groups packages by module and highlights the module name

Lastly, all product release folders have a downloadable Javadoc archive (ZIP or JAR) for developers to download and browse offline if needed.

Figure 3: Want to browse Javadoc offline? Download a Javadoc archive.

 

As always, thanks to everyone who has contributed and continues to contribute Liferay reference docs.

 

Coming soon:

  • Application Taglib docs

  • Reference docs for more applications (apps for Digital Enterprise 7.0 and Portal CE 7.0)

 

See also:

James Hinkey 2016-11-29T16:21:33Z
Categories: CMS, ECM

Behind Every Great Module There's a Great Design

Liferay - Tue, 11/22/2016 - 01:28

Software should be modular. It has been a core thought of developers and architects for quite a long time. The microservices style of development and deployment is a thing now, and different projects are taking the modularity from logical design also to physical level. There’s project Jigsaw that’ll offer a way to create modules in Java 9. Liferay is onboard with modularity as well, with their most recent release of Liferay DXP built on top of OSGi framework.

The annual Liferay Developer Conference, DevCon, was set up in Darmstadt once again. One of the keypoints this year was modularity in software, so much so, that there actually was a conference inside a conference. MODCONF to be exact, focusing more on the features of modular software in the big picture.

Ambientia Crew in Liferay Devcon

The talks revolved around the idea of creating interchangeable modules where you can just plug in a different implementation through certain extension points. How to accomplish this depends on the framework you’re using but the benefits of this kind of thinking remain the same. Being able to easily extend and provide your own implementation for something is a powerful thing. Testability of the code is usually enhanced as well, almost like a side-effect to modularization.

Modularity is not a silver bullet, however. To really be able to harness the power modularity is offering, you’ll have to spend time to really think about your design. What are the modules, do they offer a simple API which is easily understandable? Clutter your API and you’ll have a hard time extending it. Upgrade and break your API often and you’ll spend your days fixing the implementations. Or answering emails of fellow developers, demanding to know why you broke their code again. Create an API that is easy to understand and try to design it in a way that you don’t have to change it that often.

Modularity in Liferay DXP

What I was happy to see during the conference was that Liferay is really eating their own dogfood as well. They are modularizing everything and creating guidelines on how the developers should create modules of their own. There were other interesting topics as well, but the main point I took home with me was this: Behind every great module there’s a great design!

 

 

Lauri Hiltunen 2016-11-22T06:28:24Z
Categories: CMS, ECM

Digital Transformation in the Age of Digital Disruption

Liferay - Mon, 11/21/2016 - 22:48

It isn’t possible to read a news article or open a newspaper without reading about Digital Transformation. From consultants and software vendors to infrastructure providers and analysts everyone is talking about digital transformation and how it can improve customer experience, increase revenue streams and improve operations.

So what defines digital transformation?

Capgemini Consulting writes: The use of technology to radically improve performance or reach of enterprises.

Thought leader Brian Solis of Altimeter Group defines it as: The realignment of, or new investment in, technology and business models to more effectively engage digital customers at every touchpoint in the customer experience lifecycle.

Technology is an important component of digital transformation but the most successful stories feature organisations that combine digital activity with strong leadership to empower digital transformation. These businesses also recognise it is a major change management program. Technology alone can’t deliver a transformation. Organisations need to understand what their customer wants at any given moment in time. Think about how to effectively engage customers and think about all the touchpoints that customers have across their entire lifecycle.

Customers hold the power in the digital age

In recent years there has been a marked increase in the power of the customer. Customers now have the power of choice as barriers to change continue to lower and anyone can change suppliers, find a new vendor, or new source of services or goods.

As a result of the power shift to the customer, churn is now a key risk. Previously organisations could rely on a certain amount of loyalty from customers or apathy as change was often difficult. However, new technology and digital platforms have made it easier for customers to shop around and look for better services or value for their money.

Customers are expecting unique and tailored services that meet their needs. Generic processes that appeal to the masses are no longer acceptable. We live in an always on, just in time, on-demand world and if your organisation can’t offer personalised customer service your customer will find a company that can. Customers are no longer willing to put up with average service delivered to the masses. A single experience or interaction impacts customer satisfaction and therefore customer loyalty is often fleeting.

Digital transformation or digital disruption?

Repeatedly the organisations representing successful digital transformation in the new-age economy are Uber or Airbnb - but are they really digitally transforming?

It’s possible to make the case that these organisations are in fact digital disruptors not digital transformers. The challenges faced by legacy organisations are very different to an Uber or Airbnb business. These new disrupters didn’t encounter the challenge of changing existing systems in the same way that legacy organisations must. Maintaining existing business, managing current systems, dealing with operational processes and business models – these factors make digital transformation far more complex.

Feedback from our customers points to some of the main challenges of digital transformation:

·       Internal silos between business units and systems

·       Legacy systems that contain essential customer information were never designed to expose the data

·       Change leader to head the initiative across the organisations to drive digital transformation

·       Budgets being continually squeezed

·       Integration of organisation-wide systems

In fact a recent Forrester study identified that among customer experience decision makers the biggest technical barrier to customer-facing systems was inadequate integration with back-office systems.
Vendor Landscape: Digital Experience Portals, Mark Grannan, Forrester, 4 January 4, 2016

In light of the challenges a question we hear at Liferay consistently is “How do I solve the real problem of delivering our services across channels in a practical, valuable way to our customer?”

Use a platform that will enable flexibility

The answer most commonly is to start with a platform with all the core components in one place and mitigate the risk in acting on a digital trend.

It’s not about trying to do everything at once, start somewhere but choose a platform that provides flexibility to extend and develop. Today’s businesses are constantly discovering new ideas, new source of growth and new initiatives that they want to unlock so they shouldn’t be inhibited by a digital platform.

We regularly see software development being a key differentiator as it enables organisations to develop on top of the platform selected at the start and deliver new projects. Agility to be flexible - in order to adapt and evolve as the market or your business changes - is critical as it’s difficult to predict what will change and how the competition will evolve. Therefore, flexibility in your chosen platform is key to overcoming these challenges.

Digital Experience Platforms to Power Digital Transformation

The concept of a digital experience platform (DXP) is now emerging as horizontal portal and content management systems (CMS) technologies converge. As businesses require a broad range of capabilities to deliver the necessary personalised customer experience a system with limited features is not enough.

Gartner’s Magic Quadrant for Horizontal Portals 2016 identified: The primary catalyst for change in the horizontal portal market is the response to digital business transformation: the evolution of traditional portal into the digital experience platform. The DXP reflects a business-driven focus on improving customer, partner and employee experiences across digital and physical channels.
Gartner “Magic Quadrant for Horizontal Portals” by Jim Murphy, Gene Phifer, Gavin Tay, Magnus Revang, 17 October 2016.

Responding to business’ need to manage and deliver hyper personalised experiences across every digital touchpoint Liferay introduced Liferay Digital Experience Platform (DXP). Liferay DXP is the next advancement of the Liferay portal platform redesigned and extended with greater capabilities for digital businesses. At the core of DXP is the portal acting as the integration layer connecting backend systems across business operations so that companies can give consistent and unified experiences to customers, partners and employees.

Align your business to better serve customers and attract new ones

So, whether you are just starting a digital transformation or you’re reviewing progress to date some key points are worth remembering:

  • Your customer should be at the heart of any transformation strategy – think about their experience and journey in every interaction with your organisation.
  • Digital Transformation is not just about technology; operational processes and people are just as critical to success
  • Silos within operations and systems are causing some of the greatest challenges – think about how you can incentivise the entire organisation to deliver a seamless experience and integrate all touchpoints.
  • Digital is constantly evolving so choose technology that allows your organisation to be flexible.
  • Start small and prove the concept, then grow from there.

2016 Gartner Magic Quadrant for Horizontal Portals

Read what Gartner says about the role of horizontal portals in delivering on digital experience initiatives and key findings and recommendations in the 2016 Gartner Magic Quadrant for Horizontal Portals. Plus, find out why Liferay is named a Leader in the report for the 7th consecutive year.

Adrian Johnson 2016-11-22T03:48:43Z
Categories: CMS, ECM

Liferay DXP: Adding Custom Document Actions

Liferay - Mon, 11/21/2016 - 14:38

At Coin we have developed SimpleEdit, a new and improved way for editing your documents stored in Alfresco or Liferay.
Now that Liferay has moved on to DXP, we are in the process of upgrading SimpleEdit to this new platform.

One of the required modules for SimpleEdit was a JSP hook that provided the user with buttons in the Documents section of the Control Panel and the Documents and Media portlet.

Control Panel

 

 

Documents and Media Portlet

 

Looking at the DXP UI, it became clear that we needed to add our buttons in the following locations.

Control Panel

 

 

Documents and Media Portlet

 

We hoped to be lucky and just port our changes to the new versions of JSP's in Liferay DXP, as described in a tutorial. But after a deep dive into the source code it became clear that this would not be the way forward. These menu's weren't build anymore in a JSP, but through different classes.
After several deep dives into the source code, reading a handful tutorials (see the Resources section) and even a short Twitter conversation with one of the Liferay Document Library developers, we were able to add the four menu items mentioned above.

So here is an overview of what you would need to do if you ever want to achieve the same goals.

Control Panel Document Overview screen

Lets start with the Control Panel. We used to override the file_entry_action.jsp file and add the SimpleEdit action to the icon-menu.
In the new Document Library module the main object used is DLViewFileVersionDisplayContext. The only JSP code here is just one line (all the other lines are scriptlets :-s):

<liferay-ui:menu menu="<%= dlViewFileVersionDisplayContext.getMenu() %>" />

So you'll need to provide the implementation for a Display Context object and have it return some kind of menu. If you don't know what a Display Context is, read the Liferaypedia article describing it (don't worry, I had to discover this new object myself).

A Display Context is a Java class that controls access to a portlet screen's UI elements

To register your Display Context object so that a dlDisplayContextProvider can return it, you should also implement a Display Context Factory. This factory class instantiates and returns your Display Context which provides all the necessities for your view. The factory class will be chained in the list of factory classes according to the service ranking.

The Factory Class

The factory is really easy. From the previous mentioned article you'll learn that for the Document Library several interfaces and abstract classes are available to extend.

@Component( immediate = true, service = DLDisplayContextFactory.class ) public class SimpleEditDisplayContextFactory implements DLDisplayContextFactory { ... }

Just don't forget to define this factory as an @Component and register it as a DLDisplayContextFactory!

At the time of writing you are supposed to implement four constructors, each for a different use case. For the current requirement the most important constructor is getDLViewFileVersionDisplayContext.

public DLViewFileVersionDisplayContext getDLViewFileVersionDisplayContext(DLViewFileVersionDisplayContext parentDlViewFileVersionDisplayContext, HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FileVersion fileVersion) { return new SimpleEditDisplayContext(parentDlViewFileVersionDisplayContext, httpServletRequest, httpServletResponse, fileVersion); }

See how it instantiates and returns the custom SimpleEditDisplayContext? The other three constructors just return the parent Display Context they receive as an input parameter as you are not in the process of changing those.

The Display Context Class

In the file_entry_action.jsp you can see that getMenu() is the method you need to implement in your Display Context.

public class SimpleEditDisplayContext extends BaseDLViewFileVersionDisplayContext { private static final Log LOGGER = LogFactoryUtil.getLog(SimpleEditDisplayContext.class); private static final UUID SIMPLE_EDIT_UUID = UUID.fromString("7B61EA79-83AE-4FFD-A77A-1D47E06EBBE10"); public SimpleEditDisplayContext(DLViewFileVersionDisplayContext parentDLDisplayContext, HttpServletRequest request, HttpServletResponse response, FileVersion fileVersion) { super(SIMPLE_EDIT_UUID, parentDLDisplayContext, request, response, fileVersion); } public Menu getMenu() throws PortalException { Menu menu = super.getMenu(); URLMenuItem urlMenuItem = new URLMenuItem(); urlMenuItem.setMethod(HttpMethods.GET); urlMenuItem.setKey(SimpleEditLabel.OPEN.getUIItemKey()); urlMenuItem.setLabel("Open with SimpleEdit"); urlMenuItem.setURL("simpleedit://..."); menu.getMenuItems().add(urlMenuItem); return menu; } }

Actually this isn't all that hard either. You just need to create a new URLMenuItem, provide it with the proper values and add it to the list of MenuItems you retrieve from the BaseDLViewFileVersionDisplayContext.

After putting these two classes in place and deploying the module, you'll see the following result.

A vertical ellipsis is added to the Document and upon hovering on it, your MenuItem is shown among the other actions.

Document Detail screen

Maybe your first hope is (as well as mine was): the getMenu() method will serve as the provider for the actions menu in the Document Detail screen... Bad luck, if you go to the Document Detail you'll see that the menu hasn't changed a bit.

Again after some deep dives into the code, I was rattled on how to achieve this. But incidentally, I stumbled across the section Configuring your apps action menu (a real coincidence as at that time I was gathering information concerning styling). However this seemed to be more what I was trying to do here. Too bad this important piece of information was so well hidden. But now I had a proper clue of what to look for in the source code.

Thus I learned that you'll need something called a Portlet Configuration Icon (what's  in a name, heh) and that you can extend the BasePortletConfigurationIcon class for ease of implementation.
The three most important methods however (or the three I implemented) are:

  • isShow - whether or not your action should be displayed (can be conditional)
  • getMessage - to provide a translation for your action
  • getUrl - the url your action should link to

Portlet Configuration Icon can also be declared as a Declarative Service. As you are actually extending the already defined list of Document Library Configuration Icons, you can reuse the properties from e.g. the DownloadFileEntryPortletConfigurationIcon to add yours to that list. For more information on the properties, please read the aforementioned section in the Lexicon tutorial.

@Component( immediate = true, property = { "javax.portlet.name=com_liferay_document_library_web_portlet_DLAdminPortlet", "path=/document_library/view_file_entry" }, service = PortletConfigurationIcon.class ) public class SimpleEditConfigurationIcon extends BasePortletConfigurationIcon { private static final Log LOGGER = LogFactoryUtil.getLog(SimpleEditConfigurationIcon.class); @Reference private Portal portal; @Reference DLAppService dlAppService; public boolean isShow(PortletRequest portletRequest) { return true; } public String getMessage(PortletRequest portletRequest) { return "Open with SimpleEdit"; } public String getURL(PortletRequest portletRequest, PortletResponse portletResponse) { HttpServletRequest servletRequest = portal.getHttpServletRequest(portletRequest); FileEntry fileEntry = retrieveFile(servletRequest); return "simpleedit://..."; } private FileEntry retrieveFile(HttpServletRequest request) { try { long fileEntryId = ParamUtil.getLong(request, "fileEntryId"); FileEntry fileEntry = null; if (fileEntryId > 0) { fileEntry = dlAppService.getFileEntry(fileEntryId); } if (fileEntry == null) { return null; } String cmd = ParamUtil.getString(request, Constants.CMD); if (fileEntry.isInTrash() && !cmd.equals(Constants.MOVE_FROM_TRASH)) { LOGGER.info("File entry is not supposed to be opened."); return null; } return fileEntry; } catch (PortalException e) { LOGGER.error("An exception ocurred while retrieving Simple Edit Url.", e); return null; } } }

If you update your module now, I think you are happy to see the action.

So that's it for the Control Panel. Let's move on to customizing the Documents and Media Portlet!

Documents and Media Portlet

For Liferay 6.2 it was sufficient to override two JSP files: the file_entry_action.jsp I spoke earlier about and the view_file_entry.jsp file as well. In that last one you could add a button to the already provided action buttons (Download, Edit, Move, ...).
In fact, all it took were those two JSP's to adjust both the Control Panel and the Documents and Media Portlet because those shared the same resources.

So maybe you have already opened the Documents and Media Portlet after the first section? Then you have seen that for Liferay DXP this is no longer the case. All the effort you have done so far, has had just one impact. Only in the Documents Overview your custom action is added to the Document.

If you go to the Documents Detail screen, you only see an "Info" button. So you'll need to add some more code to get your action in that list as well.

Overview screen

But wait a minute. Isn't it strange that there is just one sole action shown? Shouldn't there be more actions such as Download, Edit, Move, ... ?
That's because this Portlet can also be configured and one of these Configurations is "Show Actions". When you activate this you'll see the other actions as well.

You should take this configuration into account while adding your custom action to the menu. I had some trouble figuring this out, so here is where the short Twitter conversation with Sergio González comes in place (he's mentioned as one of the Document Library developers in the source code):

Here is the link to the source code he mentions: https://t.co/YEGooQgR6Y

If you follow that, you see that you can use a SettingsFactoryUtil class that can retrieve the settings for the portlet instance you are using. If you want to use that, you'll need to store the ThemeDisplay from the original request to retrieve the necessary parameters from it. In the code below you can see this addition, as well as a showAction() method where the decision logic is captured. Please note the fact that if it concerns the Document Library Admin Portlet, it always returns true. If this is not the case, you will never see the action in the Control Panel anymore (I had to find this out at hand and via the example code as well).

public class SimpleEditDisplayContext extends BaseDLViewFileVersionDisplayContext { private static final Log LOGGER = LogFactoryUtil.getLog(SimpleEditDisplayContext.class); private ThemeDisplay themeDisplay; public SimpleEditDisplayContext(UUID uuid, DLViewFileVersionDisplayContext parentDLDisplayContext, HttpServletRequest request, HttpServletResponse response, FileVersion fileVersion) { super(uuid, parentDLDisplayContext, request, response, fileVersion); this.themeDisplay = (ThemeDisplay) request.getAttribute(WebKeys.THEME_DISPLAY);; } public Menu getMenu() throws PortalException { Menu menu = super.getMenu(); if (showAction()) { URLMenuItem urlMenuItem = new URLMenuItem(); urlMenuItem.setMethod(HttpMethods.GET); urlMenuItem.setKey("open-with-simple-edit"); urlMenuItem.setLabel("Open with SimpleEdit"); urlMenuItem.setURL("simpleedit://..."); menu.getMenuItems().add(urlMenuItem); } return menu; } private boolean showAction() throws SettingsException { PortletDisplay portletDisplay = themeDisplay.getPortletDisplay(); String portletName = portletDisplay.getPortletName(); if (portletName.equals(PortletKeys.DOCUMENT_LIBRARY_ADMIN)) { return true; } Settings settings = SettingsFactoryUtil.getSettings(new PortletInstanceSettingsLocator(themeDisplay.getLayout(), portletDisplay.getId())); TypedSettings typedSettings = new TypedSettings(settings); return typedSettings.getBooleanValue("showActions"); } }

Another update of the module leads to the result that no actions are shown when the "Show Actions" Configuration is not selected. And your custom action is still added to the list when the Configuration is selected.

So with that in place, let's focus on the Document Detail screen again.

Document Detail screen

Now that you have configured the Documents and Media with the "Show Actions" configuration to true, you see all the other buttons next to Info as well (Download, Edit, ...). So how to add your own button to that list?

Well I don't know if you noticed it, but in the DLViewFileVersionDisplayContext there is also a method getToolbarItems(). When I was on the endeavour of adding an action to the Control Panel, I fidgeted with that method but it didn't had any result for the Control Panel. Nor in the Document Overview nor in the Document Detail view.
But one more try while investigating this last issue and I learned that those buttons on the Document Detail screen are in fact "Toolbar Items". Actually I have no idea why the menuItems aren't reused for this row. That would seem a logical decision, but for some reason or another Liferay didn't do it.

Therefor you'll have to provide an implementation for the getToolbarItems() method as well in order to complete this entire functionality. Again it is rather easy (just as the getMenu() implementation). Create a Toolbar Item yourself and populate it with the correct properties.

@Override public List<ToolbarItem> getToolbarItems() throws PortalException { List<ToolbarItem> toolbarItems = super.getToolbarItems(); URLToolbarItem toolbarItem = new URLToolbarItem(); toolbarItem.setKey("open-with-simple-edit"); toolbarItem.setLabel("Open with SimpleEdit"); toolbarItem.setURL("simpleedit://..."); toolbarItems.add(toolbarItem); return toolbarItems; }

Another strange thing here is that for the Toolbar Items you do not need to consider the "Show Actions" configuration yourself. Whereas for the Menu you did need to take that into account. It seems like the Liferay people are missing some sort of consistency mechanism. Maybe they have there good reasons for it, but it just seems strange to me at the moment. Calling the getToolbarItems() method is conditionally determined on a higher level apparently, and the getMenu() method is not. Just deal with it, I think.

Conclusion

It seems somewhat more complex than overriding two JSP files like you would've done in the past. And there are some minor drawbacks at the moment.

It's a little sad that you can't annotate the DisplayContext with an @Component. This would make it much easier to inject Liferays Declarative Services or even your own ones. The real SimpleEdit implementation is more complicated than the code shown in this blog post and thus we had to pull some tricks. Liferay does provide you with some mechanics to work around this, e.g. Service Trackers. It is just a side effect of the modularity environment and we will have to learn to work with this. Let's hope the Liferay developers will find a way around this.

Another peculiarity is the fact that in the Display Context object you have to provide the getMenu() and the getToolbarItems() methods for roughly the same functionality. And that you need to consider the "Show Actions" Configuration yourself in the getMenu() method but not in the getToolbarItems() method. Some consistency might be in place here. But as long as you know about it, you can deal with it.

Also I feel that the naming sometimes sounds strange. I am still trying to find out what the actions for a Document Entry have to do with an Icon for the Portlet Configuration.
All in all it is harder to understand the internal workings of the platform at the moment. There is a whole new syntax in Liferay DXP, the relations between the modules are not always clear and the documentation is far from complete.
That's what you get from dividing everything in small modules and reworking the entire back end.

On the plus side DXP provides you with more flexibility and dynamics. If you divide your classes neatly in different modules, you can easily swap implementations, changing the behaviour of the Portal as you like without any downtime.
And you are working with real Java classes (instead of those dreadful JSP's) which provide you with lots of advantages such as service injection, OO programming, unit testing, ...

By implementing just three classes, you can add your custom buttons to the Document and Media Portlet and the Control Panel.

  • Display Context - to provide a view all the UI elements and the decision to show these or not
  • Display Context Factory - to conditionally add your DisplayContext to the list of already existing DisplayContexts
  • Configuration Icon - to add actions to the Portlets action menu

I hope you have learned a lot from this blog post. You probably also understood that you'll need to search heavily in the tutorials and the Liferay source code to gather all the information you need to connect the dots for the adjustments you are trying to do. But that is a new challenge that makes our job interesting, day in day out!

Thanks for reading!

You can find the source code for this blog post in my Github repository.

Resources Tutorials

Display Context
Configuring your apps actions menu
Service Trackers
Service Ranking
Overriding core JSP's

Source code

Document Library
Document Library Google Docs module
Frontend image editor - DisplayContextHelper

Koen Olaerts 2016-11-21T19:38:34Z
Categories: CMS, ECM

Create a copy document from document and media into portlet local temp folder

Liferay - Thu, 11/17/2016 - 01:48
OutputStream outputStream = null; InputStream inputStream = null; try { DLFileEntry fileEntry = DLFileEntryLocalServiceUtil.getFileEntry(targetGroupId, folderId , filename); inputStream = DLFileEntryLocalServiceUtil.getFileAsStream(themeDisplay.getUserId(),fileEntry .getFileEntryId(), fileEntry .getVersion()); String tempFolderPath = resourceRequest.getPortletSession().getPortletContext().getRealPath(File.separator) + "temp"+File.separator;   File tempFolder = new File(tempFolderPath); if (!tempFolder.exists()) {    try{     tempFolder.mkdir();    }catch(Exception se){        se.printStackTrace();    }  }else{ String[] files;        if (tempFolder.isDirectory()) {         files = tempFolder.list();            for (int i = 0; i < files.length; i++) {                File file = new File(tempFolder, files[i]);                if (!file.isDirectory()) {                 file.delete();                }            }        } }   File file = new File(tempFolderPath+File.separator+spreadSheetName); outputStream = new FileOutputStream(file); int read = 0; byte[] bytes = new byte[1024]; while ((read = inputStream.read(bytes)) != -1) { outputStream.write(bytes, 0, read); } outputStream.close();   spreadSheetPath = file.getAbsolutePath(); } catch (Exception e) { e.printStackTrace(); } finally{ try{ if(outputStream != null) outputStream.close(); if(inputStream != null) inputStream.close(); }catch(Exception e){ e.printStackTrace(); } } Vikash Kumar 2016-11-17T06:48:01Z
Categories: CMS, ECM

Content SEO Title - Putty In Your Hand

Liferay - Fri, 11/11/2016 - 11:18

In my last technical post titled Content SEO - Hidden in Plain Sight, I exposed a caveat in the way the title of a content item is auto-crafted by Liferay. Here’s an excerpt from that article, which I hope highlights the problem. If not, I encourage you to give that post a read..

 

Note that the Title specified is Young Night. But if you look at what got into the page source above, you can see we had: Young Night - Browse Poems - Liferay. The page name and site name seem to get suffixed to the Title.

 

So, here is how I solved it:

Lunch!

 

Over at LSNA2016, I hunted down the Content Management Roadmap table and found Julio Camarero crowded by rabid developers bombarding him with all sorts of CMS questions. I took my seat across from him… and waited.

...and waited

...and waited

...AND WAITED!

 

I eventually spied the door open and shamelessly stuck my foot in. In a blast of what felt like 400 words, I explained the problem as alluded to in my previous post hyperlinked at the top of this article.

 

Julio nodded and responded with - I paraphrase - “Yeah, there’s a JSP in the asset publisher where we construct the content title in that way. Email me in a couple days.”

 

I did. He responded. And here it is.

 

The first order of business is to understand that the below JSP is where the title and subtitle are seeded.

ROOT/html/portlet/asset_publisher/asset_html_metadata.jsp

  <%@ include file="/html/portlet/asset_publisher/init.jsp" %> <% AssetEntry assetEntry = (AssetEntry)request.getAttribute("view.jsp-assetEntry"); AssetRenderer assetRenderer = (AssetRenderer)request.getAttribute("view.jsp-assetRenderer"); String title = (String)request.getAttribute("view.jsp-title"); if (Validator.isNull(title)) { title = assetRenderer.getTitle(locale); } String summary = StringUtil.shorten(assetRenderer.getSummary(locale), abstractLength); PortalUtil.setPageSubtitle(title, request); PortalUtil.setPageDescription(summary, request); PortalUtil.setPageKeywords(AssetUtil.getAssetKeywords(assetEntry.getClassName(), assetEntry.getClassPK()), request); %>  

Now, you can insert a line to set the page title to the empty string.

  <%@ include file="/html/portlet/asset_publisher/init.jsp" %> <% AssetEntry assetEntry = (AssetEntry)request.getAttribute("view.jsp-assetEntry"); AssetRenderer assetRenderer = (AssetRenderer)request.getAttribute("view.jsp-assetRenderer"); String title = (String)request.getAttribute("view.jsp-title"); if (Validator.isNull(title)) { title = assetRenderer.getTitle(locale); } String summary = StringUtil.shorten(assetRenderer.getSummary(locale), abstractLength); PortalUtil.setPageTitle("", request); PortalUtil.setPageSubtitle(title, request); PortalUtil.setPageDescription(summary, request); PortalUtil.setPageKeywords(AssetUtil.getAssetKeywords(assetEntry.getClassName(), assetEntry.getClassPK()), request); %>  

The problem with the above is that your title eventually changes from:

Young Night - Browse Poems - Liferay

 

… to

Young Night - - Liferay

 

Ugh! So, now I needed to figure out where that concatenation happens. I just plain didn't know. After some unsuccessful grepping, I decided to reach out to Julio again, and he graciously shone a light on where this happens. Here are the relevant lines from init.vm of the _unstyled theme.

  #if ($pageSubtitle) #set ($the_title = $pageSubtitle + " - " + $the_title) #end  

Assuming your theme is based on the _unstyled theme, this value can be overridden by defining an init_custom.vm file and setting $the_title to be the $pageSubtitle alone, i.e.

  #set ($the_title = $pageSubtitle)  

Now, your title looks like this:

Young Night - Liferay  

There!

 

WHAT?! Not satisfied?

 

I suppose you don’t want the site name to appear either. You find that trailing - Liferay there undesirable?

 

Keep in mind that the site name (aka company name) may be a good thing to hang on to depending on how your SEO philosophy pans out. I don’t know what the best practice is with respect to that, but if you don’t want it, simply remove the - $company_name variable from the value of the <title/> element in the portal_normal.vm of your theme.

  <head> <title>$the_title - $company_name</title> … …   So, in conclusory verse:

Tweak $the_title of your content

Fa-la-la-la-la la-la-la-laaa

Find the $company_name, yank it

Fa-la-la-la-la la-la-la-laaa

Have a great November!

 

FYI a ticket has been opened for the Content SEO fields to be configurable.

https://issues.liferay.com/browse/LPS-68495

...as well as an issue for the problem at hand, here:

https://issues.liferay.com/browse/LPS-68493

Javeed Chida 2016-11-11T16:18:40Z
Categories: CMS, ECM

Blog

Liferay - Tue, 11/08/2016 - 01:47

You can follow my blog on https://www.miguelangeljulvez.com/blog

Miguel Ángel Júlvez 2016-11-08T06:47:36Z
Categories: CMS, ECM

Back end customizations in LR7

Liferay - Mon, 11/07/2016 - 06:33

Liferay 7 has been developed keeping modularity as the soul and core of development. This feature of Liferay 7 has made it quite usable and customizable. This modularity has provided liferay developers, the facility that they can customize(override) any back end functionality quite easily. In Liferay 7 you do not need to create any separate hooks, any xml-entries(in almost all the cases) whether you want to use any Lifecycle events(pre-login, post-login etc), service wrappers, Model Listeners, create or override any MVC command, StrutsActions etc. All of these have become quite easy and straight forward now.

For all of these things you just need to create your custom source class and define some properties. If you are creating something new for example Model Listeners, Lifecycle events, service wrappers, MVC command etc you just need to do:

a) Create a custom source file.
b) Define some properties

Let's take a few examples

1. Post-login event
    @Component(
        immediate = true, property = {"key=login.events.post"},         //specifies post-login event
        service = LifecycleAction.class                                              //specifies Lifecycle implementation
    )
    public class PostLoginLifecycleAction implements LifecycleAction{
                      // your functionality here        
    }

Check this blade sample for more on how pre-login or post-login events are implemented.

 

2. ModelListener

@Component(
        immediate = true,
        service = ModelListener.class                                              //specifies ModelListener Implementation
    )
    public class SampleUserModelListener extends BaseModelListener<User> {
                       // your functionality here        
    }

Check this blade sample for more on how model listeners are implemented.

 

3. MVC Commands

You need to create different source files for each MVC Command and that command will be mapped as:


@Component(
        property = {
            "javax.portlet.name=your_portlet_name_id",               //your portlets name
            "mvc.command.name=/view",                                      //your mvc command defined in jsp
            },
        service = MVCRenderCommand.class)   

// If creating MVCRenderCommand. Use MVCActionCommand.class if creating functionality of Action Command

 

In jsps define mvccommands as:

MVCRenderCommand:

<portlet:renderURL var="view">
      <portlet:param name="mvcRenderCommandName" value="/view" />
</portlet:renderURL>

MVCActionCommand:

<portlet:actionURL name="mvcActionCommandName" var="editSample" >
</portlet:actionURL>

Check this Overriding MVC Commands for more on how MVCCommands are implemented.

 

Existing source customization

1.Create a new source file.
2.Go to original file, copy all @Component properties into your new source file. add new property "service.ranking:Integer=100" inside properties of @Component part with a value higher than original file as following (I am overriding com.liferay.journal.exportimport.data.handler.JournalPortletDataHandler here):

@Component(
        property = {"javax.portlet.name=" + JournalPortletKeys.JOURNAL",
                "service.ranking:Integer=10"
                },
        service = PortletDataHandler.class
    )

Check this original JournalPortletDataHandler.

Only service.ranking:Integer=10 is added to tell OSGI that my ranking is higher. Now OSGI will pick this file at run time.

Code your required functionality into this file and done.

 

Doing all customization was quite easy and we just needed a pure java source file, no xmls were needed in above cases.

Gaurav Jain 2016-11-07T11:33:34Z
Categories: CMS, ECM

Embedded Analytics

Liferay - Mon, 10/31/2016 - 08:57
Build Around MDX

I cannot make the case to encourage you to write MDX. It's a complex query language, underutilized, supported by major vendors*, but after 20 years of existence still struggle to get traction, much like SQL.

But a query language adds an abstraction layer needed for embedded analytics, avoiding APIs nightmares, closed doors and impossible iframes.

MDX was originally used by Excel Pivot Tables to access remote SQL Server data. Getting support later from IBM Cognos, Oracle Hyperion, SAS, Microstrategy, SAP, and others, to reach a wider Excel user base. Now most of this vendors use their own portals for analytics. 

Pentaho's Mondrian project  is one of few open source OLAP Server alternatives to run MDX queries. Exploratory web applications like JPivot, Saiku and Pivot4j are great for advanced users, who can keep an eye on the underlying MDX. But a bunch of isolated charts and tables are not enough to convince users to explore, validate and take action.

Liferay can be a better platform for MDX. I will try to show the case in a modular and open source environment with no dependencies and minimum requirements, to respond last mile business questions.

On Liferay Marketplace

I already embed a Mondrian OLAP Server in a Liferay portlet, run MDX queries against XMLA servers using Olap4j, show results using JSON, DataTables, Select2, C3 charts and D3 visualizations, with drilldown, filter and search across, including an ACE editor with MDX highlighting and metadata autocomplete. But it's not enough.

See the app in the marketplace.

Who What Where When

Multidimensional models are built to respond questions about specific facts using measures, dimensions, hierarchies and levels. They have limitations, but can easily respond a wide range of when, what, who, where type of questions. For other questions, like how and why, the user have to make an additional effort to infer responses.

MDX queries are executed against multidimensional models to respond this type of questions. I can show results from the old and still awesome FoodMart model.

 

Last Mile Analysis

This portlets are focused on navigation and simple interactions, to communicate and bring data analysis exploration to a wider user base. They just present the results of MDX queries, using portal preferences and public render parameters to enable exploration.

But someone have to write and configure MDX queries. The ACE editor portlet is just a helper for advanced users to write MDX queries. Also someone has to configure and provide data sources.

And the rest of the journey is out of scope.

Big Path?

Mondrian is a JDBC wrapper and depends on SQL. Simple models are currently working with Apache Calcite Avatica for MongoDB. Apache Kylin already proved some parts for Hadoop.

Mondrian 4 OSGi Bundle?

The latest version of Mondrian already supports OSGi, but new features broke compatibility with previous models and has low adoption. Perhaps OSGI could support multiple versions and forks like GeoMondrian. But OSGi is also new to Liferay.

Mondrian cache compete with Liferay in the same virtual machine. For large data sets or complex environments Mondrian can be configured independent as remote XMLA server, without OSGi dependencies.

Feedback

This is a work in progress, please request access to the private repository.

Fabian Larroca 2016-10-31T13:57:56Z
Categories: CMS, ECM

A wild structure type has appeared!

Liferay - Tue, 10/25/2016 - 10:37

People playing around with the new features of Liferay may have noticed something very interesting when creating web content structures. What is it you might ask? It is a way to embeded web content articles into other articles!

 

But wait, how do I use this stinking thing?

 

Using DDM Journal Article Structure

 

If you've already tried to use this you may notice that when you use the usual getData() it returns a JSON string containing the className and classPK of the article. Cool, but I don't have access to serviceLocator and don't know how to get the display of the content. Well you can, thanks to <@liferay_ui['asset-display'] />. Lets see the template.

As we can see above, by using the taglib we are able to pass in those two values that the structure gives us and display our web content. Because we are referencing the primary key of the article, if we make revisions those changes will be reflected when displaying the content.

 

But why should I care?

 

Good question, I mean this is a cool feature but how does it help accomplish your needs? Imagine you've created some great copy that you want to use throughout your site. You want to use it on a landing page with some other content to make some nice columns, but you also want to include it by itself on another page. And who wants to repeat themselves? With this, you can make your content and get it looking exactly how you need it to look, then you can use this new structure type to embeded it wherever you need it! And since you are creating a new template, you can rearrange how you want the articles to be displayed on the page without changing the embeded articles template.

 

But wait, there is more...

 

Imagine that I do want to change the template of the embeded article so that the markup is different depending on how it is being used. Lets look at some templates:

 

 

 

In this template I am using freeMarkerPortletPreferences.setValue("view", "basicContentView") to set my own custom preference. I've given it a value that is unique so that nothing else is disrupted. And here is another template where I am using the Bootstrap 3 markup for the carousel:

 

 

 

 

Again I am using the same pattern to set a unique view preference. But what about in the template I am embedding? I've created a basic structure and template for displaying a picture and some text. But I want to display it differently if I am creating a carousel or displaying them all together. So lets take a peak at how this affects my card template:

 

 

In this template we check to see what view has been set, and provide a default view if none is set. Look at this gif to see this in action:

 

 

The two web content articles on the left are displaying the web content articles on the right. You can see the template's I've included above to see how using the preferences to set a custom value allows us to change the way a web content template is displayed on the page. 

 

Enjoy using this new feature!

Travis Cory 2016-10-25T15:37:25Z
Categories: CMS, ECM

The Missing Maven Elements

Liferay - Wed, 10/19/2016 - 15:20

I mentioned last time in The State of Maven Development, that we were missing a few items for our Maven users.

Well I have good news...

You can now build themes in Maven!

In pom.xml:

<build> <plugins> <plugin> <artifactid>maven-dependency-plugin</artifactid> <executions> <execution> <phase>generate-resources</phase> <goals> <goal>copy-dependencies</goal> </goals> <configuration> <outputdirectory>${project.build.directory}/deps</outputdirectory> </configuration> </execution> </executions> </plugin> <plugin> <groupid>com.liferay</groupid> <artifactid>com.liferay.portal.tools.theme.builder</artifactid> <version>1.0.0</version> <executions> <execution> <phase>process-resources</phase> <goals> <goal>build-theme</goal> </goals> <configuration> <diffsdir>src/main/webapp/</diffsdir> <name>sample-maven-theme</name> <outputdir>${project.build.directory}/${project.build.finalName}</outputdir> <parentdir>${project.build.directory}/deps/com.liferay.frontend.theme.styled-2.0.13.jar</parentdir> <parentname>_styled</parentname> <templateextension>ftl</templateextension> <unstyleddir>${project.build.directory}/deps/com.liferay.frontend.theme.unstyled-2.0.13.jar</unstyleddir> </configuration> </execution> </executions> </plugin> <plugin> <groupid>com.liferay</groupid> <artifactid>com.liferay.css.builder</artifactid> <version>1.0.20</version> <executions> <execution> <id>default-build-css</id> <phase>generate-sources</phase> <goals> <goal>build-css</goal> </goals> </execution> </executions> <configuration> <portalcommonpath>/</portalcommonpath> <docrootdirname>src/main/resources</docrootdirname> </configuration> </plugin> </plugins> </build> <dependencies> <dependency> <groupid>com.liferay</groupid> <artifactid>com.liferay.frontend.theme.styled</artifactid> <version>2.0.13</version> </dependency> <dependency> <groupid>com.liferay</groupid> <artifactid>com.liferay.frontend.theme.unstyled</artifactid> <version>2.0.13</version> </dependency> </dependencies>

 

We’ve also published 18 maven archetypes with many more on the way!

 

  • com.liferay:com.liferay.project.templates.activator
  • com.liferay:com.liferay.project.templates.api
  • com.liferay:com.liferay.project.templates.content.targeting.report
  • com.liferay:com.liferay.project.templates.content.targeting.rule
  • com.liferay:com.liferay.project.templates.content.targeting.tracking.action
  • com.liferay:com.liferay.project.templates.control.menu.entry
  • com.liferay:com.liferay.project.templates.fragment
  • com.liferay:com.liferay.project.templates.mvc.portlet
  • com.liferay:com.liferay.project.templates.panel.app
  • com.liferay:com.liferay.project.templates.portlet
  • com.liferay:com.liferay.project.templates.portlet.configuration.icon
  • com.liferay:com.liferay.project.templates.portlet.provider
  • com.liferay:com.liferay.project.templates.portlet.toolbar.contributor
  • com.liferay:com.liferay.project.templates.service
  • com.liferay:com.liferay.project.templates.service.builder
  • com.liferay:com.liferay.project.templates.service.wrapper
  • com.liferay:com.liferay.project.templates.simulation.panel.entry
  • com.liferay:com.liferay.project.templates.template.context.contributor

 

Check back with us as we continue to work on documentation, more archetypes including one new one that should make starting your next project much easier.

David Truong 2016-10-19T20:20:54Z
Categories: CMS, ECM

Single Page Applications with SennaJS and Liferay Faces

Liferay - Mon, 10/17/2016 - 15:46
Single Page Applications with SennaJS and Liferay Faces

One of Liferay 7’s most exciting new features is SennaJS, a Single Page Application engine. SennaJS makes the portal more user-friendly in many ways. For example, when a link is clicked and navigation occurs, SennaJS requests the necessary portlet markup via XHR and renders it in the browser so that the whole portal page is not reloaded. As part of our latest release of Liferay Faces, we’ve added support for SennaJS so that JSF portlet developers can take advantage of this amazing feature.

How do I use SennaJS with Liferay Faces portlets?

Liferay Faces Bridge enables support for SennaJS in Liferay 7 by default. Utilizing SennaJS is as simple as using h:link, h:outputLink, or any other component which renders an <a> tag.1 If the link navigates to another page in your portal, SennaJS will automatically handle the navigation and cause an XHR GET which will load only the changed parts of the page. You can try this live in the Liferay Faces Showcase h:link example by clicking the To Param page > link. Clicking the link causes an XHR GET via SennaJS which is shown visually with a blue loading bar at the top of the page. You can compare this to a full-page GET by changing the end of the URL from param back to navigation and hitting enter. The full-page navigation is not only slower, but it causes more blinking and changes on the page.

Disabling SennaJS

Although SennaJS improves the user experience, it may be unnecessary for (or, rarely, incompatible with) certain use cases. If your portlet requires that SennaJS be disabled, then simply add <single-page-application>false</single-page-application> to the <portlet> section of your liferay-portlet.xml.

Under The Hood: How Liferay Faces Integrates with SennaJS

The Liferay Faces team worked closely with Bruno Basto, a Liferay front-end engineer, and Eduardo Lundgren, one of the creators of SennaJS, to ensure that Liferay 7’s usage of SennaJS would be compatible with Liferay Faces. Fortunately, SennaJS and Liferay 7 were already working together elegantly before we attempted to add Liferay Faces to the mix. Liferay 7 required only a few minor tweaks to allow Liferay Faces to take advantage of SennaJS. Bruno Basto implemented these changes. Thanks to him, SennaJS avoids loading duplicate resources in the <head> section by automatically tracking all CSS and JS resources and assuming that JS resources should not be unloaded.2,3

Since SennaJS is enabled by default in Liferay 7, the Liferay Faces team only needed to disable it for JSF components which were incompatible with its features. SennaJS assumes all links cause navigation, and it uses an XHR to obtain the markup that would otherwise be obtained via a full-page GET. Since JSF commandLink components are intended to submit a form rather than simply navigate to another page, they must not invoke SennaJS’s functionality. Likewise, SennaJS automatically causes forms to submit via XHR. However this feature is not compatible with JSF, since JSF expects to be in total control of form submissions.4 In order to avoid these incompatibilities, we used the data-senna-off attribute to disable SennaJS for all commandLinks and forms in Liferay 7.

With the minor changes listed above, Liferay Faces now allows JSF developers to enjoy the major benefits of SennaJS in Liferay 7!

  1. …with the exception of commandLink components. See the “Under the Hood” section for more details.
  2. Even though SennaJS tracks all the JavaScript files that are loaded, there is no way for it to generically unload all the components from those files, so it assumes it should not unload a JS element. This has the beneficial side effect of not reloading the same JS multiple times when a user navigates from one page to another and back again with SennaJS.
  3. All of these improvements benefit Liferay portlet developers who use JSP technology as well.
  4. Of course, JSF has its own functionality for submitting forms via ajax.
Kyle Joseph Stiemann 2016-10-17T20:46:57Z
Categories: CMS, ECM

So Now what?

Liferay - Mon, 10/17/2016 - 03:54

In my last Blog entry (First Day at the SPA) I showed how to create a simple Newsletter Signup Form with the JSON API and some AngularJs, and earned a relaxing day at the Spa, but now what?

I still want more, more relaxing days at the pool, good and healthy food, muscle relaxing shiatsu massages, and still be able the deliver robust solutions.

So how can you continue optimizing my workflow, and still have time to chill?

 

The short Answer

JSON Webservices and a little bit of HTML and Javascript, and XML(And Yes we are still on a Vanilla Liferay Installation)

 

The long Answer

To show my take on the long answer, I will continue optimizing the small example, of my last Blog entry. 

The example Task: Adapt and Optimize the Mailing List sign up (control), to reflect a more realistic Signup form.

So the basic appearance won't change, only the signup process in the background.

Lets first look at the current process

Step 1) You enter an email-address.
Step 2) Your email-address is saved.

  Now lets look at the "improved" process

Step 1) The user enters an email-address.
Step 2) An automatic email is send to the entered email-address
Step 3) The user has to activate the subscription, with the provided link in the email
Step 4) After activation (clicking the link), the email-address is marked as activated

Like this achiving a "double opt in" for the Mailing List, which can save you much problems, at least in austria.

So Lets begin

I'am still using the prior coded liferay-service-provider.js, which was updated since the last post. (It can be found on Github

Step 1: Create our “database”

Since I want Liferay's Workflowengine to take care of the backendprocess, but I don't want all the created webcontents to passthrough the same Mailinglist Workflow, the data entries will now be saved as Dynamic Data List Records. Here we can specify different workflows to individual List, and so being totally independent.    We create a new Data Definition, with the Fields Email and Activated. Later will not be shown it is only to hold the status of the entry.   And create a new Data Defenition List with the currently created Data Defenition. (Here we could already set the worklfow if it would be present)
  Step 2: Adapat the HTML/Javascript "CODING" Although there is no change in the frontend, there will be some minor changes in the HTML / Javascript portion, to help with the activation link and the minor changes in the liferay-service-provider API.   <div class="name-box"> <style type="text/css"> .name-box .span4.no-margin{margin:0;} </style> <div class="span4 no-margin" data-ng-app="newApp"> <div class="ng-view"></div> <script src="/angular/angular.min.js"></script> <script src="/angular/angular-route.min.js"></script> <script src="/angular-addon/liferay-service-provider.js"></script> <script> (function(app){ app.config(["liferayProvider","$routeProvider", function (liferayProvider,$routeProvider) { liferayProvider.setToken(btoa("newsletterservice:newsletterservice")); liferayProvider.addTypes({ name:"Newsletter", type: TYPES.DDLRECORD, groupId: 24913, recordSetId: 25719 }); $routeProvider .when("/", { template: "<form class='form-search' id='nameBox'>" + "<div class='input-append'>" + "<input class='search-query' data-ng-model='Email' placeholder='Enter Name ... ' type='text' />" + "<button class='btn' data-ng-click='saveName()' type='button'><i class='icon-envelope'>&nbsp;</i>Add Name</button>" + "</div></form>", controller: "baseController" }) .when("/success", { template: "<div class='alert alert-success'><b>Success!</b> Your name is now entered in our mailingslist!</div>" }) .when("/failure", { template: "<div class='alert alert-error '><b>Error!</b> Please try it again <a href='#/' > go back </a>!</div>" }) .when("/activate/:id",{ template: "<div class='alert alert-info'>{{message}}</div>", controller: "activationController" }).otherwise("/"); }]).controller("activationController", ["$scope", "$routeParams", "liferay", function($scope, $routeParams, liferay){ liferay["Newsletter"].update({Activated:true}, $routeParams.id).then(function(){ $scope.message = "Email activated!"; }).catch(function(){ $scope.message = "Error!"; }); }]).controller("baseController", ["$scope", "$location", "liferay", function($scope, $location, liferay){ $scope.saveName = function(){ liferay["Newsletter"].create({Email:$scope.Email}).then(function(){ $location.url("/success"); }).catch(function(){ $location.url("/failure"); }); }; }]); }(angular.module("newApp",["LiferayService", "ngRoute"]))); </script> </div> </div><div class="name-box"> <style type="text/css"> .name-box .span4.no-margin{margin:0;} </style> <div class="span4 no-margin" data-ng-app="newApp"> <div class="ng-view"></div> <script src="/angular/angular.min.js"></script> <script src="/angular/angular-route.min.js"></script> <script src="/angular-addon/liferay-service-provider.js"></script> <script> (function(app){ app.config(["liferayProvider","$routeProvider", function (liferayProvider,$routeProvider) { liferayProvider.setToken(btoa("newsletterservice:newsletterservice")); liferayProvider.addTypes({ name:"Newsletter", type: TYPES.DDLRECORD, groupId: 24913, recordSetId: 25719 }); $routeProvider .when("/", { template: "<form class='form-search' id='nameBox'>" + "<div class='input-append'>" + "<input class='search-query' data-ng-model='Email' placeholder='Enter Name ... ' type='text' />" + "<button class='btn' data-ng-click='saveName()' type='button'><i class='icon-envelope'>&nbsp;</i>Add Name</button>" + "</div></form>", controller: "baseController" }) .when("/success", { template: "<div class='alert alert-success'><b>Success!</b> Your name is now entered in our mailingslist!</div>" }) .when("/failure", { template: "<div class='alert alert-error '><b>Error!</b> Please try it again <a href='#/' > go back </a>!</div>" }) .when("/activate/:id",{ template: "<div class='alert alert-info'>{{message}}</div>", controller: "activationController" }).otherwise("/"); }]).controller("activationController", ["$scope", "$routeParams", "liferay", function($scope, $routeParams, liferay){ liferay["Newsletter"].update({Activated:true}, $routeParams.id).then(function(){ $scope.message = "Email activated!"; }).catch(function(){ $scope.message = "Error!"; }); }]).controller("baseController", ["$scope", "$location", "liferay", function($scope, $location, liferay){ $scope.saveName = function(){ liferay["Newsletter"].create({Email:$scope.Email}).then(function(){ $location.url("/success"); }).catch(function(){ $location.url("/failure"); }); }; }]); }(angular.module("newApp",["LiferayService", "ngRoute"]))); </script> </div> </div>   Due to the fact the the SPA has now to react to some urls, I added ngRoute Module and updated the Code to use the benefits of this module. (the activation link logic, could have been coded with 2-3 lines of vanilla Javascript code, but updating the code to benefite of ngRoute module makes it a real SPA, and it maintainable) Also since the liferay-service-provider.js should use DDRecord instead of WebContent, some minor changes in the config were needed, to set it up.   Step 3: Create a workflow XML file that reflects the needed/wanted process/flow

I must say the Liferay Workflowengine is pretty neat, and has some nice features. In my opinion it could improve documentation wise, but I cant be that bad, if I could wipout this xml in a few hours.

<?xml version="1.0"?> <workflow-definition xmlns="urn:liferay.com:liferay-workflow_6.2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:liferay.com:liferay-workflow_6.2.0 http://www.liferay.com/dtd/liferay-workflow-definition_6_2_0.xsd"> <name>Newsletter process</name> <description>Newsletter</description> <version>1</version> <state> <name>created</name> <metadata></metadata> <actions> <action> <name>start</name> <script><![CDATA[ var _log = Packages.com.liferay.portal.kernel.log.LogFactoryUtil.getLog("WORKFLOW"); _log.error("START"); var email = ""; var id = 1; try{ var idOffset = 3; id = workflowContext.get("entryClassPK") - idOffset; var record = Packages.com.liferay.portlet.dynamicdatalists.service.DDLRecordLocalServiceUtil.getRecord(id); email = record.getFieldValue("Email"); } catch(e) { _log.error(e); } Packages.com.liferay.util.mail.MailEngine.send( Packages.javax.mail.internet.InternetAddress("test@liferay.com"), Packages.javax.mail.internet.InternetAddress(email), "Activation Email", "<html><body><a href='http://liferay:8080/web/spa-tests/main-page#/activate/" + id + "'>activation</a></body></html>", true ); ]]> </script> <script-language>javascript</script-language> <execution-type>onEntry</execution-type> </action> </actions> <initial>true</initial> <transitions> <transition> <name>timer</name> <target>timer</target> </transition> </transitions> </state> <task> <name>timer</name> <assignments> <user/> </assignments> <task-timers> <task-timer> <name>Timer</name> <description>1</description> <delay> <duration>1</duration> <scale>minute</scale> </delay> <recurrence> <duration>1.0</duration> <scale>minute</scale> </recurrence> <timer-actions> <timer-action> <name>approve</name> <script> <![CDATA[ var idOffset = 3; var id = workflowContext.get("entryClassPK") - idOffset; var record = Packages.com.liferay.portlet.dynamicdatalists.service.DDLRecordLocalServiceUtil.getRecord(id); if(record.getFieldValue("Activated") == true){ var companyId = workflowContext.get("companyId"); var workflowInstanceLink = Packages.com.liferay.portal.service.WorkflowInstanceLinkLocalServiceUtil.getWorkflowInstanceLink( companyId, workflowContext.get("groupId"), workflowContext.get("entryClassName"), workflowContext.get("entryClassPK") ); var workflowInstanceId = workflowInstanceLink.getWorkflowInstanceId(); Packages.com.liferay.portal.kernel.workflow.WorkflowInstanceManagerUtil.signalWorkflowInstance( companyId, workflowContext.get("userId"), workflowInstanceId, "end", workflowContext ); } ]]> </script> <script-language>javascript</script-language> <priority>1</priority> </timer-action> </timer-actions> </task-timer> </task-timers> <transitions> <transition> <name>end</name> <target>end</target> <default>true</default> </transition> </transitions> </task> <state> <name>end</name> <actions> <action> <name>Approve</name> <description>Approve</description> <script> <![CDATA[ Packages.com.liferay.portal.kernel.workflow.WorkflowStatusManagerUtil.updateStatus( Packages.com.liferay.portal.kernel.workflow.WorkflowConstants.toStatus("approved"), workflowContext ); ]]> </script> <script-language>javascript</script-language> <execution-type>onEntry</execution-type> </action> </actions> </state> </workflow-definition>   Basically what this code does

1) On entering the intial State, an email is sent, with the activiation link
2) Now the Engine is checking periodically (1 minute interval), if the email-address was activated, through the link
3) If the entry was activated, the workflow will terminate. (this is a great place to an some code, that sends a welcome email, or so)

Now, just upload the workflow

and finally add it to the prior created DDL List

Last Step

Lean back and enjoy the time you have saved, and will save in the future, if you take advantage of the rich toolset that Liferay provides out-of-the-box.

Please share any Ideas, possible Improvements, Comments, Feedback and/or nice Spa locations in/near austria. I would like to hear from you.

 

Obviously there are still some improvements, since it is merely a proof of concept, but this I leave for you to explore. Charles Lam 2016-10-17T08:54:25Z
Categories: CMS, ECM

New themes Blog entry (4/4) - 1975 London theme

Liferay - Thu, 10/13/2016 - 04:08

Hi everyone!

 

In our final entry of the series, we are happy to introduce a theme that you can use to create your own subscription site. Welcome to 1975 London!.

 

 

"A music site as example"

 

The new 1975 London theme includes an awesome site template with news and an album player to sit back and relax while listening to small fragments of your favourite songs.

 

This theme includes some new exciting features such as:

 

  • Different ways to show the content preview: you can choose from two different cards and two layouts to show album previews display and combine it with different text positions to get modern and awesome layouts.

 

 

  • Two layouts to show album previews.

 

 

  • Modern layout to display news

  • Easy way to put your header over your content through a setting.

 

Soon in the marketplace

 

The 1975 London theme will soon be published in the marketplace.

  Marcos Castro 2016-10-13T09:08:27Z
Categories: CMS, ECM

Securing Liferay Chapter 5: An easily missed HTTPS caveat

Liferay - Wed, 10/12/2016 - 10:20

You probably know the basic installation instructions for Liferay Bundles: „unzip and run startup.sh“ - with this you get to a working Liferay installation in a minute. It will run with all defaults - which might not be what you want in production.

This is part 5 of a series. All the chapters are linked at the bottom of this article - I recommend to start with chapter 1. This chapter is an extension to chapter 3 - an aspect that you'll not run into when you just follow my recommendation. But if you ignore those recommendations, there's something you might want to know. Just as before, please note that I'm using Apache httpd as the webserver of my choice. If you are more familiar with any of the others: Feel free to search/replace the name. It's the principle that I'd like to discuss, not the name or origin of the webserver.

Tomcat (or: my appserver) speaks https too...

Yes it does. Still, in chapter 3 I'm recommending to configure https through Apache httpd. Why?

  • because I'm used to it and I have it anyway just to get access to mod_rewrite
  • because it supports a lot of different configurations and has a lot of documentation (and implementations) readily available (Let'sEncrypt anyone?)
  • because... security...
What's more secure when Apache httpd handles encryption?

Httpd, being a separate process, typically runs as a different user than tomcat. Further more, it's started as root and drops those privileges once configured. And here's the main reason: You can store your private server key (the crown jewels of a web server) to be readable only to root. And when you're running on a separate server, you even have that level of protection between your Appserver and application (Liferay).

If your appserver handled encryption, it would need access to your private key as well. Granted, the private key in a Java keystore is typically password protected. The only problem is that the password also needs to be read by the user that tomcat is running as. So basically you can't really store the private key (that never must escape your control) in a way that the appserver can't read it.

Any security hole in either appserver or application might expose your filesystem to the outside - and it'd be a very bad idea to leak your private key. You might not even know when it escapes - I find it's a lot harder to properly protect the private key when you only have the appserver doing everything from serving application data to handling connection encryption.

And did I mention that just mod_rewrite alone has saved me hours for quickly fixing issues in production? If you've not yet considered an extra Apache httpd: let this chapter be the trigger for changing your setup. And if you did: Congratulations. Great choice.

Olaf Kock 2016-10-12T15:20:25Z
Categories: CMS, ECM

Docker image: Liferay DXP - Wildfly (JBoss)

Liferay - Mon, 10/10/2016 - 18:59

I recently played with Docker, CloudFoundry and decided to spend some time creating a docker image for Liferay DXP on Wildfly (i.e JBoss). I'm sharing my experience on this blog entry.

Run Docker image

Assuming you already have docker installed on your machine, you can easily run the docker image by executing the following command:

docker run -it -p 8080:8080 dorgendubal/liferay-dxp-wildfly

(The image is 2GB large and it will take some time to download it)

The startup takes about 2 minutes. You can then access your liferay:

http://localhost:8080/

Enjoy!

  Pre-configured Docker image for Québec/Canada

I also created a pre-configured image for demo'ing purposes in Québec/Canada.

  • default locales are fr_CA/en_CA
  • wizard, terms of use and reminder question are skipped
  • administrator is preconfigured

docker run -it -p 8080:8080 dorgendubal/liferay-dxp-wildfly-quebec

  Contribution / Source code

Interested in contributing or in looking at the source code ?
https://github.com/SvenWerlen/docker-liferay-dxp-wildfly

Sven Werlen 2016-10-10T23:59:09Z
Categories: CMS, ECM

Portlet Filters in Liferay 7

Liferay - Tue, 10/04/2016 - 02:26

Liferay 7 comes with a lot of feature and each feature is worth to learn.  Portlet Filter is one of them. However, it is possible to write portlet filter in earlier versions but in Liferay 7 it has certain benefits.

As compared to earlier version Portlet Filter can be deployed as service in OSGI. There is no need to write filter in the same portlet plugin that is the ultimate benefit of Liferay 7 modular architecture, it gives us the possibility to apply filter on OOTB portlets with ease. This is beneficial where It can be applied and removed independently from the portlet without redeploying the portlet as the contracts between osgi services are loosely coupled.

 

In earlier version It is hard to apply filters on OOTB portlets. I think, it is not possible without EXT plugin. Taking advantage of OSGI in Liferay 7, Portlet Filter on OOTB portlet can be applied as a service and can be removed without need of the Portal downtime.

 

There can be following Portlet Filters.

a) Render Filter: -Render Filter mainly implements the doFilter(RenderRequest request, RenderResponse response, FilterChain chain) method of the the javax.portlet.filter.RenderFilter class.

b) Resource Filter: - Resource Filter implements the doFilter(ResourceRequest request, ResourceResponse response, FilterChain chain) method of the the javax.portlet.filter.ResourceFilter class.

c) Action Filter: - Action Filter mainly implements the doFilter(ActionRequest request, ActionResponse response, FilterChain chain) method of the the javax.portlet.filter.ActionFilter class.

d) Event Filter: - Event Filter mainly implements the doFilter(EventRequest request, EventResponse response, FilterChain chain) method of the the javax.portlet.filter.EventFilter class.

 

To define PortletFilter first step there is need to declare following properties that will identify this service as PortletFilter and on which portlet service it is applied. Suppose we want to apply filter on Login Portlet. It can be done just by writing an independent module that has following component class.

@Component(

                immediate = true,

                property = {

        "javax.portlet.name= com_liferay_login_web_portlet_LoginPortlet"

    },

    service = PortletFilter.class

)

public class MyLoginPortletFilter implements RenderFilter {

 

                @Override

                public void destroy() {

                }

 

                @Override

                public void doFilter(

                                                RenderRequest request, RenderResponse response, FilterChain chain)

                                throws IOException, PortletException {

 

                                System.out.println("Before filter action");

                                chain.doFilter(request, response);

                                System.out.println("After filter action");

                }

 

                @Override

                public void init(FilterConfig filterConfig) throws PortletException {

                }

}

 

Sushil Patidar 2016-10-04T07:26:23Z
Categories: CMS, ECM

First Day at the SPA

Liferay - Fri, 09/30/2016 - 10:15

To develop for Liferay is no simple Task. There are many things you have to keep in mind, if you want to create and develop professional Applications, and you also have to learn many different technologies, if you aren't yet an savvy Java-Developer. So what are the alternatives, searching the web and marketplace portlets, or ...? Here is a different approach, that uses vanilla Liferay and HTML / Javascript and very little time.

 

So how can you create applications, and still have time for a day at the SPA? The short Answer

JSON Webservices and a little bit of HTML and Javascript.
 

The long Answer

To show my take on the long answer, I made up this small example. But bare in mind, that any form with many amount and logic is create able.
 

The example Task: Create a Mailing List sign up (control)

  Step 1: Create our “database”
  1. First we need to create a web-folder, lets call it “Mailing List”.
    Here all the “data entries”, will be stored

     
  2. Next we create our “datastructure” as an Journal / WebContent Structure (the Liferay naming is a bit confusing, to an Liferay beginner like me), adding all field we need.

    For our example the field Email should be enough.

  3. Now create a template for the newly created structure

     

Step 2: Now comes the best part "CODING"

Write some Javascript and HTML Code, that uses the jsons webservices to create new Articles (this is the important part), these entries are our “data entries”. For our example I used angular and created all the liferay specific functions in an separate module, so that I can reuse. (It is not yet production grade code, but nice for a demo)

Here, the HTML / Javascript code that should be added into the WebContent:

<div class="newletter-box"> <style type="text/css">.newletter-box .span4.no-margin{margin:0;}</style> <div class="span4 no-margin"> <div id="newsletterAlertBox" class="alert alert-success hide"> <b>Success!</b> Your email address is now entered in our mailingslist! </div> <form id="newsletterBox" class="form-search"> <div class="input-append" data-ng-app="newsApp" data-ng-controller="baseController"> <input class="search-query" data-ng-model="Email" placeholder="Enter Email ... " type="text" /> <button class="btn" data-ng-click="saveEmailAddress()" type="button"> <i class="icon-envelope">&nbsp;</i>Add to List </button> </div> </form> <script src="/angular/angular.min.js"></script> <script src="/angular-addon/liferay-service-provider.js"></script> <script> (function(app){ app.config(["liferayProvider", function (liferayProvider) { liferayProvider.setToken(btoa("newsletterservice:newsletterservice")); liferayProvider.addTypes({ name:"Newsletter", groupId: 24913, folderId: 24927, structureId: 24930, templateId: 24932 }); }]).controller("baseController", ["$scope", "liferay", function($scope, liferay){ $scope.saveEmailAddress = function(){ liferay["Newsletter"].create({Email:$scope.Email}).success(function(){ document.querySelectorAll("#newsletterAlertBox").className = "alert alert-success"; document.querySelectorAll("#newsletterBox").className += " hide"; }).error(function(){ console.warn(arguments); }) }; }]); }(angular.module("newsApp",["LiferayService"]))); </script> </div> </div>

I posted the code for the angular module, on github: https://github.com/akumagamo/javascript-liferay-angular-service, if you wanted to use it or try it out yourself.

Last Step:

Go to the SPA and enjoy the time you have saved. (Since a form like this can be build in 30 mins, and about 60-90 mins without the liferay-service-provider.js ) No Java, no Jenkins, no Maven, no ....  just Vanilla Liferay and some basic HTML and Javascript.

I'm eager to hear from other people and experiences. Please share any Ideas, Comments and Feedback you might have.

Charles Lam 2016-09-30T15:15:25Z
Categories: CMS, ECM
Syndicate content