How to pass a function to component to handle a drag event - javascript

I want to use v-for and is to render the components I need. So I create the component Cube.vue like this:
<script>
export default {
name: 'cube',
render: function(createElement) {
return createElement("div",{
props: {
style: {
type: Object
},
dragstartHandler: {
type: Function
},
classNames: {
type: Array|String|Object
}
},
style: this.style,
attrs: {
draggable: "true",
},
on: {
dragstart: this.dragstartHandler
},
'class': this.classNames
},[
createElement("div",{
class: ["resizer", "top-left"]
}),
createElement("div",{
class: ["resizer", "top-right"]
}),
createElement("div",{
class: ["resizer", "bottom-left"]
}),
createElement("div",{
class: ["resizer", "bottom-right"]
}),
])
}
}
</script>
And then, I use it like this
<component
v-for="component in components"
:class="component.classes"
:key="component.refValue"
:is="component.type"
:style="component.style"
:dragstartHandler="component.dragstartHandler"
:ref="component.refValue"
>
</component>
Everything as I expected, except the dragstartHandler. It throws an error
[Vue warn]: Invalid handler for event "dragstart": got undefined
I try to console.log() the components. The result is :
[{…}, __ob__: Observer]
0:
classes: Array(2)
dragstartHandler: ƒ ()
refValue: "cube-0"
style: Object
type: "cube"
It really is a function. But I don't know why it go to undefined in the render. I have checked I didn't spell wrong.
I just want pass the function to the component to handle the drag event. So how does it happened and what should I do to resolve it.
The dragHandler function is these:
dragstartCopyHandler(event) {
event.dataTransfer.setData("elementId", event.target.id);
event.dataTransfer.setData("componentOffsetX", event.offsetX);
event.dataTransfer.setData("componnetOffsetY", event.offsetY);
event.dataTransfer.setData("dropEffect", "copy");
event.dataTransfer.dropEffect = "copy";
},
dragstartMoveHandler(event) {
console.log("move start")
event.dataTransfer.setData("elementId", event.target.id);
event.dataTransfer.setData("componentOffsetX", event.offsetX);
event.dataTransfer.setData("componnetOffsetY", event.offsetY);
event.dataTransfer.setData("dropEffect", "move");
event.dataTransfer.dropEffect = "move";
},
And I pass the dragstartMoveHandler to the component.
this.components.push({
refValue: `${elementId}-${this.count}`,
type: elementId,
style: style,
dragstartHandler: this.dragstartMoveHandler,
classes: ["component", elementId]
});
I write the pages use js to control Dom before. And today I want to rewrite it with vue. So here might something wrong with the function, but the problem now is the function passed is undefined.

The cube component needs to define a prop for dragstartHandler so that it receives it from the parent for passing it on to the div. The same is true of the style and class bindings, which are also not working as you expect, but you don't see an error there because those are built-in bindings which get transferred to the root element.
Cube.vue
render() {
...
},
props: {
dragstartHandler: {
type: Function,
},
}

Related

Vue render button

My question is about render a button on vue instance, to click in a button and then it render another button with event click, If I simple mount the button it dont get the function tes.
const Hello = {
props: ['text'],
template: '<button v-on:click="tes"> </button> ',
};
new Vue({
el: '#app',
data: {
message: 'Click me'
},
methods:{
alertar: function(event){
const HelloCtor = Vue.extend(Hello);
var instance = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
instance.$mount() // pass nothing
this.appendChild(instance.$el)
},
tes: function(){
alert('Teste');
}
}
})
Erro :
vue.js:597 [Vue warn]: Invalid handler for event "click": got undefined
(found in <Root>)
warn # vue.js:597
(index):52 Uncaught TypeError: this.appendChild is not a function
at Vue.alertar ((index):52)
at invoker (vue.js:2029)
at HTMLParagraphElement.fn._withTask.fn._withTas
The problem is that you create a child component inside of your parent Vue that contains the template with the binding to the tes function. That means that the child will look in its own methods for tes, however it is a property of your parent, not of the child itself so it will never be able to find it in its own scope. You have to add the function to the child component instead:
const Hello = {
props: ['text'],
template: '<button v-on:click="tes"> </button> ',
methods: {
tes: function(){
alert('Teste');
}
}
};
Just expanding #Philip answer
Basically you can't access parent methods in programatically created components.
You need to specify the methods inside the child components.
const Hello = {
props: ['text'],
template: '<button v-on:click="this.tes"> Vue Generated</button> ',
methods: {
tes: function(){
alert('Teste');
}}
};
new Vue({
el: '#app',
data: {
message: 'Hello Vue.js!'
},
mounted(){
this.alertar()
},
methods:{
alertar: function(event){
const HelloCtor = Vue.extend(Hello);
var instance = new HelloCtor({
propsData: {
text: 'HI :)'
}
})
instance.$mount() // pass nothing
this.$refs.container.appendChild(instance.$el)
},
tes: function(){
alert('Teste');
}
}
})
Check this fiddle here
https://jsfiddle.net/50wL7mdz/370645/
However in some cases you may be able to access the parent components methods using
$parent directive which I believe will not work when components is created programatically.

