p:steps but enable click on all steps
Asked Answered
D

3

8

I have primefaces steps using tag <p:steps> like below :

<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
   <p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel"/>
   <p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel"/>
   <p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel"/>
   <p:menuitem value="step 4"/>
</p:steps>

And the result is like this :

enter image description here

I can click step 1 but not step 3 and 4. How can I enable click for all steps?

Douceur answered 11/8, 2017 at 4:25 Comment(4)
What are you trying to implement using p:steps?Hysterogenic
to let the user know what step he/she must do to complete the tutorial, but the user can click the step to the next/prev step instead of clicking the link from main menuDouceur
Sounds like functionally you want p:tabView instead. You probably chose p:steps for cosmetic reasons, which is ill-advised.Housing
Could you please share also you css? I am interested in how did you manage to make p:steps appearance exaclty the way you showed it. Thank you!Wicklow
A
8

Wow, that's a nice question!

I've tried many things with the current API to accomplish it, but seems like it's not possible with our current options.

To solve this I wrote a custom renderer for the Steps component:

Most of the code below is the same from the PrimeFaces's GitHub. I just changed a few things to solve this specific problem.

import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import org.primefaces.component.api.AjaxSource;
import org.primefaces.component.api.UIOutcomeTarget;
import org.primefaces.component.steps.Steps;
import org.primefaces.component.steps.StepsRenderer;
import org.primefaces.model.menu.MenuItem;
import org.primefaces.util.ComponentTraversalUtils;

public class CustomStepsRenderer extends StepsRenderer {

@Override
protected void encodeItem(FacesContext context, Steps steps, MenuItem item, int activeIndex, int index) throws IOException {
    ResponseWriter writer = context.getResponseWriter();
    String itemClass;

    if (steps.isReadonly()) {
        itemClass = (index == activeIndex) ? Steps.ACTIVE_ITEM_CLASS : Steps.INACTIVE_ITEM_CLASS;
    } else {
        if (index == activeIndex) {
            itemClass = Steps.ACTIVE_ITEM_CLASS;
        }
        else {
            itemClass = Steps.VISITED_ITEM_CLASS;
        }
    }

    String containerStyle = item.getContainerStyle();
    String containerStyleClass = item.getContainerStyleClass();

    if (containerStyleClass != null) {
        itemClass = itemClass + " " + containerStyleClass;
    }

    //header container
    writer.startElement("li", null);
    writer.writeAttribute("class", itemClass, null);
    writer.writeAttribute("role", "tab", null);
    if (containerStyle != null) {
        writer.writeAttribute("style", containerStyle, null);
    }

    encodeMenuItem(context, steps, item, activeIndex, index);

    writer.endElement("li");
}

@Override
protected void encodeMenuItem(FacesContext context, Steps steps, MenuItem menuitem, int activeIndex, int index) throws IOException {        
    ResponseWriter writer = context.getResponseWriter();
    String title = menuitem.getTitle();
    String style = menuitem.getStyle();
    String styleClass = this.getLinkStyleClass(menuitem);

    writer.startElement("a", null);
    writer.writeAttribute("tabindex", "-1", null);
    if (shouldRenderId(menuitem)) {
        writer.writeAttribute("id", menuitem.getClientId(), null);
    }
    if (title != null) {
        writer.writeAttribute("title", title, null);
    }

    writer.writeAttribute("class", styleClass, null);

    if (style != null) {
        writer.writeAttribute("style", style, null);
    }

    if (steps.isReadonly() || menuitem.isDisabled()) {
        writer.writeAttribute("href", "#", null);
        writer.writeAttribute("onclick", "return false;", null);
    } else {
        String onclick = menuitem.getOnclick();

        //GET
        if (menuitem.getUrl() != null || menuitem.getOutcome() != null) {
            String targetURL = getTargetURL(context, (UIOutcomeTarget) menuitem);
            writer.writeAttribute("href", targetURL, null);

            if (menuitem.getTarget() != null) {
                writer.writeAttribute("target", menuitem.getTarget(), null);
            }
        } //POST
        else {
            writer.writeAttribute("href", "#", null);

            UIComponent form = ComponentTraversalUtils.closestForm(context, steps);
            if (form == null) {
                throw new FacesException("MenuItem must be inside a form element");
            }

            String command;
            if (menuitem.isDynamic()) {
                String menuClientId = steps.getClientId(context);
                Map<String, List<String>> params = menuitem.getParams();
                if (params == null) {
                    params = new LinkedHashMap<String, List<String>>();
                }
                List<String> idParams = new ArrayList<String>();
                idParams.add(menuitem.getId());
                params.put(menuClientId + "_menuid", idParams);

                command = menuitem.isAjax()
                        ? buildAjaxRequest(context, steps, (AjaxSource) menuitem, form, params)
                        : buildNonAjaxRequest(context, steps, form, menuClientId, params, true);
            } else {
                command = menuitem.isAjax()
                        ? buildAjaxRequest(context, (AjaxSource) menuitem, form)
                        : buildNonAjaxRequest(context, ((UIComponent) menuitem), form, ((UIComponent) menuitem).getClientId(context), true);
            }

            onclick = (onclick == null) ? command : onclick + ";" + command;
        }

        if (onclick != null) {
            writer.writeAttribute("onclick", onclick, null);
        }
    }

    writer.startElement("span", steps);
    writer.writeAttribute("class", Steps.STEP_NUMBER_CLASS, null);
    writer.writeText((index + 1), null);
    writer.endElement("span");

    Object value = menuitem.getValue();
    if (value != null) {
        writer.startElement("span", steps);
        writer.writeAttribute("class", Steps.STEP_TITLE_CLASS, null);
        writer.writeText(value, null);
        writer.endElement("span");
    }

    writer.endElement("a");
}

Then, register this new renderer in your faces-config.xml file:

