Jump to content

Simple way to have multiple select fields as per value choice


MilenKo
 Share

Recommended Posts

Hey gents,

I am working on a tech services profile and I got a bit stuck on choosing the optimal approach to provide two drop-down selection fields where the options for the second would depend on the choice of the first. Here are some sample categories to get the idea better:-

Spoiler

-Windows
   - Installation
   - Update
   - Optimization

- Linux
   - Kernel config
   - Desktop environment
   - LAMP

- Mac
   - MAMP
   - MacOS Update
   - App Store Config

- Networking
   - Router 
   - Switch
   - Wi-Fi
 

The needed functionality is to allow the admin to select from first drop-down a choice in between Windows, Linux, Mac, Networking and based on their choice to have the second list of options populated with the sub-choices for the specific top "category" ONLY. I know I could add two select fields and add the options there, however I am not sure/aware how to interlink those so the admin can't select for example category Networking and subcategory Router.

I might have been overlooking the things as I've checked even the functionality of ProFields, however so far I was unable to find the elegant solution for two fields with values dependable on selection.

Any ideas or suggestions how to approach this?

Link to comment
Share on other sites

For sure one approach would be to have the first level categories as parents and then sub-categories as children, however some services might be offered for multiple parent-level pages, so it would cause a page repetition. Besides that, if a re-structuring is needed, it would be fun to move 30-40 pages from one parent to another.

P.S. I found this topic and would see how would that go but still need the pro's opinion what is the best and easiest approach

Link to comment
Share on other sites

Hey @elabx I did find this module from @kongondo and have no doubt that it would do the job perfectly. The reason not yet to purchase it is because I am just trying to weight every option and see which one fits the best. Quite honestly, I would love to purchase all the premium modules from the PW-guru-gang as this would be just a tiny payout for the flawless and completely free support we've all received throughout the years, but presently I am on a bit tight budget so that would have to wait. I am definitely purchasing the FormBuilder and Dynamic Select as I see them fit in almost every project...

  • Like 1
Link to comment
Share on other sites

54 minutes ago, MilenKo said:

but presently I am on a bit tight budget so that would have to wait.

Of course, totally understand!

What if you have a page select per category, and the field's visibility depends on the Operating System selection. So if selection is "Windows", it will show the field that only contains the options for Windows.

So on your template you would have the fields:

  • operating_system
  • windows 
  • mac
  • linux

Hope I am understanding your problem correctly!

Link to comment
Share on other sites

I'm not sure I understand the scenario fully, but I think you could easily achieve this with core PW functionality. I don't have time right now to build a "quick and dirty" example setup, but I guess with field dependencies and page fields (as the main "tools") you should be good to go. Maybe I'll write a quick "proof of concept" later...

 

 

 

 

Link to comment
Share on other sites

@dragan thank you for the info and the eventual write up (if time permits for sure). @Robin S I've been reading your information this afternoon and will test the implementation to see how it goes. I am sure there would be tons of approaches and I have no doubt that the easiest would be to use @kongondo's module but let's test everything and see for such a simple implementation which one would fit the purpose.

Link to comment
Share on other sites

@MilenKo Finally managed to put together a quick setup.

Good news is that it works (with a hook), but it's not ideal UX-wise.

In order for the sub-categories to switch, the page must be saved first. Only then do you see the valid options.

Perhaps this can be optimized with the new JS field API, but I haven't tried it yet.

On the other hand - if the number of main and sub-categories won't change constantly, you could simply create four sets of page-reference fields (one for each main category), and work with field dependencies (show only if ___). But that won't scale, and if your above example is just a simplified example, you'll get into maintenance hell sooner or later.

Link to comment
Share on other sites

Here's an example that works without having to first save & re-load the page you're editing.

The setup:

In my page tree somewhere (usually under a parent I call "meta", "settings" or similar), I have main and sub-categories as pages:

429815186_Screenshot_2020-01-18PagesProcessWirepwtest.png.37c12f8aebd4b5d76ca8fbfc2cc5f5ea.png

 

The sub category pages have template "empty" with just the title field and nothing else - not even a physical template.php file.

The main category pages have tpl "maincat", which holds an addtl. field "main2sub", which is a page reference field. Here you map your sub-categories to each main category.

