Feedback Laraform is now in maintenance mode. Read more on our GitHub.

Creating Dynamic Tag

#Dynamic Tags

When using tags element we might come across a case where we have an initial list of options as possible tags but the user is allowed to create a new one. In this guide we're going to demonstrate a solution how this can be achieved.

#Database Tables

Let's say we have 3 data tables with the following fields:

user
  id - integer
  email - string
  password - string

skill
  id - integer
  name - string
  public - integer

skill_user
  user_id - integer
  skill_id - integer

#User Model

The User model is connected to Skill with belongsToMany:

<?php

// app/User.php

namespace App;

use Illuminate\Foundation\Auth\User as Authenticatable;
// ...

class User extends Authenticatable
{
    // ...

    public function skills() {
      return $this->belongsToMany(Skill::class);
    }
}
<?php

// app/Skill.php

namespace App;

use Illuminate\Database\Eloquent\Model;

class Skill extends Model
{
    //
}

#Creating Form

Now let's create a form where we'll assign skills to a user:

<?php

namespace App\Forms;

use App\Skill;
use App\User;

class UserForm extends \Laraform
{
  public $model = User::class;

  public function schema() {
    return [
      'id' => [
        'type' => 'key'
      ],
      'skills' => [
        'type' => 'tags',
        'label' => 'Skills',
        'create' => true,
        'items' => $this->skills()
      ]
    ]
  }

  protected function skills() {
    return Skill::where('public', 1)->mapWithKeys(function($skill) {
      return [
        $skill->id => $skill->name
      ]
    })
  }
}

If we render the form we can see that all the skills are available as tags. We also enabled create which means if we type a new string and hit enter a new tag will be added which does not exist in the database.

#Saving New Tag

The next step is to save a new skill tag when added. Let's create a before hook to save that before the entity gets inserted into the database:

class UserForm extends \Laraform
{
  // ...

  public function before() {
    $skills = collect($this->data['skills']);

    foreach ($skills->filter(function($value){ return !is_numeric($value); }) as $key => $name) {
      $skill = new Skill;
      $skill->name = $name;
      $skill->public = 0;
      $skill->save();

      $this->data['skills'][$key] = $skill->id;
    }
  }

  // ...
}

Now a new tag, which does not have a numeric value (meaning it's added manually) will be inserted to the database and its value will be replaced in the data. If you try saving the form new while having a new tag, you'll notice it will be created in the database and will be assigned to the user.

So far so good, but what if we try to load this form for the currently authenticated user for example? If that user has skills which aren't public they will not appear in the tags list.

#Appending Non-Public Skills

We need to make those ones available on a per user basis, meaning each authenticated user will have a unique list of tags which will include public tags plus the tags created by them.

Let's implement that by updating our skills method:

class UserForm extends \Laraform
{
 // ...

  protected function skills() {
    $existingSkills = Auth::user()->skills->keyBy('id')->keys();

    return Skill::where('public', 1)->orWhereIn('id', $existingSkills)->mapWithKeys(function($skill) {
      return [
        $skill->id => $skill->name
      ]
    })
  }
}

Now every skill that the user is assigned will surely among the tags list.

#Complete Form

Let's see the whole class in one extended with a Submit button:

<?php

namespace App\Forms;

use App\Skill;
use App\User;

class UserForm extends \Laraform
{
  public $model = User::class;

  public function schema() {
    return [
      'id' => [
        'type' => 'key'
      ],
      'skills' => [
        'type' => 'tags',
        'label' => 'Skills',
        'create' => true,
        'items' => $this->skills()
      ]
    ]
  }

  public function buttons() {
    return [[
      'label' => 'Submit'
    ]];
  }

  public function before() {
    $skills = collect($this->data['skills']);

    foreach ($skills->filter(function($value){ return !is_numeric($value); }) as $key => $name) {
      $skill = new Skill;
      $skill->name = $name;
      $skill->public = 0;
      $skill->save();

      $this->data['skills'][$key] = $skill->id;
    }
  }

  protected function skills() {
    $existingSkills = Auth::user()->skills->keyBy('id')->keys();

    return Skill::where('public', 1)->orWhereIn('id', $existingSkills)->mapWithKeys(function($skill) {
      return [
        $skill->id => $skill->name
      ]
    })
  }
}