question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. ItĀ collects links to all the places you might be looking at while hunting down a tough bug.

And, if youā€™re still stuck at the end, weā€™re happy to hop on a call to see how we can help out.

[Feature Proposal] Conditional fields (aka. hide/show fields depending on other fields, aka. toggle field visibility)

See original GitHub issue

This is the most upvoted feature in our feature poll. So itā€™s the first one we can tackle, now that we have v5 ready for launch.

Letā€™s do this!!! šŸ’Ŗ

The Problem

Iā€™ve studied all previous PRs for this:

As I said in https://github.com/Laravel-Backpack/addons/issues/11 ā€¦ this is a tricky problem to solveā€¦ because the developer needs can be so very different. In various places, people ask:

  • for the ā€œsourceā€ to be a select, others radio, checkbox, checklist, select2_from_array, etc;
  • for the ā€œsourceā€ to be multiple fields, not just one;
  • for the ā€œactionā€ to be ā€œhidingā€;
  • for the ā€œactionā€ to be ā€œshowingā€;
  • for the ā€œactionā€ to be ā€œhiding and disablingā€;
  • for the ā€œactionā€ to be ā€œcalculating the valueā€;

Iā€™m ashamed we havenā€™t done anything to solve this until now. Literal shame. Butā€¦ the reason we havenā€™t merged any of thoseā€¦ is that I didnā€™t see way for us to cover what most devs need, most of the time. We would only be covering a particular use case, which would most certainly have meant we would be adding features and niche use cases to it for years, as people report them. And that simply wasnā€™t a good idea, to take on such a big maintenance burdenā€¦ when itā€™s not that difficult to do it in JS, for the people who needed it. You could do it in create.js and update.js and you have a solution, for your project alone, with maximum customizability. Thatā€™s why Iā€™ve been suggesting ā€œcreating an addon for itā€ to devs who have made PRs for thisā€¦ because they were niche solutions, and for sure somebody would have used itā€¦ but not everybody would have been happy with it.

I didnā€™t see how we can build this feature/field/whatever so that most people will be happy with it. But nowā€¦ I think I do. And itā€™s sort of what @pxpm suggested hereā€¦ but without the AJAX calls, which I think are unproductive and bound to fail.

And thanks to the open-core splitā€¦ we can start adding more and more feature, right into backpack/pro.

The Solution

What ifā€¦ instead of wanting to define the JS behavior in PHPā€¦ we accept we have to write a little bit of JS for this? And have Backpack make that bit of JS so easy to write, itā€™s a pleasure? In Backpack v5, thanks to the script widget, we can do:

Widget::add()->type('script')->content(asset('/path/to/js/file.js'));

So what ifā€¦

Step 1. We make it even easier, by providing a convenience method on CRUD, that also allows for inline content. Something like:

CRUD::addJavascript('/path/to/js/file.js');

// or even

CRUD::addJavascript("alert('what you want to be done here')");

Step 2. We make it dead-simple to write ALL of the combinations above, by providing a selector and a few actions on the crud javascript object (itā€™s already onpage, usually used for working with DataTables). So that what JS you actually write looks like this:

// option 1 - probably needs jQuery
crud.field('agree').on("change", function() {
    crud.field('price').show();
});
crud.field('agree').onChange(function() {
    crud.field('price').show();
});

// option 2 - maybe doesn't need jQuery
crud.field('agree').addEventListener("change", function() {
    crud.field('price').show();
});

// of course, you should be able to do a few other stuff with your `fields`, but the minimum would be:
crud.field('price').show();
crud.field('price').hide();
crud.field('price').disable();
crud.field('price').enable();
crud.field('price').value(); // aka .setValue(), aka .val()

I believe this would solve all cases people have already expressedā€¦ but also fix the cases people havenā€™t expressed yet. Which Iā€™m sure will come up, in real apps, right after we introduce a feature with limited customizability. But by moving this logic to JSā€¦ it opens up the possibility for you to doā€¦ anything you want. Itā€™s JS, and you have complete control.


Thoughts, anyone? Am I missing something? Or is this a solution we could all get behind? Iā€™m eager to prototype this, to see if we can launch it with Backpack v5 this week šŸŽ‰ I have a feeling itā€™s either a lot simpler than I expectā€¦ or a lot more difficult šŸ˜…

Canā€™t tell if this idea is incredibly goodā€¦ or incredibly stupid šŸ˜… Let me know.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Reactions:3
  • Comments:34 (22 by maintainers)

