Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Ux Collection Js Laravel Package

tienvx/ux-collection-js

tienvx/ux-collection-js is a lightweight Laravel/PHP package for dynamic form collections. It makes it easy to add, remove, reorder, and manage repeatable fields or list items in the browser with minimal JavaScript and simple, Blade-friendly integration.

View on GitHub
Deep Wiki
Context7

Getting Started

  • Install the JS, not the bundle. This is a Symfony UX package: composer require tienvx/ux-collection-js will pull the code, but in a Laravel app you are really interested in the Stimulus controller and any helpers shipped under the package's assets/ directory. Copy that controller into resources/js/controllers/ (e.g. collection_controller.js) and register it in your bootstrap.js (import Collection from './controllers/collection_controller'; application.register('collection', Collection)).
  • Read the README + assets/ first, then the demo. The package is thin; the README documents the Stimulus controller name, its data-* values, and the expected DOM contract (add button, remove button, prototype/template, min/max). The demo templates show what a "collection" form looks like — translate that to Blade.
  • The first use case is a repeatable form section: invoice line items, addresses, phone numbers, attendees. You render existing rows server-side in Blade, plus a hidden <template> (the "prototype") containing one blank row whose field names use a placeholder index. The controller clones the template on "Add", removes on "Remove", and optionally enforces min/max.
  • Minimum viable wiring in Blade:
    <div data-controller="collection"
         data-collection-min-value="1"
         data-collection-max-value="10">
        <div data-collection-target="container">
            @foreach (old('items', $items ?? []) as $i => $item)
                @include('partials.item', ['index' => $i, 'item' => $item])
            @endforeach
        </div>
        <template data-collection-target="prototype">
            @include('partials.item', ['index' => '__index__', 'item' => null])
        </template>
        <button type="button" data-collection-target="add">Add line</button>
    </div>
    

Implementation Patterns

  • Prototype substitution, done once. Put the token (__index__ or whatever the controller expects) in name, id, for, and any data-* references inside the partial. In the partial, derive everything from a single $index variable so replacement is a single string swap, not a regex over the whole fragment.
  • Monotonic index counter. Keep the next index in a Stimulus value (this.indexValue) initialized to count(existing rows); increment on every add, and never reuse indices. Reusing children.length breaks after deletions and desynchronizes old() re-renders from PHP validation.
  • Server contract first. Decide the input name shape up front — items[0][description], items[0][qty] — and validate it in the controller: 'items' => ['array','max:10'], 'items.*.qty' => ['required','integer','min:1']. The JS max/min is UX sugar; the server rules are the source of truth.
  • Re-render after validation failure. On a failed FormRequest, pass old('items', $items) back into the same partial loop using $loop->index (or explicit keys) so the indices the browser sent are the indices Blade re-renders. Any mismatch shows up as "field silently disappears" bugs.
  • Encapsulate it. Build a <x-collection> Blade component that owns: the wrapper data-controller, the existing rows, the <template>, and add/remove buttons. Pass min, max, sortable, and the partial name as props — your controllers and views then never touch Stimulus attributes directly.
  • Livewire / Alpine interaction. If the page is Livewire, prefer wire:click for add/remove and skip this package's DOM cloning; mixing both means cloned nodes are invisible to Livewire's model and won't be dehydrated. If you must mix, hook Livewire.hook('morph.updated', ...) to re-scan for add/remove triggers, since the controller's connect() will not rerun on morph.
  • Progressive enhancement fallback. Keep a plain POST "add one row" route (?rows=N or a _rows hidden input) so the form works without JS and so you have a server-side reproduction when debugging the JS.
  • Drag-and-drop / reordering (if the package supports it). Treat order as meaningful server-side — either reindex items on submit before validation, or store an explicit position field, otherwise reordering in the browser silently doesn't change anything.

Gotchas and Tips

  • It is not a Laravel package. No service provider, no config file, no Blade directives, no Artisan commands. Symfony Flex's assets/controllers.json auto-registration does not exist in Laravel — you register the controller yourself, and you ignore anything under the package's src/ unless you find a useful reference implementation.
  • Stimulus version drift. Last release was 2022-07 and the repo has ~13 stars, so it predates or barely targets Stimulus 3.x / Symfony UX 2.x conventions. Verify whether the controller uses this.element, this.dispatch(), or older CustomEvent patterns, and pin @hotwired/stimulus accordingly. If connect() never fires, 99% of the time the controller isn't registered or the data-controller name doesn't match the application.register() identifier.
  • Placeholder double-substitution. Naive html.replace(/__index__/g, i) is fine; naive element.innerHTML.replace(token, i) on the whole wrapper is not — it can hit tokens in unrelated places and produces indices inside IDs that collide with existing rows. Restrict replacement to the cloned <template> fragment.
  • Deletes vs. validation. Removing a row from the DOM doesn't remove it from old() if the request fails afterwards; on re-render you may resurface deleted rows. Either submit the removed indices in a removed[] field and filter server-side, or accept the resurface as a known limitation and document it.
  • id/for collisions. Cloned rows must get unique IDs, or label clicks focus the first row's input. Since you're substituting the index anyway, build IDs as items-{$index}-description in the partial — don't rely on a separate post-clone ID fixup script.
  • No ESM/type guarantees. Check the package package.json and build output before assuming import Collection from 'tienvx/ux-collection-js' works. Copying the controller source into your app is usually more reliable than consuming it as a node module.
  • Debugging loop. console.log in connect() → confirm registration; log the prototype target → confirm the <template> is found and content is non-null; log indexValue before/after add; then inspect the submitted payload in $request->all() to confirm names line up with your validation rules.
  • Honest cost/benefit for Laravel. Livewire (wire:click, @entangle) or Alpine (x-for, x-ref) covers add/remove collections with better docs and no cross-framework glue. Use this package when you specifically want the Symfony CollectionType prototype/substitution semantics (min/max, remove buttons per row, optional sortable) in a server-rendered Blade form with vanilla Stimulus — otherwise skip it.
  • Extension point. The controller's data-* options (add/remove selectors, prototype target, min/max, placeholder) are the API. If you need custom behavior, copy the controller into your app and modify it rather than patching node_modules or fighting the upstream release cadence.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings