Vue, Disable all other inputs after radio is clicked - vue.js

I loop over an array and display a list of radio buttons. I am trying to disable all other radio inputs except for the one selected after one is clicked. All I have been able to manage is to disable all of the radios or the one that is clicked.
I was able to accomplish dynamically adding a class in the v-for loop, so I tried disabling the inputs in the same manner, but everything gets disabled:
:disabled="{'disabled':answer.answerID == isChecked}"
I also tried using a method, but didn't have any luck there either:
/* List data */
"answers":[
{
"answerID": "1",
"answerName": "Blueberries"
},
{
"answerID": "2",
"answerName": "Apples"
},
{
"answerID": "3",
"answerName": "Bananas"
},
{
"answerID": "4",
"answerName": "Pineapple"
},
{
"answerID": "5",
"answerName": "Strawberries"
}
]
/* Component code */
<template>
<input
v-for="(answer, index) in answers"
:key="index"
type="radio"
class="mg-input mg-answer"
ref="mgAnswer"
name="mg-answer"
:value="answer.answerName"
v-model="answerVal"
:disabled="disableAnswer(answers, index)"
:class="{'mg-checked':answer.answerID == isChecked}"
#click="
isChecked = answer.answerID
checkAnswer(answers, index, $event)" />
</template>
<script>
export default {
data: function () {
return {
// Class definer for is checked or not
isChecked: undefined,
// Answer Data Properties
answerVal: '',
checkedAnswerID: '',
checkedAnswerElem: '',
}
},
methods: {
checkAnswer: function (arr, i, event) {
let mgAnswer = this.$refs.mgAnswer
this.checkedAnswerID = arr[i].answerID
this.checkedAnswerElem = mgAnswer[i]
if (mgAnswer[i].answerName !== this.answerVal) {
this.answerVal = ''
}
},
disableAnswer: function (arr, i) {
if (arr[i].answerName !== this.answerVal) {
return true
}
}
}
}
</script>

disableAnswer(answers, index) is evaluated only once upon rendering. Initially, answerVal is null, as no answers have been selected yet, so disableAnswer returns true, causing all radio buttons to be disabled immediately.
A quick fix is to add answerVal as a function argument (i.e., disableAnswer(answers, index, answerVal)), so that the function is re-evaluated when answerVal changes. We have to also modify disableAnswer to ignore null values of answerVal, which would occur at initialization:
disableAnswer(arr, i, answerVal) {
if (!answerVal) {
// not yet set
return;
}
if (arr[i].answerName !== answerVal) {
return true
}
}
new Vue({
el: '#app',
data() {
return {
// Class definer for is checked or not
isChecked: undefined,
// Answer Data Properties
answerVal: '',
checkedAnswerID: '',
checkedAnswerElem: '',
"answers":[
{
"answerID": "1",
"answerName": "Blueberries"
},
{
"answerID": "2",
"answerName": "Apples"
},
{
"answerID": "3",
"answerName": "Bananas"
},
{
"answerID": "4",
"answerName": "Pineapple"
},
{
"answerID": "5",
"answerName": "Strawberries"
}
]
};
},
methods: {
checkAnswer: function (arr, i, event) {
let mgAnswer = this.$refs.mgAnswer
this.checkedAnswerID = arr[i].answerID
this.checkedAnswerElem = mgAnswer[i]
if (mgAnswer[i].answerName !== this.answerVal) {
this.answerVal = ''
}
},
disableAnswer: function (arr, i, answerVal) {
if (!answerVal) return;
if (arr[i].answerName !== answerVal) {
return true
}
}
}
})
input {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
border-radius: 50%;
width: 16px;
height: 16px;
border: 2px solid #999;
transition: 0.2s all linear;
outline: none;
margin-right: 5px;
position: relative;
top: 4px;
}
input[disabled] {
border: 1px solid #ccc;
}
.mg-checked {
border: 6px solid black;
}
<script src="https://unpkg.com/vue#2.5.17"></script>
<div id="app">
<input
v-for="(answer, index) in answers"
:key="index"
type="radio"
class="mg-input mg-answer"
ref="mgAnswer"
name="mg-answer"
:value="answer.answerName"
v-model="answerVal"
:disabled="disableAnswer(answers, index, answerVal)"
:class="{'mg-checked':answer.answerID == isChecked}"
#click="
isChecked = answer.answerID
checkAnswer(answers, index, $event)" />
{{answerVal}}
</div>
You might also find useful a minor refactoring of your code: demo

