Hi,
autocomplete would be nice feature.
https://material.google.com/components/text-fields.html#text-fields-auto-complete-text-field
When would you implement this?
Hi. The autocomplete components it's on my backlog. I didn't prioritize yet, but will come after 0.5.0. Thanks.
@marcosmoura I've done one already. You can assign me to this task and I will try the component I have (because I developed it using version 0.5.2) on the new code base and, once I'm done with it, I'll create a PR.
I did not forget to create the css helper classes.
Oh. That's amazing @pablohpsilva! I would love to see this PR! :D
Soon enough @marcosmoura! LOL
I just have to have some free time to spare. I think I'll have that time tonight.
You guys can check my progress on this branch. When I'm done I'll PR it.
Hey @pablohpsilva, I've reviewed your work. Good stuff, man! How far are you with the job? I need the autocomplete for my project but it makes no sense to build from scratch if you are almost done with it. Cheers
@gazpachu I had to stop working for a few days. Now I'm putting my shit together and I'll finish it by tomorrow (that's the plan at least).
Thank you @gazpachu !!! :)
OK, I need you guys help @gazpachu @marcosmoura @breadlesscode . The issue is: I'mm not getting why the autocomplete is not working like the md-input component. I did try so many ways and my final try can be seen right here on my vue-material fork. The Autocomplete code is below. The use of vue-resource will be replaced with something else in the future. Keep in mind the code below IS NOT the same I was preparing on my vue-material fork. The code below is how I created an autocomplete component way before I had the idea to implement it for vue-material project.
The code below should work right out of the box. All you have to have: [email protected] and [email protected] installed. Like I said (and can be seen), the vue-resource dependency will be removed, since the idea will be to send the fetchFunction through a param. The <style> is is plain CSS.
<template lang="html">
<div class="Autocomplete__Wrapper">
<input class="md-input"
v-model="query"
type="text"
autocomplete="off"
@keydown.down="down"
@keydown.up="up"
@keydown.enter="hit"
@keydown.esc="reset"
@blur="onBlur"
@focus="onFocus"
@input="update"/>
<md-button class="md-icon-button Autocomplete__IconButton"
v-if="hasSearchButton"
@click="callFetch">
<md-icon>{{searchIcon}}</md-icon>
</md-button>
<ul class="md-list md-theme-default Autocomplete__List"
v-if="items && hasItems">
<li class="md-list-item md-menu-item md-option Autocomplete__Item"
:class="activeClass(index)"
v-for="(item, index) in items"
@mousedown="hit"
@mousemove="setActive(index)">
<md-button @click.native="hit"
@mousemover="setActive(index)">
{{item.name || item.nome || item.nomExtensao}}
</md-button>
</li>
</ul>
</div>
</template>
<script type="text/javascript">
import { util } from 'vue';
// getClosestVueParent from https://vuematerial.github.io
const getClosestVueParent = (parent, cssClass) => {
if (!parent || !parent.$el) {
return false;
}
if (parent._uid === 0) { // eslint-disable-line no-underscore-dangle
return false;
}
if (parent.$el.classList.contains(cssClass)) {
return parent;
}
return getClosestVueParent(parent.$parent, cssClass);
};
export default {
name: 'MdTypeahead',
props: {
url: {
type: String,
default() {
return 'https://typeahead-js-twitter-api-proxy.herokuapp.com/demo/search';
},
},
queryParam: {
type: String,
default() { return 'q'; },
},
minChars: {
type: Number,
default() { return 3; },
},
fetchFunction: {
type: Function,
},
hasSearchButton: {
type: Boolean,
default() { return false; },
},
searchIcon: {
type: String,
default() { return 'search'; },
},
limit: {
type: Number,
default() { return 0; },
},
},
data() {
return {
items: [],
query: '',
current: -1,
loading: false,
selectFirst: false,
selected: null,
};
},
computed: {
hasItems() {
return this.items.length > 0
},
isEmpty() {
return !this.query
},
isDirty() {
return !!this.query
},
parentContainer() {
if (this.$parent) {
return getClosestVueParent(this.$parent, 'md-input-container');
}
util.warn('You need to use this component wrapped in a \'md-input-container\' component. Material Design guide.', this);
},
hasSelected() {
return !!(this.selected && this.query.length);
},
selectedText() {
return this.selected ?
(this.selected.name || this.selected.nome || this.selected.nomExtensao || this.selected.title) :
'';
},
},
watch: {
query(value) {
this.setParentValue(value);
},
},
methods: {
update() {
if (!this.query) {
return this.reset();
}
if (this.minChars && this.query.length < this.minChars) {
return;
}
this.loading = true;
this.callFetch();
},
callFetch() {
if (this.fetchFunction) {
this.fetchFunction().then((response) => this.handleRequest(response));
return;
}
this.fetch().then((response) => this.handleRequest(response));
},
fetch() {
if (!this.$http) {
return util.warn('You need to install the `vue-resource` plugin', this);
}
if (!this.url) {
return util.warn('You need to set the `src` property', this);
}
const src = this.queryParam
? this.url
: this.url + this.query;
const params = this.queryParam
? Object.assign({ [this.queryParam]: this.query }, this.data)
: this.data;
return this.$http.get(src, { params });
},
handleRequest(response) {
if (this.query) {
let data = (response.data.hasOwnProperty('length')) ?
response.data :
response.data.resposta;
data = this.prepareResponseData ?
this.prepareResponseData(data) :
data;
this.items = this.limit ?
data.slice(0, this.limit) :
data;
this.current = -1;
this.loading = false;
if (this.selectFirst) {
this.down();
}
}
},
reset() {
this.items = [];
this.loading = false;
},
setActive (index) {
this.current = index;
},
activeClass (index) {
return {
active: this.current === index
};
},
hit() {
if (this.current !== -1) {
this.onHit(this.items[this.current]);
}
},
up() {
if (this.current - 1 < 0) {
this.current = this.items.length - 1;
return;
}
this.current -= 1;
},
down() {
if ((this.current + 1) >= this.items.length) {
this.current = 0;
return;
}
this.current += 1;
},
onHit(hit) {
this.selected = hit;
this.query = this.selectedText;
this.$emit('TYPEAHEAD_SELECTED', hit);
this.reset();
},
onFocus() {
this.parentContainer.isFocused = true;
},
onBlur() {
setTimeout(() => {
this.parentContainer.isFocused = false;
this.reset();
}, 1E2);
},
setParentValue(query) {
this.parentContainer.setValue(query || this.$el.query);
},
}
}
</script>
<style lang="css" scoped>
.Autocomplete__Wrapper {}
.md-list.Autocomplete__List {
position: absolute;
width: 100%;
z-index: 10;
max-height: 200px;
overflow-y: scroll;
}
.Autocomplete__Item.active {
background-color: rgba(153, 153, 153, 0.2);
text-decoration: none;
}
.Autocomplete__Item.active .md-button.md-theme-default {
background-color: transparent;
}
.Autocomplete__IconButton {
position: absolute;
right: 0;
top: 12px;
}
.Autocomplete__IconButton .md-icon {
color: rgba(0,0,0,0.7);
}
</style>
@pablohpsilva Hi
Thanks for your code!! I think that these framework is wonderfull..
I have a error with your code @pablohpsilva , when I try use like as component, it generate a error Cannot create property 'isFocused' on boolean 'false'
I'm new in Vue, I'm start 6 days ago and I don't know what util can do, then I don't know why this.parentContainer.isFocused when parentContainer is a function.
Thanks
I'm sorry, I resolved the problem with <md-input-container> before template. :D
Here is the PR #644 :D
Closing this issue as our focus is on the new 1.0.0 version.
Most helpful comment
Hi. The autocomplete components it's on my backlog. I didn't prioritize yet, but will come after 0.5.0. Thanks.