iamluc / GloomyPagerBundle

This pager/datagrid/crud has advanced filtering & sorting (Array & Entity/QueryBuilder) in addition of pagination
MIT License
10 stars 4 forks source link

Detailed documentation #3

Open pmithrandir opened 11 years ago

pmithrandir commented 11 years ago

Hello,

I'm trying to discover your Bundle that seems to do just what I need.

I read the documentation, but I think you could improve it by giving more example.

What I'm looking for :

"An exception has been thrown during the rendering of a template ("Notice: Undefined index: slug in ...."

I think the Bundle seems promising, but this lack of documentation create difficulties that could be easily passed with your help.

Also, I think your demo website is down.

Please receive my thanks for your module. If you find some time to help me, it would be even greater !!!

Pierre

pmithrandir commented 11 years ago

By the way, I don't think filtering works on foreign key. Could you confirm ?

Thanks, Pierre

iamluc commented 11 years ago

Hi Pierre,

I know this bundle lacks of documentation... but help is welcome :-)

I will try to answer all of yours questions. First, here is an exemple to use with AJAX

<?php
//...
class DefaultController extends Controller
{
    /**
     * @Route("/exemple1")
     * @Template()
     */
    public function indexAction()
    {
         $colors = array(
            array('id' => 1, 'color' => 'blue'),
            array('id' => 2, 'color' => 'red'),
            array('id' => 3, 'color' => 'orange'),
            array('id' => 4, 'color' => 'black'),
            array('id' => 5, 'color' => 'green'),
            array('id' => 6, 'color' => 'yellow'),
            array('id' => 7, 'color' => 'grey'),
            array('id' => 8, 'color' => 'white'),
            array('id' => 9, 'color' => 'pink'),
            array('id' => 10, 'color' => 'wood'),
            array('id' => 11, 'color' => 'maroon'),
            );

        return array('datagrid' => $this->get('gloomy.datagrid')->factory($colors));
    }
//...

{% extends "::base.html.twig" %}

{% block stylesheets %}
    {{ parent() }}
    {{ datagrid_stylesheets(datagrid) }}
{% endblock %}

{% block javascripts %}
    {{ parent() }}
    {{ datagrid_javascripts(datagrid) }}

    <script src="/bundles/gloomypager/js/gloomy-utils.js" type="text/javascript"></script>

    <script language="javascript">
        updateDatagrid = function(url, data) {
            gloomyAjaxUpdater(url, '#datagrid', {spinner: '#loading', type: 'post', data: data, onsuccess: function() { bindPager() }});
        }

        bindPager = function() {
            $('#datagrid form').on(
                'submit',
                function(event) {
                    event.preventDefault();
                    updateDatagrid($(this).attr('action'), $(this).serializeArray());
                }
            );

            $('#datagrid a:not(.outbound, [href^="#"])').on(
                'click',
                function(event) {
                    event.preventDefault();
                    updateDatagrid(this.href);
                }
            );
        }

        jQuery(document).ready(function() {
            bindPager();
        })
    </script>
{% endblock %}

{% block body %}
    <div id="datagrid">
        <div id="loading" style="color: red; display: none;">
            Loading...
        </div>

        {{ datagrid_content(datagrid) }}
    </div>
{% endblock %}

I hope it helps !

pmithrandir commented 11 years ago

Hi,

I forgot to thank you for you example. I will try to put it in place next week end.

Regarding the 3 lasts questions, that are actually more emergency for me, do you have any answer / tip for me please ?

Thanks Pierre

PS : I will try to get some time to rephrase the documentation if I think I can addd something relevant / generic to add. I'm kind of busy on my project, but I will try.

iamluc commented 11 years ago

I wrote 2 exemples in the documentation :

iamluc commented 11 years ago

I just added an exemple with a QueryBuilder in the documentation.

Tell me if you need something else :-)

pmithrandir commented 11 years ago

Hello,

I tried to apply your advices today.

