Vue: Pass function into parent component - function

Okay, I'm new to Vue and trying to simplify my problem as much as I can do.
I have two components: NewsItem, NewsItemList
NewsItem:
<template>
<div>
<h1>A title</h1>
<a #click="aFunctionDeclaredInThisComponent">Link</a>
</div>
</template>
NewsItemList:
<template>
<NewsItem />
<div>A mystic box</div>
</template>
I want to show/hide the mystic box in NewsItemList, if the function aFunctionDeclaredInThisComponent is activated, which is in NewsItem. I would like to toggle show/hide with a active state in CSS.
My problem is, I don't know how to pass the function into the parent component. Please note, that I use <script setup>.
Thank you!

Not sure what you are trying to do, but I wrote you a minimal example of how to send data from child component to parent component. Check it out here.
The idea is to emit an event from the child and listen to it in the parent. Don't mind the naming I used, just try to understand how I'm interacting with the data in parent component via child one and try to implement it into your code:
Parent:
<template>
<Comp #my-var="callback" />
<p
v-text="`Clicked hide from child? ${test}`"
/>
<div v-show="! test">
I'm the parent div
</div>
</template>
<script setup>
import { ref } from 'vue'
import Comp from './Comp.vue'
const test = ref(false)
const callback = data => test.value = data
</script>
Child:
<template>
<button
v-text="'click to hide parent div'"
#click="doEmit()"
/>
</template>
<script setup>
const emits = defineEmits(['myVar'])
const doEmit = () => emits('myVar', true)
</script>

Related

Vue dynamically add Component

For my Vue project i have a login component and a responsive overlay component.
I built it that way so i could load my responsive overlay component with the needed content using props.
To help you understand the structure, it looks like the following:
Home.vue
<script setup lang="ts">
import ResponsiveOverlay from '../components/responsiveOverlay.vue'
</script>
<template>
<div class="overlayWrapper">
<ResponsiveOverlay v-if='showDisplay' :switchContent='switchOverlayContent'></ResponsiveOverlay>
</div>
</template>
ResponsiveOverlay.vue
<script setup lang="ts">
import LoginOverlay from '../components/loginOverlay.vue'
const props = defineProps({
switchContent: String,
})
</script>
<template>
<div class="responsiveOverlayWrapper">
<LoginOverlay v-if='switchContent == "login"'></LoginOverlay>
</div>
</template>
LoginOverlay.vue
<template>
<h1>Login</h1>
</template>
SwitchOverlayContent is a string variable, that (as of now) is hardcoded to contain "login"
As you can see, i tried using v-if="showDisplay" with the ResponsiveOverlay in a hope i could just toggle the bound bool and it would show, which did not work, probably because the v-if is only ran when loading the page.
So what i would need is a solution where i could click a button which would then set switchOverlayContent dynamically to "login" and then display the ResponsiveOverlayContent.
How would i achieve said behaviour and also, is my strategy of having a component inside of a component viable?

How to use slots inside of template components in HTML

