How does SUSI AI web bot plugin work

  

In this blog, we’ll learn how SUSI AI web plugin works. Normally, for any bot like Kik bot, we don’t have to worry about the chat boxes or the way chatbot is rendered on screen because all of that is handled by the service on which we are building our bot. So, for these bots, we simply take the text input from user, send a GET request to SUSI server, receive the response and display it.

This is not the case with SUSI AI Web Bot plugin. In this case, there is not Bot platform.

Hence, there are a lot of other things that we need to take care of here. Let’s explore the main ones among them.

Adding bot to website:

The final product that we’re going to provide to the client is going to be just a JavaScript code. Hence the HTML code of bot widget needs to be added to the <body> of the website. This is done usingappend() method of jQuery. The html() method sets or returns the content (innerHTML) of selected elements.

Syntax:

$(selector).append(content)

So we store the HTML code of bot widget in a variable and then returns the content of that variable to the body tag using:

$("body").append(mybot);

Here “mybot” is the variable containing HTML code of bot widget.
The text boxes of user texts and bot texts are added to a HTML element in the same way.

Event handling:

JavaScript’s interaction with HTML is handled through events that occur when the user or the browser manipulates a page. Loading of the page, clicking, pressing a key, closing a window, resizing the window etc are all examples of events.

In the SUSI AI web bot, there are two major events.

  1. Clicking a button

This is required for allowing clicks on send button. Clicking of a button can be done in many ways. There are two major ways that are used in SUSI AI web bot.

Some elements already exist on the webpage. For example – the HTML code of web bot. It is added to the body tag as soon as webpage loads. For these elements we use click(). The click() binding is called “direct” binding which can be attached to the elements that already exists.

Syntax:

$(selector).click(function);

Selector – Class, ID or name of HTML element.
Function – Specifies the function to run when click event occurs.

Some elements are dynamically generated. This means that they are added at a later point of time. For example – the text messages are added when user types in. This happens after loading of page. For this, we have to create “delegated” binding by using on() method.

Syntax:

$(selector).on(event,childSelector,data,function);

Selector – Class, ID or name of HTML element
Event – (Required) Specifies one or more event(s) or namespaces to attach to the selected elements.
childSelector – (Optional) Specifies that the event handler should only be attached to the specified child elements
Data – (Optional) Specifies additional data to pass along to the function
Function – Specifies the function to run when click event occurs.

  1. Pressing the enter key

For identifying which key is pressed, we use key codes. The code for enter key is 13.
The on() method is used for handling this event. It’ll be clear from the following code snippet:

Keyup – Keyboard key is released
Keypress – Keyboard key is pressed

$('#txMessage').on('keyup keypress', function(e) {
        var keyCode = e.keyCode || e.which;
        var text = $("#txMessage").val();
        if (keyCode === 13) {
                if(text == "" ||  $.trim(text) == '') {
                        e.preventDefault();
                        return false;
                } else {
                        $("#chat-input").blur();
                        setUserResponse(text);
                        send(text);
                        e.preventDefault();
                        return false;
                }
        }
});

This is the code used to handle “enter” key event in SUSI AI web bot. Here “txMessage” is the id of text box where user entered text.

Making GET HTTP request to SUSI Server

We use ajax() method of jQuery to perform an asynchronous HTTP request to SUSI Server. Looking at the code will make things more clear:

$.ajax({
        type: "GET",
        url: baseUrl+text,
        contentType: "application/json",
        dataType: "json",
        success: function(data) {
                main(data);
        },
        error: function(e) {
                console.log(e);
        }
});

This is a GET type Ajax request. The endpoint of SUSI API is in url . We can see from dataType that the response received will be a JSON file. If the file is successfully received then main function is called else the error is logged into console.

References :

Variable Font Size Badgeyay

Badgeyay is a simple badge generator that aims for promoting an open-source tool for generation of badges in PDF format. The project has options to choose from predefined set of images or upload a background image. User can choose from set of fonts and color of the same. But now Badgeyay also has option to choose custom font-size in generation of badges.