My (simplified) product tpl has two fields:

  • maincat -> page reference field single. Parent = main cat / 1366
  • select_categories -> page reference field multiple / output as checkboxes. No configuration set in field settings "selectable pages".

32549258_Screenshot_2020-01-18EditPageMacpwtest.png.f663f7dfdd7369c74baee6533fcace39.png

So far, so boring.

I need JS + PHP for the auto-switch to happen.

I created site/templates/admin/admin.js

In order for PW to even load it, I edited site/templates/admin.php:

$config->scripts->add($config->urls->templates . "admin/admin.js");
require($config->paths->adminTemplates . 'controller.php'); // this must stay as the last line

admin.js looks like this:

Spoiler

document.addEventListener("DOMContentLoaded", function (event) {

    const urlParams = new URLSearchParams(window.location.search);
    const pid = urlParams.get('id');
    const select = document.getElementById("Inputfield_maincat");
    if (select) {
        getAllowedSubCats(event); // on page load
        select.addEventListener("change", getAllowedSubCats, true); // on main-cat change
    }

    function getAllowedSubCats(event) {

        console.log(event.type);
        // change this: outermost inputfield wrapper ID that holds your sub-categories:
        const subcatInput = document.getElementById('wrap_Inputfield_select_categories');
        // if you're not using checkboxes, change your selector accordingly:
        const subcatItems = subcatInput.querySelectorAll("input[type=checkbox]");
        let mainCatValue = select.options[select.selectedIndex].value;
        console.log(mainCatValue);

        if(event.type === 'change') {
            subcatItems.forEach(function (elem) {
                elem.removeAttribute("checked");
            });
        }

        // change this to point to your PHP script URL that returns the matching sub-categories:
        const url = "http://pw.test/api/update_subcats/" + pid + "/" + mainCatValue + "/";

        fetch(url, {
            headers: {
                'Content-Type': 'application/json',
            },
            method: "GET",
        })
            .then((response) => response.json())
            .then((data) => {
                // console.log('Success:', data);
                const subcats = data.subCats; // the response array holding our sub-category page IDs

                // reset disabled state:
                subcatItems.forEach(function (elem) {
                    elem.removeAttribute("disabled");
                    elem.closest('li').setAttribute("style", "display: block;");
                });

                subcatItems.forEach(function (elem) {
                    const cbValue = elem.getAttribute("value"); // checkbox value
                    const cbValueInt = Number(cbValue); // this is necessary, because DOM attributes are strings
                    if (!subcats.includes(cbValueInt)) {
                        // disable and hide all categories that are not allowed for selected main category:
                        elem.setAttribute("disabled", "disabled");
                        elem.closest('li').setAttribute("style", "display: none;");
                    }
                });
            })
            .catch((error) => {
                console.error('Error:', error);
            });
    }

});

 

It's written in plain JS (ES6). With jQuery it would have been a bit shorter, no doubt, but I got into the habit of using as much vanilla JS as possible. If you need to support older browsers, you'd need to run it through something like Babel.

In nearly every PW installation, I create a dedicated template + page that acts as an API / AJAX endpoint. Create a new template "api", enable URL-segments, create a file "api.php" in site/templates/, and finally create a hidden page somewhere that uses this template. Use the option "must have trailing slashes" (check yes).

The api.php looks something like this:

Spoiler

<?php

if (!$input->urlSegment1 ) {
    return $this->halt();
}

if ($input->urlSegment1 === 'update_subcats' && $input->urlSegment2 && $input->urlSegment3) {
    header('Content-Type: application/json; charset=utf-8');

    $pid = (int) $input->urlSegment2; // page id of product
    $productPage = $pages->get($pid); // product page
    $mainCat = (int) $input->urlSegment3; // page id of main category, e.g. Windows, Mac
    $subCats = $pages->get($mainCat)->main2sub; // assigned page reference sub-categories attached to main category

    $subCatsArray = array();
    foreach($subCats as $sub) {
        $subCatsArray[] = $sub->id;
    }

    $ar = array(
        "success" => 'yay!',
        "pid" => $pid,
        "mainCat" => $mainCat,
        "subCats" => $subCatsArray,
    );

    $json = json_encode($ar);
    echo $json;
}

 

Basically, what happens, is:

