diff --git a/.github/workflows/discord-releases.yml b/.github/workflows/discord-releases.yml
index c75bedd41..84ea5b888 100644
--- a/.github/workflows/discord-releases.yml
+++ b/.github/workflows/discord-releases.yml
@@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Package Releases
uses: SethCohen/github-releases-to-discord@v1.13.1
with:
diff --git a/.github/workflows/pint.yml b/.github/workflows/pint.yml
index 097689b83..230a58799 100644
--- a/.github/workflows/pint.yml
+++ b/.github/workflows/pint.yml
@@ -2,6 +2,11 @@ name: Fix PHP code style issues
on:
push:
+ branches:
+ - '*' # matches every branch that doesn't contain a '/'
+ - '*/*' # matches every branch containing a single '/'
+ - '**' # matches every branch
+ - '!master'
paths:
- '**.php'
@@ -14,7 +19,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
with:
ref: ${{ github.head_ref }}
diff --git a/.github/workflows/run-phpstan.yml b/.github/workflows/run-phpstan.yml
index a94ed2a24..4faf52718 100644
--- a/.github/workflows/run-phpstan.yml
+++ b/.github/workflows/run-phpstan.yml
@@ -1,6 +1,16 @@
name: run-phpstan
-on: [push, pull_request]
+on:
+ pull_request:
+ branches:
+ - 'master'
+ - 'development'
+ push:
+ branches:
+ - '*' # matches every branch that doesn't contain a '/'
+ - '*/*' # matches every branch containing a single '/'
+ - '**' # matches every branch
+ - '!master'
jobs:
test:
@@ -20,7 +30,7 @@ jobs:
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup cache environment
id: extcache
@@ -31,7 +41,7 @@ jobs:
key: ${{ env.extensionKey }}
- name: Cache extensions
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
@@ -55,7 +65,7 @@ jobs:
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-PHPStan-P${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
diff --git a/.github/workflows/run-tests-pcov-pull.yml b/.github/workflows/run-tests-pcov-pull.yml
index 0a5f52d3e..ef73e4b42 100644
--- a/.github/workflows/run-tests-pcov-pull.yml
+++ b/.github/workflows/run-tests-pcov-pull.yml
@@ -1,6 +1,11 @@
name: run-tests-pcov-pull
on:
+ push:
+ branches:
+ - 'develop'
+ - 'development'
+ - 'master'
pull_request:
branches:
- 'develop'
@@ -18,14 +23,14 @@ jobs:
laravel: [10]
stability: [prefer-dist]
- name: PCOV-PULL - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }}
+ name: PCOV - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }}
env:
extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-withpcov
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pcov,pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup cache environment
id: extcache
@@ -47,7 +52,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Cache extensions
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ steps.extcache.outputs.key }}
@@ -61,7 +66,7 @@ jobs:
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-PCOV-PULL-PHP${{ matrix.php }}-Laravel${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
@@ -86,7 +91,7 @@ jobs:
run: php ./vendor/bin/paratest --cache-directory=".phpunit.cache/code-coverage" --strict-coverage --coverage-clover ./coverage.xml --processes=4
- name: Upload coverage reports to Codecov
- uses: codecov/codecov-action@v3
+ uses: codecov/codecov-action@v4
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
with:
diff --git a/.github/workflows/run-tests-pull.yml b/.github/workflows/run-tests-pull.yml
index 1abeaeee8..38f635f9c 100644
--- a/.github/workflows/run-tests-pull.yml
+++ b/.github/workflows/run-tests-pull.yml
@@ -8,27 +8,25 @@ on:
- 'master'
jobs:
- test:
+ test-laravel10:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
+ max-parallel: 3
matrix:
os: [ubuntu-latest]
php: [8.1, 8.2, 8.3]
- laravel: [10.*,11.*]
+ laravel: [10.*]
stability: [prefer-dist]
- exclude:
- - laravel: 11.*
- php: 8.1
- name: STD-PULL - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }}
+ name: PULL PHP-${{ matrix.php }} - Laravel-10
env:
- extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}
+ extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup cache environment
id: extcache
@@ -36,14 +34,14 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: ${{ env.extensions }}
- key: ${{ runner.os }}-${{ env.extensionKey }}
+ key: ${{ env.extensionKey }}
- name: Cache extensions
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.extcache.outputs.dir }}
- key: ${{ steps.extcache.outputs.key }}
- restore-keys: ${{ steps.extcache.outputs.key }}
+ key: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
+ restore-keys: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
- name: Setup PHP
uses: shivammathur/setup-php@v2
@@ -64,11 +62,11 @@ jobs:
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
- key: ${{ runner.os }}-STDPULL-PHP-${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
- restore-keys: ${{ runner.os }}-STDPULL-PHP-${{ matrix.php }}-L${{ matrix.laravel }}-composer-
+ key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-
- name: Add token
run: |
@@ -79,7 +77,90 @@ jobs:
run: composer require "laravel/framework:${{ matrix.laravel }}.*" --no-interaction --no-update
- name: Update dependencies
+ if: steps.composer-cache.outputs.cache-hit != 'true'
run: composer update --${{ matrix.stability }} --no-interaction
+ - name: Setup problem matchers for PHPUnit
+ run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+ - name: Run Unit Tests
+ run: php ./vendor/bin/paratest --no-coverage --processes=4
+
+
+ test-laravel11:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ max-parallel: 2
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.2, 8.3]
+ laravel: [11.*]
+ stability: [prefer-dist]
+
+ name: PULL PHP-${{ matrix.php }} - Laravel-11
+ env:
+ extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }}
+ extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup cache environment
+ id: extcache
+ uses: shivammathur/cache-extensions@v1
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: ${{ env.extensions }}
+ key: ${{ env.extensionKey }}
+
+ - name: Cache extensions
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.extcache.outputs.dir }}
+ key: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
+ restore-keys: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: ${{ env.extensions }}
+ tools: phpunit:latest
+ ini-values: memory_limit=512M
+ coverage: none
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup problem matchers for PHP
+ run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-
+
+ - name: Add token
+ run: |
+ composer config github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies
+ if: steps.composer-cache.outputs.cache-hit != 'true'
+ run: composer require "laravel/framework:${{ matrix.laravel }}.*" --no-interaction --no-update
+
+ - name: Update dependencies
+ if: steps.composer-cache.outputs.cache-hit != 'true'
+ run: composer update --${{ matrix.stability }} --no-interaction
+
+ - name: Setup problem matchers for PHPUnit
+ run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
- name: Run Unit Tests
run: php ./vendor/bin/paratest --no-coverage --processes=4
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index ce258214d..9f6997a6c 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,29 +1,33 @@
name: run-tests
-on: [push]
+on:
+ push:
+ branches:
+ - '*' # matches every branch that doesn't contain a '/'
+ - '*/*' # matches every branch containing a single '/'
+ - '**' # matches every branch
+ - '!master'
jobs:
- test:
+ test-laravel10:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
+ max-parallel: 3
matrix:
os: [ubuntu-latest]
php: [8.1, 8.2, 8.3]
- laravel: [10.*,11.*]
+ laravel: [10.*]
stability: [prefer-dist]
- exclude:
- - laravel: 11.*
- php: 8.1
- name: STD-PUSH - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }}
+ name: PHP-${{ matrix.php }} - Laravel-10
env:
- extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}
+ extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr
steps:
- name: Checkout code
- uses: actions/checkout@v3
+ uses: actions/checkout@v4
- name: Setup cache environment
id: extcache
@@ -34,7 +38,7 @@ jobs:
key: ${{ env.extensionKey }}
- name: Cache extensions
- uses: actions/cache@v3
+ uses: actions/cache@v4
with:
path: ${{ steps.extcache.outputs.dir }}
key: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
@@ -59,7 +63,86 @@ jobs:
run: |
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
- - uses: actions/cache@v3
+ - uses: actions/cache@v4
+ with:
+ path: ${{ steps.composer-cache.outputs.dir }}
+ key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
+ restore-keys: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-
+
+ - name: Add token
+ run: |
+ composer config github-oauth.github.com ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Install dependencies
+ if: steps.composer-cache.outputs.cache-hit != 'true'
+ run: composer require "laravel/framework:${{ matrix.laravel }}.*" --no-interaction --no-update
+
+ - name: Update dependencies
+ if: steps.composer-cache.outputs.cache-hit != 'true'
+ run: composer update --${{ matrix.stability }} --no-interaction
+
+ - name: Setup problem matchers for PHPUnit
+ run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json"
+
+ - name: Run Unit Tests
+ run: php ./vendor/bin/paratest --no-coverage --processes=4
+
+
+ test-laravel11:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ max-parallel: 2
+ matrix:
+ os: [ubuntu-latest]
+ php: [8.2, 8.3]
+ laravel: [11.*]
+ stability: [prefer-dist]
+
+ name: PHP-${{ matrix.php }} - Laravel-11
+ env:
+ extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-L{{ matrix.laravel }}
+ extensions: dom, curl, libxml, mbstring, zip, pcntl, pcov, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo, :psr
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup cache environment
+ id: extcache
+ uses: shivammathur/cache-extensions@v1
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: ${{ env.extensions }}
+ key: ${{ env.extensionKey }}
+
+ - name: Cache extensions
+ uses: actions/cache@v4
+ with:
+ path: ${{ steps.extcache.outputs.dir }}
+ key: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
+ restore-keys: ${{ runner.os }}-${{ steps.extcache.outputs.key }}
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ extensions: ${{ env.extensions }}
+ tools: phpunit:latest
+ ini-values: memory_limit=512M
+ coverage: none
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Setup problem matchers for PHP
+ run: echo "::add-matcher::${{ runner.tool_cache }}/php.json"
+
+ - name: Get composer cache directory
+ id: composer-cache
+ run: |
+ echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
+
+ - uses: actions/cache@v4
with:
path: ${{ steps.composer-cache.outputs.dir }}
key: ${{ runner.os }}-STDPUSH-PHP${{ matrix.php }}-L${{ matrix.laravel }}-composer-${{ hashFiles('**/composer.json') }}
diff --git a/.gitignore b/.gitignore
index a36981422..d3e4b8ca4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -18,4 +18,6 @@ phpunit.xml.dist.dev
.history/*
.env
phpunit.xml.bak
-phpstan.txt
\ No newline at end of file
+phpstan.txt
+coverage.xml
+./tmp/**
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7060f51c2..7b13aa903 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
All notable changes to `laravel-livewire-tables` will be documented in this file
+## UNRELEASED
+### New Features
+- Add new columns (ArrayColumn, AvgColumn, CountColumn, SumColumn) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1761
+
+## [v3.2.8] - 2024-07-03
+### Bug Fixes
+- Fix hide bulk actions when empty not reflecting in frontend by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1747
+- Apply cursor pointer only on clickable columns when using Bootstrap by @MP70 in https://github.com/rappasoft/laravel-livewire-tables/pull/1742
+
+### New Features
+- Always hide bulk actions option by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1752
+- Add ArrayColumn (BETA) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1751
+- Optionally disable total item count for simple pagination by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1755
+
+### Tweaks
+- TypeHint fixes by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1750
+- Change Return Type for attributes() to static by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1749
+- Switch to using Composer\InstalledVersions for AboutCommand by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1748
+
## [v3.2.7] - 2024-06-05
### Bug Fixes
- Ensure HTML Columns return HTML correctly by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1737
diff --git a/coverage.xml b/coverage.xml
deleted file mode 100644
index 7428459d2..000000000
--- a/coverage.xml
+++ /dev/null
@@ -1,2894 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/docs/bulk-actions/_index.md b/docs/bulk-actions/_index.md
index f76ab3244..48cb71a17 100644
--- a/docs/bulk-actions/_index.md
+++ b/docs/bulk-actions/_index.md
@@ -1,4 +1,4 @@
---
title: Bulk Actions
-weight: 9
+weight: 10
---
diff --git a/docs/column-types/_index.md b/docs/column-types/_index.md
new file mode 100644
index 000000000..821c869a2
--- /dev/null
+++ b/docs/column-types/_index.md
@@ -0,0 +1,4 @@
+---
+title: Column Types
+weight: 5
+---
diff --git a/docs/column-types/array_column.md b/docs/column-types/array_column.md
new file mode 100644
index 000000000..1571da2b9
--- /dev/null
+++ b/docs/column-types/array_column.md
@@ -0,0 +1,22 @@
+---
+title: Array Columns (beta)
+weight: 1
+---
+
+Array columns provide an easy way to work with and display an array of data from a field.
+
+```
+ArrayColumn::make('notes', 'name')
+ ->data(fn($value, $row) => ($row->notes))
+ ->outputFormat(fn($index, $value) => "".$value->name."")
+ ->separator('
')
+ ->sortable(),
+```
+
+### Empty Value
+You may define the default/empty value using the "emptyValue" method
+
+```
+ArrayColumn::make('notes', 'name')
+ ->emptyValue('Unknown'),
+```
\ No newline at end of file
diff --git a/docs/column-types/avg_column.md b/docs/column-types/avg_column.md
new file mode 100644
index 000000000..faa653ca2
--- /dev/null
+++ b/docs/column-types/avg_column.md
@@ -0,0 +1,14 @@
+---
+title: Avg Columns (beta)
+weight: 2
+---
+
+Avg columns provide an easy way to display the "Average" of a field on a relation.
+
+```
+ AvgColumn::make('Average Related User Age')
+ ->setDataSource('users','age')
+ ->sortable(),
+```
+
+The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on.
\ No newline at end of file
diff --git a/docs/column-types/boolean_columns.md b/docs/column-types/boolean_columns.md
new file mode 100644
index 000000000..32a759918
--- /dev/null
+++ b/docs/column-types/boolean_columns.md
@@ -0,0 +1,81 @@
+---
+title: Boolean Columns
+weight: 3
+---
+
+Boolean columns are good if you have a column type that is a true/false, or 0/1 value.
+
+For example:
+
+```php
+BooleanColumn::make('Active')
+```
+
+Would yield:
+
+
+
+### Using your own view
+
+If you don't want to use the default view and icons you can set your own:
+
+```php
+BooleanColumn::make('Active')
+ ->setView('my.active.view')
+```
+
+You will have access to `$component`, `$status`, and `$successValue`.
+
+To help you better understand, this is the Tailwind implementation of BooleanColumn:
+
+```html
+@if ($status)
+
+@else
+
+@endif
+```
+
+### Setting the truthy value
+
+If you want the false value to be the green option, you can set:
+
+```php
+BooleanColumn::make('Active')
+ ->setSuccessValue(false); // Makes false the 'successful' option
+```
+
+That would swap the colors of the icons in the image above.
+
+### Setting the status value
+
+By default, the `$status` is set to:
+
+```php
+(bool)$value === true
+```
+
+You can override this functionality:
+
+```php
+BooleanColumn::make('Active')
+ // Note: Parameter `$row` available as of v2.4
+ ->setCallback(function(string $value, $row) {
+ // Figure out what makes $value true
+ }),
+```
+
+### Different types of boolean display
+
+By default, the BooleanColumn displays icons.
+
+If you would like the BooleanColumn to display a plain Yes/No, you can set:
+
+```php
+BooleanColumn::make('Active')
+ ->yesNo()
+```
diff --git a/docs/column-types/button_group_column.md b/docs/column-types/button_group_column.md
new file mode 100644
index 000000000..5a3390049
--- /dev/null
+++ b/docs/column-types/button_group_column.md
@@ -0,0 +1,34 @@
+---
+title: Button Group Columns
+weight: 4
+---
+
+Button group columns let you provide an array of LinkColumns to display in a single cell.
+
+```php
+ButtonGroupColumn::make('Actions')
+ ->attributes(function($row) {
+ return [
+ 'class' => 'space-x-2',
+ ];
+ })
+ ->buttons([
+ LinkColumn::make('View') // make() has no effect in this case but needs to be set anyway
+ ->title(fn($row) => 'View ' . $row->name)
+ ->location(fn($row) => route('user.show', $row))
+ ->attributes(function($row) {
+ return [
+ 'class' => 'underline text-blue-500 hover:no-underline',
+ ];
+ }),
+ LinkColumn::make('Edit')
+ ->title(fn($row) => 'Edit ' . $row->name)
+ ->location(fn($row) => route('user.edit', $row))
+ ->attributes(function($row) {
+ return [
+ 'target' => '_blank',
+ 'class' => 'underline text-blue-500 hover:no-underline',
+ ];
+ }),
+ ]),
+```
diff --git a/docs/column-types/color_columns.md b/docs/column-types/color_columns.md
new file mode 100644
index 000000000..e4920144d
--- /dev/null
+++ b/docs/column-types/color_columns.md
@@ -0,0 +1,41 @@
+---
+title: Color Columns
+weight: 5
+---
+
+Color columns provide an easy way to a Color in a Column
+
+You may pass either pass a CSS-compliant colour as a field
+```php
+ColorColumn::make('Favourite Colour', 'favourite_color'),
+```
+
+Or you may use a Callback
+```php
+ColorColumn::make('Favourite Colour')
+ ->color(
+ function ($row) {
+ if ($row->success_rate < 40)
+ {
+ return '#ff0000';
+ }
+ else if ($row->success_rate > 90)
+ {
+ return '#008000';
+ }
+ else return '#ffa500';
+
+ }
+ ),
+```
+
+You may also specify attributes to use on the div displaying the color, to adjust the size or appearance, this receives the full row. By default, this will replace the standard classes, to retain them, set "default" to true. To then over-ride, you should prefix your classes with "!" to signify importance.
+```php
+ ColorColumn::make('Favourite Colour')
+ ->attributes(function ($row) {
+ return [
+ 'class' => '!rounded-lg self-center',
+ 'default' => true,
+ ];
+ }),
+```
diff --git a/docs/column-types/component_column.md b/docs/column-types/component_column.md
new file mode 100644
index 000000000..0db9c9483
--- /dev/null
+++ b/docs/column-types/component_column.md
@@ -0,0 +1,28 @@
+---
+title: Component Columns
+weight: 6
+---
+
+Component columns let you specify a component name and attributes and provides the column value to the slot.
+
+```php
+// Before
+Column::make("Email", "email")
+ ->format(function ($value) {
+ return view('components.alert')
+ ->with('attributes', new ComponentAttributeBag([
+ 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger',
+ 'dismissible' => true,
+ ]))
+ ->with('slot', $value);
+ }),
+
+// After
+ComponentColumn::make('E-mail', 'email')
+ ->component('email')
+ ->attributes(fn ($value, $row, Column $column) => [
+ 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger',
+ 'dismissible' => true,
+ ]),
+```
+
diff --git a/docs/column-types/count_column.md b/docs/column-types/count_column.md
new file mode 100644
index 000000000..d33cf5df9
--- /dev/null
+++ b/docs/column-types/count_column.md
@@ -0,0 +1,14 @@
+---
+title: Count Columns (beta)
+weight: 7
+---
+
+Count columns provide an easy way to display the "Count" of a relation.
+
+```
+ CountColumn::make('Related Users')
+ ->setDataSource('users')
+ ->sortable(),
+```
+
+The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on.
\ No newline at end of file
diff --git a/docs/column-types/date_columns.md b/docs/column-types/date_columns.md
new file mode 100644
index 000000000..abc8bbb42
--- /dev/null
+++ b/docs/column-types/date_columns.md
@@ -0,0 +1,28 @@
+---
+title: Date Columns
+weight: 8
+---
+
+Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views.
+
+You may pass either a DateTime object, in which you can define an "outputFormat"
+```php
+DateColumn::make('Updated At', 'updated_at')
+ ->outputFormat('Y-m-d H:i:s),
+```
+
+Or you may pass a string, in which case you can define an "inputFormat" in addition to the outputFormat:
+```php
+DateColumn::make('Last Charged', 'last_charged_at')
+ ->inputFormat('Y-m-d H:i:s')
+ ->outputFormat('Y-m-d'),
+```
+
+You may also set an "emptyValue" to use when there is no value from the database:
+```php
+DateColumn::make('Last Charged', 'last_charged_at')
+ ->inputFormat('Y-m-d H:i:s')
+ ->outputFormat('Y-m-d')
+ ->emptyValue('Not Found'),
+```
+
diff --git a/docs/column-types/image_columns.md b/docs/column-types/image_columns.md
new file mode 100644
index 000000000..f816280af
--- /dev/null
+++ b/docs/column-types/image_columns.md
@@ -0,0 +1,26 @@
+---
+title: Image Columns
+weight: 9
+---
+
+Image columns provide a way to display images in your table without having to use `format()` or partial views:
+
+```php
+ImageColumn::make('Avatar')
+ ->location(
+ fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg')
+ ),
+```
+
+You may also pass an array of attributes to apply to the image tag:
+
+```php
+ImageColumn::make('Avatar')
+ ->location(
+ fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg')
+ )
+ ->attributes(fn($row) => [
+ 'class' => 'rounded-full',
+ 'alt' => $row->name . ' Avatar',
+ ]),
+```
diff --git a/docs/column-types/link_columns.md b/docs/column-types/link_columns.md
new file mode 100644
index 000000000..cba4bc887
--- /dev/null
+++ b/docs/column-types/link_columns.md
@@ -0,0 +1,24 @@
+---
+title: Link Columns
+weight: 10
+---
+
+Link columns provide a way to display HTML links in your table without having to use `format()` or partial views:
+
+```php
+LinkColumn::make('Action')
+ ->title(fn($row) => 'Edit')
+ ->location(fn($row) => route('admin.users.edit', $row)),
+```
+
+You may also pass an array of attributes to apply to the `a` tag:
+
+```php
+LinkColumn::make('Action')
+ ->title(fn($row) => 'Edit')
+ ->location(fn($row) => route('admin.users.edit', $row))
+ ->attributes(fn($row) => [
+ 'class' => 'rounded-full',
+ 'alt' => $row->name . ' Avatar',
+ ]),
+```
diff --git a/docs/column-types/livewire_component_column.md b/docs/column-types/livewire_component_column.md
new file mode 100644
index 000000000..b75172881
--- /dev/null
+++ b/docs/column-types/livewire_component_column.md
@@ -0,0 +1,5 @@
+---
+title: Livewire Component (beta)
+weight: 11
+---
+
diff --git a/docs/column-types/sum_column.md b/docs/column-types/sum_column.md
new file mode 100644
index 000000000..7f99f7cab
--- /dev/null
+++ b/docs/column-types/sum_column.md
@@ -0,0 +1,14 @@
+---
+title: Sum Columns (beta)
+weight: 12
+---
+
+Sum columns provide an easy way to display the "Sum" of a field on a relation.
+
+```
+ SumColumn::make('Total Age of Related Users')
+ ->setDataSource('users','age')
+ ->sortable(),
+```
+
+The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on.
\ No newline at end of file
diff --git a/docs/columns/other-column-types.md b/docs/columns/other-column-types.md
index dc80da449..2e515eedf 100644
--- a/docs/columns/other-column-types.md
+++ b/docs/columns/other-column-types.md
@@ -3,247 +3,14 @@ title: Other Column Types
weight: 4
---
-## Boolean Columns
-Boolean columns are good if you have a column type that is a true/false, or 0/1 value.
-For example:
-```php
-BooleanColumn::make('Active')
-```
-Would yield:
+## Aggregate Columns
-
+### AvgColumn
-### Using your own view
+### CountColumn
-If you don't want to use the default view and icons you can set your own:
-
-```php
-BooleanColumn::make('Active')
- ->setView('my.active.view')
-```
-
-You will have access to `$component`, `$status`, and `$successValue`.
-
-To help you better understand, this is the Tailwind implementation of BooleanColumn:
-
-```html
-@if ($status)
-
-@else
-
-@endif
-```
-
-### Setting the truthy value
-
-If you want the false value to be the green option, you can set:
-
-```php
-BooleanColumn::make('Active')
- ->setSuccessValue(false); // Makes false the 'successful' option
-```
-
-That would swap the colors of the icons in the image above.
-
-### Setting the status value
-
-By default, the `$status` is set to:
-
-```php
-(bool)$value === true
-```
-
-You can override this functionality:
-
-```php
-BooleanColumn::make('Active')
- // Note: Parameter `$row` available as of v2.4
- ->setCallback(function(string $value, $row) {
- // Figure out what makes $value true
- }),
-```
-
-### Different types of boolean display
-
-By default, the BooleanColumn displays icons.
-
-If you would like the BooleanColumn to display a plain Yes/No, you can set:
-
-```php
-BooleanColumn::make('Active')
- ->yesNo()
-```
-## Color Columns
-
-Color columns provide an easy way to a Color in a Column
-
-You may pass either pass a CSS-compliant colour as a field
-```php
-ColorColumn::make('Favourite Colour', 'favourite_color'),
-```
-
-Or you may use a Callback
-```php
-ColorColumn::make('Favourite Colour')
- ->color(
- function ($row) {
- if ($row->success_rate < 40)
- {
- return '#ff0000';
- }
- else if ($row->success_rate > 90)
- {
- return '#008000';
- }
- else return '#ffa500';
-
- }
- ),
-```
-
-You may also specify attributes to use on the div displaying the color, to adjust the size or appearance, this receives the full row. By default, this will replace the standard classes, to retain them, set "default" to true. To then over-ride, you should prefix your classes with "!" to signify importance.
-```php
- ColorColumn::make('Favourite Colour')
- ->attributes(function ($row) {
- return [
- 'class' => '!rounded-lg self-center',
- 'default' => true,
- ];
- }),
-```
-
-## Date Columns
-
-Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views.
-
-You may pass either a DateTime object, in which you can define an "outputFormat"
-```php
-DateColumn::make('Updated At', 'updated_at')
- ->outputFormat('Y-m-d H:i:s),
-```
-
-Or you may pass a string, in which case you can define an "inputFormat" in addition to the outputFormat:
-```php
-DateColumn::make('Last Charged', 'last_charged_at')
- ->inputFormat('Y-m-d H:i:s')
- ->outputFormat('Y-m-d'),
-```
-
-You may also set an "emptyValue" to use when there is no value from the database:
-```php
-DateColumn::make('Last Charged', 'last_charged_at')
- ->inputFormat('Y-m-d H:i:s')
- ->outputFormat('Y-m-d')
- ->emptyValue('Not Found'),
-```
-
-## Image Columns
-
-Image columns provide a way to display images in your table without having to use `format()` or partial views:
-
-```php
-ImageColumn::make('Avatar')
- ->location(
- fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg')
- ),
-```
-
-You may also pass an array of attributes to apply to the image tag:
-
-```php
-ImageColumn::make('Avatar')
- ->location(
- fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg')
- )
- ->attributes(fn($row) => [
- 'class' => 'rounded-full',
- 'alt' => $row->name . ' Avatar',
- ]),
-```
-
-## Link Columns
-
-Link columns provide a way to display HTML links in your table without having to use `format()` or partial views:
-
-```php
-LinkColumn::make('Action')
- ->title(fn($row) => 'Edit')
- ->location(fn($row) => route('admin.users.edit', $row)),
-```
-
-You may also pass an array of attributes to apply to the `a` tag:
-
-```php
-LinkColumn::make('Action')
- ->title(fn($row) => 'Edit')
- ->location(fn($row) => route('admin.users.edit', $row))
- ->attributes(fn($row) => [
- 'class' => 'rounded-full',
- 'alt' => $row->name . ' Avatar',
- ]),
-```
-
-## Button Group Columns
-
-Button group columns let you provide an array of LinkColumns to display in a single cell.
-
-```php
-ButtonGroupColumn::make('Actions')
- ->attributes(function($row) {
- return [
- 'class' => 'space-x-2',
- ];
- })
- ->buttons([
- LinkColumn::make('View') // make() has no effect in this case but needs to be set anyway
- ->title(fn($row) => 'View ' . $row->name)
- ->location(fn($row) => route('user.show', $row))
- ->attributes(function($row) {
- return [
- 'class' => 'underline text-blue-500 hover:no-underline',
- ];
- }),
- LinkColumn::make('Edit')
- ->title(fn($row) => 'Edit ' . $row->name)
- ->location(fn($row) => route('user.edit', $row))
- ->attributes(function($row) {
- return [
- 'target' => '_blank',
- 'class' => 'underline text-blue-500 hover:no-underline',
- ];
- }),
- ]),
-```
-
-## Component Columns
-
-Component columns let you specify a component name and attributes and provides the column value to the slot.
-
-```php
-// Before
-Column::make("Email", "email")
- ->format(function ($value) {
- return view('components.alert')
- ->with('attributes', new ComponentAttributeBag([
- 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger',
- 'dismissible' => true,
- ]))
- ->with('slot', $value);
- }),
-
-// After
-ComponentColumn::make('E-mail', 'email')
- ->component('email')
- ->attributes(fn ($value, $row, Column $column) => [
- 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger',
- 'dismissible' => true,
- ]),
-```
+### SumColumn
\ No newline at end of file
diff --git a/docs/examples/_index.md b/docs/examples/_index.md
index a831d1a26..03b1d982b 100644
--- a/docs/examples/_index.md
+++ b/docs/examples/_index.md
@@ -1,4 +1,4 @@
---
title: Examples
-weight: 14
+weight: 16
---
diff --git a/docs/filter-types/_index.md b/docs/filter-types/_index.md
index 6c6d8ccf4..3631183ba 100644
--- a/docs/filter-types/_index.md
+++ b/docs/filter-types/_index.md
@@ -1,4 +1,4 @@
---
title: Filter Types
-weight: 11
+weight: 12
---
diff --git a/docs/filters/_index.md b/docs/filters/_index.md
index 43af373bd..e0ac299f1 100644
--- a/docs/filters/_index.md
+++ b/docs/filters/_index.md
@@ -1,4 +1,4 @@
---
title: Filters
-weight: 10
+weight: 11
---
diff --git a/docs/footer/_index.md b/docs/footer/_index.md
index e16f7efb5..4edcb2460 100644
--- a/docs/footer/_index.md
+++ b/docs/footer/_index.md
@@ -1,4 +1,4 @@
---
title: Footer
-weight: 13
+weight: 15
---
diff --git a/docs/misc/_index.md b/docs/misc/_index.md
index 9f806b32e..193fabdf4 100644
--- a/docs/misc/_index.md
+++ b/docs/misc/_index.md
@@ -1,4 +1,4 @@
---
title: Misc.
-weight: 15
+weight: 17
---
diff --git a/docs/pagination/_index.md b/docs/pagination/_index.md
index 723cb0c11..50491640d 100644
--- a/docs/pagination/_index.md
+++ b/docs/pagination/_index.md
@@ -1,4 +1,4 @@
---
title: Pagination
-weight: 7
+weight: 8
---
diff --git a/docs/reordering/_index.md b/docs/reordering/_index.md
index bc21a450b..e66081c28 100644
--- a/docs/reordering/_index.md
+++ b/docs/reordering/_index.md
@@ -1,4 +1,4 @@
---
title: Reordering
-weight: 11
+weight: 13
---
diff --git a/docs/rows/_index.md b/docs/rows/_index.md
index f731c19d2..7d5ebba99 100644
--- a/docs/rows/_index.md
+++ b/docs/rows/_index.md
@@ -1,4 +1,4 @@
---
title: Rows
-weight: 5
+weight: 6
---
diff --git a/docs/search/_index.md b/docs/search/_index.md
index edb174fde..4aac1542b 100644
--- a/docs/search/_index.md
+++ b/docs/search/_index.md
@@ -1,4 +1,4 @@
---
title: Search
-weight: 8
+weight: 9
---
diff --git a/docs/secondary-header/_index.md b/docs/secondary-header/_index.md
index 5e2b9aba5..f334c9e68 100644
--- a/docs/secondary-header/_index.md
+++ b/docs/secondary-header/_index.md
@@ -1,4 +1,4 @@
---
title: Secondary Header
-weight: 12
+weight: 14
---
diff --git a/docs/sorting/_index.md b/docs/sorting/_index.md
index a99eabf1d..422739fb6 100644
--- a/docs/sorting/_index.md
+++ b/docs/sorting/_index.md
@@ -1,4 +1,4 @@
---
title: Sorting
-weight: 6
+weight: 7
---
diff --git a/phpstan.neon b/phpstan.neon
index a0324459d..7ff21a707 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -15,10 +15,13 @@ parameters:
ignoreErrors:
- identifier: missingType.generics
- identifier: missingType.iterableValue
+ - '#Property Rappasoft\\LaravelLivewireTables\\DataTableComponent\:\:\$model has no type specified#'
+ - '#Unable to resolve the template type TMapWithKeysKey in call to method Illuminate\\Support\\Collection#'
+ - '#Unable to resolve the template type TMapWithKeysValue in call to method Illuminate\\Support\\Collection#'
- '#Access to an undefined property Rappasoft\\LaravelLivewireTables\\Views\\Column\:\:\$view#'
- "#Unsafe usage of new static#"
- '#on array\, mixed\>\> in empty\(\) does not exist.#'
- '#on array, mixed>> in isset\(\) does not exist#'
- - '#on non-empty-array<1|string, array, mixed>> in isset\(\) does not exist.#'
+ - '#on non-empty-array<1\|string, array, mixed>> in isset\(\) does not exist.#'
- '#\$callback of method Illuminate\\Support\\Collection::filter\(\) expects \(callable\(string, int\): bool\)\|null, Closure\(mixed\): int<0, max> given.#'
- '#Property Illuminate\\Database\\Query\\Builder\:\:\$joins \(array\) on left side of \?\? is not nullable.#'
diff --git a/src/Commands/MakeCommand.php b/src/Commands/MakeCommand.php
index cdaa67130..8338a1216 100644
--- a/src/Commands/MakeCommand.php
+++ b/src/Commands/MakeCommand.php
@@ -5,6 +5,7 @@
use Illuminate\Console\Command;
use Illuminate\Contracts\Console\PromptsForMissingInput;
use Illuminate\Database\Eloquent\Model;
+use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Str;
use Livewire\Features\SupportConsoleCommands\Commands\ComponentParser;
@@ -97,10 +98,7 @@ protected function createClass(bool $force = false): bool
return $classPath;
}
- /**
- * @param mixed $path
- */
- protected function ensureDirectoryExists($path): void
+ protected function ensureDirectoryExists(string $path): void
{
if (! File::isDirectory(dirname($path))) {
File::makeDirectory(dirname($path), 0777, true, true);
@@ -147,7 +145,7 @@ public function getModelImport(): string
/*
* Credits to Harm Smits: https://stackoverflow.com/a/67099502/2263114
*/
- private function getClassesList($file): array
+ private function getClassesList(string $file): array
{
$classes = [];
$namespace = '';
@@ -215,7 +213,7 @@ private function generateColumns(string $modelName): string
return $columns;
}
- protected function possibleModels()
+ protected function possibleModels(): array
{
$modelPath = is_dir(app_path('Models')) ? app_path('Models') : app_path();
@@ -226,7 +224,7 @@ protected function possibleModels()
->all();
}
- protected function promptForMissingArguments(InputInterface $input, OutputInterface $output)
+ protected function promptForMissingArguments(InputInterface $input, OutputInterface $output): void
{
if ($this->didReceiveOptions($input)) {
diff --git a/src/Mechanisms/RappasoftFrontendAssets.php b/src/Mechanisms/RappasoftFrontendAssets.php
index 62889d0ff..d460f5431 100644
--- a/src/Mechanisms/RappasoftFrontendAssets.php
+++ b/src/Mechanisms/RappasoftFrontendAssets.php
@@ -56,7 +56,7 @@ public function boot(): void
}
- protected function registerBladeDirectives()
+ protected function registerBladeDirectives(): void
{
Blade::directive('rappasoftTableScripts', [static::class, 'rappasoftTableScripts']);
Blade::directive('rappasoftTableStyles', [static::class, 'rappasoftTableStyles']);
diff --git a/src/Traits/ComponentUtilities.php b/src/Traits/ComponentUtilities.php
index 1e421e0b6..8ec4bd8b6 100644
--- a/src/Traits/ComponentUtilities.php
+++ b/src/Traits/ComponentUtilities.php
@@ -37,6 +37,14 @@ trait ComponentUtilities
protected array $additionalSelects = [];
+ protected array $extraWiths = [];
+
+ protected array $extraWithCounts = [];
+
+ protected array $extraWithSums = [];
+
+ protected array $extraWithAvgs = [];
+
/**
* Set any configuration options
*/
diff --git a/src/Traits/Configuration/ComponentConfiguration.php b/src/Traits/Configuration/ComponentConfiguration.php
index 90611185a..044f5655e 100644
--- a/src/Traits/Configuration/ComponentConfiguration.php
+++ b/src/Traits/Configuration/ComponentConfiguration.php
@@ -96,4 +96,60 @@ public function setDataTableFingerprint(string $dataTableFingerprint): self
return $this;
}
+
+ public function setExtraWiths(array $extraWiths): self
+ {
+ $this->extraWiths = $extraWiths;
+
+ return $this;
+ }
+
+ public function addExtraWith(string $extraWith): self
+ {
+ $this->extraWiths[] = $extraWith;
+
+ return $this;
+ }
+
+ public function addExtraWiths(array $extraWiths): self
+ {
+ $this->extraWiths = [...$this->extraWiths, ...$extraWiths];
+
+ return $this;
+ }
+
+ public function setExtraWithCounts(array $extraWithCounts): self
+ {
+ $this->extraWithCounts = $extraWithCounts;
+
+ return $this;
+ }
+
+ public function addExtraWithCount(string $extraWithCount): self
+ {
+ $this->extraWithCounts[] = $extraWithCount;
+
+ return $this;
+ }
+
+ public function addExtraWithCounts(array $extraWithCounts): self
+ {
+ $this->extraWithCounts = [...$this->extraWithCounts, ...$extraWithCounts];
+
+ return $this;
+ }
+
+ public function addExtraWithSum(string $relationship, string $column): self
+ {
+ $this->extraWithSums[] = ['table' => $relationship, 'field' => $column];
+
+ return $this;
+ }
+
+ public function addExtraWithAvg(string $relationship, string $column): self
+ {
+ $this->extraWithAvgs[] = ['table' => $relationship, 'field' => $column];
+
+ return $this;
+ }
}
diff --git a/src/Traits/Configuration/FilterConfiguration.php b/src/Traits/Configuration/FilterConfiguration.php
index 9eac5d75f..3367c9168 100644
--- a/src/Traits/Configuration/FilterConfiguration.php
+++ b/src/Traits/Configuration/FilterConfiguration.php
@@ -121,7 +121,7 @@ public function generateFilterGenericData(): array
return (new FilterGenericData($this))->toArray();
}
- public function setFilterGenericData(array $filterGenericData = [])
+ public function setFilterGenericData(array $filterGenericData = []): void
{
$this->filterGenericData = $filterGenericData;
}
diff --git a/src/Traits/Helpers/ColumnHelpers.php b/src/Traits/Helpers/ColumnHelpers.php
index 009a6f4bd..920d63fe6 100644
--- a/src/Traits/Helpers/ColumnHelpers.php
+++ b/src/Traits/Helpers/ColumnHelpers.php
@@ -4,6 +4,7 @@
use Illuminate\Support\Collection;
use Rappasoft\LaravelLivewireTables\Views\Column;
+use Rappasoft\LaravelLivewireTables\Views\Columns\AggregateColumn;
trait ColumnHelpers
{
@@ -18,6 +19,15 @@ public function setColumns(): void
->filter(fn ($column) => $column instanceof Column)
->map(function (Column $column) {
$column->setComponent($this);
+ if ($column instanceof AggregateColumn) {
+ if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) {
+ $this->addExtraWithCount($column->getDataSource());
+ } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) {
+ $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn());
+ } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) {
+ $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn());
+ }
+ }
if ($column->hasField()) {
if ($column->isBaseColumn()) {
@@ -198,6 +208,15 @@ public function getPrependedColumns(): Collection
->filter(fn ($column) => $column instanceof Column)
->map(function (Column $column) {
$column->setComponent($this);
+ if ($column instanceof AggregateColumn) {
+ if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) {
+ $this->addExtraWithCount($column->getDataSource());
+ } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) {
+ $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn());
+ } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) {
+ $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn());
+ }
+ }
if ($column->hasField()) {
if ($column->isBaseColumn()) {
@@ -217,6 +236,15 @@ public function getAppendedColumns(): Collection
->filter(fn ($column) => $column instanceof Column)
->map(function (Column $column) {
$column->setComponent($this);
+ if ($column instanceof AggregateColumn) {
+ if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) {
+ $this->addExtraWithCount($column->getDataSource());
+ } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) {
+ $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn());
+ } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) {
+ $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn());
+ }
+ }
if ($column->hasField()) {
if ($column->isBaseColumn()) {
diff --git a/src/Traits/Helpers/ComponentHelpers.php b/src/Traits/Helpers/ComponentHelpers.php
index c600200b5..96eae35f8 100644
--- a/src/Traits/Helpers/ComponentHelpers.php
+++ b/src/Traits/Helpers/ComponentHelpers.php
@@ -149,4 +149,44 @@ public function getAdditionalSelects(): array
{
return $this->additionalSelects;
}
+
+ public function hasExtraWiths(): bool
+ {
+ return ! empty($this->extraWiths);
+ }
+
+ public function getExtraWiths(): array
+ {
+ return $this->extraWiths;
+ }
+
+ public function hasExtraWithCounts(): bool
+ {
+ return ! empty($this->extraWithCounts);
+ }
+
+ public function getExtraWithCounts(): array
+ {
+ return $this->extraWithCounts;
+ }
+
+ public function hasExtraWithSums(): bool
+ {
+ return ! empty($this->extraWithSums);
+ }
+
+ public function getExtraWithSums(): array
+ {
+ return $this->extraWithSums;
+ }
+
+ public function hasExtraWithAvgs(): bool
+ {
+ return ! empty($this->extraWithAvgs);
+ }
+
+ public function getExtraWithAvgs(): array
+ {
+ return $this->extraWithAvgs;
+ }
}
diff --git a/src/Traits/Helpers/FilterHelpers.php b/src/Traits/Helpers/FilterHelpers.php
index 55c824fad..d4c043531 100644
--- a/src/Traits/Helpers/FilterHelpers.php
+++ b/src/Traits/Helpers/FilterHelpers.php
@@ -312,7 +312,7 @@ public function hasFilterGenericData(): bool
return ! empty($this->filterGenericData);
}
- public function getFilterGenericData()
+ public function getFilterGenericData(): array
{
if (! $this->hasFilterGenericData()) {
$this->setFilterGenericData($this->generateFilterGenericData());
diff --git a/src/Traits/Helpers/FooterHelpers.php b/src/Traits/Helpers/FooterHelpers.php
index 37e8276bd..df220e3c0 100644
--- a/src/Traits/Helpers/FooterHelpers.php
+++ b/src/Traits/Helpers/FooterHelpers.php
@@ -47,7 +47,7 @@ public function useHeaderAsFooterIsDisabled(): bool
*/
public function getFooterTrAttributes($rows): array
{
- return $this->footerTrAttributesCallback ? call_user_func($this->footerTrAttributesCallback, $rows) : ['default' => true];
+ return isset($this->footerTrAttributesCallback) ? call_user_func($this->footerTrAttributesCallback, $rows) : ['default' => true];
}
/**
@@ -56,6 +56,6 @@ public function getFooterTrAttributes($rows): array
*/
public function getFooterTdAttributes(Column $column, $rows, int $index): array
{
- return $this->footerTdAttributesCallback ? call_user_func($this->footerTdAttributesCallback, $column, $rows, $index) : ['default' => true];
+ return isset($this->footerTdAttributesCallback) ? call_user_func($this->footerTdAttributesCallback, $column, $rows, $index) : ['default' => true];
}
}
diff --git a/src/Traits/Helpers/SecondaryHeaderHelpers.php b/src/Traits/Helpers/SecondaryHeaderHelpers.php
index cc7694002..6af852483 100644
--- a/src/Traits/Helpers/SecondaryHeaderHelpers.php
+++ b/src/Traits/Helpers/SecondaryHeaderHelpers.php
@@ -32,7 +32,7 @@ public function secondaryHeaderIsDisabled(): bool
*/
public function getSecondaryHeaderTrAttributes($rows): array
{
- return $this->secondaryHeaderTrAttributesCallback ? call_user_func($this->secondaryHeaderTrAttributesCallback, $rows) : ['default' => true];
+ return isset($this->secondaryHeaderTrAttributesCallback) ? call_user_func($this->secondaryHeaderTrAttributesCallback, $rows) : ['default' => true];
}
/**
@@ -41,6 +41,6 @@ public function getSecondaryHeaderTrAttributes($rows): array
*/
public function getSecondaryHeaderTdAttributes(Column $column, $rows, int $index): array
{
- return $this->secondaryHeaderTdAttributesCallback ? call_user_func($this->secondaryHeaderTdAttributesCallback, $column, $rows, $index) : ['default' => true];
+ return isset($this->secondaryHeaderTdAttributesCallback) ? call_user_func($this->secondaryHeaderTdAttributesCallback, $column, $rows, $index) : ['default' => true];
}
}
diff --git a/src/Traits/Helpers/TableAttributeHelpers.php b/src/Traits/Helpers/TableAttributeHelpers.php
index 866e72526..ffebab51c 100644
--- a/src/Traits/Helpers/TableAttributeHelpers.php
+++ b/src/Traits/Helpers/TableAttributeHelpers.php
@@ -52,7 +52,7 @@ public function getTbodyAttributes(): array
*/
public function getThAttributes(Column $column): array
{
- return $this->thAttributesCallback ? call_user_func($this->thAttributesCallback, $column) : ['default' => true];
+ return isset($this->thAttributesCallback) ? call_user_func($this->thAttributesCallback, $column) : ['default' => true];
}
/**
@@ -60,7 +60,7 @@ public function getThAttributes(Column $column): array
*/
public function getThSortButtonAttributes(Column $column): array
{
- return $this->thSortButtonAttributesCallback ? call_user_func($this->thSortButtonAttributesCallback, $column) : ['default' => true];
+ return isset($this->thSortButtonAttributesCallback) ? call_user_func($this->thSortButtonAttributesCallback, $column) : ['default' => true];
}
/**
@@ -68,7 +68,7 @@ public function getThSortButtonAttributes(Column $column): array
*/
public function getTrAttributes(Model $row, int $index): array
{
- return $this->trAttributesCallback ? call_user_func($this->trAttributesCallback, $row, $index) : ['default' => true];
+ return isset($this->trAttributesCallback) ? call_user_func($this->trAttributesCallback, $row, $index) : ['default' => true];
}
/**
@@ -76,21 +76,21 @@ public function getTrAttributes(Model $row, int $index): array
*/
public function getTdAttributes(Column $column, Model $row, int $colIndex, int $rowIndex): array
{
- return $this->tdAttributesCallback ? call_user_func($this->tdAttributesCallback, $column, $row, $colIndex, $rowIndex) : ['default' => true];
+ return isset($this->tdAttributesCallback) ? call_user_func($this->tdAttributesCallback, $column, $row, $colIndex, $rowIndex) : ['default' => true];
}
public function hasTableRowUrl(): bool
{
- return $this->trUrlCallback !== null;
+ return isset($this->trUrlCallback);
}
public function getTableRowUrl(int|Model $row): ?string
{
- return $this->trUrlCallback ? call_user_func($this->trUrlCallback, $row) : null;
+ return isset($this->trUrlCallback) ? call_user_func($this->trUrlCallback, $row) : null;
}
public function getTableRowUrlTarget(int|Model $row): ?string
{
- return $this->trUrlTargetCallback ? call_user_func($this->trUrlTargetCallback, $row) : null;
+ return isset($this->trUrlTargetCallback) ? call_user_func($this->trUrlTargetCallback, $row) : null;
}
}
diff --git a/src/Traits/WithData.php b/src/Traits/WithData.php
index 6d3261c93..23d1055bb 100644
--- a/src/Traits/WithData.php
+++ b/src/Traits/WithData.php
@@ -56,6 +56,29 @@ protected function baseQuery(): Builder
$this->setBuilder($this->applyFilters());
+ $builder = $this->getBuilder();
+
+ if ($this->hasExtraWiths()) {
+ $builder->with($this->getExtraWiths());
+ }
+
+ if ($this->hasExtraWithSums()) {
+ foreach ($this->getExtraWithSums() as $extraSum) {
+ $builder->withSum($extraSum['table'], $extraSum['field']);
+ }
+ }
+ if ($this->hasExtraWithAvgs()) {
+ foreach ($this->getExtraWithAvgs() as $extraAvg) {
+ $builder->withAvg($extraAvg['table'], $extraAvg['field']);
+ }
+ }
+
+ if ($this->hasExtraWithCounts()) {
+ $builder->withCount($this->getExtraWithCounts());
+ }
+
+ $this->setBuilder($builder);
+
return $this->getBuilder();
}
@@ -175,7 +198,7 @@ protected function joinRelation(Column $column): Builder
return $this->getBuilder();
}
- protected function performJoin($table, $foreign, $other, $type = 'left'): Builder
+ protected function performJoin(string $table, string $foreign, string $other, string $type = 'left'): Builder
{
$joins = [];
@@ -249,7 +272,8 @@ protected function getTableAlias(?string $currentTableAlias, string $relationPar
public function builder(): Builder
{
if ($this->hasModel()) {
- return $this->getModel()::query()->with($this->getRelationships());
+ return $this->getModel()::query()
+ ->with($this->getRelationships());
}
// If model does not exist
diff --git a/src/Traits/WithEvents.php b/src/Traits/WithEvents.php
index 448dcc69f..dca549f2a 100644
--- a/src/Traits/WithEvents.php
+++ b/src/Traits/WithEvents.php
@@ -4,7 +4,7 @@
trait WithEvents
{
- public function setSortEvent($field, $direction): void
+ public function setSortEvent(string $field, string $direction): void
{
$this->setSort($field, $direction);
}
@@ -14,7 +14,7 @@ public function clearSortEvent(): void
$this->clearSorts();
}
- public function setFilterEvent($filter, $value): void
+ public function setFilterEvent(string $filter, string $value): void
{
$this->setFilter($filter, $value);
}
diff --git a/src/Traits/WithFilters.php b/src/Traits/WithFilters.php
index ec6c7aac2..878c660ce 100644
--- a/src/Traits/WithFilters.php
+++ b/src/Traits/WithFilters.php
@@ -3,6 +3,7 @@
namespace Rappasoft\LaravelLivewireTables\Traits;
use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Collection;
use Rappasoft\LaravelLivewireTables\Traits\Configuration\FilterConfiguration;
use Rappasoft\LaravelLivewireTables\Traits\Helpers\FilterHelpers;
@@ -23,7 +24,7 @@ trait WithFilters
public int $filterCount;
- protected $filterCollection;
+ protected ?Collection $filterCollection;
public array $filterComponents = [];
diff --git a/src/Traits/WithFooter.php b/src/Traits/WithFooter.php
index 376f2340b..b1035cf29 100644
--- a/src/Traits/WithFooter.php
+++ b/src/Traits/WithFooter.php
@@ -16,9 +16,9 @@ trait WithFooter
protected bool $columnsWithFooter = false;
- protected $footerTrAttributesCallback;
+ protected ?object $footerTrAttributesCallback;
- protected $footerTdAttributesCallback;
+ protected ?object $footerTdAttributesCallback;
public function setupFooter(): void
{
diff --git a/src/Traits/WithPagination.php b/src/Traits/WithPagination.php
index 6f022a9fb..946bed2cb 100644
--- a/src/Traits/WithPagination.php
+++ b/src/Traits/WithPagination.php
@@ -53,7 +53,7 @@ public function mountWithPagination(): void
}
// TODO: Test
- public function updatedPerPage($value): void
+ public function updatedPerPage(int|string $value): void
{
if (! in_array((int) $value, $this->getPerPageAccepted(), false)) {
$value = $this->getPerPageAccepted()[0] ?? 10;
diff --git a/src/Traits/WithSecondaryHeader.php b/src/Traits/WithSecondaryHeader.php
index 94022b5f1..875d0ae03 100644
--- a/src/Traits/WithSecondaryHeader.php
+++ b/src/Traits/WithSecondaryHeader.php
@@ -14,9 +14,9 @@ trait WithSecondaryHeader
protected bool $columnsWithSecondaryHeader = false;
- protected $secondaryHeaderTrAttributesCallback;
+ protected ?object $secondaryHeaderTrAttributesCallback;
- protected $secondaryHeaderTdAttributesCallback;
+ protected ?object $secondaryHeaderTdAttributesCallback;
public function bootedWithSecondaryHeader(): void
{
diff --git a/src/Traits/WithTableAttributes.php b/src/Traits/WithTableAttributes.php
index f75f75f9e..b58c6baf0 100644
--- a/src/Traits/WithTableAttributes.php
+++ b/src/Traits/WithTableAttributes.php
@@ -21,15 +21,15 @@ trait WithTableAttributes
protected array $tbodyAttributes = [];
- protected $thAttributesCallback;
+ protected ?object $thAttributesCallback;
- protected $thSortButtonAttributesCallback;
+ protected ?object $thSortButtonAttributesCallback;
- protected $trAttributesCallback;
+ protected ?object $trAttributesCallback;
- protected $tdAttributesCallback;
+ protected ?object $tdAttributesCallback;
- protected $trUrlCallback;
+ protected ?object $trUrlCallback;
- protected $trUrlTargetCallback;
+ protected ?object $trUrlTargetCallback;
}
diff --git a/src/Traits/WithTableHooks.php b/src/Traits/WithTableHooks.php
index 160713e56..eb0f18fd8 100644
--- a/src/Traits/WithTableHooks.php
+++ b/src/Traits/WithTableHooks.php
@@ -6,14 +6,14 @@
trait WithTableHooks
{
- public function callHook($name, $params = [])
+ public function callHook(string $name, array $params = []): void
{
if (method_exists($this, $name)) {
wrap($this)->__call($name, $params);
}
}
- public function callTraitHook($name, $params = [])
+ public function callTraitHook(string $name, array $params = []): void
{
foreach (class_uses_recursive($this) as $trait) {
$method = $name.class_basename($trait);
diff --git a/src/Views/Columns/AggregateColumn.php b/src/Views/Columns/AggregateColumn.php
new file mode 100644
index 000000000..a284b08ff
--- /dev/null
+++ b/src/Views/Columns/AggregateColumn.php
@@ -0,0 +1,23 @@
+label(fn () => null);
+ }
+}
diff --git a/src/Views/Columns/AvgColumn.php b/src/Views/Columns/AvgColumn.php
new file mode 100644
index 000000000..3af197873
--- /dev/null
+++ b/src/Views/Columns/AvgColumn.php
@@ -0,0 +1,18 @@
+label(fn () => null);
+ }
+}
diff --git a/src/Views/Columns/CountColumn.php b/src/Views/Columns/CountColumn.php
new file mode 100644
index 000000000..4a1ffd812
--- /dev/null
+++ b/src/Views/Columns/CountColumn.php
@@ -0,0 +1,18 @@
+label(fn () => null);
+ }
+}
diff --git a/src/Views/Columns/SumColumn.php b/src/Views/Columns/SumColumn.php
new file mode 100644
index 000000000..a01822d7b
--- /dev/null
+++ b/src/Views/Columns/SumColumn.php
@@ -0,0 +1,18 @@
+label(fn () => null);
+ }
+}
diff --git a/src/Views/Filters/MultiSelectDropdownFilter.php b/src/Views/Filters/MultiSelectDropdownFilter.php
index ae784b58c..3022ddba5 100644
--- a/src/Views/Filters/MultiSelectDropdownFilter.php
+++ b/src/Views/Filters/MultiSelectDropdownFilter.php
@@ -2,6 +2,7 @@
namespace Rappasoft\LaravelLivewireTables\Views\Filters;
+use Illuminate\Support\Collection;
use Rappasoft\LaravelLivewireTables\Views\Filter;
use Rappasoft\LaravelLivewireTables\Views\Traits\Core\HasWireables;
use Rappasoft\LaravelLivewireTables\Views\Traits\Filters\{HasOptions,IsArrayFilter};
@@ -42,7 +43,7 @@ public function getFilterPillValue($value): ?string
foreach ($value as $item) {
$found = $this->getCustomFilterPillValue($item)
- ?? collect($this->getOptions())
+ ?? (new Collection($this->getOptions()))
->mapWithKeys(fn ($options, $optgroupLabel) => is_iterable($options) ? $options : [$optgroupLabel => $options])[$item]
?? null;
@@ -54,7 +55,7 @@ public function getFilterPillValue($value): ?string
return implode(', ', $values);
}
- public function isEmpty($value): bool
+ public function isEmpty(mixed $value): bool
{
if (! is_array($value)) {
return true;
diff --git a/src/Views/Filters/SelectFilter.php b/src/Views/Filters/SelectFilter.php
index a62260dd9..0f247ada4 100644
--- a/src/Views/Filters/SelectFilter.php
+++ b/src/Views/Filters/SelectFilter.php
@@ -2,6 +2,7 @@
namespace Rappasoft\LaravelLivewireTables\Views\Filters;
+use Illuminate\Support\Collection;
use Rappasoft\LaravelLivewireTables\Views\Filter;
use Rappasoft\LaravelLivewireTables\Views\Traits\Core\HasWireables;
use Rappasoft\LaravelLivewireTables\Views\Traits\Filters\{HasOptions,IsStringFilter};
@@ -22,8 +23,8 @@ class SelectFilter extends Filter
public function getKeys(): array
{
- return collect($this->getOptions())
- ->map(fn ($value, $key) => is_iterable($value) ? collect($value)->keys() : $key)
+ return (new Collection($this->getOptions()))
+ ->map(fn ($value, $key) => is_iterable($value) ? (new Collection($value))->keys() : $key)
->flatten()
->map(fn ($value) => (string) $value)
->filter(fn ($value) => strlen($value) > 0)
@@ -42,8 +43,9 @@ public function validate(string $value): array|string|bool
public function getFilterPillValue($value): ?string
{
+
return $this->getCustomFilterPillValue($value)
- ?? collect($this->getOptions())
+ ?? (new Collection($this->getOptions()))
->mapWithKeys(fn ($options, $optgroupLabel) => is_iterable($options) ? $options : [$optgroupLabel => $options])[$value]
?? null;
}
diff --git a/src/Views/Traits/Configuration/AggregateColumnConfiguration.php b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php
new file mode 100644
index 000000000..2f181ed2f
--- /dev/null
+++ b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php
@@ -0,0 +1,59 @@
+dataSource = $dataSource;
+
+ if (isset($foreignColumn)) {
+ $this->setForeignColumn($foreignColumn);
+ }
+
+ $this->setDefaultLabel();
+
+ return $this;
+ }
+
+ public function setAggregateMethod(string $aggregateMethod): self
+ {
+ $this->aggregateMethod = $aggregateMethod;
+ $this->setDefaultLabel();
+
+ return $this;
+ }
+
+ public function setForeignColumn(string $foreignColumn): self
+ {
+ $this->foreignColumn = $foreignColumn;
+ $this->setDefaultLabel();
+
+ return $this;
+ }
+
+ public function setDefaultLabel(): void
+ {
+ $this->label(function ($row, Column $column) {
+ if ($this->hasForeignColumn()) {
+ return $row->{$this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn()};
+ }
+
+ return $row->{$this->getDataSource().'_'.$this->getAggregateMethod()};
+ });
+
+ }
+
+ public function sortable(?callable $callback = null): self
+ {
+ $this->sortable = true;
+
+ $this->sortCallback = ($callback === null) ? ($this->hasForeignColumn() ? fn (Builder $query, string $direction) => $query->orderBy($this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn(), $direction) : fn (Builder $query, string $direction) => $query->orderBy($this->dataSource.'_count', $direction)) : $callback;
+
+ return $this;
+ }
+}
diff --git a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php
index 72188fd79..c0f7657b7 100644
--- a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php
+++ b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php
@@ -26,4 +26,14 @@ public function outputFormat(callable $callable): self
return $this;
}
+
+ /**
+ * Define the Empty Value to use for the Column
+ */
+ public function emptyValue(string $emptyValue): self
+ {
+ $this->emptyValue = $emptyValue;
+
+ return $this;
+ }
}
diff --git a/src/Views/Traits/Helpers/AggregateColumnHelpers.php b/src/Views/Traits/Helpers/AggregateColumnHelpers.php
new file mode 100644
index 000000000..1748b8091
--- /dev/null
+++ b/src/Views/Traits/Helpers/AggregateColumnHelpers.php
@@ -0,0 +1,46 @@
+dataSource;
+ }
+
+ public function hasDataSource(): bool
+ {
+ return isset($this->dataSource);
+ }
+
+ public function getAggregateMethod(): string
+ {
+ return $this->aggregateMethod;
+ }
+
+ public function hasForeignColumn(): bool
+ {
+ return isset($this->foreignColumn);
+ }
+
+ public function getForeignColumn(): string
+ {
+ return $this->foreignColumn;
+ }
+
+ public function getContents(Model $row): null|string|\BackedEnum|HtmlString|DataTableConfigurationException|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
+ {
+ if (! isset($this->dataSource)) {
+ throw new DataTableConfigurationException('You must specify a data source');
+ } else {
+ return parent::getContents($row);
+ }
+ }
+}
diff --git a/src/Views/Traits/Helpers/ArrayColumnHelpers.php b/src/Views/Traits/Helpers/ArrayColumnHelpers.php
index 7c32f7c70..8289038e4 100644
--- a/src/Views/Traits/Helpers/ArrayColumnHelpers.php
+++ b/src/Views/Traits/Helpers/ArrayColumnHelpers.php
@@ -48,10 +48,6 @@ public function getContents(Model $row): null|string|\BackedEnum|HtmlString|Data
$outputValues = [];
$value = $this->getValue($row);
- if (! $this->hasSeparator()) {
- throw new DataTableConfigurationException('You must set a valid separator on an ArrayColumn');
- }
-
if (! $this->hasDataCallback()) {
throw new DataTableConfigurationException('You must set a data() method on an ArrayColumn');
}
diff --git a/src/Views/Traits/Helpers/ColorColumnHelpers.php b/src/Views/Traits/Helpers/ColorColumnHelpers.php
index 00f017e9e..d437243e7 100644
--- a/src/Views/Traits/Helpers/ColorColumnHelpers.php
+++ b/src/Views/Traits/Helpers/ColorColumnHelpers.php
@@ -2,12 +2,13 @@
namespace Rappasoft\LaravelLivewireTables\Views\Traits\Helpers;
+use Illuminate\Database\Eloquent\Model;
use Illuminate\View\ComponentAttributeBag;
trait ColorColumnHelpers
{
// TODO: Test
- public function getColor($row): string
+ public function getColor(Model|int $row): string
{
return $this->hasColorCallback() ? app()->call($this->getColorCallback(), ['row' => $row]) : ($this->getValue($row));
}
diff --git a/src/Views/Traits/Helpers/ColumnHelpers.php b/src/Views/Traits/Helpers/ColumnHelpers.php
index a43d21681..fb2fbfb38 100644
--- a/src/Views/Traits/Helpers/ColumnHelpers.php
+++ b/src/Views/Traits/Helpers/ColumnHelpers.php
@@ -131,7 +131,7 @@ public function getContents(Model $row): null|string|\BackedEnum|HtmlString|Data
}
// TODO: Test
- public function getValue(Model $row)
+ public function getValue(Model $row): mixed
{
if ($this->isBaseColumn()) {
return $row->{$this->getField()};
diff --git a/src/Views/Traits/IsAggregateColumn.php b/src/Views/Traits/IsAggregateColumn.php
new file mode 100644
index 000000000..1824118b9
--- /dev/null
+++ b/src/Views/Traits/IsAggregateColumn.php
@@ -0,0 +1,14 @@
+setPrimaryKey('id');
+ }
+
+ public function columns(): array
+ {
+ return [
+ Column::make('ID', 'id')
+ ->sortable()
+ ->setSortingPillTitle('Key')
+ ->setSortingPillDirections('0-9', '9-0'),
+ Column::make('Name')
+ ->sortable()
+ ->searchable(),
+ AvgColumn::make('Average Age')
+ ->setDataSource('pets', 'age'),
+ CountColumn::make('Number of Pets')
+ ->setDataSource('pets'),
+ SumColumn::make('Total Age')
+ ->setDataSource('pets', 'age'),
+
+ ];
+ }
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
index fdcbd44a6..ab22227bd 100644
--- a/tests/TestCase.php
+++ b/tests/TestCase.php
@@ -9,7 +9,7 @@
use Livewire\LivewireServiceProvider;
use Orchestra\Testbench\TestCase as Orchestra;
use Rappasoft\LaravelLivewireTables\LaravelLivewireTablesServiceProvider;
-use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\{PetsTable,PetsTableUnpaginated};
+use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\{PetsTable,PetsTableUnpaginated,SpeciesTable};
use Rappasoft\LaravelLivewireTables\Tests\Models\Breed;
use Rappasoft\LaravelLivewireTables\Tests\Models\Pet;
use Rappasoft\LaravelLivewireTables\Tests\Models\Species;
@@ -19,6 +19,8 @@ class TestCase extends Orchestra
{
public PetsTable $basicTable;
+ public SpeciesTable $speciesTable;
+
public PetsTableUnpaginated $unpaginatedTable;
/**
@@ -76,7 +78,7 @@ protected function setUp(): void
}
$this->setupBasicTable();
$this->setupUnpaginatedTable();
-
+ $this->setupSpeciesTable();
}
protected function setupBasicTable()
@@ -95,6 +97,22 @@ protected function setupBasicTable()
$this->basicTable->render();
}
+ protected function setupSpeciesTable()
+ {
+ $view = view('livewire-tables::datatable');
+ $this->speciesTable = new SpeciesTable();
+ $this->speciesTable->boot();
+ $this->speciesTable->bootedComponentUtilities();
+ $this->speciesTable->bootedWithData();
+ $this->speciesTable->bootedWithColumns();
+ $this->speciesTable->bootedWithColumnSelect();
+ $this->speciesTable->bootedWithSecondaryHeader();
+ $this->speciesTable->booted();
+ $this->speciesTable->renderingWithData($view, []);
+ $this->speciesTable->renderingWithPagination($view, []);
+ $this->speciesTable->render();
+ }
+
protected function setupUnpaginatedTable()
{
diff --git a/tests/Traits/Configuration/ComponentConfigurationTest.php b/tests/Traits/Configuration/ComponentConfigurationTest.php
index 001cea2a0..3bed87443 100644
--- a/tests/Traits/Configuration/ComponentConfigurationTest.php
+++ b/tests/Traits/Configuration/ComponentConfigurationTest.php
@@ -300,4 +300,106 @@ public function test_can_set_hide_configurable_areas_when_reordering_status(): v
$this->basicTable->setHideConfigurableAreasWhenReorderingStatus(true);
}
+
+ public function test_no_extra_withs_by_default(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWiths());
+ $this->assertEmpty($this->basicTable->getExtraWiths());
+ }
+
+ public function test_can_add_extra_with(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWiths());
+ $this->assertEmpty($this->basicTable->getExtraWiths());
+ $this->basicTable->addExtraWith('user');
+ $this->assertTrue($this->basicTable->hasExtraWiths());
+ $this->assertSame(['user'], $this->basicTable->getExtraWiths());
+ }
+
+ public function test_can_add_extra_withs(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWiths());
+ $this->assertEmpty($this->basicTable->getExtraWiths());
+ $this->basicTable->addExtraWiths(['user', 'pets']);
+ $this->assertTrue($this->basicTable->hasExtraWiths());
+ $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWiths());
+ }
+
+ public function test_can_set_extra_withs(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWiths());
+ $this->assertEmpty($this->basicTable->getExtraWiths());
+ $this->basicTable->addExtraWith('test');
+ $this->assertSame(['test'], $this->basicTable->getExtraWiths());
+ $this->assertTrue($this->basicTable->hasExtraWiths());
+ $this->basicTable->setExtraWiths(['user', 'pets']);
+ $this->assertTrue($this->basicTable->hasExtraWiths());
+ $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWiths());
+ }
+
+ public function test_no_extra_with_counts_by_default(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithCounts());
+ $this->assertEmpty($this->basicTable->getExtraWithCounts());
+ }
+
+ public function test_can_add_extra_with_count(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithCounts());
+ $this->assertEmpty($this->basicTable->getExtraWithCounts());
+ $this->basicTable->addExtraWithCount('users');
+ $this->assertTrue($this->basicTable->hasExtraWithCounts());
+ $this->assertSame(['users'], $this->basicTable->getExtraWithCounts());
+ }
+
+ public function test_can_add_extra_with_counts(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithCounts());
+ $this->assertEmpty($this->basicTable->getExtraWithCounts());
+ $this->basicTable->addExtraWithCounts(['user', 'pets']);
+ $this->assertTrue($this->basicTable->hasExtraWithCounts());
+ $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWithCounts());
+ }
+
+ public function test_can_set_extra_with_counts(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithCounts());
+ $this->assertEmpty($this->basicTable->getExtraWithCounts());
+ $this->basicTable->addExtraWithCount('test');
+ $this->assertSame(['test'], $this->basicTable->getExtraWithCounts());
+ $this->assertTrue($this->basicTable->hasExtraWithCounts());
+ $this->basicTable->setExtraWithCounts(['user', 'pets']);
+ $this->assertTrue($this->basicTable->hasExtraWithCounts());
+ $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWithCounts());
+ }
+
+ public function test_no_extra_with_sums_by_default(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithSums());
+ $this->assertEmpty($this->basicTable->getExtraWithSums());
+ }
+
+ public function test_can_add_extra_with_sum(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithSums());
+ $this->assertEmpty($this->basicTable->getExtraWithSums());
+ $this->basicTable->addExtraWithSum('users', 'age');
+ $this->assertTrue($this->basicTable->hasExtraWithSums());
+ $this->assertSame([['table' => 'users', 'field' => 'age']], $this->basicTable->getExtraWithSums());
+ }
+
+ public function test_no_extra_with_avgs_by_default(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWiths());
+ $this->assertEmpty($this->basicTable->getExtraWiths());
+ }
+
+ public function test_can_add_extra_with_avg(): void
+ {
+ $this->assertFalse($this->basicTable->hasExtraWithAvgs());
+ $this->assertEmpty($this->basicTable->getExtraWithAvgs());
+ $this->basicTable->addExtraWithAvg('user', 'age');
+ $this->assertTrue($this->basicTable->hasExtraWithAvgs());
+ $this->assertSame([['table' => 'user', 'field' => 'age']], $this->basicTable->getExtraWithAvgs());
+ }
}
diff --git a/tests/Views/Columns/ArrayColumnTest.php b/tests/Views/Columns/ArrayColumnTest.php
new file mode 100644
index 000000000..0eff2808b
--- /dev/null
+++ b/tests/Views/Columns/ArrayColumnTest.php
@@ -0,0 +1,87 @@
+assertSame('Array Col', $column->getTitle());
+ }
+
+ public function test_can_set_the_separator(): void
+ {
+ $column = ArrayColumn::make('Array Col');
+
+ $this->assertSame('
', $column->getSeparator());
+ $column->separator('
');
+ $this->assertTrue($column->hasSeparator());
+
+ $this->assertSame('
', $column->getSeparator());
+ }
+
+ public function test_can_set_the_output_format(): void
+ {
+ $column = ArrayColumn::make('Array Col');
+
+ $this->assertNull($column->getOutputFormatCallback());
+ $this->assertFalse($column->hasOutputFormatCallback());
+ $column->outputFormat(fn ($index, $value) => "".$value->name.'');
+ $this->assertTrue($column->hasOutputFormatCallback());
+ }
+
+ public function test_requires_the_data_callback(): void
+ {
+ $this->expectException(DataTableConfigurationException::class);
+ $column = ArrayColumn::make('Average Age')
+ ->separator('
')
+ ->sortable();
+ $contents = $column->getContents(Pet::find(1));
+ $this->assertNull($contents);
+ }
+
+ public function test_can_get_the_output_format_callback(): void
+ {
+ $this->expectException(DataTableConfigurationException::class);
+ $column = ArrayColumn::make('Average Age')
+ ->separator('
')
+ ->data(fn ($value, $row) => ($row->pets))
+ ->sortable();
+ $this->assertNotNull($column->getDataCallback());
+
+ $contents = $column->getContents(Pet::find(1));
+ $this->assertNull($contents);
+ }
+
+ public function test_requires_the_output_format_callback(): void
+ {
+ $this->expectException(DataTableConfigurationException::class);
+ $column = ArrayColumn::make('Average Age')
+ ->separator('
')
+ ->data(fn ($value, $row) => ($row->pets))
+ ->sortable();
+
+ $contents = $column->getContents(Pet::find(1));
+ $this->assertNull($contents);
+ }
+
+ public function test_can_get_empty_value(): void
+ {
+ $column = ArrayColumn::make('Average Age')
+ ->separator('
')
+ ->data(fn ($value, $row) => ($row->pets))
+ ->sortable();
+
+ $this->assertSame('', $column->getEmptyValue());
+ $column->emptyValue('Unknown');
+ $this->assertSame('Unknown', $column->getEmptyValue());
+
+ }
+}
diff --git a/tests/Views/Columns/AvgColumnTest.php b/tests/Views/Columns/AvgColumnTest.php
new file mode 100644
index 000000000..f3657c710
--- /dev/null
+++ b/tests/Views/Columns/AvgColumnTest.php
@@ -0,0 +1,111 @@
+assertSame('Average Age', $column->getTitle());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_setup_column_correctly(string $relation_name, string $foreign_field): void
+ {
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+
+ $this->assertNotEmpty($column);
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_not_skip_set_data_source(string $relation_name, string $foreign_field): void
+ {
+ $this->expectException(DataTableConfigurationException::class);
+
+ $column = AvgColumn::make('Average Age')
+ ->sortable();
+ $contents = $column->getContents(Pet::find(1));
+ $this->assertNull($contents);
+
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_data_source(string $relation_name, string $foreign_field): void
+ {
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasDataSource());
+ $this->assertSame($relation_name, $column->getDataSource());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_foreign_column(string $relation_name, string $foreign_field): void
+ {
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame($foreign_field, $column->getForeignColumn());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_set_foreign_column(string $relation_name, string $foreign_field): void
+ {
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame($foreign_field, $column->getForeignColumn());
+ $column->setForeignColumn('test');
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame('test', $column->getForeignColumn());
+
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_data_source_fields(string $relation_name, string $foreign_field): void
+ {
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasDataSource());
+ $this->assertSame($relation_name, $column->getDataSource());
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame($foreign_field, $column->getForeignColumn());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_aggregate_method(string $relation_name, string $foreign_field): void
+ {
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertSame('avg', $column->getAggregateMethod());
+ $column->setAggregateMethod('test_avg');
+ $this->assertSame('test_avg', $column->getAggregateMethod());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_renders_correctly(string $relation_name, string $foreign_field): void
+ {
+ $rows = $this->speciesTable->getRows();
+ $column = AvgColumn::make('Average Age')
+ ->setDataSource('pets', 'age');
+ $contents = $column->getContents($rows->first());
+ $this->assertSame('15', $contents);
+ $contents = $column->getContents($rows[2]);
+ $this->assertSame('6', $contents);
+ }
+}
diff --git a/tests/Views/Columns/CountColumnTest.php b/tests/Views/Columns/CountColumnTest.php
new file mode 100644
index 000000000..6c3d1e772
--- /dev/null
+++ b/tests/Views/Columns/CountColumnTest.php
@@ -0,0 +1,50 @@
+assertSame('Total Users', $column->getTitle());
+ }
+
+ public function test_can_setup_column_correctly(): void
+ {
+ $column = CountColumn::make('Total Users')
+ ->setDataSource('users')
+ ->sortable();
+
+ $this->assertNotEmpty($column);
+ }
+
+ public function test_can_not_skip_set_data_source(): void
+ {
+ $this->expectException(DataTableConfigurationException::class);
+
+ $column = CountColumn::make('Average Age')
+ ->sortable();
+ $contents = $column->getContents(Pet::find(1));
+ $this->assertNull($contents);
+
+ }
+
+ public function test_renders_correctly(): void
+ {
+ $rows = $this->speciesTable->getRows();
+ $row1 = $rows->first();
+ $column = CountColumn::make('Pets')
+ ->setDataSource('pets');
+ $contents = $column->getContents($rows->first());
+ $this->assertSame('2', $contents);
+ $contents = $column->getContents($rows->last());
+ $this->assertSame('0', $contents);
+ }
+}
diff --git a/tests/Views/Columns/SumColumnTest.php b/tests/Views/Columns/SumColumnTest.php
new file mode 100644
index 000000000..9d5cbff51
--- /dev/null
+++ b/tests/Views/Columns/SumColumnTest.php
@@ -0,0 +1,111 @@
+assertSame('Sum User Age', $column->getTitle());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_setup_column_correctly(string $relation_name, string $foreign_field): void
+ {
+ $column = SumColumn::make('Sum User Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+
+ $this->assertNotEmpty($column);
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_not_skip_set_data_source(string $relation_name, string $foreign_field): void
+ {
+ $this->expectException(DataTableConfigurationException::class);
+
+ $column = SumColumn::make('Sum User Age')
+ ->sortable();
+ $contents = $column->getContents(Pet::find(1));
+ $this->assertNull($contents);
+
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_set_foreign_column(string $relation_name, string $foreign_field): void
+ {
+ $column = SumColumn::make('Sum User Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame($foreign_field, $column->getForeignColumn());
+ $column->setForeignColumn('test');
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame('test', $column->getForeignColumn());
+
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_data_source(string $relation_name, string $foreign_field): void
+ {
+ $column = SumColumn::make('Sum User Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasDataSource());
+ $this->assertSame($relation_name, $column->getDataSource());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_foreign_column(string $relation_name, string $foreign_field): void
+ {
+ $column = SumColumn::make('Sum User Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame($foreign_field, $column->getForeignColumn());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_data_source_fields(string $relation_name, string $foreign_field): void
+ {
+ $column = SumColumn::make('Sum User Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertTrue($column->hasDataSource());
+ $this->assertSame($relation_name, $column->getDataSource());
+ $this->assertTrue($column->hasForeignColumn());
+ $this->assertSame($foreign_field, $column->getForeignColumn());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_can_get_aggregate_method(string $relation_name, string $foreign_field): void
+ {
+ $column = SumColumn::make('Sum User Age')
+ ->setDataSource($relation_name, $foreign_field)
+ ->sortable();
+ $this->assertSame('sum', $column->getAggregateMethod());
+ $column->setAggregateMethod('test_sum');
+ $this->assertSame('test_sum', $column->getAggregateMethod());
+ }
+
+ #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')]
+ public function test_renders_correctly(string $relation_name, string $foreign_field): void
+ {
+ $rows = $this->speciesTable->getRows();
+ $column = SumColumn::make('Total Age')
+ ->setDataSource('pets', 'age');
+ $contents = $column->getContents($rows->first());
+ $this->assertSame('30', $contents);
+ $contents = $column->getContents($rows[2]);
+ $this->assertSame('12', $contents);
+ }
+}