Cannot use jquery plugin inside VueJS component - javascript

I have worked with VueJS for a while, and it is great. I have been able to integrate it with jQueryUI (for an old looking website) and I created a datepicker component, and a datetime picker component as well, both working correctly.
Now I am trying to create a simple phone number component, which simply provides an input with a mask that helps with the phone number format. The plugin for jquery that provides the masking, works correctly on it's own, but if I try to mask an input inside my component, it does not work.
Here is the example code in jsfiddle:
Simple Masked Phone input component for vuejs 2.4.0 - jsfiddle
Javascript:
Vue.component('phone', {
template: '#phone',
props: {
value : {
type : String,
default: ''
},
mask: {
type : String,
default: '(999) 999-9999'
}
},
data: function() {
return {
internalValue: ''
};
},
created: function() {
this.internalValue = $.trim(this.value);
},
mounted: function() {
$(this.$el).find('.phone:eq(0)').mask('(999) 999-9999');
},
methods: {
updateValue: function (value) {
this.$emit('input', value);
}
}
});
var vueapp = new Vue({
el: '#content',
data: {
myphone: ''
}
});
$('.phonex').mask('(999) 999-9999');
HTML:
<div id="content">
<script type="text/x-template" id="phone">
<input type="text" class="phone" v-model="internalValue" v-on:input="updateValue($event.target.value)" />
</script>
<label>Vue Phone</label>
<phone v-model="myphone"></phone>
<br />
{{ myphone }}
<br />
<label>Simple Phone</label>
<input type="text" class="phonex" />
</div>
This is what I see:
Dependencies:
jquery-2.2.4.min.js
jquery.maskedinput.min.js (1.4.1)
vue.js (2.4.0)
Is there anything I am doing wrong here? Thanks.

You don't need the .find('.phone:eq(0)') in your jquery, removing it seems to fix the masking (as shown here), though this does seem to mess with Vue's data binding.
After doing a bit more digging it looks like this is a known issue.
And is addressed here:
Vue is a jealous library in the sense that you must let it completely
own the patch of DOM that you give it (defined by what you pass to
el). If jQuery makes a change to an element that Vue is managing, say,
adds a class to something, Vue won’t be aware of the change and is
going to go right ahead and overwrite it in the next update cycle.
The way to fix this is to add the event handler when you call .mask on the element.
So for example:
mounted: function() {
var self = this;
$(this.$el).mask('(999) 999-9999').on('keydown change',function(){
self.$emit('input', $(this).val());
})
},
Here is the fiddle with the fix: https://jsfiddle.net/vo9orLx2/

Related

Vue.js - Transfer focus to component

I have a form fragment wrapped in a component that is hidden by v-if. When the user clicks a button, it toggles the boolean, revealing the hidden component, and when that happens I'd like to transfer focus to the first form input on the fragment.
I've tried using aria-live to no avail. I suspect the nature of the SPA interferes with the registration of those live regions (meaning my guess is that they must be registered when the page is rendered, as they don't seem responsive when injected into the DOM). I did not however, chase the answer down a rabbit hole so that is speculative. So then I added a class to the target input and tried to use HTMLElement.focus()
document.querySelector('.focus')[0].focus();
This also did not work. Does anyone know of a reason why I cannot seem to focus on an element that was recently injected into the DOM and is visible on the page at the time?
I think what's needed is for your form component to have something defined for when it's mounted:
Vue.config.productionTip = false;
const template = `<div>
<div>
<inner v-if="showInner" />
<button #click="toggle">Toggle inner component</button>
</div>
</div>`
const inner = {
name: 'inner',
template: '<form><input ref="textInput" type="text"/></form>',
mounted() {
this.$refs.textInput.focus()
}
};
new Vue({
template,
data: function() {
return {
showInner: false
};
},
methods: {
toggle() {
this.showInner = !this.showInner;
}
},
components: {
inner
}
}).$mount("#app");
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"></div>

How do I toggle the contents of an input filed to be encrypted and non encrypted in Vue?

