Polymer 1.0 dom-repeat is not notified properly when array changes - javascript

I have a <paper-dialog> modal which acts like a form to invite new candidates to take a challenge. In the <invite-candidates> component there is a dom-repeat which renders a complex input(<invite-candidate-input>) for each object in the candidates list.
Every time i open(toggle) that modal, i clear the list and add a new object to be rendered.The first time i open the page and open the modal, i see an input, as expected. But if i close the modal and then open it again, it is cleared, a new object is added to the list, but the dom-repeat isn't notified. When i click the button to add another input, it's added to the list and rendered. But my list has 2 objects now, and only 1 <invite-candidate-input>.
<dom-module id="candidate-page">
<template>
<button id="createActionButton" on-click="_toggleDialog"></button>
<invite-candidates id="inviteCandidates"></invite-candidates>
</template>
<script>
Polymer({
is: 'candidate-page',
properties: {
Address: String,
challenges: {
type: Array,
notify: true
}
},
_toggleDialog: function () {
this.$.inviteCandidates.toggle();
}
});
</script>
<dom-module id="invite-candidates">
<template>
<paper-dialog id="dialog" modal style="background: white;">
<form is="iron-form" id="form">
<template is="dom-repeat" items="{{candidates}}">
<invite-candidate-input candidate="{{item}}"></invite-candidate-input>
</template>
<div id="addCandidate" on-click="_addCandidate">
<iron-icon icon="add"></iron-icon>
<span>Add another candidate</span>
</div>
<div class="buttonGroup">
<paper-button class="primary" raised on-click="inviteCandidates" dialog-confirm>Invite
</paper-button>
</div>
</form>
</paper-dialog>
</template>
<script>
Polymer({
is: 'invite-candidates',
properties: {
candidates: {
type: Array,
notify: true,
value: []
}
},
listeners: {
'candidate-removed': '_removeCandidate'
},
toggle: function () {
this._clearDialog();
this._addCandidate();
this.$.dialog.toggle();
},
_addCandidate: function () {
this.push('candidates', {});
},
_removeCandidate: function(event){
var index = this.candidates.indexOf(event.detail);
this.splice('candidates', index, 1);
},
_clearDialog: function () {
var el;
while ((el = document.querySelector('invite-candidate-input')) !== null) {
el.remove();
}
this.candidates = [];
}
});
</script>
<dom-module id="invite-candidate-input">
<template>
<div id="inputContainer">
<paper-input id="address" label="Address" value="{{candidate.Address}}"></paper-input>
<iron-icon icon="remove" on-click="_removeItem"></iron-icon>
</div>
<br>
</template>
<script>
Polymer({
is: 'invite-candidate-input',
properties: {
challenges: {
type: Array
},
candidate: {
type: Object,
reflectToAttribute: true,
notify: true
}
}
_removeItem: function () {
this.fire('candidate-removed', this.candidate);
this.remove();
}
});
</script>

It is because in _clearDialog you do this.candiates = [];. If you do this.splice('candidates', this.candidates.length) it should work.

Related

how to uncheck answered question in child vue from parent

