XF 2.2 Selectrow - conditional action to show content

stromb0li

Active member
Does XenForo have documentation on what actions can natively be handled via <xf> tags vs rolling their own javascript for client-side actions?

Example; I've defined a dropdown using <xf:selectrow> and <xf:option> tags, but I'd like to show text based on what the individual select. The text is static and likely not to change. Can this natively be handled via <xf> tags or must this be handled via custom javascript include?

Thank you!
 
Sorry to bubble this up, but any thoughts on this one? Similarly, I'd like to change the value of a button based on the option selected. Is there something native I can take advantage of or do I have to handle all of these via javascript include? If using javascript includes, are there any xenforo specific items that can be taken advantage of?
 
Template tags are a bit distinct from JS handlers, though there is some overlap as some tags will automatically invoke certain JS handlers. Generally speaking, you attach JS handlers to elements with the data-xf-init attribute (or data-xf-click attribute for click events), and pass further options to the handler with other data attributes. There are dozens of built-in ones but they are not properly documented.

As for hiding/showing things based on a form control, there is XF.Disabler:

HTML:
<xf:selectrow name="some_name" value="some_value"
    label="Some label">

    <xf:option value="some_value"
        label="Some label"
        data-xf-init="disabler" data-container=".js-hideMe" data-hide="true" />
</xf:selectrow>

<!-- ... -->

<div class="js-hideMe">
    This will be hidden unless the value above is selected.
</div>
 
As for hiding/showing things based on a form control, there is XF.Disabler:
The more I read your posts, the more I realize how feature rich XF is. This and <xf:dependent>, which are very common use cases, literally save us from writing a lot of JS code.
 
Template tags are a bit distinct from JS handlers, though there is some overlap as some tags will automatically invoke certain JS handlers. Generally speaking, you attach JS handlers to elements with the data-xf-init attribute (or data-xf-click attribute for click events), and pass further options to the handler with other data attributes. There are dozens of built-in ones but they are not properly documented.

As for hiding/showing things based on a form control, there is XF.Disabler:

HTML:
<xf:selectrow name="some_name" value="some_value"
    label="Some label">

    <xf:option value="some_value"
        label="Some label"
        data-xf-init="disabler" data-container=".js-hideMe" data-hide="true" />
</xf:selectrow>

<!-- ... -->

<div class="js-hideMe">
    This will be hidden unless the value above is selected.
</div>
This is exactly what I was looking for, thank you! I have a few use-cases where I could use these. Which class are they in? By chance, is there a built in "Are you sure you want to do this" dialog that I could implement on a deletion scenario (xf:button)? :p I'm also trying to see how I can change the href value on a button based on the option value selected. These should be the last two scenarios to unblock most scenarios I'd need.

The more I read your posts, the more I realize how feature rich XF is. This and <xf:dependent>, which are very common use cases, literally save us from writing a lot of JS code.
+1, the further I get in my add-on, the more I really enjoy developing in XF. Super powerful! Really have appreciated all the help @Jeremy P, would have been super stuck without all your support!

Edit: Looks like these are the built-in ones, but not sure how to implement them to achieve the other two use-cases (popup "Are you sure" and swapping of href link on a button)
  • alerts-list
  • draft
  • draft-trigger
  • focus-trigger
  • poll-block
  • preview
  • share-buttons
  • share-input
  • web-share
  • copy-to-clipboard
  • push-toggle
  • push-cta
  • reaction
  • bookmark-click
  • bookmark-label-filter
  • content-vote
  • install-prompt
 
Last edited:
As for hiding/showing things based on a form control, there is XF.Disabler:
This works flawlessly when the select tag value changes (onChange) event. However, if I use it during onload (select value set on load of form), it doesn't seem to work. Is there a way it can work on both events?


By chance, is there a built in "Are you sure you want to do this" dialog that I could implement on a deletion scenario (xf:button)
There's a XF:Delete plugin that does just this.
 
