Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Extract search input component #282

Merged
merged 5 commits into from
Jul 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 12 additions & 10 deletions app/assets/stylesheets/nav_bar.scss
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
.nav-search-container {
width: 50% ;
}
.nav-search-container > input {
.nav-search-container div > input {
height: 33px;
width: 100%;
outline: none;
Expand All @@ -72,18 +72,26 @@
font-size: 14px;
padding: 0 10px;
}
.nav-search-container > input:focus{
.nav-search-container div > input:focus{
opacity: 100%;
border-radius: 5px 5px 0 0;
}
.nav-search-container > input::placeholder{
.nav-search-container div > input::placeholder{
color: white;
opacity: 60%;
}
.nav-search-container > input::-ms-input-placeholder{
.nav-search-container div > input::-ms-input-placeholder{
color: white;
opacity: 60%;
}

.nav-search-container #home-search-drop-down{
position: absolute;
z-index: 9999;
font-size: 12px !important;
width: 228px !important;
}

.nav-language{
background-color: transparent;
width: 47px;
Expand Down Expand Up @@ -123,12 +131,6 @@
padding: 0 !important;
}

.nav-search-drop-down{
position: absolute;
z-index: 9999;
font-size: 12px !important;
width: 228px !important;
}

@media (max-width: 1200px){
.top-nav .menu-btn i{
Expand Down
10 changes: 10 additions & 0 deletions app/components/ontology_search_input_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# frozen_string_literal: true

class OntologySearchInputComponent < ViewComponent::Base

def initialize(name: 'search', placeholder: 'Search for an ontology or concept (Ex: Agrovoc ...)', scroll_down: true)
@name = name
@placeholder = placeholder
@scroll_down = scroll_down
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
= render SearchInputComponent.new(placeholder: @placeholder,
name: @name, scroll_down: @scroll_down, ajax_url: '/ajax/ontologies',
item_base_url: '/ontologies/', id_key:'acronym',
actions_links: {search_ontology_content: "/search?query=o", see_all_ontologies: "/ontologies?search=o"}) do |s|

- s.template do
%a{href: "LINK", class: "home-search-ontology-content", 'data-turbo-frame': '_top'}
%p#seached-ontology.home-searched-ontology
NAME(ACRONYM)
%p.home-result-type
TYPE
22 changes: 22 additions & 0 deletions app/components/search_input_component.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# frozen_string_literal: true

class SearchInputComponent < ViewComponent::Base

renders_one :template

def initialize(name: '', placeholder: '', actions_links: {},
scroll_down: true, use_cache: true,
ajax_url:,
item_base_url:,
id_key:)
super
@name = name
@placeholder = placeholder
@actions_links = actions_links
@use_cache = use_cache
@scroll_down = scroll_down
@ajax_url = ajax_url
@item_base_url = item_base_url
@id_key = id_key
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
%div{'data-controller': 'search-input',
'data-search-input-ajax-url-value': @ajax_url,
'data-search-input-item-link-base-value': @item_base_url,
'data-search-input-id-key-value': @id_key,
'data-search-input-cache-value': @use_cache.to_s,
'data-search-input-scroll-down-value': @scroll_down.to_s}
%input#home-search{:name => @name, :placeholder => @placeholder,
:type => "text", 'data-action': 'input->search-input#search blur->search-input#blur',
'data-search-input-target': 'input'}
#home-search-drop-down{'data-search-input-target': 'dropDown', 'data-action': 'mousedown->search-input#prevent'}
- @actions_links.each do |key, value|
%a.home-search-ontology-content#home-search-ontology-content{href: value, 'data-turbo-frame': '_top', 'data-search-input-target': 'actionLink'}
%p.mb-0
%div
%img{src: asset_path("loop.svg")}/
%p
=key.to_s.humanize
%template{'data-search-input-target': 'template'}
= template
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
import {Controller} from "@hotwired/stimulus"
import useAjax from "../../javascript/mixins/useAjax";

// Connects to data-controller="search-input"
export default class extends Controller {
static targets = ["input", "dropDown", "actionLink", "template"]
static values = {
items: Array,
ajaxUrl: String,
itemLinkBase: String,
idKey: String,
cache: {type: Boolean, default: true},
scrollDown: {type: Boolean, default: true}
}

connect() {
this.input = this.inputTarget
this.dropDown = this.dropDownTarget
this.actionLinks = this.actionLinkTargets
this.items = this.itemsValue
}

search() {
this.#searchInput()
}

prevent(event){
event.preventDefault();
}
blur() {
this.dropDown.style.display = "none";
this.input.classList.remove("home-dropdown-active");
}

#inputValue() {
return this.input.value.trim()
}

#useCache() {
return this.cacheValue
}
#scrollDownEnabled(){
return this.scrollDownValue
}

#scrollDown(currentScroll) {
const startPosition = window.pageYOffset;
const distance = 300 - currentScroll;
const duration = 1000;
let start = null;

function scrollAnimation(timestamp) {
if (!start) start = timestamp;
const progress = timestamp - start;
const scrollPosition = startPosition + easeInOutCubic(progress, 0, distance, duration);
window.scrollTo(0, scrollPosition);
if (progress < duration) {
window.requestAnimationFrame(scrollAnimation);
}
}

function easeInOutCubic(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t * t + b;
t -= 2;
return c / 2 * (t * t * t + 2) + b;
}

window.requestAnimationFrame(scrollAnimation);
}

#fetchItems() {
if (this.items.length !== 0 && this.#useCache()) {
this.#renderLines()
} else {
useAjax({
type: "GET",
url: this.ajaxUrlValue + '?search=' + this.#inputValue(),
dataType: "json",
success: (data) => {
this.items = data.map(x => { return {...x, link: (this.itemLinkBaseValue + x[this.idKeyValue])}} )
this.#renderLines()
},
error: () => {
console.log("error")
//TODO show errors
}
})
}
}