To implement font size feature,  first, the component that is determining the font of the label has to be identified. The label that determines the text on the badge is the <text> label and within it, the label that determines the properties of the text is <tspan>. So mainly we need to alter the properties in the tspan.

The property that determines the font size for the badge is font-size and its default value is set to 31.25 px. If the property in the labels changed, then we can see the corresponding changes in the PDF generated from the svg.

Now the challenges were:

  • To Determine the font value from the frontend.
  • Using the same for the font-config.
  • Changing the built svg accordingly.

Procedure

  1. Firstly frontend component has to be changed to incorporate a slider to give input for the variable font size. So a range input is inserted with range from 15 px to 45 px and default as 30 px. The size_print label gets changed dynamically to show the value selected from the range slider.
<li>
<input type="radio" name="fontsize" id="font-size-picker"> Choose font size
</li>
<section id="font-size-input" style="display:none;">
<label for="inputFile" id="size_print"></label>
<div>
<input type="range" id="font-size" max=45 min=15 step=5 value=30  class="form-control" name="font_size">
</div>
</section>
  1. After adding the component, form script is changed to add toggle behaviour to the button. For adding the toggling behaviour in the component, checkbox is used and the value of the label is updated dynamically as the slider value is changed.
$("#size_print").text($("#font-size").val() + " px");

      $("#font-size-picker").click(function () {

          if ($(this).is(":checked")) {

              $("#font-size-input").css("display", "block");

          } else {

              $("#font-size-input").css("display", "none");

          }

      });

      $("#font-size").on('input', function () {

          $("#size_print").text($(this).val() + " px");

      });
  1. After completing the work on the frontend, it is necessary to modify the backend too. The method for choosing custom font has to be refactored. It now checks whether the custom font is set or font size variable is set, and creates a config file for fonts which after use gets deleted.
font_config = {}
   # custom font is specified
   if custom_font != '':
       font_config['font'] = custom_font
   if font_size != '':
       font_config['font_size'] = font_size
   if custom_font != '' or font_size != '':
       json_str = json.dumps(font_config)
       print(json_str)
       f = open(os.path.join(app.config['UPLOAD_FOLDER'], 'fonts.json'), "w+")
       f.write(json_str)
       f.close()
  1. The generator class is modified as well to accommodate the changes, by adding a new class attribute called font_size. We find the keys in the dict object loaded from the file and assign the same to class attribute.
if 'font_size' in self.DATA.keys():
               self.font_size = self.DATA['font_size']
  1. Make the necessary change in the svg, so that font size change can be represented in the generated PDF. Replace the old font size with the new font size specified.
if self.font_size:
           content = content.replace("font-size:31.25px",
                                     "font-size:" + str(self.font_size) + "px")
  1. After all the changes, badge generated will have a different font size.

The Pull request for the above change is at this Link

Topics Involved

Working on this Issue (Link) involve following topics:

  • SVG Label manipulation
  • Sending data from Ember frontend to Backend.
  • Javascript for the toggle radio button.

References

  • Extracting map information from the SVG (Link)
  • Python Documentation for class (Link)
  • About Github Pages- (Link)
  • Ajax Serialize method to serialize the form contents – (Link)

Updating the UI of the generator form in Open Event Webapp

  • Add a pop-up menu bar similar to the one shown in Google/Susper6be8d972-72bc-4e12-b27a-219e46608cfc.png
  • Add a version deployment link at the bottom of the page like the one shown in staging.loklak.org.

29072668-b1db-4ef5-8865-c71ef2438433.png

 

  • Implementing the top-bar and the pop-up menu bar

The first task was to introduce a top-bar and a pop-up menu bar in Generator. The top-bar would contain the text Open Event Webapp Generator and an icon button on the right side of it which would show a pop-up menu. The pop-up menu would contain a number of icons which would link to different pages like FOSSASIA blogs and it’s official website, different projects like loklak, SUSI and Eventyay and also to the Webapp Project Readme and issues page.

Creating a top navbar is easy but the pop-up menu is a comparatively tougher. The first step was to gather the gather the small images of the different services. Since this feature had already been implemented in Susper project, we just copied all the icon images from there and copy it into a folder named icons in the open event webapp. Then we create a custom menu div which would hold all the different icons and present it an aesthetic manner. Write the HTML code for the menu and then CSS to decorate and position it! Also, we have to create a click event handler on the pop-up menu button for toggling the menu on and off.

Here is an excerpt of the code. The whole file can be seen here

<div class="custom-navbar">
 <a href='.' class="custom-navtitle">
   <strong>Open Event Webapp Generator</strong> <!-- Navbar Title -->
 </a>
 <div class="custom-menubutton">
   <i class="glyphicon glyphicon-th"></i> <!-- Pop-up Menu button -->
 </div>
 <div class="custom-menu"> <!-- Custom pop-up menu containing different links -->
   <div class="custom-menu-item">
     <a class="custom-icon" href="http://github.com/fossasia/open-event-webapp" target="_blank"><img src="./icons/code.png">
       <p class="custom-title">Code</p></a>
   </div>
   <!-- Code for other links to different projects-->
 </div>
</div>

Here is a screenshot of how the top-bar and the pop-up menu looks!

bb82ba88-4317-46b0-91ec-514499c5cfde.png

  • Adding version deployment info to the bottom

The next task was to add a footer to the page which would contain the version deployment info. The user can click on that link and we can then be taken to the latest version of the code which is currently deployed.

To show the version info, we make use of the Github API. We need to get the hash of the latest commit made on the development branch. We send an API request to the Github requesting for the latest hash and then dynamically add the info and the link received to the footer. The user can then click on that link and will be taken to the latest deployment page of the webapp!

var apiUrl = "https://api.github.com/repos/fossasia/open-event-webapp/git/refs/heads/development";
$.ajax({url: apiUrl, success: function(result){
 var version = result['object']['sha'];
 var versionLink = 'https://github.com/fossasia/open-event-webapp/tree/' + version;
 var deployLink = $('#deploy-link');
 deployLink.attr('href', versionLink);
 deployLink.html(version);
}});

This is how the footer looks after the API Response

44385ad8-e094-490b-8575-a47932aa75c5.png

References:

Implementation of Image Viewer in Susper

We have implemented image viewer in Susper similar to Google.

Before when a user clicks on a thumbnail the images are opened in a separate page, but we want to replace this with an image viewer similar to Google.

Implementation Logic:

1. Thumbnails for images in susper are arranged as shown in the above picture.

2. When a user clicks on an image a hidden empty div(image viewer) of the last image in a row is opened.

3. The clicked image is then rendered in the image viewer (hidden div of the last element in a row).

4. Again clicking on the same image closes the opened image viewer.

5. If a second image is clicked then, if an image is in the same row, it is rendered inside the same image viewer. else if the image is in another row, this closes the previous image viewer and renders the image in a new image viewer (hidden div of the last element of the row)

6. Since image viewer is strictly the hidden empty div of the last element in a row when it is expanded it occupies the position of the next row, moving them further down similar to what we want.

Implementation Code

results.component.html

<div *ngFor="let item of items;let i = index">
 <div class="item">
   <img src="{{item.link}}" height="200px" (click)="expandImage(i)" [ngClass]="'image'+i">
 </div>
 <div class=" item image-viewer" *ngIf="expand && expandedrow === i">
   <span class="helper"></span> <img [src]="items[expandedkey].link" height="200px" style="vertical-align: middle;">
 </div>

</div>

Each thumbnail image will have a <div class=” item image-viewer” which is in hidden state initially.

Whenever a user clicks on a thumbnail that triggers expandImage(i)

results.component.ts