I have an input field and I want the contents to read 'ab****gh' and be able to toggle the contents with a click to read 'abcdefgh'. Basically a reveal and not reveal. I'm having trouble making the value reactive when I change it. Below is some partial code that I've been working with.
Basically i'm trying to swap the content of the input with the encrypted value
Can anyone see where I'm going wrong?
regex_hide_characters: /(?<!^).(?!$)/g,
inputValue: this.value,
encryptedInputValue: this.value.replace(this.regex_hide_characters, '*'),
hidePrivateContent() {
this.reveal = !this.reveal;
if (!this.reveal) {
this.$refs.input.value = this.encryptedInputValue;
}
},
Here is a very basic sample of how you'd achieve something like this:
new Vue({
el: '#app',
computed: {
hiddenPass() {
if (this.isPass) return this.pass.slice(0, 2) + '*******';
return this.pass;
}
},
data() {
return {
isPass: true,
pass: 'abc124defg'
}
},
methods: {}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app" v-cloak>
<div>{{hiddenPass}}</div>
<button #click="isPass=!isPass">toggle view</button>
</div>
In the snippet, I'm taking advantage of computed properties in order to determine, based on data on the instance, how to show the "protected" pass.
I hope this helps!

Are Vue.js and debounce (lodash/underscore) compatible?

Following an answer to my question on debouncing I am wondering if vue.js and lodash/underscore are compatible for this functionality. The code in the answer
var app = new Vue({
el: '#root',
data: {
message: ''
},
methods: {
len: _.debounce(
function() {
return this.message.length
},
150 // time
)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message">Length: <span>{{ len() }}</span>
</div>
indeed holds on the execution of my function when there is continuous input, but when it is finally executed after some inactivity, the input for function() seems to be wrong.
A practical example after starting the code above:
quick sequence of characters, then no activity:
One extra character (b) added, and no activity -- the length is updated (but wrongly, see below)
Erase all the characters with Backspace in a quick sequence:
Add one character:
It looks like the function is ran on the last but one value of message.
Could it be that _.debounce handles the vue.js data before it is actually updated with the <input> value?
Notes:
tested with both lodash and underscore, with the same results (for both debounceand throttle functions).
I also tested it on JSFiddle in case there would be some interference with the SO snippet
Here's an improved version of #saurabh's version.
var app = new Vue({
el: '#root',
data: {
message: '',
messageLen: 0
},
methods: {
updateLen: _.debounce(
function() {
this.messageLen = this.message.length
}, 300)
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message" v-on:keyup="updateLen">Length: <span>{{ messageLen }}</span>
</div>
Why this is happening is because Vue invokes methods only when vue variables used in the methods changes, if there are no changes in the vue varaibles, it will not trigger those methods.
In this case as well, once we stop typing, it will continue to show last called method's output and will only show again once you enter the input again.
One alternate approach if you dont want to call an function on all inputs, you can call a mehtod on blur event, so method will be invoked only when focus goes out of input field, like following:
var app = new Vue({
el: '#root',
data: {
message: '',
messageLen: 0
},
methods: {
updatateLen:
function() {
this.messageLen = this.message.length
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.6/vue.js"></script>
<script src="https://unpkg.com/underscore#1.8.3"></script> <!-- undescore import -->
<div id="root">
<input v-model="message" v-on:blur="updatateLen">Length: <span>{{ messageLen }}</span>
</div>

How to fire function after v-model change?

I have input which I use to filter my array of objects in Vue. I'm using Salvattore to build a grid of my filtered elements, but it doesn't work too well. I think I have to call rescanMediaQueries(); function after my v-model changes but can't figure how.
Here is my Vue instance:
var articlesVM = new Vue({
el: '#search',
data: {
articles: [],
searchInput: null
},
ready: function() {
this.$http.get('posts').then(function (response) {
this.articles = response.body;
});
}
});
And here is how I have built my search
<div class="container" id="search">
<div class="input-field col s6 m4">
<input v-model="searchInput" class="center-align" id="searchInput" type="text" >
<label class="center-align" for="searchInput"> search... </label>
</div>
<div id="search-grid" v-show="searchInput" data-columns>
<article v-for="article in articles | filterBy searchInput">
<div class="card">
<div class="card-image" v-if="article.media" v-html="article.media"></div>
<div class="card-content">
<h2 class="card-title center-align">
<a v-bind:href="article.link">{{ article.title }}</a>
</h2>
<div class="card-excerpt" v-html="article.excerpt"></div>
</div>
<div class="card-action">
<a v-bind:href="article.link"><?php _e('Read More', 'sage'); ?></a>
</div>
</div>
</article>
</div>
I did get the grid system working by adding watch option to my Vue, but every time I wrote something to my input and then erase it my filterBy method wouldn't work at all. It didn't populate any data even if I tried to retype the same keyword as earlier. Here is the watch option I used:
watch: {
searchInput: function (){
salvattore.rescanMediaQueries();
}
}
I think your problem is with the scoping of this in your success handler for http. Your articles object in Vue component is not getting any values from your http.get(..) success handler.
Inside your ready function, your http success handler should be as follows:
this.$http.get('posts').then(response => {
this.articles = response.body; // 'this' belongs to outside scope
});`
Alternatively you can also do:
var self = this; // self points to 'this' of Vue component
this.$http.get('posts').then(response => {
self.articles = response.body; // 'self' points to 'this' of outside scope
});`
Another similar issue: https://stackoverflow.com/a/40090728/654825
One more thing - it is preferable to define data as a function, as follows:
var articlesVM = new Vue({
el: '#search',
data: function() {
return {
articles: [],
searchInput: null
}
},
...
}
This ensures that your articles object is unique to this instance of the component (when you use the same component at multiple places within your app).
Edited after comment #1
The following code seems to work alright, the watch function works flawlessly:
var vm = new Vue({
el: '#search',
template: `<input v-model="searchInput" class="center-align" id="searchInput" type="text" >`,
data: {
searchInput: ""
},
watch: {
searchInput: function() {
console.log("searchInput changed to " + this.searchInput);
}
}
})
The input in template is an exact copy of your version - I have even set the id along with v-model, though I do not see the reason to set an id
Vue.js version: 2.0.3
I am unable to see any further, based on details in the question. Can you check if your code matches with the one above and see if you can get the console debugging messages?
Edited after comment #4, #5
Here is another thought which you need to verify:
Role of vue.js: Render the DOM
Role of salvattore plugin: Make the DOM layouts using CSS only
Assuming the above is true for salvattore plugin, and hopefully it does not mess with vue.js observers / getters / setters, then you can do the following: provide a time delay of about 50 ms so that vue.js completes the rendering, and then call the salvattore plugin to perform the layouts.
So your watch function needs to be as follows:
watch: {
searchInput: function (){
setTimeout(function(){
salvattore.rescanMediaQueries();
}, 50);
}
}
Alternatively you may also use Vue.nexttick() as follows:
Vue.nextTick(function () {
// DOM updated
})
The nextTick is documented here: https://vuejs.org/api/#Vue-nextTick
I do not know if you may need to provide a little bit of extra time for salvattore plugin to start the layouts, but one of the above should work out.
Let me know if it works!

Marionette.js - can I detect onAppend?

I have a silly problem, where my only solution is a sloppy hack that is now giving me other problems.
See my fiddle,
or read the code here:
HTML:
<input id='1' value='input1' />
<template id='template1'>
<input id='2' value='input2' />
</template>
JS - Item View Declaration:
// Declare an ItemView, a simple input template.
var Input2 = Marionette.ItemView.extend({
template: '#template1',
onRender: function () {
console.log('hi');
},
ui: { input2: '#2' },
onRender: function () {
var self = this;
// Despite not being in the DOM yet, you can reference
// the input, through the 'this' command, as the
// input is a logical child of the ItemView.
this.ui.input2.val('this works');
// However, you can not call focus(), as it
// must be part of the DOM.
this.ui.input2.focus();
// So, I have had to resort to this hack, which
// TOTALLY SUCKS.
setTimeout(function(){
self.ui.input2.focus();
self.ui.input2.val('Now it focused. Dammit');
}, 1000)
},
})
JS - Controller
// To start, we focus input 1. This works.
$('#1').focus();
// Now, we make input 2.
var input2 = new Input2();
// Now we 1. render, (2. onRender is called), 3. append it to the DOM.
$(document.body).append(input2.render().el);
As one can see above, my problem is that I can not make a View call focus on itself after it is rendered (onRender), as it has not yet been appended to the DOM. As far as I know, there is no other event called such as onAppend, that would let me detect when it has actually been appended to the DOM.
I don't want to call focus from outside of the ItemView. It has to be done from within for my purposes.
Any bright ideas?
UPDATE
Turns out that onShow() is called on all DOM appends in Marionette.js, be it CollectionView, CompositeView or Region, and it isn't in the documentation!
Thanks a million, lukaszfiszer.
The solution is to render your ItemView inside a Marionette.Region. This way an onShow method will be called on the view once it's inserted in the DOM.
Example:
HTML
<input id='1' value='input1' />
<div id="inputRegion"></div>
<template id='template1'>
<input id='2' value='input2' />
</template>
JS ItemView
(...)
onShow: function () {
this.ui.input2.val('this works');
this.ui.input2.focus();
},
(...)
JS Controller
$('#1').focus();
var inputRegion = new Backbone.Marionette.Region({
el: "#inputRegion"
});
var input2 = new Input2();
inputRegion.show(input2);
More information in Marionette docs: https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.region.md#region-events-and-callbacks
Well, I managed to solve it by extending Marionette.js, but if anyone else has a better idea that doesn't involve extending a library, I will GLADLY accept it and buy you a doughnut.
// After studying Marionette.js' annotated source code,
// I found these three functions are the only places
// where a view is appended after rendering. Extending
// these by adding an onAppend call to the end of
// each lets me focus and do other DOM manipulation in
// the ItemView or Region, once I am certain it is in
// the DOM.
_.extend(Marionette.CollectionView.prototype, {
appendHtml: function(collectionView, itemView, index){
collectionView.$el.append(itemView.el);
if (itemView.onAppend) { itemView.onAppend() }
},
});
_.extend(Marionette.CompositeView.prototype, {
appendHtml: function(cv, iv, index){
var $container = this.getItemViewContainer(cv);
$container.append(iv.el);
if (itemView.onAppend) { itemView.onAppend() }
},
});
_.extend(Marionette.Region.prototype, {
open: function(view){
this.$el.empty().append(view.el);
if (view.onAppend) { view.onAppend() }
},
});

Categories