I have a question about my code... I managed to dispay a datagrid with a filter on a foregign key value(the theme).When I select one value, the page reload, and the value is kept.

But, the filter doesn't work.

Do I have to do something special in my controler ?

Thanks,

My twig :

{% block datagrid_column_filter__theme %}
    <th>
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[f][theme]" value="theme" />
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[o][theme]" value="equals" />

        <select name="{{ datagrid.pager.getConfig('filtersVar') }}[v][id]" onchange="this.form.submit();">
            <option value="">-- Thème --</option>
            {% for row in field.getOption('select_options') %}
                <option value="{{ row.id }}" {% if datagrid.pager.getValue('filtersVar').v['id']|default('') == row.id %}selected="selected"{% endif %}>{{ row.nom }}</option>
            {% endfor %}
        </select>
    </th>
{% endblock %}

My controller :

    public function listAction()
    {

        $idees = $this->getDoctrine()->getEntityManager()
                ->getRepository('MyBundle:Idee')->getLatestIdeesQb()
                ->addSelect('t')->leftJoin('i.theme', 't')
                ;

        $wrapper = new QueryBuilderWrapper($idees);
        $wrapper
            ->addField(new Field('theme.nom', 'string', 'Thème', 't.nom', array('tree' => true)), 'theme')
        ;
        $wrapper->setOrderBy(array('updated_at' => 'desc'));
        $datagrid = $this->get('gloomy.datagrid')->factory($wrapper);
        $datagrid->showOnly(
            array(
                'title', 
                'description', 
                'created_at', 
                'theme'
                )
            );

        $themes = $this->getDoctrine()->getEntityManager()->getRepository('MyBundle:Theme')->findAll();
        $datagrid->getField('theme')->addOption('select_options', $themes);

        return array('datagrid' => $datagrid);

    }

Also, the created_at column containing a date is always empty, any clue ?

Thanks for your help and the code update,

Pierre

pmithrandir commented 11 years ago

Hello,

I found answers to my questions : the code for the filter is that :

{% block datagrid_column_filter__theme %}
    <th>
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[f][theme]" value="theme" />
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[o][theme]" value="equals" />

        <select name="{{ datagrid.pager.getConfig('filtersVar') }}[v][theme]" onchange="this.form.submit();">
            <option value="">-- Thème --</option>
            {% for row in field.getOption('select_options') %}
                <option value="{{ row.id }}" {% if datagrid.pager.getValue('filtersVar').v['theme']|default('') == row.id %}selected="selected"{% endif %}>{{ row.nom }}</option>
            {% endfor %}
        </select>
    </th>
{% endblock %}

For the date, I think I found a bug in the code. My field is named "created_at", which need qualifier : i.created_at, but property createdAt. The property by default is wrong, and the only way I found to correct it was to add a new setter in the Field class to redefine the property. It's just a temporary solution, as I had to change your code.

Do you think we could resolv the bug, or at least to provide both missing setters for qualifier and property ? I don't see any probleme to allow the usser to change these values.

    public function setProperty($property)
    {
        $this->_property = $property;
        return $this;
    }
    public function setQualifier($qualifier)
    {
        $this->_qualifier = $qualifier;
        return $this;
    }

To come back to the main subject, I think what I missed the most was a description in the Field class of all variable. "What is a qualifier or a property for you". A quick description in the header of the class would be a great improvment to your work.

Also, the javascript variable are not easy to understand(that was the problem when I tried to customize the filter).

_gp[f][f][theme]=theme
_gp[f][f][title]=title
_gp[f][o][theme]=equals
_gp[f][o][title]=contains
_gp[f][v][theme]=1
_gp[f][v][title]=

would be more intuitive like that.

_gp[filter][qualifier][theme]=theme
_gp[filter][qualifier][title]=title
_gp[filter][type][theme]=equals
_gp[filter][type][title]=contains
_gp[filter][value][theme]=1
_gp[filter][value][title]=

