Skip to content
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

Autocomplete Field #243

Closed
romaninsh opened this issue Oct 18, 2017 · 8 comments
Closed

Autocomplete Field #243

romaninsh opened this issue Oct 18, 2017 · 8 comments
Labels
hangout agenda 🔈 Will discuss on next hangout

Comments

@romaninsh
Copy link
Member

romaninsh commented Oct 18, 2017

Our standard field for relations (hasOne) has been a dropdown. This is limiting because it must load all the options right away.

Autocomplete field is always in a very high demand, so this ticket specifies how it should be created.

Semantics

FormField\AutoComplete should either extend \FormField\Dropdown or Input\Generic, whichever suits. Adding it to the form should be possible like this:

$form->setModel('Order', false);
$form->addField('client_id', ['AutoComplete']);

If form does not use model, it should still be possible to use AutoComplete.

$form->addField('country_id', ['AutoComplete', 'model'=>new Country($db)]);

It's important that AutoComplete work only with Models (no raw data sources) because it must use call-back for loading data.

Look and Feel

We would like to use a standard field, to keep it consistent with the rest of the form. User should still be able to specify an icon, like with regular input fields:

screen shot 2017-10-18 at 15 10 19

When typing entry, we can use search() api to perform back-end query using URL provided by a Callback class. The results should be limited to 50 records ($this->recordLimit).

