0
votes

I have within a xp:repeat control a xp:button control. I would like to compute the styleClass property for the button, so I identify which one I have clicked by adding "active" to the styleClass.

Unfortunately when I set the active ID (ID of the button selected) in a viewScope variable and compare that with the ID computed in the styleClass they differentiate.

The difference is that the value for the ID in the viewScope seems to contain an additional iterator value e.g.

view:_id1:_id81:repeat2:0:button2 or view:_id1:_id81:repeat2:1:button2

and the computed value in the styleClass not e.g.

view:_id1:_id81:repeat2:button2

This last ID value is the same for all the other buttons.

For computing the ID value I use:

getComponent("button2").getClientId(facesContext)

as mentioned by Tim Tripcony Getting the clientId of an XPages button programmatically

What am I doing wrong or how can I compute the styleClass property properly in SSJS?

5
Please add full source code for computing the ID and computing the styleClass, specifically where and when it's triggered. Changes are it's to do with timing and the fact that the component tree only holds a single set of controls for the repeat which are iterated over at runtime. - Paul Stephen Withers
var myID:String = getComponent("button2").getClientId(facesContext); var activeID:String = sessionScope.get("btnSubRActive"); if (activeID == myID){ return "btn btn-responsive btn-marginb active"; } else{ return "btn btn-responsive btn-marginb not-active"; } - Patrick Kwinten
Why don't you use the server generated ID ("button2")? Every partial refresh or any refresh (every change on the Xpages) can generate new clientside id's for all controls. - Ferry Kranenburg

5 Answers

4
votes

Don't use client side ids. Use repeat index instead to memorize which button was clicked.

<xp:repeat
    id="repeat1"
    rows="30"
    value="..."
    indexVar="index">
    <xp:button
        value="Label"
        id="button1"
        styleClass="#{javascript:
            'yourClass' + (viewScope.buttonPressed == index ? 'Active' : '')}">
        <xp:eventHandler
            event="onclick" submit="true"
            refreshMode="partial" refreshId="repeat1">
            <xp:this.action><![CDATA[#{javascript:
                viewScope.buttonPressed = index}]]></xp:this.action>
        </xp:eventHandler>
    </xp:button>
</xp:repeat>
1
votes

After I wrote my first answer I headed home. On my way I thought this through and think I found a rather easy solution; since I fell that some of the ideas from the other answer could help understand my solution proposal I leave it intact and create this new one. Here's what you can try:

add a value to your repeater's indexVar property, e.g. rIndex (you'll find it under All Properties)

assuming that a button to become active it must be clicked by a user you add some SSJS code to its onclick event like this in order to store the current indexVar value in a requestScope variable:

requestScope.put("activeIndex", rIndex)

make sure that the onclick performs a partial refresh on a container that includes the repeat control

create code to compute your repeated button's style class; if the repeat's iteration index is the one stored in the requestScope you assign one style class, otherwise assign another one:

if(rIndex==requestScope.get("activeIndex")){
  "btnStyleActive";
}else{
  "btnStyleInactive";
}

Because the onclick partially refreshes the repeater and its contents the correctstyleClass is applied.

I don't have Domino Designer at home so I can't try it, but I feel this must work.

Update: Just built my own solution, very similar to Knut's snippet, and it's working nicely; no runtime errors. For completeness' sake I added an entry variable (var="rEntry") to the repeater but you may not need that.