You can do something like this,
check if input is clicked or if not clicked in that case make disabled set to false.
<input :disabled="disableAnswer(answer)" />
Js
disableAnswer : function(answer){
if(this.answerVal=="" ||this.answerVal==answer.answerName) {
return false;
} else {
return true;
}
}
Added Snippet:
function callMe(){
var vm = new Vue({
el : '#root',
data : {
answerVal:"",
answers:[
{
"answerID": "1",
"answerName": "Blueberries"
},
{
"answerID": "2",
"answerName": "Apples"
},
{
"answerID": "3",
"answerName": "Bananas"
},
{
"answerID": "4",
"answerName": "Pineapple"
},
{
"answerID": "5",
"answerName": "Strawberries"
}
]
},
methods: {
disableAnswer(item){
if(this.answerVal=="" ||this.answerVal==item) {
return false;
} else {
return true;
}
}
}
})
}
callMe();
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.11/dist/vue.js"></script>
<div id='root'>
<input
v-for="(answer, index) in answers"
:key="index"
type="radio"
class="mg-input mg-answer"
ref="mgAnswer"
name="mg-answer"
:value="answer.answerName"
v-model="answerVal"
:disabled="disableAnswer(answer.answerName)"
/>
</div>

Related

Adobe XD Plugin VueJs

I'm using the adobe official vue plugin example from their repo called "ui-hello-vue".
It works fine out of the box but I'm finding it hard to put the vue code into a panel.
And strangely it doesn't even seem to let me do console.log anymore.
Even just logging directly to the console when app is initiated doesn't even work.
As I click on the panel to show it there is just a blank panel.
Any ideas?
main.js
// main.js
console.log('FROM HERE');
const styles = require("./styles.css");
const Vue = require("vue").default;
const hello = require("./hello.vue").default
const { Text, Color } = require("scenegraph");
let dialog;
function getDialog() {
if (dialog == null) {
document.body.innerHTML = `<dialog><div id="container"></div></dialog>`
dialog = document.querySelector("dialog");
var app4 = new Vue({
el: "#container",
components: { hello },
render(h) {
return h(hello, { props: { dialog } })
}
})
}
return dialog
}
let panel;
function create() {
// [1]
const html = `
<style>
.break {
flex-wrap: wrap;
}
label.row > span {
color: #8E8E8E;
width: 20px;
text-align: right;
font-size: 9px;
}
label.row input {
flex: 1 1 auto;
}
form {
width:90%;
margin: -20px;
padding: 0px;
}
.show {
display: block;
}
.hide {
display: none;
}
</style>
<form method="dialog" id="main">
<div class="row break">
<label class="row">
<span>↕︎</span>
<input type="number" uxp-quiet="true" id="txtV" value="10" placeholder="Height" />
</label>
<label class="row">
<span>↔︎</span>
<input type="number" uxp-quiet="true" id="txtH" value="10" placeholder="Width" />
</label>
</div>
<footer><button id="ok" type="submit" uxp-variant="cta">Apply</button></footer>
</form>
<p id="warning">This plugin requires you to select a rectangle in the document. Please select a rectangle.</p>
`;
function increaseRectangleSize() { // [2]
const { editDocument } = require("application"); // [3]
const height = Number(document.querySelector("#txtV").value); // [4]
const width = Number(document.querySelector("#txtH").value); // [5]
// [6]
editDocument({ editLabel: "Increase rectangle size" }, function(selection) {
const selectedRectangle = selection.items[0]; // [7]
selectedRectangle.width += width; // [8]
selectedRectangle.height += height;
});
}
panel = document.createElement("div"); // [9]
panel.innerHTML = html; // [10]
panel.querySelector("form").addEventListener("submit", increaseRectangleSize); // [11]
return panel; // [12]
}
function show(event) { // [1]
if (!panel) event.node.appendChild(create()); // [2]
}
function update(selection) { // [1]
const { Rectangle } = require("scenegraph"); // [2]
const form = document.querySelector("form"); // [3]
const warning = document.querySelector("#warning"); // [4]
if (!selection || !(selection.items[0] instanceof Rectangle)) { // [5]
form.className = "hide";
warning.className = "show";
} else {
form.className = "show";
warning.className = "hide";
}
}
module.exports = {
commands: {
menuCommand: function () {
getDialog().showModal();
}
},
panels:
{
enlargeRectangle: {
show,
update
}
}
};
mainfest.json
// mainfest.json
{
"id": "UI_HELLO_VUE",
"name": "(UI) Hello Vue",
"version": "1.0.0",
"host": {
"app": "XD",
"minVersion": "13.0"
},
"icons": [
{
"width": 24,
"height": 24,
"path": "images/icon#1x.png"
},
{
"width": 48,
"height": 48,
"path": "images/icon#2x.png"
}
],
"uiEntryPoints": [
{
"type": "menu",
"label": "UI Hello Vue",
"commandId": "menuCommand"
},
{
"type": "panel",
"label": "Enlarge a Rectangle",
"panelId": "enlargeRectangle"
}
]
}
You are missing semi-colons on 7, 14, 20 & 24. That could explain no error.