Last edited:
This is exactly what I was looking for, thank you! I have a few use-cases where I could use these. Which class are they in?
They are in the JS files (/js/xf/*.js). Here is a more comprehensive list via grepping the files:

add-user
admin-asset-editor
admin-nav
admin-search
admin-toggle-advanced
ajax-submit
alerts-list
asset-upload
attachment-manager
attachment-on-insert
auto-complete
auto-submit
auto-timezone
avatar-cropper
avatar-upload
banner-positioner
banner-upload
bookmark-click
bookmark-label-filter
braintree-apple-pay-form
braintree-payment-form
braintree-paypal-form
carousel
changed-field-notifier
change-submit
check-all
checkbox-select-disabler
code-block
code-editor
code-editor-switcher-container
color-picker
content-vote
cookie-consent-form
copy-to-clipboard
date-input
desc-loader
disabler
draft
draft-trigger
editor
editor-manager
element-tooltip
emoji-completer
field-adder
filter
focus-inserter
focus-trigger
form-fill
form-submit-row
guest-captcha
guest-username
h-captcha
h-scroller
inline-mod
input-validator
install-prompt
key-captcha
lightbox
list-sorter
login-form
member-tooltip
min-length
multi-quote
nestable
notices
number-box
oembed
page-jump
password-hide-show
password-strength
payment-provider-container
permission-choice
permission-form
permission-matrix
poll-block
post-edit
prefix-loader
prefix-menu
preview
preview-tooltip
push-cta
push-toggle
qa-captcha
quick-reply
quick-search
quick-thread
rating
reaction
re-captcha
reg-form
responsive-data-list
select-plus
select-to-quote
share-buttons
share-input
share-tooltip
solve-captcha
stats
sticky
sticky-header
stripe-payment-form
tabs
tagger
tel-box
textarea-handler
thread-edit-form
toggle-storage
token-input
token-input-select
tooltip
touch-proxy
translate-submit
turnstile
tweet
user-mentioner
video-init
video-player
web-share
approval-control
attribution
comment-loader
comment-toggle
cookie-consent
cookie-consent-toggle
duplicator
editor-placeholder
focus
inserter
like
menu
menu-proxy
message-loader
multi-check
off-canvas
overlay
prefix-grabber
preview-click
quick-edit
quote
remover
remove-user
scroll-to
shifter
solution-edit
submit
switch
switch-overlay
text-edit
toggle
toggle-class

I'm also trying to see how I can change the href value on a button based on the option value selected. These should be the last two scenarios to unblock most scenarios I'd need.
You could maybe use the disabler to have two buttons and only show one or the other. Short of that, I'm not sure if this is covered by a built-in handler.

This works flawlessly when the select tag value changes (onChange) event. However, if I use it during onload (select value set on load of form), it doesn't seem to work. Is there a way it can work on both events?
You can trigger the event manually:

JavaScript:
$option.trigger('change');
 
Sorry for more questions :( I have a prototype of what I want to do, but feel it is littered with bad practices and it doesn't seem to be supported with overlay support (when inspecting my button, I can see the href attribute is updated on the <a> element, but the first link selected is the value always used for loading content vs updated href attribute value. Even when specifying data-cache="false", the first link clicked will be used / cached).

Here's my example code:
HTML:
<xf:selectrow data-xf-init="dropdownSelect">
<xf:option value="1">Note A</xf:option>
<xf:option value="2">Note B</xf:option>
<xf:option value="3">Note C</xf:option>
</xf:selectrow>


....


<xf:js>
!function($, window, document, _undefined)
{
    "use strict";

    XF.dropdownSelect = XF.Element.newHandler({

        init: function()
        {
            $(this.$target).on( "change", function() {
                $("#target").attr("href", "https://mywebsite.com/notes/"+$(this).val()+"/new");
            } );
        }
    });

    XF.Element.register('dropdownSelect', 'XF.dropdownSelect');
}
(jQuery, window, document);
</xf:js>

Questions:
1) Is it best practice to reuse the existing XF namespace or as an add-on should I use my own?
2) If I use my own, how do you handle sub-folders in the name?
3) What is the best way to define the target?
4) When specifying the href value, is there a javascript equivilent of the link builder vs using a constant value like I have in the example?
5) I saw there is a built-in "inserter" with an option for href. Would it be posible to reuse that in this example, or would that only work for click events instead of change? Similarly, would it be possible to set another element's attribute to swap out using that implementation?

P.S. Yes, I will move this to a js file and include it; just using it inline for testing/demonstration of what I'm trying to do for right now :)

Thank you again for all the help this past week!
 
Last edited:
it doesn't seem to be supported with overlay support (when inspecting my button, I can see the href attribute is updated on the <a> element, but the first link selected is the value always used for loading content vs updated href attribute value.
Oh, the href value is stored on the overlay handler when it is invoked. To get around this, it might be best to clone the existing button element, update the clone href, swap them, and call XF.activate() (which applies handlers to dynamically inserted elements). Something like this:

JavaScript:
const $newButton = $existingButton.clone();
$newButton.attr('href', 'some/link');
$existingButton.replaceWith($newButton);
XF.activate($newButton);

1) Is it best practice to reuse the existing XF namespace or as an add-on should I use my own?
Best practice to use your own, but equally if you apply a unique enough prefix (ie XF.VendorAddOnThing) then using XF shouldn't cause you much trouble.

2) If I use my own, how do you handle sub-folders in the name?
In JS the namespace is just a global variable, so there isn't really the same sub-folder mapping concept as PHP. Having a single namespace per add-on (or even vendor) is pretty common.

3) What is the best way to define the target?
Using XF.findRelativeIf is the most flexible. You can specify an options object on the handler and pass options via HTML data attributes:

JavaScript:
AddOn.SomeHandler = XF.Element.newHandler({
    options: {
        link: null,
        href: null,
    },

    init ()
    {
        const $link = XF.findRelativeIf(this.options.link, this.$target);

        this.$target.on('change', () =>
        {
             $link.attr('href', this.options.href);
        });
    },
});

HTML:
<some-element data-xf-init="some-handler" data-link="#target" data-href="{{ link('some/route') }}" />

4) When specifying the href value, is there a javascript equivilent of the link builder vs using a constant value like I have in the example?
Not really, in the core routes are passed as options per above. You could pass multiple routes via options or do string manipulation on the passed one based on the input value, as you have in your existing code.

5) I saw there is a built-in "inserter" with an option for href. Would it be posible to reuse that in this example, or would that only work for click events instead of change? Similarly, would it be possible to set another element's attribute to swap out using that implementation?
The base inserter can be set up to handle any kind of event, but it's geared towards loading content over AJAX and inserting/replacing entire elements. The href option there is the route returning the HTML you want to load, so probably not applicable here.
 
Last edited:
$option.trigger('change');
Sorry, haven't been able to figure this one out. How do I use this in JS?

I'm using the following code

HTML:
<xf:selectrow name="foobar" label="Foo or Bar" value="{$foobar}">                                   
    <xf:option value="foo" data-xf-init="disabler" data-container="#myDiv" data-hide="yes">Foo</xf:option>   
    <xf:option value="bar">Bar</xf:option>       
</xf:selectrow>
<div id="myDiv">
    <xf:textboxrow name="abc" value="{$abc}" label="ABC" />
</div>

I checked out the notice_edit Admin template but I couldn't find the use of the 'change' trigger in it and yet it seems to work nicely when a notice is opened for edit and "Notice Type" is set to "Floating"
 
Oh, I thought you meant you were setting the value in your own onload event. XF handlers are initialized using jQuery's ready method, which is roughly equivalent to DOMContentLoaded, and also when content is dynamically inserted via some built-in mechanisms.

The handler already checks the value upon initialization, and indeed even if I change the notice_edit template to a <xf:selectrow> (instead of a <xf:radiorow>) it works as anticipated, so I can't reproduce this. I don't see anything obviously wrong in your code though.
 
so I can't reproduce this
I think I got the culprit. It seems we can't have the same container defined for multiple options.

For example, this works
HTML:
<xf:selectrow name="foobar" label="Foo or Bar" value="{$foobar}">                                  
    <xf:option value="foo" data-xf-init="disabler" data-container="#myDiv" data-hide="yes">Foo</xf:option>  
    <xf:option value="bar">Bar</xf:option>      
</xf:selectrow>
<div id="myDiv">
    <xf:textboxrow name="abc" value="{$abc}" label="ABC" />
</div>

And this doesn't
HTML:
<xf:selectrow name="foobar" label="Foo or Bar" value="{$foobar}">                                  
    <xf:option value="foo" data-xf-init="disabler" data-container=".myDiv" data-hide="yes">Foo</xf:option>  
    <xf:option value="bar">Bar</xf:option>      
    <xf:option value="goo" data-xf-init="disabler" data-container=".myDiv" data-hide="yes">Goo</xf:option>  
</xf:selectrow>
<div class="myDiv">
    <xf:textboxrow name="abc" value="{$abc}" label="ABC" />
</div>

In my case, the markup contained two option tags with the same data-container

So to overcome this, I'm resorting to the following:

HTML:
<xf:selectrow name="foobar" label="Foo or Bar" value="{$foobar}">                                  
    <xf:option value="foo" data-xf-init="disabler" data-container="#myDiv1" data-hide="yes">Foo</xf:option>  
    <xf:option value="bar">Bar</xf:option>      
    <xf:option value="goo" data-xf-init="disabler" data-container="#myDiv2" data-hide="yes">Goo</xf:option>  
</xf:selectrow>
<div id="myDiv1">
    <xf:textboxrow name="abc" value="{$abc}" label="ABC" />
</div>
<div id="myDiv2">
    <xf:textboxrow name="abc" value="{$abc}" label="ABC" />
</div>

It's kind of duplicating content but gotta do what you have to do.
 
Oh yeah. They will both fight for control of the container. Not sure if it helps your use case, but you can add a disabler on the middle option and set data-invert="true" and then the container will be hidden when it is selected and shown when the other two are selected.
 
Top Bottom