Admin Product Picker Component

Admin Product Picker Component

How do I use the product picker in my custom module?

The Product Picker provides a modal that lets users select a single or many products/variants with quantities. Parts of the admin use this such as the Price Lists and Create Order form. Modules such as aerocargo/upsellsalso make use of the Product Picker.

You can use the <product-picker></product-picker>Vue component from any admin view inside of your module that extends the official admin layout (admin::layouts.main). When using the Product Picker you need to provide a url for the get-products-url prop (this value is usually always the admin.catalog.products.pickerroute) and you need to provide a slot for the button that will be used to open the picker.

@extends('admin::layouts.main')

@section('content')
   <h2>
       <a href="{{ route('admin.modules') }}" class="btn">@include('admin::icons.back') Back</a>
       <span class="ml-4">My Module</span>
   </h2>

   <div class="card">
       <product-picker v-slot="picker"
                       get-products-url="{{ route('admin.catalog.products.picker') }}">
           <a href="#" @click.prevent="picker.open" class="btn">Add Product</a>
       </product-picker>
   </div>
@endsection

What are the available props for the product picker?













Prop Type Description
get-products-url String This is the only required prop. This is the URL for the API to get the products from. It’s common to set this value to {{ route('admin.catalog.products.picker') }}.
image-url-prefix String This is the image factory prefix to use for the product image URLs. This is set by default to use {{ image_factory(60, 60)->contain() }}.
no-image-url String This is the image URL to use when a product doesn’t have an image. This is set by default to use {{ asset('modules/aerocommerce/admin/no-image.svg') }}.
action-name String This is the button text shown on the confirm button of the product picker modal (bottom right). This is set by default to “Add Products”.
clear-selected-after-emit Boolean
pick-product Boolean This is a boolean that defaults to false. When true, products will be allowed to be selected as well as variants.
product-only Boolean This is a boolean that defaults to false. When true, variants will not be listed and not selectable.
pick-only Boolean This is a boolean that defaults to false. When true, variants will not need to be in stock to be selectable.
needs-quantity Boolean This is a boolean that defaults to true. When false, a quantity will not be able to be set for the selected variants/products.
emit-image Boolean This is a boolean that defaults to false. When true, the products/variants image will be emitted when selected.
emit-sku Boolean This is a boolean that defaults to false. When true, the products/variants sku will be emitted when selected.
emit-model Boolean This is a boolean that defaults to false. When true, the products/variants model will be emitted when selected.
emit-name Boolean This is a boolean that defaults to false. When true, the products/variants name will be emitted when selected.
emit-price Boolean This is a boolean that defaults to false. When true, the products/variants price will be emitted when selected.

Example

You can make use of the props like you usually would with a Vue component

@extends('admin::layouts.main')

@section('content')
   <h2>
       <a href="{{ route('admin.modules') }}" class="btn">@include('admin::icons.back') Back</a>
       <span class="ml-4">My Module</span>
   </h2>

   <div class="card">
       <product-picker v-slot="picker"
                       get-products-url="{{ route('admin.catalog.products.picker') }}"
                       :max-selection="5"
                       :pick-product="true"
                       :emit-name="true"
                       :emit-model="true"
                       :emit-sku="true">
           <a href="#" @click.prevent="picker.open" class="btn">Add Product</a>
       </product-picker>
   </div>
@endsection

What are the available events for the product picker?

Event Description
products-selected This event emits when the action button is clicked (bottom right of the modal) and provides an array of all of the selected product(s)/variant(s).
selected This event emits when the action button is clicked (bottom right of the modal) and is emitted for each product/variant that has been selected - only a single product/variant is provided.

Example

The usual use-case for the Product Picker is to allow users to select products and/or variants to be added to a list. This example code is a Blade view that allows the user to select up to 5 products or variants and then displays them in a list with a custom message. It also allows the user to remove the selected products/variants. The next steps to make this code fully functional would be to wrap the table in a form and to add a submit button that saves the products/variants.

@extends('admin::layouts.main')

@section('content')
   <h2>
       <a href="{{ route('admin.modules') }}" class="btn">@include('admin::icons.back') Back</a>
       <span class="ml-4">My Module</span>
   </h2>

   <div class="card p-0">
       <table>
           <thead>
               <tr class="header">
                   <th class="whitespace-no-wrap">Model</th>
                   <th class="whitespace-no-wrap">Sku</th>
                   <th class="whitespace-no-wrap">Message</th>
                   <th></th>
               </tr>
           </thead>
           <tbody>
               <tr v-for="(item, index) in list" :key="item.buyable_id + item.buyable_type" v-cloak>
                   <td class="whitespace-no-wrap">
                       <input type="hidden" :name="'items[' + index + '][buyable_id]'" :value="item.buyable_id">
                       <input type="hidden" :name="'items[' + index + '][buyable_type]'" :value="item.buyable_type">
                       <span v-text="item.model"></span>
                   </td>
                   <td class="whitespace-no-wrap" v-if="item.sku" v-text="item.sku"></td>
                   <td class="whitespace-no-wrap" v-else><span class="text-grey">—</span></td>
                   <td class="whitespace-no-wrap">
                       <input type="text" :name="'items[' + index + '][message]'" v-model="item.message" placeholder="Message">
                   </td>
                   <td>
                       <div class="flex items-center justify-end">
                           <a href="#" @click.prevent="list.splice(index, 1)">@include('admin::icons.bin')</a>
                       </div>
                   </td>
               </tr>
               <tr v-if="list.length === 0">
                   <td colspan="4">No data to show</td>
               </tr>
           </tbody>
       </table>

       <product-picker v-slot="picker"
                       v-if="list.length < 5"
                       get-products-url="{{ route('admin.catalog.products.picker') }}"
                       :max-selection="5"
                       :pick-product="true"
                       :emit-name="true"
                       :emit-model="true"
                       :emit-sku="true"
                       @selected="selected">
           <a href="#" @click.prevent="picker.open" class="btn m-4">Add Product</a>
       </product-picker>
   </div>
@endsection

@push('scripts')
   <script>
       window.AeroAdmin.addData('list', []);

       window.AeroAdmin.addMethod('selected', function (item) {
           if (this.findIndex(item.buyable_id, item.buyable_type) === -1) {
               this.list.push({
                   ...item,
                   message: '',
               });
           } else {
               this.$notify({
                   type: 'error',
                   title: (item.sku ?? item.name) + ' has already been added to the list',
               });
           }
       });

       window.AeroAdmin.addMethod('findIndex', function (id, type) {
           return this.list.findIndex((l) => l.buyable_id === id && l.buyable_type === type);
       });
   </script>
@endpush