changeset 32:45f384a24553 codex

Support for server side tables
author Luka Sitas <sitas.luka.97@gmail.com>
date Tue, 19 Aug 2025 22:15:50 -0400
parents 8dd668020310
children 93adaad3ca65
files src/Generator/Controller/stubs/controller.stub src/Generator/Route/stubs/routes.stub src/Generator/View/IndexViewGenerator.php src/Generator/View/snippets/index/value.stub src/Generator/View/stubs/index.stub
diffstat 5 files changed, 154 insertions(+), 125 deletions(-) [+]
line wrap: on
line diff
--- a/src/Generator/Controller/stubs/controller.stub	Tue Aug 19 20:34:53 2025 -0400
+++ b/src/Generator/Controller/stubs/controller.stub	Tue Aug 19 22:15:50 2025 -0400
@@ -24,6 +24,15 @@
         return view('{{ tableName }}.index', $data);
     }
 
+    public function get_data()
+    {
+        $data = [];
+        $data['records'] = {{ model }}::get_data([]);
+        $data['total_records'] = count($data['records']);
+
+        return $data;
+    }
+
     /**
      * Show the form for creating a new resource.
      *
--- a/src/Generator/Route/stubs/routes.stub	Tue Aug 19 20:34:53 2025 -0400
+++ b/src/Generator/Route/stubs/routes.stub	Tue Aug 19 22:15:50 2025 -0400
@@ -6,7 +6,8 @@
 	->prefix('{{ tableName }}') 
 	->as('{{ tableName }}.')
 	->group( function () {
-		Route::get('/', 'index')->name('index');
+        Route::get('/', 'index')->name('index');
+        Route::post('/get_data', 'get_data')->name('get_data');
 		Route::get('/create', 'create')->name('create');
 		Route::get('/{{{ modelVariable }}}/edit', 'edit')->name('edit');
 		Route::get('/{{{ modelVariable }}}', 'show')->name('show');
--- a/src/Generator/View/IndexViewGenerator.php	Tue Aug 19 20:34:53 2025 -0400
+++ b/src/Generator/View/IndexViewGenerator.php	Tue Aug 19 22:15:50 2025 -0400
@@ -2,10 +2,10 @@
 
 namespace Wizard\MagicForger\Generator\View;
 
+use Illuminate\Support\Str;
 use Symfony\Component\Console\Attribute\AsCommand;
+use Wizard\MagicForger\Generator\BaseGenerator;
 use Wizard\MagicForger\Helpers\RelationshipNavigator;
-use Wizard\MagicForger\Generator\BaseGenerator;
-use Illuminate\Support\Str;
 
 #[AsCommand(name: 'mf:index_view')]
 class IndexViewGenerator extends BaseGenerator
@@ -76,91 +76,77 @@
     {
         return str_replace(['Resources\\', '\\'], ['resources/', '/'], $this->getViewNamespace($this->getTableInput()).'index.blade.php');
     }
-		
+
+    protected function renderColumns()
+    {
+        $renders = [];
+        $values = [];
 
-		protected function renderColumns() {
-				$renders = [];
-				$headers = [];
-				$values = [];
-				$colCount = 0;
+        $columns = $this->getTableColumns($this->getCurrentTable());
+        $relations = RelationshipNavigator::getRelations($this->getCurrentTable());
+        // gether the select columns based on the relations
+        $selects = [];
 
-				$columns = $this->getTableColumns($this->getCurrentTable());
-        $relations = RelationshipNavigator::getRelations($this->getCurrentTable());
-				//gether the select columns based on the relations
-				$selects = [];
+        foreach ($relations['belongsTo'] as $relation) {
+            $selects[$relation['column']] = $relation['table'];
+        }
 
-				foreach($relations['belongsTo'] as $relation) {
-					$selects[$relation['column']] = $relation['table'];
-				}
-
-				foreach($columns as $column) {
-					$name = $column['name'];
-					if(in_array($name, $this->columns_to_ignore)) continue;
-
+        foreach ($columns as $column) {
+            $name = $column['name'];
+            if (in_array($name, $this->columns_to_ignore)) {
+                continue;
+            }
 
-					//Get the expected header name
-					$replacements = [
-						'{{header}}' => Str::headline($name),
-						'{{headerClass}}' => 'p-2',
-						'{{value}}' => '{{ $item->' . $name . ' ?? "" }}',
-						'{{valueClass}}' => 'p-2',
-					];
+            // Get the expected header name
+            $replacements = [
+                '{{header}}' => Str::headline($name),
+                '{{column_name}}' => $name,
+                '{{valueClass}}' => 'p-2',
+            ];
 
-					$type = $column['type_name'];
-
+            $type = $column['type_name'];
 
-					//date
-					if(in_array($type, ['date'])) {
-						$replacements['{{value}}'] = '{{ $item->'.$name.'?->format(\'Y-m-d\') ?? "" }}';
-					}
-					//time
-					if(in_array($type, ['timestamp'])) {
-						$replacements['{{value}}'] = '{{ $item->'.$name.'?->format(\'Y-m-d H:i\') ?? "" }}';
-					}
-					//checkbox
-					if(in_array($type, ['tinyint'])) {
-						$replacements['{{valueClass}}'] .= ' text-center';
-						$replacements['{{value}}'] = '{{ $item->' . $name . ' ?? "0" }}';
-					}
-					//select
-					elseif(in_array($type, ['bigint']) && array_key_exists($name, $selects)) {
-						$replacements['{{header}}'] = Str::headline(Str::singular($selects[$name]));
-						$replacements['{{value}}'] = '{{ $item->'.Str::singular($selects[$name]) . '?->name ?? "" }}';
-					}
-					// bigint, float
-					elseif(in_array($type, ['bigint', 'float', 'int'])) {
-						$replacements['{{valueClass}}'] .= ' text-start';
-					}
-					else {
-						//text area
-						//varchar, , etc
-					}
+            // date
+            if (in_array($type, ['date'])) {
+                $replacements['{{value}}'] = '{{ $item->'.$name.'?->format(\'Y-m-d\') ?? "" }}';
+            }
+            // time
+            if (in_array($type, ['timestamp'])) {
+                $replacements['{{value}}'] = '{{ $item->'.$name.'?->format(\'Y-m-d H:i\') ?? "" }}';
+            }
+            // checkbox
+            if (in_array($type, ['tinyint'])) {
+                $replacements['{{valueClass}}'] .= ' text-center';
+                $replacements['{{value}}'] = '{{ $item->'.$name.' ?? "0" }}';
+            }
+            // select
+            elseif (in_array($type, ['bigint']) && array_key_exists($name, $selects)) {
+                $replacements['{{header}}'] = Str::headline(Str::singular($selects[$name]));
+                $replacements['{{value}}'] = '{{ $item->'.Str::singular($selects[$name]).'?->name ?? "" }}';
+            }
+            // bigint, float
+            elseif (in_array($type, ['bigint', 'float', 'int'])) {
+                $replacements['{{valueClass}}'] .= ' text-start';
+            } else {
+                // text area
+                // varchar, , etc
+            }
 
-					$snippet = $this->getSnippet('index/value');	
-					// Replace placeholders with actual values
-        	$values[] = str_replace(
-							array_keys($replacements),
-							$replacements,
-        	    $snippet
-        	);
+            $snippet = $this->getSnippet('index/value');
+            // Replace placeholders with actual values
+            $values[] = str_replace(
+                array_keys($replacements),
+                $replacements,
+                $snippet
+            );
 
-					$snippet = $this->getSnippet('index/header');	
-					// Replace placeholders with actual values
-        	$headers[] = str_replace(
-							array_keys($replacements),
-							$replacements,
-        	    $snippet
-        	);
-					$colCount++;
-				}
+        }
 
-			return ['headers' => $headers, 'values' => $values, 'colCount' => $colCount];
-		}
+        return ['values' => $values];
+    }
 
     /**
      * Get available insertions including model relationships.
-     *
-     * @return array
      */
     public function get_available_inserts(): array
     {
@@ -171,20 +157,16 @@
         $rendered = $this->renderColumns();
 
         // Build code blocks for each relation type
-				$headers = '';
-				$values = '';
-				$colCount = '';
-				
-				if(!empty($rendered)) {
-					$headers = !empty($rendered['headers']) ? implode("\n    ", $rendered['headers']) : '';
-					$values = !empty($rendered['values']) ? implode("\n    ", $rendered['values']) : '';
-					$colCount = isset($rendered['colCount']) ? $rendered['colCount'] : '';
-				}
+        $headers = '';
+        $values = '';
+        $colCount = '';
+
+        if (! empty($rendered)) {
+            $values = ! empty($rendered['values']) ? implode("\n    ", $rendered['values']) : '';
+        }
 
         // Assign to stub placeholders
-        $inserts['{{ headerInsertPoint }}'] = $headers;
-        $inserts['{{ valueInsertPoint }}'] = $values;
-        $inserts['{{ colCount }}'] = $colCount;
+        $inserts['{{ columnInsertPoint }}'] = $values;
 
         return $inserts;
     }