I'll post my page's xml code so that you can copy and paste and try:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.resources>
        <xp:styleSheet href="/test.css"></xp:styleSheet>
    </xp:this.resources>
    <xp:panel id="pnMainContainer">
        <xp:repeat
            id="repeat1"
            rows="30"
            indexVar="rIndex"
            var="rEntry">
            <xp:this.value><![CDATA[#{javascript:[1, 2, 3, 4, 5]}]]></xp:this.value>
            <xp:panel id="pnRptInner">
                <xp:label id="label1">
                    <xp:this.value><![CDATA[#{javascript:"Entry content for this iteration = "+ rEntry.toString() + " "}]]></xp:this.value>
                </xp:label>
                <xp:button id="button1">
                    <xp:this.value><![CDATA[#{javascript:"Repeated Button @ index #" + rIndex.toString()}]]></xp:this.value>
                    <xp:this.styleClass><![CDATA[#{javascript:if(requestScope.get("activeIndex")==rIndex){
    "btnStyleActive";
}else{
    "btnStyleInactive";
}}]]></xp:this.styleClass>
                    <xp:eventHandler
                        event="onclick"
                        submit="true"
                        refreshMode="partial" refreshId="pnMainContainer">
                        <xp:this.action><![CDATA[#{javascript:requestScope.put("activeIndex", rIndex)}]]></xp:this.action>
                    </xp:eventHandler></xp:button>
            </xp:panel>
        </xp:repeat>
    </xp:panel>
</xp:view>

To make it complete, here's the very simple css:

.btnStyleInactive{color:blue;}
.btnStyleActive{color:red;};

Make sure that:

  • the button's onclick performs a partial update on the repeater itself or a surrounding container
  • the onclick code is SSJS
0
votes

you cannot rely on control ids within a static resource like a styleSheet since those ids are always calculated. Add on more component to your xpage and all control may receive new ids. I strictly avoid using ids within my styleSheets for that reason.

I'm not 100% sure whether I understood what you're trying to do here, so the following solutions may not be relevant for you; I'll try anyways:

  1. try to use pseudo identifiers within your css like

    div.myRepeaterClass button:active{whatever: however;}

  2. use a calculated styleClass that invcludes the repeater's indexVar; To do that you give your repeat control's indexVar property a name like rIndex. Then your button's styleClass could be rendered to

    styleClass="#{javascript:"myBtnClass_" + rIndex.toString()}"

This way you at least have unique styleClasses for your buttons

  1. this may be the most complicated one, in case the others don't work: as you may know css can also use all kinds of control attributes as a reference; assume you have a standard html element with a title attribute your css could be

    [title=myElementTitle]{whatever: however;}

For Xpage controls you could utilize the generic attribute property: you'll find an element "attrs" under "All propeties >> basics" for most controls. Here you can add your own custom attribute - value pairs which you could then reference from your styleSheet. If necessary you then could write some onClick code manipulating that custom attribute. Example:

create a custom attribute myRepeatBtn with a value btnInactive. Then write JS code that changes that value to btnActive. Your css could use this selector:

[myRepeatBtn=btnActive]{whatever: however;}

A partial refresh will cause the css to react to that change

Hope this maskes sense ;) Good luck.

0
votes

Alternatively from the button write the entry's NoteID to the viewScope variable and calculate the class based on that. I'm assuming there is some subsequent processing to be done, in which case that NoteID will be very useful.

It's a technique I've used on many occasions, for example enabling / disabling editable fields for "in view edit".

0
votes

within a repeater the "var" can be used in many places, but, for any reason whatsoever, not to calculate a styleClass.

The following workaround works: Use a standard div, span, ... instead of a xp:div, xp: span ... and then you can calculate the class. (Take care that you write valid javascript, since there is no editor support)

the code looks like this:

<xp:repeat
    id="repeat1"
    value="objList"
    var="obj">
    <span class="#{javascript:return (obj.Condition) ? 'class1' : 'class2';}">
        <xp:button
            value="obj.Name"
            id="button1"
            styleClass="btn">
            <xp:eventHandler
                event="onclick" submit="true"
                refreshMode="partial" refreshId="repeat1">
                <xp:this.action>
                ...
                </xp:this.action>
            </xp:eventHandler>
        </xp:button>
    </span>
</xp:repeat>

css depending on parent element class:

    ....
.class1 .btn{
    text-align: left;
}

.class2 .btn{
    text-align: right;
}

    ...