On page load and on main-category-dropdown change, I send an AJAX request to my API script with the page ID I'm currently editing and the main category page id. The API script then sends back the matching sub-categories as JSON. JS again then loops through all checkboxes, and de-activates and hides all non-matching choices.

It works, but it's not perfect*. I wish PW had an in-built way to handle such scenarios.

As always, YMMV.

* There's a split second on page load where you see all sub-categories. And also, when you select again the same main-category (without having saved the page), if you made selections previously, they're being erased. The first (minor) issue could probably easy be prevented if you set your subcat field to closed. You could first do your JS-syncing and at the end make them visible with Inputfields.open. Speaking of Inputfields JS API... I found that Inputfields.reload() didn't work here in my example (I tried several types of fields). Pity - would have made such a thing much easier...

dynamic-checkboxes.gif.ecee381649a65f4534a0c6552bb99573.gif

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

7 hours ago, dragan said:

I wish PW had an in-built way to handle such scenarios.

It does, it's just not documented unfortunately. I linked to information about it in my earlier post above.

Here is a demo...

Page structure:

2020-01-19_123527.png.a8a31846bb91b9c79b264910756d526d.png

Field settings for subcategory field:

2020-01-19_123615.png.5850e239d03adf4f5ca32c24440a95cb.png

"page.category" will be replaced with the ID of the page selected in the Category inputfield in Page Edit, whenever that field changes.

The "has_parent" part is just to avoid unwanted pages appearing in the Subcategory inputfield if the Category inputfield is changed to empty (no page selected).

Result:

dependent.gif.03574b16c145984df24c26d0ec819423.gif

  • Like 6
  • Thanks 1
Link to comment
Share on other sites

On 1/19/2020 at 12:41 AM, Robin S said:

I linked to information about it in my earlier post above

I have of course read that thread, but that approach has a few limitations.

If you set up categories and sub-categories the way you did, it works without a doubt.

However, if you want to stick to the DRY principle (don't repeat yourself), and choose a maincat<-->subcat relationship that I have chosen, this won't work. In a real-life scenario you'll likely have several sub-categories that are eligible for more than just one main category. Suppose you (or your client) re-names a certain sub-category, you'll have to update all instances of that one single sub-category for each main-category as well. With a small setup this might not be an issue at all. But as the site (and products...) grows, this can turn into a chore to maintain. I'd rather have a separate, independent set of sub-cats to choose from. IRL many of those sub-cats are overlapping ("backups" or "upgrade" surely isn't only available on Win or Mac).

From a UX/GUI perspective, when you need multiple subcats to attach to a maincat, your options are rather poor. Select Multiple, even in 2020, looks and feels... like 1999 (without the party aspect). So if you decide to use checkboxes in the admin, this approach won't work.

Link to comment
Share on other sites

2 hours ago, dragan said:

However, if you want to stick to the DRY principle (don't repeat yourself), and choose a maincat<-->subcat relationship that I have chosen, this won't work.

There's nothing in the core dependent selects feature that dictates how you organise your pages. A parent/child relationship to define categories and subcategories is just one that suits many scenarios, and if I understand right it's the way that was specified in the original question.

The thing that determines how it works is this:

On 1/19/2020 at 12:41 PM, Robin S said:

"page.category" will be replaced with the ID of the page selected in the Category inputfield in Page Edit, whenever that field changes.

So you can use "page.page_reference_field_name" in the selector string setting for the dependent field in any way that suits you. You could have all your subcategories under one parent and connect them with one or more categories using a "category" page reference field in the subcategory template. Then the selector string for the subcategory field could include "category=page.category" to limit the subcategories according to what is selected in the category field on the currently edited page.

2 hours ago, dragan said:

From a UX/GUI perspective, when you need multiple subcats to attach to a maincat, your options are rather poor.

AsmSelect works with the core dependent selects, but it certainly would be nice if the range of compatible inputfield types could be expanded.

Link to comment
Share on other sites

on a side note, I was able to create dependent selects using 2 selectize fields, where the options available in the 2nd select dynamically changed based on the selection in the first field.  Maybe this topic is relevant...

 

  • Thanks 1
Link to comment
Share on other sites

Wow, guys, really Wow! I asked for an idea and I got about 3 different well explained approaches. Let me test all one by one and see which one would be the winner, but there is definitely no doubt that the three would be quite useful to know. I can't thank you enough for the tips. I mean it!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...