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

Add master checkbox to Grid #1921

Open
wants to merge 62 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
4794b95
add master checkbox in table header
mhuser Oct 28, 2022
df9f80c
ugly working mastercheckbox
mhuser Oct 29, 2022
cebaf59
Add indeterminate state to master checkbox
mhuser Oct 29, 2022
472db65
Merge branch 'atk4:develop' into add-master-checkbox-to-grid--addSele…
mhuser Nov 4, 2022
2542bfc
add behat test
mhuser Nov 5, 2022
7fd9d4b
Merge branch 'add-master-checkbox-to-grid--addSelection()' of https:/…
mhuser Nov 5, 2022
3574c18
Fix merge git mess
mhuser Nov 5, 2022
f72140d
Toast selection
mhuser Nov 5, 2022
ce62a0a
Behat with selection toast
mhuser Nov 5, 2022
5ed0e7a
syntax
mhuser Nov 5, 2022
6a837c6
make behat happy
mhuser Nov 5, 2022
8ead2ab
syntax
mhuser Nov 5, 2022
761c459
add selector for behat
mhuser Nov 5, 2022
eb5b5ee
account for grid id
mhuser Nov 5, 2022
86be84d
revert selector
mhuser Nov 5, 2022
5900356
use same class as crud menu behat
mhuser Nov 5, 2022
52ad16e
strip end blanks
mhuser Nov 5, 2022
ed8c541
correct row index
mhuser Nov 5, 2022
e942c77
js syntax
mhuser Nov 5, 2022
e4fa197
reduce ipp
mhuser Nov 5, 2022
1b18382
more js syntax
mhuser Nov 5, 2022
31a0f25
const $checkbox
mhuser Nov 5, 2022
404861f
fix EOL and text empty selection reliably
mhuser Nov 5, 2022
a1a1a7a
fix EOL
mhuser Nov 6, 2022
80ac317
make it npm friendly
mhuser Nov 8, 2022
df582bd
npm run build
mhuser Nov 8, 2022
337b5e9
npm build 2nd attempt
mhuser Nov 8, 2022
f413c40
is this missing ?
mhuser Nov 8, 2022
58bfe12
enforce LF against mysterious CRLF
mhuser Nov 8, 2022
98533cf
even '\r\n' moved to '\n'
mhuser Nov 8, 2022
ad89648
some more tweaks
mhuser Nov 8, 2022
5cc92ab
even more \r\n
mhuser Nov 8, 2022
6674fc5
one more
mhuser Nov 9, 2022
9bc4968
git reset --hard on Linux and npm run build
mhuser Nov 9, 2022
3d6dc3f
Merge branch 'atk4:develop' into add-master-checkbox-to-grid--addSele…
mhuser Nov 9, 2022
2b3c2ca
Merge branch 'atk4:develop' into add-master-checkbox-to-grid--addSele…
mhuser Nov 10, 2022
9e018e1
Merge branch 'develop'
mvorisek May 29, 2023
3fdd93a
fix merge
mvorisek May 29, 2023
5a18354
npm run build js
mhuser Aug 7, 2023
7118430
Merge remote-tracking branch 'upstream/develop' into add-master-check…
mhuser Aug 7, 2023
024b5f9
more npm build
mhuser Aug 7, 2023
245d5dc
even more npm build
mhuser Aug 7, 2023
da216b7
make behat happy
mhuser Aug 10, 2023
676f65d
Revert "make behat happy"
mhuser Aug 10, 2023
082ab90
fix slow checkbox on page load
mhuser Aug 10, 2023
8fb3949
checkout lf files and npm run build
mhuser Aug 10, 2023
159cf9b
fix checkbox column witdth
mhuser Aug 10, 2023
7166991
restore js/package-lock.json and js/package.json
mhuser Aug 10, 2023
9536060
npm run build again
mhuser Aug 10, 2023
2e244bb
fix class array
mhuser Aug 10, 2023
4eb182b
Merge branch 'develop'
mvorisek Sep 4, 2023
fe7d4ad
fix CS
mvorisek Sep 4, 2023
390cc22
Merge branch 'develop'
mvorisek Sep 23, 2023
8c4cc48
Merge branch 'develop'
mvorisek Sep 24, 2023
d85362a
Merge branch 'develop'
mvorisek Sep 30, 2023
a00ecac
Merge branch 'develop'
mvorisek Feb 1, 2024
2608a62
Merge branch 'develop'
mvorisek Feb 2, 2024
9469d03
fix performance issue after PR 2146
mvorisek Feb 2, 2024
ea43c69
Merge branch 'develop'
mvorisek Feb 2, 2024
fec1793
Merge branch 'develop'
mvorisek Mar 29, 2024
1b28c35
simplify/fix Behat
mvorisek Mar 29, 2024
ad4909d
Merge branch 'develop'
mvorisek May 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions demos/_unit-test/grid-rowclick.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Atk4\Ui\App;
use Atk4\Ui\Grid;
use Atk4\Ui\Js\Jquery;
use Atk4\Ui\Js\JsExpression;
use Atk4\Ui\Js\JsFunction;
use Atk4\Ui\Js\JsToast;
Expand All @@ -17,7 +18,7 @@
require_once __DIR__ . '/../init-app.php';

