77use Filament \Forms \Components \Concerns \CanBeSearchable ;
88use Filament \Forms \Components \Concerns \HasPlaceholder ;
99use Filament \Forms \Components \Field ;
10+ use Illuminate \Database \Eloquent \Concerns \HasRelationships ;
11+ use Illuminate \Database \Eloquent \Relations \BelongsTo ;
12+ use Illuminate \Database \Eloquent \Relations \BelongsToMany ;
1013use Illuminate \Support \Collection ;
1114
1215class SelectTree extends Field
1316{
17+ use HasRelationships;
1418 use CanBeDisabled;
1519 use CanBeSearchable;
1620 use HasPlaceholder;
@@ -27,21 +31,19 @@ class SelectTree extends Field
2731
2832 protected bool $ independent = true ;
2933
34+ protected string $ titleAttribute ;
35+
3036 protected bool $ clearable = true ;
3137
3238 protected bool $ expandSelected = true ;
3339
34- protected bool $ disabledBranchNode = false ;
40+ protected bool $ enableBranchNode = false ;
3541
3642 protected bool $ grouped = true ;
3743
38- protected ?string $ treeModel = null ;
39-
4044 protected array $ options = [];
4145
42- protected string $ treeParentKey ;
43-
44- protected string $ titleAttribute ;
46+ protected string |Closure $ relationship ;
4547
4648 protected ?Closure $ modifyQueryUsing ;
4749
@@ -52,6 +54,92 @@ public function withCount(bool $withCount = true): static
5254 return $ this ;
5355 }
5456
57+ protected function setUp (): void
58+ {
59+ $ this ->relationship = $ this ->getName ();
60+
61+ $ this ->loadStateFromRelationshipsUsing (static function (self $ component ): void {
62+ $ relationship = $ component ->getRelationship ();
63+ if ($ relationship instanceof BelongsTo) {
64+ $ component ->state ($ relationship ->getResults ()?->getKey());
65+ } else {
66+ $ results = $ relationship ->getResults ();
67+ $ state = $ results
68+ ->pluck ($ relationship ->getRelatedKeyName ())
69+ ->toArray ();
70+ $ component ->state ($ state );
71+ }
72+ });
73+
74+ $ this ->saveRelationshipsUsing (static function (self $ component , $ state ) {
75+ if ($ component ->getRelationship () instanceof BelongsToMany) {
76+ $ state = Collection::wrap ($ state ?? []);
77+ $ component ->getRelationship ()->sync ($ state ->toArray ());
78+ $ component ->dehydrated (false );
79+ }
80+ });
81+
82+ }
83+
84+ private function buildTree (int $ parent = null ): array |Collection
85+ {
86+
87+ if ($ this ->getOptions ()) {
88+ return $ this ->getOptions ();
89+ }
90+
91+ if ($ this ->getRelationship () instanceof BelongsTo) {
92+ $ key = $ this ->getRelationship ()->getForeignKeyName ();
93+ }
94+
95+ if ($ this ->getRelationship () instanceof BelongsToMany) {
96+ $ key = $ this ->getRelationship ()->getRelatedPivotKeyName ();
97+ }
98+
99+ $ defaultQuery = $ this ->getRelationship ()->getRelated ()->query ()
100+ ->where ($ key , $ parent );
101+
102+ // // If we're not at the root level and a modification callback is provided, apply it.
103+ // if (!$parent && $this->modifyQueryUsing) {
104+ // $defaultQuery = $this->evaluate($this->modifyQueryUsing, ['query' => $defaultQuery]);
105+ // }
106+
107+ // Fetch the results from the default query.
108+ $ results = $ defaultQuery ->get ();
109+
110+ // Map the results into a tree structure.
111+ return $ results ->map (function ($ result ) {
112+
113+ // Recursively build children trees for the current result.
114+ $ children = $ this ->buildTree ($ result ->id );
115+
116+ // Create an array representation of the current result with children.
117+ return [
118+ 'name ' => $ result ->name ,
119+ 'value ' => $ result ->id ,
120+ 'children ' => $ children ->isEmpty () ? null : $ children ->toArray (),
121+ ];
122+ });
123+ }
124+
125+ public function relationship (string $ relationship , string $ titleAttribute ): self
126+ {
127+ $ this ->relationship = $ relationship ;
128+ $ this ->titleAttribute = $ titleAttribute ;
129+
130+ return $ this ;
131+ }
132+
133+ public function getRelationship (): BelongsToMany |BelongsTo
134+ {
135+ return $ this ->getModelInstance ()->{$ this ->evaluate ($ this ->relationship )}();
136+ }
137+
138+ public function getTitleAttribute (): string
139+ {
140+ return $ this ->evaluate ($ this ->titleAttribute );
141+ }
142+
55143 public function clearable (bool $ clearable = true ): static
56144 {
57145 $ this ->clearable = $ clearable ;
@@ -94,23 +182,16 @@ public function alwaysOpen(bool $alwaysOpen = true): static
94182 return $ this ;
95183 }
96184
97- public function multiple (bool $ multiple = true ): static
98- {
99- $ this ->multiple = $ multiple ;
100-
101- return $ this ;
102- }
103-
104185 public function options (array $ options ): static
105186 {
106187 $ this ->options = $ options ;
107188
108189 return $ this ;
109190 }
110191
111- public function disabledBranchNode (bool $ disabledBranchNode = true ): static
192+ public function enableBranchNode (bool $ enableBranchNode = true ): static
112193 {
113- $ this ->disabledBranchNode = $ disabledBranchNode ;
194+ $ this ->enableBranchNode = $ enableBranchNode ;
114195
115196 return $ this ;
116197 }
@@ -142,7 +223,7 @@ public function getWithCount(): bool
142223
143224 public function getMultiple (): bool
144225 {
145- return $ this ->evaluate ($ this ->multiple );
226+ return $ this ->evaluate ($ this ->getRelationship () instanceof BelongsToMany );
146227 }
147228
148229 public function getClearable (): bool
@@ -160,62 +241,13 @@ public function getOptions(): array
160241 return $ this ->evaluate ($ this ->options );
161242 }
162243
163- public function getDisabledBranchNode (): bool
244+ public function getEnableBranchNode (): bool
164245 {
165- return $ this ->evaluate ($ this ->disabledBranchNode );
246+ return $ this ->evaluate ($ this ->enableBranchNode );
166247 }
167248
168249 public function getEmptyLabel (): string
169250 {
170- return ! $ this ->emptyLabel ? __ ('No results found ' ) : $ this ->evaluate ($ this ->emptyLabel );
171- }
172-
173- public function tree (string $ treeModel , string $ treeParentKey , string $ titleAttribute , Closure $ modifyQueryUsing = null ): static
174- {
175- $ this ->treeModel = $ treeModel ;
176- $ this ->treeParentKey = $ treeParentKey ;
177- $ this ->titleAttribute = $ titleAttribute ;
178- $ this ->modifyQueryUsing = $ modifyQueryUsing ;
179-
180- return $ this ;
181- }
182-
183- private function buildTree (int $ parent = null ): array |Collection
184- {
185- // Check if custom options are set; if yes, return them.
186- if ($ this ->getOptions ()) {
187- return $ this ->getOptions ();
188- }
189-
190- // Check if the treeModel is not set; if yes, return an empty collection.
191- if (! $ this ->treeModel ) {
192- return collect ();
193- }
194-
195- // Create a default query to fetch items with the specified parent ID.
196- $ defaultQuery = $ this ->treeModel ::query ()
197- ->where ($ this ->treeParentKey , $ parent );
198-
199- // If we're not at the root level and a modification callback is provided, apply it.
200- if (! $ parent && $ this ->modifyQueryUsing ) {
201- $ defaultQuery = $ this ->evaluate ($ this ->modifyQueryUsing , ['query ' => $ defaultQuery ]);
202- }
203-
204- // Fetch the results from the default query.
205- $ results = $ defaultQuery ->get ();
206-
207- // Map the results into a tree structure.
208- return $ results ->map (function ($ result ) {
209-
210- // Recursively build children trees for the current result.
211- $ children = $ this ->buildTree ($ result ->id );
212-
213- // Create an array representation of the current result with children.
214- return [
215- 'name ' => $ result ->{$ this ->titleAttribute },
216- 'value ' => $ this ->getMultiple () ? $ result ->{$ this ->titleAttribute } : $ result ->id ,
217- 'children ' => $ children ->isEmpty () ? null : $ children ->toArray (),
218- ];
219- });
251+ return $ this ->emptyLabel ? $ this ->evaluate ($ this ->emptyLabel ) : __ ('No results found ' );
220252 }
221253}
0 commit comments