Browser

screenshot

1Browser::make()
2 ->modules([Publications::class])
3 ->name('publications')
4 ->max(4);
1<x-twill::browser
2 module-name="publications"
3 name="publications"
4 label="Publications"
5 :max="4"
6/>
1@formField('browser', [
2 'moduleName' => 'publications',
3 'name' => 'publications',
4 'label' => 'Publications',
5 'max' => 4,
6])
Option Description Type Default value
name Name of the field string
label Label of the field string
moduleName Name of the module (single related module) (use the modules method with a single class for the FormBuilder) string
modules Array of modules (multiple related modules), must include name or be a module class array
endpoints Array of endpoints (multiple related modules), must include value and label array
params Array of query params (key => value) to be passed to the browser endpoint array
max Max number of attached items integer 1
note Hint message displayed in the field string
fieldNote Hint message displayed above the field string
browserNote Hint message displayed inside the browser modal string
itemLabel Label used for the Add button string
buttonOnTop Displays the Add button above the items boolean false
wide Expands the browser modal to fill the viewport boolean false
sortable Allows manually sorting the attached items boolean true
disabled Disables the field boolean false
connectedBrowserField Name of another browser field to connect to string

Browser fields can be used inside as well as outside the block editor.

Inside the block editor, no migration is needed when using browsers. Refer to the section titled Adding browser fields to a block for a detailed explanation.

Outside the block editor, browser fields are used to save belongsToMany relationships. The relationships can be stored in Twill's own twill_related table or in a custom pivot table.

Using browser fields as related items

The following example demonstrates how to use a browser field to attach Authors to Articles.

  • Update the Article model to add the HasRelated trait:
1use A17\Twill\Models\Behaviors\HasRelated;
2 
3class Article extends Model
4{
5 use HasRelated;
6 
7 /* ... */
8}
  • Update ArticleRepository to add the browser field to the $relatedBrowsers property:
1class ArticleRepository extends ModuleRepository
2{
3 protected $relatedBrowsers = ['authors'];
4}
  • Add the browser field to resources/views/twill/articles/form.blade.php:
1@extends('twill::layouts.form')
2 
3@section('contentFields')
4 ...
5 
6 <x-twill::browser
7 module-name="authors"
8 name="authors"
9 label="Authors"
10 :max="4"
11 />
12@stop

Multiple browser fields referring to the same module

In some cases you may want to have 2 browser fields pointing to the same module. As by default the array elements in $relatedBrowsers expect the module name and field name to match we can work around this.

With the following form:

1$form->add(
2 Browser::make()->modules([Page::class])->name('page_1'),
3);
4 
5$form->add(
6 Browser::make()->modules([Page::class])->name('page_2'),
7);

We can setup $relatedBrowsers like this:

1protected $relatedBrowsers = [
2 'page_1' => [
3 'moduleName' => 'pages',
4 'relation' => 'page_1'
5 ],
6 'page_2' => [
7 'moduleName' => 'pages',
8 'relation' => 'page_2'
9 ]
10];

Multiple modules as related items

You can use the same approach to handle polymorphic relationships through Twill's related table.

  • Update ArticleRepository:
1class ArticleRepository extends ModuleRepository
2{
3 protected $relatedBrowsers = ['collaborators'];
4}
  • Add the browser field to resources/views/twill/articles/form.blade.php:
1@extends('twill::layouts.form')
2 
3@section('contentFields')
4 ...
5 
6 <x-twill::browser
7 :modules="[
8 [
9 'label' => 'Authors',
10 'name' => 'authors',
11 ],
12 [
13 'label' => 'Editors',
14 'name' => 'editors',
15 ],
16 ]"
17 name="collaborators"
18 label="Collaborators"
19 :max="4"
20 />
21@stop
  • Alternatively, you can use manual endpoints instead of module names:
1<x-twill::browser
2 :endpoints="[
3 [
4 'label' => 'Authors',
5 'value' => '/authors/browser',
6 ],
7 [
8 'label' => 'Editors',
9 'value' => '/editors/browser',
10 ],
11 ]"
12 name="collaborators"
13 label="Collaborators"
14 :max="4"
15/>

Working with related items

To retrieve the items in the frontend, you can use the getRelated method on models and blocks. It will return of collection of related models in the correct order:

1$item->getRelated('collaborators');
2 
3// or, in a block:
4 
5$block->getRelated('collaborators');

Connecting 2 browser fields

The following example demonstrates how to make a browser field depend on the selected items of another browser field.

