mirror of
https://github.com/frappe/books.git
synced 2024-11-13 00:46:28 +00:00
fix: Searchbar
- Use Dropdown in Searchbar - Doctypes and Reports searchable
This commit is contained in:
parent
f975446801
commit
bddc6bc6e5
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user