May Sale! All accounts are 40% off this week.

iarwain's avatar

How to extend Illumimnate\View\ComponentAttributeBag

Illuminate\View\ComponentAttributeBag has a funtion merge()

The function merges attributes which is super helpful when building nested components. However, the merge handles class attributes different from all other attributes by appending, rather than overwriting. I would like to extend the function so that other types of attributes can be appended rather than overwritten. Specifically, I'm building components that utilize AlpineJS. AlpineJS uses custom attributes to declare functionality. Specifically the x-on: directive (@click, @input, @mousedown,...). But when components are nested, and you merge attributes with the current function... only the outermost components x-on directives get included.

I would like to extend that function so that I can conditionally merge x-on attributes by appending rather than overwriting.

It's also weird because you HAVE to use the ComponentAttributeBag to pass attributes along. I tried just building my own array, but blade ends up not rendering the component.

Is there a laravel(y) way for me to extend that class?

For now, I just have this little function to do the job:

  static function mergeXonAttributes($attributes,$newXonAttributes){
    $xonData = [];
    foreach($attributes as $key=>$value) {
      $xon = (strpos($key,'@')!==false || strpos($key,'x-on')!==false);
      if(!$xon) continue;
      $xonData[$key] = implode(';', array_unique(
        array_filter([$newXonAttributes[$key] ?? '', $value])
      ));
    }
    $xonData = array_merge($newXonAttributes,$xonData);
    $attributes->setAttributes(array_merge($attributes->getAttributes(),$xonData));
  }
0 likes
8 replies
jaseofspades88's avatar

That just looks like a mess. What is the problem you're trying to solve?

iarwain's avatar

What is the Laravel way to extend this Class, add a new function to it... or is there a way? Illuminate\View\ComponentAttributeBag

iarwain's avatar

component 1:

<input {{ $attributes->merge(['class'=>'foo2', '@change'=>"handleChange2()"]) }}  />

component 2:

<div id="inputWrapper">
			<x-component-1 {{ $attributes->merge(['class'=>'foo3', '@change'=>"handleChange3()"]) }} />
</div>

Blade:

<x-component-2 class="foo1" @change="handleChange1()" />

Output:

<div id="inputWrapper">
			<input class="foo1 foo2 foo3" @change="handleChange1()" />
</div>

Notice the classes got appended, but the @change functions did not. I want it to be:

<div id="inputWrapper">
			<input class="foo1 foo2 foo3" @change="handleChange1();handleChange2();handleChange3();" />
</div>

Also, I'd rather not have to write @props for every @ (x-on) function, on every component I build in order to pass them around. This should be handled in the merge function.

Sinnbeck's avatar
Sinnbeck
Best Answer
Level 102

The class implements macroable so you can add your own method to it as a macro

1 like
iarwain's avatar

@Sinnbeck thanks for the replies today. You're blog post was very helpful and did answer my actual question... however, I just updated laravel and found out the devs have added the functionality I was going to write.

iarwain's avatar

OK, looks like I was behind the times. After update to Laravel 9 it seems the developers found out how troubling the original implementation was. They've added an AppendableAttributeValue, which can be used like so in the merge function.

$attributes->merge([
	'class'=>'mr-6 class2'
	'@change' => $attributes->prepends('onChange2($event);')
]) 

reference: github.com/laravel/framework/blob/9.x/src/Illuminate/View/AppendableAttributeValue.php github.com/laravel/framework/blob/9.x/src/Illuminate/View/ComponentAttributeBag.php

Please or to participate in this conversation.