Message Boards

Using bootstrap features in Liferay 7.3 - scrollspy

thumbnail
Krzysztof Gołębiowski, modified 3 Years ago.

Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Master Posts: 549 Join Date: 6/25/11 Recent Posts
Hi everyone,

I'm developing an interface in Liferay 7.3, which will show a side menu fragment that's going to be updated as the user scrolls the page. There is no dedicated scrollspy component in Clay so I'm trying to use the one from Bootstrap and the first step is to enable jQuery to make bootstrap JS work. That was easy emoticon

There is a second problem though, which seems to be more difficult and is about how Bootstrap scrollspy works in SPA environment. It seems that when you enter the page directly, scrollspy immediately applies "active" class to appropriate element and continues working as you scroll the page. However, when the user navigates to the page through Liferay (utilizing SPA), bootstrap JS code does not seem to be triggered at all and the menu does not work.

Any ideas on how to deal with such cases?

Thanks in advance,

KG
thumbnail
Christoph Rabel, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Legend Posts: 1554 Join Date: 9/24/09 Recent Posts
I can just guess here, maybe this works:
Liferay.on('endNavigate', function(event) {
   $('body').scrollspy('refresh');
});


https://help.liferay.com/hc/en-us/articles/360017886192-Automatic-Single-Page-Applications
thumbnail
Krzysztof Gołębiowski, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Master Posts: 549 Join Date: 6/25/11 Recent Posts
Ehh, that was not so easy thing in general, mainly because the plan was to keep it in a fragment, and not load with a theme. 

The final solution was to declare the function:

function refreshScrollspy() {
            $('[data-spy="scroll"]').each(function () {
                $(this).scrollspy('refresh');
            });
        }


which was then called after the navigation with the event listener:

Liferay.on("endNavigate", function(event) {
                refreshScrollspy();
                ...
            });


but also when the fragment is loaded for the first time (just refreshScrollspy()).

It is generally a pain to do any "global" actions in Liferay SPA environment. Attaching and then detaching (or deactivating) global EventListeners or managing Liferay events is quite complicated and you have to handle all the entry/exit points to your JS code. Especially if it is located within a fragment, so it should be registered once the user sees the fragment for the first and then cleaned up once he leaves the page in order to avoid leaving behind any unusable event listeners that relate with DOM object which are already gone.

It would be great to have some JS context or variable that is related only with the current page (during SPA session), and disappears once the user navigates away.
thumbnail
Christoph Rabel, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Legend Posts: 1554 Join Date: 9/24/09 Recent Posts
I actually had no problems anymore since the early days of Liferay 7.0.
But we have more or less "banned" jQuery from our projects and usually use webpack + vue.js + dynamic imports nowadays. I was always expecting that something won't work but for some reason it works pretty well.
https://github.com/dccs-liferay/webpack-example/blob/master/modules/dccs-frontend-demo/webpack/js/app.js
But I remember that scrollspy issue too. In the early days of 7.0 a colleague added it to a website and had similar problems.
thumbnail
Krzysztof Gołębiowski, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Master Posts: 549 Join Date: 6/25/11 Recent Posts
Sure, there are no issues when developing custom portlets or modules where all the JS development stack is available. The problem begins if you want to go "full Liferay" where you develop just a light theme, a couple of fragments and ADTs or Web Content Templates for dynamic content (all using standard Clay or Bootstrap components), and allow the editors to freely build everything in the way they want.

I'll have a look into your dccs-frontend-demo, maybe I'll get some inspiration of how to develop it in better way emoticon
thumbnail
Krzysztof Gołębiowski, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Master Posts: 549 Join Date: 6/25/11 Recent Posts
And the other issue is handling all the "fancy fragments with JS" in Fragment edit mode, which in my current case means that the sticky menu should remain in its original place to allow the editor to reorganize and update the fragments.
thumbnail
Christoph Rabel, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Legend Posts: 1554 Join Date: 9/24/09 Recent Posts
Check the Tabs fragment "example". It contains js code to check, if the page is in edit mode.

I couldn't find anything in freemarker and will probably open a feature request in the next couple of days to add a "getMode()" function to fragments (freemarker and js).
thumbnail
Christoph Rabel, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Legend Posts: 1554 Join Date: 9/24/09 Recent Posts
I have created a feature request.
https://issues.liferay.com/browse/LPS-115828
Currently you can check for edit mode in javascript using:
document.body.classList.contains('has-edit-mode-menu');
thumbnail
Krzysztof Gołębiowski, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Master Posts: 549 Join Date: 6/25/11 Recent Posts
Yes, I'm already using this "has-edit-mode-menu" class but it's definitely not nice and doesn't work server-side. I upvoted your JIRA task emoticon
thumbnail
Krzysztof Gołębiowski, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Liferay Master Posts: 549 Join Date: 6/25/11 Recent Posts
I kept working with fragments for a few days and widened a little bit the scope of things missing in Liferay Javascript API. I described everything in a new Feature Request - https://issues.liferay.com/browse/LPS-115990. I know you have quite a lot of experience in the matter so feel free to add or correct anything.
thumbnail
Dave Kliczbor, modified 3 Years ago.

RE: Using bootstrap features in Liferay 7.3 - scrollspy

Junior Member Posts: 77 Join Date: 7/12/11 Recent Posts
I think I found a server-side method to detect inside a fragment template whether it is in Edit Mode or View Mode.
It is based on the observation that the URL contains the parameter p_l_mode=edit if the content page is currently being edited. So it basically comes down to get the request from themeDisplay, and then the parameter "p_l_mode" from the request, then checking whether that parameter has the value "edit".Wrapped into a TemplateContextContributor module, it becomes two Component classes:
package my.template.context.contributor;

import com.liferay.portal.kernel.template.TemplateContextContributor;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import my.template.objects.PortletHelper;

@Component(
    immediate = true,
    property = "type=" + TemplateContextContributor.TYPE_GLOBAL,
    service = TemplateContextContributor.class
)
public class MyDisplayTemplateContextContributor
	implements TemplateContextContributor {  
  @Override
  public void prepare(Map<string, object> contextObjects, HttpServletRequest httpServletRequest) {
    contextObjects.put("templateHelper", templateHelper);
  }
  @Reference(unbind="-")
  TemplateHelper templateHelper;
}
</string,>
and
package my.template.objects;

import com.liferay.portal.kernel.theme.ThemeDisplay;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;

@Component(
    immediate = true,
    service = TemplateHelper.class)
public class TemplateHelper {  
  public boolean isEditing(ThemeDisplay themeDisplay) {
    return "edit".equals(themeDisplay.getRequest().getParameter("p_l_mode"));
  }
}
The syntax inside the fragment template then becomes:
[#if templateHelper.isEditing(themeDisplay) ]
    <p>This fragment is disabled during editing</p>
[#else]
    <p>This fragment does something in VIEW mode.</p>
[/#if]
It would work the same way in all FTL templates (not only fragment templates), because we use TYPE_GLOBAL and themeDisplay is already present in all templates.The reason why I would put this into a template context contributor (instead of just using themeDisplay.getRequest().getParameter("p_l_mode") == "edit" inside the FTL code) is that at some point you'll almost certainly need to check the permissions of the user – because any user could theoretically just add "&p_l_mode=edit" to the browser URL, prompting your fragment to possibly show both parts. Such checks would need to go into the isEditing method.
(The issue stemming from the discussion on this page has already been updated with the same text.)