vue js ag-grid filter component not working

I am quite new to vue-js and ag-grid, I would like to have a custom filter on my ag-grid so tried using component as filter as shown in vue-js ag-grid example: "https://www.ag-grid.com/javascript-grid-filter-component/" but its not working and giving "componentType is not a constructor" error in console.
Below is my code:
Gird:
<template>
<div class="all-devices" style="width: 100%; height: 425px;">
<ag-grid-vue
style="width: 100%; height: 100%;"
class="ag-theme-balham"
:gridOptions="gridOptions"
#grid-ready="onGridReady"
:columnDefs="columnDefs"
:defaultColDef="defaultColDef"
:rowData="rowData"
:frameworkComponents="frameworkComponents"
></ag-grid-vue>
</div>
</template>
<script>
import { AgGridVue } from "ag-grid-vue";
import PartialMatchFilter from "./PartialMatchFilter";
export default {
name: "AllDevices",
components: {},
data() {
return {
gridOptions: null,
columnDefs: null,
defaultColDef: null,
rowData: null,
frameworkComponents: null
};
},
components: {
AgGridVue
},
beforeMount() {
this.gridOptions = {};
this.columnDefs = [
{
headerName: "Row",
field: "row",
width: 450
},
{
headerName: "Filter Component",
field: "name",
width: 430,
filter: "partialMatchFilter"
}
];
this.rowData = [
{
row: "Row 1",
name: "Michael Phelps"
},
{
row: "Row 2",
name: "Natalie Coughlin"
},
{
row: "Row 3",
name: "Aleksey Nemov"
},
{
row: "Row 4",
name: "Alicia Coutts"
},
{
row: "Row 5",
name: "Missy Franklin"
},
{
row: "Row 6",
name: "Ryan Lochte"
},
{
row: "Row 7",
name: "Allison Schmitt"
},
{
row: "Row 8",
name: "Natalie Coughlin"
},
{
row: "Row 9",
name: "Ian Thorpe"
},
{
row: "Row 10",
name: "Bob Mill"
},
{
row: "Row 11",
name: "Willy Walsh"
},
{
row: "Row 12",
name: "Sarah McCoy"
},
{
row: "Row 13",
name: "Jane Jack"
},
{
row: "Row 14",
name: "Tina Wills"
}
];
this.defaultColDef = { filter: true };
this.frameworkComponents = { partialMatchFilter: PartialMatchFilter };
},
methods: {
onGridReady(params) {
params.api.sizeColumnsToFit();
}
}
};
</script>
<style>
</style>
Filter component:
<template>
<div>
<input style="height: 20px" :ref="'input'" v-model="text" />
</div>
</template>
<script>
export default {
name: "PartialMatchFilter",
data() {
return {
text: "",
valueGetter: null
};
},
methods: {
isFilterActive() {
return this.text !== null && this.text !== undefined && this.text !== "";
},
doesFilterPass(params) {
return (
!this.text ||
this.text
.toLowerCase()
.split(" ")
.every(filterWord => {
return (
this.valueGetter(params.node)
.toString()
.toLowerCase()
.indexOf(filterWord) >= 0
);
})
);
},
getModel() {
return { value: this.text };
},
setModel(model) {
if (model) {
this.text = model.value;
}
},
afterGuiAttached() {
this.$refs.input.focus();
},
componentMethod(message) {
alert(`Alert from PartialMatchFilterComponent ${message}`);
}
},
watch: {
text: function(val, oldVal) {
if (val !== oldVal) {
this.params.filterChangedCallback();
}
}
},
created() {
this.valueGetter = this.params.valueGetter;
}
};
</script>
Am I missing something? Please help! - Thanks
I had the same problem.
First, make this change in your columnDefs and get rid of frameworkComponents. Its just cleaner.
filter: "partialMatchFilter" -> filterFramework: PartialMatchFilter
Then the actual fix.
In your filter component add Vue.extend:
<template>
...
</template>
<script>
import Vue from "vue";
export default Vue.extend({
...
});
The example I followed -> https://github.com/ag-grid/ag-grid-vue-example/tree/master/src/rich-grid-example
Ref for Vue.extend -> https://v2.vuejs.org/v2/api/#Vue-extend

