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

Execute php callback on Grid->addSelection() for multiple selection #1727

Closed
mhuser opened this issue Jan 11, 2022 · 23 comments · Fixed by #1920
Closed

Execute php callback on Grid->addSelection() for multiple selection #1727

mhuser opened this issue Jan 11, 2022 · 23 comments · Fixed by #1920
Labels

Comments

@mhuser
Copy link
Contributor

mhuser commented Jan 11, 2022

First of all, I want to thank you for this great tool that is atk4/ui !
Since some time I have a question which I believe does not find answer in the documentation (but maybe I am just blinded).
Let's suppose we have two models User and Role and User hasMany Role.

  • It is quite straightforward to use \Atk4\Ui\Grid and addCondition to display a given Role and add a User.
  • Also we can almost out of the box display the Role list of a User and add one using the same method.

My question is : how to use $grid->addSelection() to mass-attribute a Role to a selected set of User ? (Which enable to use grid filtering and sorting)
In the demo there is a call to some javascript

$grid->menu->addItem('show selection')->on('click', new \Atk4\Ui\JsExpression(
    'alert("Selected: "+[])',
    [$sel->jsChecked()]
));

But I does not understand how to relies on a php callback instead that would receive the selected id list and perform some processing such at foreach the list and create the appropriate role lines or others.

@mhuser
Copy link
Contributor Author

mhuser commented Jan 15, 2022

I finally managed it by using javascript to set value of an hidden form input in a static modal.
Any other idea for better implementation welcome.

    $mod2 = \atk4\ui\Modal::addTo($app);
    $form2 = \atk4\ui\Form::addTo($mod2);
    $form2->addControl('selection_data', [\atk4\ui\Form\Control\Hidden::class]);
    $val2 = $form2->getControl('selection_data');
    $val2->setAttr(['type' => 'hidden']);
    $form2->addControl('role',
        [
        \atk4\ui\Form\Control\Lookup::class,
        'model' => new Role($app->db),
        'placeholder' => 'Search by name',
        'search' => ['Name'],
    ]);
    $form2->onSubmit(function ($f){
        $errors = [];
        if (!$f->model->get('role')) {
            $errors[] =  $f->error('role', 'Required');
        }
        if ($errors) {
            return $errors;
        }
        $user_ids = explode(',', $f->model->get('selection_data'));
	    foreach($user_ids as $id){
            $m = new RoleLine($f->getApp()->db);
            $m->set('IdRole', $f->model->get('role'));
            $m->set('IdUser', $id);
            $m->set('department_id', $f->getApp()->department->getId());
            $m->save();
        }
        $modal_chain = new \atk4\ui\jQuery('.ui.modal');
        $modal_chain->modal('hide');
        return [$modal_chain];
    });
    $grid->menu->addItem(['Attribuer rôle', 'icon'=>'theater masks'])
    ->on('click',
        function($j, $param) use ($mod2, $val2) {
            return [$val2->jsInput()->val($param), $mod2->show()];
        },
        [$sel->jsChecked()]);

@mvorisek
Copy link
Member

mvorisek commented Oct 1, 2022

@mhuser is there anything to be improve in atk4/ui?

@mhuser
Copy link
Contributor Author

mhuser commented Oct 2, 2022

Hello @mvorisek thank you a lot for asking!

I find atk4/ui is a really great tool!

When starting to use Grid I stumbled on this:
It is easy to create a grid with a model and some conditions. When interacting with it, it is also very straightforward to

  1. use $grid->menu->addItem(...); to pop a JsModal and add a new record or perform an action on all records
  2. use $grid->addModalAction(...); to pop a modal and perform an action with a Form on a single existing record

And all of this even without writing any JavaScript, which is incredibly nice!

However, when it come to performing an action on a selection of records, things escalated quickly:
The demo page introduce $grid->addSelection(); with only a JsExpression that pop up an alert. Such is also the documentation.

To be honest, I fear this is not very helpful for an individual with limited atk4/ui skills like me.

The solution above works, but the code required for it is verbose and error-prone when several actions exist. In comparison, the other atk4/ui lines look nice.

I have later discovered this documentation making an exemple of multiple selection in Grid and calling Action. But I was not able to get the full power of it yet.

In my wildest dream, there are two elements that could be improved with Atk4/Ui/Grid:

  • To have the ability that $grid->menu->addItem(...) would receive the $grid->addSelection() results in some transparent way (even if not using it)
  • To have a checkbox in the title row that check/unchecks all the column

@mvorisek
Copy link
Member

mvorisek commented Oct 2, 2022

Thank you for the perfect analysis.