Matching search should be performed against $title column of the model by using ($m->title, 'like', '%'.$q.'%);, then calling setLimit() and export([$m->id_field, $m->title_field]);

The response JSON should be structured like this potentially allowing more fields:

[
 { "id": 35, "name": "test" }
]

Add button

A must-have feature is ability to add new record through a Modal dialog. To enable, user should pass extra argument:

$form->addField('country_id', ['AutoComplete', 'plus'=>true, 'model'=>new Country($db)]);

This would add a button to the right of the field with a plus on it. User should also be able to specify a custom icon. I recommend if the custom icon is specified, we would combine it with a "corner plus" icon. https://semantic-ui.com/elements/icon.html#/definition - see corner icons. (Optional)

Add button should open Modal window referencing it's own call-back code with a form. Upon form submission, window should close, notification should be displayed, that new client is added and the opening field should automatically select new record.

Stability

It should be possible to have multiple AutoComplete buttons on a form, without clashing.

@DarkSide666
Copy link
Member

DarkSide666 commented Oct 18, 2017

See in smbo project:

  • Form_Field_Semantic_DropdownMulti
  • Form_Field_Semantic_DropdownSingle

classes for example.

Also this nasty issue of Semantic Semantic-Org/Semantic-UI#2072 and solution/workaround which works in smbo in practice: Semantic-Org/Semantic-UI#2072 (comment)

@ibelar
Copy link
Contributor

ibelar commented Oct 18, 2017

Start taking a look at dropdown from semantic ui and did a bit of testing.

Here what I find out so far

The HTML structure generate for this input field should look like this:

<div id="input_id" class="ui search selection dropdown">
    <input class="search"></input>
</div>

Note: the class name are important, even for the Input element using 'search', when calling $('#input_id').dropdown() for autocomplete to work. Otherwise, you just get a regular dropdown.

Here is the structure of data that the dropdown will accept:

{
 "success":true,
 "results":[
      {"name" : "data1", "id" : "1"},
      {"name" : "data2", "id" : "2"},
      {"name" : "data3", "id" : "3"}
      ]
}

Note that the fields structure must be pass in as the 'Fields' option when calling $.dropdown()

$this->jsInput(true)->dropdown([
            'fields'      => ['name' => 'name', 'value'=>'id'],
            'apiSettings' => [
                'mockResponse' => [
                    'success' => true,
                    'results' => [
                        ['name' => 'data1', 'id' => '1'],
                        ['name' => 'data2', 'id' => '2'],
                        ['name' => 'data3', 'id' => '3'],
                    ]
                ],
            ]
        ]);

mockResponse can be used instead of an url in apiSettings to simply test the dropdown without calling the server.

In real scenario, replace mockResponse with url in apiSettings like this:

apiSettings: {
      // this url parses query server side and returns filtered results
      url: '//api.semantic-ui.com/tags/{query}'
    },

@romaninsh, @DarkSide666 - That should be the basis of the AutoComplete. Not sure yet about how to implement this to generate proper HTML into the page and properly generate the callback to return the json results. Would you take care of that part? I can make sure js will work fine from there.

@romaninsh
Copy link
Member Author

Not clear what's needed. Isn't search() taking care of that already? Why does it have to generate HTML?

other than that, if you can provide a reference HTML + JS, I can probably get it sorted inside PHP.

@ibelar
Copy link
Contributor

ibelar commented Oct 19, 2017

From what I understand, semantic ui dropdown module is more appropriate then search module to achieve what you need.

Basically, the AutoComplete field class should render html like describe above, so when executing the js action for the dropdown, semantic will know what to do base on the html attribute class value.

So hypothetically, the AutoComplete.php class would look like this:

<?php
namespace atk4\ui\FormField;
use atk4\ui\Form;

class AutoComplete extends Input
{
    public $defaultTemplate = 'formfield/autocomplete.html';
    public $ui = 'ui search selection dropdown';
    public function init()
    {
        parent::init();

       $this->jsInput(true)->dropdown([
            'fields'      => ['name' => 'name', 'value'=>'id'],
              //setup url that will generate json data.
            'apiSettings' => ['url' => $this->getCallbackUrl()],
    ]);
}

Then the autocomplete.html template file would look like this:

 <div id="{$_id}" class="{_ui}ui{/} {$class} {$_class}">
    <input class="search"></input>
</div>

@romaninsh
Copy link
Member Author

yes, that's about right 👍

@DarkSide666
Copy link
Member

Just for reference this is how I implemented that in smbo project. That's definitely not perfect, but it works (somehow) :)

<?php
/**
 * Dropdown form field for single select by Imants.
 * Uses Semantic-UI dropdown.
 *
 * @TODO Semantic UI don't allow to unselect value :(
 *       https://github.com/Semantic-Org/Semantic-UI/issues/2072
 * Workaround #1: https://jsfiddle.net/xu4fv5n0/2/ - see slightly improved version below
 * Workaround #2: https://jsfiddle.net/hghkwkgv/6/
 */
class Form_Field_Semantic_DropdownSingle extends Form_Field_Dropdown
{
    public function getInput($attr=[])
    {
        $this->setProperty('class','ui search selection dropdown'); //add small?
        $this->setProperty('name', $this->name);
        $this->js(true)->_v3()->dropdown([
            'placeholder' => 'auto',
            'forceSelection' => true, // if you find tab annoying, then put false here
            'match' => 'text',
            'fullTextSearch' => 'exact',

            // workaround for semantic ui issue mentioned above
            'onChange' => $this->js(null,'function(value) {
                var target = jQuery(this).dropdown();
                if (value!="") {
                    target.find(".dropdown.icon").removeClass("dropdown").addClass("delete").on("click", function() {
                        target.dropdown("clear");
                        jQuery(this).removeClass("delete").addClass("dropdown");
                        return false;
                    });
                }
            }'),
        ]);

        // workaround to force onChange event on initialization
        $this->js(true)->_v3()
            ->closest('.ui.selection')->find('.item.active')->addClass('qwerty')->end()
            ->dropdown('clear')
            ->find('.qwerty')->removeClass('qwerty')
            ->trigger('click');

        return parent::getInput($attr);
    }

    public function getOption($value)
    {
        return $this->getTag('option', [
            'value'     => $value,
            'selected'  => $this->value && $value == $this->value,
        ]);
    }
}


<?php
/**
 * Dropdown form field for multi select by Imants.
 * Uses Semantic-UI dropdown.
 */
class Form_Field_Semantic_DropdownMulti extends Form_Field_Dropdown
{
    public function getInput($attr=[])
    {
        $this->setProperty('multiple',true); // This is main difference from single dropdown/autocomplete
        $this->setProperty('class','ui search selection dropdown'); // add small?
        $this->setProperty('name', $this->name.'[]');
        $this->js(true)->_v3()->dropdown([
            'placeholder' => 'auto',
            'forceSelection' => true, // if you find tab annoying, then put false here
            'match' => 'text',
            'fullTextSearch' => 'exact',
        ]);

        return parent::getInput($attr);
    }

    public function getOption($value)
    {
        return $this->getTag('option', [
            'value'     => $value,
            'selected'  => $this->value && in_array($value, $this->value),
        ]);
    }
}

@ibelar
Copy link
Contributor

ibelar commented Oct 20, 2017

Thanks @DarkSide666 ;

I have manage to make it works in FormField/AutoComplete.php in branch feature/autocomplete-field.
Note: having an issue with icon before....but field label are working fine.

Although it is now using mock data, what is needed to do now is too pass in an url instead of mock data.

But I am a bit mix up on how to implement that within the field. I am guessing a callback url but how do you connect that to the field model so it return json from it....

@PhilippGrashoff
Copy link
Collaborator

As we have the Lookup FormField doing this nicely, can we close here?

@PhilippGrashoff PhilippGrashoff added the hangout agenda 🔈 Will discuss on next hangout label Aug 22, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
hangout agenda 🔈 Will discuss on next hangout
Projects
None yet
Development

No branches or pull requests

5 participants