$model = new Country($app->db);
$grid = Grid::addTo($app, ['name' => 'grid']);
$grid = Grid::addTo($app, ['name' => 'grid', 'ipp' => 5]);
$grid->setModel($model);

$grid->addDecorator($model->fieldName()->name, [Table\Column\Link::class, 'url' => 'xxx']);
Expand All @@ -38,7 +39,11 @@
return new JsToast(['message' => 'Clicked on row']);
});

$grid->addSelection();
$sel = $grid->addSelection();

$grid->addBulkAction('Show selected', static function (Jquery $j, array $ids) use ($grid) {
return new JsToast('Selected: ' . implode(', ', array_map(static fn ($id) => $grid->getApp()->uiPersistence->typecastSaveField($grid->model->getIdField(), $id), $ids)) . '#');
});

// emulate navigate for <a> for Behat
// TODO emulate for all tests automatically in our Atk4\Ui\Behat\Context
Expand Down
57 changes: 57 additions & 0 deletions js/src/helpers/grid-checkbox.helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import $ from 'external/jquery';

export default {
/**
* Simple helper to help displaying Fomantic-UI checkbox within an atk grid.
* The master checkbox in the header of the column enable to toggle all
* content checkboxes to check or uncheck. A partially checked master checkbox
* is displayed if appopriate.
*/
masterCheckbox: function () {
$('.table .master.checkbox').checkbox({
// check all children
onChecked: function () {
const $childCheckbox = $(this).closest('.table').find('.child.checkbox');
$childCheckbox.checkbox('check');
},
// uncheck all children
onUnchecked: function () {
const $childCheckbox = $(this).closest('.table').find('.child.checkbox');
$childCheckbox.checkbox('uncheck');
},
});
},

childCheckbox: function () {
$('.table .child.checkbox').checkbox({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

About 1/2 of the CI Behat jobs fail with:

tests-behat/grid.feature:79

image

I did not experienced such Behat failures on other CI jobs. We run Behat testing very frequently, so it seems related with this PR.

Google Chrome Performance tools in developer tools might help you to understand the issue.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IPP=1000 page load is slow, probably because it checks all checkboxes.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without the $grid->addSelection();, setting the IPP to 1000 is much faster.
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to set fireOnInit: false. As of now, it is not possible to have checkboxes preset on init.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks much better.
image
How fortunate this behat ipp test has been added!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will try to set fireOnInit: false. As of now, it is not possible to have checkboxes preset on init.

I guess the init invoked something in the master checkbox which recomputed something using the whole data, ie. O(n^2) complexity. Or some slowdown due FUI JS? In general, 1k rows is no heavy workload, so it should be fast even with 10k rows :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, each checkbox controls all the other checkboxes to see if the master checkbox has to be changed, which is very inefficient. The default is unchecked for all, and cannot be changed yet so this check has no sense.

// Fire on load to set parent value
fireOnInit: false,

// Change parent state on each child checkbox change
onChange: function () {
const $listGroup = $(this).closest('.table');
const $parentCheckbox = $listGroup.find('.master.checkbox');
const $checkbox = $listGroup.find('.child.checkbox');
let allChecked = true;
let allUnchecked = true;

// check to see if all other siblings are checked or unchecked
$checkbox.each(function () {
if ($(this).checkbox('is checked')) {
allUnchecked = false;
} else {
allChecked = false;
}
});
// set parent checkbox state, but don't trigger its onChange callback
if (allChecked) {
$parentCheckbox.checkbox('set checked');
} else if (allUnchecked) {
$parentCheckbox.checkbox('set unchecked');
} else {
$parentCheckbox.checkbox('set indeterminate');
}
},
});
},
};
2 changes: 2 additions & 0 deletions js/src/setup-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import $ from 'external/jquery';
import mitt from 'mitt';
import lodashDebounce from 'lodash/debounce';
import atk from 'atk';
import gridCheckboxHelper from './helpers/grid-checkbox.helper';
import tableDropdownHelper from './helpers/table-dropdown.helper';
import urlHelper from './helpers/url.helper';

Expand Down Expand Up @@ -82,6 +83,7 @@ atk.utils = {
},
};