My application is similar to a quiz. each question may have radio button answers, integers, text etc.
Consider the case for multiple choice question. I have a vue for the Radio button options. in the parent Vue i have a reset button for each of the question. If I click on the reset, it should removed the selected answer for that particular question.
How can I achieve this given that the reset button is in Parent vue and the answer to be reset is in the child Vue?
Parent:
<template>
<div class="inputContent">
<p class="lead" v-if="title">
{{title}}
<span v-if="valueConstraints.requiredValue" class="text-danger">* .
</span>
</p>
<b-alert variant="danger" show v-else>
This item does not have a title defined
</b-alert>
<!-- If type is radio -->
<div v-if="inputType==='radio'">
<Radio :constraints="valueConstraints" :init="init"
:index="index" v-on:valueChanged="sendData" />
</div>
<!-- If type is text -->
<div v-else-if="inputType==='text'">
<TextInput :constraints="valueConstraints" :init="init" v-
on:valueChanged="sendData"/>
</div>
<div class="row float-right">
<b-button class="" variant="default" type=reset #click="reset">
Reset1
</b-button>
<b-button class="" variant="default" v-
if="!valueConstraints.requiredValue" #click="skip">
Skip
</b-button>
</div>
</div>
</template>
<style></style>
<script>
import { bus } from '../main';
import Radio from './Inputs/Radio';
import TextInput from './Inputs/TextInput';
export default {
name: 'InputSelector',
props: ['inputType', 'title', 'index', 'valueConstraints',
'init'],
components: {
Radio,
TextInput,
},
data() {
return {
};
},
methods: {
skip() {
this.$emit('skip');
},
// this emits an event on the bus with optional 'data' param
reset() {
bus.$emit('resetChild', this.index);
this.$emit('dontKnow');
},
sendData(val) {
this.$emit('valueChanged', val);
this.$emit('next');
},
},
};
</script>
the child vue:
<template>
<div class="radioInput container ml-3 pl-3">
<div v-if="constraints.multipleChoice">
<b-alert show variant="warning">
Multiple Choice radio buttons are not implemented yet!
</b-alert>
</div>
<div v-else>
<b-form-group label="">
<b-form-radio-group v-model="selected"
:options="options"
v-bind:name="'q' + index"
stacked
class="text-left"
#change="sendData"
>
</b-form-radio-group>
</b-form-group>
</div>
</div>
</template>
<style scoped>
</style>
<script>
import _ from 'lodash';
import { bus } from '../../main';
export default {
name: 'radioInput',
props: ['constraints', 'init', 'index'],
data() {
return {
selected: null,
};
},
computed: {
options() {
return _.map(this.constraints['itemListElement'][0]['#list'], (v) => {
const activeValueChoices = _.filter(v['name'], ac => ac['#language'] === "en");
return {
text: activeValueChoices[0]['#value'],
value: v['value'][0]['#value'],
};
});
},
},
watch: {
init: {
handler() {
if (this.init) {
this.selected = this.init.value;
} else {
this.selected = false;
}
},
deep: true,
},
},
mounted() {
if (this.init) {
this.selected = this.init.value;
}
bus.$on('resetChild', this.resetChildMethod);
},
methods: {
sendData(val) {
this.$emit('valueChanged', val);
},
resetChildMethod(selectedIndex) {
this.selected = false;
},
},
};
</script>
One way would be to use an event bus
in your main js add:
//set up bus for communication
export const bus = new Vue();
in your parent vue:
import {bus} from 'pathto/main.js';
// in your 'reset()' method add:
// this emits an event on the bus with optional 'data' param
bus.$emit('resetChild', data);
in your child vue
import {bus} from 'path/to/main';
// listen for the event on the bus and run your method
mounted(){
bus.$on('resetChild', this.resetChildMethod());
},
methods: {
resetChildMethod(){
//put your reset logic here
}
}

onclick event not firing in vue.js

