About Sharp for Laravel — Part 2.

Teams are Entities too

Philippe Lonchampt
code16

--

This story is the second part of a series which started here.

Like reviewed in the previous post, Sharp is an open source content management framework for Laravel and we choose to write a little demo with Players and Teams of handball (just see it as soccer, but with hands). In the last post, we ended with a Players form like this:

The issue we will tackle in this post is how to display a finite list of Teams instead of this regular text field. Let’s pretend we changed the Players team attribute for a team_id integer, we created a Team mobel, and a team() belongsTo relashionship from Player to Team.

A first option, in Sharp, to handle this change is to use a select field instead of the text field:

function buildFormFields()
{
[...]
$this->addField(
SharpFormSelectField::make(
"team_id",
Team::pluck("name", "id")->all())
->setLabel("Team")
->setDisplayAsDropdown()
);
[...]
}

Notice I changed team for team_id here, to follow a foreign key name convention, and therefore I had to report this change in the buildFormLayout() function. But I did not need to change anything in the update() function. Here it is:

function update($id, array $data)
{
$instance = $id ? Player::findOrFail($id) : new Player;

return tap($instance, function($player) use($data) {
$this->save($player, $data);
})->id;
}

This will work because Sharp, when using the WithSharpFormEloquentUpdater trait, will automatically handle relations if they are declared in the models—meaning that the Player model must look like this:

class Player extends Model
{
public function team()
{
return $this->belongsTo(Team::class);
}
}

So, here’s the result:

The select field works just like you’d expect:

But suppose now there are hundreds of Teams… this select would be hard to use, even if we could easily sort them alphabetically. Let’ change the field for an autocomplete:

function buildFormFields()
{
[...]
$this->addField(
SharpFormAutocompleteField::make("team_id", "local")
->setLabel("Team")
->setLocalValues(Team::all())
->setLocalSearchKeys(["name"])
->setListItemInlineTemplate("{{name}}")
->setResultItemInlineTemplate("{{name}}")
);
[...]
}

Reviewing this bit of code part by part:

SharpFormAutocompleteField::make("team_id", "local")
->setLabel("Team")

First we create the autocomplete, making it “local” (as opposite of “remote”, in an API case).

            ->setLocalValues(Team::all())
->setLocalSearchKeys(["name"])

Then we initialize values, which is “all teams”, and we indicate to Sharp which attributes should be used in the field search (obviously here, the name, but in many cases it is useful to declare more than one field here).

            ->setListItemInlineTemplate("{{name}}")
->setResultItemInlineTemplate("{{name}}")

And finally how teams should be displayed in the search list and in the field itself? This is the purpose of templates configuration, which is based on Vue.js templates and is quite powerful. Here we display the name, in both cases.

And here we go:

Okay, good enough. We need to slightly update the Player’s Entity List to reflect our changes:

function getListData(EntityListQueryParams $params)
{
$players = Player::orderBy(
$params->sortedBy(), $params->sortedDir()
);
[...] return $this
->setCustomTransformer("ratings", function($rating) {
[...]
})
->setCustomTransformer("team", function($team, $player) {
return $player->team->name;
})
->transform(
$players->with("team")->paginate(30)
);
}

Note there is a way to avoid this custom transformer, in common cases like this one, with the “:” separator:

function buildListDataContainers()
{
[...]
$this->addDataContainer(
EntityListDataContainer::make("team:name")
->setLabel("Team")
->setSortable()
);
[...]
}
function buildListLayout()
{
$this->addColumn("name", 4)
->addColumn("team:name", 4)
->addColumn("ratings", 4);
}

And to finish this second part, let’s add a Team Entity List, which should be pretty obvious:

class TeamSharpList extends SharpEntityList
{

function buildListDataContainers()
{
$this->addDataContainer(
EntityListDataContainer::make("name")
->setLabel("Name")
)->addDataContainer(
EntityListDataContainer::make("players")
->setLabel("Players")
);
}

function buildListConfig()
{
$this->setPaginated();
}

function buildListLayout()
{
$this->addColumn("name", 8)
->addColumn("players", 4);
}

function getListData(EntityListQueryParams $params)
{
return $this
->setCustomTransformer("players", function($players, $team) {
return $team->players()->count();
})
->transform(
Team::orderBy("name", "asc")->paginate(30)
);
}
}

Note that this code needs a players() HasMany relationship in the Team model.

The full code of this little project is available here: https://github.com/code16/sharp-handball-sample/tree/part-2.

In the next part, we’ll see that form validation is as simple as it should be, how we can add a country filter in the Teams list, and how we can build complex lists in forms to handle hasMany relationship on the other side.

Here the link to part 3 of this series.

--

--

Philippe Lonchampt
code16
Editor for

Developer and founder of Code 16, maintainer of Laravel Sharp.