Extending Blade in Laravel - Creating a custom form object

3.4k Views Asked by At

The documentation on extending blade in Laravel 5 doesn't seem to have much depth and i'm struggling to understand if what I want to do is possible.

NOTE: I am using Laravel 5.1

This is the only example the Laravel Docs give for extending blade.

Blade::directive('datetime', function($expression) {
    return "<?php echo with{$expression}->format('m/d/Y H:i'); ?>";
});

Now what I want to do is have a blade file that looks like this:

@form('horizontal')
    {{ $form->text('email') }}
    {{ $form->text('password') }}
    {{ $form->submit }}
@endform

Then I want a new $form object to be created whenever I use @form and have it available to use after I use @form but it doesn't need to be available after @endform

After a bit of Googling and just messing around I have found I can do something like this:

\Blade::directive('form', function($expression) {
    return '<?php $form = new App\Form'.$expression.'; ?>
    <?php echo $form->open(); ?>';
});

(This was updated from a previous edit)

That's most of the way there. I have a $form object and can create a new form and I can send a custom param to that form object. I can also use the $form object after @form. Of course it will be available throughout the view, that's not perfect, but not necessarily a problem.

However since I seem to be doing something that is completely undocumented I fear I may be doing something wrong, or that I may run into problems later.

I should also note that every single guide I have found online refer to $compiler->createMatcher and $compiler->createPlainMatcher but both of these seem to be removed in Laravel 5.1.

Can anyone offer any insight into how blade extensions work and suggest better ways of doing this? Right now it seems to work just about, but feels very 'hacky' and perhaps not safe to rely on going forward.

UPDATE HTML MACROS

To update on a use case for this based on an answer below suggesting HTML macros. CSS Framework Bootstrap includes different formatting for vertical and horizontal forms. The purpose of this functionality would be to make it easy to switch between these two, an example is below:

@form('horizontal')
    {{ $form->text("email", $user->email, 'Email') }}
@endform

The Form class might then look something like this:

class Form
{
    public $direction;

    public function __construct($direction)
    {
        $this->direction = $direction;
    }

    public function text($name, $value, $label)
    {
        return $this->wrap('<input type="text" value="'.$value.'" />', $name, $label);
    }

    public function wrap($element, $name, $label)
    {
        if($this->direction == 'horizontal') {
            //Horizontal
            return '<div class="form-group">
                <label class="col-sm-2 control-label" for="'.$name.'">'.$label.'</label>
                <div class="col-sm-10">
                    '.$element.'
                </div>
            </div>';
        } else {
            //Vertical
            return '<div class="form-group">
                <label for="'.$name.'">'.$label.'</label>
                '.$element.'
            </div>';
        }
    }
}

As far as i'm aware HTML Macro's cannot achieve this sort of thing.

This is all pseudo example code, so apologies for any errors. I believe the basic premise of it should be clear though.

3

There are 3 best solutions below

3
On BEST ANSWER

You might want to look into this plugin "illuminate/html": "~5.0".

You can do things like open forms which these two method will do.

{!! Form::open() !!}

{!! Form::model($pose,['method' => 'PATCH', 'route' => ['poses.update', $pose->id]]) !!}

As a bonus you also get many other items.

{!! Form::text('title', null, [
           'class' => 'form-control', 
           'id' => 'title', 'placeholder' => 'Add title.'
          ]) !!}
{!! Form::textarea('body', null, [
           'class' => 'form-control', 
           'id' => 'body', 'placeholder' => 'Post a status'
          ]) !!}
{!! Form::close() !!}

And many others, see the source.

As far as your original question, you can always use the 5.0 function and just put the original method createOpenMatcher in your service provider. See my answer here.

public function createOpenMatcher($function){
    return '/(?<!\w)(\s*)@'.$function.'\(\s*(.*)\)/';
}

Update per comment

So per you comment, the Illuminate form helper will do what you are asking.

{{ Form::open(['class' => 'form-horizontal']) }}

Which renders as

<form method="POST" 
    action="http://example.com/sample" 
    accept-charset="UTF-8" 
    class="form-horizontal">
<input name="_token" 
    type="hidden" 
    value="ZqVwyZXXkh6sHLKpDJjvAJP0s6Bg5bXeuA1RcOnK">

Anything passed to the array is added to the form tag.

I might not understand your question 100%, but I think you may be trying to solve with Blade extensions something that might be much easier to solve with your code editor or the Form helper plugin. Maybe you could add the two types of HTML you are trying to generate and we could discuss that?

I use both PhPStorm and Sublime text. Both of these have options where you can include code snippets of text. In PhPStorm they are called Live Templates and Sublime calls them snippets.

I use a textfieldtab live template for PhPStorm for my text fields.

<!-- $VALUE$ Field -->
<div class="form-group">
    {!! Form::label('$NAME$', '$VALUE$:') !!}
    {!! Form::text('$NAME$', null, [
               'class' => 'form-control', 
               'id'=>'$NAME$', 
               'placeholder' => '$VALUE$'
     ]) !!}
    {!! $errors->first('$NAME$', '<span class="error">:message</span>') !!}
</div>

Which gives you the render html

<div class="form-group">
        <label for="sample">Sample:</label>
        <input class="form-control" 
               id="sample" 
               placeholder="Sample" 
               name="sample" 
               type="text">
 </div>

Although I do extend Blade from time to time. I have found that for the majority of times, when I want to extend it, there is usually an easier way.

Final Edit

Why don't you use CSS?

<style>
    form.horizontal .col-sm-10, form.horizontal .col-sm-2{
        display:inline-block;
    }
</style>

<form class="horizontal">
    <div class="form-group">
        <label class="col-sm-2 control-label" for="field_name">Enter Name:</label>
        <div class="col-sm-10">
            <input type="text" value="New Name" />
        </div>
    </div>
</form>
<form class="vertical">
    <div class="form-group">
        <label class="col-sm-2 control-label" for="field_name">Enter Name:</label>
        <div class="col-sm-10">
            <input type="text" value="New Name" />
        </div>
    </div>
</form>
2
On

Laravel has a Forms package that has been extracted out of the core framework. It is available here - http://laravelcollective.com/docs/5.0/html

IMHO, extending Blade is not the best way to achieve what you want. You can use custom macros from the package above. E.g.,

Form::macro('horizontal', function()
{
    $form = '<input type="text" name="email">';
    $form .= '<input type="password" name="password">';
    $form .= '<input type="submit" value="Submit">';
    return $form;
});

And in your view: {!! Form::horizontal() !!}

Edit: Response to updated question below

You can extend the FormBuilder class to add your own custom methods - https://github.com/LaravelCollective/html/blob/5.0/src/FormBuilder.php

Add a private wrap method that takes the input and direction. Or you could even have separate wrapHorizontal and wrapVertical methods.

0
On

Try the below link you may find it useful. http://laravel-recipes.com/categories/21

Here the forms are of the form {{ ..Code goes here.. }}. In laravel 5.1 use these form syntax as {!! ..Code goes here.. !!}.