To have the ability that $grid->menu->addItem(...) would receive the $grid->addSelection() results in some transparent way (even if not using it)

If you can, please submit a PR with an improvement.

To have a checkbox in the title row that check/unchecks all the column

opened #1877, it is a separate issue

@mhuser
Copy link
Contributor Author

mhuser commented Oct 9, 2022

If you can, please submit a PR with an improvement.

Thank you.

Let's try it. This will enable me to better understand what is going behind the hood and I will be the first to benefits from it.

@mhuser
Copy link
Contributor Author

mhuser commented Oct 26, 2022

I know that the question may sound silly, but once I have cloned atk4-ui master on a development web server, how can I get the demos to work? I guess there is something to do with composer to setup autoload.

@mvorisek
Copy link
Member

run composer update in atk4/ui root directory

@mhuser
Copy link
Contributor Author

mhuser commented Oct 26, 2022

Thank you so much !

@mhuser
Copy link
Contributor Author

mhuser commented Oct 26, 2022

OK, I have the running demo and the sqlite db was populated thanks to your composer hint.
I have no css styles yet at this stage. This is because the app tries to have stylesheets and others loaded by their full local paths instead of relative path from the server adress. This is forbidden by the server.
Seems strange to me to have to modify the demos/init-app.php to force the paths. Is there some magic configuration to do or a doc I could follow without bothering you?

@mvorisek
Copy link
Member

mvorisek commented Oct 26, 2022

in the latest develop version it should be configured automatically in:

ui/src/App.php

Lines 630 to 655 in b1b2268

protected function createRequestPathFromLocalPath(string $localPath): string
{
static $requestUrlPath = null;
static $requestLocalPath = null;
if ($requestUrlPath === null) {
if (\PHP_SAPI === 'cli') { // for phpunit
$requestUrlPath = '/';
$requestLocalPath = \Closure::bind(function () {
return dirname((new ExceptionRenderer\Html(new \Exception()))->getVendorDirectory());
}, null, ExceptionRenderer\Html::class)();
} else {
$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
$requestUrlPath = $request->getBasePath();
$requestLocalPath = $request->server->get('SCRIPT_FILENAME');
}
}
$fs = new \Symfony\Component\Filesystem\Filesystem();
$localPathRelative = $fs->makePathRelative($localPath, dirname($requestLocalPath));
$res = '/' . $fs->makePathRelative($requestUrlPath . '/' . $localPathRelative, '/');
// fix https://github.com/symfony/symfony/pull/40051
if (str_ends_with($res, '/') && !str_ends_with($localPath, '/')) {
$res = substr($res, 0, -1);
}
return $res;
}

please describe:

  • your webserver configuration
  • confirm you use the latest develop version
  • dump your $_SERVER variable at l632 or any entry place, and post here the data

@mhuser
Copy link
Contributor Author

mhuser commented Oct 26, 2022

Apache HTTP Server 2.4 and PHP 74 on a local NAS server. atk4-ui is a subdirectory in the http/web directory.
Using version up to commit b1b2268
$_SERVER:

USER: http
HOME: /var/services/web
SCRIPT_NAME: /atk4-ui/demos/test.php
REQUEST_URI: /atk4-ui/demos/test.php
QUERY_STRING: 
REQUEST_METHOD: GET
SERVER_PROTOCOL: HTTP/1.1
GATEWAY_INTERFACE: CGI/1.1
REMOTE_PORT: 60394
SCRIPT_FILENAME: /var/services/web/atk4-ui/demos/test.php
SERVER_ADMIN: [no address given]
CONTEXT_DOCUMENT_ROOT: /var/services/web
CONTEXT_PREFIX: 
REQUEST_SCHEME: https
DOCUMENT_ROOT: /var/services/web
REMOTE_ADDR: 2a02:aa17:327f:6380:4463:ac80:9642:44a
SERVER_PORT: 443
SERVER_ADDR: 2a02:aa17:327f:6380:211:32ff:fe80:2943
SERVER_NAME: etml-elo.diskstation.me
SERVER_SOFTWARE: Apache/2.4.43 (Unix)
SERVER_SIGNATURE: 
PATH: /sbin:/bin:/usr/sbin:/usr/bin:/usr/syno/sbin:/usr/syno/bin:/usr/local/sbin:/usr/local/bin
HTTP_SEC_FETCH_USER: ?1
HTTP_SEC_FETCH_SITE: none
HTTP_SEC_FETCH_MODE: navigate
HTTP_SEC_FETCH_DEST: document
HTTP_UPGRADE_INSECURE_REQUESTS: 1
HTTP_COOKIE: stay_login=0; smid=A6w2rEfFtRb89YYpRiAdBaohBHOfLMuH-Ej4D9SMdB3Df7fCpRt8JvaopH7tF8bX0RGcuYMyTjTT7opuYOqSuw
HTTP_ACCEPT_ENCODING: gzip, deflate, br
HTTP_ACCEPT_LANGUAGE: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
HTTP_ACCEPT: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
HTTP_USER_AGENT: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:106.0) Gecko/20100101 Firefox/106.0
HTTP_CONNECTION: close
HTTP_HOST: etml-elo.diskstation.me
HTTP_X_REAL_PORT: 49908
HTTP_X_PORT: 443
HTTP_X_HTTPS: on
HTTP_X_REAL_IP: 2a02:aa17:327f:6380:4463:ac80:9642:44a
HTTP_X_FORWARDED_BY: 2a02:aa17:327f:6380:211:32ff:fe80:2943
proxy-nokeepalive: 1
MOD_X_SENDFILE_ENABLED: yes
HTTPS: on
FCGI_ROLE: RESPONDER
PHP_SELF: /atk4-ui/demos/test.php
REQUEST_TIME_FLOAT: 1666799611.7181
REQUEST_TIME: 1666799611

