benlowery.co.uk

Blade Components - So sharp you'll cut yourself

Since this was written Laravel has added @component

This approach works exactly the same with @component('someview', [$someArray])@endcomponent

You can read about components here

One thing worth noting is you can use {{ $slot }} to handle some cases that couldn't be (neatly) handled with the below method. Progress is a wonderful thing.

@include('the', $awesome)

One of the interesting things about the blade @include() function is that it takes an array as a second parameter.

The contents of that array are made available as variables in the included template.

@include('sometemplate', ['foo' => 'somefoo', 'bar' => 'somebar'])

includes sometemplate but usefully makes $foo & $bar (containing 'somefoo' and 'somebar') available.

In my current project:

@include('Core::components.textinput', ['label' => 'Meeting Title', 'name' => 'title'])

becomes

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<label class="control-label col-lg-6"
       for="title">Meeting Title</label>

<div class="col-lg-12">
    <input data-bind="value: title"
           id="title"
           name="title"
           type="text"
           class="form-control"
           placeholder="">
</div>

If you are wondering about the Core:: in template includes that is for another blog post (coming soon hint: you can namespace Views...)

The textinput.blade.php template would look like this :-

<div data-bind="validationElement: {{ $property or $name }}"
     class="form-group">
    <label class="control-label {{ $labelwidth or 'col-lg-6' }}"
           for="title">{{ $label or '' }}</label>

    <div class="{{ $inputwidth or 'col-lg-12' }}">
        <input data-bind="value: {{ $property or $name }}"
               id="{{ $name }}"
               name="{{ $name }}"
               type="text"
               class="form-control"
               placeholder="{{ $placeholder or '' }}">
    </div>
</div>

As you can see the minimum required is usually a name and a label however (in this instance) you can also supply a $property (I use knockout on this project so that would be the observable name) and a $labelwidth or $inputwidth

This is a big win, instead of repeating the same 10-20 lines of markup ad nauseum, we extract only the parts that change each time making global changes far easier to accommodate.

In more complex examples you can also pass in extra options :-

@include('Core::components.ckeditor',
        ['label' => 'Agenda','name' => 'agenda', 'options' => '{ height: 200 }'])

Since you are passing a template and an array you are not limited to what you can pass, in one instance I found it very convenient to json_encode($foo) and bind that to the component as an initial parameter.

Why would you do this?

In projects that are very form heavy or have lots of repeating 'components' the amount of duplicated markup you need to write is large.

While you can use live templates to generate much of that (I use phpstorm and they work well), refactoring becomes tedious and error prone if you need to change all of those inputs/components later, this approach abstracts and makes reusable all the common inputs that you would use throughout your project as well as greatly reducing the likelihood of introducing accidental errors in your markup.

Plus you can turn this :-

@include('Core::components.textinput', 
        ['label' => 'Meeting Title', 'name' => 'title'])
@include('Core::components.datetime_picker', 
        ['label' => 'Meeting Date/Time', 'name' => 'meeting_datetime'])
@include('Core::components.textarea', 
        ['label' => 'Address/Location', 'name' => 'address'])

@include('Core::components.ckeditor',
        ['label' => 'Agenda','name' => 'agenda', 'options' => '{ height: 200 }'])
<hr>
@include('Core::components.ckeditor',
        ['label' => 'Minutes', 'name' => 'minutes', 'options' => '{height: 200}'])

<hr/>
<!-- start meeting guest list -->
@include('Core::meeting.partials.meeting_guest_list')
<!-- end meeting guest list -->

Into this (in under 5 minutes)

<div data-bind="validationElement: title"
     class="form-group">
    <label class="control-label col-lg-6"
           for="title">Meeting Title</label>

    <div class="col-lg-12">
        <input data-bind="value: title"
               id="title"
               name="title"
               type="text"
               class="form-control"
               placeholder="">
    </div>
</div>
<div data-bind="validationElement: meeting_datetime, validationOptions: { insertMessages: false}"
     class="form-group">
    <label class="control-label col-lg-6"
           for="name">Meeting Date/Time</label>

    <div class="col-lg-12">
        <input
                data-bind="datetimepicker: meeting_datetime, datepickerOptions:"
                autocomplete="off"
                id="meeting_datetime"
                name="meeting_datetime"
                type="text"
                class="form-control">

        <p data-bind="validationMessage: meeting_datetime"
           class="help-block has-error"></p>
    </div>
</div>
<div data-bind="validationElement: address"
     class="form-group">
    <label class="control-label col-lg-6"
           for="title">Address/Location</label>

    <div class="col-lg-12">
        <textarea data-bind="value: address"
                  id="address"
                  name="address"
                  type="text"
                  class="form-control"
                  rows="5"
                  placeholder=""></textarea>
    </div>
</div>

<div data-bind="validationElement: agenda"
     class="form-group">

    <label class="control-label col-lg-6"
           for="agenda">Agenda</label>

    <div class="col-lg-12">
        <textarea data-bind='ckEditor:  agenda, config: { height: 200 }'
                  class="form-control"
                  name="agenda"
                  id="agenda"
                  placeholder=""></textarea>

        <p data-bind="validationMessage: agenda"
           class="help-block has-error"></p>
    </div>

</div>
<hr>
<div data-bind="validationElement: minutes"
     class="form-group">

    <label class="control-label col-lg-6"
           for="minutes">Minutes</label>

    <div class="col-lg-12">
        <textarea data-bind='ckEditor:  minutes, config: {height: 200}'
                  class="form-control"
                  name="minutes"
                  id="minutes"
                  placeholder=""></textarea>

        <p data-bind="validationMessage: minutes"
           class="help-block has-error"></p>
    </div>

</div>

<hr/>
<!-- start meeting guest list -->
<div class="form-group">
    <label class="control-label col-lg-6"
           for="place_by">Find Attendee</label>

    <div class="col-lg-12">
        <div class="control-group">
            <div class="control-inline">
                <select
                        data-bind=" options: availableAttendees,
                                value: selectedAttendeeId,
                                optionsCaption: 'Choose',
                                optionsValue: 'id',
                                optionsText: function(item){return item.first_name + ' ' + item.last_name;},
                                select2:{ width: 'resolve' }"
                        name="attendee_id"
                        class="form-control"
                        id="attendee_id"></select>
            </div>
        </div>

    </div>
    <div class="col-lg-6">
        <button data-bind="click: addAttendee, disable: !selectedAttendeeId()"
                class="btn btn-primary">Add to Meeting
        </button>
    </div>
</div>

<div class="table-responsive">
    <table class="table table-bordered">
        <thead>
        <tr>
            <th>Name</th>
            <th>Email</th>
            <th>Mobile</th>
            <th>Actions</th>
        </tr>
        </thead>

        <tbody data-bind="foreach: attending">
        <tr>
            <td data-bind="text: formatName($data)"></td>
            <td data-bind="text: email"></td>
            <td data-bind="text: mobile_no"></td>
            <td>
                <button data-bind="click: $parent.removeAttendee"
                        class="btn btn-sm btn-danger">Remove
                </button>
            </td>
        </tr>
        </tbody>

    </table>
</div>
<!-- end meeting guest list -->