I have the following Vue.js template :
<script type="text/x-template" id="ti-page-inquire">
<div>
<h3 class="mdc-typography--headline3">{{page.name}}</h3>
<ti-button v-bind:button="page.button" v-on:click="onSubmit"></ti-button>
</div>
</script>
<script type="text/x-template" id="ti-button">
<button class="mdc-button mdc-button--raised" v-bind:title="button.name">{{button.name}}</button>
</script>
script
Vue.component('ti-page-inquire', {
props: ['page'],
template: '#ti-page-inquire',
methods : {
onSubmit : function() {
alert(1);
}
}
});
Vue.component('ti-button', {
props: ['button'],
template: '#ti-button',
mounted: function () {
// ripple on button
mdc.ripple.MDCRipple.attachTo(this.$el);
}
});
when I click on my custom button, nothing happens. I think it's because its looking for onSubmit in the ti-button component, but how do I get it to look in the ti-page-inquire component?
Components are black boxes you should catch all events inside it and emit them to the outer world.
Fiddle example
Vue.component('ti-button', {
props: ['button'],
template: '#ti-button',
mounted: function () {
// ripple on button
mdc.ripple.MDCRipple.attachTo(this.$el);
},
methods: {
buttonClicked: function() {
this.$emit('button-clicked');
}
}
});
<script type="text/x-template" id="ti-page-inquire">
<div>
<h3 class="mdc-typography--headline3">{{page.name}}</h3>
<ti-button v-bind:button="page.button" v-on:button-clicked="onSubmit"></ti-button>
</div>
</script>
<script type="text/x-template" id="ti-button">
<button class="mdc-button mdc-button--raised" v-bind:title="button.name" #clicked="buttonClicked">{{button.name}}</button>
</script>
This might be because you need to listen for a native click event. So you need to use the .native modifier ..
<ti-button v-bind:button="page.button" v-on:click.native="onSubmit"></ti-button>
This will only work if the button is the root element of your ti-button component. Otherwise you'll have to pass your event listeners to your button in the ti-button component like this ..
<button v-on="$listeners" ...> ... </button>
Try to emit an event from ti-button component to the parent one by using this.$emit function :
Vue.component('ti-button', {
props: ['name'],
template: '#vButton',
data: {
name: 'hi'
},
methods: {
submit() {
this.$emit('submit')
}
}
});
<template id="vButton">
<button v-bind:title="name" #click="submit">{{name}}</button>
</template>
the emitted event submit it called in the parent component like v-on:submit="onSubmit" and handled using onSubmit method:
<script type="text/x-template" id="ti-page-inquire">
<div>
<h3 class="mdc-typography--headline3">{{page.name}}</h3>
<ti-button v-bind:button="page.button" v-on:submit="onSubmit"></ti-button>
</div>
</script>
Vue.component('ti-page-inquire', {
props: ['page'],
template: '#ti-page-inquire',
methods : {
onSubmit : function() {
alert(1);
}
}
});
Sometimes you need also to emit some parameters, so you could do it like :
this.$emit('submit',params)
params could be of any type

Polymer 2.0 iron-localstorage two arrays

I'm trying to make a shopping cart in polymer And I do not have much knowledge
How do I insert a selected data in template dom-repeat to an array binding to iron localsotage e.model.item it does not work.
<dom-module id="shop-cart">
<template>
<iron-ajax url="list.json" last-response="{{ListProducts}}" auto>
</iron-ajax>
<template is="dom-repeat" items="{{ListProducts}}">
<p style="display:block;width:400px">
<span>{{item.code}}</span>
<span>{{item.title}}</span>
<paper-button raised class="indigo" on-
click="addProduct">Add</paper-button>
<br/>
</p>
</template>
<iron-localstorage name="my-app-storage"
value="{{Orders}}"
on-iron-localstorage-load-empty="initializeDefaultOrders"
></iron-localstorage>
<template is="dom-repeat" items="Orders" as="order">
<div>
<p>{{order.code}}</p>
<p>{{order.title}}</p>
</div>
</template>
</template>
<script>
class ShopCart extends Polymer.Element {
static get is() {
return 'shop-cart';
}
static get properties() {
return {
Product: {
type: String
},
Orders: {
type: Array,
value() {
return [
{
code:'',
title:'',
}
];
},
},
ListProducts: {
type: Array,
value() {
return [];
},
}
}
}
initializeDefaultOrders() {
this.Orders = {
code:'',
title:''
}
};
addProduct(e) {
this.Product= e.model.item.title;
this.push('Orders',this.Product);
this.set('Product','');
}
deleteProduct(e) {
this.splice('Orders', e.model.index, 1);
}
}
window.customElements.define(ShopCart.is, ShopCart);
</script>
</dom-module>
<shop-cart></shop-cart>
The value passed to your method, addProduct(e), has nothing to do with the data model of the item of ListProducts.
Here is an example of shopping cart that binds the selection (a checkbox being checked) to a property of the item, item.selected.
https://github.com/renfeng/android-repository/blob/master/elements/android-sdk-manager.html#L267-L297
If checkboxes are not desirable, you can add a custom attribute to your button. e.g. selected
The following works only for Polymer 1.
<paper-button raised class="indigo" on-click="addProduct" selected="[[item.title]]">Add</paper-button>
And, have the following line to retrieve the title of the item selected.
this.Product= e.target.getAttribute("selected");
For Polymer 2, here is your fix.
https://github.com/renfeng/stackoverflow-question-44534326/commit/b2a4226bd5a1f5da7fa2d5a8819c53c65df7c412
Custom attribute has been proposed for Polymer 2, but not seem to be accepted for this moment. See https://github.com/Polymer/polymer/issues/4457

