Toggling button issue in polymer - javascript

Im trying to toggle the displaying of message using a button.
Below is my code.
class DisplayMessage extends PolymerElement {
// DO YOUR CHANGES HERE
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
<button on-click="toggle">Toggle Message</button> <br />
<template is="dom-if" if="{{user.authorise }}">
<br />
<span>I should now display message.</span>
</template>
`;
}
toggle() {
// DO YOUR CHANGES HERE
// hint: use set method to do the required changes
//console.log(this.user);
//this.user = !this.user
}
static get properties() {
return {
prop1: {
type: String,
value: 'user',
},
user: {
type: Object,
value: function () {
return { authorise: false}; // HINT: USE BOOLEAN VALUES TO HIDE THE MESSAGE BY DEFAULT
},
notify: true,
},
};
}
}
window.customElements.define('display-message', DisplayMessage);
I tried thinking for like hours, but couldn't solve. The requirement her is on clicking the button, the click handler toggle should change the value of authorize in user property to true. And on clicking again to false and so on. I need to use set method within toggle method. I'm not getting how to do this. Please help me on this.
Thanks in advance.

Why use a library/dependency for such a small component, that can be done with native code
<display-message id=Message name=Cr5>You are authorized</display-message>
<script>
customElements.define("display-message", class extends HTMLElement {
static get observedAttributes() {
return ["name", "authorized"]
}
connectedCallback() {
this.innerHTML = `<h2>Hello <span>${this.getAttribute("name")}</span></h2><button>Toggle message</button><br><div style=display:none>${this.innerHTML}</div>`;
this.querySelector('button').onclick = () => this._toggle();
}
_toggle(state) {
let style = this.querySelector('div').style;
style.display = state || style.display == "none" ? "inherit" : "none";
this.toggleAttribute("authorized", state);
console.log(Message.name, Message.authorized);
}
get name() { return this.getAttribute("name") }
set name(value) {
this.querySelector('span').innerHTML = value;
this.setAttribute("name", value);
}
get authorized() { return this.hasAttribute("authorized") }
set authorized(value) { this._toggle(value) }
attributeChangedCallback(name, oldValue, newValue) {
if (oldValue) this[name] = newValue;
}
})
Message.name = "Cr5";
Message.authorized = true;
</script>

class DisplayMessage extends PolymerElement {
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
<button on-click="toggle">Toggle Message</button> <br />
<template is="dom-if" if="{{user.authorise}}">
<br />
<span>I should now display message.</span>
</template>
`;
}
toggle() {
if(this.user.authorise==false)
{
this.set('user.authorise', true);
}
else
{
this.set('user.authorise', false);
}
}
static get properties() {
return {
prop1: {
type: String,
value: 'user',
},
user: {
type: Object,
value: function () {
return { authorise: false };
},
},
};
}
}
window.customElements.define('display-message', DisplayMessage);

Related

text input (input type="text") value is not updating after changing property using an event with LitElement library

