How to update Vuejs2 page content when changing select option on api rxjs observable api endpoint? - vue.js

I'm a bit new at Vuejs2 and rxjs. So please be kind ^_^. I have an Observable api endpoint. I want to change the param value "food_type" via a select drop down on the same page. I want it so that when I select an item via the drop down the param value is updated, changing the end point and the data on the page gets reloaded. How can I achieve this?
here is my select drop down….
<div class="col-sm-2 divTableHead hand">
<select name="food_type" id="food_type" class="form-control" v-model="food_type">
<option value="" selected>Feeding</option>
<option value=“A”>One</option>
<option value=“AB”>Two Bee</option>
<option value=“BB”>Bee Bee</option>
<option value=“CB”>Cee Bee</option>
<option value=“CC”>Cee Cee</option>
</select>
</div>
here is what my Observable looks like…
data() {
return {
thisCat: [],
food_type: ''
}
},
subscriptions() {
return {
thisCat: Observable.from(axios.get(`${process.env.KITTY_URL}/api/v1/feedings/?cat__slug&cat__name=${this.$route.params.catName}&food_type=${""}`)
.catch(error => console.log(error)))
.pluck("data","results")
}
},
Thank you -_^

Seems like what you're looking for is a Watcher.
This is most useful when you want to perform asynchronous or expensive
operations in response to changing data.
That's exactly the case!
Check out the example below I prepared for you using the JSONPlaceholder API.
var app = new Vue({
el: '#app',
data: {
postID: '',
loading: false,
postContent: null,
},
watch: {
postID: function () {
this.fetchPost()
}
},
methods: {
fetchPost: function(id) {
this.loading = true;
fetch('https://jsonplaceholder.typicode.com/posts/'+this.postID)
.then(response => response.json())
.then(json => {
this.postContent = {
title: json.title,
body: json.body
}
this.loading = false;
})
},
}
});
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<div id="app">
<select v-model="postID">
<option value="" disabled>Select a post</option>
<option value="1">Post #1</option>
<option value="2">Post #2</option>
<option value="3">Post #3</option>
<option value="4">Post #4</option>
<option value="5">Post #5</option>
</select>
<h2 v-if="loading">Loading...</h2>
<div v-if="postContent" class="post_content">
<h3>{{postContent.title}}</h3>
<p>{{postContent.body}}</p>
</div>
</div>
As you can see, the watcher watches for any changes of that property and perform whatever you told it to do. In this case, call the fetchPost method and perform a fetch.

Related

How to get Please select to show in a dropdown box in vue

I have this form that has a dropdown box. Everything works fine except if you have "Please Select" selected it saves the value of it which is 0 to the database and because of that when I go to the page that displays the product, the page breaks because it can't find the category.
Here is my code
<template>
<div>
<select class="form-select form-control" v-model="newCategory">
<option value="0" selected>Please Select</option
<option v-for=category in categories" :value=category.id>
{{ category.name }}
</option>
</select>
<button class="btn btn-success" #click="save">Save</button>
</div>
</template>
<script>
export default {
props: ['categories'],
data() {
return {
newCategory: 0
}
},
methods: {
save(){
axios.post('/api/products/create', {
category: this.newCategory
}).then(response => {
this.newCategory = 0;
});
}
}
}
</script>
Just check this.newCategory before you send a request to the server. If it is 0, show a toast or something.
The code should be like this:
save(){
if(this.newCategory === 0) show something and return;
// bottom codes will be shown only in else section
axios.post('/api/products/create', {
category: this.newCategory
}).then(response => {
this.newCategory = 0;
});
}
First you need to make your newCategory field accept null values, because you need to populate it even if you are saving data without selecting option. Then try the following:
<select v-model="newCategory">
<option value="" disabled selected>Select your option</option>
<option v-for=category in categories" :value=category.id>
{{ category.name }}
</option>
</select>
data() {
return {
newCategory: null
}
},

Get each HTML element in a Vue slot from JavaScript