@mvorisek
Copy link
Member

I will take a look.

for now, edit the App cdns here

ui/src/App.php

Line 147 in b1b2268

$this->cdn[$k] = $this->createRequestPathFromLocalPath(__DIR__ . '/..' . $v);
or anywhere where you create your App

@mhuser
Copy link
Contributor Author

mhuser commented Oct 27, 2022

Thanks to you I am now up and running!
I applied this bodge to demos/init-app.php

foreach ($app->cdn as $k => $v) {
    if (str_starts_with($v, '/') && !str_starts_with($v, '//')) {
        $app->cdn[$k] = str_replace('/volume1/web', 'https://etml-elo.diskstation.me', $v);
    }
}

@mhuser
Copy link
Contributor Author

mhuser commented Oct 28, 2022

Draft started in #1920

@mvorisek
Copy link
Member

@mhuser before

ui/src/App.php

Line 641 in b1b2268

$request = \Symfony\Component\HttpFoundation\Request::createFromGlobals();
line I added:

$localDirPath = '/var/services/web';
$subdir = '/atk4-ui';
$_SERVER = [
    'HOME' => $localDirPath,
    'REQUEST_METHOD' => 'GET',
    'SERVER_PROTOCOL' => 'HTTP/1.1',
    'GATEWAY_INTERFACE' => 'CGI/1.1',
    'REMOTE_PORT' => '60394',
    'SCRIPT_NAME' => $subdir . '/demos/test.php',
    'REQUEST_URI' => $subdir . '/demos/test.php',
    'QUERY_STRING' => '',
    'SCRIPT_FILENAME' => $localDirPath . $subdir . '/demos/test.php',
    'CONTEXT_DOCUMENT_ROOT' => $localDirPath,
    'CONTEXT_PREFIX' => '',
    'REQUEST_SCHEME' => 'https',
    'DOCUMENT_ROOT' => $localDirPath,
    'REMOTE_ADDR' => '2a02:aa17:327f:6380:4463:ac80:9642:44a',
    'SERVER_PORT' => '443',
    'SERVER_ADDR' => '2a02:aa17:327f:6380:211:32ff:fe80:2943',
    'SERVER_NAME' => 'etml-elo.diskstation.me',
    'HTTP_HOST' => 'etml-elo.diskstation.me',
    'HTTP_X_REAL_PORT' => '49908',
    'HTTP_X_PORT' => '443',
    'HTTP_X_HTTPS' => 'on',
    'HTTP_X_REAL_IP' => '2a02:aa17:327f:6380:4463:ac80:9642:44a',
    'HTTP_X_FORWARDED_BY' => '2a02:aa17:327f:6380:211:32ff:fe80:2943',
    'HTTPS' => 'on',
    'PHP_SELF' => $subdir . '/demos/test.php',
];

and demos/index.php demos homepage has about this content:

image

navigate to the homepage and please post here:

  • what URL you use to access the homepage in your browser
  • what link in <script src="... is rendered currently on your side and what link you expect
  • what is your local (at webserver) path of the this/ui repo and are there any symlinks [1]?

[1] can be analysed by linux realpath https://serverfault.com/questions/1069147/how-can-i-quickly-test-a-path-in-bash-to-determine-if-any-segment-of-it-is-a-sym

@mhuser
Copy link
Contributor Author