   <render-kit>
        <renderer>
            <component-family>org.primefaces.component</component-family>
            <renderer-type>org.primefaces.component.StepsRenderer</renderer-type>
            <renderer-class>YOUR_PACKAGE.CustomStepsRenderer</renderer-class>
        </renderer>
    </render-kit>

Don't forget to change YOUR_PACKAGE to your CustomStepsRenderer package location.

After that, just build/re-deploy your application and everything should work fine:

enter image description here

Alansen answered 18/8, 2017 at 18:44 Comment(1)
Hugely helpful. Thanks so much for taking the time to post this.Fondea
H
3

Well, p:steps and p:wizard are the components in PrimeFaces component suite that represent or indicate the step(s) in a workflow to manage multiple steps of single form (step by step) for process simplication and can be used interchangably if you understand the usage properly (depending on the requirement).

For using p:steps component, you should ensure that the next step(s) will only be displayed when the current step is completely processed and required data is gathered. Assume the process of online shopping, where payment processing is the last step and that will appear if and only if, you have any item in your cart and have provided the other information (if any).

The above scenario can also be implemented using p:wizard component. Where only current step is processed partially and next step is displayed if current step passes validations. However, p:wizard component has feasibility to override it's default behavior by controlling the wizard flow, rendering of custom previous & next buttons with custom action handlers and skipping of validation to view next steps.

Hysterogenic answered 15/8, 2017 at 10:50 Comment(0)
L
2

menuform:I may answer your question a bit late, but I will post it so if other persona have the same problem, may it work for them.

I use JavaScript for the solution, so may it not the solution that you need:

// That is your code. I added ids to capture them with the DOM.
<p:steps activeIndex="3" styleClass="custom" readonly="false" style="padding: 20px;">
   <p:menuitem value="step 1." actionListener="#{masterController.menuSales(preferencesController)}" update="mainPanel" id="step1"/>
   <p:menuitem value="step 2." actionListener="#{masterController.menuCustomer(preferencesController)}" update="mainPanel" id="step2"/>
   <p:menuitem value="step 3." actionListener="#{masterController.menuItem(preferencesController)}" update="mainPanel" id="step3"/>
   <p:menuitem value="step 4" id="step4"/>
</p:steps>

// Now we can make the script
<script>
  // First of all, we will capture all the steps with the DOM (you can also work with jQuery, but I will post the solution with DOM in case you do not have your code prepared to jQuery)
  var step1 = document.getElementById("menuform:step1");
  var step2 = document.getElementById("menuform:step2");
  var step3 = document.getElementById("menuform:step3");
  var step4 = document.getElementById("menuform:step4");

  // Then, we are going to set the attributes href and onclick, and give them some style to make the elements look like proper links
  step1.setAttribute("href", "[url]");
  step1.setAttribute("onclick", true);
  step1.style.cursor = "pointer";

  step2.setAttribute("href", "[url]");
  step2.setAttribute("onclick", true);
  step2.style.cursor = "pointer";

  step3.setAttribute("href", "[url]");
  step3.setAttribute("onclick", true);
  step4.style.cursor = "pointer";

  step4.setAttribute("href", "[url]");
  step4.setAttribute("onclick", true);
  step4.style.cursor = "pointer";
</script>

Is important to change href and onclick (click event), because the element 'steps' change both of them, thats like them looks like when you inspect the code with the console: - href="#" - onclick="return false;"

Lapotin answered 21/5, 2019 at 13:8 Comment(1)
That was the key in my caseIdempotent

© 2022 - 2024 — McMap. All rights reserved.