Files
rspade_system/app/RSpade/Commands/Rsx/Component_Create_Command.php
root 9ebcc359ae Fix code quality violations and enhance ROUTE-EXISTS-01 rule
Implement JQHTML function cache ID system and fix bundle compilation
Implement underscore prefix for system tables
Fix JS syntax linter to support decorators and grant exception to Task system
SPA: Update planning docs and wishlists with remaining features
SPA: Document Navigation API abandonment and future enhancements
Implement SPA browser integration with History API (Phase 1)
Convert contacts view page to SPA action
Convert clients pages to SPA actions and document conversion procedure
SPA: Merge GET parameters and update documentation
Implement SPA route URL generation in JavaScript and PHP
Implement SPA bootstrap controller architecture
Add SPA routing manual page (rsx:man spa)
Add SPA routing documentation to CLAUDE.md
Phase 4 Complete: Client-side SPA routing implementation
Update get_routes() consumers for unified route structure
Complete SPA Phase 3: PHP-side route type detection and is_spa flag
Restore unified routes structure and Manifest_Query class
Refactor route indexing and add SPA infrastructure
Phase 3 Complete: SPA route registration in manifest
Implement SPA Phase 2: Extract router code and test decorators
Rename Jqhtml_Component to Component and complete SPA foundation setup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-19 17:48:15 +00:00

299 lines
10 KiB
PHP
Executable File

