2
0
mirror of https://github.com/frappe/books.git synced 2024-11-14 01:14:03 +00:00

fix: Searchbar

- Use Dropdown in Searchbar
- Doctypes and Reports searchable
This commit is contained in:
Faris Ansari 2019-11-20 01:59:44 +05:30
parent f975446801
commit bddc6bc6e5

View File

@ -1,164 +1,112 @@
<template> <template>
<div v-on-outside-click="clearInput" class="relative"> <div v-on-outside-click="clearInput" class="relative">
<div class="rounded-6px relative flex items-center overflow-hidden"> <Dropdown :items="suggestions" class="text-sm h-full">
<div class="absolute flex justify-center w-8"> <template
<SearchIcon class="w-3 h-3 stroke-current text-gray-600" /> v-slot="{
</div> toggleDropdown,
<input highlightItemUp,
type="search" highlightItemDown,
class="bg-gray-200 text-sm p-1 pl-8 focus:outline-none" selectHighlightedItem
@click="focus(0)" }"
@keydown.down="navigate('down')"
@keydown.up="navigate('up')"
placeholder="Search..."
autocomplete="off"
spellcheck="false"
v-model="inputValue"
@keydown.enter="searchOrSelect"
/>
</div>
<div v-if="inputValue" class="absolute bg-white text-sm rounded shadow-md">
<div
v-for="(doc, i) in suggestion"
:key="i+1"
:ref="i+1"
:route="doc.route"
:class="{ 'border-t uppercase text-xs text-gray-600 mt-3 first:mt-0': doc.seperator, 'bg-gray-200': isFocused(i+1) && !doc.seperator }"
class="px-3 py-2 rounded-lg"
@click.native="routeTo(doc.route)"
> >
<div :class="doc.seperator ? 'small' : ''">{{ doc.name }}</div> <div
<!-- <div class="small ml-auto">{{ doc.doctype }}</div> --> class="rounded-6px relative flex items-center overflow-hidden h-full"
</div> >
</div> <div class="absolute flex justify-center w-8">
<feather-icon name="search" class="w-3 h-3 text-gray-800" />
</div>
<input
type="search"
class="bg-gray-200 text-sm p-1 pl-8 focus:outline-none h-full w-56"
placeholder="Search..."
autocomplete="off"
spellcheck="false"
v-model="inputValue"
@input="search"
ref="input"
@focus="$toggleDropdown = toggleDropdown"
@keydown.up="highlightItemUp"
@keydown.down="highlightItemDown"
@keydown.enter="selectHighlightedItem"
@keydown.tab="toggleDropdown(false)"
@keydown.esc="toggleDropdown(false)"
/>
</div>
</template>
</Dropdown>
</div> </div>
</template> </template>
<script> <script>
import frappe from 'frappejs'; import frappe from 'frappejs';
import SearchIcon from '@/components/Icons/Search'; import Dropdown from '@/components/Dropdown';
export default { export default {
data() { data() {
return { return {
inputValue: '', inputValue: '',
suggestion: [], searchList: [],
currentlyFocused: 0 suggestions: [],
$toggleDropdown: null
}; };
}, },
components: { components: {
SearchIcon Dropdown
},
mounted() {
this.makeSearchList();
}, },
methods: { methods: {
focus(key) { async search() {
this.currentlyFocused = key % (this.suggestion.length + 1); this.$toggleDropdown && this.$toggleDropdown(true);
},
navigate(dir) {
const seperatorIndexes = this.suggestion.map((item, i) => {
if (item.seperator) return i;
});
let nextItem = this.currentlyFocused + (dir === 'up' ? -1 : 1);
if (seperatorIndexes.includes(this.currentlyFocused)) {
nextItem = this.currentlyFocused + (dir === 'up' ? -2 : 2);
}
this.focus(nextItem);
},
isFocused(i) {
if (i === this.currentlyFocused) {
return true;
}
return false;
},
async searchOrSelect() {
if (this.currentlyFocused != 0) {
this.routeTo(this.$refs[this.currentlyFocused][0].$attrs.route);
return;
}
const searchableDoctypes = frappe.getDoctypeList({ this.suggestions = this.searchList.filter(d => {
isSingle: 0, let key = this.inputValue.toLowerCase();
isChild: 0 return d.label.toLowerCase().includes(key);
}); });
const documents = await this.getDocuments(searchableDoctypes);
const doctypes = this.getDoctypes(searchableDoctypes); if (this.suggestions.length === 0) {
const reports = this.getReports(); this.suggestions = [{ label: 'No results found.' }];
this.suggestion = documents.concat(doctypes).concat(reports); }
if (this.suggestion.length === 0)
this.suggestion = [{ seperator: true, name: 'No results found.' }];
}, },
clearInput(e) { clearInput(e) {
this.inputValue = ''; this.inputValue = '';
this.$emit('change', null); this.$emit('change', null);
}, },
async getDocuments(searchableDoctypes) { makeSearchList() {
const promises = searchableDoctypes.map(doctype => { const doctypes = this.getDoctypes();
return frappe.db.getAll({ const reports = this.getReports();
doctype,
filters: { let searchList = [].concat(doctypes).concat(reports);
name: ['includes', this.inputValue], this.searchList = searchList.map(d => {
keywords: ['like', this.inputValue] if (d.route) {
}, d.action = () => this.routeTo(d.route);
fields: ['name']
});
});
const data = await Promise.all(promises);
// data contains list of documents, sorted according to position of its doctype in searchableDoctypes
const items = [];
items.push({
seperator: true,
name: 'Documents'
});
for (let i = 0; i < data.length; i++) {
// i represents doctype position in searchableDoctypes
if (data[i].length > 0) {
for (let doc of data[i]) {
let doctype = searchableDoctypes[i];
items.push({
doctype,
name: doc.name,
route: `/edit/${doctype}/${doc.name}`
});
}
} }
} return d;
if (items.length !== 1) return items;
return [];
},
getDoctypes(searchableDoctypes) {
const items = [{ seperator: true, name: 'Lists' }];
let filteredDoctypes = searchableDoctypes.filter(doctype => {
return (
doctype.toLowerCase().indexOf(this.inputValue.toLowerCase()) != -1
);
}); });
filteredDoctypes = filteredDoctypes.map(doctype => { },
var titleCase = doctype.replace(/([A-Z])/g, ' $1'); getDoctypes() {
titleCase = titleCase.charAt(0).toUpperCase() + titleCase.slice(1); let doctypes = Object.keys(frappe.models).sort();
let doctypeMetas = doctypes.map(doctype => frappe.getMeta(doctype));
let searchableDoctypes = doctypeMetas.filter(meta => {
return !meta.isSingle && !meta.isChild;
});
return searchableDoctypes.map(meta => {
return { return {
name: titleCase, label: meta.label || meta.name,
route: `/list/${doctype}` route: `/list/${meta.name}`,
group: 'List'
}; };
}); });
if (filteredDoctypes.length > 0) return items.concat(filteredDoctypes);
return [];
}, },
getReports() { getReports() {
const items = [{ seperator: true, name: 'Reports' }];
let reports = require('../../reports/view'); let reports = require('../../reports/view');
reports = Object.values(reports); return Object.values(reports).map(report => {
let filteredReports = reports.filter(report => {
return (
report.title.toLowerCase().indexOf(this.inputValue.toLowerCase()) !=
-1
);
});
filteredReports = filteredReports.map(report => {
return { return {
name: report.title, label: report.title,
route: `/report/${report.method}` route: `/report/${report.method}`,
group: 'Reports'
}; };
}); });
if (filteredReports.length > 0) return items.concat(filteredReports);
return [];
}, },
routeTo(route) { routeTo(route) {
this.$router.push(route); this.$router.push(route);