mirror of
https://github.com/Hopiu/vue-material.git
synced 2026-05-22 14:01:52 +00:00
Merge branch 'master' into develop
This commit is contained in:
commit
6297347a0c
5 changed files with 451 additions and 2 deletions
|
|
@ -116,6 +116,126 @@
|
|||
</md-table>
|
||||
</api-table>
|
||||
|
||||
<api-table name="md-autocomplete">
|
||||
<md-table slot="properties">
|
||||
<md-table-header>
|
||||
<md-table-row>
|
||||
<md-table-head>Name</md-table-head>
|
||||
<md-table-head>Type</md-table-head>
|
||||
<md-table-head>Description</md-table-head>
|
||||
</md-table-row>
|
||||
</md-table-header>
|
||||
|
||||
<md-table-body>
|
||||
<md-table-row>
|
||||
<md-table-cell>v-model</md-table-cell>
|
||||
<md-table-cell><code>String</code></md-table-cell>
|
||||
<md-table-cell>A required model object to bind the value.</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>debounce</md-table-cell>
|
||||
<md-table-cell><code>Number</code></md-table-cell>
|
||||
<md-table-cell>Sets the debounce time. Default <code>1000</code> milliseconds</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>fetch</md-table-cell>
|
||||
<md-table-cell><code>Function</code></md-table-cell>
|
||||
<md-table-cell>Sets the fetch function mdAutocomplete will call after the debounce is reached. Chosing <code>fetch</code> prop <strong>disables</strong> the use of either <code>list</code> and <code>filterList</code> props.</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>list</md-table-cell>
|
||||
<md-table-cell><code>Array</code></md-table-cell>
|
||||
<md-table-cell>Sets an array of possible values. Default <code>[]</code>. MdAutocomplete will only search in this list if it's set. Chosing <code>list</code> prop <strong>disables</strong> the use of <code>fetch</code> prop.</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>filter-list</md-table-cell>
|
||||
<md-table-cell><code>Function</code></md-table-cell>
|
||||
<md-table-cell>Sets a filter function which will be used to filter the <code>list</code> props. Chosing <code>filterList</code> prop <strong>requires</strong> the use of <code>list</code> props and <strong>disables</strong> the use of <code>fetch</code> prop.</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>min-chars</md-table-cell>
|
||||
<md-table-cell><code>Number</code></md-table-cell>
|
||||
<md-table-cell>Sets the minimum number of characters before making opening the autocomplete options or making a request. Default <code>3</code></md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>prepare-response-data</md-table-cell>
|
||||
<md-table-cell><code>Function</code></md-table-cell>
|
||||
<md-table-cell>This function will be called once the <code>fetch</code> prop has a response. It can manipulate the data received from the server. The output should always be an <code>Array</code>.</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>print-attribute</md-table-cell>
|
||||
<md-table-cell><code>String</code></md-table-cell>
|
||||
<md-table-cell>This prop will be used to print values on the autocomplete list. It shall match an object key expected on the <code>fetch</code> result list. Default <code>name</code></md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>query-param</md-table-cell>
|
||||
<md-table-cell><code>String</code></md-table-cell>
|
||||
<md-table-cell>Sets the query parameter. Example: http//api.com/<strong>q</strong>?=SOMETHING. Default <code>q</code></md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>disabled</md-table-cell>
|
||||
<md-table-cell><code>Boolean</code></md-table-cell>
|
||||
<md-table-cell>Disable the input and prevent his actions. Default <code>false</code></md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>required</md-table-cell>
|
||||
<md-table-cell><code>Boolean</code></md-table-cell>
|
||||
<md-table-cell>Apply the required rule to style the label with an "*". Default <code>false</code></md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>placeholder</md-table-cell>
|
||||
<md-table-cell><code>String</code></md-table-cell>
|
||||
<md-table-cell>Sets the placeholder.</md-table-cell>
|
||||
</md-table-row>
|
||||
|
||||
<md-table-row>
|
||||
<md-table-cell>maxlength</md-table-cell>
|
||||
<md-table-cell><code>Number</code></md-table-cell>
|
||||
<md-table-cell>Sets the maxlength and enable the text counter.</md-table-cell>
|
||||
</md-table-row>
|
||||
</md-table-body>
|
||||
</md-table>
|
||||
|
||||
<md-table slot="events">
|
||||
<md-table-header>
|
||||
<md-table-row>
|
||||
<md-table-head>Name</md-table-head>
|
||||
<md-table-head>Value</md-table-head>
|
||||
<md-table-head>Description</md-table-head>
|
||||
</md-table-row>
|
||||
</md-table-header>
|
||||
|
||||
<md-table-body>
|
||||
<md-table-row>
|
||||
<md-table-cell>change</md-table-cell>
|
||||
<md-table-cell>The <code>String</code> selected</md-table-cell>
|
||||
<md-table-cell>Triggered when the user selects an item from the autocomplete list</md-table-cell>
|
||||
</md-table-row>
|
||||
<md-table-row>
|
||||
<md-table-cell>input</md-table-cell>
|
||||
<md-table-cell>The <code>String</code> selected</md-table-cell>
|
||||
<md-table-cell>Triggered when the user selects an item from the autocomplete list</md-table-cell>
|
||||
</md-table-row>
|
||||
<md-table-row>
|
||||
<md-table-cell>selected</md-table-cell>
|
||||
<md-table-cell>Emits the <code>Object</code> as well as the <code>String</code> selected.</md-table-cell>
|
||||
<md-table-cell>Triggered when the user selects an item from the autocomplete list</md-table-cell>
|
||||
</md-table-row>
|
||||
</md-table-body>
|
||||
</md-table>
|
||||
</api-table>
|
||||
|
||||
<api-table name="md-textarea">
|
||||
<md-table slot="properties">
|
||||
<md-table-header>
|
||||
|
|
@ -187,6 +307,11 @@
|
|||
<md-input v-model="initialValue" readonly></md-input>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container>
|
||||
<label>Autocomplete (with fetch)</label>
|
||||
<md-autocomplete v-model="autocompleteValue" :fetch="fetchAutocomplete"></md-autocomplete>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container>
|
||||
<label>With label</label>
|
||||
<md-input placeholder="My nice placeholder"></md-input>
|
||||
|
|
@ -227,6 +352,11 @@
|
|||
<md-input v-model="initialValue"></md-input>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container>
|
||||
<label>Autocomplete (with fetch)</label>
|
||||
<md-input v-model="autocompleteValue" :fetch="fetchFunction"></md-input>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container>
|
||||
<label>With label</label>
|
||||
<md-input placeholder="My nice placeholder"></md-input>
|
||||
|
|
@ -265,7 +395,19 @@
|
|||
return {
|
||||
initialValue: 'My initial value'
|
||||
};
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
fetchFunction(param) {
|
||||
// param = { queryParam: query }
|
||||
|
||||
// 'fetchAutocomplete' should return a Promise.
|
||||
|
||||
// md-autocomplete will call fetchAutocomplete and pass
|
||||
// 'param' as an argument.
|
||||
// the 'param' is composed by a query param and
|
||||
// a query.
|
||||
},
|
||||
},
|
||||
};
|
||||
</code-block>
|
||||
</div>
|
||||
|
|
@ -495,8 +637,34 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
initialValue: 'My initial value'
|
||||
autocompleteValue: '',
|
||||
initialValue: 'My initial value',
|
||||
listAutocomplete: [
|
||||
{name: 'oi'},
|
||||
{name: 'hello'},
|
||||
{name: 'salut'}
|
||||
]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
fetchAutocomplete(param) {
|
||||
const myInit = {
|
||||
method: 'GET',
|
||||
headers: new Headers(),
|
||||
mode: 'cors',
|
||||
cache: 'default'
|
||||
};
|
||||
const url = 'https://typeahead-js-twitter-api-proxy.herokuapp.com/demo/search';
|
||||
const queryParam = Object.keys(param)[0];
|
||||
const queryValue = param[queryParam];
|
||||
const queryUrl = `${url}?${queryParam}=${queryValue}`;
|
||||
|
||||
return window.fetch(queryUrl, myInit)
|
||||
.then((res) => res.json());
|
||||
},
|
||||
filterList(list, query) {
|
||||
return list.filter((el) => el.name.indexOf(query) !== -1);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import mdInputContainer from './mdInputContainer.vue';
|
||||
import mdInput from './mdInput.vue';
|
||||
import mdAutocomplete from './mdAutocomplete.vue';
|
||||
import mdTextarea from './mdTextarea.vue';
|
||||
import mdInputContainerTheme from './mdInputContainer.theme';
|
||||
|
||||
export default function install(Vue) {
|
||||
Vue.component('md-input-container', mdInputContainer);
|
||||
Vue.component('md-input', mdInput);
|
||||
Vue.component('md-autocomplete', mdAutocomplete);
|
||||
Vue.component('md-textarea', mdTextarea);
|
||||
|
||||
Vue.material.styles.push(mdInputContainerTheme);
|
||||
|
|
|
|||
209
src/components/mdInputContainer/mdAutocomplete.vue
Normal file
209
src/components/mdInputContainer/mdAutocomplete.vue
Normal file
|
|
@ -0,0 +1,209 @@
|
|||
<template lang="html">
|
||||
<div class="md-autocomplete"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur">
|
||||
<md-menu :md-offset-x="8"
|
||||
md-offset-y="45"
|
||||
ref="menu"
|
||||
class="md-autocomplete-menu">
|
||||
<span md-menu-trigger></span>
|
||||
<input class="md-input"
|
||||
ref="input"
|
||||
type="text"
|
||||
v-model="query"
|
||||
:disabled="disabled"
|
||||
:required="required"
|
||||
:placeholder="placeholder"
|
||||
:maxlength="maxlength"
|
||||
:name="name"
|
||||
@focus="onFocus"
|
||||
@blur="onBlur"
|
||||
@input="debounceUpdate"/>
|
||||
|
||||
<md-menu-content>
|
||||
<md-menu-item v-for="item in items"
|
||||
v-if="items.length"
|
||||
@keyup.enter="hit(item)"
|
||||
@click.native="hit(item)">
|
||||
{{ item[printAttribute] }}
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import autocompleteCommon from '../../core/utils/autocomplete-common';
|
||||
import common from './common';
|
||||
import getClosestVueParent from '../../core/utils/getClosestVueParent';
|
||||
|
||||
export default {
|
||||
mixins: [common, autocompleteCommon],
|
||||
data() {
|
||||
return {
|
||||
items: [],
|
||||
loading: false,
|
||||
query: '',
|
||||
selected: null,
|
||||
timeout: 0,
|
||||
parentContainer: null,
|
||||
searchButton: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
listIsEmpty() {
|
||||
return this.list.length === 0;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
list(value) {
|
||||
this.items = Object.assign([], value);
|
||||
},
|
||||
query(value) {
|
||||
this.$refs.input.value = value;
|
||||
this.setParentUpdateValue(value);
|
||||
},
|
||||
value(value) {
|
||||
this.query = value;
|
||||
this.setParentUpdateValue(value);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
debounceUpdate() {
|
||||
this.onInput();
|
||||
|
||||
if (this.timeout) {
|
||||
window.clearTimeout(this.timeout);
|
||||
}
|
||||
|
||||
this.timeout = window.setTimeout(() => {
|
||||
if (!this.listIsEmpty) {
|
||||
this.renderFilteredList();
|
||||
return;
|
||||
}
|
||||
this.update();
|
||||
}, this.debounce);
|
||||
},
|
||||
hit(item) {
|
||||
this.query = item[this.printAttribute];
|
||||
this.$refs.input.value = item[this.printAttribute];
|
||||
this.selected = item;
|
||||
this.onInput();
|
||||
this.$emit('selected', this.selected, this.$refs.input.value);
|
||||
},
|
||||
makeFetchRequest(queryObject) {
|
||||
return this.fetch(queryObject)
|
||||
.then((response) => {
|
||||
let data = response || response.data || response.body;
|
||||
|
||||
data = this.prepareResponseData ?
|
||||
this.prepareResponseData(data) :
|
||||
data;
|
||||
this.items = this.limit ?
|
||||
data.slice(0, this.limit) :
|
||||
data;
|
||||
|
||||
this.loading = false;
|
||||
|
||||
this.toggleMenu();
|
||||
});
|
||||
},
|
||||
onFocus() {
|
||||
if (this.parentContainer) {
|
||||
this.parentContainer.isFocused = true;
|
||||
}
|
||||
this.$refs.input.focus();
|
||||
},
|
||||
onInput() {
|
||||
this.updateValues();
|
||||
this.$emit('change', this.$refs.input.value);
|
||||
this.$emit('input', this.$refs.input.value);
|
||||
},
|
||||
renderFilteredList() {
|
||||
if (this.filterList) {
|
||||
this.items = this.filterList(Object.assign([], this.list), this.query);
|
||||
}
|
||||
this.toggleMenu();
|
||||
},
|
||||
reset() {
|
||||
this.items = [];
|
||||
this.query = '';
|
||||
this.loading = false;
|
||||
},
|
||||
setParentValue(value) {
|
||||
this.parentContainer.setValue(value || this.$refs.input.value);
|
||||
},
|
||||
setParentDisabled() {
|
||||
this.parentContainer.isDisabled = this.disabled;
|
||||
},
|
||||
setParentRequired() {
|
||||
this.parentContainer.isRequired = this.required;
|
||||
},
|
||||
setParentPlaceholder() {
|
||||
this.parentContainer.hasPlaceholder = !!this.placeholder;
|
||||
},
|
||||
setParentUpdateValue(value) {
|
||||
this.setParentValue(value);
|
||||
this.updateValues(value);
|
||||
},
|
||||
setSearchButton() {
|
||||
this.searchButton = this.parentContainer.$el.querySelector('[md-autocomplete-search]');
|
||||
|
||||
if (this.searchButton) {
|
||||
this.searchButton.addEventListener('click', this.makeFetchRequest);
|
||||
}
|
||||
},
|
||||
update() {
|
||||
if (!this.query && !this.list.length) {
|
||||
return this.reset();
|
||||
}
|
||||
|
||||
if (this.minChars && this.query.length < this.minChars) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const queryObject = { [this.queryParam]: this.query };
|
||||
|
||||
return this.makeFetchRequest(queryObject);
|
||||
},
|
||||
toggleMenu() {
|
||||
if (this.items.length) {
|
||||
this.$refs.menu.toggle();
|
||||
}
|
||||
},
|
||||
updateValues(value) {
|
||||
const newValue = value || this.$refs.input.value || this.value;
|
||||
|
||||
this.setParentValue(newValue);
|
||||
this.parentContainer.inputLength = newValue ? newValue.length : 0;
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.searchButton) {
|
||||
this.searchButton.removeEventListener('click', this.makeFetchRequest);
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
this.parentContainer = getClosestVueParent(this.$parent, 'md-input-container');
|
||||
|
||||
if (!this.listIsEmpty) {
|
||||
this.items = Object.assign([], this.list);
|
||||
}
|
||||
|
||||
this.query = this.value;
|
||||
|
||||
this.verifyProps();
|
||||
this.setSearchButton();
|
||||
|
||||
this.setParentDisabled();
|
||||
this.setParentRequired();
|
||||
this.setParentPlaceholder();
|
||||
this.handleMaxLength();
|
||||
this.updateValues();
|
||||
});
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
@ -127,6 +127,18 @@ $input-size: 32px;
|
|||
}
|
||||
}
|
||||
|
||||
.md-input-container {
|
||||
.md-autocomplete,
|
||||
.md-autocomplete .md-menu,
|
||||
.md-autocomplete .md-menu .md-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.md-theme-default.md-input-container .md-autocomplete .md-icon:not(.md-icon-search):after {
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.md-input-container {
|
||||
&.md-input-placeholder {
|
||||
label {
|
||||
|
|
|
|||
58
src/core/utils/autocomplete-common.js
Normal file
58
src/core/utils/autocomplete-common.js
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
export default {
|
||||
props: {
|
||||
debounce: {
|
||||
type: Number,
|
||||
default: 1E3
|
||||
},
|
||||
disabled: Boolean,
|
||||
fetch: {
|
||||
type: Function
|
||||
},
|
||||
filterList: Function,
|
||||
list: {
|
||||
type: Array,
|
||||
default() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
minChars: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
name: String,
|
||||
prepareResponseData: Function,
|
||||
printAttribute: {
|
||||
type: String,
|
||||
default: 'name'
|
||||
},
|
||||
queryParam: {
|
||||
type: String,
|
||||
default: 'q'
|
||||
},
|
||||
required: Boolean
|
||||
},
|
||||
methods: {
|
||||
onFocus() {
|
||||
if (this.parentContainer) {
|
||||
this.parentContainer.isFocused = true;
|
||||
}
|
||||
},
|
||||
onBlur() {
|
||||
this.parentContainer.isFocused = false;
|
||||
this.setParentValue();
|
||||
},
|
||||
verifyProps() {
|
||||
if (!this.parentContainer) {
|
||||
return this.throwErrorDestroy('You should wrap the md-input in a md-input-container');
|
||||
} else if (this.listIsEmpty && this.filterList) {
|
||||
return this.throwErrorDestroy('You should use a `filterList` function prop with the `list` prop');
|
||||
} else if (!this.fetch && this.listIsEmpty) {
|
||||
return this.throwErrorDestroy('You should use a `fetch` function prop');
|
||||
}
|
||||
},
|
||||
throwErrorDestroy(errorMessage) {
|
||||
this.$destroy();
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
}
|
||||
};
|
||||
Loading…
Reference in a new issue