Jul 09

customizing form field widget markup

OctoberCMS is very extendable in nature. while I was checking stackoverflow questions I hit with this question.

Question was to modify relation form-widget's output, so widget can just not only show checkbox and title it should also show related gallery's images.

Requirement was like this

requirement

And, final solution output look like this

relation-widget-output

stackoverflow.com/questions/62812288/octobercms-show-title-and-image-of-a-gallery-in-backend

So, I wondered, is it possible to customize form-widgets ? So, I thought lets give it a try.

What was my thought initial process.


1 . May be I can overried field options to replace its partial But, unfortunately there was no such optoin in field-options.

2 . So, then I thought may be we can overried its partial but there was no such option to do that. according to question we need to modify relation form-widget's content/partial markup.

Its was having this method to render its content

Relation widget's render method
public function render()
{
    $this->prepareVars();
    return $this->makePartial('relation');
}

So seems there is no chance to anyhow use any event to modify any veriables etc ... .

Now, Suddenly I got idea before that I extended one of my Form widget to add my field's partial path

Form widget extension
BackendWidgetsForm::extend(function($widget) {
    $widget->addViewPath(
        File::symbolizePath('~/plugins/hardik/plugin/field_partials')
    );
});

Solution


So Basically We need to extend relation-widget and include our view path where we can later overried _relation.html partial file.

Here's our relational field `galleries`
// HardikDemoModelsCollection <- model 
public $belongsToMany = [
    'galleries' => [
        'HardikDemoModelsGallery',
        'table' => 'hardik_demo_collection_gallery'
    ]
];
Here's our field `galleries` definition
fields:
    title:
        label: Title
        span: auto
        type: text
    galleries:
        label: Galaries
        nameFrom: title
        descriptionFrom: description
        span: left
        type: relation

So, Just like Form widget every widget has its own extend method and we are going to use that, But we need to make sure it only extended wiht-in specific Controller and Model

Extending Controller and Relation Widget using conditions
use SystemClassesPluginBase;
use HardikDemoControllersCollection;
use HardikDemoModelsCollection as CollectionModel;

class Plugin extends PluginBase
{
    public function boot() {

        // we only want to happen on Collection controller

        Collection::extendFormFields(function($form, $model, $context) {

            // and when model is CollectionModel     
            if (!$model instanceof CollectionModel) {
             return;
            }

            // we add path for our own partial file
            BackendFormWidgetsRelation::extend(function($widget) {
                $widget->addViewPath(
                   File::symbolizePath('~/plugins/hardik/demo/field_partials')
                );
          });
      });
    }

    // ....  other code  
}

Now we just need to put our _relation.htm partila file with our customized markup.

plugins/hardik/demo/field_partials/_relation.htm

I added marks which things I added manually with // WE ADDED THIS - start and // WE ADDED THIS - start comments, other thing is straight copy from respective partials. also added lot of comments to understand code better.

modules/backend/formwidgets/relation/partials/_relation.htm

and

modules/backend/widgets/form/partials/_field_checkboxlist.htm