Using component area to show various content

I have a relatively simple task although I am just a beginner so it's difficult to proceed.
I have a list of users on the left and a right panel to show that users info. The information about the user has an edit button that I want to take over that right panel and then save will return back to the user details.
What is the best approach to go about this?
Should the 2 pages be different components or should I just use javascript to show and hide content? Is there a better approach then either of those?
Sorry I'm new and just trying to get my had around the concept.
Thanks
I wrote a simple example for you:
const data = [{
id: 1,
name: 'user1',
age: 21
},{
id: 2,
name: 'user2',
age: 33
}]
const mixin = {
props: {
userId: {
required: true
}
},
data () {
return {
user: {}
}
},
methods: {
loadUser () {
/*ajax to get user detail data here*/
setTimeout(_=>{
this.user = data.filter(o=>o.id==this.userId)[0]
},10)
}
},
created () {
this.loadUser()
},
watch: {
userId (newVal) {
if(newVal){
this.loadUser()
}
}
}
}
Vue.component('user-viewer',{
template: `<div>
name:{{user.name}}<br>
age: {{user.age}}<br>
<button #click="edit">edit</button>
</div>`,
mixins: [mixin],
methods: {
edit () {
this.$emit('switch-edit-mode',true)
}
}
});
Vue.component('user-editor',{
template: `<div>
name:<input type="text" v-model="user.name"><br>
age: <input type="text" v-model="user.age"><br>
<button #click="sendData">save</button>
</div>`,
mixins: [mixin],
methods: {
sendData () {
/*ajax send user data here*/
setTimeout(_=>{
/*false means edit complete,so that user list must be reloaded*/
this.$emit('switch-edit-mode',false);
},10)
}
}
});
var app = new Vue({
el: '#app',
data () {
return {
users: [],
isModify: false,
userId: null
}
},
methods: {
toggleModify (modify) {
this.isModify = modify
if(!modify){
this.fetchUsers();
}
},
fetchUsers () {
/*load your user list data here*/
this.users = data.map(o=>({
id: o.id,
name: o.name
}))
}
},
created () {
this.fetchUsers()
}
})
*{
padding:0;
margin:0;
}
ul,li{
list-style:none;
}
.main{
display: flex;
}
.user-list{
width: 250px;
}
.user-list>li{
border:1px solid skyblue;
border-bottom: none;
}
.user-list>li:last-child{
border-bottom:1px solid skyblue;
}
.content-wrapper{
flex:1;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<style>
*{
padding:0;
margin:0;
}
ul,li{
list-style:none;
}
.main{
display: flex;
}
.user-list{
width: 250px;
}
.user-list>li{
border:1px solid skyblue;
border-bottom: none;
}
.user-list>li:last-child{
border-bottom:1px solid skyblue;
}
.content-wrapper{
flex:1;
}
</style>
<div id="app">
<div class="main">
<ul class="user-list">
<li v-for="user in users" #click="userId=user.id">{{user.name}}</li>
</ul>
<div class="content-wrapper">
<component v-if="userId" :is="isModify?'user-editor':'user-viewer'" #switch-edit-mode="toggleModify" :user-id="userId"></component>
<div v-else>please choose a user to view or edit</div>
</div>
</div>
</div>
your mixin file:(mixin.js)
export default{
props: {
userId: {
required: true
}
},
data () {
return {
user: {}
}
},
methods: {
loadUser () {
/*ajax to get user detail data here*/
setTimeout(_=>{
this.user = data.filter(o=>o.id==this.userId)[0]
},10)
}
},
created () {
this.loadUser()
},
watch: {
userId (newVal) {
if(newVal){
this.loadUser()
}
}
}
}
usage:
import mixin from 'mixin.js'
export default{
...
mixins: [mixin]
}

vue component data watch outside

In my application i have a component and i want wath his properties outside of the component.
I've created this example:
Vue.component('vue-table', {
template: '<div><template v-for="row in apiData.content"><span>{{row.name}}</span><button #click="remove(row)">remove</button><br></template></div>',
data: function() {
return {
//this data will be loaded from api
apiData: {
total: 20,
content: [
{id: 10, name: 'Test'},
{id: 12, name: 'John'},
{id: 13, name: 'David'},
],
},
};
},
methods: {
remove(row) {
this.apiData.content.splice(this.apiData.content.indexOf(row), 1);
},
},
})
new Vue({
el: '#app',
methods: {
isActive(){
//how can i check if in vue-table apiData.content > 0?
//return this.$refs.table.apiData.data.length > 0;
},
},
})
http://jsfiddle.net/z11fe07p/2806/
So i want to change class of span to 'active' when the length of vue-table apiData.content.length > 0
How can i do this?
The standard practice would be to emit an event in the child and have the parent receive and act on it. You might wonder whether you can watch the length of an array -- one that doesn't even exist when the component is instantiated -- and the answer is yes.
Look at the watch section. IMO, this is so cool that it's probably frowned upon.
Vue.component('vue-table', {
template: '<div><template v-for="row in apiData.content"><span>{{row.name}}</span><button #click="remove(row)">remove</button><br></template></div>',
data: function() {
return {
//this data will be loaded from api
apiData: {},
};
},
methods: {
remove(row) {
this.apiData.content.splice(this.apiData.content.indexOf(row), 1);
},
},
watch: {
'apiData.content.length': function(is, was) {
this.$emit('content-length', is);
}
},
created() {
this.apiData = {
total: 20,
content: [{
id: 10,
name: 'Test'
}, {
id: 12,
name: 'John'
}, {
id: 13,
name: 'David'
}, ],
};
}
})
new Vue({
el: '#app',
data: {
isActive: false
},
methods: {
setActive(contentLength) {
this.isActive = contentLength > 0;
}
},
})
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
.active {
font-weight: bold;
}
<script src="//unpkg.com/vue#latest/dist/vue.js"></script>
<div id="app">
<p>
<span :class="{active: isActive}">Users:</span>
</p>
<vue-table refs="table" #content-length="setActive"></vue-table>
</div>

How to add and remove CSS class names in Angular 2?

I get the data in the JSON format and must and I must to handle which item the user clicked on. I'm write this code and it correctly work.
My Example with DOM-usage:
#Component({
selector: 'my-app',
template: `
<div *ngFor="let person of personsList; let i = index">
<span class="toggle-icon" (click)="toggleStatus(person.id)" id="{{person.id}}">{{person.name}}</span>
</div>
`,
styles: ['.active { color: red; }']
})
export class App {
toggleIsActive: boolean = false;
personsList: any;
constructor() {
this.personsList = [
{
"id": "1",
"name": "Alex"
},
{
"id": "2",
"name": "John"
}
]
}
toggleStatus(id){
const span = document.getElementById(`${id}`);
if (span.className.indexOf('active') >= 0) {
span.classList.remove('active');
} else {
span.classList.add('active');
}
}
}
How I can add and remove CSS class names without DOM for a similar case?
I'm just add "personIsActive" field, and use Class binding.
#Component({
selector: 'my-app',
template: `
<div *ngFor="let person of personsList; let i = index">
<span class="toggle-icon" [class.active]="person.personIsActive"
(click)="toggleStatus(person.id)">{{person.name}}</span>
</div>
`,
styles: ['.active { color: red; }']
})
export class App {
toggleIsActive: boolean = false;
personsList: any;
constructor() {
this.personsList = [
{
"id": "1",
"name": "Alex",
"personIsActive": false
},
{
"id": "2",
"name": "John",
"personIsActive": false
}
]
}
toggleStatus(id){
for (let i = 0; i < this.personsList.length; i++) {
if (this.personsList[i].id === id) {
this.personsList[i].personIsActive= !this.personsList[i].personIsActive;
}
}
}
}