Mutating data in Alpine JS - alpine.js

I am trying to achieve something that seems trivial.
<main x-data='x'>
<p x-text='foo'>
</main>
The foo needs to be changed by some external event (callback from a library etc.)
This
window.x = {
foo: 'bar',
setFoo: foo => this.foo = foo
}
// late, after `alpine:init`
window.x.foo = 'boo' // doesn't work
window.x.setFoo('boo') // doesn't work
The same goes for the $store.
I can try and declare Alpine.data('x'), but then there is no (documented) way to call a setter.

In your example x is now an Alpine.js component, so you have to use the Alpine.js way to mutate the reactive data. First, instead of p-text, you have to use x-text:
<main x-data='x'>
<p x-text='foo'>
</main>
And to mutate data, you can access the reactive properties in $data object:
x.$data.foo = '42'
For the store you can use the global Alpine.store() method:
// Create number1 property in $store:
document.addEventListener('alpine:init', () => {
Alpine.store('number1', '0')
})
// Set number1 to 42 externally:
Alpine.store('number1', 42)

Related

In svelte 3 is the $: reactive statement declaration slower or faster than HTML element class attribute? How can I measure it effectively?

Since I am building a series of components that will be updated several times per second and in large quantities I was wondering if there was a difference in FPS between the first solution below and the second:
First one (HTML element class)
<script>
export let className = undefined
export let something = false
export let somethingelse = false
</script>
<div class={`myCustomClass1 myCustomClass2 ${something ? "myCustomClass3" : "myCustomClass4"}${somethingelse ? "myCustomClass5" : "myCustomClass6"}${className ? ` ${className}` : ""}`}
>
Hello!
</div>
Second one ($: reactive statement):
<script>
export let className = undefined
export let something = false
export let somethingelse = false
$: classes = "myCustomClass1 myCustomClass2${something ? "myCustomClass3" : "myCustomClass4"}${somethingelse ? "myCustomClass5" : "myCustomClass6"}${className ? ` ${className}` : ""}
</script>
<div class={classes}>Hello!</div>
Basically: is the $: reactive statement declaration slower than HTML element class attribute?
How can I measure it effectively?
I wanted to comment because this will not directly answer your question, but I don't have enough rep
to bind a class to a variable, svelte has this, it might be more practical for you :
<script>
export let classIsActive = false;
export let evenShorter = false;
</script>
<div
class:activateClass={classIsActive}
class:evenShorter
class:combinationClass={classIsActive && evenShorter}
/>
In my opinion, I think that there is no difference in performance on both methods because for both methods every time the props get updated the class attribute need to be recalculated.
However, I prefer using $: classes = ... cause it increases the readability of the code, also you may need to declare a function that handles the logic of building class attribute, for example:
export function classNames(...args){
return args.map(arg=>{
if(Array.isArray(arg)){
if(arg.length === 1){
return arg[0]
}
return arg[0] ?arg[1] :arg[2]||''
}
return arg
}).join(' ')
}
App.svelte
<script>
import {classNames} from './classNames'
export let className = undefined
export let something = false
export let somethingelse = false
$:classnames = classNames("myCustomClass1 myCustomClass2", [something, "myCustomClass3", "myCustomClass4"], [somethingelse, "myCustomClass5","myCustomClass6"], [className])
</script>
<div class={classnames}>
Hello!
</div>

How to pass a Laravel filtered collection as array to Vue?

I have a component which I'm trying to make it receive an array.
If I do it like this in a blade file:
<mega-menu :grouped-categories="{{ $categories->toJson() }}"></mega-menu>
It works as intended. It appears in Vue as array.
However, if I try to run some collection methods like 'filter' and 'groupBy' and then apply 'toJson', Vue receives it as an Object which is not what I need.
<mega-menu
:grouped-categories="{{ $categories->filter(function($category) { return $category['enabled']; })->groupBy('group)->toJson() }}">
</mega-menu>
I'm suspicious that something is happening during filtering or grouping that converts it into an array. Any ideas on how to manipulate ´$categories´ variable which is an instance of Collection and make it pass as array to Vue?
According to the information provided by #Vivick, the associative arrays pass to Javascript as Objects. So I'll handle the Object inside the Vue component.
To avoid problems with special chars add addslashes in template like:
:users="'{{ addslashes(\App\Users::all()->toJson()) }}'"
In vue component get the Users as prop:
props: {
users: String
},
And add it to data
data() {
return {
users: JSON.parse(this.mailinglists)
};
},
Hope this helps.
use
v-bind:color="{{json_encode($yourVariable)}}"
in element
for example:
<color-item v-bind:color="{{json_encode($yourVariable)}}" ></color-item>
in your controller:
$myArray = array("name" => "لوازمات");
$item = "لوازمات";
$collection = Color::all();
return view('home', compact('item', 'collection', 'myArray', ...));
in your blade:
#extends('layouts.app', ['item' => $item,'myArray' => $myArray, 'colors'=> $collection])
pass variable into vuejs:
<primary-header
:colors="{{json_encode($colors)}}"
:item="{{json_encode($item)}}"
v-bind:my-array="{{json_encode($myArray)}}">
in your vuejs file
<template>
<div>
{{item}}
{{colors[0].name}}
{{myArray.name}}
</div>
</template>
<script>
export default {
name: "primary-header",
props: {
item: String,
myArray: Object,
colors: Array
},
methods: {
}
};
</script>
<style scoped>
</style>

Laravel Blade Composor/Creator override

I am intending to provide a default set of variables to various blade views with the ability to override them later. For example
a.blade.php
<p>a.blade</p>
<p>{{ $foo }}</p>
#include('b')
#include('b', ['bar' => 'bar as param'])
b.blade.php
<p>b blade</p>
<p>{{ $bar }}</p>
When I use View::creator, it allows me to use ->with() at runtime to recompose my views and override the ones bound in the creator. If I use View::composer, this is not the case.
So given the below creator
View::creator('a', function ($view) {
$view->with(['foo' => 'foo as CREATED']);
});
I am able to then use (in a route Closure for example)
return view('a')->with('foo', 'foo from composition');
// Expected a.blade foo from composition (excluding inclusion of b)
// Actual a.blade foo from composition (excluding inclusion of b)
But I am not able to use
return view('a', ['foo' => 'foo from composition']);
// Expected a.blade foo from composition (excluding inclusion of b)
// Actual a.blade foo as CREATED (excluding inclusion of b)
This isn't so much of an issue here as I can use ->where() but my problem lies in the fact that I can no longer use blade directives #include or #component within my templates.
So given the above example, add a creator for a
View::creator('b', function ($view) {
$view->with(['bar' => 'bar AS CREATED']);
});
Then in a.blade.php as above
#include('b')
#include('b', ['bar' => 'bar as param'])
// Expected
// b.blade bar AS CREATED
// b.blade bar as param
// Actual
// b.blade bar AS CREATED
// b.blade bar AS CREATED
So I'm looking for a way I can achieve the ability to provide defaults for variables to views and then override them with these syntax
view('a', ['foo' => 'bar']);
#include('a', ['foo' => 'bar'])
#component('a', ['foo' => 'bar'])

How to pass javascript variable from laravel controller to vuejs component

I'm trying to build a page on Laravel 5.4 which contains few data which needs to be manipulated and then sent across the views. My view contains components of the vuejs v2.0. I want those data to be implemented in the components. I tried using the laracasts PHP Vars to JS transformer but unable to get it. I followed the steps by placing "laracasts/utilities": "~2.0" in my composer.json then I added the serviceprovider as mentioned in the documentation, I published the vendor and added the following in config/javascript.php,
'bind_js_vars_to_this_view' => 'Nitseditor.show',
I'm having a dynamic views folder which is currently inside my Nitseditor\home\resources\views Now in my controller I'm having following codes:
public function show()
{
JavaScript::put([
'foo' => 'bar',
'age' => 29
]);
return view(Nitseditor.show);
}
Now first of all it was throwing an error as I see that it was including use MongoDB\BSON\Javascript; then I removed and tried using use JavaScript
Now in the app.js file which is present in my asset folder, I'm including each components and trying to do console.log(foo); but its throwing an error foo not defined.
There are a few ways to do this depending on what you are trying to achieve and how your project is set up. The simplest way is to make a request to your controller from inside your component that returns json. Laravel 5.4 comes with axios so you can use that:
methods: {
getData(){
axios.get('/controller/route')
.then(response => {
// set your variables from the response
this.myData = response.data;
})
.catch(error => {
console.log(error);
});
},
data(){
return {
myData: {}
}
}
If you need child components to access the data then you would need to put that in the parent and pass myData" using props.
You could also create a directive and pass your variable down directly from your blade template:
Vue.directive('init', {
bind: function(el, binding, vnode) {
vnode.context[binding.arg] = binding.value;
}
});
Then you would just need to do:
<div v-init:vars="{foo: 'foo', age: 29}"></div>
And pass vars as props to any component that needs them:
Here's the JSFiddle: https://jsfiddle.net/3e05qwLh/
If you have multiple descendants that rely on your variables you will probably want to look at using vuex.
Dunno if it helps but I use this method for passing variables to javascript:
// in master layout (head section)
<meta name="foo" content="{{ $foo }}">
// in javascript (included or in the template)
foo = $('meta[name=foo]').attr('content');
If you have javascript in the blade template you can use directly this method:
foo = "{{ $foo }}";

How to get the size of a filtered (piped) set in angular2

I wrote my own filter pipe as it disappeared in angular2:
import {Pipe, PipeTransform} from 'angular2/core';
#Pipe({
name: 'myFilter'
})
export class MyFilter implements PipeTransform {
transform(customerData: Array<Object>, args: any[]) {
if (customerData == undefined) {
return;
}
var re = new RegExp(args[0]);
return customerData.filter((item) => re.test(item.customerId));
}
}
And use it in my template:
<tr *ngFor="#singleCustomerData of customerData | myFilter:searchTerm">
...
</tr>
Now I'd like to see how many matches the pipe returns. So essentially the size of the returned array.
In angular 1.x we were able so assign the returned set to a variable in a template like so:
<div ng-repeat="person in filtered = (data | filter: query)">
</div>
But we can no longer assign variables in templates in angular2.
So how do I get the size of the filtered set without calling the filter twice?
You still must call the filter a second time but you can use it directly like this :
{{ (customerData | myFilter:searchTerm)?.length }}
original
AFAIK there is currently no way to do this directly. A hack would be to add a template variable to the content and use a ViewChildren(...) query to get the created items and count them.
<tr *ngFor="let singleCustomerData of customerData | myFilter:searchTerm" #someVar>
...
</tr>
<div>count: {{filteredItems?.length}}</div>
#ViewChildren('someVar') filteredItems;
An alternative approach would be to pass a reference to a counter variable to the pipe like shown in https://plnkr.co/edit/Eqjyt4qdnXezyFvCcGAL?p=preview
update (Angular >=4.0.0)
Since Angular 4 *ngFor supports as
<tr *ngFor="let singleCustomerData of customerData | myFilter:searchTerm as result">
which you can use in the template (inside the element that *ngFor is added to) like
<div>{{result?.length}}</div>
Gunter answer is in the right direction, it lacks only the info on how to use the result out of the *ngFor loop. One possible solution is to enclose the *ngFor in a wider directive, like the following:
<ng-directive *ngIf='customerData | myFilter:searchTerm as filteredItems'>
<tr *ngFor="let singleCustomerData of filteredItems">
...
</tr>
<div>count: {{filteredItems.length}}</div>
</ng-directive>
Credits for this hint go to the following post:
https://netbasal.com/using-pipe-results-in-angular-templates-430683fa2213
I don't know what you exactly want to do with the size and the Günter's solution can fit your needs.
That said, you can inject the component instance into your pipe and set directly the length into a property of this component.
#Pipe({
name: 'dump'
})
export class DumpPipe {
constructor(#Inject(forwardRef(() => AppComponent)) app:AppComponent) {
this.app = app;
}
transform(array: Array<string>, args: string): Array<string> {
(...)
this.app.filteredItemLength = array.length;
return array;
}
}
#Component({
(...)
})
export class AppComponent {
(...)
}
See this answer:
How to save model manipulation of *ngFor with pipes? - "#item of (result = (items | orderAsc))" doesn't work in A2.
Hope it helps you,
Thierry
You can simply pass an object from the class component to HTML-pipe as the second argument. And in the class pipe pass the resulting array.

Resources