1<x-twill::browser
2 module-name="products"
3 name="product"
4 label="Product"
5 :max="1"
6/>
7 
8<x-twill::browser
9 label="Product variant"
10 name="product_variant"
11 module-name="productVariants"
12 connected-browser-field="product"
13 note="Select a product to enable this field"
14 :max="1"
15/>

The second browser is using the connectedBrowserField option, which will:

  • add the connected browser's selected items IDs to the browser endpoint url, using the connectedBrowserIds query parameter,
  • disable the browser field when the connected browser is empty,
  • empty the browser field automatically when removing all items from the connected browser.

From your module's controller, you can then use connectedBrowserIds to do something like:

1public function getBrowserData($prependScope = [])
2{
3 if ($this->request->has('connectedBrowserIds')) {
4 $products = collect(json_decode($this->request->get('connectedBrowserIds')));
5 $prependScope['product_id'] = $products->toArray();
6 }
7 
8 return parent::getBrowserData($prependScope);
9}

In the presented example, this will make sure only variants of the selected product in the first browser can be selected in the second one.

Using a custom pivot table

Using the HasRelated trait means that Twill is storing the relationship in a polymorphic table, which can make efficient database queries harder to implement depending on the needs of your frontend. Using a pivot table between 2 Twill models is available if you need it.

  • Create your pivot table:
1Schema::create('artist_artwork', function (Blueprint $table) {
2 createDefaultRelationshipTableFields($table, 'artist', 'artwork');
3 $table->integer('position')->unsigned()->nullable();
4 });
  • Add the relationship to your model (i.e. Artist):
1public function artworks(): BelongsToMany
2{
3 return $this->belongsToMany('artworks')->orderByPivot('position');
4}
  • Configure the $browsers property in your repository (i.e. ArtistRepository):
1protected $browsers = ['artworks'];

Additional parameters can also be overridden with an array. When only the browser name is given, the rest of the parameters are inferred from the name.

1protected $browsers = [
2 'artworks' => [
3 'titleKey' => 'title',
4 'relation' => 'artworks',
5 'browserName' => 'artworks',
6 'moduleName' => 'artworks',
7 'positionAttribute' => 'position',
8 ],
9];

For even more control, you can use updateBrowser() in your own afterSave() method and getFormFieldsForBrowser() in your own getFormFields() method.

Morphable browser fields

While a bit more complex to setup, you can target a morphTo.

For example we have a MenuItem model, and we want to target multiple types of models in our system.

In our MenuItem we add the relation to our linkable:

1public function linkable()
2{
3 return $this->morphTo();
4}

This goes with the following migration on the menu_item:

1$table->bigInteger('linkable_id')->nullable();
2$table->string('linkable_type')->nullable();

Then in our MenuItemRepository we have to setup a few things:

1// Prepare the fields.
2public function prepareFieldsBeforeCreate($fields)
3{
4 $fields = parent::prepareFieldsBeforeCreate($fields);
5 $fields['linkable_id'] = Arr::get($fields, 'browsers.linkables.0.id', null);
6 $fields['linkable_type'] = Arr::get($fields, 'browsers.linkables.0.endpointType', null);
7 
8 return $fields;
9}
10 
11// On save we set the linkable id and type.
12public function prepareFieldsBeforeSave($object, $fields)
13{
14 $fields = parent::prepareFieldsBeforeSave($object, $fields);
15 
16 $id = Arr::get($fields, 'browsers.linkables.0.id', null);
17 $type = Arr::get($fields, 'browsers.linkables.0.endpointType', null);
18 
19 if ($id) {
20 $fields['linkable_id'] = $id;
21 }
22 if ($type) {
23 $fields['linkable_type'] = $type;
24 }
25 
26 return $fields;
27}
28 
29// Set the browser value to our morphed data.
30public function getFormFields($object)
31{
32 $fields = parent::getFormFields($object);
33 
34 $linkable = $object->linkable;
35 
36 if ($linkable) {
37 $fields['browsers']['linkables'] = collect([
38 [
39 'id' => $linkable->id,
40 'name' => $linkable->title,
41 'edit' => moduleRoute($object->linkable->getTable(), 'content', 'edit', $linkable->id),
42 'thumbnail' => $linkable->defaultCmsImage(['w' => 100, 'h' => 100]),
43 ],
44 ])->toArray();
45 }
46 
47 return $fields;
48}

And finally in our form we can add the field:

1 
2<x-twill::browser
3 label="Link"
4 :max="1"
5 name="linkables"
6 :modules="[
7 [
8 'label' => 'Homepages',
9 'name' => 'homepages',
10 ],
11 [
12 'label' => 'Pages',
13 'name' => 'content.pages'
14 ]
15 ]"
16/>