VueJS: Pass data from template element to another - vue.js

In my app I've got some card elements printed dynamically from an array of data. I wanted these cards to open an action sheet on click, that would display an image url from the same array element.
<f7-button class="img-trigger" #click="$refs.actionsOneGroup.open()">
<f7-card v-for="npc in npcs" :key="npc.npcId" class="boss-card-container">
<f7-card-header class="no-border boss-card subheading no-hairline" valign="bottom" :style="'background-image:url(' + npc.npcImg + ');'">
{{ npc.npcName }}
</f7-card-header>
</f7-card>
</f7-button>
<f7-actions class="img-container" ref="actionsOneGroup">
<f7-actions-group v-for="npc in npcs" :key="npc.npcId">
<img :src="npc.npcImg" class="boss-image">
</f7-actions-group>
</f7-actions>
As you can see here, I iterate through the npc array to print the f7-card elements, and on the cards I show a small preview of the npc.npcImg image. What I would like to do is show that same image inside the action sheet. For now I just iterate through the array again separately, which of course results in all the images printing inside the same element, as expected.
I'm not sure how to link the two together and pass the npc.npcImg down to the action sheet component.
Help is much appreciated.

I don't know about the framework7, but in vue.js you have to use this array item as source to display the image. You can pass it as parameter on the refs.actionsOneGroup.open() function and store it in a variable to use it after. In the example I saved the npc in the selectedNpc variable. Your code will looks like it:
<f7-button class="img-trigger">
<f7-card v-for="npc in npcs" :key="npc.npcId" class="boss-card-container" #click="$refs.actionsOneGroup.open(npc)">
<f7-card-header class="no-border boss-card subheading no-hairline" valign="bottom" :style="'background-image:url(' + npc.npcImg + ');'">
{{ npc.npcName }}
</f7-card-header>
</f7-card>
</f7-button>
<f7-actions class="img-container" ref="actionsOneGroup">
<f7-actions-group v-if="selectedNpc">
<img :src="selectedNpc.npcImg" class="boss-image">
</f7-actions-group>
</f7-actions>
I created a codepen to show you how to do it in vue.js

Related

How to change HTML tags of the component dynamically after click in Vue3 composition-api?

