vuetify自动完成功能允许芯片之间存在未知项目

时间:2018-12-03 00:40:56

标签: vue.js vuetify.js

我正在尝试修改https://vuetifyjs.com/en/components/autocompletes#example-scoped-slots处的示例代码,以允许任意内容与芯片之间的任何自动完成项都不匹配(以便用户可以在类似于slack和facebook的消息中标记其他用户)

因此,例如,用户可以键入“ Sandra”,然后选择“ sandra adams”,然后键入“ foo”,然后键入另一个空格,然后再键入“ John”,并且自动完成会再次弹出并允许用户选择“约翰·史密斯”。

我已经遍历了文档中的所有属性,但似乎对此内置版本不提供支持。

在显示自动完成选项时,我尝试使用自定义过滤来忽略消息的不相关部分,但是当自动完成选项失去焦点时,自动完成似乎会删除非芯片内容,并且我看不到允许我防止此行为的属性。

不确定是否要使用autcomplete,或者我最好是通过破解组合框来满足此要求,因为此示例似乎更接近我正在尝试做的事情https://vuetifyjs.com/en/components/combobox#example-no-data,但是我相信我会失去自动完成功能附带的Ajax功能。

2 个答案:

答案 0 :(得分:1)

您可以通过将自动完成功能的异步搜索与组合框结合在一起来实现。

例如:

new Vue({
  el: '#app',
  data: () => ({
    activator: null,
    attach: null,
    colors: ['green', 'purple', 'indigo', 'cyan', 'teal', 'orange'],
    editing: null,
    descriptionLimit: 60,
    index: -1,
    nonce: 1,
    menu: false,
    count: 0,
    model: [],
    x: 0,
    search: null,
    entries: [],
    y: 0
  }),
   computed: {
      fields () {
        if (!this.model) return []

        return Object.keys(this.model).map(key => {
          return {
            key,
            value: this.model[key] || 'n/a'
          }
        })
      },
      items () {
        return this.entries.map(entry => {
          const Description = entry.Description.length > this.descriptionLimit
            ? entry.Description.slice(0, this.descriptionLimit) + '...'
            : entry.Description

          return Object.assign({}, entry, { Description })
        })
      }
    },

  watch: {
    search (val, prev) {
    
        // Lazily load input items
        axios.get('https://api.publicapis.org/entries')
          .then(res => {
          console.log(res.data)
            const { count, entries } = res.data
            this.count = count
            this.entries = entries
          })
          .catch(err => {
            console.log(err)
          })
          .finally(() => (this.isLoading = false))
          
      /*if (val.length === prev.length) return

      this.model = val.map(v => {
        if (typeof v === 'string') {
          v = {
            text: v,
            color: this.colors[this.nonce - 1]
          }

          this.items.push(v)

          this.nonce++
        }

        return v
      })*/
    },
     model (val, prev) {
        if (val.length === prev.length) return

        this.model = val.map(v => {
          if (typeof v === 'string') {
            v = {
              Description: v
            }

            this.items.push(v)

            this.nonce++
          }

          return v
        })
      }
  },

  methods: {
    edit (index, item) {
      if (!this.editing) {
        this.editing = item
        this.index = index
      } else {
        this.editing = null
        this.index = -1
      }
    },
    filter (item, queryText, itemText) {
      const hasValue = val => val != null ? val : ''

      const text = hasValue(itemText)
      const query = hasValue(queryText)

      return text.toString()
        .toLowerCase()
        .indexOf(query.toString().toLowerCase()) > -1
    }
  }
})
<link href='https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons' rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify/dist/vuetify.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js" integrity="sha256-mpnrJ5DpEZZkwkE1ZgkEQQJW/46CSEh/STrZKOB/qoM=" crossorigin="anonymous"></script>

<div id="app">
<v-app>
<v-content>
        <v-container>
<v-combobox
    v-model="model"
    :filter="filter"
    :hide-no-data="!search"
    :items="items"
    :search-input.sync="search"
    hide-selected
    label="Search for an option"
    :allow-overflow="false"
    multiple
    small-chips
    solo
    hide-selected
    return-object
    item-text="Description"
    item-value="API"
    :menu-props="{ closeOnClick: false, closeOnContentClick: false, openOnClick: false, maxHeight: 200 }"
    dark
  >
   <template slot="no-data">
      <v-list-tile>
        <span class="subheading">Create</span>
        <v-chip
          label
          small
        >
          {{ search }}
        </v-chip>
      </v-list-tile>
    </template>
    <template
      v-if="item === Object(item)"
      slot="selection"
      slot-scope="{ item, parent, selected }"
    >
      <v-chip
        :selected="selected"
        label
        small
      >
        <span class="pr-2">
          {{ item.Description }}
        </span>
        <v-icon
          small
          @click="parent.selectItem(item)"
        >close</v-icon>
      </v-chip>
    </template>
    <template
      slot="item"
      slot-scope="{ index, item, parent }"
    >
      <v-list-tile-content>
        <v-text-field
          v-if="editing === item.Description"
          v-model="editing"
          autofocus
          flat
          hide-details
          solo
          @keyup.enter="edit(index, item)"
        ></v-text-field>
        <v-chip
          v-else
          dark
          label
          small
        >
          {{ item.Description }}
        </v-chip>
      </v-list-tile-content>
    </template>
  </v-combobox>
  </v-container>
  </v-content>
  </v-app>
</div>

答案 1 :(得分:0)

所以我最终构建了一个与vuetify兼容的无渲染组件,因为它通过默认插槽并找到了贡品支持的任何类型的标签(文本区域,带有文本类型的输入或contenteditable),并允许您放置任意的vue,这些vue将通过作用域限定的插槽用于构建贡品菜单项。

将来可能会尝试将其包装为一个小的NPM包,以供任何希望以声明性方式以比vue-tribute允许的方式更灵活的方式将tribute.js用于vue的人使用,但是现在这是我的概念证明 >

InputWithMentions.vue

<script>
import Tribute from "tributejs"
// eslint-disable-next-line 
import * as css from "tributejs/dist/tribute.css"
import Vue from "vue"

export default {
    mounted() {
       let menuItemSlot = this.$scopedSlots.default

        let tribute = new Tribute({
          menuItemTemplate: item => 
          {
              let menuItemComponent =  
                new Vue({
                    render: function (createElement) { 
                        return createElement('div', menuItemSlot({ menuItem: item }))
                    }
                })

                menuItemComponent.$mount()
                return menuItemComponent.$el.outerHTML
          },
           values: [
                {key: 'Phil Heartman', value: 'pheartman'},
                {key: 'Gordon Ramsey', value: 'gramsey'}
          ]})

          tribute.attach(this.$slots.default[0].elm.querySelectorAll('textarea, input[type=text], [contenteditable]'))
    },
    render(createElement) {
        return createElement('div', this.$slots.default)
    }
}
</script>

User.vue

   <InputWithMentions>
          <v-textarea
            box
            label="Label"
            auto-grow
            value="The Woodman set to work at once, and so sharp was his axe that the tree was soon chopped nearly through.">
          </v-textarea>
          <template slot-scope="{ menuItem }">
                <v-avatar size="20" color="grey lighten-4">
                  <img src="https://vuetifyjs.com/apple-touch-icon-180x180.png" alt="avatar">
                </v-avatar>
                {{ menuItem.string }}
          </template>
     </InputWithMentions>