Hephaestus's avatar

Getting Data for Selectize.js Select Box

Hi,

I'm using Selectize.js to produce a searchable dropdown select list, with the list items sorted into individual categories (optgroups). The code I've written works exactly as I need it to and only makes two simple SQL queries, but I'm not happy with how it's structured. The main issues I see:

  1. It's not very reusable, when I use Selectize again with a different model I'll be repeating a lot of configuration and if I want to use this same data on an action that has already queried the categories/items
  2. The method to produce the list has a lot of responsibilities and isn't very readable

I've been playing with it for quite a while but I can't decide on how best to improve it, I'm not sure how to breakdown the logic that gathers the data and outputs it in a form useful to Selectize and I'm not sure how best to go about making this reusable. I'm sure there are ways Laravel can help both the readability and reusability of the code, but I'm not sure what they are.

Any advice or suggestions would be really appreciated, including comments on the general code quality.

Code

To simplify things I've changed the code to refer to Items and Categories and stripped out some surrounding code (use statements, etc), as the domain-specific language might be confusing.

ItemController.php

ItemController makes a view and passes it Item::selectizeList(), which returns an array of three arrays, $selectOptions, $selectizeOptions and $optgroups. $selectOptions is an assoc array comprised of an array for each Category, containing their child Item names keyed by their ID and is used to produce the fallback select box with Form::select. Selectize requires two JSON blocks, one defining the options and one defining the optgroups. $selectizeOptions is a JSON formatted string containing all the Item objects to display in the select box, with the Category name added to allow Selectize to sort them into optgroups. $optgroup is a JSON formatted string containing all of the Categories with their names and slug keyed as label and value, respectively, used by Selectize to format the optgroups.

class ItemsController extends Controller
    public function index()
    {
        return View::make('items.index', Item::selectizeList());
    }
}

items/index.blade.php

The items.index template displays a basic HTML select box using Form::select() and the $selectOptions array and then enables and configures Selectize on that selectbox, passing it the $selectizeOptions and $optgroups variables and formatting the options to display both the title and subtitle.

@extends('master')

@section('content')
    {!! Form::select('item_id', $selectOptions, null, ['id' => 'selectItem', 'placeholder' => 'Please select...']) !!}
@stop

@section('footer_scripts')
    <script>
        $('#selectItem').selectize({
            valueField: 'id',
            searchField: ['title', 'subtitle'],
            optgroupField: 'category',
            optgroupLabelField: 'category',
            options: {!! $selectizeOptions !!},
            optgroups: {!! $optgroups !!},
            render: {
                option: function(data, escape) {
                    return '<div class="option">' +
                            '<span>' + escape(data.title) + '</span> \u2014' +
                            '<span>(' + escape(data.subtitle) + ')</span>' +
                            '</div>';
                },
                item: function(data, escape) {
                    return '<div class="option">' +
                            '<span>' + escape(data.title) + '</span> \u2014' +
                            '<span>(' + escape(data.subtitle) + ')</span>' +
                            '</div>';
                }
            }
        });
    </script>
@stop

Item.php

class Item extends \Eloquent
{
    public static function selectizeList()
    {
        // Retrieve all Categories that have Items and eager-loads those Items

        $categories = Category::with(['item' => function($query){
            $query->select('id', 'title', 'slug', 'subtitle', 'category_id');
            $query->orderBy('name', 'asc');
        }])->has('item')->get();

        $selectizeOptions = [];
        $optgroups = [];
        $selectOptions = [];

        // Options JSON for Selectize
        // Get an array of the Items that belong to each category and add the name of that Category 
        // as an element of each item, add the Items from each successive category to the $selectizeOptions
        // array and then convert the array to JSON

        foreach($categories as $category)
        {
            $itemArray = $category->items->toArray();

            foreach($itemArray as $key => $value)
            {
                $itemArray[$key] = array_add($itemArray[$key], 'category', $category->name);
            }

            $selectizeOptions = array_merge($selectizeOptions, $itemArray);
        }

        $selectizeOptions = json_encode($selectizeOptions);

        // Optgroups JSON for Selectize
        // Pull out an array of Category names keyed by their slugs and add the associative arrays

        foreach($categories->lists('name', 'slug') as $name => $slug)
        {
            $optgroups[] = ['value' => $slug, 'label' => $name];
        }

        $optgroups = json_encode($optgroups);


        //Items Fallback Select List
        // Create an array of Categories containing an array of their associated Item names, keyed 
        // by the Item ID. Used to generate the Form::select()


        foreach($categories as $category)
        {
            $selectOptions[$category->name] = $category->items->lists('name', 'id')->all();
        }

        return ['selectOptions' => $selectOptions, 'selectizeOptions' => $selectizeOptions, 'optgroups' => $optgroups];
    }
}

Data Formats

$selectOptions = [
    'Category One' => [
        1 => 'One',
        2 => 'Two'
    ],
    'Category Two' => [
        3 => 'Three',
        4 => 'Four'
    ],
]
$selectizeOptions = [
    {id: 1, title: 'One', slug: 'one', subtitle: 'One Subtitle', category: 'Category One'},
    {id: 2, title: 'Two', slug: 'two', subtitle: 'Two Subtitle', category: 'Category One'},
    {id: 3, title: 'Three', slug: 'three', subtitle: 'Three Subtitle', category: 'Category Two'},
    {id: 4, title: 'Four', slug: 'four', subtitle: 'Four Subtitle', category: 'Category Two'}
]
$optgroups = [
    {label: 'Category One', value: 'category-one'},
    {label: 'Category Two', value: 'category-two'}
]
0 likes
1 reply
Hephaestus's avatar

I just saw the notice about lists() being deprecated in favour of pluck(), so I've replaced that usage in the original code.

Please or to participate in this conversation.