I'm trying to use slots inside of a Vue component to display different titles more easily. However, when I try to replace the slot with data, regardless of the relative positioning in the markup, the slot only uses it's fallback option.
It's my understanding that the template to be used goes first, with a label, then slots are put in and given a "name," with fallback text between the opening and closing slot tags, like so:
<template id="somename-template>
<slot name="attrname>Some Fallback</slot>
</template>
Then data is stored as such:
<somename>
<span slot="attrname">Real text</slot>
</somename>
I have tried repositioning the both above and below the script, and above and below the , however no combination provides the expected results.
My actual code:
<body>
<template id="comp-dem-template">
<header-component></header-component>
</template>
<script>
customElements.define('comp-dem',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('comp-dem-template').content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
});
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
<comp-dem>
<span slot="pagetitle">
Images
</span>
</comp-dem>
</body>
The markup should look like:
<h1>Images</h1>
However, instead looks like:
<h1>Page Title Fallback</h1>
I can tell it's probably a super simple thing that I'm doing wrong (or it's the wrong tool for the job), but even looking at other working examples, I can't tell what that exactly is.
It's not quite clear to me what you're trying to accomplish. You're passing the <span slot="pagetitle">Images</span> to <comp-dem> but the <comp-dem> component doesn't have a slot - it's the <header-component> that has a slot. Why do you need to wrap a component in a component?
For the code to work, the slot needs to be passed like so:
<body>
<template id="comp-dem-template">
<header-component>
<span slot="pagetitle">
Images
</span>
</header-component>
</template>
<script>
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
</body>
Or, if you insist on using <comp-dem>, I think you might need to do the following:
<body>
<template id="comp-dem-template">
<header-component>
<span slot="pagetitle">
<slot name="pagetitle"><slot>
</span>
</header-component>
</template>
<script>
customElements.define('comp-dem',
class extends HTMLElement {
constructor() {
super();
const template = document.getElementById('comp-dem-template').content;
const shadowRoot = this.attachShadow({mode: 'open'}).appendChild(template.cloneNode(true));
}
});
Vue.component('header-component', {
template: '<h1><slot name="pagetitle">Page Title Fallback</slot></h1>'
})
new Vue({ el: '#comp-dem-template' })
</script>
<comp-dem>
<span slot="pagetitle">
Images
</span>
</comp-dem>
</body>

Polymer 1.0: Clickable item in dom-repeat to e.g. iron-page which contains further info (contact list)

I'm setting up a contact list in Polymer 1.0. When the user clicks on a name, there should be a (animated) page opened for further details. All of these data elements are pulled from an external .json file.
Two questions for this approach..:
1) where to begin? How do I wrap, for example, an iron-page or neon-animated-page around my current setup (which is searchable, which is also the -temporary- reason it's a dom-repeat instead of an iron-list):
<template id="resultlist" is="dom-repeat" items="{{data}}" filter="contactFilter">
<paper-item>
<paper-item-body two-line>
<div>{{item.name}}</div>
<div secondary>{{item.number}}</div>
</paper-item-body>
</paper-item>
</template>
2) For quick try-out with binding options I've created an paper-dialog (instead of an page behaviour) which displays further data for the chosen person... On top of that paper-dialog should the chosen name being displayed. But I only get the first name of the array in my .json file. How can I setup the code to display the {{item.name}} of the chosen item?
Ps. I'm aware of the contacts-app from Rob Dodson (https://github.com/robdodson/contacts-app), but I can't figure out how it should be done in Polymer 1.0.
Update 27.10.2015
After Hugo's answer I'm not able to get the solution to work in an dom-module structure.
Sorry for misunderstanding, but I can't figure out where I'm wrong.
Having to following:
phonebook.html, which acts like an index
...
<body unresolved>
<template is="dom-bind" id="application">
<neon-animated-pages selected="[[selected]]" entry-animation="fade-in-animation" exit-animation="fade-out-animation">
<contact-list></contact-list>
<contact-details></contact-details>
</neon-animated-pages>
</template>
<script>
var application = document.querySelector('#application');
application.selected = 0;
document.addEventListener('show-details', function() {
application.selected = 1;
});
document.addEventListener('show-list', function() {
application.selected = 0;
});
</script>
</body>
DOM-module contact-list.html, the list it self.
<dom-module id="contact-list">
<template>
<style include="phonebook-styles"></style>
<iron-ajax url="../data/data.json" handle-as="json" last-response="{{data}}" auto></iron-ajax>
<div class="container">
<h3>Contactlist:</h3>
<div class="template-container">
<template is="dom-repeat" id="templateUsers" items="{{data}}">
<paper-item on-tap="showDetails">
<paper-item-body two-line>
<div>{{item.name}}</div>
<div secondary>{{item.phonenumber}}</div>
</paper-item-body>
<div class="item-details-link">
<iron-icon icon="account-circle"></iron-icon>
</div>
</paper-item>
</template>
</div>
</div>
</template>
<script>
Polymer({
is: 'contact-list',
properties: {
selectedContact:{
type:Object,
value:function(){
return null;
}
}
},
showDetails: function(ev) {
var data = this.$.templateUsers.itemForElement(ev.target);
//alert(JSON.stringify(data)) // works with data chosen data selection...
this.selectedContact = data;
this.fire('show-details', this.selectedContact);
}
});
</script>
</dom-module>
DOM-module contact-details.html, the details-list.
<dom-module id="contact-details">
<template>
<!-- Do I need to declare the .json in my details module? -->
<iron-ajax url="../data/data.json" handle-as="json" last-response="{{data}}" auto></iron-ajax>
<paper-icon-button icon="arrow-back" on-tap="showList"></paper-icon-button>
<h3>Contact details</h3>
<template is="dom-repeat" items="{{data}}">
<div>{{selectedContact.name}}</div>
</template>
</template>
<script>
Polymer({
is: 'contact-details',
showList: function() {
this.fire('show-list');
}
});
</script>
</dom-module>
Everything, like the transitions, work. The chosen contact is also displayed in an alertbox (commented out in contact-list.html), but isn't forwarded to the contact-details.html page.
There are multiple steps to implement the solution:
Setup the neon animated pages ( one page would be the contact list, the other page would be the details )
Display the list of contacts ( you already have this one )
Add a "selectedContact" property to your element
Add a tap/click handler to the list items element and inside the handler set the selectedContact. You need to get the contact item from the DOM element clicked. ( Check an example here : http://jsbin.com/lofarabare/6/edit )
You can bind the contact details page elements to the selectedContact properties, e.g {{selectedContact.name}}
Inside the handler also Change the neon animated pages selected property to have it display the animation to the other page.
-- Extra feedback
I checked the way you handle events, feedback below:
Give the elements some id so you can add the event listener directly to them (e.g application.$.myContactList.addEventListener('show-detail',function(ev){...})
The way you fire the event from the contact-list is correct, however you are not reading the event data inside the event listener for the 'show-detail' event. The event listener receives the event as argument "ev". You can get the event data using ev.detail
With the event data (the selected contact) you can update your contact details component. Give it some id like 'details' and just update the 'selectedContact' property. **You need to declare the selectedContact in the details component, right now you don't have it there **

Calling a polymer element within a polyment with JSON as parameter

I am calling a polymer element within another element. The inner polymer element has a published attribute to which I am binding JSON from the parent polymer. However it is not getting reflected.
<polymer-element name="parent-test" attributes="testData">
<template>
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>
</template>
<script>
Polymer('parent-test', {
testData: [],
ready: function () {
debugger;
this.testData = [1, 2, 3, 4]
}
});
</script>
</polymer-element>
<polymer-element name="child-test" attributes="testDataChild">
<template>
<!--{{testDataChild}}-->
<template repeat="{{test in testDataChild}}">
{{test}}
</template>
</template>
<script>
Polymer('child-test', {
testDataChild: [],
ready: function () {
debugger;
}
});
</script>
</polymer-element>
I am not sure what could be the problem here.
Edit:
Seems like I am not having the actual parentContent at the time of generating the child-polymer-element.
If I assign hardcoded values in ready function for this.parentContent, it doesnt work as well.
If I assign hardcoded values in create function for this parent.Content, it works.
So, I am not not sure if this is something related to generating the child polymer element before the values getting binded to parent.
Thanks,
Sam
I modified your plunk example and get it working without your workaround :
Plunk
<polymer-element name="child-test" attributes="testdatachild">
<template>
<br><br>
In Child el.:
<br>
<template repeat="{{test in testdatachild}}">
{{test}}
<br>
</template>
</template>
<script>
Polymer('child-test', {
ready: function () {
}
});
</script>
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>
<br>
</template>
<script>
Polymer('parent-test', {
created: function () {
this.testData = [1, 2, 3, 4];
}
});
</script>
The main problem seems to be the order of the code
I guess it works better to first declare the child, then the parent, as the child is used in the parent...
Also, as specified in the polymer documentation :
polymer
Important: For properties that are objects or arrays, you should always initialize the properties in the created callback. If you set the default value directly on the prototype (or on the publish object), you may run into unexpected “shared state” across different instances of the same element.
Here is modified example of you code that works : Plunk
Why your example is not working, I don't have all answers buy you are right for one:
<!-- This won't work cause:
"Attributes on child-test were data bound prior to Polymer upgrading the element.
This may result in incorrect binding types." -->
This is Parent test
<child-test testdatachild="{{testData}}"></child-test>

Recommendations for managing multiple instances of the same polymer element in a page?

I have a general question. One of the major benefits of building a new polymer element is that it can be used like a native HTML element in a page. So, depending on the element that you build, it's logical that you would be able to add multiple instances of that element in a page.
Say I build a simple task list polymer element that has multiple views. A simple view that just lists the task names in a list and a detailed view that list the tasks and many other properties of the task in a list.
Then I add the element to my page multiple times. Maybe I want one instance of the element to list tasks related to Home and another to list tasks related to Work. But I want to send a link to someone with the Home task list opened in the simple view and the Work task list opened in detailed view. Or maybe I want the Home task list opened in edit mode and the Work task list opened in view mode.
How would you build the element so that you can change attributes/settings to more then one of these elements on a page?
The beauty of polymer is that you can change your component view by just adding / changing attributes to it.
Create custom tags and provide specific attributes depending on your requirement (HOME / WORK profile), and change your view accordingly.
Example:
Step 1: Create task container
<polymer-element name="task-list" noscript>
<template>
<h3>Tasklist</h3>
<core-menu id="tasks">
<content></content>
</core-menu>
</template>
</polymer-element>
Step2: Create task component
<polymer-element name="add-task" attributes="label detail">
<template>
<div id="task">
<input type="checkbox" id="tick" on-click="{{lineThrough}}" /> {{label}}
<div style="color:#999;margin: 5px 25px;">
{{detail}}
</div>
</div>
</template>
<script>
Polymer('add-task', {
lineThrough: function() {
this.$.task.style.textDecoration = this.$.tick.checked ? 'line-through': 'initial';
}
});
</script>
</polymer-element>
And now using above components, you can create your basic task list:
<task-list>
<add-task label="Learn Polymer" detail="http://www.polymer-project.org/"></add-task>
<add-task label="Build something great" detail="create polymer element"></add-task>
</task-list>
Screenshot
Now, To have control over changing task view (list / detailed / editable). Just add 2 attributes to task-list component. To control child view add-task from parent task-list element, you need to publish properties of your child element.
Your child component should be:
<polymer-element name="add-task" attributes="label detail">
<template>
<div id="task">
<template if="{{isEditable}}">
<input value="{{label}}" />
</template>
<template if="{{!isEditable}}">
<input type="checkbox" id="tick" on-click="{{lineThrough}}" /> {{label}}
</template>
<template if="{{isDetailed}}">
<div style="color:#999;margin: 5px 25px;">
{{detail}}
</div>
</template>
</div>
</template>
<script>
Polymer('add-task', {
publish: {
isDetailed: false,
isEditable: false
},
lineThrough: function() {
this.$.task.style.textDecoration = this.$.tick.checked ? 'line-through': 'initial';
}
});
</script>
</polymer-element>
Parent component with required attributes
<polymer-element name="task-list" attributes="editable detailed">
<template>
<h3>Tasklist</h3>
<core-menu flex id="tasks">
<content></content>
</core-menu>
</template>
<script>
Polymer('task-list', {
editable: false,
detailed: false,
domReady: function() {
var items = this.$.tasks.items;
for(var i = 0; i < items.length; i++) {
items[i].isDetailed = this.detailed;
items[i].isEditable = this.editable;
}
}
});
</script>
</polymer-element>
That's it, now you can control your task view by specifying required attributes to your parent component.
<task-list detailed editable>
<add-task label="Learn Polymer" detail="http://www.polymer-project.org/"></add-task>
<add-task label="Build something great" detail="create polymer element"></add-task>
</task-list>
Screenshots
With detailed and editable attributes
Without detailed and editable attributes