Thank you, Pierre

pmithrandir commented 11 years ago

Hi,

I got another problem with the documentation of the Ajax call.

As explained in the post before, i have a custom filter. But, this filter is not rebuild when I call datagrid_content in ajax.

Because there is an inclusion, I can't redefine the block in the ajax content...

I'm not so familiar with block usage and Ajax, but I didn't find a way to regenerate the page(with pagination, table content, and everything) using your construction.

If you have time, I would appreciate some help on this subject. Pierre

iamluc commented 11 years ago

Hi Pierre, I'm currently on vacations. I will try to answer next week.

pmithrandir commented 11 years ago

Hello,

Thank you for your attention, I will wait your answer. Thanks, Pierre

iamluc commented 11 years ago

Hi,

Concerning the Field class, the arguments are :

$property : It is the way to access and display the data If you use the QueryBuilderWrapper or the EntityWrapper, it is just the name of you property. If you need to walk through object, you must put array('tree' => true) in the $options argument. ie. :

$wrapper->addField(new Field('company.name', 'text', 'Company name', 'company.name', array('tree' => true)), 'company_name');

$type : The type of the data (string or date) If it is a date, you can set the format like that :

$field->setDateFormat('d/m/Y')

$label : The label of the field

$qualifier : The way to sort or filter the datas. If you use DbalQueryBuilderWrapper to make a query like that :

    select  CONCAT(t.lastname, ' ', t.firstname) as name
    from    Customers

Your field will be

new Field('name', 'text', 'Customer name', 'CONCAT(t.lastname, \' \', t.firstname)')

$options : Misc. Use array('tree' => true) if your need to walk through object to get your propert.

Some wrappers (QueryBuilderWrapper or the EntityWrapper) have a populateFields() method to create automatically the fields. But you can redefine them with methods getFields() and addField()

If you don't want the populateFields() method to be called, just put your Fields in the constructor.

Concerning the filers variables :

_gp[f][f][0] = theme  --> Filter Field 0 is theme
_gp[f][o][0] = equals --> Filter Operator 0 is equals
_gp[f][v][0] = 1      --> Filter value 0 is 1

I use short notation because if you put a lot of filters in an URL (GET), you will have a problem with Internet Explorer which limit the GET string to 2048 characters.

At last, for your problem with datagrid_content use in ajax. Did you put 'datagrid_theme' in the shell template or in the datagrid one ?

pmithrandir commented 11 years ago

Hello,

Here is my code :

{% block mainCol %}
        <span id="loading" style="color: orange; position:absolute; width:100px; height:30px; left:48%; top:30%;border:1px solid black; background-color:white; display: none;text-align:center; line-height:30px;">
            Loading...
        </span>
        <div id="datagrid">
                    {% include 'JaiUneIdeeSiteBundle:Idee:datagrid.html.twig' with {'datagrid': datagrid} %}
                </div>
{% endblock %}
{% block datagrid_column_filter__theme %}
    <th>
        <div id="div_filter_511fa4a7551c6" onmouseout="hideFiltersOpts( event, '511fa4a7551c6' );">
                <div style="white-space: nowrap;">
            <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[f][theme]" value="theme" />
            <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[o][theme]" value="equals" />

            <select name="{{ datagrid.pager.getConfig('filtersVar') }}[v][theme]" onchange="this.form.submit();">
                <option value="">-- Thème --</option>
                {% for row in field.getOption('select_options') %}
                    <option value="{{ row.id }}" {% if datagrid.pager.getValue('filtersVar').v['theme']|default('') == row.id %}selected="selected"{% endif %}>{{ row.nom }}</option>
                {% endfor %}
            </select>
          </div>
        </div>
    </th>
{% endblock %}

And in the datagrid file : (it's in a block, so I can't redefine a block here with the theme).

            {{ datagrid_content(datagrid) }}

