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

Adding support for One-to-One relations #133

Closed
javiereguiluz opened this issue Feb 17, 2015 · 14 comments
Closed

Adding support for One-to-One relations #133

javiereguiluz opened this issue Feb 17, 2015 · 14 comments
Labels

Comments

@javiereguiluz
Copy link
Collaborator

(This issue is part of this meta issue which will add full support for all kinds of Doctrine relations)

The current situation

Support for one-to-one relations is half-finished:

List action

If the related entity is managed by EasyAdmin, the list action displays it as a link to the show action of that related Entity. Otherwise, it's displayed just as a regular text. This feature requires that the related entity defines the __toString() method.

one-to-one-relation-list-action

Show action

Identical to list action: the related Entity is linked or not depending upon it's managed by EasyAdmin or not.

one-to-one-relation-show-action

Edit action

The related entity is transformed into a <select> list:

one-to-one-relation-edit-action

When there are up to hundreds of items, the <select> displays them all:

one-to-one-relation-edit-action-revealed

When there are thousands, the <select> just displays some of them (the most recently inserted items).

New action

Same as the edit action:

one-to-one-relation-new-action

What I want to do

  • List action: I'm happy with the current behavior.
  • Show action: I'm happy with the current behavior.
  • Edit and New action: the current behavior is ridiculous. I want to show the related entity using the spectacular selectize.js jQuery plugin and provide AJAX autocomplete.

My questions to the community

If you like EasyAdminBundle and want to help improve it, please help me discussing this issue before starting to develop its code. Specifically, I'd like to ask the following questions:

  • For the Show action, would you like to display the related entity data? (Inline or in another tab inside the same page). Or is the link convenient enough?
  • For the Edit and New actions, do you know any better alternative than provide autocompleting <select>? If not, do you have any clue about how to do that with Doctrine + Form component + selectize.js plugin?

Thank you all for your help.

@Pierstoval
Copy link
Contributor

For the Edit and New actions, the jQuery plugin you mention sounds great, even if I'd prefer vanilla js. The advantage of this plugin is that it allows the same usage for *ToMany relations, which would be incredibly awesome to handle easily these relations in EasyAdmin.

Using the Form component is great too, because it allows the persistence of a brand new object, which I'm gonna need soon for a project in which there'll be entities that only are in the middle of a "ManyToMany with attribute" relation.

Combining the two options does not seem to be a bad idea though it's gonna be harder to implement.

Why don't allow the developer to set up this parameter in the entity configuration ? This would allow to use both systems, with a simple "if" statement in the controller.

What do you think ?

@ogizanagi
Copy link
Contributor

@Pierstoval :

Using the Form component is great too, because it allows the persistence of a brand new object, which I'm go ...

I think you're talking about embedded forms, right ? This was discussed in #109 and the approach @javiereguiluz suggested in the configuration seems good to me.

@javiereguiluz : I don't know the selectize plugin, but I'm more used to the very good select2 or even chosen ones which provide way more examples. But as I never used selectize, I cannot talk about the few advantages it features.
As @Pierstoval said, those plugins are also useful for other relations types. It will be great to include them ! 😃

The Show action seems good enough for the purposes.

@Pierstoval
Copy link
Contributor

@ogizanagi Yep I'm talking about embedded forms.

I think that the property config should be prototyped as said in #109 by @javiereguiluz , I mean this:

SocialNetWork:
    class: AppBundle\Entity\SocialNetwork
    form:
        fields: ['name', 'link', { property: 'logo', embedded: true }]

but by changing the embedded parameter into something more verbose.

