Files
raven-client/ayanova/src/views/home-search.vue
2020-04-09 13:47:40 +00:00

338 lines
9.7 KiB
Vue

<template>
<v-container>
<v-row v-if="formState.ready">
<v-col>
<v-form ref="form">
<v-row justify="start">
<gz-error :errorBoxMessage="formState.errorBoxMessage"></gz-error>
<v-col cols="12" sm="4" lg="4" xl="3">
<v-text-field
v-model="searchPhrase"
clearable
:label="$ay.t('Search')"
ref="searchPhrase"
@change="getDataFromApi()"
hint="text, *xt, te*"
></v-text-field>
</v-col>
<v-col cols="12" sm="4" lg="4" xl="3">
<v-select
v-model="searchObjectType"
:items="selectLists.objectTypes"
item-text="name"
item-value="id"
:label="$ay.t('Object')"
></v-select>
</v-col>
<v-col cols="12" sm="4" lg="4" xl="3">
<v-btn
color="primary"
v-on:click="getDataFromApi()"
value="SEARCH"
>
<v-icon>fa-search</v-icon>
</v-btn>
</v-col>
<v-col cols="12">
<p v-if="!items.length">{{ $ay.t("NoResults") }}</p>
<v-card v-if="items.length" max-width="900">
<v-list>
<v-subheader v-if="maxResultsReturned">
<span>({{ $ay.t("TooManyResults") }})</span>
</v-subheader>
<template v-for="item in items">
<!-- KEY MUST BE UNIQUE INSIDE v-for OR LIST ITEM GOES SNAKEY -->
<v-subheader :key="'s' + item.index" v-if="item.subheader"
><v-icon large class="mr-2">{{ item.icon }}</v-icon
><span class="title">{{
item.subheader
}}</span></v-subheader
>
<v-list-item link :key="item.index">
<v-list-item-content>
<v-list-item-title
@click="openItem(item)"
v-text="item.name"
></v-list-item-title>
<v-list-item-subtitle>{{
item.info
}}</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-btn icon @click="getExcerpt(item)">
<v-icon color="grey lighten-1" large
>fa-info-circle</v-icon
>
</v-btn>
</v-list-item-action>
</v-list-item>
</template>
</v-list>
</v-card>
</v-col>
</v-row>
</v-form>
</v-col>
</v-row>
</v-container>
</template>
<script>
/////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* Xeslint-disable */
/**
* NOTE: In router.js I've made the ayatype parameter OPTIONAL, so make sure to deal with that, it may be undefined, not just zero
* NOTE: going to default max results for simplicity, picklists are 100 but this should be higher so maybe 200 by default
* - PUT SEARCH into main menu, it's constantly there like help with the source as part of the data sent with the click
- I.E. source could be widget from list or widget entry form and so user is searching widgets or source could be a main page where there is no source
so it's empty and search everything
- User clicks on search, it opens a main search form view which is also accessible under HOME section normally so two ways to get there
- PUT SEARCH into HOME section as a main form (make sure it has no search button on *it's* menu)
- Search page allows to select types of objects in an array (maybe, did I code the server that way or single at a time object type)
- Also tags can be selected
- Object type and tag sb clearable with easy single clicks to speed up searching.
- Results are in standard grid list which is sortable or printable or openable to records etc
USE-CASE: No snippet (excerpts) just name and type.
- Excerpt available on demand (user clicks on excerpt button)
This search will return both widgets and users:
{
"phrase": "e*",
"nameOnly": false,
"typeOnly": 0,
"maxResults": 100
}
*
*/
////////////////////////////////////////////////////////////////////////////////////////////////////////////
const FORM_KEY = "home-search";
const API_BASE_URL = "Search/";
const FORM_CUSTOM_TEMPLATE_KEY = "home-search";
const MAX_RESULTS = 200;
export default {
created() {
let vm = this;
initForm(vm)
.then(() => {
vm.formState.ready = true;
window.$gz.eventBus.$on("menu-click", clickHandler);
generateMenu(vm);
if (vm.$route.params.ayatype) {
vm.searchObjectType = vm.$route.params.ayatype;
}
})
.catch(err => {
vm.formState.ready = true;
window.$gz.errorHandler.handleFormError(err, vm);
});
},
beforeDestroy() {
window.$gz.eventBus.$off("menu-click", clickHandler);
},
components: {},
data() {
return {
formCustomTemplateKey: FORM_CUSTOM_TEMPLATE_KEY,
selectLists: {
objectTypes: []
},
searchPhrase: null,
searchObjectType: 0,
items: [],
maxResultsReturned: false,
formState: {
ready: false,
dirty: false,
valid: true,
readOnly: false,
loading: false,
errorBoxMessage: null,
appError: null,
serverError: {}
},
rights: window.$gz.role.defaultRightsObject()
};
},
methods: {
openItem(item) {
window.$gz.eventBus.$emit("openobject", {
type: item.type,
id: item.id
});
},
getExcerpt(item) {
item.info = "***NEW INFO HERE****";
},
getDataFromApi() {
let vm = this;
if (!vm.searchPhrase || vm.formState.loading) {
return;
}
vm.formState.loading = true;
window.$gz.form.deleteAllErrorBoxErrors(vm);
/**
* {
"phrase": "e*",
"nameOnly": false,
"typeOnly": 0,
"maxResults": 100
}
*
*/
window.$gz.api
.upsert(API_BASE_URL, {
phrase: vm.searchPhrase,
nameOnly: false,
typeOnly: !!vm.searchObjectType ? vm.searchObjectType : 0,
maxResults: MAX_RESULTS
})
.then(res => {
if (res.error != undefined) {
vm.formState.serverError = res.error;
window.$gz.form.setErrorBoxErrors(vm);
} else {
vm.maxResultsReturned =
res.data.searchResults.length == MAX_RESULTS;
// vm.items = [];
let newResults = [];
let nDex = 0;
let lastType = -1;
for (let i = 0; i < res.data.searchResults.length; i++) {
let item = res.data.searchResults[i];
if (item.type != lastType) {
//change of type, set subheader props
let tsub = window.$gz._.find(vm.selectLists.objectTypes, [
"id",
item.type
]);
if (tsub != null) {
item.subheader = tsub.name;
} else {
item.subheader = "TYPE " + item.type;
}
item.icon = window.$gz.util.iconForCoreType(item.type);
lastType = item.type;
}
item.info = null;
item.index = ++nDex;
newResults.push(item);
}
vm.items = newResults;
//Update the form status
window.$gz.form.setFormState({
vm: vm,
loading: false
});
}
})
.catch(function handleGetDataFromAPIError(error) {
//Update the form status
window.$gz.form.setFormState({
vm: vm,
loading: false
});
window.$gz.errorHandler.handleFormError(error, vm);
});
}
}
};
/////////////////////////////
//
//
function clickHandler(menuItem) {
if (!menuItem) {
return;
}
let m = window.$gz.menu.parseMenuItem(menuItem);
if (m.owner == FORM_KEY && !m.disabled) {
switch (m.key) {
default:
window.$gz.eventBus.$emit(
"notify-warning",
FORM_KEY + "::context click: [" + m.key + "]"
);
}
}
}
//////////////////////
//
//
function generateMenu(vm) {
let menuOptions = {
isMain: true,
icon: "fa-search",
title: vm.$ay.t("Search"),
helpUrl: "form-home-search",
formData: {
ayaType: window.$gz.type.UserOptions
},
menuItems: []
};
window.$gz.eventBus.$emit("menu-change", menuOptions);
}
/////////////////////////////////
//
//
function initForm(vm) {
return new Promise(function(resolve, reject) {
(async function() {
try {
await fetchTranslatedText(vm);
await populateSelectionLists(vm);
} catch (err) {
reject(err);
}
resolve();
})();
});
}
//////////////////////////////////////////////////////////
//
// Ensures UI translated text is available
//
function fetchTranslatedText(vm) {
return window.$gz.translation.fetch([
"TooManyResults",
"NoResults",
"Object"
]);
}
//////////////////////
//
//
function populateSelectionLists(vm) {
return window.$gz.api.get("EnumList/List/Core").then(res => {
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error, vm);
} else {
vm.selectLists.objectTypes = res.data;
window.$gz.form.addNoSelectionItem(vm.selectLists.objectTypes);
}
});
}
</script>