github_iconTop GitHub Comments

5reactions
promatikcommented, Mar 31, 2022

Hi everyone! Iā€™ve done this in the past, using Javascript, I created a simple solution for what I needed at that time, take a look at the result;

https://user-images.githubusercontent.com/1838187/161063634-b3cc44fb-201e-471a-adea-7cee785f0425.mp4

You can show/hide based on select and input[type="checkbox"].

Until Backpack provides a solution for this, you can use this code. It uses wrapperAttributes to add attributes to fields (select, checboxs) so it hides and shows based on that

Itā€™s super easy to implement, here is the code;

public/assets/js/backpack-toggle.js

JavaScript
let toggleInputFields = elem => {
  const targetsIds = elem.dataset.toggles.split(' ');
  targetsIds.forEach(id => {
    const targetElem = document.querySelector(
      `#${id}, [data-field-name=${id}]`
    );

    const checkbox = elem.querySelector('input[type="checkbox"]');
    targetElem.classList.toggle('d-none', !checkbox.checked);
  });
};

let toggleSelectFields = elem => {
  const { value } = elem;
  const { dataset: { toggles }} = elem.closest('[data-toggles]');

  document.querySelectorAll(`[${toggles}]`).forEach(field => {
    let r = false;

    // Check if json or single value
    try {
      r = !JSON.parse(field.attributes[toggles].value).includes(value);
    } catch (e) {
      r = field.attributes[toggles].value !== value;
    }

    // Toggle visibility
    field.classList.toggle('d-none', r);

    // Clear any child selects
    field.querySelectorAll('[data-toggles] select').forEach(select => {
      select.value = '';
      toggleSelectFields(select);
    });
  });
};

let initTogglers = () => {
  document.querySelectorAll('[data-toggles]').forEach(elem => {
    const field = elem.querySelector('input[type="checkbox"], select');
    // eslint-disable-next-line default-case
    switch (field.type) {
      case 'checkbox':
        // On click
        elem.addEventListener('click', () => {
          toggleInputFields(elem);
        });

        // Check initial status
        if (elem.querySelector('[type=checkbox]').checked) {
          toggleInputFields(elem);
        }
        break;
      case 'select-one':
        // On change
        $(elem).on('change', () => {
          toggleSelectFields(field);
        });

        // Check initial status
        toggleSelectFields(field);
        break;
    }
  });
};

document.addEventListener('DOMContentLoaded', initTogglers);

public/assets/css/backpack-toggle.css

CSS
.form-quote {
  border-left: 1px solid #e7e8ea;
  margin-left: 2rem;
  max-width: calc(100% - 4rem);
  margin-bottom: 0;
  padding-top: 0.5rem;
  padding-bottom: 0.5rem;
}

app\Http\Controllers\Admin\EntityCrudController.php