mhuser commented Oct 29, 2022

  • URL for local demos homepage: https://etml-elo.diskstation.me/atk4-ui/demos/ (only accessible locally)
  • Link currently rendered: <script src="/volume1/web/atk4-ui/public/external/jquery/dist/jquery.min.js">...
  • What link I expect: <script src="/atk4-ui/public/external/jquery/dist/jquery.min.js">...
  • My local path: /volume1/web/atk4-ui/ but it appears to be somehow a symlink of /var/services/web/atk4-ui/

I think it is my configuration that is the issue (the server is on a Synology NAS setup from their package)

@mvorisek
Copy link
Member

Thank you a lot for the data! I will do my best to make it working. As there is no modrewrite, it should be possible :)

@mvorisek
Copy link
Member

mvorisek commented Nov 9, 2022

I was able to reproduce with the following filesystem configuration:

RUN mkdir /mount && mkdir /mount/html && chmod 0777 /mount/html
RUN ln -s /mount/html /var/www/ht # /var/www/html directory cannot be removed as mounted by Docker
WORKDIR /mount/html

and

foreach ($_SERVER as $k => $v) {
    if (is_string($v)) {
        $_SERVER[$k] = preg_replace('~^/mount/html~', '/var/www/ht', $v, 1);
    }
}

at the beginning of the demos/index.php file the output is then like:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Agile UI Demo v4.0-dev</title>
    <meta charset="utf-8">
    <script>'use strict'; window.__atkBundlePublicPath = '/mount/html/public';</script>
    <script src="/mount/html/public/external/jquery/dist/jquery.js"></script>
<script src="/mount/html/public/external/fomantic-ui/dist/semantic.js"></script>
<link rel="stylesheet" type="text/css" href="/mount/html/public/external/fomantic-ui/dist/semantic.css">
...

and when var_dump([$localPath, $requestLocalPath, $res]) is added to

ui/src/App.php

Line 652 in 7102df9

$localPathRelative = $fs->makePathRelative($localPath, dirname($requestLocalPath));
it shows:

array(3) {
  [0]=>
  string(46) "/mount/html/src/../public/external/jquery/dist"
  [1]=>
  string(27) "/var/www/ht/demos/index.php"
  [2]=>
  string(40) "/mount/html/public/external/jquery/dist/"
}

more debug:

        $localPath = realpath($localPath);
        $localPathRelative = $fs->makePathRelative($localPath, dirname($requestLocalPath));
        var_dump([$localPath, dirname($requestLocalPath), $localPathRelative]);
        $res = '/' . $fs->makePathRelative($requestUrlPath . '/' . $localPathRelative, '/');
        var_dump([$requestUrlPath . '/' . $localPathRelative, $res]);

shows

array(3) {
  [0]=>
  string(18) "/mount/html/public"
  [1]=>
  string(17) "/var/www/ht/demos"
  [2]=>
  string(30) "../../../../mount/html/public/"
}
array(2) {
  [0]=>
  string(37) "/demos/../../../../mount/html/public/"
  [1]=>
  string(19) "/mount/html/public/"
}

array(3) {
  [0]=>
  string(39) "/mount/html/public/external/jquery/dist"
  [1]=>
  string(17) "/var/www/ht/demos"
  [2]=>
  string(51) "../../../../mount/html/public/external/jquery/dist/"
}
array(2) {
  [0]=>
  string(58) "/demos/../../../../mount/html/public/external/jquery/dist/"
  [1]=>
  string(40) "/mount/html/public/external/jquery/dist/"
}

@mhuser
Copy link
Contributor Author

mhuser commented Nov 9, 2022

Looks similar to the issue I faced !

@mvorisek
Copy link
Member

mvorisek commented Nov 9, 2022

please test if adding:

        $localPath = realpath($localPath);
        $requestLocalPath = realpath($requestLocalPath);

at

ui/src/App.php

Line 651 in 7102df9

$fs = new \Symfony\Component\Filesystem\Filesystem();

fixes the path calculation, eg. fixes #1727 (comment)

@mhuser
Copy link
Contributor Author

mhuser commented Nov 9, 2022

Yes, it fixes for my situation. With these lines, I do not need to tweak the cdns any more!
image

@mvorisek
Copy link
Member

mvorisek commented Nov 9, 2022

@mhuser thank you for reporting the problem and helping me to understand it, fix merged in #1936, I was litte worried about realpath performance with many calls, but it turned out __DIR__ is resolved implicitly, and all inputs should be constructed using it, thus with this knowledge/assumptiom, the fix requires only one realpath per app, ❤️

@mhuser
Copy link
Contributor Author

mhuser commented Nov 10, 2022

@mvorisek This sounds great! Thank you a lot for the quick fix!

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.

2 participants