Uploaded image for project: 'MyFaces Core'
  1. MyFaces Core
  2. MYFACES-3733

Implement vdl.createComponent(...)

    XMLWordPrintableJSON

Details

    • Task
    • Status: Closed
    • Major
    • Resolution: Fixed
    • None
    • 2.2.0-beta
    • JSR-344
    • None

    Description

      This is a difficult issue to do in JSF 2.2 . I have spent a long time to solve this one, and given the complexity involved and since there is no documentation anywhere about how this should work, I'll let the required explanation here.

      The idea is allow to include generated vdl fragments into pages programatically. This includes normal components, composite components or just fragments of markup. The method signature is this:

      public UIComponent createComponent(FacesContext context, String taglibURI, String tagName, Map<String,Object> attributes)

      Some valid examples of this are:

      // Normal component
      UIComponent component = vdl.createComponent(facesContext,
      "http://java.sun.com/jsf/html",
      "outputText", attributes);

      // Composite component
      UIComponent component = vdl.createComponent(facesContext,
      "http://java.sun.com/jsf/composite/testComposite",
      "dynComp_1", attributes);

      // Dynamic include
      Map<String, Object> attributes = new HashMap<String, Object>();
      attributes.put("src", "/addSimpleIncludeVDL_1_1.xhtml");
      UIComponent component = vdl.createComponent(facesContext,
      "http://java.sun.com/jsf/facelets",
      "include", attributes);

      The javadoc does not suggest the dynamic include is valid, but I think users expect these kind of stuff work.

      Theoretically it sounds like something easy to do, but unfortunately it is not. The reasons why this is so are:

      • Facelets algorithm wraps html markup into UILeaf instances, which is a special "transient" component. UILeaf instances are never saved or restored from the component tree, but in some points of the algorithm (restore view and before render response when vdl.buildView() is called) the component tree is updated, adding or removing UILeaf instances.
      • Facelets has an algorithm that require id generation to be stable, otherwise a duplicate id exception may arise. A lot of effort has been done to organize this part, and the current solution works very well. But in this case, we need to generate unique ids that can be refreshed somehow.
      • Facelets algorithm has an special logic to deal with dynamic sections like the ones generated by c:if or
        ui:include src="# {...}

        " . Add facelets sections programatically could make this algorithm fail, removing sections that should be.

      • Facelets PSS algorithm needs to be taken into account too. The listener that is used to register programmatic changes on the tree in DefaultFaceletsStateManagementStrategy uses ComponentSupport.MARK_CREATED to identify which component belongs to the component tree and which one was added by outside. Add facelets sections programatically could make this algorithm fail, because it could assume some sections of the tree does not need to be saved fully, even if that's not true.

      The issue in the spec is this:

      https://java.net/jira/browse/JAVASERVERFACES_SPEC_PUBLIC-611

      At start the idea was to export FaceletFactory directly, but I told to the EG that it was a bad idea by multiple reasons (That's a Pandora's Box). See:

      https://java.net/projects/javaserverfaces-spec-public/lists/jsr344-experts/archive/2012-11/message/91

      This previous message is useful too:

      https://java.net/projects/javaserverfaces-spec-public/lists/jsr344-experts/archive/2012-06/message/18

      After thinking and trying different strategies to overcome this issue, I finally found the following solution:

      • Use the compiler for generate a custom Facelet "inline" or "on the fly". It is not necessary to create an
        xml document and then parse it, just generate the Tag class and pass it to the compiler to generate an
        Abstract Syntax Tree (AST), with the hierarchy of facelet TagHandler instances.
      • To solve the issue with UILeaf instances, the best is create a stateful ComponentSystemEventListener that on restore view phase it compiles the custom Facelet and apply it over the fragment. The ideal and only event to attach the listener is PostRestoreStateEvent, but we need to add the code in UIComponent.processEvent().
      • In the case of a ui:include, if multiple components are returned, all of them are grouped into a single
        UIPanel. If the code returns one component, it returns that component.
      • If the code generates a branch, or in other words, multiple nested components, it should attach the
        listener to deal with UILeaf instances, if it just generates one component do not do that because it is
        not necessary.
      • To solve the issue with the ids, just call UIViewRoot.createUniqueId() and use the generated value to derive unique facelets ids. The new algorithm that generate ids is very flexible and it will support this case. This base key should be saved in the state so the same ids are generated for the same fragment.
      • Support composite components needs special treatment. The idea is support something like this:

      UIComponent component = vdl.createComponent(facesContext,
      "http://java.sun.com/jsf/composite/testComposite",
      "dynComp_1", attributes);

      // .... add children / facets to the algorithm

      someComponentInTheView.getChildren().add(component);

      In this case the "processing" of the composite component content must be done only when the component is added to the view. The idea is vdl.createComponent only create the root component class, and then use a listener attached to PostAddToViewEvent to process the content. We need to modify the algorithm, because in this case children/facets are created programatically and not using facelets algorithm. The idea is add an extra facelet in the compiler to detect when the result is a composite component. The listener attached to PostAddToViewEvent must be done in a way that only works on the first addition to PostAddToViewEvent.

      • If the call to vdl.createComponent() occur when there is an active FaceletCompositionContext instance, reuse that instance doing the necessary changes in the context, otherwise instantiate a clean context.
      • Facelets PSS algorithm will work just fine as long as the returned component does not have ComponentSupport.MARK_CREATED set when the view is refreshed, saved or restored. It is enough to just use the component attribute map.
      • The two base cases to test are:
      • Create components programatically inside a "binding" method.
      • Create components programatically in PreRenderViewEvent or in the Renderer.
        The difference is the "binding" occur when there is a FaceletCompositionContext instance active, but in the other cases there is no active instance.

      Comply with all previous requirements can be difficult, but it is very important, otherwise the algorithm will not be stable enough.

      Attachments

        Issue Links

          Activity

            People

              lu4242 Leonardo Uribe
              lu4242 Leonardo Uribe
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: