3

I have a custom resource-tool working fine in the view panel of a resource, but it dont appears when i go o the edit mode. Is there something i should add to the component or to the Nova configuration to enable the component in the edit mode?

Code in User.php

public function fields(Request $request)
{

    return [
        ID::make()->sortable(),

        Text::make('First name', 'firstName')
            ->sortable()
            ->rules('required', 'max:255'),

        Text::make('Last name', 'lastName')
            ->sortable()
            ->rules('required', 'max:255'),

        Text::make('Email')
            ->sortable()
            ->rules('required', 'email', 'max:254')
            ->creationRules('unique:users,email')
            ->updateRules('unique:users,email,{{resourceId}}'),

        Password::make('Password')
            ->onlyOnForms()
            ->creationRules('required', 'string', 'min:6')
            ->updateRules('nullable', 'string', 'min:6'),

        YesNovaUserPermissions::make(),
    ];
}

User view:

enter image description here

User edit:

enter image description here

Ricardo Albear
  • 496
  • 8
  • 26

1 Answers1

4

Nova does not seem to allow you to obtain this functionality with a custom resource but you can with a custom field. You basically create a "dummy" field which does not really exist on the model and use a mutator on the model to overwrite the default model saving functionality.

Following the documentation above, you can build a Vue component which will appear within the resource edit form itself, similarly to how I have done with the tags picker pictured below.

Tag picker in form

Code for that:

<template>
  <default-field :field="field" :errors="errors" :show-help-text="showHelpText">

  <label for="tag" class="inline-block text-80 pt-2 leading-tight">Tag</label>

  <template slot="field">
    <div id="multitag-flex-holder">
      <div id="multitag-search-holder" class="w-1/2">
        <div class="search-holder">
          <label>Search Categories</label>
          <input type="text" v-model="searchString" @focus="isSearching = true" @blur="isSearching = false" style="border:2px solid #000"/>

          <div class="found-tags" v-if="isSearching">
            <div v-for="(tag, i) in foundTags" @mousedown="addToSelected(tag)" :key="i">{{tag.name}}</div>
          </div>
        </div>
      </div>

      <div class="select-tags-holder w-1/2">
        <div class="selected-tags">
          <div v-for="(tag, i) in selectedTags" :key="'A'+i" @click="removeTag(tag)">{{tag.name}}   X</div>
        </div>
      </div>
    </div>
  </template>

  </default-field>
</template>

<script>
import { FormField, HandlesValidationErrors } from 'laravel-nova'

export default {
  mixins: [FormField, HandlesValidationErrors],

  props: ['resourceName', 'resourceId', 'field'],

  data: function () {
    return {
      selectedTags:[],
      isSearching:false,
      searchString:''
    }
  },

  mounted(){
    console.log(this.field)
    this.field.value.forEach((tag)=>{
      this.addToSelected(tag)
    })
    formData.append('whereType', 'Tag');
  },

  computed: {
    // a computed getter
    foundTags() {
      // `this` points to the vm instance

        return this.field.tags.filter((tag) => {
          if(tag.name.search(new RegExp(this.searchString, "i")) >= 0){
            if(this.selectedTagNames.indexOf(tag.name) == -1){
              return tag;
            }
          };
        })

    },
    selectedTagNames(){
      var selNames = this.selectedTags.map((tag) => {
          return tag.name;
      })
      return selNames;
    }
  },

  methods: {
    /*
     * Set the initial, internal value for the field.
     */
    setInitialValue() {
      this.value = this.field.value || ''
    },

    removeTag(tag){
      var index = this.selectedTags.indexOf(tag);
      if (index > -1) {
        this.selectedTags.splice(index, 1);
      }
    },

    addToSelected(tag){
      this.selectedTags.push(tag)
    },

    /**
     * Fill the given FormData object with the field's internal value.
     */
    fill(formData) {
      var tagIdArray = []
      this.selectedTags.forEach((tag)=>{
        tagIdArray.push(tag.id)
      })
      formData.append(this.field.attribute, tagIdArray)
    },
  },
}

</script>

Then, you can overwrite how the save functionality works in your model to accommodate for the "dummy" field. Note below instead of syncing the tags directly on the mutator, which will work most of the time depending on your data structure, I had to pass the tags to the "Saved" event on the model to accommodate for when creating a new record and the associated record id is not yet available, thus cannot be synced for a many to many relationship.

public function setTagsAttribute($value)
{

    $tags = explode(",", $value);
    $this->tempTags = $tags;
    unset($this->tags);
}

protected static function booted()
{
    static::saved(function ($article) {
        $article->tags()->sync($article->tempTags);
    });
}
A_funs
  • 1,228
  • 2
  • 19
  • 31