#renderLines() {
const inputValue = this.#inputValue();
let results_list = []
if (inputValue.length > 0) {

this.actionLinks.forEach(action => {
const content = action.querySelector('p')
content.innerHTML = inputValue
const currentURL = new URL(action.href, document.location)
currentURL.searchParams.set(currentURL.searchParams.keys().next().value, inputValue)
action.href = currentURL.pathname + currentURL.search
})

this.dropDown.innerHTML = ""
let breaker = 0
for (let i = 0; i < this.items.length; i++) {
if (breaker === 4) {
break;
}
// Get the current item from the ontologies array
const item = this.items[i];

let text = Object.values(item).reduce((acc, value) => acc + value, "")


// Check if the item contains the substring
if (text.toLowerCase().includes(inputValue.toLowerCase())) {
results_list.push(item);
breaker = breaker + 1
}
}

results_list.forEach((item) => {
let link = this.#renderLine(item);
this.dropDown.appendChild(link);
});

this.actionLinks.forEach(x => this.dropDown.appendChild(x))
this.dropDown.style.display = "block";

this.input.classList.add("home-dropdown-active");
if ((window.scrollY < 300) && this.#scrollDownEnabled()) {
this.#scrollDown(window.scrollY);
}

} else {
this.dropDown.style.display = "none";
this.input.classList.remove("home-dropdown-active");
}

}

#renderLine(item) {
let template = this.templateTarget.content
let newElement = template.firstElementChild
let string = newElement.outerHTML

Object.entries(item).forEach( ([key, value]) => {
key = key.toString().toUpperCase()
if (key === 'TYPE'){
value = value.toString().split('/').slice(-1)
}
string = string.replace(key, value.toString())
})

return new DOMParser().parseFromString(string, "text/html").body.firstElementChild
}

#searchInput() {
this.#fetchItems()
}
}
3 changes: 3 additions & 0 deletions app/javascript/component_controllers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import Metadata_selector_component_controller
from "../../components/metadata_selector_component/metadata_selector_component_controller";
import Ontology_subscribe_button_component_controller
from "../../components/ontology_subscribe_button_component/ontology_subscribe_button_component_controller";
import Search_input_component_controller
from "../../components/search_input_component/search_input_component_controller";

application.register("turbo-modal", TurboModalController)
application.register("file-input", FileInputLoaderController)
application.register("select-input", Select_input_component_controller)
application.register("metadata-select", Metadata_selector_component_controller)
application.register("subscribe-notes", Ontology_subscribe_button_component_controller)
application.register("search-input", Search_input_component_controller)
14 changes: 2 additions & 12 deletions app/views/home/index.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,8 @@
= t('.welcome', site: $SITE)
%p
= t('.tagline')
%input#home-search{:name => "search", :placeholder => "Search for an ontology or concept (Ex: Agrovoc ...)", :type => "text", 'data-home-search-target': 'input', 'data-action': 'input->home-search#search blur->home-search#blur'}
#home-search-drop-down{'data-home-search-target': 'dropDown', 'data-action': 'mousedown->home-search#prevent'}
%a.home-search-ontology-content#home-search-ontology-content{href: "#", 'data-home-search-target': 'searchOntologyContent'}
%p#seached-ontology{'data-home-search-target': 'ontology'}
%div
%img{src: asset_path("loop.svg")}/
%p Search ontology content
%a.home-search-ontology-content#home-search-ontologies{href: "#", 'data-home-search-target': 'homeSearchOntologies'}
%p#seached-ontologies{'data-home-search-target': 'searchedOntologies'}
%div
%img{src: asset_path("loop.svg")}/
%p See all ontologies
= render OntologySearchInputComponent.new

.home-body-container
.home-section
%h4 Wanna upload an ontology?
Expand Down
17 changes: 3 additions & 14 deletions app/views/layouts/_topnav.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,8 @@
.nav-search-container{style:'visibility: hidden'}
%input
- else
.nav-search-container{'data-controller': "home-search"}
%input.nav-input{:placeholder => "Search in "+portal_name+" ...", :type => "text", :name => "search", 'data-home-search-target': 'input', 'data-action': 'input->home-search#search blur->home-search#blur'}
#home-search-drop-down.nav-search-drop-down{'data-home-search-target': 'dropDown', 'data-action': 'mousedown->home-search#prevent'}
%a.home-search-ontology-content#home-search-ontology-content{href: "#", 'data-home-search-target': 'searchOntologyContent'}
%p#seached-ontology{'data-home-search-target': 'ontology'}
%div
%img{src: asset_path("loop.svg")}/
%p Search ontology content
%a.home-search-ontology-content#home-search-ontologies{href: "#", 'data-home-search-target': 'homeSearchOntologies'}
%p#seached-ontologies{'data-home-search-target': 'searchedOntologies'}
%div
%img{src: asset_path("loop.svg")}/
%p See all ontologies
.nav-search-container
= render OntologySearchInputComponent.new(placeholder: "Search in "+portal_name+" ...", scroll_down: false)

- if session[:user].nil?
%a.nav-a{:href => "/login"} Login
Expand All @@ -44,7 +33,7 @@
- d.with_section do |s|
- s.header do
Recently Viewed
- for ont in session[:ontologies]
- for ont in Array(session[:ontologies])
- s.item do
= link_to(ont.ontology_name, "/ontologies/#{ont.ontology_acronym}/?p=classes&conceptid=#{CGI.escape(ont.concept)}")
- d.with_section do |s|
Expand Down