0

I want to create a composite that serves as an enhancement of <p:calendar>. This is what I have:

calendar.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:composite="http://java.sun.com/jsf/composite">

  <composite:interface>
    <composite:attribute name="bean" />
  </composite:interface>

  <composite:implementation>
    <h:form id="calendarForm" class="calendarForm" >
      <h:outputScript library="js" name="calendar.js" />
      <p:inputText type="hidden" id="dateInfoMap" class="dateInfoMap" value="#{cc.attrs.bean.dateInfoMap}"/>

        <p:calendar
          id="calendar"
          value="#{cc.attrs.bean.selectedDate}"
          mode="inline" >
          <p:ajax event="viewChange" listener="#{cc.attrs.bean.makeDateInfoMapForMonth}" update="dateInfoMap" oncomplete="alterCalendar(this)" />
        </p:calendar>

    </h:form>
  </composite:implementation>
</html>

The function cc.attrs.bean.makeDateInfoMapForMonth creates an object that contains information about each day of the current month (as obtainable from the viewChange event) and stores it in cc.attrs.bean.dateInfoMap.

calendar.js

function alterCalendar(ajaxRequest) {
  let calendar = document.getElementById(ajaxRequest.source);
  let dateInfoMap = getDateInfoMap(calendar);
  // do stuff with this calendar and dateInfoMap
}

function getDateInfoMap(calendar) {
  return JSON.parse($(calendar).closest(".calendarForm").find(".dateInfoMap")[0].value);
}

All of this works as it should: When I change the month, the new month gets displayed with all the changes made in alterCalendar() using the correct information from dateInfoMap.

However, this is only done after I changed the month for the first time. <p:calendar> does not seem to fire an event after it has been rendered the first time. How can I have alterCalendar() be applied at this moment?

Two possible solutions both seem inappropriate for a component like this:

  1. I could tell the user of the component to call a javascript function as an oncomplete-callback if there is an ajax that triggers the calendar to be rendered. This is a) not nice for the user, b) not always applicable, and c) it would be hard to obtain the calendar instance dynamically, without having to manually type the components id.

  2. I could use an expression like $(document).ready(), but if the calendar's rendering is indeed triggered by another ajax, this will probably not work, and 1 c) also applies here.

Is there another, more elegant way where the calendar's instance or ID can be obtained dynamically?

Edit: I tried using the beforeShow attribute of <p:calendar>, but it does not do anything. I am using Primefaces 6.2

Kukeltje
  • 12,223
  • 4
  • 24
  • 47
maddingl
  • 197
  • 4
  • 20
  • You know you can put javascript directly in the xhtml? No need for an inputHidden and you can also call the initial alterCalendar by having a call to it after the `p:calendar` in javascript? – Kukeltje Aug 15 '19 at 13:20
  • The inputHidden is used to make the bean variable `dateInfoMap` available in Javascript. What would be the alternative to that? – maddingl Aug 16 '19 at 06:35
  • And how do I get the calendar id or instance dynamically if I put javascript directly in the xhtml? `[...][...]` just gives me the `window` object – maddingl Aug 16 '19 at 06:38
  • inputhidden, see : https://stackoverflow.com/questions/2547814/mixing-jsf-el-in-a-javascript-file And you can have any dynamic variable there to be unique – Kukeltje Aug 16 '19 at 07:00
  • Regarding the calendar, you can assign a widgetvar dynamically and use that to access it – Kukeltje Aug 16 '19 at 07:04
  • inputHidden: Nice, I had only seen these `#{...}` isolatedly and didn't know you can use them anywhere in javascript on the xhtml-page. I am going to add the map content as a parameter to `alterCalendar()` – maddingl Aug 16 '19 at 07:33
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/198027/discussion-between-maddingl-and-kukeltje). – maddingl Aug 16 '19 at 07:33

1 Answers1

2

With the help of kukeltje, I could solve the problem and eliminate other issues as well.

1) The key was #{cc.clientId}. This is available in the composite namespace and gives me the id of the component as needed for Javascript.

2) A form should not be part of a composite. See here for more info.

3) I can use #{...} anywhere in Javascript on the xhmtl-page.

4) I don't need a hidden input field, I can use RequestContext to pass the value to the Javascript function. If I used #{cc.attrs.bean.dateInfoMap}, the value wouldn't always be up to date, since it is rendered with the <p:ajax>. See here for more info.

This leads me to this solution:

calendar.xhtml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:composite="http://java.sun.com/jsf/composite">

  <composite:interface>
    <composite:attribute name="bean" />
  </composite:interface>

  <composite:implementation>
    <h:outputScript library="js" name="calendar.js" />
    <p:calendar
      id="calendar"
      value="#{cc.attrs.bean.selectedDate}"
      mode="inline" >
    <p:ajax
      event="viewChange"
      listener="#{cc.attrs.bean.makeDateInfoMapForMonth}"
      oncomplete="alterCalendar('#{cc.clientId}', args.dateInfoMap)" />
    </p:calendar>
    <script>
      alterCalendar('#{cc.clientId}', '#{cc.attrs.bean.dateInfoMap}');
    </script>
  </composite:implementation>
</html>

in the bean:

public void makeDateInfoMapForMonth(DateViewChangeEvent event) {
    dateInfoMap = //...
    RequestContext.getCurrentInstance().addCallbackParam("dateInfoMap", dateInfoMap);
  }

calendar.js

function alterCalendar(componentClientId, dateInfoMap) {
  let calendar = document.getElementById(componentClientId + ":calendar");
  dateInfoMap = JSON.parse(dateInfoMap);
  $(calendar).ready(function () {
    // do stuff
  });
  });
}

maddingl
  • 197
  • 4
  • 20