Can I Pass a Function To a Props on a VueJS Component

I have a rather simple component. Basically it is a dropdown select and I need to run some custom code that is in an external javascript function. However, this prop is not required on every instance of the component. So sometimes there may be nothing in the prop. Other times it might do one thing, others might do something else.
<template id="drop-list-template">
<select class="form-control"
v-model="value"
v-bind:class="{ required: isRequired, invalid: !isValid }"
v-on:blur="validate"
v-on:change="changed"> <-- This is the Prop I want to use
<option v-if="showEmptyOption" value="">{{ emptyOption }}</option>
<option v-for="i in items"
v-bind:value="i.value"
v-bind:selected="i.checked === value"
v-bind:disabled="i.enabled === false">
{{ i.text }}
</option>
</select>
</template>
So in the on-change event, it will call the changed method. That was not working. I then added a special prop to the code file:
Vue.component("drop-list", {
template: "#drop-list-template",
props: {
dataset: { type: Array, required: true },
isRequired: { type: Boolean, required: false, default: false },
emptyOption: { type: String, required: false, default: "*Select an Option *" },
showEmptyOption: { type: Boolean, required: false, default: true },
special: { required: false }
},
data: function () {
return {
items: this.dataset,
isValid: true,
value: ""
}
},
methods: {
validate: function (event) {
var Result = true;
if ((this.isRequired === true) && (this.value === ""))
Result = false;
this.isValid = Result;
return Result;
},
changed: function (event) {
if (this.special) {
AbnormalitiesAndImpressions(); <-- Obviously this works
alert("After");
this.special(); <-- Would want this to run AbnormalitiesAndImpressions
}
}
}
});
And implement it via:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
special="AbnormalitiesAndImpressions">
</drop-list>
Where AbnormalitiesAndImpressions is just dumb right now:
function AbnormalitiesAndImpressions(lstAbs, lstImps) {
alert("Got to here");
}
When I run it, the "Got to here" alert pops up and so does the "After" alert. It then fails because this.special(); is not a function.
Bottom line is I am trying to let the user (myself in this case) create as many of these lists as needed. What will happen on some of them is they tweak what is available in other controls. So a sort of validation is going on. I just want this to be customizable per each use of the component.
I would even be fine with an anonymous function like the following:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
special="function () { AbnormalitiesAndImpressions(); }">
</drop-list>
Update
I have updated my component slightly:
<template id="drop-list-template">
<select class="form-control"
v-model="value"
v-bind:class="{ required: isRequired, invalid: !isValid }"
v-on:blur="validate"
v-on:change="change">
<option v-if="showEmptyOption" value="">{{ emptyOption }}</option>
<option v-for="i in items"
v-bind:value="i.value"
v-bind:selected="i.checked === value"
v-bind:disabled="i.enabled === false">
{{ i.text }}
</option>
</select>
</template>
And the corresponding javascript:
Vue.component("drop-list", {
template: "#drop-list-template",
props: {
dataset: { type: Array, required: true },
isRequired: { type: Boolean, required: false, default: false },
emptyOption: { type: String, required: false, default: "*Select an Option *" },
showEmptyOption: { type: Boolean, required: false, default: true },
special: { type: Function, required: false }
},
data: function () {
return {
items: this.dataset,
isValid: true,
value: ""
}
},
methods: {
change: function (event) {
if (this.special)
this.special();
}
}
});
And the implementation:
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
:special="AbnormalitiesAndImpressions">
</drop-list>
And here is the page's Vue code:
var vm = new Vue({
el: "#app",
data: {
Result: {},
Defaults: {},
Errors: [],
Abnormalities: [],
Impressions: []
},
methods: {
AbnormalitiesAndImpressions: function () {
alert("Should get overridden");
}
}
});
vm.AbnormalitiesAndImpressions = function (lstAbs, lstImps) {
alert("Got to here: " + lstAbs + "\n" + lstImps);
}
I found that if I did not add the methods short version of AbnormalitiesAndImpressions that it would give me a Vue warning that the property did not exist. However, the version of AbnormalitiesAndImpressions at the bottom of that file actually runs. I like this as each implementation could change and they should be on the page and not on the component.
When I change the dropdown item, I do get the Got to here message. And of course it has two undefined as the lstAbs and lspImps were not passed in.
New Question
Is it possible then to pass values to my props function? In this case, they can be strings. But if I do the code below...
<drop-list ref="lstAbnormalities"
v-bind:dataset="Abnormalities"
v-bind:is-required="true"
:special="AbnormalitiesAndImpressions('test')">
</drop-list>
When the page loads, the alert is popped right away and Does have the test parameter. And when I actually change the select the alert does not fire at all.
Ok, so let's do it step by step.
Correct method declaration
methods: {
change: function (event) {
if (this.special)
this.special();
}
}
This method declaration isn't good, it's changing the this context, always declare methods with arrow functions or with the shorthand syntax. Anonymous functions declared like this: function () { //... } creates a new this context, and beacuse of that this.special is always undefined. So change it to:
methods: {
change (event) {
if (this.special)
this.special();
}
}
Do it at all methods, it'll avoid a lot of headache. To another anonymous functions, always use arrow functions.
Method passed as prop
About your new question, let me explain what's happening when you set the special prop as AbnormalitiesAndImpressions with vanilla Js to clarify your mind.
Think about a method foo, just like this below:
function foo (string) {
return string;
}
Above we can see the method declaration, in Js is possible to assign a function to a variable, so, if a create a variable a it can be equals to foo, just like it:
let a = foo;
As you can see, I'm passing the function foo to the var a, not the return of the function foo, it's what you do when you set the special property as AbnormalitiesAndImpressions, because of that you can't do this: :special="AbnormalitiesAndImpressions('test')", but, if we look back to my example, one thing we can do, that is:
a('bar');
And it'll return 'bar', so, applying it to Vue, at your component drop-list, where you call the function as this.special you can pass params to the function, did you get it?

vuejs how to run emit on every array change

I've got an array in child component like this:
arrayChild:
[
{name: 'name1', text: 'text1', buttons: 'false', active: "true"},
{name: 'name2', text: 'text2', buttons: 'false', active: "false"},
...
]
I want to emit the arrayChild on every change in the name, text and active (not buttons change!)
How can I do it?
I created basic function to emit this on button click:
<btn #click="emitParent()">emit my Array</btn>
emitUp() {
this.$emit('offerArray', this.arrayChild)
}
But it emit only on button click. I need to emit this automatically on any change within name, text and active. How can I do it? Should I use some kind of computed?
Vue.js offers watchers! So you can "watch" a property and when a change is made you can do stuff.See below:
export default {
data() {
return {
propertyName: 'valueOfProperty'
}
},
watch: {
propertyName(theNewChangedValue) {
//do stuff here
}
}
}
Also consider a deep watcher,that is useful when working with array of objects
propertyName: {
handler: function(newValue) {
//do stuff when array of object changes
},
deep: true
}

Polymer one-way-binding not working

After hours of trying I'm clueless. Maybe some of you have the answer. I've create a Polymer template like this:
<dom>
</dom>
<script>
Polymer ({
is: 'foo',
proprties: {
timeRange: {
type: String,
value: 'day',
readOnly: true,
observer: "_refresh",
},
...
},
...
});
</script>
The observer function is just for debugging. The calling host uses this like that:
<dom>
<foo time-range='[[timeRange]]'></foo>
</dom>
<script>
Polymer ({
is: 'host',
proprties: {
timeRange: {
type: String,
value: 'day',
readOnly: true,
notify: true,
observer: "timeRangeChanged",
},
...
},
onDayTap: function() {
this._setTimeRange('day');
},
onMonthTap: function() {
this._setTimeRange('month');
},
onYearTap: function() {
this._setTimeRange('year');
},
...
});
</script>
The 'timeRangeChanged' observer refreshes a chart. But the timeRange-property in foo never gets changed - the _refresh-observer gets called only once on start. What am I doing wrong.
Hope somebody can help me out here
PS. I'm using Polymer 1.0
Hope this will help:
https://jsfiddle.net/fLbp26kp/
Polymer ({
is: 'foo',
properties: {
timeRange: {
type: String,
value: 'day'
observer: "_refresh",
}
}
});
If you're trying to update foo.timeRange you would need to remove readOnly from timeRange in order for it to be set from somewhere else.
Foo without readOnly.

Call method from another component VueJS version 1

I want to call the method from another component in let's say parent component... So, let's say I have notie.vue and there's my method named 'flash' and I want to call that after API request when I get success.
So let's say this is the case: after get API I want to call something like this with parameters ---> notie.success('notification type', 'notification message')
Here's the code:
Props are defined like this
props: {
type: {
default: 'info',
required: false
},
content: {
default: 'This is just demo text',
required: false
},
visible: {
type: Boolean,
default: false
}
}
Here's data
data: function () {
return {
show: true
}
}
Here's method
methods: {
flash: function () {
this.show = true;
}
}
Here's HTML code
<div class="notie" v-bind:class="[{active: show}, type]" v-on:click="close()">
<div class="notie__icon" :class="type"></div>
<div class="notie__content" v-text="content"></div>
</div>
Here for example i want to call my notie component
submit: function () {
this.$http.post('/api/books/add', {
data: this.data,
}).then(function (response) {
// I want to use notie component right here to notice to users.
}, function (response) {
});
}
So my question is how can I call flash() after API request on success or error?

Categories

Resources