-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
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
Forms API #1476
Conversation
all forms have titles
this makes it completely clear what both of these do, and also makes it clear that buttons can't be put on custom forms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There seems to be an inconsistency, where calling getters of form input without form submission sometimes gets a null
but sometimes gets an InvalidStateException
.
Throwing an exception may also result in some dependence (the assumption that "if I don't get this exception, I am OK"), which does not work as expected when someone tries to get the value from a form they resent but is not yet resubmitted.
// I have not reviewed the code under pocketmine\form\element
yet.
src/pocketmine/form/MenuForm.php
Outdated
} | ||
|
||
/** | ||
* Returns the index of the option selected by the user. Pass this to {@link getButton} to get the button object |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Dead link to getButton
. I believe this should be Pass this to {@link MenuForm::getOption} to get the MenuOption object
.
src/pocketmine/form/MenuForm.php
Outdated
public function getSelectedOption() : MenuOption{ | ||
$index = $this->getSelectedOptionIndex(); | ||
if($index === null){ | ||
throw new \InvalidStateException("No option selected, maybe the form hasn't been submitted yet"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider changing the message to Form is closed or has not been submitted yet
to be more precise.
* Sets the selected option to the specified index or null. null = no selection. | ||
* @param int $option | ||
*/ | ||
public function setSelectedOptionIndex(int $option) : void{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a practical reason to expose this method publicly?
* | ||
* @param bool $choice | ||
*/ | ||
public function setChoice(bool $choice) : void{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason to expose this method publicly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Plugins may want to modify form result data when it's submitted (there will be events for this)
/** | ||
* Clears response data from a form, useful if you want to reuse the same form object several times. | ||
*/ | ||
public function clearResponseData() : void{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is very confusing. It is not called by the server, but relies on the developer to call it.
This leads to inconsistency:
- Is a Form object expected to be reused for a single player?
- Is a Form object expected to be reused for multiple players?
- Can the developer use the form values outside onSubmit()?
The answer is:
- If the same Form instance is only sent once, you can use the form values outside onSubmit() and hold the Form instance forever.
- If the same Form instance is sent more than once (same player or not), it would be a very bad practice to depend on the value held in the Form instance outside onSubmit(), even in the same tick.
It is particularly tempting to hold a CustomForm instance and use the values there later, since the data cannot be easily extracted. I can foresee this is going to cause a lot of concurrency bugs that can be very difficult to identify. Therefore, it is suggested that, starting from the core, one of either approach ("resend same instance" or "new instance each resend") be adopted to avoid confusion.
- Adopting the "resend same instance" approach should automatically call the
clearResponseData()
method after each onSubmit() call. - Adopting the "new instance each resend" approach should remove this method and add a flag to identify a Form as sent and throw an exception when attempting to use sendForm() with it.
Deciding which approach to adopt should require extensive consultation.
return $json; | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about also adding an empty public function onSelected()
, which is called before/after MenuForm::onSubmit()
is called? This can allow developers to write their handlers in the menu options instead of from the big MenuForm object, resulting in extra routing work. Without adding this in the core, developers have to add a few lines of routing code + assertion that the MenuOption::onSelected()
method exists in order to put the handler code with the MenuOption.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
before, or after? If a developer does it themselves, they are explicit about when they want it to be called, which can be any time - if the core does it, then developers have to get used to BEFORE, or AFTER, no in-betweens.
* Represents a custom form which can be shown in the Settings menu on the client. This is exactly the same as a regular | ||
* CustomForm, except that this type can also have an icon which can be shown on the settings section button. | ||
*/ | ||
class ServerSettingsForm extends CustomForm{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All other Form classes are abstract. Why is this suddenly non-abstract?
* @param string[] $steps | ||
* @param int $defaultStepIndex | ||
*/ | ||
public function __construct(string $text, array $steps, int $defaultStepIndex = 0){ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Calling it Steps makes it confusing - If possible, rename to $options. A dropdown is a list of options that you can choose from, not a slider
src/pocketmine/Player.php
Outdated
@@ -3279,6 +3279,11 @@ public function sendWhisper(string $sender, string $message){ | |||
} | |||
|
|||
public function sendForm(Form $form) : void{ | |||
if($form->hasBeenSent()){ | |||
throw new \InvalidArgumentException("Cannot send the same form more than once, create a new one instead"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But: Why? So i can not cache a Form and send it to a player? So it has to be recreated (rebuildt in the core) every time? Isn't that just a waste of calculation if i have a form with never changing contents?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The core code has no foolproof way to tell if your form is immutable (so to speak). You can clone the form object if you're concerned about the overhead.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have already explained in my previous comment:
It is particularly tempting to hold a CustomForm instance and use the values there later, since the data cannot be easily extracted. I can foresee this is going to cause a lot of concurrency bugs that can be very difficult to identify. Therefore, it is suggested that, starting from the core, one of either approach ("resend same instance" or "new instance each resend") be adopted to avoid confusion.
- Adopting the "resend same instance" approach should automatically call the clearResponseData() method after each onSubmit() call.
- Adopting the "new instance each resend" approach should remove this method and add a flag to identify a Form as sent and throw an exception when attempting to use sendForm() with it.
As it has been noticed that resetting contents each time makes it very inconvenient to retain data (especially inconvenient for custom form data), the "new instance each resend" approach is considered more appropriate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Regarding the overhead of form creation overhead, perhaps you should compare it against encoding the form, sending it to the client, making the client display it and sending it back. As one wouldn't reasonably send forms very frequently, the impact on performance is negligible, so trying to cache forms for the sake of performance would be premature optimization, and therefore your comment is 4√(all evil), and so you are 8√(all evil).
"Offtoppic" question: Could you merge the changes from master into the branch / update it? |
abstract class ModalForm extends Form{ | ||
|
||
/** @var string */ | ||
private $content; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please concider adding a function to get/set the content
how do you make a player open a form when they hold click a specific block |
Please, for questions head over to the Forums. This is a place to discuss the changes and issues, not some random guy's issues about not reading the docs / commits |
I believe image handling on UI has been added, correct me if I'm wrong. If so, I suggest adding an image class. No unnecessary replies required. A yes or no would suffice. |
Have eyes been added yet, if so, they should be used. |
wishes github had a facepalm reaction |
Aside from the bugs, some things we're not happy with yet:
$player->sendForm(new class($this, $username, $error) extends ModalForm{
/** @var Main */
private $plugin;
public function __construct(Main $plugin, string $username, string $error){
$this->plugin = $plugin;
parent::__construct("Account transfer failed!", "Failed to transfer \"$username\" account to yours.\nError: $error\n\nWould you like to try again?");
}
public function onSubmit(Player $player) : ?Form{
if($this->getChoice()){
$this->plugin->requestAccountDetails($player);
}else{
$this->plugin->warnCancelTransfer($player);
}
return null;
}
}); requires that the form being sent must declare a |
Actually that applies to any parameter. Passing a variable that a player input into a form and displaying it in another form requires constructor rewrites. I suggest an additional parameter |
@thebigsmileXD Explicitly requiring passing the Plugin also has the advantage that it's possible to know the scope of the form and manage it with plugin enable/disable stuff. |
Why don't make some events like public function onSubmit(PlayerFormSubmitEvent $e){
// assuming we are handling a Toggle element in index 0 that enables something
/** @var Toggle $toggle */
$toggle = $e->getForm()->getElement(0);
$this->doSomethingToPlayer($e->getPlayer(), $toggle->getValue());
$e->getPlayer()->sendForm(new CustomForm("Test Form", array(
new Toggle("Test toggle 1", false),
new Toggle("Test toggle 2", true)
)));
} will be MUCH simpler to implement for plugin developers as we don't have to pass the owner to our form to do things on public function onSubmit(PlayerFormSubmitEvent $e){
// first we want to make sure the form we are receiving is a "TestToggleForm1" form
if($e->getForm()->getInternalName() !== "TestToggleForm1") return;
// then we manage to do something...
// assuming we are handling a Toggle element in index 0 that enables something
/** @var Toggle $toggle */
$toggle = $e->getForm()->getElement(0);
$this->doSomethingToPlayer($e->getPlayer(), $toggle->getValue());
// maybe the constructor for a CustomForm object can be
// "string $internalName, string $title, array $elements"
$e->getPlayer()->sendForm(new CustomForm("TestToggleForm2", "Test Form 2", array(
new Toggle("Test toggle 1", false),
new Toggle("Test toggle 2", true)
)));
} Obviously this is more straight-forward but it will certainly have problems in more complicated plugins that use forms to do complicated things but I wrote this suggestion in a couple of minutes so it definitely needs some refining here and there but the main idea is there. |
@AryToNeX This will certainly make it much harder to pass extra data through the form. |
@AryToNeX I highly say NO THANKS to events. customui was discontinued just because events created plugin-crossover calls, and every plugin reacted to the event. |
Events are a reasonable ask (since they are just, well, events) but definitely a bad idea to make them the primary way to handle a form. public function onSubmit(PlayerFormSubmitEvent $e){
// first we want to make sure the form we are receiving is a "TestToggleForm1" form
if($e->getForm()->getInternalName() !== "TestToggleForm1") return;
... here's the problem already. |
Not meant to be rude, but, can we get this merged by New Year? |
Not meant to be rude, but this PR make teaspoon plugin error |
Plugins should adapt to the core, not the other way around. Furthermore, this is a development branch. |
@WaldoFS that's entirely irrelevant to the discussion. TeaSpoon is its own project. |
@WaldoFS Not meant to be rude, plugin are suppose to make themself work with the core, not the other way around |
Ok, thanks all 😘 |
I tested @thebigsmileXD 's WarpUI with this branch, but it didn't work properly.
@dktapps This has been W.I.P For almost THREE months. When will it be done? |
@GooglePlugin When somebody got time for it. |
How do you change the ‘Submit’ button to other text? |
This is not a plugin support forum |
you must be new round here... |
I haven't fully understand the rule here. Not meant to be rude, but I really look forward to this ;) |
It appears no constructive discussion is going on in this pull request, and therefore I am locking. If you have any further comments, please create a thread on the forums. |
I'm going to close this since there hasn't been any movement on it recently. A new PR will be created when we have something worth shipping. |
No description provided.