Merge branch 'master' into develop

This commit is contained in:
pablohpsilva 2017-05-21 20:24:07 -03:00
commit 6297347a0c
5 changed files with 451 additions and 2 deletions

View file

@ -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 @@
&lt;md-input v-model=&quot;initialValue&quot;&gt;&lt;/md-input&gt;
&lt;/md-input-container&gt;
&lt;md-input-container&gt;
&lt;label&gt;Autocomplete (with fetch)&lt;/label&gt;
&lt;md-input v-model=&quot;autocompleteValue&quot; :fetch=&quot;fetchFunction&quot;&gt;&lt;/md-input&gt;
&lt;/md-input-container&gt;
&lt;md-input-container&gt;
&lt;label&gt;With label&lt;/label&gt;
&lt;md-input placeholder=&quot;My nice placeholder&quot;&gt;&lt;/md-input&gt;
@ -265,7 +395,19 @@
return {
initialValue: &apos;My initial value&apos;
};
}
},
methods: {
fetchFunction(param) {
// param = { queryParam: query }
// &apos;fetchAutocomplete&apos; should return a Promise.
// md-autocomplete will call fetchAutocomplete and pass
// &apos;param&apos; as an argument.
// the &apos;param&apos; 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>

View file

@ -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);

View 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>

View file

@ -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 {

View 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);
}
}
};