Vue 2 component prop getting wrong value - javascript

I am trying to build a menu between categories. If a category has a sub-category it returns a value that says has_subCategory as boolean 0/1.
<template>
<select><slot></slot></select>
</template>
<script>
export default {
props: ['value',
'hasSubCat'],
watch: {
value: function(value, hasSubCat) {
this.relaod(value);
this.fetchSubCategories(value, hasSubCat);
}
},
methods: {
relaod: function(value) {
var select = $(this.$el);
select.val(value || this.value);
select.material_select('destroy');
select.material_select();
},
fetchSubCategories: function(value, hasSubCat) {
var mdl = this;
var catID = value || this.value;
var has_subCat = hasSubCat || this.hasSubCat;
console.log("has_subCat:" + has_subCat);
mdl.$emit("reset-subcats");
if (catID) {
if (has_subCat == 0) {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
} else {
axios.get(URL.API + '/subcategories/' + catID)
.then(function(response) {
response = response.data.subcatData;
response.unshift({
subcat_id: '0',
subcategory_name: 'All Subcategories'
});
mdl.$emit("update-subcats", response);
$('.subdropdown').fadeIn();
})
.catch(function(error) {
if (error.response.data) {
swal({
title: "Something went wrong",
text: "Please try again",
type: "error",
html: false
});
}
});
}
} else {
if ($('.subdropdown').is(":visible") == true) {
$('.subdropdown').fadeOut();
}
}
}
},
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');
}
}
</script>
<material-selectcat v-model="catId" name="category" #reset-subcats="resetSubCats" #update-subcats="updateSubCats" id="selcat">
<option v-for="cat in cats" :value="cat.cat_id" :hasSubCat="cat.has_subCat" v-text="cat.category_name"></option>
</material-selectcat>
The data looks like this:
cat_id:"0"
category_name:"All Subcategories"
has_subCat:0
What I dont understand is that console.log("has_subCat:" + hasSubCat); prints out different values each time I change the select. It should only display 0 or 1

Watcher in vue.js is supposed to be used in order to watch one value, but you can fulfill your requirement with help of computed.
export default {
props: ['value',
'hasSubCat'],
watch: {
/* without this, watcher won't be evaluated */
watcher: function() {}
},
computed: {
watcher: function() {
this.reload(this.value);
this.fetchSubCategories(this.value, this.hasSubCat);
}
},
...
}
I also made a simplified working fiddle, you can have a look.

You are assuming that the second parameter of your watch function is hasSubCat which is not the case. While the first parameter of the value watch function represents the new value of the property, the second parameter is actually the old value of the watched property. Try this out to understand more.
watch: {
value: function(value, oldValue) {
console.log('new value:', value)
console.log('old value:', oldValue)
}
}
So to watch both of value and hasSubCat, you can do something like this:
watch: {
value: function(newValue) {
this.reload(newValue)
this.fetchSubCategories(newValue, this.hasSubCat)
},
hasSubCat: function(newHasSubCat) {
this.reload(this.value)
this.fetchSubCategories(this.value, newHasSubCat)
}
}

Related

V-model is not listening to value change for an input (vuejs)

