Search code examples
javascripthtmlcssvue.jsfast-ui

2 Way binding issue using Fast-UI + Vue.js ver. 2.6.11


This was tested on the verions (package.json):

{
  "name": "fast-ui-vuejs-ver-2611-binding-issue",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "@microsoft/fast-components": "^2.10.1",
    "@microsoft/fast-element": "^1.5.1",
    "@vue/cli-plugin-babel": "4.1.1",
    "lodash-es": "^4.17.21",
    "vue": "^2.6.11",
    "vue-selector": "0.0.1"
  },
  "devDependencies": {
    "@vue/cli-plugin-eslint": "4.1.1",
    "@vue/cli-service": "4.1.1",
    "babel-eslint": "^10.0.3",
    "eslint": "^6.7.2",
    "eslint-plugin-vue": "^6.0.1",
    "vue-template-compiler": "^2.6.11"
  },
}

Preconditions

  1. open the sandbox using this URL;
  2. wait until everything is installed;
  3. wait until you see the slider on the right-hand side of the screen.

Steps to reproduce the issue:

  1. drag the thumb(handle) of the slider forwarnd and back;
  2. have a look at the value (it has changed as expected);
  3. click on + or - buttons;
  4. try to use slider again;
  5. get back to + or - button, and click one or another.
Actual result:

Binding stops working or at least works not as intended after interacting with different components/elements

Expected Result:

It must work after any interaction it has.

The things I have tried:
  • v-model;
  • custom-bining directive for events (onchange, drag).

code example of cusotom binding directive:

Implementation file

Vue.directive("cd-bind", {
  bind(el, binding, vnode) {
    const inputHandler = (event) =>
      (vnode.context.$data[binding.expression] = event.target.value);
    el.addEventListener("input", inputHandler);
    el.addEventListener("change", inputHandler);
    el.addEventListener("slide", inputHandler);
  },
});

Usage:

<fast-slider
      v-cd-bind="value"
      :value="value"
      :min="0"
      :max="20"
></fast-slider>
Additional information:

After an element loses focus and another element gets focused, binding (or some part of it stops working).

Video on this issue

The suggestion made by "EisenbergEffect" that may help someone to find out the way to fix it site:

"For Aurelia, we had to add some special configuration to enable better two-way binding. I'm guessing that there's a similar need with Vue. Typically in a two-way or model bind scenario the framework needs to know how to correlate event names with the properties that change so it can add event listeners. My guess is that Vue doesn't know what events to attach because it only has code to handle built-in elements. As a next step, I think you may want to take a look and see if Vue offers an API to provide that data to their model binding system. Hopefully they do. In that case, we would just need to set that up properly for the components we publish...and add that to our documentation for Vue. Another issue may be related to whether Vue's binding is setting properties or attributes. They can be different and tend to be a little when it comes to input elements. Does: value set the value attribute or the value property?"

Main component code:
<template>
  <fast-card>
    <span style="color: white"
      >Current value:
      <b style="color: red; font-size: 150%">{{ value }}</b></span
    >
    <fast-slider
      :value="value"
      :max="max"
      :min="min"
      ref="comp"
      @change="change($event)"
    >
      <div
        slot="track"
        class="time-seeker__played"
        :style="{ width: timePlayedPercentage + '%' }"
      ></div>
    </fast-slider>

    <fast-card>
      <fast-button @click="value = value > 0 ? value - 1 : value"
        >-</fast-button
      >
      <fast-button @click="value = value < 20 ? value + 1 : value"
        >+</fast-button
      >
    </fast-card>
  </fast-card>
</template>

<script>
export default {
  name: "HelloWorld",
  data() {
    return {
      value: 3,
      min: 0,
      max: 20,
    };
  },
  computed: {
    timePlayedPercentage: function () {
      let value = Math.round((this.value * 100) / this.max);
      // console.log(value, "%");
      return value;
    },
  },
  methods: {
    change(e) {
      this.value = +e.target.value;
    },
  },
};
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped lang="scss">
div {
  width: 100%;
}

.time-seeker__played {
  transition: width 75ms cubic-bezier(0.86, 0.05, 0.4, 0.96);
  background: red;
  height: 100%;
  border-radius: 15%;
}

fast-card {
  margin-block: 1rem;
}

fast-button {
  margin-inline-end: 1rem;
}
</style>


Solution

  • After make some inquiries to the FAST-UI team, they have created a few PR in their repository:

    After they have updated the core code of this project, this 2-way binding has disappeared!

    However, to make thins work, they have implemented a new HTML attribute called current-value (or currentValue in JS) that is in a change of it.

    This feature is available in:

    The difference in HTML/Vue components

    Before

    <fast-slider
          :value="value"
          :max="max"
          :min="min"
          ref="comp"
          @change="change($event)"
        >
    

    After

    <fast-slider
          :current-value="value"
          :max="max"
          :min="min"
          ref="comp"
          @change="change($event)"
        >
    

    Here is the version that works perfectly thanks to FAST-UI team: open workable sandbox example

    Also, there is another PR that is pending as for 28.10.2021 feat: add current-checked attribute to checkable form controls #5326 that will add current-checked attribute to make it responsive to UI/JS changes.