expandImage(key) {
 if (key === this.expandedkey    this.expand === false) {
   this.expand = !this.expand;
 }
 this.expandedkey = key;
 let i = key;
 let previouselementleft = 0;
 while ( $('.image' + i) && $('.image' + i).offset().left > previouselementleft) {
   this.expandedrow = i;
   previouselementleft = $('.image' + i).offset().left;
   i = i + 1;

The expandImage() function takes the unique key and finds which image is the last element is the last image in the whole row, and on finding the last image, expands the image viewer of the last element and renders the selected image in the image viewer.

The source code for the whole implementation of image viewer could be seen at pull: https://github.com/fossasia/susper.com/pull/687/files

Resources:

  1. Selecting elements in Jquery: https://learn.jquery.com/using-jquery-core/selecting-elements/

 

Implementing ICS/ICAL to sync calendars with the event schedule in Open Event Webapp

As an end result, we want to provide a button to the user which will export the whole data of the event schedule to an ICS file and present it to the user for download after clicking the button. The whole work regarding the feature can be seen here.

Instead of implementing the whole specification ourselves which would be much tougher and time-consuming, we looked for some good open source libraries to do a bit of heavy lifting for us. After searching exhaustively for the solution, we came across this library which seemed appropriate for the job. The heart of the library is a function which takes in an object which contains information about the session. It expects information about the start and end time, subject, description and location of the session. Here is an excerpt from the function. The whole file can be seen here

var addEvent = function (session) {
 var calendarEvent = [
   'BEGIN:VEVENT',
   'UID:' + session.uid,
   'CLASS:PUBLIC',
   'DESCRIPTION:' + session.description,
   'DTSTART;VALUE=DATETIME:' + session.begin,
   'DTEND;VALUE=DATE:' + session.stop,
   'LOCATION:' + session.location,
   'SUMMARY;LANGUAGE=en-us:' + session.subject,
   'TRANSP:TRANSPARENT',
   'END:VEVENT'
 ];
 calendarEvents.push(calendarEvent);
};

We need to call the above function for every session in the event schedule. In the schedule template file, we have the jsonData object available which contain all the information about the event. It contains a field called timeList which contains the chronological order of the different sessions taking place throughout the events. The structure of that sub-object is something like this.

[{'slug': '2017-03-20', 'times': {'caption' : '09:00-09:30', 'sessions': [{'title': 'Welcome', 'description': 'Opening of the event', 'start': '09:00', 'end': '09:30'}]}]

So, we define a function for iterating through every session in the above object and adding it to the calendar. We can use most of the attributes directly but have to modify the date and time fields of the session to an appropriate format before adding it. The specification expects time in the ISO 8601 Format. You can read more about the specification here. For eg – If the date is 2017-03-20 and the time is 09:30 then it should be written as 20170320T093000. Here is some part of the function here

function exportICS() {
 var scheduleArr = {{{json timeList}}};
 // Helper functions for converting time to ISO 8601 Format
 function removeDashFromDate(date) {
   return date.replace(/-/g, '');
 }
 function removeColonFromTime(time) {
   return time.replace(/:/g, '');
 }
 // Iteration through the object and adding every session to the calendar
 scheduleArr.forEach(function(scheduleDay) {
   var date = removeDashFromDate(scheduleDay.slug);
   scheduleDay.times.forEach(function(time) {
     time.sessions.forEach(function(session) {
       var sessObj = {};
       sessObj.begin = date + 'T' + removeColonFromTime(session.start) + '00';
       sessObj.stop = date + 'T' + removeColonFromTime(session.end) + '00';
       sessObj.subject = session.title;
       sessObj.description = session.description;
       sessObj.location = session.location;
       cal.addEvent(sessObj);
     });
   });
 });
 cal.download('calendar', 'ics', false); // Download the ics file of the calendar
}

After defining the function, we add a button for starting the download of the whole schedule of the event. On clicking, we call the function which initiates the download after all the sessions of the event have been added.

<span class="schedule-download">
 <button type="button" class="btn btn-default export-schedule"><i class="fa fa-calendar" aria-hidden="true"></i></button>
</span>

$('.export-schedule').click(function() {
 exportICS();
});

Here is the export schedule button

65203af9-3962-4ab5-9655-3250bf2253a0.png

This is the download pop-up of the ICS file of the event.

Screenshot from 2017-08-10 21-56-16.png

After importing it in the Google calendar

Screenshot from 2017-08-10 23-01-22.png

References

Implementing Logging Functionality in Open Event Webapp

  • INFO: Info statements give information about the task currently being performed by the webapp
  • SUCCESS: Success statements give the information of a task being successfully completed
  • ERROR: Error statements give information about a task failing to complete. These statements also contain a detailed error log

Along with the type of the statement, the object also contains information about the task. For all types of statements, there is a field called smallMessage containing short information about the task. For the ERROR statements where more information is required to see what went wrong, the message object has an additional field called largeMessage which holds detailed information about the event.

We also create a new file called buildlogger.js and define a function for creating log statements about the tasks being performed by generator and export it. The function creates a message object from the arguments received and then return it to the client under the buildLog event via the socket IO.

exports.addLog = function(type, smallMessage, socket, largeMessage) {
 var obj = {'type' : type, 'smallMessage' : smallMessage, 'largeMessage': largeMessage};
 var emit = false;
 if (socket.constructor.name === 'Socket') {
   emit = true;
 }
 if (emit) {
   socket.emit('buildLog', obj);
 }
};

Most of the steps of the generation process are defined in the generator.js file. So, we include the logging file there and call the addLog function for sending logs messages to the client. All the different steps like cleaning temporary folders, copying assets, fetching JSONs, creating the website directory, resizing images etc have multiple log statements for their inception and successful/erroneous completion. Below is an excerpt from the cleaning step.

var logger = require('./buildlogger.js');
 async.series([
   (done) => {
     console.log('CLEANING TEMPORARY FOLDERS\n');
     logger.addLog('Info', 'Cleaning up the previously existing temporary folders', socket);
     fs.remove(distHelper.distPath + '/' + appFolder, (err) => {
       if(err !== null) {
         // Sending Error Message when the remove process failed
         logger.addLog('Error', 'Failed to clean up the previously existing temporary folders', socket, err);
       }
       // Success message denoting the completion of the step
       logger.addLog('Success', 'Successfully cleaned up the temporary folders', socket);
       done(null, 'clean');
     });
   }
 )]

But we have only done the server side work now. We also have to handle the message on the client side. We send the message object to the client under the event buildLog and set up a listener for that event to catch the sent message. After the message object is received on the client side, we extract the information out of that object and then display it on the website. We have a div having an id of buildLog for displaying the log information. The content of the message is dynamically added to it as soon as it is received from the server. All the client side logic is handled in the form.js file.

socket.on('buildLog', function(data) {
   var spanElem = $('<span></span>'); // will contain the info about type of statement
   var spanMess = $('<span></span>'); // will contain the actual message
   var aElem = $('<button></button>'); // Button to view the detailed error log
   var paragraph = $('<p></p>'); // Contain the whole statement
   var divElem; // Contain the detailed error log
   spanMess.text(data.smallMessage);
   spanElem.text(data.type.toUpperCase() + ':');
   paragraph.append(spanElem);
   paragraph.append(spanMess);
   divElem.text(data.largeMessage);
   paragraph.append(aElem);
   paragraph.append(divElem);
   $('#buildLog').append(paragraph); // Div containing all the log messages
};

This is how the logs look on the client side. They are loaded on the go in real time as and when the events occur.

image (1).jpg

Resources:

Implementing Tracks Filter in Open Event Webapp using the side track name list

4f451d29-c6c2-44be-9ef7-d91a45fc1eb0.png

On Clicking the Design, Art, Community Track

ff85d907-512b-4a41-be49-888bbd17bf83.png

But, it was not an elegant solution. We already had a track names list present on the side of the page which remained unused. A better idea was to use this side track names list to filter the sessions. Other event management sites like http://sched.org follow the same idea. The relevant issue for it is here and the major work can be seen in this Pull Request. Below is the screenshot of the unused side track names list.

5b15a297-fd5e-4c23-bc1b-dbed193db0f4.png

The end behavior should be something like this, the user clicks on a track and only sessions belonging to the track should be visible and the rest be hidden. There should also be a button for clearing the applied filter and reverting the page back to its default view. Let’s jump to the implementation part.

First, we make the side track name list and make the individual tracks clickable.

<div class="track-names col-md-3 col-sm-3"> 
  {{#tracknames}}
    <div class="track-info">
      <span style="background-color: {{color}};" 
      class="titlecolor"></span>
      <span class="track-name" style="cursor: pointer">{{title}}
      </span>
    </div>
  {{/tracknames}}
</div>

f45f1591-937d-4e2f-9245-237cfaf3af0d.png

Now we need to write a function for handling the user click event on the track name. Before writing the function, we need to see the basic structure of the tracks page. The divs with the class date-filter contain all the sessions scheduled on a given day. Inside that div, we have another div with class tracks-filter which contains the name of the track and all the sessions of that track are inside the div with class room-filter.

Below is a relevant block of code from the tracks.hbs file

<div class="date-filter">
  // Contains all the sessions present in a single day
  <div class="track-filter row">
    // Contains all the sessions of a single track
    <div class="row">
      // Contains the name of the track
      <h5 class="text">{{caption}}</h4>
    </div>
    <div class="room-filter" id="{{session_id}}">
      // Contain the information about the session
    </div>
  </div>
</div>

We iterate over all the date-filter divs and check all the track-filter divs inside it. We extract the name of the track and compare it to the name of the track which the user selected. If both of them are same, then we show that track div and all the sessions inside it. If the names don’t match, then we hide that track div and all the content inside it. We also keep a variable named flag and set it to 0 initially. If the user selected track is present on a given day, we set the flag to 1. Based on it, we decide whether to display that particular day or not. If the flag is set, we display the date-filter div of that day and the matched track inside it. Otherwise, we hide the div and all tracks inside it.

$('.track-name').click(function() {
  // Get the name of the track which the user clicked
  trackName = $(this).text();
  // Show the button for clearing the filter applied and reverting to the default view
  $('#filterInfo').show();
  $('#curFilter').text(trackName);
  // Iterate through the divs and show sessions of user selected track
  $('.date-filter').each(function() {
    var flag = 0;
    $(this).find('.track-filter').each(function() {
      var name = $(this).find('.text').text();
      if(name != trackName) {
        $(this).hide();
        return;
      }
      flag = 1;
      $(this).show();
    });
    if (flag) {
     $(this).show();
    } else {
      $(this).hide();
    }
  });
});

On Selecting the Android Track of FOSSASIA Summit, we see something like this

935f208b-c17c-4d41-abb6-7197c003d962.png

Now the user may want to remove the filter. He/she can just click on the Clear Filter button shown in the above screenshot to remove the filter and revert back to the default view of the page.

$('#clearFilter').click(function() {                                                                                                   
  trackFilterMode = 0;                                                                                                                 
  display();                                                                                                                           
  $('#filterInfo').hide();                                                                                                             
});

Back to the default view of the page

2ff61ccf-17cf-4595-9c2f-e6825de549f7.png

References:

Implementing Search Bar Using GitHub API In Yaydoc CI

In Yaydoc’s, documentation will be generated by typing the URL of the git repository to the input box from where user can generate documentation for any public repository, they can see the preview and if they have access, they can push the documentation to the github pages on one click. But In Yaydoc CI user can register the repository only if he has access to the specific repository, so I decided to show the list to the repository where user can select from the list but then it also has one problem that Github won’t give us all the user repository data in one api hit and then I made a search bar in which user can search repository and can register to the Yaydoc CI.

var search = function () {
  var username = $("#orgs").val().split(":")[1];
  const searchBarInput = $("#search_bar");
  const searchResultDiv = $("#search_result");

  searchResultDiv.empty();
  if (searchBarInput.val() === "") {
    searchResultDiv.append('<p class="text-center">Please enter the repository name<p>');
    return;
  }

  searchResultDiv.append('<p class="text-center">Fetching data<p>');

  $.get(`https://api.github.com/search/repositories?q=user:${username}+fork:true+${searchBarInput.val()}`, function (result) {
    searchResultDiv.empty();
    if (result.total_count === 0) {
      searchResultDiv.append(`<p class="text-center">No results found<p>`);
      styles.disableButton("btnRegister");
    } else {
      styles.disableButton("btnRegister");
      var select = '<label class="control-label" for="repositories">Repositories:</label>';
      select += '<select class="form-control" id="repositories" name="repository" required>';
      select += `<option value="">Please select</option>`;
      result.items.forEach(function (x){
        select += `<option value="${x.full_name}">${x.full_name}</option>`;
      });
      select += '</select>';
      searchResultDiv.append(select);
    }
  });
};

$(function() {
  $("#search").click(function () {
    search();
  });
});

In the above snippet I have defined search function which will get executed when user clicks the search button. The search function will get the search query from input box, if the search query is empty it’ll show the message as “Please enter repository name”, if it is not empty it’ll hit the GitHub API to fetch user repositories. If the GitHub returns empty array it’ll show “No results found”. In between searching time “Fetching data” will be shown.

$('#search_bar').on('keyup keypress', function(e) {
    var keyCode = e.keyCode || e.which;
    if (keyCode === 13) {
      search();
    }
  });

  $('#ci_register').on('keyup keypress', function(e) {
    var keyCode = e.keyCode || e.which;
    if (keyCode === 13) {
      e.preventDefault();
      return false;
    }
  });

Still we faced some problem, like on click enter button form is automatically submitting. So I’m registering event listener. In that listener I’m checking whether the key code is 13 or not. Key code 13 represent enter key, so if the key code is 13 then i’ll prevent the form from submitting. You can see the working of the search bar in the Yaydoc CI.

Resources:

Implementing a Collapsible Responsive App Bar of SUSI Web Chat

In the SUSI Web Chat application we wanted to make few static pages such as: Overview, Terms, Support, Docs, Blog, Team, Settings and About. The idea was to show them in app bar. Requirements were  to have the capability to collapse on small viewports and to use Material UI components. In this blog post I’m going to elaborate how we built the responsive app bar Using Material UI components.

First we added usual Material UI app bar component  like this.

             <header className="nav-down" id="headerSection">
             <AppBar
               className="topAppBar"
title={<a href={this.state.baseUrl} ><img src="susi-white.svg" alt="susi-logo"  className="siteTitle"/></a>}
               style={{backgroundColor:'#0084ff'}}
               onLeftIconButtonTouchTap={this.handleDrawer}
               iconElementRight={<TopMenu />}
             />
             </header>

We added SUSI logo instead of the text title using below code snippet and linked it to the home page like this.

title={<a href={this.state.baseUrl} ><img src="susi-white.svg" alt="susi-logo"  className="siteTitle"/></a>}

We have defined “this.state.baseUrl” in constructor and it gets the base url of the web application.

this.state = {
       openDrawer: false, 
baseUrl: window.location.protocol + '//' + window.location.host + '/'
       };

We need to open the right drawer when we click on the button on top left corner. So we have to define two methods to open and close drawer as below.

   handleDrawer = () => this.setState({openDrawer: !this.state.openDrawer});
   handleDrawerClose = () => this.setState({openDrawer: false});

Now we have to add components that we need to show on the right side of the app bar. We connect those elements to the app bar like this. “iconElementRight={}”

We defined “TopMenu” Items like this.

   const TopMenu = (props) => (
   <div>
     <div className="top-menu">
     <FlatButton label="Overview"  href="/overview" style={{color:'#fff'}} className="topMenu-item"/>
     <FlatButton label="Team"  href="/team" style={{color:'#fff'}} className="topMenu-item"/>
     </div>

We added FlatButtons to place links to other static pages. After all we needed a FlatButton that gives IconMenu to show login and signup options.

     <IconMenu {...props} iconButtonElement={
         <IconButton iconStyle={{color:'#fff'}} ><MoreVertIcon /></IconButton>
     }>
     <MenuItem primaryText="Chat" containerElement={<Link to="/logout" />}
                   rightIcon={<Chat/>}/>
     </IconMenu>
   </div>
   );

After adding all these correctly you will see this kind of an app bar in your application.

Now our app bar is ready. But it does not collapse on small viewports.
So we planned to hide flat buttons on small sized screens and show the menu button. For that we used media queries.

@media only screen and (max-width: 800px){
   .topMenu-item{ display: none !important;  }
   .topAppBar button{ display: block  !important; }
}

This is how we built the responsive app bar using Material UI components. You can check the preview from this url. If you are willing to contribute to SUSI Web Chat here is the GitHub repository.

Resources:

  • Material UI Components: http://www.material-ui.com/#/components/
  • Learn More about media queries: https://www.w3schools.com/css/css_rwd_mediaqueries.asp

Implementing Hiding App Bar of SUSI Web Chat Application

In the SUSI Web Chat application we got a requirement to build a responsive app bar for static pages and there was another requirement to  show and hide the app bar when user scrolls. Basically this is how it should work: The app bar should be hidden after user scrolls down to a certain extent. When user scrolls up, It should appear again.

First we tried readymade node packages to do this task. But these packages are hard to customize. So we planned to make this feature from the sketch. We used Jquery for this. This is how we built this.

First we installed jQuery package using this command.

npm install jquery

Next we imported it on top of the application like this.

import $ from 'jquery'

We have discussed about this app bar and how we made it in previous blog post. Our app bar is like this.

             <header className="nav-down" id="headerSection">
             <AppBar
               className="topAppBar"
               title={<img src="susi-white.svg" alt="susi-logo"
               className="siteTitle"/>}
               style={{backgroundColor:'#0084ff'}}
               onLeftIconButtonTouchTap={this.handleDrawer}
               iconElementRight={<TopMenu />}
             />
             </header>

We have to use these HTML elements to write jQuery code. But we can’t refer HTML elements before it renders. So we have to define it soon after the render method executes. We can do it using “React LifeCycle” method. We have to add our code into the “componentDidMount()” method.
This is how we used jQuery inside the “componentDidMount()” lifeCycle method. Here we assigned the height of the App Bar using “$(‘header’).outerHeight();”

componentDidMount(){
     var didScroll;
     var lastScrollTop = 0;
     var delta = 5;
     var navbarHeight = $('header').outerHeight();

Here we assigned the height of the app bar to “navbarHeight” variable.

     $(window).scroll(function(event){
         didScroll = true;
     });

In this part we checked whether the user has scrolled or not. If user scrolled we set the value of “didScroll” to “true”.
Now we have to define what to do if user has scrolled.

     function hasScrolled() {
         var st = $(window).scrollTop();
         if(Math.abs(lastScrollTop - st) <= delta){

             return;
         }

Here we get the absolute scrolled height. If the height is less than the delta value we defined, it does not do anything. It just returns.

         if (st > lastScrollTop && st > navbarHeight){
             $('header').removeClass('nav-down').addClass('nav-up');
         } else if(st + $(window).height() < $(document).height()) {
             $('header').removeClass('nav-up').addClass('nav-down');
         }
         lastScrollTop = st;
     }

Here we hide the app bar after user scrolled down more than the height of the app bar. If we need to change the height which app bar should disappear, we just need to add a value to the condition like this.

if (st > lastScrollTop && st > navbarHeight + 200){

If the user scrolled down more than that value we change the class name of the element “nav-down” to “nav-up”.
And we change the className “nav-up” to “nav-down” when user is scrolling up.
We defined CSS classes in the stylesheet to do these things and the animations of action.

header {
   background: #f5b335;
   height: 40px;
   position: fixed;
   top: 0;
   transition: top 0.5s ease-in-out;
   width: 100%;
}

.nav-up {
   top: -100px;
}

We have defined the things which we need to do when user scrolls.
Now we have to call this function if user has scrolled

     setInterval(function() {
         if (didScroll) {
             hasScrolled();
             didScroll = false;
         }
     }, 2500);
   }

If the “didcroll” is “true” we execute the “hasScrolled()” function. And set 2500 millisecond time interval. Because of that app bar does not hide right after user scrolls. It triggers the function after 2.5 seconds later.
This is how we built the scroll bar hiding feature using react JS and jQuery.

Resources:

  • Learn more about React LifeCycle Methods http://busypeoples.github.io/post/react-component-lifecycle/
  • Use jQuery in React component: https://medium.com/@shuvohabib/using-jquery-in-react-component-the-refs-way-969de9aa651f