Nanna
Nanna

Reputation: 575

Vue single file components: Printing component element from child component

I'm using @vue/vli 4.1.1 with single file components. A simplified structure of my application looks like this:

src
├── App.vue
├── assets
├── components
│   ├── PrintRecipe.vue
│   └── Recipe.vue
└── main.js

Inside the Recipe component, I would like to include a print button and its functionality, as implemented in the PrintRecipe component. When the button is pressed, the #recipe element from the Recipe component should be printed.

Here is a dummy version of Recipe.vue:

<template>
    <div>
        <div id="other-content">
            ...
        </div> 
        <div id="recipe">
            ...
        </div> 
        <PrintRecipe/>
    </div>
</template>

<script>
import PrintRecipe from './PrintRecipe.vue'

export default {
  components: {
    PrintRecipe
  },
  ...
}
</script>

and this is a dummy version of PrintRecipe.vue:

<template>
    <div>
        <button @click="printRecipe">Print recipe</button>
    </div>
</template>

<script>
import { Printd } from 'printd';

export default {
  mounted() {      
    this.d = new Printd()
  },
  methods: {
    printRecipe () {
      this.d.print( this.$el, [this.cssText])
    }
  }
}
</script>

Currently this opens a print view, where the document to be printed only contains "Print recipe".

Is there a way that I can access the elements from the template of Recipe.vue in order to be able to print them?

I have done some research and so far I only see information on how to access data from the parent component, not elements.

Upvotes: 1

Views: 2517

Answers (3)

Estus Flask
Estus Flask

Reputation: 222910

Either Recipe parent component can handle the interaction, PrintRecipe becomes presentation component:

Recipe.vue

...
<div ref="recipe">...</div> 
<PrintRecipe v-on:print="printRecipe" ref="printRecipe" />
...

...
methods: {
    printRecipe() {
      this.d.print( this.$refs.recipe.$el, [this.cssText])
    }
}
...

PrintRecipe.vue

...
methods: {
    printRecipe() {
      this.$emit('print');
    }
}
...

Or PrintRecipe child get receive necessary data from Recipe parent to handle the interaction, such as a reference to DOM element:

...
<div ref="recipe">...</div> 
<PrintRecipe />
...

PrintRecipe.vue

...
methods: {
    printRecipe() {
      this.d.print(this.$parent.$refs.recipe.$el, [this.cssText])
    }
}
...

Whether components have to communicate through $parent, props or event bus depends on the case.

Upvotes: 2

Rijosh
Rijosh

Reputation: 1544

Send a custom Vue event on clicking the button back to the Recipe.vue component

export default {
  mounted() {      
    this.d = new Printd()
  },
  methods: {
    printRecipe () {
      this.$emit('recipeClicked'); // custom event
    }
  }
}

From the Recipe.vue you can receive the event and write your logic to show the content. Here is my logic;

<template>
    <div>
        <div id="other-content">
            ...
        </div> 
        <div id="recipe" v-show="showContent">
            ...
        </div> 
        <PrintRecipe @recipeClicked="showContent=true"/>
    </div>
</template>

<script>
import PrintRecipe from './PrintRecipe.vue'

export default {
  components: {
    PrintRecipe
  },
  data:()=>{
    return {
      showContent:false
    }
  }
  ...
}
</script>

Upvotes: 1

Nanna
Nanna

Reputation: 575

As it turns out, Vue slots was what I needed.

The PrintRecipe component should have a slot, to where the element that should be printed is inserted.

PrintRecipe.vue

<template>
    <div>
        <slot/>
        <button @click="printRecipe">Print recipe</button>
    </div>
</template>

<script>
import { Printd } from 'printd';

export default {
  mounted() {      
    this.d = new Printd()
  },
  methods: {
    printRecipe () {
      this.d.print( this.$el, [this.cssText])
    }
  }
}
</script>

The insertion is done inside Recipe.vue by expanding the PrintRecipe element in the following way:

<template>
    <div>
        <div id="other-content">
            ...
        </div> 
        <PrintRecipe>
            <div id="recipe">
                ...
            </div> 
        </PrintRecipe>
    </div>
</template>

<script>
import PrintRecipe from './PrintRecipe.vue'

export default {
  components: {
    PrintRecipe
  },
  ...
}
</script>

Upvotes: 2

Related Questions