I propose the use of 4 choices:

  • list : A <select> box with all entities listed in it:
    Pros: Already implemented, easiest way to do it, and can be used for *ToMany by setting the multiple attribute to "true" and managing multiple objects instead of only one.
    Cons: Problem if you have a lot of elements.
  • inputs : An <input type="radio" /> collection, with a "none" element if the property is nullable.
    Pros: Prettiest and fastest way to choose an element, very good when you have, for example, less than 5 elements, and can be changed to checkbox for *ToMany relations.
    Cons: Only for small collections of elements.
  • selectize (or other plugin name) : An <input> handled by some javascript/jQuery plugins, like selectize or anything else.
    Pros: Standardized for both *ToOne and *ToMany relations, because the plugin certainly allows the maximum number of elements inside the collection, and allows a research to be done.
    Cons: Can be annoying when you need to search an element quickly by its words and you have many elements that look like each other. It also requires a proper webservice to retrieve datas, which is more development to do. (By the way, I've created a webservice generator based on entity names, maybe we could reuse some code inside ?)
  • form : An embedded form, created dynamically either from another managed entity's field configuration (which can be hard to implement, but not impossible, if we create a dedicated service for this feature) or directly from a FormType.
    Pros: Allows persistence of new elements directly in the page.
    Cons: Not easily implementable for *ToMany relations, because it would need many forms, which is quite hard to setup in the Symfony Form component (but it's possible to do it). Also requires a great knowledge of embedded forms, which is not really common.

I hope to have been the most objective and open-minded I could about this subject :)

To conclude, this would allow us to directly set up the "relation render" in EasyAdmin by changing a potential render parameter in the configuration.

Example:

Website:
    class: AppBundle\Entity\Website
    form:
        fields: ['name', 'host', { property: 'headerLogo', render: 'form' }]
Pages:
    class: AppBundle\Entity\Page
    form:
        fields: ['name', 'content', 'slug', { property: 'category', render: 'inputs' } ]
Categories:
    class: AppBundle\Entity\Category
    form:
        fields: ['name', 'slug', { property: 'parent', render: 'list' } ]
'Blog articles':
    class: AppBundle\Entity\Post
    form:
        fields: ['name', 'content', 'slug', { property: 'medias', render: 'selectize' } ]

What do you think ?

@ogizanagi
Copy link
Contributor

Indeed, quite a good idea.
A form type guesser might be useful in order to choose the best input to suit according to the potential number of elements if nothing is set in the render option.

E.g:

  • 5 items in the database => radio / checkboxes
  • more than 5 items => selectize or another js plugin with AJAX autocomplete.

This will require to perform a request in order to get the amount of items in the database, but I don't think it will be a real issue.

Do you think this is relevant ?

@Pierstoval
Copy link
Contributor

I do, and that's precisely the behavior I imagined for setting a null value in the render option, or simply not setting it: automatic selection of the rendering mode depending on the relation and the number of elements in the associated collection :)

You're talking about the amout of items in the database, but in fact, we almost always need to retrieve the whole collection in the database. Pagination does not seem to be a good option in that case, so we need all of them. And with a simple count($collection); we get the amount of items without making any other query than the findAll() one.

The only exception is when using a selectize-like plugin, because we do not officially need to retrieve the collection, as it has to be handled by an API. A good option is to handle it only once, and fetch the objects in a javascript object. This allows faster usage in the autocompletion.

@ogizanagi
Copy link
Contributor

I was not talking about pagination at all ^^. But using selectize (or select2 as I know it better) we can use AJAX requests to cleverly load entities within a search. (see "Loading remote data"). But this might not be suitable for our needs, as we will need to set one or more properties to search against in the configuration. So it cannot easily be guessed.

If loading the whole collection, using count($collection) is indeed the easiest way. But I fear for the performances on applications with very large databases...
But if we do, no need to use an AJAX request at all. Simply serializing the collection to json using the __toString() method as label and passing it to the view should do the trick.

Or...we can in fact use pagination (but I was not talking about that before ! 😸). We can load first a reasonable amount of the latest entities. Then, if the user doesn't find the one he needs from the list, click on a "load more" button (through AJAX).

@Pierstoval
Copy link
Contributor

In fact, I was talking about pagination as a matter of demonstration that we need the whole collection, which can be a simple array ^^.

You're totally right about very large databases, but there are not many solutions.

Maybe we could check at first how many elements there is in the database, in order to pick a strategy ? But that sounds very heavy...

By the way, for me the best possibility is to retrieve all elements in a JS object with a simple key=>value pair corresponding to primaryKey=>__toString() result. And for this, we must properly create a webservice. And this webservice cannot be optimized, because we cannot tell Doctrine to retrieve only "some" fields, as the __toString() method can use any field...

Brainsplosion

@ogizanagi
Copy link
Contributor

Brainsplosion

😅

Maybe we could check at first how many elements there is in the database, in order to pick a strategy ? But that sounds very heavy...

Exactly. A COUNT query might not be so heavy and cached. Keep in mind that this behavior will only happen if nothing is set for the render value. The developer is most likely to know what choice will be the best for each form fields in his application.

the best possibility is to retrieve all elements in a JS object with a simple key=>value pair corresponding to primaryKey=>__toString()

Exactly for the format. It will allow user to search for an entity against the __toString() method, which sound the best.

And for this, we must properly create a webservice.

No. I don't understand the need to create a webservice called only once & used only for this purpose if we can just pass data to the form view.

we cannot tell Doctrine to retrieve only "some" fields, as the __toString() method can use any field

Yep... that's why retrieving the whole collection should be limited according to the real amount of entities in the database, and IMO the best strategy for a selectize or another plugin will be to use pagination to load more on demand (then a webservice will be useful).

@Pierstoval
Copy link
Contributor

So there's only two choices left for the selectize-like plugin:

  • Use a webservice to retrieve datas on each modification in the search input, but it'll be slower to search for elements.
  • Inject datas directly in a JSON object in the view, for it to be handled directly with javascript for (theoretical?) faster performances, but taking the risk to overload the browser with huge amount of datas.

What do you think ?

@ogizanagi
Copy link
Contributor

So there's only two choices left for the selectize-like plugin:

three

  • Load only a reasonable amount of data from a webservice (the 100 latest entities for example). Load more on demand.

@Pierstoval
Copy link
Contributor

Hmm, indeed, this is another good option :)

@sr972
Copy link
Contributor

sr972 commented Mar 2, 2015

Load only a reasonable amount of data from a webservice (the 100 latest entities for example). Load more on demand.

Even it's another JS Plugin then selectize, i want to bring datatables into the game for this kind of lazy loading. Source: http://www.datatables.net/

It can preload some amount of data and you're able to load other things by ajax calls.
Examples: http://www.datatables.net/examples/server_side/pipeline.html

I know it would be a load of work to do, but server-side processing + lazy loading could be worth the time.

@diegocastro
Copy link

Inject datas directly in a JSON object in the view, for it to be handled directly with javascript for (theoretical?) faster performances, but taking the risk to overload the browser with huge amount of datas.

Have a config value with a limit on how many entries to show, if not set show all entries.

@Pierstoval
Copy link
Contributor

Maybe we could take profit of the search action, for it to return a serialized JSON collection when the request is an AJAX one.
This way, there is no more method to create, and a simple ajax plugin is sufficient.

But that said, I think we should implement selectizer-like plugin as default, and implement all others solutions as possible ones for each entity. As usual, it should be "globalable", but the entity has priority.

My conclusion is still the same as in this comment, and we can develop all solutions, it just needs some time, and a proper "let's go" from you @javiereguiluz for each solution 😃

Can we conclude a decision on this, even if we don't work on the development yet?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants