set watcher on props of vuejs component - laravel

I have created datepicker vuejs component.
I have set two date picker in page to select start date and end date. So startDate and endDate of one datepicker is depend on value of another. So for that, I need to set watcher on selected value.
It was working fine with VueJS (< 2.3.0) using this.$emit('input', value). But in VueJS (2.4.4), it is not working.
I'm very poor in front-end development. So I got nothing even after spending two days on vuejs. I just come to know that it is just because of vue version > 2.2.6.
If anyone know the solution, it will be realy very much helpful for me.
Here is my code.
DatePicker.vue
<template>
<div class="input-append date form_datetime">
<input size="16" type="text" readonly>
<span class="add-on"><i class="icon-th fa fa-calendar"></i></span>
</div>
</template>
<script>
export default {
twoWay: true,
props: ['slctd', 'startDate', 'endDate'],
mounted: function () {
var self = this;
$(this.$el).datetimepicker({
format: "mm/dd/yyyy",
autoclose: true,
minView: 2,
maxView: 3,
daysShort: true,
initialDate: this.slctd,
startDate: this.startDate,
endDate: this.endDate,
}).on('changeDate', function (ev) {
var value = new Date(ev.date.valueOf()).toString('MM/dd/yyyy');
self.$emit('input', value);
});
},
watch: {
slctd: function (value) {
console.log('watch:value ' + value);
$(this.$el).find("input").val(value);
},
startDate: function (value) {
console.log('watch:start ' + value);
$(this.$el).datetimepicker('setStartDate', value);
},
endDate: function (value) {
console.log('watch:end ' + value);
$(this.$el).datetimepicker('setEndDate', value);
}
},
}
</script>
JS
window.Vue = require('vue');
Vue.component('component-date-picker', require('./components/DatePicker.vue'));
window.app = new Vue({
el: '#app',
data: {
queries: {
start_date: '10/04/2017',
end_date: '10/05/2017',
}
},
});
HTML
<component-date-picker v-bind:end-date="queries.end_date" v-bind:slctd="queries.start_date" v-model="queries.start_date"></component-date-picker>
<component-date-picker v-bind:start-date="queries.start_date" v-bind:slctd="queries.end_date" v-model="queries.end_date"></component-date-picker>
VueJS - 2.2.6 (working)
VueJS - 2.4.4 (not working)
I have also tried this too. But didn't get success
HTML
<component-date-picker v-bind:end-date="queries.end_date" v-bind:slctd="queries.start_date" v-model="queries.start_date" v-on:input="set_start_date"></component-date-picker>
<component-date-picker v-bind:start-date="queries.start_date" v-bind:slctd="queries.end_date" v-model="queries.end_date" v-on:input="set_end_date"></component-date-picker>
JS
methods: {
set_start_date: function(value){
console.log('called');
this.queries.start_date = value;
},
set_end_date: function(value){
this.queries.end_date = value;
},
}
In this code, I method was called and I was getting 'called' in console. But watcher of component not working. It should work as startDate/endDate is changed in these methods. I don't know what is the new working concept of VueJS 2.4.4.
Solved
It was my stupid mistake due to lack of knowledge in front-end. I was re-creating queries object in create method of vue root instance. And in previous code, there was nothing in create so it was working fine.
Here is my mistake
JS
created: function () {
/* my mistake starts */
this.queries = queryString.parse(location.search, {arrayFormat: 'bracket'});
/* my mistake ends */
this.queries.start_date = (this.queries.start_date) ? this.queries.start_date : null;
this.queries.end_date = (this.queries.end_date) ? this.queries.end_date : null;
},

Related

Render content with Vue syntax / component string through AJAX call?

I have this HTML pattern:
<div id="New"> == ajax loaded content == </div>
It was easy to render HTML at server side and use innerHTML to inject the content into the right place.
Now I am trying to use Vue.js to do the same thing but render HTML at the client side. I can make this pattern into a component, let's say componentA, with template:
componentA
template:
`<div><slot></slot></div>`
It works if the HTML page content is something like:
<componentA>
<componentB></componentB> and some other none component content
</componentA>
The componentB is rendered and replaced the slot in componentA.
The problem is how do I use AJAX call (the call is made outside of componentA) to load
<componentB></componentB> and some other none component content
into the slot of componentA, and still make componentB to render correctly?
In real situation, the content from AJAX call can be
<componentB>, <componentC>, <componentD> ...
The following will treat componentB as regular string
in HTML:
<componentA>
<div id="New"></div>
</componentA>
in JS:
document.getElementById('New').innerHTML =
'<componentB></componentB> And some other none component content';
Is there a proper way to render string from AJAX return with Vue syntax as Vue?
One solution is put the ajax response like <component></component> to Component.template inside render function (Vue Guide: Render Function).
Like below demo:
const Foo = Vue.component('foo', {template: '<p>Foo - {{flag}}</p>', props: ['flag']})
const Bar = Vue.component('bar', {template: '<p>Bar - {{flag}}</p>', props: ['flag']})
const Generic = Vue.component('generic', {
render: function (createElement) {
return createElement('div', [
createElement('h3', 'Title'),
createElement('button', {on: {click: this.loadComponent}}, 'Load Component'),
this.dynamicComponent
&& createElement(Vue.component('v-fake-slot', {template:this.dynamicComponent, props: ['flag']}), {
props: {
flag: this.parent
}
})
])
},
props: ['parent'],
data () {
return {
components: ['<foo :flag="flag"></foo>', '<bar :flag="flag"></bar>'],
index: 0,
dynamicComponent: ''
}
},
methods: {
loadComponent: function () {
setTimeout(() => {
this.index += 1
this.dynamicComponent = this.components[this.index % 2]
}, 1000)
}
}
})
new Vue({
el: '#app',
data () {
return {
test: 'root'
}
}
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<generic :parent="test"></generic>
</div>

Remove button after 30 minutes. Laravel

I'm making a discussion forum and I want to remove the users ability to edit the comment they made after 30 mins.
This is the code for my button in the vue.js, it's not a "real" button, it's a clickable icon
<div class="btn-link-edit action-button"
#click="edit(comment)">
<i class="fas fa-pencil-alt"></i>
</div>
method in vue.js
edit(model) {
this.mode = 'Editar';
this.form = _.cloneDeep(model);
this.dialogFormVisible = true;
},
What would be the best way to add this timer, the timer should start right when the user makes the comment, in the table for this I have a field called comment_time with that information.
How can I do this?
The simplest way to do that is:
Here in template:
<div id="app">
<div v-for="comment in comments">
<p>
{{comment.text}}
</p>
<button v-if="commentTime(comment.comment_time)">Edit </button>
</div>
</div>
Vue script:
new Vue({
el: "#app",
data: {
comments: [
{ text: "Nancy comment", comment_time: 1579206552201}
]
},
computed: {
now () {
return new Date()
}
},
methods: {
commentTime(cTime){
let t = new Date(cTime)
t.setMinutes(t.getMinutes() + 30)
return this.now.getTime() < t.getTime()
}
}
})
You can show the result here:
your code in jsfiddle
First, start by putting v-if="canEdit" on your <div>. Then in your script section of the Vue component we're going to create a canEdit boolean, then a loop to update it on a regular basis. This assumes you have a specific Comment.vue component and this.comment is being passed to the component already, maybe as a prop or something, and that it contains the typical Laravel Model fields, specifically created_at.
data() {
return {
canEdit: true, // Defaults to `true`
checkTimer: null, // Set the value in `data` to help prevent a memory leak
createdAt: new Date(this.comment.created_at),
};
},
// When we first make the component, we set `this.createdPlus30`, then create the timer that checks it on a regular interval.
created() {
this.checkTimer = setInterval(() => {
this.canEdit = new Date() > new Date(this.created_at.getTime() + 30*60000);
}, 10000); // Checks every 10 seconds. Change to whatever you want
},
// This is a good practice whenever you create an interval timer, otherwise it can create a memory leak.
beforeDestroy() {
clearInterval(this.checkTimer);
},

Unknown custom element: - did you register the component correctly?

I'm new to vue.js so I know this is a repeated issue but cannot sort this out.
the project works but I cannot add a new component. Nutrition component works, profile does not
My main.js
import Nutrition from './components/nutrition/Nutrition.vue'
import Profile from './components/profile/Profile.vue'
var Vue = require('vue');
var NProgress = require('nprogress');
var _ = require('lodash');
// Plugins
Vue.use(require('vuedraggable'));
// Components
Vue.component('nutrition', Nutrition);
Vue.component('profile', Profile);
// Partials
Vue.partial('payment-fields', require('./components/forms/PaymentFields.html'));
// Filters
Vue.filter('round', function(value, places) {
return _.round(value, places);
});
Vue.filter('format', require('./filters/format.js'))
// Transitions
Vue.transition('slide', {enterClass: 'slideInDown', leaveClass: 'slideOutUp', type: 'animation'})
// Send csrf token
Vue.http.options.headers['X-CSRF-TOKEN'] = Laravel.csrfToken;
// Main Vue instance
new Vue({
el: '#app',
components: {
},
events: {
progress(progress) {
if (progress === 'start') {
NProgress.start();
} else if (progress === 'done') {
NProgress.done();
} else {
NProgress.set(progress);
}
},
'flash.success': function (message) {
this.$refs.flash.showMessage(message, 'success');
},
'flash.error': function (message) {
this.$refs.flash.showMessage(message, 'error');
}
}
});
Profile.vue
<template>
<div class="reddit-list">
<h3>Profile </h3>
<ul>
</ul>
</div>
</template>
<script type="text/babel">
export default {
name: 'profile', // this is what the Warning is talking about.
components: {
},
props: {
model: Array,
}
}
</script>
profile.blade.php
#extends('layouts.app')
#section('title', 'Profile')
#section('body-class', 'profile show')
#section('content')
<script>
window.Laravel.profileData = []
</script>
<profile></profile>
#endsection
Whenever I try to go to this page I get:
[Vue warn]: Unknown custom element: <profile> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
I tried doing a local component such as
Vue.components('profile', {
template: '<div>A custom component!</div>'
});
or even I tried adding the profile into the components in vue but still no luck, can anyone point me in the right direction?
Simply clear the cache on your browser if you run into this problem. Worked pretty well for me
I didn't fixed it but it was fixed by itself it appears some kind of magic called (CACHE). i did have my gulp watch running but i powered off my computer, and then ON again and it works.

VueJS 2 typeahead callback method

I'm using the Typeahead component from vue-strap,
Am trying to make a callback after i hit on the selection which will stamp the data into my input texts.
Once i hit enter, inventoryName and inventoryId is successfully stamped in but the inventoryCode component is reset.
How to prevent it from reset or did i do anything wrong? As i can see from console.log it has the value.
<typeahead v-model="inventoryCode" placeholder="Inventory code..." async="{{url('api/inventory')}}/" :template="inventorySearch" :on-hit="inventoryCallBack"></typeahead>
<input type="text" name="inventory_name" id="inventory_name" class="form-control" v-model="inventoryName" readonly />
new Vue({
el: '#app',
components:{Typeahead},
data: {
'inventoryCode': '',
'inventoryName': '',
'inventoryId': '',
'inventorySearch': '<div style="min-width:150px;"><b>#{{item.inventory_name}}</b></div><div>#{{item.inventory_code}}</div><div>#{{item.inventory_short_desc}}</div>'
},
methods:{
inventoryCallBack: function(item){
console.log(item.inventory_code);
this.inventoryCode = item.inventory_code;
this.inventoryName = item.inventory_name;
this.inventoryId = item.id;
console.log(this.inventoryCode);
},
}
});
inventoryCallBack: function(item){
console.log(item.inventory_code);
this.inventoryCode = item.inventory_code;
this.inventoryName = item.inventory_name;
this.inventoryId = item.id;
return item.inventory_code
},
This should do the trick, missed out this part.

Vue.js Retrieving Remote Data for Options in Select2

I'm working on a project that is using Vue.js and Vue Router as the frontend javascript framework that will need to use a select box of users many places throughout the app. I would like to use select2 for the select box. To try to make my code the cleanest I can, I've implemented a custom filter to format the data the way select2 will accept it, and then I've implemented a custom directive similar to the one found on the Vue.js website.
When the app starts up, it queries the api for the list of users and then stores the list for later use. I can then reference the users list throughout the rest of the application and from any route without querying the backend again. I can successfully retrieve the list of users, pass it through the user list filter to format it the way that select2 wants, and then create a select2 with the list of users set as the options.
But this works only if the route that has the select2 is not the first page to load with the app. For example, if I got to the Home page (without any select2 list of users) and then go to the Users page (with a select2), it works great. But if I go directly to the Users page, the select2 will not have any options. I imagine this is because as Vue is loading up, it sends a GET request back to the server for the list of users and before it gets a response back, it will continues with its async execution and creates the select2 without any options, but then once the list of users comes back from the server, Vue doesn't know how to update the select2 with the list of options.
Here is my question: How can I retrieve the options from an AJAX call (which should be made only once for the entire app, no matter how many times a user select box is shown) and then load them into the select2 even if the one goes directly to the page with the select2 on it?
Thank you in advance! If you notice anything else I should be doing, please tell me as I would like this code to use best practices.
Here is what I have so far:
Simplified app.js
var App = Vue.extend({
ready: function() {
this.fetchUsers();
},
data: function() {
return {
globals: {
users: {
data: []
},
}
};
},
methods: {
fetchUsers: function() {
this.$http.get('./api/v1/users/list', function(data, status, response) {
this.globals.users = data;
});
},
}
});
Sample response from API
{
"data": [
{
"id": 1,
"first_name": "John",
"last_name": "Smith",
"active": 1
},
{
"id": 2,
"first_name": "Emily",
"last_name": "Johnson",
"active": 1
}
]
}
User List Filter
Vue.filter('userList', function (users) {
if (users.length == 0) {
return [];
}
var userList = [
{
text : "Active Users",
children : [
// { id : 0, text : "Item One" }, // example
]
},
{
text : "Inactive Users",
children : []
}
];
$.each( users, function( key, user ) {
var option = { id : user.id, text : user.first_name + ' ' + user.last_name };
if (user.active == 1) {
userList[0].children.push(option);
}
else {
userList[1].children.push(option);
}
});
return userList;
});
Custom Select2 Directive (Similar to this)
Vue.directive('select', {
twoWay: true,
bind: function () {
},
update: function (value) {
var optionsData
// retrive the value of the options attribute
var optionsExpression = this.el.getAttribute('options')
if (optionsExpression) {
// if the value is present, evaluate the dynamic data
// using vm.$eval here so that it supports filters too
optionsData = this.vm.$eval(optionsExpression)
}
var self = this
var select2 = $(this.el)
.select2({
data: optionsData
})
.on('change', function () {
// sync the data to the vm on change.
// `self` is the directive instance
// `this` points to the <select> element
self.set(select2.val());
console.log('emitting "select2-change"');
self.vm.$emit('select2-change');
})
// sync vm data change to select2
$(this.el).val(value).trigger('change')
},
unbind: function () {
// don't forget to teardown listeners and stuff.
$(this.el).off().select2('destroy')
}
})
Sample Implementation of Select2 From Template
<select
multiple="multiple"
style="width: 100%"
v-select="criteria.user_ids"
options="globals.users.data | userList"
>
</select>
I may have found something that works alright, although I'm not sure it's the best way to go about it. Here is my updated code:
Implementation of Select2 From Template
<select
multiple="multiple"
style="width: 100%"
v-select="criteria.reporting_type_ids"
options="globals.types.data | typeList 'reporttoauthorities'"
class="select2-users"
>
</select>
Excerpt from app.js
fetchUsers: function() {
this.$http.get('./api/v1/users/list', function(data, status, response) {
this.globals.users = data;
this.$nextTick(function () {
var optionsData = this.$eval('globals.users.data | userList');
console.log('optionsData', optionsData);
$('.select2-users').select2({
data: optionsData
});
});
});
},
This way works for me, but it still kinda feels hackish. If anybody has any other advice on how to do this, I would greatly appreciate it!
Thanks but I'm working on company legacy project, due to low version of select2, I encountered this issue. And I am not sure about the v-select syntax is from vue standard or not(maybe from the vue-select libaray?). So here's my implementation based on yours. Using input tag instead of select tag, and v-model for v-select. It works like a charm, thanks again #bakerstreetsystems
<input type="text"
multiple="multiple"
style="width: 300px"
v-model="supplier_id"
options="suppliers"
id="select2-suppliers"
>
</input>
<script>
$('#app').ready(function() {
var app = new Vue({
el: "#app",
data: {
supplier_id: '<%= #supplier_id %>', // We are using server rendering(ruby on rails)
suppliers: [],
},
ready: function() {
this.fetchSuppliers();
},
methods: {
fetchSuppliers: function() {
var self = this;
$.ajax({
url: '/admin_sales/suppliers',
method: 'GET',
success: function(res) {
self.suppliers = res.data;
self.$nextTick(function () {
var optionsData = self.suppliers;
$('#select2-suppliers').select2({
placeholder: "Select a supplier",
allowClear: true,
data: optionsData,
});
});
}
});
},
},
});
})
</script>

Resources