Content of that partial: plugins/hardik/demo/field_partials/_relation.htm
<div class="relation-widget" id="<?= $this->getId() ?>">
<?php
// We added this condition exclusivly for our gallery relation
// we also know its many to many and we will do only
// for this field 'Collection[galleries]' else it will render normally
if($field->getName() != 'Collection[galleries]') {
    // for other relation fields we do normal partial as it is
    // modules/backend/formwidgets/relation/partials/_relation.htm
    echo $this->makePartial('~/modules/backend/widgets/form/partials/_field_'.$field->type.'.htm', ['field' => $field]);
} else {
    // for our 'Collection[galleries]' field
    // we used this _field_checkboxlist's content
    // modules/backend/widgets/form/partials/_field_checkboxlist.htm
    $fieldOptions = $field->options();
    $checkedValues = (array) $field->value;
    $isScrollable = count($fieldOptions) > 10;
    $readOnly = $this->previewMode || $field->readOnly || $field->disabled;
    $quickselectEnabled = $field->getConfig('quickselect', $isScrollable);

    // WE ADDED THIS - start
    list($model, $attribute) = $this->resolveModelAttribute($this->valueFrom);
    $relationType = $model->getRelationType($attribute);
    $relationModel = $model->makeRelation($attribute);
    // WE ADDED THIS - end
    ?>
    <!-- Checkbox List -->
    <?php if ($readOnly && $field->value): ?>

        <div class="field-checkboxlist">
            <?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
                <?php
                    $index++;
                    $checkboxId = 'checkbox_'.$field->getId().'_'.$index;
                    if (!in_array($value, $checkedValues)) continue;
                    if (is_string($option)) $option = [$option];
                ?>
                <div class="checkbox custom-checkbox">
                    <input
                        type="checkbox"
                        id="<?= $checkboxId ?>"
                        name="<?= $field->getName() ?>[]"
                        value="<?= e($value) ?>"
                        disabled="disabled"
                        checked="checked">

                    <label for="<?= $checkboxId ?>">
                        <?= e(trans($option[0])) ?>
                    </label>
                    <?php if (isset($option[1])): ?>
                        <p class="help-block"><?= e(trans($option[1])) ?></p>
                    <?php endif ?>
                </div>
            <?php endforeach ?>
        </div>

    <?php elseif (!$readOnly && count($fieldOptions)): ?>

        <div class="field-checkboxlist <?= $isScrollable ? 'is-scrollable' : '' ?>">
            <?php if ($quickselectEnabled): ?>
                <!-- Quick selection -->
                <div class="checkboxlist-controls">
                    <div>
                        <a href="javascript:;" data-field-checkboxlist-all>
                            <i class="icon-check-square"></i> <?= e(trans('backend::lang.form.select_all')) ?>
                        </a>
                    </div>
                    <div>
                        <a href="javascript:;" data-field-checkboxlist-none>
                            <i class="icon-eraser"></i> <?= e(trans('backend::lang.form.select_none')) ?>
                        </a>
                    </div>
                </div>
            <?php endif ?>

            <div class="field-checkboxlist-inner">

                <?php if ($isScrollable): ?>
                    <!-- Scrollable Checkbox list -->
                    <div class="field-checkboxlist-scrollable">
                        <div class="control-scrollbar" data-control="scrollbar">
                <?php endif ?>

                <input
                    type="hidden"
                    name="<?= $field->getName() ?>"
                    value="0" />

                <?php $index = 0; foreach ($fieldOptions as $value => $option): ?>
                    <?php
                        $index++;
                        $checkboxId = 'checkbox_'.$field->getId().'_'.$index;
                        if (is_string($option)) $option = [$option];
                        $gallery = $relationModel::find($value);
                    ?>
                    <div class="checkbox custom-checkbox">
                        <input
                            type="checkbox"
                            id="<?= $checkboxId ?>"
                            name="<?= $field->getName() ?>[]"
                            value="<?= e($value) ?>"
                            <?= in_array($value, $checkedValues) ? 'checked="checked"' : '' ?>>

                        <label for="<?= $checkboxId ?>">
                            <?= e(trans($option[0])) ?>
                        </label>
                        <div>
                          <?php
                            // WE ADDED THIS - start
                            $style = 'height:80px;width:100px;margin-right:1rem;';
                            foreach ($gallery->photos as $photo) {
                              echo "<img style='{$style}' src='{$photo->getPath()}' />";
                            }
                            // WE ADDED THIS - end
                          ?>
                        </div>
                        <?php if (isset($option[1])): ?>
                            <p class="help-block"><?= e(trans($option[1])) ?></p>
                        <?php endif ?>
                    </div>
                <?php endforeach ?>

                <?php if ($isScrollable): ?>
                        </div>
                    </div>
                <?php endif ?>

            </div>

        </div>

    <?php else: ?>

        <!-- No options specified -->
        <?php if ($field->placeholder): ?>
            <p><?= e(trans($field->placeholder)) ?></p>
        <?php endif ?>

    <?php endif ?>
<?php } ?>
</div>

So, now final moment let's check how it looks.

relation-widget-output

Ad
Ad