--- a/src/Generator/View/snippets/index/value.stub	Tue Aug 19 20:34:53 2025 -0400
+++ b/src/Generator/View/snippets/index/value.stub	Tue Aug 19 22:15:50 2025 -0400
@@ -1,1 +1,5 @@
-<td class="{{valueClass}}">{{value}}</td>
+{
+    name: '{{column_name}}',
+    label: '{{header}}',
+    class: '{{valueClass}}'
+},
--- a/src/Generator/View/stubs/index.stub	Tue Aug 19 20:34:53 2025 -0400
+++ b/src/Generator/View/stubs/index.stub	Tue Aug 19 22:15:50 2025 -0400
@@ -8,43 +8,76 @@
     <div class="py-5">
         <div class="container">
             <div class="bg-white shadow-sm rounded p-4">
-            	    <div class="d-flex align-items-center justify-content-between mb-3">
-            	        <span></span>
-            	        <a href="{{ route('{{ tableName }}.create') }}" class="btn btn-primary">
-            	            + New {{ ucfirst('{{ modelVariable }}') }}
-            	        </a>
-            	    </div>
-            	    <table class="table table-hover">
-            	        <thead>
-            	            <tr>
-            	                {{ headerInsertPoint }}
-            	                <th scope="col">Actions</th>
-            	            </tr>
-            	        </thead>
-            	        <tbody>
-            	            @forelse ($items as $item)
-            	                <tr>
-            	                    {{ valueInsertPoint }}
-            	                    <td>
-            	                        <div class="d-flex gap-2"> 
-            	                            <a href="{{ route('{{ tableName }}.show', $item) }}" class="text-primary">View</a>
-            	                            <a href="{{ route('{{ tableName }}.edit', $item) }}" class="text-warning">Edit</a>
-            	                            <form action="{{ route('{{ tableName }}.destroy', $item) }}" method="POST" class="d-inline">
-            	                                @csrf
-            	                                @method('DELETE')
-            	                                <button type="submit" class="btn btn-link text-danger p-0" onclick="return confirm('Delete?')">Delete</button>
-            	                            </form>
-            	                        </div>
-            	                    </td>
-            	                </tr>
-            	            @empty
-            	                <tr>
-            	                    <td colspan="{{ colCount }}">No {{ tableName }} found.</td>
-            	                </tr>
-            	            @endforelse
-            	        </tbody>
-            	    </table>
+            	<div class="d-flex align-items-center justify-content-between mb-3">
+            	    <a href="{{ route('{{ tableName }}.create') }}" class="btn btn-primary">
+            	        + New {{ ucfirst('{{ modelVariable }}') }}
+            	    </a>
             	</div>
