-
Notifications
You must be signed in to change notification settings - Fork 920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature Proposal] Conditional fields (aka. hide/show fields depending on other fields, aka. toggle field visibility) #4158
Comments
Hello @tabacitu In general I agree with your idea, it goes along with what I was already thinking with some twists, so I will try to sum up what I thought when reading it. At first it seems to me that the only thing we are doing is "facilitating" the use of JS in page, not really adding the
One good thing I think it's worth for us doing is separating all those options (show, hide, setValue etc..) because I am sure some of them are easier to achieve than others and probably we could make the So starting by the most commons I think: show/hide/disable/enable First we should establish that there are very different scenarios happening in cruds, including repeatable fields, fields inside modals, etc. So a simple That said, I think that is where backpack should shine in to help developer to don't have to think about how backpack fields are built to I am 100% against javascript in the controllers. At most I would agree with CRUD::addfield([
'name' => 'add_extra_cost',
'type' => 'checkbox',
'events' => [
'onCheck' => function () {
// for single form fields
$this->hideField('field_name');
$this->hideRepeatableField('some_field'); // we would know to search the current clicked row and hide the corresponding group field.
$this->disableField('field_name');
},
'onUncheck' => function () {
$this->showField('field_name');
$this->showRepeatableField('field_name');
$this->enableField('field_name');
},
]);
// events
function hideField($name) {
// return JS string to find and hide a simple field
}
function hideRepeatableField($name) {
// return JS string to find and hide a field inside a repeatable container
} and we would translate this into JS like: //
checkbox.on('change', function() {
if($(this).isChecked()) {
{!! $display_the_strings_we_build !!}
}else{
{!! $display_the_strings_we_build !!}
}
} this would not solve ALL the interactivity, but establishes a base for if/when we decide to support ajax for more complicated calculations and no need for dev to know how to search inside repeatable fields etc and repeating the code over and over again. I also think it's worth adding that for fields that are complicated to get the containers that should be hidden etc, we could define on those fields a Let me know if I missed the point here @tabacitu . |
Thanks for the feedback @pxpm 🙏 First of all, let me get the "big stuff" out of the way 😅 I have already considered and dismissed doing something like this: CRUD::addfield([
'name' => 'add_extra_cost',
'type' => 'checkbox',
'events' => [
'onCheck' => function () {
// for single form fields
$this->hideField('field_name');
$this->hideRepeatableField('some_field'); // we would know to search the current clicked row and hide the corresponding group field.
$this->disableField('field_name');
},
'onUncheck' => function () {
$this->showField('field_name');
$this->showRepeatableField('field_name');
$this->enableField('field_name');
},
]); I agree with you that it will be the most dev-friendly way to do this. I do! For devs who don't know/want to code JS, this will be GOLD. And what those people want to do most of the time is show/hide fields, for which this solution would work ok. But if you look at the bigger picture, there are more reasons NOT to like this than reasons to like this:
|
Agreed. This is one reason I'm having doubts about this. But I think if we make the syntax simple enough, and with
If they're doing a complicated thing and they don't know what they're doing, there will be spaghetti somewhere. I'd rather it's in JS than in their controller.
True... they would be writing the same code over and over again. But it's not longer code than giving the attributes for the same thing. Note: I will ignore the "repeatable" thing because I don't think that's something that we will ever need to do. You target a field by name. If you don't know it's name, boo-fucking-hoo, code your own selector 😅 Might change my mind about this if anybody comes up with a real situation where a selector is needed specifically for inside a certain row in a repeatable. But that will be pretty difficult to in a general way, to account for the row but also all field types. Even the PHP syntax will need the JS selector. But again. I don't think that's something we should be worrying about (or providing) from day one. So I'll ignore it. Ok back to how much code they take up:
'onCheck' => function () {
// for single form fields
$this->hideField('field_name');
$this->disableField('field_name');
},
'onUncheck' => function () {
$this->showField('field_name');
$this->enableField('field_name');
},
crud.field('add_extra_box').on("change", function() {
if (this.val == 1) {
crud.field('field_name').hide();
crud.field('field_name').disable();
} else {
crud.field('field_name').show();
crud.field('field_name').enable();
}
});
crud.field('add_extra_box').on("change", function() {
crud.field('field_name').toggleVisibility();
crud.field('field_name').toggleEnabledness(); // horrible name, but you know what I mean
});
Ok sa you said a sensitive word here... "AJAX" 😅 That is out of the question. We are not creating a baby-Livewire, when in half-a-year or a year we'll be providing support for Livewire itself. No freaking way. Sorry to turn down your idea, Pedro. But I do not like it. Let's get back to mine, please 😅 (that's how you make friends, good job @tabacitu 😅👏👏👏) Poke holes at it. Am I missing something? Would it be bad? Why? Would it not cover enough use cases? Would it be difficult to use? Would it be difficult to understand? |
Hi @tabacitu, I was reading this thread and this is really interesting, if we think outside and look at the fields, the first question that comes to my mind is, does the fields API need to be changed or enhanced? does this have anything to do with the fields? well, yes this is related to the fields, but can we if feature versions of backpack introduce another type (ex: groups, or boxes) and we have to support the show/hide/disable/enable, I think this is very possible, and regarding those thoughts, this can cover more than the fields, so what comes to my mind is something like a new wrapper over the UI crud objects that provide those features. This way we can support any field that is currently used by backpack and can ensure evolution. What @pxpm said is very logical, especially the points about keeping backpack simple to use, (after all this is what makes backpack one of the greatest in administration panels) but the missing key here is, how to introduce this type of feature that play in the front, support all fields and feature fields, and it's easy to understand, easy to use, and of course can grow smoothly without affecting core components, the answer that comes to my mind is another type of wrapper filed, it's only job is UI state of what is inside of it, like Flutter with the GestureDetector. So tell me what do you think about this, sometimes I overthink, I hope this is not the case 😅 |
@tabacitu first of all I LOVE when we disagree ❤️ Looking at your example, yeah, dead simple ... Just one or two more rows in JS than PHP but not ugly. I 100% agree that for dead dead simple tasks it would not be much overhead added using plain JS, but can you try with your example get one field in a specific repeatable row ? I will help you out: //if element does not have a custom-selector attribute we use the name attribute
if (typeof element.attr('data-custom-selector') == 'undefined') {
form.find('[name="'+$dependency+'"], [name="'+$dependency+'[]"]').change(function(el) {
$(element.find('option:not([value=""])')).remove();
element.val(null).trigger("change");
});
} else {
// we get the row number and custom selector from where element is called
let rowNumber = element.attr('data-row-number');
let selector = element.attr('data-custom-selector');
// replace in the custom selector string the corresponding row and dependency name to match
selector = selector
.replaceAll('%DEPENDENCY%', $dependency)
.replaceAll('%ROW%', rowNumber);
$(selector).change(function (el) {
$(element.find('option:not([value=""])')).remove();
element.val(null).trigger("change");
});
}
} It's not literally this, but you get the point of how that goes. I was giving some tries hiding some fields etc etc, and I think I got to the point that @soufiene-slimi is refering. Some fields have different wrappers and different artifacts in page that need to be hidden. Hide the labels too ? It does not seem a bad approach at all to have a Thanks @soufiene-slimi very nice idea I guess, need to give it some more thinking. @tabacitu disregard the ajax part, like I said, let's focus on simple tasks (including repeatable as a simple task as is one of our main fields). Currently we already have some kind of toggable functionality: CRUD::addField([
'name' => 'bla',
'dependencies' => ['dep_1','dep_2'],
]); This means that whenever one of those dependencies changes, the value of this field is cleared. What we are trying to do is inverting this dependency, and tell in I don't agree with you that we are writting JS inside controller if we use wrappers. Eg: You are not writting bit code when coding in C, but the compiler will translate what you write into bit code. To sum up the instructions that I think we need to cover 90%++) of the cases:
I can not argue about the time commitment to do this feature beeing higher than allowing people to writte JS in controllers, but I cannot see how this will really help to solve this toggable problem if we follow your route. I can already do what you describe using widgets, for complex scenarios, I don't think there is any benefit added creating an alias for For real real complex scenarios you can add your on JS functions with a Widget, and our "compiler" could also know: Let the house burn! 🔥 🙃 |
Thank you @soufiene-slimi & @pxpm Regarding the wrapper - that's exactly how I think about it too. Very difficult line to walk, in this case 😀 I saw it as a feature of the "wrapper" too. When I said "field" I was actually referring to the field's wrapper div.
Thank you for the example! I don't think the code itself is an argument for PHP API and against JS API:
That being said, I don't see the code as complicated as that. I think in most cases what you'll want is a subfield to show/hide another subfield. So it'd be more like: // the marketing subfield is hidden by default, only show it if the user has agreed to smth else
$("div[data-repeatable-holder=items] input[name$='[i_agree]']").change(function(){
var row = $(this).parent('.repeatable-element');
var marketingSubfield = row.find("name$='[would_you_like_some_spam]'").parent('form-group');
marketingSubfield.toggleClass('d-none');
});
// notice we can:
// - use $= to see which inputs end with [field_name] in order to select subfields, so we can even
// provide a selector for subfields, something like: crud.field('testimonials').subfield('i_agree')
// - use the DOM to our advantage... we don't need to traverse everything, just the current row But of course... for repeatable subfields there are other considerations. Like the fact that... they might not exist on pageload. Which is why I think we should not provide subfield hiding/showing. Nobody asked for this, why are we spending time thinking about it?
Yes. When you hide a field you hide the entire field wrapper. Label, input, everything. Is this questionable in any way? I don't think so... Providing granular control into what you want to hide out of a specific field would be waaaay too much.
Yeah, ok... I misspoke. It's not the same thing. I think that's a very good comparison... it is kind of like writing your own compiler, yes. But I think that only goes to strengthen or clarify my negative opinion on this:
I think you're heavily underestimating how long writing that compiler would take, and how bug-prone it would be.
Hmm... well... it would make it easier. The dev will no longer have to think about:
As a dev, you always do: crud.field('agree').on("change", function() {
crud.field('price').show();
}); doesn't matter the field type, it'd still work. To end the PHP2JS compiler discussion, I have one word: "Livewire" Will the compiler still make sense when you can hide the field using Livewire? No. Then why spend the months to build it? Let's build something that will work cover this need in an elegant way for half a year, maybe a year. It needs to be done in days, not months. |
@pxpm please remember to copy-paste here the examples we created yesterday. |
CRUD::field([
'name' => 'add_extra_cost',
'type' => 'checkbox',
//'default' => 1
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number', // 10
'visibleWhen' => ['add_extra_cost', 'isChecked'], // show when check is true
//'visibleWhen' => ['add_extra_cost', '!==', ''],
//'enabledWhen' => ['add_extra_cost', '===', 1],
'emptyWhen' => ['add_extra_cost', 'isUnchecked']
]);
// ---------------------------------------
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => ['person_type', '===', 'business'], // show when select value is business
]);
// ---------------------------------------
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date'],
],
]);
// ---------------------------------------
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date', 'OR'], // default AND
],
]);
// ---------------------------------------
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date', 'OR'], // default AND
],
]); sources inputs
available methods
syntax[FIELD, OPERATOR, VALUE, PIPE = AND]
|
For sure such (verry missing !) feature will require a Backpack Form Sate JS API and to my mind the closest thing I think about is Drupal Conditional Form Fields. ==> Huge re/work but zero JS line of code for most of case (while keeping possibility to add custom JS, of course). |
It looks very similar to what we have prototyped in our last brainstorm about this. I like it @rroblik. Thanks |
Thank you @pxpm & @rroblik . Pedro - let me rephrase your copy-paste, in a way that would help me remember what we talked about, determine if I still think is a good idea, and walk others through our findings, so they can understand and maybe give feedback. We talked about the solution I proposed above (the JS syntax) as being inconvenient... and maybe not such a good idea after all 😅 Not the syntax itself... but the need to create that JS file or write JS-in-PHP. What would a solution to problem that be? A PHP syntax. Yep, we're back to that 😅 So we said "ok, let's explore what the most common use cases are, to determine if we can actually create a PHP syntax that will cover all of them". Here's our first draft for that. Where?First of all, what items will we need to perform actions on? If you think about it... all Backpack fields store their value in an So yes, any solution will have to take that into consideration. What?What will we need to perform on Backpack fields, most of the time, to cover 90% of the use cases or more? We narrowed it down to these:
How?It looks like the needs above will be covered with a PHP syntax consisting of
In practical terms, it would be something like:
ComplicationsOf course, nothing's that simple. There are also a few complications worth mentioning:
'visibleWhen' => [
['agreed', '==', true, '&&'],
['category', '!=', 33]
] So we ended up with these examples, for the most common scenarios we identified: // *********
// show a second field when a checkbox is checked
// *********
CRUD::field([
'name' => 'add_extra_cost',
'type' => 'checkbox',
//'default' => 1
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number', // 10
'visibleWhen' => ['add_extra_cost', 'isChecked'], // show when check is true
//'visibleWhen' => ['add_extra_cost', '!==', ''],
//'enabledWhen' => ['add_extra_cost', '===', 1],
'emptyWhen' => ['add_extra_cost', 'isUnchecked']
]);
// *********
// show a second field when a first field has a certain value (would work with select, input[type=text], textarea)
// *********
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => ['person_type', '===', 'business'], // show when select value is business
]);
// *********
// show a second field when multiple conditions are met, on different fields
// *********
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['person_type', '===', 'business'], // if a fourth parameter is missing, we assume "AND" / "&&"
['start_date', '>=', 'end_date'],
],
]);
// *********
// show a second field when one of multiple conditions are met, on different fields
// *********
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date', 'OR'], // default AND
],
]);
// Questions here: should the operator be on the FIRST or SECOND condition?! That's it. That's the rephrase. Hope it makes better sense now (to us and to others). I'll type out a second reply here with my thoughts on it. |
PROs - what I like about this PHP solution:
CONs - What I don't like about this PHP solution (and proposed workarounds for some):
CRUD::field([
'name' => 'person_type',
'type' => 'select',
'options' => ['person', 'business'],
]);
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date'],
],
'disableWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date'],
],
]);
// we can find ways around that, like using pipes or ampersants for the attribute name
// (eg. `showWhen|disableWhen` / `showWhen&disableWhen`)
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen&disableWhen' => [
['person_type', '===', 'business'],
['start_date', '>=', 'end_date'],
],
]);
// but that is a code smell if you ask me; it highlights the fact that we're reinventing the wheel here,
// we're inventing an array syntax for something that cannot be completely incapsulated
// in an array syntax - logic; we're inventing unnatural ways around a problem, just like
// we did with isChecked and isUnchecked;
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
['agreed', 'isChecked', 'AND'],
['start_date', '>=', 'end_date', 'AND'],
['end_date', '<=', date('Y-m-d')],
],
]);
// this seems untidy to me... and it related to two other things I don't like about this syntax:
// - the fact that you will have to define an array of arrays;
// - the fact that we have to decide if the AND/OR has to be on the first condition or the second;
// it makes it looks dirty and complicated to me...
// so WHAT IF... instead of requiring an array of arrays... we require one long array?
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [ 'agreed', 'isChecked', 'AND', 'start_date', '>=', 'end_date', 'AND', 'end_date', '<=', date('Y-m-d')],
]);
// which, if you want to split them by lines so it's easier to read, would look like
CRUD::field([
'name' => 'extra_cost_value',
'type' => 'number',
'showWhen' => [
'agreed', 'isChecked', 'AND',
'start_date', '>=', 'end_date', 'AND',
'end_date', '<=', date('Y-m-d')
],
]);
// I think I like this one better... after all... that's how you think of them... a series of conditions...
Bottom line:
Bottom line:
|
Dear @tabacitu I disagree with most of what you said 😀 Features we are talking about here require a JS API to leverage server side to client side rules "conversions". I didn't understand it when I read your comment but maybe I'm wrong? As all fields html syntax is know in advance we can easily manage front behavior server side. Also about CON3 : this JS API must/will be expendable, by design. One other (small) thing you didn't mention is the validation : sometimes a field become mandatory depending on other field value. We can already manage it server side but not client side. hard work in perspective but... with Pro all become possible ! |
😅 That's why we're here, that's why we're talking about it in the open 😅 Thanks for pitching in!
I guess you're right. To make this feature complete, the PHP syntax would ALSO require the JS syntax I mentioned at the top of this issue. That would:
That's the thing:
DAMN IT! See? That's what I was saying... with the new scenarios that will keep popping up. Yes you might want to make something required depending on what's happening in the form. Makes perfect sense. Problem is... on the front-end you can only manipulate the front-end This is food for thought... thank you.
That's the attitude!!! 💪 |
Scenarios so far:
|
Thanks for sum up! SHOULD : ➡️ Same logic as the MUST ones so yes, it's also a MUST ! once again to my mind Drupal way of doing this must be double checked, it's the way to go without reinventing the wheel ! |
Regarding a field becoming mandatory based on other inputs, sometimes you need to duplicate code in both the frontend and backend, whether you're using a framework or not. |
That's my fear too... |
For sure server side validation remain mandatory. In fact with backpack there is not client side validation so this must stay like that 😉. I only speak about mandatory field, add a span for the * must be faisable 😀 |
This is all interesting stuff, I've been following these discussions for a while as it always crops up that I need to show certain fields when certain conditions are met. |
@fronbow Drupal Form API is not for end users but for developers and Drupal admin UI rely on it. Here with backpack no admin ui, only in code possibility ; main reason why we need an real Form API like that in Backpack ! |
I finally finished the reading! It's awesome to read process like that, always constructive. I like a lot of the backend/php solution with the wheres-alike conditions, seem really versatile, from my point of view. I'm not sure I'm adding something constructive, but for theses :
It seems to me like the disable and hiding should be set based on the field's setting. If I try to explain my pov here :
Need more brainstorming : I think hiding the field completely helps the reading and decluttered forms in general. Anyhow, I hoped this makes sense a bit. I might be to late. |
Is there any solution for this after all these discussions? |
@ashek1412 I would say the plan is : #4158 (comment) |
🤨 downvote this one |
It's a real shame that the issue is still not solved after all these discussions. My client bought the Backpack PRO and I can't give him a solution. It's a real shame for the Backapak team. |
@ashek1412 Everything is gonna be OK, you'll find a solution for your problem. Don't push down an entire package on one missing features. That is planned btw. |
however, I have to do it using javascript for the time being. but when you guys put your plan into action? |
Hi, yes please put a plan I to action |
Hi everyone! 2022-03-31.12-47-53.mp4You can show/hide based on Until Backpack provides a solution for this, you can use this code. It uses It's super easy to implement, here is the code;
JavaScriptlet 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);
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;
}
PHPprotected 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;
Example PHPprotected 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',
],
]);
} |
Nice ! Bottom line :
That must stay what you say : a workaround only |
I'm back with good news. Like I said, I think the first steps towards a good solution are:
And the above are... pretty much done! At least the working version, that I can show (v7 in my prototypes though 😅). Check out #4312 - where I've created a small JS library to help devs interact with fields. It's got only ~90 lines of JS, but it covers ALL the scenarios we talked about. I even included the proof 😅 It still needs feedback but... I kind of like it. It's a simple solution to a complicated problem. And I love it when that happens. Let me know what you guys think. |
Nice! I love simple solutions to complicated problems. I'll have to take a look at it later |
I'm going to close this issue, in order to move the conversation to the PR here #4312 🎉🎉🎉 It would be super-difficult for anybody to read all this thread anyway. Thanks A LOT for the feedback everyone! |
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 Laravel-Backpack/addons#11 ... this is a tricky problem to solve... because the developer needs can be so very different. In various places, people ask:
select
, othersradio
,checkbox
,checklist
,select2_from_array
, etc;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
andupdate.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: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:
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: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.
The text was updated successfully, but these errors were encountered: