How to self reference Astro component - astrojs

I have this component Astro component (located at "src/components/Menu.astro");
---
export interface MenuItem {
name: string;
link: string;
items?: MenuItem[];
}
export interface Props {
items: MenuItem[];
depth: number;
}
const { items, depth } = Astro.props;
---
<ul data-depth={depth}>
{
items.map(({ name, link, items: subItems }) => {
if (subItems && subItems.length > 0) {
return (
<li>
<div class="dropdown">
{link ? <a href={link}>{name}</a> : <button>{name}</button>}
<Menu items={subItems} depth={depth + 1} />
</div>
</li>
);
}
return (
<li>
<a href={link}>{name}</a>
</li>
);
})
}
</ul>
On line 28 (where the line reads <Menu items={subItems} depth={depth + 1} />) an error thrown saying;
ReferenceError: Menu is not defined
How can I self reference an Astro component in this case? Thanks in advance.
PS: "Menu" is the component file's name.

Astro has a built in method for this called Astro.self that you can use
Astro.self
Example from docs:
---
const { items } = Astro.props;
---
<ul class="nested-list">
{items.map((item) => (
<li>
<!-- If there is a nested data-structure we render `<Astro.self>` -->
<!-- and can pass props through with the recursive call -->
{Array.isArray(item) ? (
<Astro.self items={item} />
) : (
item
)}
</li>
))}
</ul>

In the front-matter, i.e. between the two ---, you write import Menu from "./Menu.astro".
You'll get new errors, as you can't use statements in the body.
Change
items.map(({ name, link, items: subItems }) => {
if (subItems && subItems.length > 0) {
return (
<li>
<div class="dropdown">
{link ? <a href={link}>{name}</a> : <button>{name}</button>}
<Menu items={subItems} depth={depth + 1} />
</div>
</li>
);
}
return (
<li>
<a href={link}>{name}</a>
</li>
);
})
to
items.map(({ name, link, items: subItems }) =>
(subItems && subItems.length > 0)
? <li>
<div class="dropdown">
{link ? <a href={link}>{name}</a> : <button>{name}</button>}
<Menu items={subItems} depth={depth + 1} />
</div>
</li>
: <li>
<a href={link}>{name}</a>
</li>
)

Related

Splice doesn't delete any item from array - Angular12

I am trying create a function for deleting specific items from a shopping cart.
It doesn't give any errors, but when I click the button nothing happens. Any idea what might be the problem?
See the codes below about my issue.
service.ts
removeCartItem(product: Product){
this.cartItemList.map((a:any, index:any)=>{
if(product.id=== a.id){
this.cartItemList.splice(index,1);
}
})
cartitem.component.html
<div class="cartitem">
<div class="container">
<div class="row">
<div class="col">
{{ cartItem.name }}
</div>
<div class="col">
<img [src]="cartItem.imageUrl" class="card-img-top" alt="..." />
</div>
<div class="col-5">
{{ cartItem.description }}
</div>
<div class="col">{{ cartItem.price | currency: "EUR" }}</div>
<div class="col">{{ cartItem.qty }}</div>
<div>
<button (click)="removeItem(item)" class="btn btn-primary">
Remove from cart
</button>
</div>
</div>
</div>
</div>
cartitem.component.ts
constructor(public service: MessengerService) { }
ngOnInit(): void {
}
removeItem(item: Product){
this.service.removeCartItem(item);
}
cart.component.ts
cartItems: Product[] = [];
cartTotal = 0;
product: any;
constructor(private msg: MessengerService, public dialog: MatDialog) {}
ngOnInit() {
this.msg.getMsg().subscribe((product: Product) => {
this.addProductToCart(product);
});
}
addProductToCart(product: Product) {
let productExists = false;
for (let i in this.cartItems) {
if (this.cartItems[i].id === product.id) {
this.cartItems[i].qty++;
productExists = true;
break;
}
}
if (!productExists) {
this.cartItems.push({
id: product.id,
name: product.name,
description: product.description,
qty: 1,
price: product.price,
imageUrl: product.imageUrl,
purchased:product.purchased
});
}
this.cartItems.forEach((item) => {
this.cartTotal += item.qty * item.price;
});
}
cart.component.html
<ul *ngIf="cartItems.length > 0" class="list-group">
<li class="list-group-item">
<h3>My Cart</h3>
</li>
<li class="list-group-item" *ngFor="let item of cartItems">
<app-cartitem [cartItem]="item"></app-cartitem>
</li>
<li class="list-group-item">
<span>Total: {{ cartTotal | currency: "EUR" }} </span>
</li>
<li class="list-group-item">
<button
id="btnFinalize"
class="btn btn-primary"
(click)="purchaseDisabled(product)"
>
Finalize purchase
</button>
</li>
</ul>
The problem is that you are trying to splice inside map. Changing to the following will work:
removeCartItem(product: Product){
let indexToRemove: number = -1;
this.cartItemList.map((a:any, index:any)=>{
if(product.id === a.id){
indexToRemove = index;
}
return a;
});
if(indexToRemove !== -1){
this.cartItemList.splice(indexToRemove,1);
}
}
But notice there is no need to use map here. It's a waste of time and memory to copy the entire array again. Just a loop through the array to find the index to remove would be enough:
removeCartItem(product: Product){
let indexToRemove: number = -1;
let index: number = 0;
for(const cardItem of this.cartItemList){
if(product.id === cardItem.id){
indexToRemove = index;
}
index++;
}
if(indexToRemove !== -1){
this.cartItemList.splice(indexToRemove,1);
}
}

Why does Vue evaluate my v-if to false despite it being true?

When I run this, vue returns the second template, even though groups.length is equal to 1.
Why? Does it have to do with the order in which the mounting occurs and v-if is evaluated? Again, I am certain that groups.length evaluates to 1. I have tried using beforeMount as opposed to mounted, but that did not work.
<template v-if = "groups.length">
<ul id = "groupList">
<li v-for = "group in groups">
<a>{{ group.name }}</a>
</li>
<div class = "addSidebar">
<label class = "btn" for = "modal-1">+</label>
</div>
</ul>
</template>
<template v-else>
<ul id = "groupList">
<li>
<a>You have not created/joined any groups.</a>
</li>
<div class = "addSidebar">
<label class = "btn" for = "modal-1">+</label>
</div>
</ul>
</template>
<script>
export default {
data() {
return {
groups: {}
}
},
methods: {
getGroups() {
axios.get('groupList').then((response) => {
this.groups = response.data
}).catch((errors) => {
console.log(errors)
});
},
newModal() {
$('#modal').modal('show');
}
},
mounted() {
this.getGroups()
},
name: "groupList"
}
</script>
you need to use javascript Async
https://www.w3schools.com/js/js_async.asp
<template >
<div>
<ul id="groupList" v-if="groups.length">
<li v-for="group in groups" :key="group.id">
<a>{{ group.name }}</a>
</li>
<div class="addSidebar">
<label class="btn" for="modal-1">+</label>
</div>
</ul>
<ul id="groupList" v-else>
<li>
<a>You have not created/joined any groups.</a>
</li>
<div class="addSidebar">
<label class="btn" for="modal-1">+</label>
</div>
</ul>
</div>
</template>
<script>
export default {
data() {
return {
groups: {},
};
},
methods: {
async getGroups() {
await axios
.get("groupList")
.then((response) => {
this.groups = response.data;
})
.catch((errors) => {
console.log(errors);
});
},
newModal() {
$("#modal").modal("show");
},
},
async mounted() {
await this.getGroups();
},
name: "groupList",
};
</script>
in your code you created 2 <template > which is not valid syntax and vue should have root element
https://v2.vuejs.org/v2/guide/components.html#A-Single-Root-Element
Change
v-if = "groups.length"
to:
v-if="groups && groups.length > 0"
And you should have a single that contains one elements in it.

Conditional Component variable value increment Vue/Laravel

[1]so i have a laravel project going on, and i want to increment the value of the variable deliver_later_num, depending on the "deliver_today" present in the same component in the items[] array, which i am outputting in the template file, i cannot figure how to do it, i do not know if i can increment the value on the template side or on the component side. here is the component code:
cartContent = new Vue({
el: '#cartList',
data: {
items: [], //array containing all the items
deliver_later_num: 0, //value to increment
},
methods: {
remove: function (product_id) {
removeProductIfFromCart(product_id);
},
incQuantity: function (product_id){
incCart(product_id)
},
decQuantity: function (product_id){
decCart(product_id)
},
}
})
here is the template file :
<div id="cartList">
<div v-for="item in items" class="items col-xs-12 col-sm-12 col-md-12 col-lg-12 clearfix">
<div class="info-block block-info clearfix" v-cloak>
<div class="square-box pull-left">
<img :src="item.attributes.image" class="productImage" width="100" height="105" alt="">
</div>
<h6 class="product-item_title">#{{ item.name }}</h6>
<p class="product-item_quantity">#{{ item.quantity }} x #{{ item.attributes.friendly_price }}</p>
<ul class="pagination">
<li class="page-item">
<button v-on:click="decQuantity(item.id)" :value="item.id" class="page-link" tabindex="-1">
<i class="fa fa-minus"></i>
</button>
</li>
<li class="page-item">
<button v-on:click="incQuantity(item.id)" :value="item.id" class="page-link" >
<i class="fa fa-plus"></i>
</button>
</li>
<li class="page-item">
<button v-on:click="remove(item.id)" :value="item.id" class="page-link" >
<i class="fa fa-trash"></i>
</button>
</li>
<input hidden class="delivers_today_state" type="text" :value=" item.attributes.delivers_today "> // if this equals 0 i want to increment the deliver_later_num value
</ul>
</div>
</div>
</div>
laravel controller code :
public function add(Request $request){
$item = Items::find($request->id);
$restID=$item->category->restorant->id;
//Check if added item is from the same restorant as previus items in cart
$canAdd = false;
if(Cart::getContent()->isEmpty()){
$canAdd = true;
}else{
$canAdd = true;
foreach (Cart::getContent() as $key => $cartItem) {
if($cartItem->attributes->restorant_id."" != $restID.""){
$canAdd = false;
break;
}
}
}
//TODO - check if cart contains, if so, check if restorant is same as pervious one
// Cart::clear();
if($item && $canAdd){
//are there any extras
$cartItemPrice=$item->price;
$cartItemName=$item->name;
$theElement="";
//Is there a varaint
//variantID
if($request->variantID){
//Get the variant
$variant=Variants::findOrFail($request->variantID);
$cartItemPrice=$variant->price;
$cartItemName=$item->name." ".$variant->optionsList;
//$theElement.=$value." -- ".$item->extras()->findOrFail($value)->name." --> ". $cartItemPrice." ->- ";
}
foreach ($request->extras as $key => $value) {
$cartItemName.="\n+ ".$item->extras()->findOrFail($value)->name;
$cartItemPrice+=$item->extras()->findOrFail($value)->price;
$theElement.=$value." -- ".$item->extras()->findOrFail($value)->name." --> ". $cartItemPrice." ->- ";
}
Cart::add((new \DateTime())->getTimestamp(), $cartItemName, $cartItemPrice, $request->quantity, array('id'=>$item->id,'variant'=>$request->variantID, 'extras'=>$request->extras,'restorant_id'=>$restID,'image'=>$item->icon,'friendly_price'=> Money($cartItemPrice, env('CASHIER_CURRENCY','usd'),true)->format(),'delivers_today' => $item->deliverstoday ));
return response()->json([
'status' => true,
'errMsg' => $theElement
]);
}else{
return response()->json([
'status' => false,
'errMsg' => __("You can't add items from other restaurant!")
]);
//], 401);
}
}
public function getContent(){
//Cart::clear();
return response()->json([
'data' => Cart::getContent(),
'total' => Cart::getSubTotal(),
'status' => true,
'errMsg' => ''
]);
}
link to the items array vue dev tools screenshot
[1]: https://i.stack.imgur.com/smLRV.png
thanks for your precious help and time.
A computed property can be used if the deliver_later_num is only dependent on the presence/absence of deliver_today on elements of items array
cartContent = new Vue({
el: '#cartList',
data: {
items: {}
},
computed: {
deliver_later_num() {
let num = 0;
Object.keys(this.items).forEach(key => {
let item = this.items[key];
Object.keys(item).forEach(k => {
if(k === 'deliver_today' && item[k]) {
num++;
}
});
});
return num;
},
}

Laravel-Vue-Pagination error when trying to go to a different page

api.php
Route::get('/products', 'ProductsController#index');
Query:
$products = DB::table('sizes')
->join('products', 'sizes.id', '=', 'products.sizes')
->join('categories', 'products.category', '=', 'categories.id')
->select('products.*', 'categories.catname', 'categories.catimage', 'categories.catdescription', 'sizes.size')
->where([['products.is_active', '=', 1],['categories.is_active', '=', 1],])
->orderBy('products.id', 'ASC')
->paginate(5);
return $products;
Vue component:
<div v-for="product in products.data" :key="product.id">
<h1>{{ product.name }}</h1>
</div>
<pagination :data="products" #pagination-change-page="getResults"></pagination>
methods: {
getResults(page = 1) {
this.$url.get('products/results?page=' + page)
.then(response => {
console.log(response)
this.products = response.data;
});
}
}
The initial load of products works, it shows 5 products and shows pagination. Whenever I try to click a new page from the pagination, I end up with multiple errors.
CORS(which I don't see how since my app is completely public) and two network errors
from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource
net::ERR_FAILED
Uncaught (in promise) Error: Network Error
Is there something I'm missing here? Am I supposed to make another endpoint that handles pagination or should this be handled from the same exact endpoint where it fetches initial pagination?
1. Copy and paste this code in a new component, like 'Pagination.vue'
<template>
<nav aria-label="...">
<ul class="pagination justify-content-center">
<li class="page-item" :class="{ disabled: pagination.current_page <= 1 }">
<a style="cursor:pointer" class="page-link" #click.prevent="changePage(1)" >First page</a>
</li>
<li class="page-item" :class="{ disabled: pagination.current_page <= 1 }">
<a style="cursor:pointer" class="page-link" #click.prevent="changePage(pagination.current_page - 1)"><i class="fa fa-arrow-left"></i></a>
</li>
<li class="page-item" v-for="(page,index) in pages" :key="page" :class="isCurrentPage(page) ? 'active' : ''">
<a style="cursor:pointer" class="page-link" #click.prevent="changePage(page)">{{ page }}
<span v-if="isCurrentPage(page)" class="sr-only">(current)</span>
</a>
</li>
<li class="page-item" :class="{ disabled: pagination.current_page >= pagination.last_page }">
<a style="cursor:pointer" class="page-link" #click.prevent="changePage(pagination.current_page + 1)"><i class="fa fa-arrow-right"></i></a>
</li>
<li class="page-item" :class="{ disabled: pagination.current_page >= pagination.last_page }">
<a style="cursor:pointer" class="page-link" #click.prevent="changePage(pagination.last_page)">Last Page</a>
</li>
</ul>
</nav>
</template>
<script>
export default {
props:['pagination', 'offset'],
methods: {
isCurrentPage(page){
return this.pagination.current_page === page
},
changePage(page) {
if (page > this.pagination.last_page) {
page = this.pagination.last_page;
}
this.pagination.current_page = page;
this.$emit('paginate');
}
},
computed: {
pages() {
let pages = []
let from = this.pagination.current_page - Math.floor(this.offset / 2)
if (from < 1) {
from = 1
}
let to = from + this.offset -1
if (to > this.pagination.last_page) {
to = this.pagination.last_page
}
while (from <= to) {
pages.push(from)
from++
}
return pages
}
}
}
</script>
2. Make it global in your js/app.js file,
Vue.component('pagination', require('./components/Pagination.vue').default);
3. In the vue component, below to the data, set the pagination component like this, you can cange the offset as much you can
<pagination v-if="pagination.last_page > 1"
:pagination="pagination"
:offset="7"
#paginate="getItems()">
</pagination>
4. Set current page to 1,
data(){
return{
items: [],
pagination: {
current_page: 1,
},
}
},
5. Make a method to send the page number and collect paginated data,
getItems(){
axios.get('api/items?page='+this.pagination.current_page)
.then(response => {
this.items = response.data.data;
this.pagination = response.data.meta;
});
},
6. Make sure you return data paginated data with resource collection,
public function index(){
return new GeneralCollection(Item::with('category')->orderBy('name')->paginate(10));
}
***if you, don't have the collection file, make one , like 'GeneralCollection',
php artisan make:resource GeneralCollection
then, include it on the controllers where you want to return collected data,
use App\Http\Resources\GeneralCollection;
7. Congrats !

Display content from AJAX request using setState

I'm doing a webapp using github search API. I want the info for each repo to be displayed under the specific repo.
I want the content from the AJAX request to be displayed when the specific button is being pressed. I am using React. Since I'm using item.x to access the information I need to get that item and I assume I need to use map but when doing that it will display all the results and not just the specific repository's. Anyway that I can get the Item since it currently says it's undefined?
let searchTerm;
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = { repositories: [],
showInfo: false };
}
render() {
let moreDetail;
if(this.state.showInfo){
moreDetail= <div className="info"> <li>
<p>Open issue count </p>:{item.open_issues_count}
</li>
<li>
<p>Number of forks </p>:{item.forks}
</li>
<li>
<p>Language </p>:{item.language}
</li></div>;
}
return(
<div>
<form>
<input type="text" className="searchbox" ref={(input) => { this.searchBox = input; }}/>
<button onClick={this.onClick}>Search</button>
</form>
<h2>Repositories</h2>
<ul>
{ this.state.repositories.map( ( item, index ) => (
<div key={ index }>
<a href={item.html_url}> <li >
{ item.name }
</li>
</a>
{moreDetail}
<button onClick={this._handleClick.bind(this)}>Detailed view</button>
</div>
)) }
</ul>
</div>
);
}
_handleClick(){
this.setState({
showInfo: !this.state.showInfo
});
}
onClick(event) {
searchTerm = this.searchBox.value;
let endpoint = 'https://api.github.com/search/repositories?sort=stars&order=desc&q=' + searchTerm;
console.log(searchTerm);
fetch(endpoint)
.then(blob => blob.json())
.then(response => {
this.setState({ repositories: response.items });
});
event.preventDefault();
}
}
The problem is with context. When you define the moreDetail variable, you don't have item in your context (only during the map function you have that.)
One option is to use the variable moreDetail as a function that receives the item you want to show.
Your render method should look something like:
render() {
const moreDetail = (item) => ( !this.state.showInfo ? <span /> :
<div className="info">
<li>
<p>Open issue count </p>:{item.open_issues_count}
</li>
<li>
<p>Number of forks </p>:{item.forks}
</li>
<li>
<p>Language </p>:{item.language}
</li>
</div>
);
return (
<div>
<form>
<input type="text" className="searchbox" ref={(input) => { this.searchBox = input; }}/>
<button onClick={this.onClick}>Search</button>
</form>
<h2>Repositories</h2>
<ul>
{ this.state.repositories.map( ( item, index ) => (
<div key={ index }>
<a href={item.html_url}> <li >
{ item.name }
</li>
</a>
{moreDetail(item)}
<button onClick={this._handleClick.bind(this)}>Detailed view</button>
</div>
)) }
</ul>
</div>
);
}

Resources