My controller :

    public function listAction()
    {

        $idees = $this->getDoctrine()->getEntityManager()
                ->getRepository('JaiUneIdeeSiteBundle:Idee')->getLatestIdeesQb()
                ->addSelect('t')->leftJoin('i.theme', 't')
                ->addSelect('t')->leftJoin('i.user', 'u')
                ;

        $wrapper = new QueryBuilderWrapper($idees);
        $wrapper
            ->addField(new Field('theme.nom', 'string', 'Thème', 't.id', array('tree' => true)), 'theme')
            ->addField(new Field('user.username', 'string', 'Auteur', 'u.username', array('tree' => true)), 'user')
        ;
        $wrapper->setOrderBy(array('updated_at' => 'desc'));
        $datagrid = $this->get('gloomy.datagrid')->factory($wrapper);
        $datagrid->showOnly(
            array(
                'title', 
                'description', 
                'created_at', 
                'theme', 
                'user'
                )
            );

        $themes = $this->getDoctrine()->getEntityManager()->getRepository('JaiUneIdeeSiteBundle:Theme')->findAll();
        $datagrid->getField('theme')->addOption('select_options', $themes);
        $datagrid->getField('created_at')->setProperty('createdAt');
        $datagrid->getField('created_at')->setDateFormat('d/m/Y à H:i:s');

        $template = $this->getRequest()->isXMLHttpRequest() ? 'datagrid.html.twig' : 'list.html.twig';
    return $this->render(
            'JaiUneIdeeSiteBundle:Idee:'.$template,
            array('datagrid' => $datagrid)
        );
        return array('datagrid' => $datagrid);

    }

It's pretty similar to your code. The only change is this setProperty method I didn't find a way to replace.

BTW, other question, are we suppose to get the same filter with a field with DateTime ? I was wonderind if there was specific output there.

You can, see the problem at this page : http://www.jaiuneidee.net/idee/list Press ENTER after clicking in any text filter. and you will see the dropdown disapeer.

Thanks, Pierre

iamluc commented 11 years ago

Hi Pierre, Sorry for the delay.

I don't understand why you said you can't theme your datagrid because datagrid_content() is in a block. I just tried that and it works :

datagrid.html.twig

{% extends "TestTestBundle:Default:layout.html.twig" %}

{% block body %}
    {% datagrid_theme datagrid _self %}
    {{ datagrid_content(datagrid) }}
{% endblock %}

{% block datagrid_column_value__color %}
    <td>
        <span style="color: {{ datagrid_item_value(datagrid, field, item) }};">{{ datagrid_item_value(datagrid, field, item) }}</span>
    </td>
{% endblock %}

{% block datagrid_column_filter__color %}
    <th>
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[f][color]" value="color" />
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[o][color]" value="equals" />

        <select name="{{ datagrid.pager.getConfig('filtersVar') }}[v][color]" onchange="this.form.submit();">
            <option value="">-- Choose a color --</option>
            {% for row in field.getOption('select_options') %}
                <option value="{{ row.color }}" {% if datagrid.pager.getValue('filtersVar').v['color']|default('') == row.color %}selected="selected"{% endif %}>{{ row.color }}</option>
            {% endfor %}
        </select>
    </th>
{% endblock %}

For your other question : yes the filter for date is a bit different. You can have a look at https://github.com/iamluc/GloomyPagerBundle/blob/master/Resources/views/Pager/macros.html.twig (macro filter)

                {% if type == 'date' %}
                    <div style="margin-top: 20px">
                        {{ 'Date'|trans([], 'pager') }}
                        <div id="div_date_{{ id }}" class="gloomy-filter-date-inline"></div>
                    </div>
                {% endif %}

I used that to display a jquery-ui datepicker inside the filter div

pmithrandir commented 11 years ago

Hi,

Thanks, for the tips for datepicker.

Regarding the custom filter, they works great without ajax. I tried to follow your exempale about AJAX, where you have a different file for datagrid_content.