<?php
namespace App\RSpade\Commands\Rsx;
use Illuminate\Console\Command;
class Component_Create_Command extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'rsx:app:component:create
{--name= : Component name (lowercase with underscores, must end in _component)}
{--path= : Target directory path (e.g., rsx/theme/components)}
{--module= : Module name to create component in}
{--feature= : Feature name within module (requires --module)}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Create a new jqhtml component with template and JavaScript class';
/**
* Execute the console command.
*
* @return int
*/
public function handle()
{
// Get and validate component name
$component_name = $this->option('name');
if (!$component_name) {
$this->error('Component name is required. Use --name=my_widget_component');
$this->info('');
$this->info('Usage examples:');
$this->info(' php artisan rsx:app:component:create --name=my_widget_component --path=rsx/theme/components');
$this->info(' php artisan rsx:app:component:create --name=user_card_component --module=dashboard');
$this->info(' php artisan rsx:app:component:create --name=form_field_component --module=backend --feature=users');
return 1;
}
// Validate component name format
if (!preg_match('/^[a-z_]+_component$/', $component_name)) {
$this->error('Component name must be lowercase with underscores and end with "_component"');
$this->info('Example: my_widget_component, user_card_component, form_field_component');
return 1;
}
// Determine target directory
$target_dir = $this->determine_target_directory();
if (!$target_dir) {
return 1; // Error already displayed
}
// Create directory if it doesn't exist
if (!is_dir($target_dir)) {
if (!mkdir($target_dir, 0755, true)) {
$this->error("Failed to create directory: {$target_dir}");
return 1;
}
$this->info("Created directory: {$target_dir}");
}
// Generate class name (uppercase with underscores)
$class_name = $this->to_class_name($component_name);
// Check if files already exist
$jqhtml_file = "{$target_dir}/{$component_name}.jqhtml";
$js_file = "{$target_dir}/{$component_name}.js";
if (file_exists($jqhtml_file)) {
$this->error("Component template already exists: {$jqhtml_file}");
return 1;
}
if (file_exists($js_file)) {
$this->error("Component JavaScript already exists: {$js_file}");
return 1;
}
// Generate and write jqhtml template
$jqhtml_content = $this->generate_jqhtml_content($class_name);
file_put_contents($jqhtml_file, $jqhtml_content);
$this->info("Created template: {$component_name}.jqhtml");
// Generate and write JavaScript class
$js_content = $this->generate_js_content($class_name);
file_put_contents($js_file, $js_content);
$this->info("Created JavaScript: {$component_name}.js");
// Show success message with usage examples
$this->show_success_message($class_name, $component_name, $target_dir);
return 0;
}
/**
* Determine the target directory based on options
*/
protected function determine_target_directory()
{
$path = $this->option('path');
$module = $this->option('module');
$feature = $this->option('feature');
// Check for conflicting options
if ($path && ($module || $feature)) {
$this->error('Cannot use --path with --module or --feature options');
return null;
}
// If feature is specified, module is required
if ($feature && !$module) {
$this->error('--feature requires --module to be specified');
return null;
}
// Determine directory based on options
if ($path) {
// Direct path specified
return base_path($path);
} elseif ($module) {
// Module specified
$module_dir = base_path("rsx/app/{$module}");
// Check if module exists
if (!is_dir($module_dir)) {
$this->error("Module '{$module}' does not exist at: rsx/app/{$module}");
$this->info("Create the module first with: php artisan rsx:app:module:create {$module}");
return null;
}
if ($feature) {
// Feature within module
return "{$module_dir}/{$feature}/components";
} else {
// Module root components directory
return "{$module_dir}/components";
}
} else {
// No options specified - prompt for location
$this->error('No target location specified. Please use one of the following options:');
$this->info('');
$this->info('Option 1: Specify a path directly');
$this->info(' --path=rsx/theme/components (for application-wide components)');
$this->info('');
$this->info('Option 2: Create in a module');
$this->info(' --module=dashboard (creates in rsx/app/dashboard/components/)');
$this->info('');
$this->info('Option 3: Create in a module feature');
$this->info(' --module=backend --feature=users (creates in rsx/app/backend/users/components/)');
$this->info('');
$this->info('Recommendation: Use --path=rsx/theme/components for components that should be');
$this->info('accessible to the entire application.');
return null;
}
}
/**
* Convert component name to class name format
*/
protected function to_class_name($name)
{
$parts = explode('_', $name);
return implode('_', array_map('ucfirst', $parts));
}
/**
* Generate jqhtml template content
*/
protected function generate_jqhtml_content($class_name)
{
// Remove '_Component' suffix for display title
$display_name = str_replace('_Component', '', $class_name);
return <<<JQHTML
<!--
{$class_name}
\$button_text="Click Me" - Text shown on the button
-->
<Define:{$class_name} style="border: 1px solid black;">
<h4>{$display_name}</h4>
<div \$id="hello_world" style="font-weight: bold; display: none;">
<% for (let i = 0; i < 2; i++): %>
Hello,
<% endfor; %>
World!
</div>
<div \$id="inner_html">
<%= content() %>
</div>
<button @click=this.on_click_hello>
<%= this.args.button_text || "Click Me" %>
</button>
</Define:{$class_name}>
JQHTML;
}
/**
* Generate JavaScript class content
*/
protected function generate_js_content($class_name)
{
return <<<JS
/**
* {$class_name} - JQHTML Component
*
* Lifecycle methods are called in this order:
* 1. on_create() - Quick UI setup, runs bottom-up through component tree
* 2. on_load() - Fetch data from APIs (parallel execution, no DOM modifications)
* 3. on_ready() - Component fully initialized, runs bottom-up through component tree
*/
class {$class_name} extends Component {
/**
* Called after render, quick UI setup (bottom-up)
* Use for: Initial state, event bindings, showing loading indicators
*/
async on_create() {
// Example: this.\$id('loading').show();
// Example: this.\$.addClass('initializing');
}
/**
* Fetch data from APIs (parallel, NO DOM modifications)
* Use for: Loading data from server, fetching configurations
* WARNING: Do NOT modify DOM here - only load data
*/
async on_load() {
// Example: this.data.users = await Users_Controller.get_users_api();
// Example: this.data.config = await this.load_config();
// WARNING: Do NOT modify DOM here - only load data
}
/**
* Component fully initialized (bottom-up)
* Use for: Final UI setup, hiding loading indicators, starting animations
*/
async on_ready() {
// Example: this.\$id('loading').hide();
// Example: this.setup_event_listeners();
}
/**
* Click handler for the hello button
* Referenced in template via @click=this.on_click_hello
*/
on_click_hello() {
this.\$id('inner_html').hide();
this.\$id('hello_world').show();
}
// For more information: php artisan rsx:man jqhtml
}
JS;
}
/**
* Show success message with usage examples
*/
protected function show_success_message($class_name, $component_name, $target_dir)
{
// Calculate relative path from project root
$relative_path = str_replace(base_path() . '/', '', $target_dir);
$this->info("");
$this->info("✅ Component '{$class_name}' created successfully!");
$this->info("Location: {$relative_path}/");
$this->info("");
$this->info("📝 Usage Examples:");
$this->info("");
$this->info("1. Include in a Blade template:");
$this->info(" @component('{$class_name}', ['button_text' => 'Submit'])");
$this->info(" <p>This content goes inside the component</p>");
$this->info(" @endcomponent");
$this->info("");
$this->info("2. Create dynamically with jQuery:");
$this->info(" \$('#my-container').component('{$class_name}', {");
$this->info(" button_text: 'Submit'");
$this->info(" });");
$this->info("");
$this->info("3. Include in another jqhtml template:");
$this->info(" <{$class_name} button_text=\"Submit\">");
$this->info(" <p>Inner content here</p>");
$this->info(" </{$class_name}>");
$this->info("");
$this->info("4. Access existing component instance:");
$this->info(" const component = \$('#my-container').component();");
$this->info(" component.on_click_hello(); // Call component methods");
$this->info("");
$this->info("📚 Learn more: php artisan rsx:man jqhtml");
}
}