atk.gridCheckboxHelper = gridCheckboxHelper;
atk.tableDropdownHelper = tableDropdownHelper;
atk.urlHelper = urlHelper;

Expand Down
94 changes: 88 additions & 6 deletions public/js/atkjs-ui.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,85 @@
return /******/ (() => { // webpackBootstrap
/******/ var __webpack_modules__ = ({

/***/ "./src/helpers/grid-checkbox.helper.js":
/*!*********************************************!*\
!*** ./src/helpers/grid-checkbox.helper.js ***!
\*********************************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {

"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)
/* harmony export */ });
/* harmony import */ var core_js_modules_esnext_async_iterator_find_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! core-js/modules/esnext.async-iterator.find.js */ "./node_modules/core-js/modules/esnext.async-iterator.find.js");
/* harmony import */ var core_js_modules_esnext_async_iterator_find_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_async_iterator_find_js__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! core-js/modules/esnext.iterator.constructor.js */ "./node_modules/core-js/modules/esnext.iterator.constructor.js");
/* harmony import */ var core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_constructor_js__WEBPACK_IMPORTED_MODULE_1__);
/* harmony import */ var core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! core-js/modules/esnext.iterator.find.js */ "./node_modules/core-js/modules/esnext.iterator.find.js");
/* harmony import */ var core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(core_js_modules_esnext_iterator_find_js__WEBPACK_IMPORTED_MODULE_2__);
/* harmony import */ var external_jquery__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! external/jquery */ "external/jquery");
/* harmony import */ var external_jquery__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(external_jquery__WEBPACK_IMPORTED_MODULE_3__);




/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ({
/**
* Simple helper to help displaying Fomantic-UI checkbox within an atk grid.
* The master checkbox in the header of the column enable to toggle all
* content checkboxes to check or uncheck. A partially checked master checkbox
* is displayed if appopriate.
*/
masterCheckbox: function () {
external_jquery__WEBPACK_IMPORTED_MODULE_3___default()('.table .master.checkbox').checkbox({
// check all children
onChecked: function () {
const $childCheckbox = external_jquery__WEBPACK_IMPORTED_MODULE_3___default()(this).closest('.table').find('.child.checkbox');
$childCheckbox.checkbox('check');
},
// uncheck all children
onUnchecked: function () {
const $childCheckbox = external_jquery__WEBPACK_IMPORTED_MODULE_3___default()(this).closest('.table').find('.child.checkbox');
$childCheckbox.checkbox('uncheck');
}
});
},
childCheckbox: function () {
external_jquery__WEBPACK_IMPORTED_MODULE_3___default()('.table .child.checkbox').checkbox({
// Fire on load to set parent value
fireOnInit: false,
// Change parent state on each child checkbox change
onChange: function () {
const $listGroup = external_jquery__WEBPACK_IMPORTED_MODULE_3___default()(this).closest('.table');
const $parentCheckbox = $listGroup.find('.master.checkbox');
const $checkbox = $listGroup.find('.child.checkbox');
let allChecked = true;
let allUnchecked = true;

// check to see if all other siblings are checked or unchecked
$checkbox.each(function () {
if (external_jquery__WEBPACK_IMPORTED_MODULE_3___default()(this).checkbox('is checked')) {
allUnchecked = false;
} else {
allChecked = false;
}
});
// set parent checkbox state, but don't trigger its onChange callback
if (allChecked) {
$parentCheckbox.checkbox('set checked');
} else if (allUnchecked) {
$parentCheckbox.checkbox('set unchecked');
} else {
$parentCheckbox.checkbox('set indeterminate');
}
}
});
}
});

/***/ }),

/***/ "./src/helpers/table-dropdown.helper.js":
/*!**********************************************!*\
!*** ./src/helpers/table-dropdown.helper.js ***!
Expand Down Expand Up @@ -3763,10 +3842,12 @@ __webpack_require__.r(__webpack_exports__);
/* harmony import */ var external_jquery__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! external/jquery */ "external/jquery");
/* harmony import */ var external_jquery__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(external_jquery__WEBPACK_IMPORTED_MODULE_0__);
/* harmony import */ var mitt__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! mitt */ "./node_modules/mitt/dist/mitt.mjs");
/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! lodash/debounce */ "./node_modules/lodash/debounce.js");
/* harmony import */ var lodash_debounce__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! lodash/debounce */ "./node_modules/lodash/debounce.js");
/* harmony import */ var atk__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! atk */ "./src/setup-atk.js");
/* harmony import */ var _helpers_table_dropdown_helper__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers/table-dropdown.helper */ "./src/helpers/table-dropdown.helper.js");
/* harmony import */ var _helpers_url_helper__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./helpers/url.helper */ "./src/helpers/url.helper.js");
/* harmony import */ var _helpers_grid_checkbox_helper__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./helpers/grid-checkbox.helper */ "./src/helpers/grid-checkbox.helper.js");
/* harmony import */ var _helpers_table_dropdown_helper__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./helpers/table-dropdown.helper */ "./src/helpers/table-dropdown.helper.js");
/* harmony import */ var _helpers_url_helper__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./helpers/url.helper */ "./src/helpers/url.helper.js");




