Using @Output EventEmitter to Hide Search Suggestions in Angular for Susper Web App

Problem: In Susper the suggestions box doesn’t hide when there are no suggestions. To fix this, we have used @Output to create interaction between the search bar and suggestions box.

Susper gives suggestions to the user when user types a query. These suggestions are retrieved from the suggest.json endpoint from Yacy server.

We have a separate component for searching a query and a separate component for showing suggestions (auto-complete.component.ts). The architectural link between the query box, suggestion box and the results page is a bit complicated.

The search bar and the auto-complete component doesn’t interact directly. Whenever a new query is entered, the search bar triggers an action with a payload including the query. On receiving the new query, auto-complete component calls Yacy server to get suggestions from the endpoint and display them inside the suggestion box. Whenever a user searches making a new query, the search bar implementation opens the suggestion box even if there are no results. So there should be a way to inform search bar component that suggestions box has received empty results and search bar could hide the suggestions box.

To achieve this we used @Output to emit an event

@Output() hidecomponent: EventEmitter<any> = new EventEmitter<any>();

autocomplete.component.ts:-

this.autocompleteservice.getsearchresults(query).subscribe(res => {
 if (res) {
   if (res[0]) {
     this.results = res[1];
     if (this.results.length === 0) {
       this.hidecomponent.emit(1);
     } else {
       this.hidecomponent.emit(0);
     }
}

 

Then in search bar component, this is binded to a function hidesuggestions() which takes care of hiding the suggestion box.

searchbar.component.html

<app-auto-complete (hidecomponent)="hidesuggestions($event)" id="auto-box" [hidden]="!ShowAuto()"></app-auto-complete>

 

searchbar.component.ts

hidesuggestions(data: number) {
 if (data === 1) {
   this.displayStatus = 'hidebox';
 } else {
   this.displayStatus = 'showbox';
 }
}
ShowAuto() {
 return (this.displayStatus === 'showbox');
}

 

Here you could see that the auto-complete component’s hidden attribute in searchbar.component.ts is binded with ShowAuto() function which takes care about the interaction and hides the suggestions box whenever there are no results.

Below a GIF shows how this suggestions feature is working on Susper

Source code related to this implementation is available at this pull

References:

Continue Reading

Multiple Page Rendering on a Single Query in Susper Angular Front-end

Problem: Susper used to render a new results page for each new character input. It should render a single page for the final query as reported in issue 371. For instance, the browser’s back button shows five pages for each of the five characters entered as a query.

Solution: This problem was arising due to code:

this.router.navigate(['/search'], {queryParams: this.searchdata});

Before we have this one line in search-bar component which gets called on each character entry

Fix:To fix this issue we required calling router.navigate only when we receive results and not on each character input.

So, we first removed the line which was cause of this issue from search-bar component and replaced it with

this.store.dispatch(new queryactions.QueryServerAction(query));

 

This triggers a QueryServer action, and make a request to Yacy end point for search results.

Now in app.component.ts , we get subscribed to resultscomponentchange$ which gets called only when new search results are received and hence we navigate to a new page after the resultscomponentchange subscription is called.

this.resultscomponentchange$ = store.select(fromRoot.getItems);
this.resultscomponentchange$.subscribe(res => {
 if (this.searchdata.query.length > 0) {
   this.router.navigate(['/search'], {queryParams: this.searchdata});
 }

});
this.wholequery$ = store.select(fromRoot.getwholequery);
this.wholequery$.subscribe(data => {
 this.searchdata = data;
});
if (localStorage.getItem('resultscount')) {
 this.store.dispatch(new queryactions.QueryServerAction({'query': '', start: 0, rows: 10, search: false}));
}

 

 

Finally, this problem got fixed and now there is only one page being rendered for a valid search. Source code for this implementation is available in this pull.

Resources:

Continue Reading

Using RouterLink in the Susper Angular Frontend to Speed up the Loading Time

In Susper, whenever the user clicks on some links, the whole application used to load again, thereby taking more time to load the page. But in Single Page Applications (SPAs) we don’t need to load the whole application. In Fact, SPAs are known to load internal pages faster than traditional HTML web pages. To achieve this we have to inform the application that a link will redirect the user to an internal page. So that the application doesn’t reload completely and reinitializes itself. In angular, this can be done by replacing href with routerLink for the tag.

Routerlink when used with tag syntactically as

<a routerLink="/contact" routerLinkActive="active">Contact</a>

doesn’t load the whole page instead it asks the server for only the contact component and renders it in place of <router-outlet></router-outlet>

This happens through an ajax call to the server asking for only contact component, thereby reducing the time it takes and doesn’t show a whole complete reload of the page.

Below time graph shows requests made when a tag with href was clicked.

If you observe it takes more than 3 seconds to load the page.

But when you use [routerLink] as an attribute for navigation, you find the page being displayed in just a blink.

What we have done in Susper?

In Susper, on issue #167, @mariobehling has noticed that there are some links which are loading slowly. On looking at the issue and a test run of the issue, I found that the problem is with the loading of the whole page, thereby immediately checked with the tag and found that a “href” attribute was used instead of “[routerLink]” angular attribute. I made a pull changing href to “[routerLink]” thereby speeding up Susper to around 3x faster than before.

https://github.com/fossasia/susper.com/pull/234/files

References

Continue Reading

Implementing a suggestion box in Susper Angular JS Front-end

In Susper, we have implemented a suggestion box for our input box. This was done using Yacy suggest API. This is how the box looks:

In this blog, let us see how to implement such a box using html, css, twitter-bootstrap and typescript. You can also check the code at the Susper repository.

The html code is simple and straightforward:

 

class=“suggestion-box” *ngIf=“results”>

</div>

A few points to notice :

  • *ngIf=”results” ensures that the box is displayed only when it has suggestions to display and not otherwise
  • The [routerLink] and [queryParams] attributes together link every result to the search page, with the correct query.

This is the css code :

a {
text-decoration: none;
}.suggestions {
font-family: Arial, sans-serif;
font-size: 17px;
margin-left: 2.4%;
}.query-suggestions:hover {
background: #E3E3E3;
}.suggestion-box{
width: 635.2px;
max-width: 100%;
border: 1px solid rgba(150,150,150,0.3);
background-color: white;
margin-left: -25.7px;
position: absolute;
boxshadow: 0px 0.2px 0px;
}

A few points to notice again:

    • Box-shadow: This gives the drop up a shadow effect, which looks really nice, the first 3 parameters are for dimensions (X-offset, Y-offset, Blur). The rgba specifies color, with parameters as (red-component, green-component, blue-component, opacity).
    • Text-decoration: This attribute is used to add/remove decoration like underline for links.
    • Font-family: The font-family mentioned here is Arial, if Arial is unavailable sans-serif is used.

In the typescript file, there are two major tasks:

  1. Splice the results if greater than five. A neat suggestion-box should have a maximum of five results, hence we splice the results:

this.results.concat(res[0]);
if ( this.results.length > 5) {
this.results = this.results.splice (0, 5);
  1. Hide the suggestion-box if there are no suggestions from the API:
@Output() hidecomponent: EventEmitter<any> = new EventEmitter<any>();

this.query$.subscribe( query => {
if (query) {
this.autocompleteservice.getsearchresults(query).subscribe(res => {
if (res) {
this.results = res[1];
if (this.results.length === 0) {
this.hidecomponent.emit(1);
} else {
this.hidecomponent.emit(0);
}

If you want a more elaborate picture, you can view the entire html, css and typescript files of the auto-complete component.

This tutorial, http://4dev.tech/2016/03/tutorial-creating-an-angular2-autocomplete/ is very useful in case you want to implement auto complete suggestion feature from scratch.

In addition, this stack overflow thread has some interesting insights too: https://stackoverflow.com/questions/35881815/implementing-autocomplete-for-angular2

Continue Reading

Implementing Themes in Angular JS for Susper

Adding themes to any website, makes it more interesting to use, and also helps customize the website according to personal preferences. This blog deals with how we implemented themes in Susper.
Susper offers the following themes

  • Default
  • Dark
  • Basic
  • Contrast
  • Terminal

This is how some of the themes look
Dark:

Contrast:  
Terminal:
Lets go through a step by step guide how to implement this:

  1. Add a Themes service  (In app/src/theme.service.ts in Susper)

Here is the code snippet:

import { Injectable } from ‘@angular/core’;

@Injectable()
export class ThemeService {

public titleColor: string;
public linkColor: string;
public descriptionColor: string;
public backgroundColor: string;

constructor() { }

}

  1.  Create a component for themes, and define functions for various themes in the .ts file.(src/app/theme/theme.component.ts in Susper)

Here is the example code:

import { Component, OnInit } from ‘@angular/core’;
import { ThemeService } from ‘../theme.service’;@Component({
selector: ‘app-theme’,
templateUrl: ‘./theme.component.html’,
styleUrls: [‘./theme.component.css’]
})
export class ThemeComponent implements OnInit {constructor(
private themeService: ThemeService
) { }ngOnInit() {
}darkTheme() {
this.themeService.backgroundColor = ‘#FFFFFF’;
this.themeService.titleColor = ‘#050404’;
this.themeService.linkColor = ‘#7E716E’;
this.themeService.descriptionColor = ‘#494443’;
}

defaultTheme() {
this.themeService.backgroundColor = ‘#FFFFFF’;
this.themeService.titleColor = ‘#1a0dab’;
this.themeService.linkColor = ‘#006621’;
this.themeService.descriptionColor = ‘#545454’;
}

basicTheme() {
this.themeService.backgroundColor = ‘#FFFFFF’;
this.themeService.titleColor = ‘#1a0dab’;
this.themeService.linkColor = ‘#494443’;
this.themeService.descriptionColor = ‘#7E716E’;
}

In the above code, the first few lines  include the constructor, which defines the theme service, and include a default function that runs as soon as the page is initialized.

We then see three kinds of themes implemented, dark, default and contrast. Let us examine the darkTheme:

darkTheme() {
this.themeService.backgroundColor = ‘#FFFFFF’;
this.themeService.titleColor = ‘#050404’;
this.themeService.linkColor = ‘#7E716E’;
this.themeService.descriptionColor = ‘#494443’;
}
  • The first line sets the background color of the screen(to white).
  • The second line is used to set the color of all the titles of the search results
  • The third line is used to set the link/url color
  • The fourth line sets the description color
    1.   Link the appropriate attributes in your html pages, using [style.’css-attribute’]

(src/app/results/results.component.html in Susper)

Following this example, you can link different parts of the html file to the attributes in your theme service and you are done!
If implementing this in a project like Susper, a few points of caution:

  • Make sure you write your spec.ts file well, and add your component for proper compilation and testing.
  • Do not forget to import the service into any component before you use it in its html files.

Resources 

Continue Reading

Implementing a Pagination Bar in Susper Angular JS Frontend

Searching on Susper most queries will have a number of search results. These results may span over several pages, and hence, a well designed pagination bar becomes essential for easy navigation. This blog deals with how we created the pagination bar in Susper. This is how the pagination bar in Susper looks:

There are a maximum of 10 pages displayed at any point of time.
At times you may have lesser results too:

where you can either directly click on a page or use the Previous and Next buttons for navigation. As you move along the pages, this is how it looks:

We will now see how we go about creating this bar.
The html source code( with a line by line explanation of the code):

<div class=“pagination-bar”>
<div class=“pagination-property” *ngIf=“noOfPages>1”>
<nav arialabel=“Page navigation” *ngIf=“(items$ | async)?.length!=0”>
  • Previous button is used to decrease the current page number, clicking on the first S in ‘Susper’ also accomplishes this task. Notice that this button is to be displayed only if you are not already on the first page of Susper.
<li class=“page-item” *ngFor=“let num of getNumber(maxPage)”><span class=“page-link” *ngIf=“presentPage>=4 && presentPage-3+num<=noOfPages” [class.active_page]=”getStyle(presentPage-3+num)”
(click)=”getPresentPage(presentPage-3+num)” href=“#”>

[class.active_page]=”getStyle(presentPage-3+num)” class=“page-text”>U

class=“page-number”>{{presentPage-3+num}}</span></span>
<span class=“page-link” *ngIf=“presentPage<4 && num<=noOfPages” [class.active_page]=”num+1 == presentPage (click)=”getPresentPage(num+1)”
href=“#”>

[class.active_page]=”num+1 == presentPage class=“page-text”>U</span>

class=“page-number”>{{num+1}}</span></span></li>

  • The above lines specify that we want to update our pagination bar only if the user has navigated more than the first three pages  
  • class.active_page gives correct css for the active page and getStyle() tells whether that particular page is active or not  
  • getPresentPage() uses the current page number to calculate which results to display  

<li class=“page-item”><span class=“page-link” (click)=”incPresentPage()”>

class=“page-text next”>SPER

</span></li>
<li class=“page-item2” *ngIf=“!getStyle(maxPage)”><span class=“spl” (click)=”incPresentPage()”>

class=“arrow”>>
class=“side-text”>Next

</span></li></ul>
</div>
</nav>
</div>

  • This part is used to increment the present page, and show the next/SPER message. Notice that it should be displayed only if you are not already on the last page

The attached typescript functions:

incPresentPage() {
this.presentPage = Math.min(this.noOfPages, this.presentPage + 1);
this.getPresentPage(this.presentPage);
}decPresentPage() {
this.presentPage = Math.max(1, this.presentPage 1);
this.getPresentPage(this.presentPage);
}
  • incPresentPage() increments the present page, if possible(that is you have not reached your maximum pages already).
  • decPresentPage() decrements the present page, if possible(that is you have not reached page 1 already).

getStyle(page) {
return ((this.presentPage) === page);
}
  • getStyle() returns true if passed page is the present page, useful to apply different css classes to the active page

getPresentPage(N) {
this.presentPage = N;
let urldata = Object.assign({}, this.searchdata);
urldata.start = (this.presentPage 1) * urldata.rows;
this.store.dispatch(new queryactions.QueryServerAction(urldata));}
  • getPresentPage () is used to change the page, and navigate to the page, with the correct rows of data

And you are done! You now have a working pagination bar to plugin at the bottom of your search results!

You can check out this elaborate tutorial on implementing Pagination bars from w3 schools : https://www.w3schools.com/css/css3_pagination.asp

Continue Reading

Making the footer-navigation bar stick to the bottom in Susper

In Susper, we have a navigation bar as a footer, as shown:

Previously this footer-navbar would appear immediately after the content, even if it was in the middle of the page. This is how the footer would appear:Since this could be a very common problem on a lot of websites, this blog deals with a simple hack for it.  

  1. Design your footer navbar as you please. You need not use any predefined bootstrap classes. You also need not specify any parameters regarding the position of the navbar (relative, absolute etc.).
  2. Enclose the rest of the data on your webpage in a div tag, do not forget to mention a class name or id name for the tag.  
  3. Now comes the simplest trick: Set a minimum height for your div! It is advisable to use vh (viewport-height) as your unit of measurement since it is easy to estimate how much of the viewport needs to be covered by your width.

This is how it is used in Susper:

Remember that each vh corresponds to one-hundredth of the viewport total height. So 100 vh here will mean a minimum height of the full viewport.

You can check the Susper repository for the source code or go through this link for alternate ways to create a sticky footer at the bottom.

Continue Reading

Refactor of Dropdown Menu in Susper

The first version of the Susper top menu was providing links to resources and tutorials. In the next version of the menu, we were looking for a menu with more colorful icons, a cleaner UI design and a menu that should appear on the homepage as well. In this blog, I will discuss about refactoring the dropdown menu. This is how earlier dropdown of Susper looks like:

We decided to create a separate component for the menu DropdownComponent.

At first, I created a drop down menu with matching dimensions similar to what Google follows. Then, I gave padding: 28px to create similar UI to market leader. This will make a dropdown menu with clean UI design. I replaced the old icons with colorful icons. In the dropdown we have:

  • Added more projects of FOSSASIA like eventyay, loklak, susi and main website of FOSSASIA. Here how it looks now :

The main problem I faced was aligning the content inside the dropdown and they should not get disturbed when the screen size changes.
I kept the each icon dimensions as 48 x 48 inside drop down menu. I also arranged them in a row. It was easy to use div element to create rows rather than using ul and li tags which were implemented earlier.

To create a horizontal grey line effect, I used the hr element. I made sure, padding remained the same above and below the horizontal line.

At the end of drop down menu, @mariobehling suggested instead of writing ‘more’, it should redirect to projects page of FOSSASIA.

This is how I worked on refactoring drop down menu and added it on the homepage as well.

Resources

Continue Reading

Using MockBackend to test Susper Angular Front-end Code

In this blog, I’ll be sharing how we are testing services which we are using in Susper development.We’re using Angular 4 in our project as tech stack and we use Jasmine for testing purpose.

Tests are written to avoid issues which occur again and again. For example: Since we have implemented knowledge graph, we faced a lot of issues like:

  • When a user enters a query, search results appear but knowledge graph does not appear.
  • When a fresh query is entered or page is refreshed, knowledge graph does not appear.
  • The API which we have used is not responding.

We overcome this issue by writing test. The data is being taken with the help of an API. So, it will require testing using HTTP. Instead of testing like this, there is a better way by using MockBackend.

Testing with MockBackend is a more sensible approach. This allows us to mock our responses and avoid hitting the actual backend which results in boosting our testing.

To use the MockBackend feature, it requires creating a mock. For knowledge-service it looks like this:

export const MockKnowledgeApi {
  results: {
    uri: ‘http://dbpedia.org/resource/Berlin’,
    label: ‘Berlin’,
  }
  MaxHits: 5
};

To use the MockBackend feature, import MockBackend, MockConnection, BaseRequestOptions and MockKnowledgeApi.

import { MockBackend, MockConnection } from ‘@angular/http/testing’;
import { MockKnowledgeApi } from ‘./shared/mock-backend/knowledge.mock’;
import { BaseRequestOptions } from ‘@angular/http’;

Create a mock setup. In this case, we will create mock setup w.r.t HTTP because data from API is being returned as HTTP. If data, is being returned in JSON format, create a mock setup w.r.t jsonp.

const mockHttp_provider = {
  provide: Http,
  deps: [MockBackend, BaseRequestOptions],
  useFactory: (backend: MockBackend,options: BaseRequestOptions) => {
    return new Http(backend, options);
}
};

Now, describe the test suite. Inside, describe the function, don’t import MockConnection. It will throw error since it is only used to create a fake backend. It should look like this:

providers: [
  KnowledgeapiService,
  MockBackend,
  BaseRequestOptions,
  mockHttp_provider,
]
Define service as KnowledgeService and backend as MockBackend. Inject both the services in beforeEach() function.
Now to actually test the service, create a query.

const searchquery = ‘Berlin’;

The written specs should look like this. I won’t go much in detail here, but I’ll cover up the key points of code.

it(‘should call knowledge service API and return the result’, () => {
backend.connections.subscribe((connection: MockConnection) => {
const options = new ResponseOptions({
body: JSON.stringify(MockKnowledgeApi)
});connection.mockRespond(new Response(options));
expect(connection.request.method).toEqual(RequestMethod.Get);
});
Here, mockRespond will mock our response and it will test whether the service is working or not. Already, we have defined a query.

It should have a link to API and should be equal to searchquery which we have defined already as ‘Berlin’.

expect(connection.request.url).toBe(
`http://lookup.dbpedia.org/api/search/KeywordSearch` +
`?&QueryString=${searchquery}`
);

At last, it will check if it’s working or not. If it’s working, then test case will pass. Please note, it will not hit the actual backend.

service.getsearchresults(searchquery).subscribe((res) => {
expect(res).toEqual(MockKnowledgeApi);
);
In this way, we have written tests for knowledge graph to avoid future issues. We will be adding tests like these for other services as well which are being used in Susper project.

Resources

Continue Reading

Filtering out a query in Solr for Susper

In this blog, I would like to share you how- we have implemented tabs feature in Susper.

When a search is attempted, results are fetched in results.component. Since there are many kinds of results being fetched from the server, how should we filter them in separate tabs? For example under the tab ‘All’: All kind of results are being fetched but .png or .mp3 which should be loaded under tabs ‘Images’ and ‘Videos’. The yacy server is written in Java on Solr technology. Going through Solr documents, we found the solution to filter queries using ‘fq’ parameter.

What is fq parameter?

‘fq’ stands for filter query. It returns the document which we want or in simple words returns filtered document. Documents will only be included in the result if they are at the intersection of the document sets resulting from each fq.

How we did we use fq parameter in Susper?

To explain it better, I’m sharing a code snippet here which has been written to filter out queries :

if (query[‘fq’]) {
  if (query[‘fq’].includes(‘png’)) {
    this.resultDisplay = ‘images’;
    urldata.fq = ‘url_file_ext_s:(png+OR+jpeg+OR+jpg+OR+gif)’;
} else if (query[‘fq’].includes(‘avi’)) {
    this.resultDisplay = ‘videos’;
} else {
  this.resultDisplay = ‘all’;
}
}

What is ongoing here is that we have subscribed to a query and used if and else conditions. query[‘fq’] simply filters out the query which has been subscribed. include(‘png’) and include(.avi) is clear that we are filtering out the documents with these tabs. This action happens when the user clicks on a tab.

If the user clicks on images tab: files with .png are displayed. If the user clicks on videos tab: files with .avi are displayed.

url_file_ext_s:() is simply another solr syntax to provide the document format.

The flowchart above explains more clearly, how fq parameter is filtering out documents without affecting the total number of documents which yacy fetches by indexing web pages based on the query.

Resources:

Continue Reading
Close Menu