The source code:
import { LitElement, html, css } from '../vendor/lit-2.4.0/lit-all.min.js';
export class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array }
}
};
static styles = css`
`;
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' }
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<input class="input" type="text" value="${this.text}">
<ul class="result-list">
${this.items.map((item) => html`<li #click="${this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
The text input (input type="text") value is not updating after changing property (this.text) using an event (this.selectItem) with LitElement library.
I tried it in browser but there is no error in browser console.
I expect that input value update after changing property with the event.
Thanks for the question! There are a few minor issues resulting in the value not updating.
One issue is that this.text is not a reactive property, so changing it isn't scheduling a re-render. Fix is to add text to the static properties.
The second issue is that your event listener click handler is the result of calling this.selectItems(item) and not a function, fixed with: #click=${() => this.selectItems(item)}.
Bonus: You may want to change the value attribute expression to a property expression using the live directive, .value="${live(this.text)}". I suggested this because the native input browser element always updates its contents if you update the value property, but only updates before a user has interacted with it when updating the value attribute. And the live directive is useful to tell Lit to dirty check the live DOM value in the input element.
Your code with the minor fixes: https://lit.dev/playground/#gist=a23dfbcdfbfcfb7de28b1f7255aaa8ee
or running in StackOverflow:
<script type="module">
import { LitElement, html, live } from 'https://cdn.jsdelivr.net/gh/lit/dist#2/all/lit-all.min.js';
class SearchInput extends LitElement {
static get properties() {
return {
src: { type: String },
items: { type: Array },
text: { type: String }, // <- Added this to make `this.text` a reactive property.
}
};
constructor() {
super();
this.items = [
{ text: 'Hola' },
{ text: 'mundo!' },
{ text: 'click these' },
];
this.selectedItem = null;
this.text = 'foo';
}
selectItem(item) {
this.selectedItem = item;
this.text = this.selectedItem.text;
}
render() {
return html`
<div class="control">
<!-- live directive is needed because user can edit the value of the input.
This tells Lit to dirty check against the live DOM value. -->
<input class="input" type="text" .value="${live(this.text)}">
<ul class="result-list">
<!-- Click event is a function -->
${this.items.map((item) =>
html`<li #click="${() => this.selectItem(item)}">${item.text}</li>`)}
</ul>
</div>
`;
}
}
customElements.define('search-input', SearchInput);
</script>
<search-input></search-input>

Value not emitting from child to parent component using custom events

I have a problem with two web components created using lit-element v3.2.0 and I'm using custom events to emit the input value from the child component up to the parent component.
The form-input component is a reusable input that extracts the input value and emits it to the parent component with a custom event named "new-value" which is dispatched when the user writes in the field.
The form-container component contains the form-input, in here I'm binding the custom event "new-value" to a method called "updateInputValue" which should reassign the inputValue property with the emitted value from the child component, but instead is stuck with whatever value initialized in the parent constructor.
form-container.js
static get properties() {
return {
inputValue: { type: String },
items: { type: Array },
}
}
constructor() {
super()
this.inputValue = ''
this.items = []
}
render() {
return html`<form>
<h1>My form container</h1>
<form-input
#new-value=${this.updateInputValue}
fieldName="name"
id="name"
label="Name"
placeholder="Enter anything"
value="${this.inputValue}"
></form-input>
<button #click=${this.addNewItem} type="submit">Add</button>
<form-list .items="${this.items}"></form-list>
</form>`
}
updateInputValue(e) {
// Update input value with the value emitted from the form-input
this.inputValue = e.detail
}
addNewItem(e) {
// Add new item to the list
e.preventDefault()
console.log('add new item with the following value:', this.inputValue)
form-input.js
static get properties() {
return {
value: { type: String },
fieldName: { type: String },
label: { type: String },
placeholder: { type: String },
type: { type: String },
}
}
constructor() {
super()
}
render() {
return html`
<div>
<label for="name">${this.label}</label>
<input
#input=${this.dispatchEvent}
id="${this.fieldName}"
name="${this.fieldName}"
placeholder="${this.placeholder}"
type="${this.type || 'text'}"
value="${this.value}"
/>
</div>
`
}
dispatchEvent(e) {
// Emit the new value from the input to the parent component
const target = e.target
if (target) {
this.dispatchEvent(
new CustomEvent('new-value', {
detail: target.value,
})
)
}
}
Any help will be very much appreciated.
You are overwriting the dispatchEvent method and calling yourself. Rename the dispatchEvent Method and give it a meaningful name. It works perfectly.
<script type="module">
import {
LitElement,
html, css
} from "https://unpkg.com/lit-element/lit-element.js?module";
class FormInput extends LitElement {
static get properties() {
return {
value: { type: String },
fieldName: { type: String },
label: { type: String },
placeholder: { type: String },
type: { type: String },
};
}
render() {
return html`
<div>
<label for="name">${this.label}</label>
<input
#input=${this.changedInput}
id="${this.fieldName}"
name="${this.fieldName}"
placeholder="${this.placeholder}"
type="${this.type || 'text'}"
value="${this.value}"
/>
</div>
`
}
changedInput(e) {
// Emit the new value from the input to the parent component
console.log(e.target.value);
const myevent = new CustomEvent('my-event', {
bubbles: true,
composed: true,
detail: {
value: e.target.value
}
})
this.dispatchEvent(
myevent
);
}
}
class FormContainer extends LitElement {
static get properties() {
return {
name: {
inputValue: { type: String },
}
};
}
updateInputValue(e) {
console.log('received ' + e.detail.value);
this.inputValue = e.detail.value;
}
addNewItem(e) {
// Add new item to the list
e.preventDefault()
console.log('add new item with the following value:', this.inputValue);
}
render() {
return html`<form>
<h1>My form container</h1>
<form-input
#my-event="${this.updateInputValue}"
fieldName="name"
id="name"
label="Name"
placeholder="Enter anything"
value="${this.inputValue}"
></form-input>
<button #click=${this.addNewItem} type="submit">Add</button>
<form-list .items="${this.items}"></form-list>
</form>
`;
}
}
customElements.define("form-container", FormContainer);
customElements.define("form-input", FormInput);
</script>
<form-container></form-container>

Vue Js Component issue- manipulating data

I've been devloping a project for a class and run into a problem where the components arent communicating well together as intended. The goal is that if there is one false in any component that it sends the message dont go to the location.
This is some of the code I currently have:
<template>
<div class="component">
<h3>Temperature Sensor</h3>
<input v-model = 'temperature' #keyup.enter = "checkTemp"> </input>
<p> Action to take : {{ action }} </p>
</div>
</template>
<script>
import { eventBus } from '../main.js';
export default {
props :{
action : String
},
computed () {
return {
//defines a movement value that we will use.
movement : true,
};
},
mounted(){
eventBus.$on('move',(movement) =>{
this.movement = movement;
if(movement == true){
this.action = "Go to Location";
}else{
this.action = "Dont go There";
}
})
},
methods : {
checkTemp(){
if(this.temperature >=50){
this.movement = false;
console.log(this.movement);
eventBus.$emit('move', this.movement);
}else {
this.movement = true;
console.log(this.movement);
eventBus.$emit('move', this.movement);
}
}
},
};
</script>
<style scoped>
div {
background-color: lightcoral;
}
</style>
And for the second component:
<template>
<div class="component">
<h3> Radiation Drone</h3>
<input v-model = 'radiation' #keyup.enter = "checkRadiation"></input>
<p > Action : {{ action }} </p>
</div>
</template>
<script>
import { eventBus } from '../main.js';
export default {
props : {
action : String
},
computed () {
return {
movement : true,
}
},
mounted(){
eventBus.$on('move',(movement) =>{
this.movement = movement;
if(movement == true){
this.action = "Go to Location";
}else{
this.action = "Dont go There";
}
})
},
methods : {
checkRadiation(){
if(this.radiation >=34){
this.movement = false;
console.log(this.movement);
eventBus.$emit('dontGo',this.movement);
}
else {
this.movement = true;
console.log(this.movement);
this.$root.$emit('goThere',this.movement);
}
}
},
};
</script>
<style scoped>
div {
background-color: lightgreen;
}
</style>
It is showing an error of not manipulating the prop directly I just dont know how to refactor my code.
Any suggestions?
You canĀ“t change the prop value directly from the child component, instead use emit to call a method created in the parent components

How to write a plugin that shows a modal popup using vue. Call should be made as a function()

I am trying to make a VueJS plugin that exports a global method, which when called, will popup a message with an input text field. Ideally, I want to be able to make the following call from any Vue component:
this.$disaplayMessageWithInput("Title","Body","Value");
And a popup should come on the screen.
I've tried building it but when the install() calls this.$ref., it isn't recognized:
DeleteConfirmation.vue
<template>
<b-modal size="lg" ref="deleteConfirmationModal" :title="this.title" header-bg-variant="danger" #ok="confirmDelete" #cancel="confirmCancel">
<p>
{{this.body}}
</p>
</b-modal>
</template>
<script>
export default {
data()
{
return {
title: null,
body: null,
valueCheck: null,
value: null
};
},
install(vue, options)
{
Vue.prototype.$deleteConfirmation = function(title, body, expectedValue)
{
this.title = title;
this.body = body;
this.valueCheck = expectedValue;
this.$refs.$deleteConfirmation.show()
}
},
}
</script>
app.js
import DeleteConfirmation from './components/global/DeleteConfirmation/DeleteConfirmation';
Vue.use(DeleteConfirmation);
The call I am trying to make is:
$vm0.$deleteConfirmation("title","body","val");
I get the below error at the run time:
app.js?id=c27b2799e01554aae7e1:33 Uncaught TypeError: Cannot read property 'show' of undefined
at Vue.$deleteConfirmation (app.js?id=c27b2799e01554aae7e1:33)
at <anonymous>:1:6
Vue.$deleteConfirmation # app.js?id=c27b2799e01554aae7e1:33
(anonymous) # VM1481:1
It looks like, this.$refs in DeleteConfirmation.vue is undefined.
Try to avoiding $ref with vue ( $ref is here for third party and some very special case )
$ref isn't reactive and is populate after the render ...
the best solution for me is using a event bus like this :
const EventBus = new Vue({
name: 'EventBus',
});
Vue.set(Vue.prototype, '$bus', EventBus);
And then use the event bus for calling function of your modal ...
(
this.$bus.on('event-name', callback) / this.$bus.off('event-name');
this.$bus.$emit('event-name', payload);
)
You can create a little wrapper around the bootstrap modal like mine
( exept a use the sweet-modal)
<template>
<div>
<sweet-modal
:ref="modalUid"
:title="title"
:width="width"
:class="klass"
class="modal-form"
#open="onModalOpen"
#close="onModalClose"
>
<slot />
</sweet-modal>
</div>
</template>
<script>
export default {
name: 'TModal',
props: {
eventId: {
type: String,
default: null,
},
title: {
type: String,
default: null,
},
width: {
type: String,
default: null,
},
klass: {
type: String,
default: '',
},
},
computed: {
modalUid() {
return `${this._uid}_modal`; // eslint-disable-line no-underscore-dangle
},
modalRef() {
return this.$refs[this.modalUid];
},
},
mounted() {
if (this.eventId !== null) {
this.$bus.$on([this.eventName('open'), this.eventName('close')], this.catchModalArguments);
this.$bus.$on(this.eventName('open'), this.modalRef ? this.modalRef.open : this._.noop);
this.$bus.$on(this.eventName('close'), this.modalRef ? this.modalRef.close : this._.noop);
}
},
beforeDestroy() {
if (this.eventId !== null) {
this.$off([this.eventName('open'), this.eventName('close')]);
}
},
methods: {
onModalOpen() {
this.$bus.$emit(this.eventName('opened'), ...this.modalRef.args);
},
onModalClose() {
if (this.modalRef.is_open) {
this.$bus.$emit(this.eventName('closed'), ...this.modalRef.args);
}
},
eventName(action) {
return `t-event.t-modal.${this.eventId}.${action}`;
},
catchModalArguments(...args) {
if (this.modalRef) {
this.modalRef.args = args || [];
}
},
},
};
</script>
<style lang="scss" scoped>
/deep/ .sweet-modal {
.sweet-title > h2 {
line-height: 64px !important;
margin: 0 !important;
}
}
</style>
AppModal.vue
<template>
<div class="modal-wrapper" v-if="visible">
<h2>{{ title }}</h2>
<p>{{ text }}</p>
<div class="modal-buttons">
<button class="modal-button" #click="hide">Close</button>
<button class="modal-button" #click="confirm">Confirm</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false,
title: '',
text: ''
}
},
methods: {
hide() {
this.visible = false;
},
}
}
</script>
Modal.js (plugin)
import AppModal from 'AppModal.vue'
const Modal = {
install(Vue, options) {
this.EventBus = new Vue()
Vue.component('app-modal', AppModal)
Vue.prototype.$modal = {
show(params) {
Modal.EventBus.$emit('show', params)
}
}
}
}
export default Modal
main.js
import Modal from 'plugin.js'
// ...
Vue.use(Modal)
App.vue
<template>
<div id="app">
// ...
<app-modal/>
</div>
</template>
This looks pretty complicated. Why don't you use a ready-to-use popup component like this one? https://www.npmjs.com/package/#soldeplata/popper-vue

How to pass initial form values to child component in Vue.js?

I'm using Vue.js. From my template I include the child component (componentB) which includes several input elements. I want to initialize those input elements from my parent template. I found a way to do this (see code below). However, I'm wondering if this is a correct way, as the articles I have read so far use different approaches (e.g. with $emit):
https://simonkollross.de/posts/vuejs-using-v-model-with-objects-for-custom-components
https://zaengle.com/blog/using-v-model-on-nested-vue-components
https://alligator.io/vuejs/add-v-model-support/
Can you confirm that my code below matches the Vue.js design concepts or are there flaws?
<template>
<div>
<div class="md-layout">
<div class="md-layout-item md-size-100">
<ComponentB ref="componentB" v-model="componentB"></ComponentB>
</div>
</div>
</div>
</template>
<script>
import { ComponentB } from "#/components";
export default {
components: {
ComponentB
},
data() {
return {
componentB: {
textInputField: "my-initial-value"
}
};
},
methods: {
validate() {
return this.$refs.componentB.validate().then(res => {
this.$emit("on-validated", res);
return res;
});
}
}
};
</script>
<style></style>
Form componentB
<template>
<div>
<md-field
:class="[
{ 'md-valid': !errors.has('textInputField') && touched.textInputField },
{ 'md-form-group': true },
{ 'md-error': errors.has('textInputField') }
]"
>
<md-icon>label_important</md-icon>
<label>My text input</label>
<md-input
v-model="textInputField"
data-vv-name="textInputField"
type="text"
name="textInputField"
required
v-validate="modelValidations.textInputField"
>
</md-input>
<slide-y-down-transition>
<md-icon class="error" v-show="errors.has('textInputField')"
>close</md-icon
>
</slide-y-down-transition>
<slide-y-down-transition>
<md-icon
class="success"
v-show="!errors.has('textInputField') && touched.textInputField"
>done</md-icon
>
</slide-y-down-transition>
</md-field>
</div>
</template>
<script>
import { SlideYDownTransition } from "vue2-transitions";
export default {
name: "componentB",
props: ['value'],
components: {
SlideYDownTransition
},
computed: {
textInputField: {
get() {return this.value.textInputField},
set(textInputField) { this.$emit('input', { ...this.value, ['textInputField']: textInputField })}
}
},
data() {
return {
touched: {
textInputField: false
},
modelValidations: {
textInputField: {
required: true,
min: 5
}
}
};
},
methods: {
getError(fieldName) {
return this.errors.first(fieldName);
},
validate() {
return this.$validator.validateAll().then(res => {
return res;
});
}
},
watch: {
textInputField() {
this.touched.runnerName = true;
}
}
};
</script>
<style></style>
The simplest way to pass data to child component is to use props, which are then available in the child component and can pass the values back up to the parent.
https://v2.vuejs.org/v2/guide/components-props.html
// PARENT COMPONENT
<ComponentB :textInputField="textInputField" ...></ComponentB>
// CHILD COMPONENT
// TEMPLATE SECTION
<md-input
v-model="textInputField"
value="textInputField"
...
>
// SCRIPT SECTION
export default {
props: {
textInputField: String
}
}

Categories

Resources