Expand Down Expand Up @@ -3820,7 +3901,7 @@ atk__WEBPACK_IMPORTED_MODULE_2__["default"].createDebouncedFx = function (func,
}, 25);
(external_jquery__WEBPACK_IMPORTED_MODULE_0___default().active)++;
}
lodashDebouncedFx = (0,lodash_debounce__WEBPACK_IMPORTED_MODULE_5__["default"])(func, wait, options);
lodashDebouncedFx = (0,lodash_debounce__WEBPACK_IMPORTED_MODULE_6__["default"])(func, wait, options);
function debouncedFx() {
if (timerId === null) {
createTimer();
Expand All @@ -3842,8 +3923,9 @@ atk__WEBPACK_IMPORTED_MODULE_2__["default"].utils = {
window.location = atk__WEBPACK_IMPORTED_MODULE_2__["default"].urlHelper.appendParams(url, params);
}
};
atk__WEBPACK_IMPORTED_MODULE_2__["default"].tableDropdownHelper = _helpers_table_dropdown_helper__WEBPACK_IMPORTED_MODULE_3__["default"];
atk__WEBPACK_IMPORTED_MODULE_2__["default"].urlHelper = _helpers_url_helper__WEBPACK_IMPORTED_MODULE_4__["default"];
atk__WEBPACK_IMPORTED_MODULE_2__["default"].gridCheckboxHelper = _helpers_grid_checkbox_helper__WEBPACK_IMPORTED_MODULE_3__["default"];
atk__WEBPACK_IMPORTED_MODULE_2__["default"].tableDropdownHelper = _helpers_table_dropdown_helper__WEBPACK_IMPORTED_MODULE_4__["default"];
atk__WEBPACK_IMPORTED_MODULE_2__["default"].urlHelper = _helpers_url_helper__WEBPACK_IMPORTED_MODULE_5__["default"];
/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (null);

/***/ }),
Expand Down
2 changes: 1 addition & 1 deletion public/js/atkjs-ui.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/js/atkjs-ui.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion public/js/atkjs-ui.min.js.map

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions src/Table/Column/Checkbox.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,15 @@ public function getHeaderCellHtml(?Field $field = null, $value = null): string
->addMoreInfo('field', $field);
}
$this->table->js(true)->find('.' . $this->class)->checkbox();
$this->table->js(true, new JsExpression('atk.gridCheckboxHelper.masterCheckbox();'));
$this->table->js(true, new JsExpression('atk.gridCheckboxHelper.childCheckbox();'));

return parent::getHeaderCellHtml($field);
return $this->getTag('head', [], [['div', ['class' => 'ui master fitted checkbox ' . $this->class], [['input/', ['type' => 'checkbox']]]]]);
}

#[\Override]
public function getDataCellTemplate(?Field $field = null): string
{
return $this->getApp()->getTag('div', ['class' => 'ui fitted checkbox ' . $this->class], [['input/', ['type' => 'checkbox']]]);
return $this->getApp()->getTag('div', ['class' => 'ui child fitted checkbox ' . $this->class], [['input/', ['type' => 'checkbox']]]);
}
}
14 changes: 14 additions & 0 deletions tests-behat/grid.feature
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,20 @@ Feature: Grid
Then No toast should be displayed
Then PATCH MINK the url should match "~_unit-test/grid-rowclick.php#test~"

Scenario: master checkbox
Given I am on "_unit-test/grid-rowclick.php"
When I press button "Show selected"
Then Toast display should contain text "Selected: #"
When I click using selector "//tr[1]//div.ui.child.checkbox"
Then I press button "Show selected"
Then Toast display should contain text "Selected: 1#"
When I click using selector "//tr//div.ui.master.checkbox"
Then I press button "Show selected"
Then Toast display should contain text "Selected: 1, 2, 3, 4, 5#"
When I click using selector "//tr//div.ui.master.checkbox"
Then I press button "Show selected"
Then Toast display should contain text "Selected: #"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#1920 adds support for non-UA (atk4/data User Action) bulk support

before we finish this PR, another PR should be made to add bulk support for "regular UA for multiple records" - https://github.com/atk4/data/blob/4.0.0/src/Model/UserAction.php#L32

Scenario: popup column header
Given I am on "collection/tablecolumnmenu.php"
Then I should not see "Name popup"
Expand Down
Loading