How to add components programatically in vuejs (option api) - vue.js

I am creating a map app using vue-leaflet and I'm trying to add paths when clicked on map.
I have everything set up but don't know how to add that l-polyline option in my template during runtime.
I tried to add it with DOM Api but it is taking it as it is without converting it to html
I want to add my component inside <l-map></l-map>
this is my map initialization
<l-map
class="mainMap"
ref="map"
v-if="isLocAvailable"
v-model="zoom"
v-model:zoom="zoom"
:center="currLocation"
#zoom="zoomUpperBound"
:options="{ zoomControl: false, maxZoom: 18 }"
#click="getCurrLoc"
>
and this is my getCurrLoc:
getCurrLoc(e) {
try {
const { lat, lng } = e.latlng;
if (this.onCreateMode === true) {
this.locations.push([lat, lng]);
}
} catch (err) {
console.log("what is err");
}
},
and i also have function for creating polyline in which i want to add that path adding functionality
createPolyline() {
console.log(this.locations);
//Code
//Reset Inputs
this.pathName = "";
this.onCreateMode = false;
this.locations = [];
},

Related

How to preview image in element ui?

I am using element ui el-image. And I want to preview my image when clicked with (:preview-src-list). But When I click first time it doesnt preview anything. just add's my downloaded image. So I need to click 2 times. But I want to click 1 time.
Here is my template code:
<el-image :src="src"
:preview-src-list="srcList"
#click="imgClick"></el-image>
ts code:
src = null;
srcList = [];
product = 'shoe1';
imgClick() {
prevImg(product).then(resp => {
const url = window.URL.createObjectURL(new Blob([resp.data]));
this.srclist = [url];
});
}
#Watch("product")
changed(value) {
getProductImage(value).then(resp => {
const url = window.URL.createObjectURL(new Blob([resp.data]));
this.src = url;
}).catc(e => {
alert(e);
});
}
mounted() {
this.changed(product);
}
I think these things happen because when you click on that image it will trigger clickHandler:
...
clickHandler() {
// don't show viewer when preview is false
if (!this.preview) {
return;
}
...
}
...
From source
And the preview is the computed property:
...
preview() {
const { previewSrcList } = this;
return Array.isArray(previewSrcList) && previewSrcList.length > 0;
}
...
From source
So nothing happened in the first click but after that you set preview-src-list and click it again then it works.
If you code is synchronous you can use event like mousedown which will trigger before click event.
<el-image
:src="url"
:preview-src-list="srcList"
#mousedown="loadImages">
</el-image>
Example
But if you code is asynchronous you can use refs and call clickHandler after that.
...
// fetch something
this.$nextTick(() => {
this.$refs.elImage.clickHandler()
})
...
Example

Using dropzone.js in vue, calling function with image file name