+                <div id="my-table"></div>
+            </div>
         </div>
     </div>
+
+    @include('includes.ServerTable')
+    @pushOnce('scripts')
+        <meta name="csrf-token" content="{{ csrf_token() }}">
+    @endpushOnce
+    <script>
+        document.addEventListener('DOMContentLoaded', function() {
+            const el = document.getElementById('my-table');
+            if (el) {
+                new ServerTable(el, {
+                    endpoint: '/{{ tableName }}/get_data',
+                    columns: [
+            	        {{ columnInsertPoint }}
+                        {
+                            name: 'actions',
+                            label: ' ',
+                            render: function(row, col, i) {
+                                let actions = `
+                                    <div class="d-flex gap-2">
+                                        <a href="{{ route('{{ tableName }}.show', 'PLACEHOLDER') }}" class="text-primary">View</a>
+                                        <a href="{{ route('{{ tableName }}.edit', 'PLACEHOLDER') }}" class="text-warning">Edit</a>
+                                        <form action="{{ route('{{ tableName }}.destroy', 'PLACEHOLDER') }}" method="POST"
+                                            class="d-inline">
+                                            @csrf
+                                            @method('DELETE')
+                                            <button type="submit" class="btn btn-link text-danger p-0"
+                                                onclick="return confirm('Delete?')">Delete</button>
+                                        </form>
+                                    </div>
+                                    `;
+                                actions = actions.split('PLACEHOLDER').join(row.id);
+                                return actions;
+                            }
+                        }
+                    ],
+                    pageSize: 10,
+                    initialSort: [{
+                        col: 'created_at',
+                        dir: 'desc'
+                    }],
+                    headers: {
+                        'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute(
+                            'content')
+                    },
+                    skeleton: `
+            <div class="st-table-container">
+                <table class="table table-hover">
+                    <thead>
+                    </thead>
+                    <tbody>
+                        <!-- Data rows will go here -->
+                    </tbody>
+                </table>
+                <div class="st-controls">
+                    <span class="st-pagination"></span>
+                    <span class="st-status"></span>
+                </div>
+            </div>
+        `
+                });
+            }
+        });
+    </script>
 </x-app-layout>