The idea is to reload juste the datagrid content / filter, and not the all layout. This is where the problem is, if you reload datagrid_content, you cannot, or I didn't find how to, redefine the filter in this file. As I said, there is an example of none working datagrid ajax on my website : http://www.jaiuneidee.net/idee/list

Select whatever filter and press enter, and you will see the issue.

Pierre

iamluc commented 11 years ago

You have 2 solutions :

With layout

ajax_layout.html.twig (which can contains only one single block)

{% block body %}
{% endblock %}

datagrid.html.twig

{% extends "TestTestBundle:Default:ajax_layout.html.twig" %}

{% block body %}
    {% datagrid_theme datagrid _self %}
    {{ datagrid_content(datagrid) }}
{% endblock %}

{% block datagrid_column_value__color %}
    <td>
        <span style="color: {{ datagrid_item_value(datagrid, field, item) }};">{{ datagrid_item_value(datagrid, field, item) }}</span>
    </td>
{% endblock %}

{% block datagrid_column_filter__color %}
    <th>
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[f][color]" value="color" />
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[o][color]" value="equals" />

        <select name="{{ datagrid.pager.getConfig('filtersVar') }}[v][color]" onchange="this.form.submit();">
            <option value="">-- Choose a color --</option>
            {% for row in field.getOption('select_options') %}
                <option value="{{ row.color }}" {% if datagrid.pager.getValue('filtersVar').v['color']|default('') == row.color %}selected="selected"{% endif %}>{{ row.color }}</option>
            {% endfor %}
        </select>
    </th>

Without layout, in a new file (ie. datagrid_theme.html.twig)

datagrid.html.twig

{% datagrid_theme datagrid 'TestTestBundle:Default:datagrid_theme.html.twig' %}
{{ datagrid_content(datagrid) }}

datagrid_theme.html.twig

{% block datagrid_column_value__color %}
    <td>
        <span style="color: {{ datagrid_item_value(datagrid, field, item) }};">{{ datagrid_item_value(datagrid, field, item) }}</span>
    </td>
{% endblock %}

{% block datagrid_column_filter__color %}
    <th>
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[f][color]" value="color" />
        <input type="hidden" name="{{ datagrid.pager.getConfig('filtersVar') }}[o][color]" value="equals" />

        <select name="{{ datagrid.pager.getConfig('filtersVar') }}[v][color]" onchange="this.form.submit();">
            <option value="">-- Choose a color --</option>
            {% for row in field.getOption('select_options') %}
                <option value="{{ row.color }}" {% if datagrid.pager.getValue('filtersVar').v['color']|default('') == row.color %}selected="selected"{% endif %}>{{ row.color }}</option>
            {% endfor %}
        </select>
    </th>
{% endblock %}
pmithrandir commented 11 years ago

Thank you for this very precise solution. I will try it this week end normally

Pierre

pmithrandir commented 11 years ago

Hello.

i'm trying to update my bundles, but because I had to modify the gloomy pager Bundle, I can't do it.

Do you think you could apply the patch I had to put in place ?

    public function setProperty($property)
    {
        $this->_property = $property;
        return $this;
    }
    public function setQualifier($qualifier)
    {
        $this->_qualifier = $qualifier;
        return $this;
    }

It helps me to execute this method to correct the wrong initialisation of column with "_" in the name like created_at.

        $datagrid->getField('created_at')->setProperty('createdAt');

I should not add any issue for what I understand the code.

Best regards, Pierre

BTW : everything else works fine. I think it would be great to allow people to create "custom" query bnehind the filter, but it's not a priority.

iamluc commented 11 years ago

Hi Pierre, It's done !

Glad to see that everything works for you.

pmithrandir commented 11 years ago

Thank you, I will update the Bundle this evening. For you to see it in real : http://jaiuneidee.net/idee/list

I still have some issues with CSS on the autocomplete element, but it works fine now.

Some improvments I have in my mind :

Something less important(and I'm not sure relevant)

Pierre