0

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.

Grandizer
  • 2,819
  • 4
  • 46
  • 75

1 Answers1

0

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?

Gabriel Carneiro
  • 643
  • 5
  • 16
  • @GabrielCameiro I see what you are getting at. Based on myself and other devs on this project I was hoping to not have to also tack on webpack and requireJS and such. Been having a difficult time just getting that setup on a few tests. Would love to use TypeScript but have not found a simple way to just configure things. This is NOT a SPA and thus I really only want to load the .js files I need for each page. – Grandizer May 14 '18 at 17:13
  • Why does not use webpack? – Gabriel Carneiro May 14 '18 at 17:19
  • Seems less ideal. At this point, I am not sure if I like one huge file from a debugging standpoint. But if that is the most current way to do things so it can transpile to ES5 (for this project) then I am up for it. We are writing this app in ASP.Net Core 2.1 using Razor Pages. I have not found a tutorial to walk me through setting up WebPack and such with TypeScript and actually get it to work. The templates that came with VS 2017 don't even seem to work in a blank new project. Also, I plan to use Vue if that helps. Thanks @GabrielCameiro. – Grandizer May 14 '18 at 18:55
  • I can see, I definitely hate ASP, looks like you'll need to create a Vue App for each page, am I right? If it's the case, you can use webpack, just modularize the output to get only the code that is needed. – Gabriel Carneiro May 14 '18 at 19:18
  • @GabrielCameiro yes, unless I do SPA which is overkill for this project. Thank you for your time and suggestions. Now on to a search for setting it up the right way in Visual Studio. – Grandizer May 15 '18 at 11:28
  • 1
    @GabrielCarneiro [method shorthand](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions) is equivalent to a normal `function` expression, not to arrow function. See also https://stackoverflow.com/questions/40140075/es6-difference-between-arrow-function-and-method-definition – ghybs May 21 '18 at 14:26