Property or method "value" is not defined on the instance in vue js 2 nested component

I wrote a basic vue js 2 basic example to test nested components.
Below is components and template structure.
Vue.component('form-com', {
template: '#form',
props: ['value'],
methods: {
onInput: function (event) {
this.$emit('input', event.target.value);
}
}
});
Vue.component('message-com', {
template: '#message',
data: function () {
return {
msg: 'Hello'
}
},
props: ['user']
});
Vue.component('welcome-com', {
template: '#welcome',
data: function () {
return {
user: 'ahmad'
}
}
});
new Vue({
el: '#container'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<!--Form Template-->
<template id="form">
<div>
<div class="form-control">
<label>Enter Your Name:</label>
<input type="text" v-bind:value="value" :input="onInput">
</div>
</div>
</template>
<!--Hello Message Template-->
<template id="message">
<div>
<h3>{{msg}} {{user}}</h3>
</div>
</template>
<template id="welcome">
<div>
<form-com :value="value"></form-com>
<br>
<message-com :user="user"></message-com>
</div>
</template>
<div id="container">
<welcome-com></welcome-com>
</div>
But when run app in browser this error is shown:
[Vue warn]: Property or method "value" is not defined on the instance but referenced during render. Make sure to declare reactive data properties in the data option.
found in
---> <WelcomeCom>
<Root>
what is problem?
Update:
I just Rewrite this Fiddle from one of chapters of Learning Vue.js 2. I just rename some parameters and component and templates names. but when I copy main fiddle to my code all things worked.
In your form-com component you can set up a v-model which binds the input value and set up a watcher to observer the changes in the input which in turn emits an custom-event which telss the parent comonent that a change has taken place.
Vue.component('form-com', {
template: '#form',
data(){
return{
myInput:''
}
},
watch: {
myInput: function (inputVal) {
this.$emit('input', inputVal);
}
}
});
Vue.component('message-com', {
template: '#message',
data: function () {
return {
msg: 'Hello'
}
},
props: ['user']
});
Vue.component('welcome-com', {
template: '#welcome',
data: function () {
return {
user: 'ahmad'
}
},
methods:{
updateUser(value){
this.user = value;
}
}
});
new Vue({
el: '#container'
})
You can listen to the events emitted from the child **form-com ** component using v-on:input or shorthand #input directly in the parent template (welcome component) where the child component is used.
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.4/vue.js"></script>
<!--Form Template-->
<template id="form">
<div>
<div class="form-control">
<label>Enter Your Name:</label>
<input type="text" v-model="myInput">
</div>
</div>
</template>
<!--Hello Message Template-->
<template id="message">
<div>
<h3>{{msg}} {{user}}</h3>
</div>
</template>
<template id="welcome">
<div>
<form-com #input="updateUser($event)" ></form-com>
<br>
<message-com :user="user"></message-com>
</div>
</template>
<div id="container">
<welcome-com></welcome-com>
</div>
Here is the jsFiddle
If you don't want to use a watcher then you can do it using computed setter . Have a look at the fiddle which is using a computed-setter
You are missing in your Component 'welcome-com' the value object:
Vue.component('welcome-com', {
template: '#welcome',
data: function () {
return {
value: '',
user: 'ahmad'
}
}
});

Data binding between sibling custom elements

There are two child custom elements inside parent custom element. I don't know if it's even possible, but what I am trying to achieve is when "value" changes, and "prop" changes as a result of binding, I need "feat1" to change accordingly equating to the value of "prop".
parent-element:
<dom-module id="parent-element">
<template>
<first-child prop={{value}}></first-child>
<second-child feat1={{prop}}></second-child>
In parent-element
<h1>{{value}}</h1>
</template>
<script>
Polymer({
is: "parent-element",
properties: {
value: {
type: String
}
},
valueChanged:function(){
console.log("value changed");
}
});
</script>
</dom-module>
first-child:
<dom-module id="first-child">
<template>
<p>first element.</p>
<h2>{{prop}}</h2>
</template>
<script>
Polymer({
is: "first-child",
properties:{
prop:{
type:String,
notify:true
}
},
ready:function(){
this.prop = "property"; //this is just example, in reality it gets data from elsewhere
}
});
</script>
</dom-module>
second-child:
<dom-module id="second-child">
<template>
<p>Second element.</p>
<h2>{{feat1}}</h2>
</template>
<script>
Polymer({
is: "second-child",
properties:{
feat1:{
type:String,
notify:true,
value:"initial value"
}
},
ready:function(){
this.addEventListener("feat1-changed",this.myAct);
},
myAct:function(){
console.log("feat1-changed ",this.feat1);
}
});
</script>
</dom-module>
Here is the plunk
Sure you can. Some issues with your code:
From the parent element, bind prop in <first-child> and feat1 in <second-child> to the same value.
Use observer defined in the properties object to listen for changes.
Set up <first-child> to be a one-way bind from child to parent.
Set up <second-child> to be a one-way bind from parent to child.
Putting it all together:
<parent-element>:
<dom-module id="parent-element">
<template>
<first-child prop={{value}}></first-child>
<second-child feat1=[[value]]></second-child>
<div>parent-element: <b><span>[[value]]</span></b></div>
<div id="log"></div>
</template>
<script>
Polymer({
is: "parent-element",
properties: {
value: {
type: String,
observer: "valueChanged"
}
},
valueChanged: function (n, o) {
var el = document.createElement("div");
el.innerHTML = "value changed in parent from "+o+" to "+n;
Polymer.dom(this.$.log).appendChild(el);
}
});
</script>
</dom-module>
<first-child>:
<template>
<div>first-child: <b><span>{{prop}}</span></b>
<button on-tap="btnTapped">change 'prop' inside first-child</button>
</div>
</template>
<script>
Polymer({
is: "first-child",
properties:{
prop:{
type:String,
notify:true,
value: "first_child_default"
}
},
btnTapped: function () {
this.prop = Math.random().toString(36).substring(7);
}
});
</script>
</dom-module>
<second-child>:
<dom-module id="second-child">
<template>
<div>second-child: <b><span>{{feat1}}</span></b></div>
<div id="log"></div>
</template>
<script>
Polymer({
is: "second-child",
properties:{
feat1:{
type:String,
value:"second_child_default", // you should never see this since default value is passed in from parent
observer: "feat1Changed"
}
},
feat1Changed: function(n, o) {
var el = document.createElement("div");
el.innerHTML = "feat1 changed in second-child from "+o+" to "+n;
Polymer.dom(this.$.log).appendChild(el);
}
});
</script>
</dom-module>
Plunker: http://plnkr.co/edit/pONoTxdCDn6jj5axoap1?p=preview
Let me know if this is the correct behaviour you are trying to achieve.

Categories

Resources