I am writing my first app in Vue3 and I use composition-api with script setup.
Using v-for, I create components that are inputs (CrosswordTile) that make up the crossword grid.
A problem appeared during the implementation of the field containing a clue to the password.
Since the text doesn't allow text to wrap, I wanted to dynamically change the tag to after a click.
Function in parent component where I handle logic after click that change tile type works fine, but I need to change tag of "target" to and set maxLength to a different value.
If it would help here is whole code on github: https://github.com/shadowas-py/lang-cross/tree/question-tile, inside CrosswordGrid.vue.
function handleTileTypeChange(target: HTMLInputElement) {
if (target && !target.classList.contains('question-field')) {
addStyle(target, ['question-field']);
iterateCrosswordTiles(getNextTile.value(target), removeStyle, ['selected-to-word-search', 'direction-marking-tile']);
} else if (target) {
removeStyle(target, ['question-field']);
if (getPrevTile.value(target)?.classList.contains('direction-marking-tile')) {
iterateCrosswordTiles(
target,
addStyle,
['selected-to-word-search', 'direction-marking-tile'],
);
}
}
TEMPLATE of ParentComponent
<div
class="csw-grid"
#input="handleKeyboardEvent($event as any)"
#mousedown.left.stop="handleClickEvent($event)"
#click.stop="">
<div v-for="row in 10" :key="row" class="csw-row" :id="`csw-row-${row}`">
<CrosswordTile
v-for="col in 8"
:key="`${col}-${row}`"
#click.right.prevent='handleTileTypeChange($event.target)'
/>
</div>
</div>
I tried to use v-if inside CrosswordTile, but it creates a new element, but I just need to modify the original one (to add/remove HTML classes from it basing on logic inside CrosswordGrid component).
How can I get access to the current component instance properties when using the composition API in script setup or how to replace the tag dynamically?
:is and is doesn't work at all.

how to interpolate a value into an HTML attribute without binding to anything on the Vue instance

The title was kinda long-winded but the question itself is pretty simple.
So I'm looping over some strings and want to make a button for each. On click, the button will call a Vue method. I want to bind the string into the HTML element somehow - it will be more clear with the code:
<li v-for="(name, idx) in $store.state.lobby" :key="idx">
<button data-name="{{name}}" v-on:click='send_game_request'> request game </button>
</li>
so, as you can see, this is very simple. When the send_game_request method gets run, it can read the name off the data-name attribute and do what it needs to with that information.
But, this is invalid syntax, because {{name}} raises an error. I'm really hoping I don't have to wrap each button into it's own sub-component, because that is just extra boilerplate that's not necessary.
I've seen other examples that use v-bind but I don't really have any need to store this information in the component's internal state. I literally just need to know what button was pressed.
You can pass the name as an argument with an inline handler:
<button #click="send_game_request($event, name)">
where $event is the original event data.
In addition to what Tony mentions in his answer,
<li v-for="(name, idx) in $store.state.lobby" :key="idx">
<button :data-name="name" v-on:click='send_game_request'> request game </button>
</li>
You could then extract value of name with datasets like so:
function send_game_request(event){
const name = event.target.dataset.name;
}
NOTE: In this instance you don't need to explicitly pass the $event into your v-on:click function binding, it will already be made available by Vue. So, you can simply invoke your method with the event argument.

Accordion has a default active panel, but it won't open

I am using a frontend library called Element UI to create some accordions on my website. I got an array of objects that I like to create accordions for. FYI, from the element ui website: the v-model used on the accordion specifies the currently active panel. The name attribute is the unique identification of the panel. That means I can do:
<el-collapse v-model="activeName" accordion>
<el-collapse-item title="Consistency" name="1">
<div>content content content</div>
</el-collapse-item>
<el-collapse-item title="Consistency" name="2">
<div>more content more content more content</div>
</el-collapse-item>
</el-collapse>
One this loads, the first accordion will open since in the data object, activeName is set to 1:
data() {
return {
activeName: '1',
}
}
Now I thought I'd just loop through the array and create accordions for the items in my array, and binding the name attribute to the index + 1, so that the first item will have the name attribute equal to 1, the second to 2 etc. So:
<el-collapse v-model="activeName" accordion>
<el-collapse-item
v-for="(item, index) in experience"
:title="item.company"
:name="index + 1"
:key="index"
>
<div> content content content </div>
</el-collapse-item>
</el-collapse>
But for some reason, when the page loads, the first item in the accordion won't open automatically. They're all closed by default. I created a codesandbox with the problem that you can see for yourself here: codesandbox
The problem is that when you run a for loop and assign name, it's a number and not a string.
:name="index+1" <---- This is a number
But, activeName is a string. So, the values don't match and that's why the accordian does not open on page load.
Here's an updated sandbox: https://codesandbox.io/s/vue-template-ysm79
I changed activeName to a number. The for loop accordian will now open and the normal HTML accordians won't.

Is there a way to delay the ngx-bootstrap accordion opening?

<accordion [closeOthers]=true>
<accordion-group *ngFor="let activity of activities" [heading]="activity.Name" (click)="openPanel(activity)" (isOpenChange)="openStatusChange($event)">
<ul *ngFor="let chemical of chemicals">
<li>{{chemical.BrandName}}</li>
</ul>
<div *ngIf="!chemicals?.length > 0">No chemicals associated with this activity type.</div>
</accordion-group>
</accordion>
When the accordian header is clicked, it opens and runs and fires 'openPanel()' which is an http.get, which then populates the panel. If the array returns empty, the *ngIf will display the "no associated stuff" message.
The problem is there is a very slight lag between the time the accordion opens and the array is filled, so the chemicals array is always empty when the accordion opens. This makes it so the "no associated stuff" message appears for about half a second, then the list populates. So I am wondering if there is a way to either delay the opening until the array is populated, or suggestions welcome.
You could use a boolean flag or function for the ngIf. Set the flag when the http promise returns as successful, that way the ngIf only triggers after the http call promise is resolved.
something like:
http.get('url').subscribe(response => {
show = true;
});

What is $emit(\'remove\') in vue js

I want to make some list item from array and want delete them when i click each list item.When i click each item each item get deleted but here $emit(\'remove\') is actually what in vue js plaese help.
<button v-on:click="$emit(\'remove\')">X</button>
seems it connected to emit event which is bind to that element.
<button v-on:click="$emit(\'remove\')">X</button>
is connected to this piece of code in the declaration, you can see this code is just above in your example
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:title="todo"
v-on:remove="todos.splice(index, 1)"
></li>
here you can see:
v-on:remove="todos.splice(index, 1)"
this is the event so when you click on that button this will be fired and that item will be removed from the list.
and make sure this list items are component so it use that template to render each items.
if you have further question please feel free to ask.