PHP
protected function setupCreateOperation() {
    Widget::add()->type('script')->content('assets/js/backpack-toggle.js');
    Widget::add()->type('style')->content('assets/css/backpack-toggle.css');
    ...
// Select Toggler
CRUD::addField([
    ...
    'wrapperAttributes' => [
        'data-toggles' => 'toggler-device-type',
    ],
]);

CRUD::addField([
    ...
    'wrapperAttributes' => [
        'class' => 'd-none form-group col-sm-12 form-quote',
        'toggler-device-type' => 'win',
    ],
]);
// Checkbox Toggler
CRUD::addField([
    ...
    'wrapperAttributes' => [
        'class' => 'field-toggler form-group col-sm-12',
        'data-toggles' => 'hours minutes',
    ],
]);

CRUD::addField([
    ...
    'wrapperAttributes' => [
        'class' => 'd-none col-12 form-quote',
        'id' => 'hours',
    ],
]);

CRUD::addField([
    'name' => 'minutes',
    ...
    'wrapperAttributes' => [
        'class' => 'd-none col-12 form-quote',
        'id' => 'minutes',
    ],
]);

The example video above uses the following code;

app\Http\Controllers\Admin\EntityCrudController.php

Example PHP
protected function setupCreateOperation() {
    Widget::add()->type('script')->content('assets/js/backpack-toggle.js');
    Widget::add()->type('style')->content('assets/css/backpack-toggle.css');
    
    // Toggler Device Type
    CRUD::addField([
        'name' => 'device_type',
        'label' => 'Device type',
        'type' => 'select_from_array',
        'options' => [
            'win' => 'Windows',
            'linux' => 'Linux',
            'mac' => 'Mac',
            'unix' => 'Unix',
        ],
        'default' => 'win',
        'wrapperAttributes' => [
            'data-toggles' => 'toggler-device-type',
        ],
    ]);

    CRUD::addField([
        'name' => 'win_script',
        'label' => 'Windows script',
        'wrapperAttributes' => [
            'class' => 'd-none form-group col-sm-12 form-quote',
            'toggler-device-type' => 'win',
        ],
    ]);

    CRUD::addField([
        'name' => 'win_type',
        'label' => 'Windows type',
        'type' => 'select_from_array',
        'options' => [
            'win10' => 'Win 10',
            'win11' => 'Win 11',
        ],
        'default' => 'win',
        'wrapperAttributes' => [
            'class' => 'd-none form-group col-sm-12 form-quote',
            'toggler-device-type' => 'win',
            'data-toggles' => 'toggler-win-type',
        ],
    ]);

    CRUD::addField([
        'name' => 'win11',
        'label' => 'Windows 11 special function',
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'toggler-win-type' => 'win11',
        ],
    ]);

    // ---

    CRUD::addField([
        'name' => 'linux_title',
        'label' => 'Linux title (unix / linux)',
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'toggler-device-type' => json_encode(['linux', 'unix']),
        ],
    ]);

    CRUD::addField([
        'name' => 'mac_title',
        'label' => 'Mac title (unix / mac)',
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'toggler-device-type' => json_encode(['mac', 'unix']),
        ],
    ]);

    CRUD::addField([
        'name' => 'unix_script',
        'label' => 'Unix script (unix / (linux / mac))',
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'toggler-device-type' => json_encode(['unix', 'linux', 'mac']),
        ],
    ]);

    // Toggler time
    CRUD::addField([
        'name' => 'scheduled_time',
        'label' => 'Scheduling time',
        'type' => 'checkbox',
        'wrapperAttributes' => [
            'class' => 'field-toggler form-group col-sm-12',
            'data-toggles' => 'hours minutes',
        ],
    ]);

    CRUD::addField([
        'name' => 'hours',
        'label' => 'Hours',
        'type' => 'select2_from_array',
        'options' => range(0, 23),
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'id' => 'hours',
        ],
    ]);

    CRUD::addField([
        'name' => 'minutes',
        'label' => 'Minutes',
        'type' => 'select2_from_array',
        'options' => range(0, 60),
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'id' => 'minutes',
        ],
    ]);

    // Toggler weekdays
    CRUD::addField([
        'name' => 'scheduled_weekdays',
        'label' => 'Scheduling weekdays',
        'type' => 'checkbox',
        'wrapperAttributes' => [
            'class' => 'field-toggler form-group col-sm-12',
            'data-toggles' => 'weekdays',
        ],
    ]);

    CRUD::addField([
        'name' => 'weekdays',
        'label' => 'Weekdays',
        'type' => 'select2_from_array',
        'allows_multiple' => true,
        'options' => ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturay'],
        'wrapperAttributes' => [
            'class' => 'd-none col-12 form-quote',
            'id' => 'weekdays',
        ],
    ]);
}
2reactions
ashek1412commented, Mar 29, 2022

however, I have to do it using javascript for the time being. but when you guys put your plan into action?

Read more comments on GitHub >

github_iconTop Results From Across the Web

AEM: Conditionally show or hide fields in TouchUI dialogs ...
How to create a TouchUI dialogs that can dynamically show or hide fields based on user input is a topic that often pops...
Read more >
Conditional fields - Help center - PandaDoc
Our conditional fields feature helps your customers navigate through documents and see only specific fields they need to fill out, based onĀ ...
Read more >
TIBCO WebFOCUSĀ® User's Guide Release 8207
WebFOCUSĀ® is the reporting engine that turns corporate data into meaningful information. It enables any executive, manager, analyst, employee, partner,Ā ...
Read more >
StreetSmart EdgeĀ® User Guide - Charles Schwab
Get an overview of the software by viewing the New Features and Education guide, which identifies the more commonly used tools, features, and...
Read more >
Gmsh 4.11.1
8 Gmsh mesh size fields; 9 Gmsh plugins; 10 Gmsh file formats. 10.1 MSH file format ... Different geometry kernels // have different...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found