I am creating a custom select component in VueJS 2. The component is to be used as below by the end-user.
<custom-select>
<option value="value 1">Option 1</option>
<option value="value 2">Option 2</option>
<option value="value 3">Option 3</option>
...
<custom-select>
I know the Vue <slot> tag and usage. But how do I get the user provided <option> tags as an array/list so I can get its value and text separately for custom rendering inside the component?
Those <option>s would be found in the default slot array (this.$slots.default), and you could get to the inner text and value of the <option>s like this:
export default {
mounted() {
const options = this.$slots.default.filter(node => node.tag === 'option')
for (const opt of options) {
const innerText = opt.children.map(c => c.text).join()
const value = opt.data.attrs.value
console.log({ innerText, value })
}
}
}
demo
You can achieve it, using v-bind and computed property
new Vue({
el: '#vue',
data: {
selected: '',
values: [
{
code: '1',
name: 'one'
},
{
code: '2',
name: 'two'
}
]
},
computed: {
selectedValue() {
var self = this;
var name = "";
this.values.filter(function(value) {
if(value.code == self.selected) {
name = value.name
return;
}
})
return name;
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue">
<div>
<select v-model="selected">
<option v-for="value in values" v-bind:value="value.code">
{{ value.name }}
</option>
</select>
</div>
<strong>{{ selected }} {{ selectedValue }}</strong>
</div>

VueJS Using #click on <option> elements and #change on <select> element

I have a simple function that manipulates a data between true and false. I use it to make my div hidden and visible like a toggle. I also have a with three element. I found out that I cannot use #click for <option> elements, I need to use #change for my <select>.
But in this way, whenever an is selected, the function is being triggered and my data toggles between true and false. Here is my <select> element;
<select #change="isDisabled">
<option>Please select a security type</option>
<option>No Security</option>
<option>Personal</option>
<option>Enterprise</option>
</select>
IsDisabled function takes a variable and change its values between true and false so my div becomes hidden and visible as follows;
<div v-if="noSecurity">something</div>
But here is the thing, I only want to trigger the function when the user select the "No Security" option. Now it's being triggered whenever I select an option, so it turned out to be some kind of a toggle. But I want to hide the div when I select the "No Security" option and show the div if something different is selected. What should I do?
I've made a CodeSandbox where you could see the result :
https://codesandbox.io/s/magical-meitner-63eno?file=/src/App.vue
But here is the explanation:
<template>
<section>
<select #change="isDisabled">
<option>Please select a security type</option>
<option>No Security</option>
<option>Personal</option>
<option>Enterprise</option>
</select>
<div v-if="noSecurity">You Choose no security, that's dangerous !</div>
</section>
</template>
<script>
import HelloWorld from "./components/HelloWorld";
export default {
name: "App",
data() {
return {
noSecurity: false,
};
},
methods: {
isDisabled(e) {
console.log("e", e.target.value);
if (e.target.value === "No Security") {
// do your change
return (this.noSecurity = !this.noSecurity);
}
// to allow reset if another option is selected
if (this.noSecurity) {
return this.noSecurity = false;
}
},
},
};
</script>
Basically when you use the #change handler, your function will receive an event, in this event you can catch the target value with event.target.value.
Doing so, you do a condition if the value is equal to No Security (so the selected item), you change your state, if it's not No Security, you do nothing, or you do something else you would like to do.
Appart from that, I advice you to change your method name isDisabled to a global convention name like handleChange, or onChange.
Pass id values in your option so when you get the select event you're clear that No security or whatver the name you would like to change will be the same.
Because if one day you change No security to another name, you have to update all your conditions in your app. Try to avoid conditions with strings values like this if you can.
<option value="1">No Security</option> // :value="securityType.Id" for example if coming from your database
<option value="2">Personal</option>
<option value="3">Enterprise</option>
then in your function it will be
if (e.target.value === noSecurityId) {
// do your change
this.noSecurity = !this.noSecurity;
}
//...
There's no need for the additional noSecurity variable. Create your select with v-model to track the selected value. Give each option a value attribute.
<select v-model="selected">
<option value="">Please select a security type</option>
<option value="none">No Security</option>
<option value="personal">Personal</option>
<option value="enterprise">Enterprise</option>
</select>
Check that value:
<div v-if="selected === 'none'">something</div>
You can still use the noSecurity check if you prefer by creating a computed:
computed: {
noSecurity() {
return this.selected === 'none';
}
}
Here's a demo showing both:
new Vue({
el: "#app",
data() {
return {
selected: ''
}
},
computed: {
noSecurity() {
return this.selected === 'none';
}
},
methods: {},
created() {}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<select v-model="selected">
<option value="">Please select a security type</option>
<option value="none">No Security</option>
<option value="personal">Personal</option>
<option value="enterprise">Enterprise</option>
</select>
<div v-if="selected === 'none'">something</div>
<div v-if="noSecurity">something</div>
</div>
Is using v-model instead of using a method is option for you? If it is, please try the following:
HTML:
<div id="hello-vue" class="demo">
<select v-model="security">
<option>Please select a security type</option>
<option>No Security</option>
<option>Personal</option>
<option>Enterprise</option>
</select>
<div v-if="security=='No Security'">something</div>
</div>
JS:
const HelloVueApp = {
data() {
return {
security: undefined
}
}
}

Change options and value at the same time

I have a problem when I try to change the options of my select and its value at the same time. If I use v-model, it works properly but if I use v-bind:value + v-on:change, it will not work.
Here is a js fiddle that will illustrate the problem : https://jsfiddle.net/2vcer6dz/18/
The first time you click on the button "change", only the first select value will be 3. If you reclick they all become 3.
Html
<div id="app">
<select v-model="value">
<option v-for="item in options" :key="item.Value" :value="item.Value">{{item.Text}}</option>
</select>
<select :value="value" v-on:change="value = $event.target.value">
<option v-for="item in options" :key="item.Value" :value="item.Value">{{item.Text}}</option>
</select>
<select-option v-model="value" :options="options"></select-option>
<br />
<input type="button" value="change" v-on:click="change" />
</div>
<template id="template-select-option">
<select :value="value" v-on:change="update($event.target.value)">
<option v-for="item in options" :key="item.Value" :value="item.Value">{{item.Text}}</option>
</select>
</template>
Javascript
Vue.component('select-option', {
template: '#template-select-option',
props: ['value', 'options'],
methods: {
update: function (value) {
this.$emit('input', value);
}
}
});
new Vue({
el: '#app',
data: {
value: 1,
options: [{Value:1, Text:1}, {Value:2, Text:2}]
},
methods: {
change: function () {
this.options = [{Value:1, Text:1}, {Value:2, Text:2}, {Value:3, Text:3}];
this.value = 3;
}
}
});
Expected result
All selects should have the value "3" when you click on the button "change"
Changing the options and the value at the same time is confusing Vue. This is probably a minor bug in Vue. If you use $nextTick to push the value change off to the next update cycle, they all work.
change: function () {
this.options = [{Value:1, Text:1}, {Value:2, Text:2}, {Value:3, Text:3}];
this.$nextTick(() => {
this.value = 3;
});
}
It seems that this is a known bug which was closed because a workaround was found.
The workaround is to declare another property and cast v-model on it. This solutions is easier to implement inside a component.
https://jsfiddle.net/6gbfhuhn/8/
Html
<template id="template-select-option">
<select v-model="innerValue">
<option v-for="item in options" :key="item.Value" :value="item.Value">{{item.Text}}</option>
</select>
</template>
Javascript
Vue.component('select-option', {
template: '#template-select-option',
props: ['value', 'options'],
computed: {
innerValue: {
get: function() { return this.value; },
set: function(newValue) { this.$emit('input', newValue); }
}
}
});
Note: In the github thread, it is suggested to use a computed property instead, but if you use a computed property, vue will throw warning every time you change the value in your dropdown because computed property don't have setter.

Vuejs + Materializecss select field

I have this code in my template:
<div class="input-field col s6">
<select v-on:change="selectChaned" v-model="item.size">
<option value="" disabled selected>Choose your option</option>
<option v-on:click="optionClicked" v-for="size in case_sizes" v-bind:value="{{ size }}">{{ size }}</option>
</select>
<label for="size">Size</label>
</div>
According to Materializecss docs, I call $('select').material_select(); to transform default select field into something cutie. What it also does - it replaces <select> and <option> tags with <ul> and <li>.
As a result I can't access value of item.size in my ViewModel js file. I even tried to listen for a click on option field and call optionClicked method (which should simply alert a message then), tried to listen for selectChaned. Nothing.
How can I get option value in ViewModel?
p.s. just for information: I only have problem with select field. Input field for example works fine:
<input placeholder="" name="name" type="text" class="validate" v-model="item.name">
In ViewModel I'm able to access item.name
It seems that Materialize doesn't dispatch any events so I couldn't find an elegant solution. But it does seem that the following Vuejs directive + jQuery workaround is working:
Vue.directive("select", {
"twoWay": true,
"bind": function () {
$(this.el).material_select();
var self = this;
$(this.el).on('change', function() {
self.set($(self.el).val());
});
},
update: function (newValue, oldValue) {
$(this.el).val(newValue);
},
"unbind": function () {
$(this.el).material_select('destroy');
}
});
And then in your HTML – bind <select> using v-select instead of v-model.
Vue.js 2.0
Template:
<div v-text="selected"></div>
<material-select v-bind="selected = selected || options[0].value" v-model="selected">
<option v-for="option in options" :value="option.value" v-text="option.name"></option>
</material-select>
Component:
"use strict";
Vue.component("material-select", {
template: '<select><slot></slot></select>',
props: ['value'],
watch: {
value: function (value) {
this.relaod(value);
}
},
methods:{
relaod : function (value) {
var select = $(this.$el);
select.val(value || this.value);
select.material_select('destroy');
select.material_select();
}
},
mounted: function () {
var vm = this;
var select = $(this.$el);
select
.val(this.value)
.on('change', function () {
vm.$emit('input', this.value);
});
select.material_select();
},
updated: function () {
this.relaod();
},
destroyed: function () {
$(this.$el).material_select('destroy');
}
});
Vue.directive('material-select', {
bind:function(el,binding,vnode){
$(function () {
$(el).material_select();
});
var arg = binding.arg;
if(!arg)arg="change";
arg = "on"+arg;
el[arg]=function() {
if (binding.expression) {
if (binding.expression in vnode.context.$data) {
vnode.context.$data[binding.expression] = el.value;
} else if (vnode.context[binding.expression] &&
vnode.context[binding.expression].length <= 1) {
vnode.context[binding.expression](el.value);
} else {
throw new Error('Directive v-' + binding.name + " can not take more than 1 argument");
}
}
else {
throw new Error('Directive v-' + binding.name + " must take value");
}
}
},
unbind:function(el) {
$(el).material_select('destroy');
}
});
new Vue({
el: '#exemple1',
data:function(){
return {
selected: '',
options:[
{value:"v1",text:'description 1'},
{value:"v2",text:'description 2'},
{value:"v3",text:'description 3'},
{value:"v4",text:'description 4'},
{value:"v5",text:'description 5'},
]
}
}
});
new Vue({
el: '#exemple2',
data:function() {
return{
selected: null,
options:[
{value:"v1",text:'description 1'},
{value:"v2",text:'description 2'},
{value:"v3",text:'description 3'},
{value:"v4",text:'description 4'},
{value:"v5",text:'description 5'},
]
}
},
methods:{
change:function(value){
this.selected = value;
alert(value);
}
}
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/css/materialize.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.98.0/js/materialize.min.js"></script>
<h4>vue js materialize</h4>
<h5>Exemple1</h5>
<div id="exemple1">
<select v-material-select:change="selected" class="blue-text">
<option value="" disabled selected ><slot>Defaut message</slot></option>
<option v-for="option in options" :value="option.value">{{ option.text}}</option>
</select>
</div>
<h5>Exemple2</h5>
<div id="exemple2">
<select v-material-select:change="change" class="blue-text">
<option disabled selected ><slot>Choisir Votre Abonnement</slot></option>
<option v-for="option in options" :value="option.value">{{ option.text}}</option>
</select>
</div>
The top answer was nice but didn't work for Vue 2.
Here is an update of which works (probably still a little hacky). I moved the jQuery hook into update() as the bind function called too early for materialize.
Vue.directive("select", {
"twoWay": true,
update: function(el, binding, vnode) {
if(!vnode.elm.dataset.vueSelectReady) {
$(el).on('change', function() {
vnode.context.$set(vnode.context, binding.expression, el.value);
});
$(el).material_select();
vnode.elm.dataset.vueSelectReady = true
}
},
unbind: function(el, binding, vnode) {
$(el).material_select('destroy');
}
});
HTML:
<select v-select=selected>
<option value="" disabled selected>Choose your option</option>
<option :value="item" v-for='item in items'>{{ item }}</option>
<label>Materialize Select</label>
</select>
You can make the dynamic select in Vue + Materializecss work with simple hacks
$('#select').val(1).material_select(); // Set value and reinitialize materializecss select
mounted () {
$("#select").change(function(){
this.update_result.category = $("#select").val();
}.bind(this)); // To set the user selected value to the data property
update_result.
}
If you are using meterializecss beta version the function name to initialize the select will differ.
I had a similar problem. The catch here is, you need to issue $('select').material_select(); only after the DOM of your Vue app is ready. So you can add a ready method to your Vue app and include $('select').material_select(); inside your ready method.
var vm = new Vue({
data: function() {
locations: ["Clayton", "Mt Meigs", "Birmingham", "Helena", "Albertville", "Albertville", "Grant"]
},
ready: function() {
$('select').material_select();
}});
Just make sure you include Jquery first, then materialize.js followed by Vue.js in your html file.
I want to include a working fiddle of custom select2 directive which I built for my project. It also supports multiple selects:
fiddle
data: function() {
return {
names: [
{id: 1, value: 'Alice'},
{id: 1, value: 'Bob'},
{id: 1, value: 'Simona'}
],
myStudents: {
names: ['Alice', 'Bob'],
}
}
},
directives: {
'select': {
twoWay: true,
params: ['options'],
bind: function () {
var self = this
$(this.el).select2().on('change', function() {
self.set($(self.el).val())
})
},
update: function (value) {
$(this.el).val(value).trigger('change')
},
},
},
<select multiple v-select="myStudents.names" name="names" v-model="myStudents.names">
<option v-for="name in names" value="{{ name.value }}">{{ name.value }}</option>
</select>
v- VueJs2.4
None of the above answers were for multiple select element. I got it working by traversing the select element options. This is not a correct approach and kind of hack but works.
Plunker
<h4>vue js materialize select</h4>
<div class="row" id="app" style="padding-bottom:2em;">
<div class="input-field col s12 m8">
<select multiple v-material-select:change="selected">
<option value="AngularJs">AngularJs</option>
<option value="Bootstrap3">Bootstrap3</option>
<option value="Bootstrap4">Bootstrap4</option>
<option value="SCSS">SCSS</option>
<option value="Ionic">Ionic</option>
<option value="Angular2">Angular2</option>
<option value="Angular4">Angular4</option>
<option value="React">React</option>
<option value="React Native">React Native</option>
<option value="Html5">Html5</option>
<option value="CSS3">CSS3</option>
<option value="UI/UX">UI/UX</option>
</select>
<label>Technologies Used</label>
</div>
<h2>Your selected options</h2>
<p>{{$data.selected}}</p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/0.100.2/js/materialize.min.js"></script>
<script src="https://unpkg.com/vue#2.4.4/dist/vue.js"></script>
<script> Vue.directive("material-select", {
bind: function(el, binding, vnode) {
$(function() {
$(el).material_select();
});
var arg = binding.arg;
if (!arg) arg = "change";
arg = "on" + arg;
el[arg] = function() {
vnode.context.$data.selected = [];
for (let i = 0; i < 12; i++) {
if (el[i].selected === true) {
vnode.context.$data.selected.push(el[i].value);
}
}
};
},
unbind: function(el) {
$(el).material_select("destroy");
}
});
var app = new Vue({el: "#app",data: { selected: []},
ready: function() {
$("select").material_select(); }});</script>
The possible solution that I found is to use an input, and attach it to a dropdown content. It works well with vue even when you are dynamically creating dropdown. And its reactive, that you don't have to emit any other event to bind values.
Codepen: https://codepen.io/aaha/project/editor/DGJNLE
<style>
input{
cursor: pointer;
}
.caret{
float:right;
position: relative;
cursor: pointer;
top:-50px;
}
ul{
width: 100%;
}
</style>
<script>
Vue.component('paper-dropdown', {
template: '<div> \
<div class="input-field">\
<input type="text" class="dropdown-button" v-bind:data-activates="_id"\
v-bind:value="value"> \
<label>{{label}}</label> \
</div> \
<i class="material-icons caret">arrow_drop_down</i>\
<ul v-bind:id="_id" class="dropdown-content"> \
<li v-for="item in options" v-on:click="setselected"><a v-bind:value="item">{{item}}</a></li> \
</ul>\
</div>',
watch: {
value: function(){
Materialize.updateTextFields();
}
},
computed:{
_id: function(){
if(this.id != null) return this.id;
return Math.random().toString(36).substr(2);
}
},
props: {
label:{
type: [String, Number],
default: ''
},
options:{
type: Array,
default: []
},
placeholder:{
type: String,
default: 'Choose your option'
},
value:{
type: String,
default: ''
},
id:{
type: String,
default: 'me'
}
},
methods:{
setselected: function(e){
this.$emit('input', e.target.getAttribute("value"));
}
},
mounted: function(){
$('.dropdown-button').dropdown({
inDuration: 300,
outDuration: 225,
constrainWidth: false, // Does not change width of dropdown to that of the activator
hover: false, // Activate on hover
gutter: 0, // Spacing from edge
belowOrigin: false, // Displays dropdown below the button
alignment: 'left', // Displays dropdown with edge aligned to the left of button
stopPropagation: false // Stops event propagation
}
);
}
});
</script>
I did something much more simple, only on mounted:
....
mounted() {
$(this.$el)
.find(".mdb-select")
.material_select();
const self = this;
$(this.$el).on("change", function(e) {
self.$emit('input', this.inputValue);
});
},
.....