I have an object property which could listen to the user input or could be changed by the view.
With the snipped below :
if I typed something the value of my input is updated and widget.Title.Name is updated.
if I click on the button "External Update", the property widget.Title.Name is updated but not the value in my field above.
Expected result : value of editable text need to be updated at the same time when widget.Title.Name change.
I don't understand why there are not updated, if I inspect my property in vue inspector, all my fields (widget.Title.Name and Value) are correctly updated, but the html is not updated.
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function () {
this.widget.Title.Name = "changed title";
},
}
})
button{
height:50px;
width:100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable"
v-on="listeners">
</p>
</template>
I searched a lot of subject about similar issues but they had reactivity problem, I think I have a specific problem with input. Have you any idea of what's going on ? I tried to add a listener to change event but it was not triggered on widget.Title.Name change.
To anwser to this problem, you need to do 3 differents things.
Add watch property with the same name as your prop (here value)
Add debounce function from Lodash to limit the number of request
Add a function to get back the cursor (caret position) at the good position when the user is typing
For the third point : when you change the value of widget.Title.Name, the component will re-render, and the caret position will be reinitialize to 0, at the beginning of your input. So, you need to re-update it at the last position or you will just write from right to left.
I have updated the snippet above with my final solution.
I hope this will help other people coming here.
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
//Added watch value to watch external change <-> enter here by user input or when component or vue change the watched property
watch: {
value: function (newVal, oldVal) { // watch it
// _.debounce is a function provided by lodash to limit how
// often a particularly expensive operation can be run.
// In this case, we want to limit how often we update the dom
// we are waiting for the user finishing typing his text
const debouncedFunction = _.debounce(() => {
this.UpdateDOMValue();
}, 1000); //here your declare your function
debouncedFunction(); //here you call it
//not you can also add a third argument to your debounced function to wait for user to finish typing, but I don't really now how it works and I didn't used it.
}
},
computed: {
listeners() {
return { ...this.$listeners, input: this.onInput };
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
},
UpdateDOMValue: function () {
// Get caret position
if (window.getSelection().rangeCount == 0) {
//this changed is made by our request and not by the user, we
//don't have to move the cursor
this.$refs["editable-text"].innerText = this.value;
} else {
let selection = window.getSelection();
let index = selection.getRangeAt(0).startOffset;
//with this line all the input will be remplaced, so the cursor of the input will go to the
//beginning... and you will write right to left....
this.$refs["editable-text"].innerText = this.value;
//so we need this line to get back the cursor at the least position
setCaretPosition(this.$refs["editable-text"], index);
}
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function () {
this.widget.Title.Name = "changed title";
},
}
})
/**
* Set caret position in a div (cursor position)
* Tested in contenteditable div
* ##param el : js selector to your element
* ##param caretPos : index : exemple 5
*/
function setCaretPosition(el, caretPos) {
var range = document.createRange();
var sel = window.getSelection();
if (caretPos > el.childNodes[0].length) {
range.setStart(el.childNodes[0], el.childNodes[0].length);
}
else
{
range.setStart(el.childNodes[0], caretPos);
}
range.collapse(true);
sel.removeAllRanges();
}
button{
height:50px;
width:100px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable"
v-on="listeners">
</p>
</template>
you can use $root.$children[0]
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
computed: {
listeners() {
return {...this.$listeners, input: this.onInput
};
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function(e) {
this.widget.Title.Name = "changed title";
this.$root.$children[0].$refs["editable-text"].innerText = "changed title";
},
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable" v-on="listeners">
</p>
</template>
or use Passing props to root instances
Vue.component('editable-text', {
template: '#editable-text-template',
props: {
value: {
type: String,
default: '',
},
contenteditable: {
type: Boolean,
default: true,
},
},
computed: {
listeners() {
return {...this.$listeners, input: this.onInput
};
},
},
mounted() {
this.$refs["editable-text"].innerText = this.value;
this.$root.$on("titleUpdated",(e)=>{
this.$refs["editable-text"].innerText = e;
})
},
methods: {
onInput(e) {
this.$emit('input', e.target.innerText);
}
}
});
var vm = new Vue({
el: '#app',
data: {
widget: {
Title: {
Name: ''
}
}
},
async created() {
this.widget.Title.Name = "toto"
},
methods: {
externalChange: function(e) {
this.widget.Title.Name = "changed title";
this.$root.$emit("titleUpdated", this.widget.Title.Name);
},
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.16/dist/vue.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="app">
<editable-text v-model="widget.Title.Name"></editable-text>
<template>Name : {{widget.Title.Name}}</template>
<br>
<br>
<button v-on:click="externalChange">External update</button>
</div>
<template id="editable-text-template">
<p ref="editable-text" v-bind:contenteditable="contenteditable" v-on="listeners">
</p>
</template>

vue.js multi select change options inside axios GET

Hi I'm using this modified wrapper to handle a multiple select for vue.js. I'm trying to change value of this inside vue component. Here is my code.
<select2-multiple :options="car_options" v-model="input.classification">
<option disabled value="0">Select one</option>
</select2-multiple>
And this is my script,
Vue.component('select2Multiple', {
props: ['options', 'value'],
template: '#select2-template',
mounted: function () {
var vm = this
$(this.$el)
// init select2
.select2({ data: this.options })
.val(this.value)
.trigger('change')
// emit event on change.
.on('change', function () {
vm.$emit('input', $(this).val())
})
},
watch: {
value: function (value) {
alert(value);
if ([...value].sort().join(",") !== [...$(this.$el).val()].sort().join(","))
$(this.$el).val(value).trigger('change');
},
options: function (options) {
// update options
$(this.$el).select2({ data: options })
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
});
var vm = new Vue({
el: '#el',
delimiters: ["[[", "]]"],
data: {
input: {
classification: []
},
},
created: function () {
var vm = this;
axios.get('http://jsonplaceholder.typicode.com/todos')
.then(function (response) {
$.each(response.data, function (i, item) {
response.data[i].id = String(response.data[i].id);
response.data[i].text = String(response.data[i].title);
vm.car_options.push(response.data[i]);
});
vm.input.classification = ["2"];
})
.catch(function (error) {
console.log(error);
});
}
});
I need to get vm.input.classification = ["2"] selected by default. And it's not working and no error message displays. I'm no expert in vue components but I feel like issue relies on vue component.
Here is a js fiddle for my example,
Finally I found the answer. We need to swapp the positions of options and value of component watch.
watch: {
options: function (options) {
// update options
$(this.$el).select2({ data: options })
},
value: function (value) {
if ([...value].sort().join(",") !== [...$(this.$el).val()].sort().join(","))
$(this.$el).val(value).trigger('change');
}
},

Backbone subview is not rendered, "Uncaught ReferenceError: view is not defined" error returned

I am working on a site that uses Backbone.js, jQuery and I am trying to render a subview that has to display a description of the current page, loaded depending on a choice made from a dropdown menu. I searched for more info in the web but I am still stuck on this. Please help!
Here is the main view in which I have to load the description view:
const InquiryContentView = Backbone.View.extend(
{
el: $('#inquiryContent'),
events: {
'change #styles': 'renderTabs',
'click li.tab': 'renderTabPanel'
},
initialize: function () {
const view = this
this.inquiryContent = new InquiryContent
this.inquiryContent.fetch(
{
success: function () {
view.listenTo(view.inquiryContent, 'update', view.render)
view.render()
}
})
},
render: function () {
const data = []
this.inquiryContent.each(function (model) {
const value = {}
value.id = model.id
value.text = model.id
value.disabled = !model.get('active')
data.push(value)
})
data.unshift({id: 'none', text: 'Select One', disabled: true, selected: true})
this.$el.append('<h2 class="pageHeader">Inquiry Content</h2>')
this.$el.append('<select id="styles"></select>')
this.$stylesDropdown = $('#styles')
this.$stylesDropdown.select2(
{
data: data,
dropdownAutoWidth: true,
width: 'element',
minimumResultsForSearch: 10
}
)
this.$el.append('<div id="navWrapper"></div>')
this.$el.append('<div id="tNavigation"></div>')
this.$navWrapper = $('#navWrapper')
this.$tNavigation = $('#tNavigation')
this.$navWrapper.append(this.$tNavigation)
this.$el.append('<div id="editorDescription"></div>')
},
renderTabs: function (id) {
const style = this.inquiryContent.findWhere({id: id.currentTarget.value})
if (this.clearTabPanel()) {
this.clearTabs()
this.tabsView = new TabsView({style: style})
this.$tNavigation.append(this.tabsView.el)
}
},
renderTabPanel (e) {
const tabModel = this.tabsView.tabClicked(e.currentTarget.id)
if (tabModel && this.clearTabPanel()) {
this.tabPanel = new TabPanelView({model: tabModel})
this.$tNavigation.append(this.tabPanel.render().el)
}
},
clearTabs: function () {
if (this.tabsView !== undefined && this.tabsView !== null) {
this.tabsView.remove()
}
},
clearTabPanel: function () {
if (this.tabPanel !== undefined && this.tabPanel !== null) {
if (this.tabPanel.dataEditor !== undefined && this.tabPanel.dataEditor.unsavedChanges) {
if (!confirm('You have unsaved changes that will be lost if you leave the page. '
+ 'Are you sure you want to leave the page without saving your changes?')) {
return false
}
this.tabPanel.dataEditor.unsavedChanges = false
}
this.tabPanel.remove()
}
return true
}
}
)
I am trying to render the subview adding this method:
renderDescription () {
this.$editorDescription = $('#editorDescription')
this.descView = new DescView({model: this.model})
this.$editorDescription.append(this.descView)
this.$editorDescription.html(DescView.render().el)
}
It has to be rendered in a div element with id='editorDescription'
but I receive Uncaught ReferenceError: DescView is not defined
Here is how DescView is implemented:
window.DescView = Backbone.View.extend(
{
el: $('#editorDescription'),
initialize: function () {
_.bindAll(this, 'render')
this.render()
},
render: function () {
$('#editorDescriptionTemplate').tmpl(
{
description: this.model.get('description')})
.appendTo(this.el)
}
);
What am I doing wrong?
Your implementation of the DescView is incomplete. You are missing a bunch of closing braces and brackets. That's why it's undefined.

How to add render and create methods to Selectize element

I have a HTML 'select' element that I want to use as 'AutoSuggest' by using Selectize.js and this is how I initialize the selectize
jQuery(ele).selectize({
//options: initData,
addPrecedence: false,
persist: false,
maxItems: 1,
create: function (input) {
return {
value: input,
text: input
};
},
render: {
option_create: function (data, escape) {
return '<div class="create"><strong>' + escape(data.input) + '</strong></div>';
}
}
});
Now, the issue is if the 'ele' is already initialized as a 'Selectize' control without the 'render' and 'create' options, how can I add these options?
I figured it out. Here is how you do this
if (ele.selectize) {
var selectizeCtrl = jQuery(ele)[0].selectize;
selectizeCtrl.settings.create = function (input) {
return {
value: input,
text: input
};
};
selectizeCtrl.settings.render.option_create = function (data, escape) {
return '<div class="create"><strong>' + escape(data.input) + '</strong></div>';
};
}

Sencha store's each function the console.log not work

I want to display the records, but when I test it to display the data on console use record.get(''), it not work . even I tap the static code console.log('some thing'). It also cant display on my console.
The code in my controller:
it near the //-------here it is
Ext.define('ylp2p.controller.addtab',{
extend: 'Ext.app.Controller',
config: {
refs: {
myTabPanel: '.makemoney #tabfirst',
},
control: {
myTabPanel: {
activate: 'onActivateTabPanel',
activeitemchange: 'onActiveItemChangeTabPanel'
}
}
},
launch: function(){
var products = Ext.getStore('productstore');
products.filterBy(function(record, id){
return record.get('loanname') === 'xixi22';
});
},
onActivateTabPanel: function(newActiveItem, viewport, oldActiveItem, eOpts) {
//test
console.log('hello the activatetabpanel is fire!');
//end test success
var tabs = Ext.getStore('loanliststore');
tabs.each(function(record) {
console.log('hello come on');//---------------------here it is
newActiveItem.add({
title: record.get('loanname')
});
console.log('');
});
},
onActiveItemChangeTabPanel: function(cmp, value, oldValue, eOpts) {
console.log('hello this is the activechangepanel is fire!');
var products = value.getStore();
products.clearFilter(true);
products.filterBy(function(record, id) {
return record.get('loanname') === value.getTitle();
});
}
});
Check by tabs.getCount() if it is greater then 0 then it should work. If not means there is no data populated in your store.

Categories

Resources