I'm having a hard time getting anything to work with this the way I need it, but I have a working dropzone instance in my Vue project.
I can upload the image and call functions within the dropzone code, however, I need to call a function directly from the form in the html in order to send the 'card' object.
All I need to do is call a function when a file is added through the dropzone form, with the filename.
My code:
<div class="uk-width-3-10">
<form v-on:change="imageChange(card)" method="post" action="{{url('product/parts/upload/store')}}" enctype="multipart/form-data"
class="dropzone" v-bind:id="'dropzone-'+i">
</form>
</div>
...
imageChange(Card){
console.log('working');
},
addCard(){
Vue.nextTick(function () {
new Dropzone("#dropzone-"+cardIndex, {
maxFilesize: 12,
renameFile: function (file) {
var dt = new Date();
var time = dt.getTime();
return time + file.name;
},
acceptedFiles: ".jpeg,.jpg,.png,.gif",
addRemoveLinks: true,
timeout: 50000,
removedfile: function (file) {
console.log(file.upload.filename);
var name = file.upload.filename;
var fileRef;
return (fileRef = file.previewElement) != null ?
fileRef.parentNode.removeChild(file.previewElement) : void 0;
},
init: function() {
this.on("addedfile",
function(file) {
instance.imageZoneNames.push({name: file.upload.filename});
console.log(file);
console.log(instance.imageZoneNames);
});
}
});
});
}
Dropzone has many events, You used removedfile() event! there is another event called addedfile() and executes when a file is added to the dropzone list
imageChange(card) {
console.log(card)
},
addCard() {
Vue.nextTick(() => {
new Dropzone('#dropzone-` + cardIndex, {
addedfile(file) {
this.imageChange(file);
}
}
}
}

Vue template fires more than once when used, i think i need a unique key somewhere

I am trying to implement font-awesome-picker to a website that i am making using vue2/php/mysql, but within standard js scripting, so no imports, .vue etc.
The script i am trying to add is taken from here: https://github.com/laistomazz/font-awesome-picker
The problem that i am facing is that i have 3 columns that have a title and an icon picker next it, that will allow the user to select 1 icon for each title. It is kinda working well...but if the same icon is used in 2 different columns then any time the user clicks again any of the 2 icons both instances of the picker will fire up, thus showing 2 popups. I need to somehow make them unique.
I've tried using
:key="list.id"
or
v-for="icon in icons" :icon:icon :key="icon"
but nothing worked. Somehow i have to separate all the instances (i think) so they are unique.
This is the template code:
Vue.component('font-awesome-picker', {
template: ' <div><div class="iconPicker__header"><input type="text" class="form-control" :placeholder="searchPlaceholder" #keyup="filterIcons($event)" #blur="resetNew" #keydown.esc="resetNew"></div><div class="iconPicker__body"><div class="iconPicker__icons"><i :class="\'fa \'+icon"></i></div></div></div>',
name: 'fontAwesomePicker',
props: ['seachbox','parentdata'],
data () {
return {
selected: '',
icons,
listobj: {
type: Object
}
};
},
computed: {
searchPlaceholder () {
return this.seachbox || 'search box';
},
},
methods: {
resetNew () {
vm.addNewTo = null;
},
getIcon (icon) {
this.selected = icon;
this.getContent(this.selected);
},
getContent (icon) {
const iconContent = window
.getComputedStyle(document.querySelector(`.fa.${icon}`), ':before')
.getPropertyValue('content');
this.convert(iconContent);
},
convert (value) {
const newValue = value
.charCodeAt(1)
.toString(10)
.replace(/\D/g, '');
let hexValue = Number(newValue).toString(16);
while (hexValue.length < 4) {
hexValue = `0${hexValue}`;
}
this.selecticon(hexValue.toUpperCase());
},
selecticon (value) {
this.listobj = this.$props.parentdata;
const result = {
className: this.selected,
cssValue: value,
listobj: this.listobj
};
this.$emit('selecticon', result);
},
filterIcons (event) {
const search = event.target.value.trim();
let filter = [];
if (search.length > 3) {
filter = icons.filter((item) => {
const regex = new RegExp(search, 'gi');
return item.match(regex);
});
}else{
this.icons = icons;
}
if (filter.length > 0) {
this.icons = filter;
}
}
},
});
I've setup a fiddle with the problem here:
https://jsfiddle.net/3yxk1ahb/1/
Just pick the same icon in both cases, and then click any of the icons again. You'll see that the popups opens for both columns.
How can i separate the pickers ?
problem is in your #click and v-show
you should use list.id instead of list.icon (i.e #click="addNewTo = list.id")
working fiddle https://jsfiddle.net/q513mhwt/

Getting reactivity from watch in Vue.js

Trying to make a component in Vue.js, which first shows image via thumbnail, loading full image in background, and when loaded, show full image.
The thing which does not work, component does not react on change of showThumb flag in watch section. What is wrong?
Vue.component('page-image',
{
props: ['data'],
template:
'<img v-if="showThumb == true" v-bind:src="thumbSrc"></img>'+
'<img v-else v-bind:src="fullSrc"></img>',
data: function()
{
return { thumbSrc: '', fullSrc: '', showThumb: true };
},
watch:
{
data: function()
{
this.thumbSrc = data.thumbImg.url;
this.fullSrc = data.fullImg.url;
this.showThumb = true;
var imgElement = new Image();
imgElement.src = this.fullSrc;
imgElement.onload = (function()
{
this.showThumb = false; // <<-- this part is broken
} );
}
}
} );
Note: there is a reason why I do it via 2 img tags - this example is simplified.
Your onload callback will have a different scope than the surrounding watch function, so you cannot set your data property like this. Change it to an arrow function to keep scope:
imgElement.onload = () =>
{
this.showThumb = false;
};
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Vue 2 custom select2: why is #change not working while #input is working

I created a custom select2 input element for Vue 2.
My question is: why is
<select2 v-model="vacancy.staff_member_id" #input="update(vacancy)"></select2>
working, but
<select2 v-model="vacancy.staff_member_id" #change="update(vacancy)"></select2>
not?
Since normal <input> elements in Vue have a #change handler, it would be nice if my custom select2 input has the same.
Some information on my custom element:
The purpose of this element is to not render all <option> elements but only those needed, because we have many select2 inputs on one page and many options inside a select2 input, causing page load to become slow.
This solution makes it much faster.
Vue.component('select2', {
props: ['options', 'value', 'placeholder', 'config', 'disabled'],
template: '<select><slot></slot></select>',
data: function() {
return {
newValue: null
}
},
mounted: function () {
var vm = this;
$.fn.select2.amd.require([
'select2/data/array',
'select2/utils'
], function (ArrayData, Utils) {
function CustomData ($element, options) {
CustomData.__super__.constructor.call(this, $element, options);
}
Utils.Extend(CustomData, ArrayData);
CustomData.prototype.query = function (params, callback) {
if (params.term && params.term !== '') {
// search for term
var results;
var termLC = params.term.toLowerCase();
var length = termLC.length;
if (length < 3) {
// if only one or two characters, search for words in string that start with it
// the string starts with the term, or the term is used directly after a space
results = _.filter(vm.options, function(option){
return option.text.substr(0,length).toLowerCase() === termLC ||
_.includes(option.text.toLowerCase(), ' '+termLC.substr(0,2));
});
}
if (length > 2 || results.length < 2) {
// if more than two characters, or the previous search give less then 2 results
// look anywhere in the texts
results = _.filter(vm.options, function(option){
return _.includes(option.text.toLowerCase(), termLC);
});
}
callback({results: results});
} else {
callback({results: vm.options}); // no search input -> return all options to scroll through
}
};
var config = {
// dataAdapter for displaying all options when opening the input
// and for filtering when the user starts typing
dataAdapter: CustomData,
// only the selected value, needed for un-opened display
// we are not using all options because that might become slow if we have many select2 inputs
data:_.filter(vm.options, function(option){return option.id === parseInt(vm.value);}),
placeholder:vm.placeholder
};
for (var attr in vm.config) {
config[attr] = vm.config[attr];
}
if (vm.disabled) {
config.disabled = vm.disabled;
}
if (vm.placeholder && vm.placeholder !== '') {
$(vm.$el).append('<option></option>');
}
$(vm.$el)
// init select2
.select2(config)
.val(vm.value)
.trigger('change')
// prevent dropdown to open when clicking the unselect-cross
.on("select2:unselecting", function (e) {
$(this).val('').trigger('change');
e.preventDefault();
})
// emit event on change.
.on('change', function () {
var newValue = $(this).val();
if (newValue !== null) {
Vue.nextTick(function(){
vm.$emit('input', newValue);
});
}
})
});
},
watch: {
value: function (value, value2) {
if (value === null) return;
var isChanged = false;
if (_.isArray(value)) {
if (value.length !== value2.length) {
isChanged = true;
} else {
for (var i=0; i<value.length; i++) {
if (value[i] !== value2[i]) {
isChanged = true;
}
}
}
} else {
if (value !== value2) {
isChanged = true;
}
}
if (isChanged) {
var selectOptions = $(this.$el).find('option');
var selectOptionsIds = _.map(selectOptions, 'value');
if (! _.includes(selectOptionsIds, value)) {
var missingOption = _.find(this.options, {id: value});
var missingText = _.find(this.options, function(opt){
return opt.id === parseInt(value);
}).text;
$(this.$el).append('<option value='+value+'>'+missingText+'</option>');
}
// update value only if there is a real change
// (without checking isSame, we enter a loop)
$(this.$el).val(value).trigger('change');
}
}
},
destroyed: function () {
$(this.$el).off().select2('destroy')
}
The reason is because you are listening to events on a component <select2> and not an actual DOM node. Events on components will refer to the custom events emitted from within, unless you use the .native modifier.
Custom events are different from native DOM events: they do not bubble up the DOM tree, and cannot be captured unless you use the .native modifier. From the docs:
Note that Vue’s event system is separate from the browser’s EventTarget API. Though they work similarly, $on and $emit are not aliases for addEventListener and dispatchEvent.
If you look into the code you posted, you will see this at the end of it:
Vue.nextTick(function(){
vm.$emit('input', newValue);
});
This code emits a custom event input in the VueJS event namespace, and is not a native DOM event. This event will be captured by v-on:input or #input on your <select2> VueJS component. Conversely, since no change event is emitted using vm.$emit, the binding v-on:change will never be fired and hence the non-action you have observed.
Terry pointed out the reason, but actually you can simply pass your update event to the child component as a prop. Check demo below.
Vue.component('select2', {
template: '<select #change="change"><option value="value1">Value 1</option><option value="value2">Value 2</option></select>',
props: [ 'change' ]
})
new Vue({
el: '#app',
methods: {
onChange() {
console.log('on change');
}
}
});
<script src="https://unpkg.com/vue#2.4.2/dist/vue.min.js"></script>
<div id="app">
<div>
<p>custom select</p>
<select2 :change="onChange"></select2>
</div>
<div>
<p>default select</p>
<select #change="onChange">
<option value="value1">Value 1</option>
<option value="value2">Value 2</option>
</select>
</div>
</div>
fiddle