Multiple Tickets: User Interface

An Event can have multiple tickets for different purposes. For instance an Arts Exhibition can have multiple Galleries. The Organizer might be interested in assigning a ticket (let’s assume paid) for each Gallery. The user can then buy tickets for the Galleries that he wishes to attend. The feature that Multiple Tickets really provide is exclusiveness. Let’s say Gallery1 has a shorter area (by land) than others. Obviously the Organizer would want fewer people to be present there than other Galleries. To do this, he can create a separate ticket for Gallery1 and specify a shorter sales period. He can also reduce the Maximum number of order that a user can make (max_order). If we would have implemented single ticket per event, this wouldn’t have been possible.

Tickets at Wizard

To handle multiple tickets at the wizard, proper naming of input tags was required. Since the number of tickets that can be created by the user was unknown to the server we had to send ticket field values as lists. Also at the client-side a way was required to let users create multiple tickets.

User Interface

A ticket can be of three types: Free, Paid and Donation. Out of these, only the Paid tickets need a Price. The Tickets holder could be a simple table, with every ticket being a table row. This became more complex afterwards, when more details about the ticket needed to be displayed. A ticket would then be two table rows with one of them (details) hidden.

Ticket holder can be a simple bootstrap table:

<table class="table tickets-table">
    <thead>
        <tr>
            <th>Ticket Name</th>
            <th>Price</th>
            <th>Quantity</th>
            <th>Options</th>
        </tr>
    </thead>
    <tbody>
      <!-- Ticket -->
      <tr>
        <!-- Main info -->
      </tr>
      <tr>
        <!-- More details (initially hidden) -->
      </tr>
      <!-- /Ticket -->
    </tbody>
</table>

To make ticket creation interactive, three buttons were needed to create the above three tickets. The type-name doesn’t not necessarily have to be shown to the user. It could be specified with the Price. For Paid ticket, the Price input element would be a number. For Free and Donation tickets, a Price input element wasn’t required. We could specify an element displaying one of the two types: Free or Donation.

Here’s the holder table with a Free Ticket and a Donation Ticket:

Screenshot from 2016-08-09 16:51:06

Since only the Price field is changing in the three types of tickets, I decided to create a template ticket outside of the form and create a JavaScript function to create one of the tickets by cloning the template.

A Free Ticket with its edit options opened up. You can see other details about the ticket in the second table row.

Screenshot from 2016-08-09 16:52:14

This is a simplified version of the template. I’ve removed common bootstrap elements (grid system) including some other fields.

<div id="ticket-template">
<tr>
    <td>
        <input type="hidden" name="tickets[type]">
        <input type="text" name="tickets[name]" class="form-control" placeholder="Ticket Name" required="required" data-uniqueticket="true">
        <div class="help-block with-errors"></div>
    </td>
    <td>
        <!-- Ticket Price -->
    </td>
    <td>
        <input type="number" min="0" name="tickets[quantity]" class="form-control" placeholder="100" value="{{ quantity }}">
    </td>
    <td>
        <div class="btn-group">
            <a class="btn btn-info edit-ticket-button" data-toggle="tooltip" title="Settings">
                <i class="glyphicon glyphicon-cog"></i>
            </a>
            <a class="btn btn-info remove-ticket-button" data-toggle="tooltip" title="Remove">
                <i class="glyphicon glyphicon-trash"></i>
            </a>
        </div>
    </td>
</tr>
<tr>
    <td colspan="4">
        <div class="row" style="display: none;">
            <!-- Other fields including Description, Sales Start and End time,
              Min and Max Orders, etc.
            -->
        </div>
    </td>
</tr>
</div>

Like I said, the Price element of ticket will make the type obvious for the user, so a type field does not need to be displayed. But the type field is required by the server. You can see it specified as hidden in the template.

The function to create a Ticket according to the type:

I’ve commented snippets to make it easy to understand.

function createTicket(type) {
    /* Clone ticket from template */
    var $tmpl = $("#ticket-template").children().clone();

    var $ticket = $($tmpl[0]).attr("id", "ticket_" + String(ticketsCount));
    var $ticketMore = $($tmpl[1]).attr("id", "ticket-more_" + String(ticketsCount));

    /* Bind datepicker and timepicker to dates and times */
    $ticketMore.find("input.date").datepicker();
    $ticketMore.find("input.time").timepicker({
        'showDuration': true,
        'timeFormat': 'H:i',
        'scrollDefault': 'now'
    });

    /* Bind iCheck to checkboxes */
    $ticketMore.find("input.checkbox.flat").iCheck({
        checkboxClass: 'icheckbox_flat-green',
        radioClass: 'iradio_flat-green'
    });

    /* Bind events to Edit (settings) and Remove buttons */
    var $ticketEdit = $ticket.find(".edit-ticket-button");
    $ticketEdit.tooltip();
    $ticketEdit.on("click", function () {
        $ticketMore.toggle("slow");
        $ticketMore.children().children(".row").slideToggle("slow");
    });

    var $ticketRemove = $ticket.find(".remove-ticket-button");
    $ticketRemove.tooltip();
    $ticketRemove.on("click", function () {
        var confirmRemove = confirm("Are you sure you want to remove the Ticket?");
        if (confirmRemove) {
            $ticket.remove();
            $ticketMore.remove();
        }
    });

    /* Set Ticket Type field */
    $ticket.children("td:nth-child(1)").children().first().val(type);

    /* Set Ticket Price field */
    var html = null;
    if (type === "free") {
        html = '';
    } else if (type === "paid") {
        html = '';
    } else if (type === "donation") {
        html = '';
    }
    $ticket.children("td:nth-child(2)").html(html);

    /* Append ticket to table */
    $ticket.hide();
    $ticketMore.children().children(".row").hide();
    $ticketsTable.append($ticket, $ticketMore);
    $ticket.show("slow");

    ticketsCount += 1;
  }

The flow is simple. Clone the template, bind events to various elements, specify type and price fields and then append to the ticket holder table.

Screenshot from 2016-08-09 17:00:59

We use the Datepicker and Timepicker JavaScript libraries for date and time elements. So fields using these widgets need to have methods called on the elements. Also, we use iCheck for checkboxes and radio buttons. Apart from these, the Edit-Ticket and Remove-Ticket buttons also need event handlers. Edit-Ticket button toggles the Ticket Details segment (second tr of a ticket). Remove-Ticket deletes the ticket. After the Price and Type fields are set, the ticket is appended to the holder table with slow animation.

Continue Reading

Building interactive elements with HTML and javascript: Interact.js + resizing

{ Repost from my personal blog @ https://blog.codezero.xyz/building-interactive-elements-with-html-and-javascript-interact-js-resizing/ }

In a few of the past blog posts, we saw about implementing resizing with HTML and javascript. The functionality was pretty basic with simple resizing. In the last blog post we saw about interact.js.

interact.js is a lightweight, standalone JavaScript module for handling single-pointer and multi-touch drags and gestures with powerful features including inertia and snapping.

Getting started with Interact.js

You have multiple option to include the library in your project.

  • You can use bower to install (bower install interact) (or)
  • npm (npm install interact.js) (or)
  • You could directly include the library from a CDN (https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.2.6/interact.min.js).
Implementing resizing

Let’s create a simple box using HTML. We’ll add a class called resizable to it so that we can reference it to initialize Interact.js

<div class="resizable">  
    Use right/bottom edge to resize
</div>

We need to create an interact instance. Once the instance is created, we have to call the resizable method on it to add resize support to the div.

interact('.resizable')
  .resizable({
    edges: { right: true, bottom: true }
  })
  .on('resizemove', function (event) {
    

  });

Inside the resizable method, we can pass configuration options. The edgesconfig key allows us to specify on which all edges, resizing should be allowed. Right now, we have allowed on the right and bottom edges. Similarly we can have resizing support in the top and left edges too.

The resizemove event is triggered by interact every time the user tries to resize the div. From the event, we can get the box that is being resized, (i.e) the target by accessing event.target.

The event object also provides us event.rect.width and event.rect.height which is the width and height of the div after resizing. We’ll not set this as the width of the div so that, the user is able to see the width change.

var target = event.target;
    // update the element's style
    target.style.width  = event.rect.width + 'px';
    target.style.height = event.rect.height + 'px';

We can also instruct Interact.js to preserve the aspect ratio of the box by adding an option preserveAspectRatio: true to the configuration object passed to resizable method during initialization.

JavaScript
interact('.resizable')
  .resizable({
    edges: { right: true, bottom: true }
  })
  .on('resizemove', function (event) {
    var target = event.target;

    // update the element's style
    target.style.width  = event.rect.width + 'px';
    target.style.height = event.rect.height + 'px';
  });

Resizing and drag-drop (with Interact.js) were used to create the Scheduler tool at Open Event. The tool allows event/track organizers to easily arrange the sessions into their respective rooms by drag-drop and also to easily change the timings of the events by resizing the event block. The entire source code of the scheduler can be viewed at app/static/js/admin/event/scheduler.js in the Open Event Organizer server’s GitHub repository.

Demo:
https://jsfiddle.net/xdfocdty/

Continue Reading

Creating Dynamic Footer with Popover

In Open-Event Webapp generator, the track page height varies according to the popover that appears on hovering the tracks. The problem with this design was the footer of the page that always remains static and produce a bad UI to user.

12

So, I have decided to make footer dynamic so that it varies it’s position according to the popover appeared on hover. The approach was a bit tricky but the diagram below will make it easy to understand.

Dynamic footer

The following code will work on hovering the track.

//popover.js 

var outerContheight= $('.main').offset().top + $('.main').outerHeight();
var tracknext= $(track).next();
var tracktocheck= track.offset().top + track.outerHeight() + 
 tracknext.outerHeight() + 15;
 var shift= tracktocheck - outerContheight;
 if(shift > 0){
 
 $('.footer').css({
 'position':'absolute',
 'top': outerContheight + shift,
 'width':'100%',
 'z-index': '999'
 })
 }

If shift > 0 which is calculated as shown in the above code it means that the footer needs to be shifted and hence we shift the footer by setting absolute position in CSS. Else we set position: static for footer.

 $('.footer').css({
 'position':'static'
 })

After following the above approach the footer position changes according to the popover. Here is the screencast for the approach.

 

Continue Reading

User Notifications

The requirement for a notification area came up when I was implementing Event-Role invites feature. For not-existing users that were not registered in our system, an email with a modified sign-up link was sent. So just after the user signs up, he will be accepted as that particular role. Now for users that were already registered to our platform a dedicated area was needed to let the user know that he has been invited to be a role at an event. Similar areas were needed for Session invites, Call for papers, etc. To take care of these we thought of implementing a separate notifications area for the user, where such messages could be sent to registered users. Issue

Base Model

I kept base db model for a user notification very basic. It had a user field that would be a Foreign key to a User class object. title and message would contain the actual data that the user would read. message can contain HTML tags, so if someone wants to display the notification with some markup he could store that in the message. The user might also want to know when a notification was received. The received_at field stores a datetime object for the same purpose.

There is also has_read field that was later added. It stores a boolean value that tells if the user has marked the notification as Read.

class Notification(db.Model):
    """
    Model for storing user notifications.
    """

    id = db.Column(db.Integer, primary_key=True)

    user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
    user = db.relationship('User', backref='notifications')

    title = db.Column(db.String)
    message = db.Column(db.Text)
    action = db.Column(db.String)
    received_at = db.Column(db.DateTime)
    has_read = db.Column(db.Boolean)

    def __init__(self,
                 user,
                 title,
                 message,
                 action,
                 received_at,
                 has_read=False):
        self.user = user
        self.title = title
        self.message = message
        self.action = action
        self.received_at = received_at
        self.has_read = has_read

action field helps the Admin identify the notification. Like if it is a message for Session Schedule change or an Event-Role invite. When a notification is logged, the administrator could tell what exactly the message is for.

Unread Notification Count

The user must be informed if he has received a notification. This info must be available at every page so he doesn’t have to switch over to the notification area to check for new ones. A notification icon at the navbar perhaps.

Screenshot from 2016-07-19 02:00:47

The data about this notification count had to be available at the navbar template at every page. I decided to define it as a method in the User class. This way it could be displayed using the User object. So if the user was authenticated, the icon with the notification count could be displayed.

class User(db.Model):
    """User model class
    """
    # other stuff
    
    def get_unread_notif_count(self):
        return len(Notification.query.filter_by(user=self,
                                                has_read=False).all())
{% if current_user.is_authenticated %}
    <!-- other stuff -->

    <li>
             <a class="info-number" href="{{ url_for('profile.notifications_view') }}">
                  <i class="fa fa-envelope-o"></i>
                  <span class="badge bg-green">{{ current_user.get_unread_notif_count() | default('', true) }}</span>
             </a>
    </li>

    <!-- other stuff -->

{% endif %}

If the count is zero, count number is not displayed.

Possible Enhancement

The notification count comes with the HTML generated by the template at the server. So to check for new notifications the user must either refresh the page or travel to another page. To show newly received notifications without refreshing the page the WebSocket API can be used. I’ve it in my bucket list and I’ll implement it soon.

Continue Reading

Building interactive elements with HTML and javascript: Interact.js + drag-drop

{ Repost from my personal blog @ https://blog.codezero.xyz/building-interactive-elements-with-html-and-javascript-interact-js-drag-drop }

In a few of the past blog posts, we saw about implementing drag-drop andresizing with HTML and javascript. The functionality was pretty basic with a simple drag-and-drop and resizing. That is where, a javascript library called as interact.js comes in.

interact.js is a lightweight, standalone JavaScript module for handling single-pointer and multi-touch drags and gestures with powerful features including inertia and snapping.

With Interact.js, building interactive elements is like a eating a piece of your favorite cake – that easy !

Getting started with Interact.js

You have multiple option to include the library in your project.

  • You can use bower to install (bower install interact) (or)
  • npm (npm install interact.js) (or)
  • You could directly include the library from a CDN (https://cdnjs.cloudflare.com/ajax/libs/interact.js/1.2.6/interact.min.js).

Implementing a simple draggable

Let’s start with some basic markup. We’ll be using the draggable class to enable interact.js on this element.

<div id="box-one" class="draggable">  
  <p> I am the first Box </p>
</div>  
<div id="box-two" class="draggable">  
    <p> I am the second Box </p>
</div>

The first step in using interact.js is to create an interact instance. Which you can create by using interact('<the selector>'). Once the instance is created, you’ll have to call the draggable method on it to enable drag. Draggable accepts a javascript object with some configuration options and some pretty useful callbacks.

// target elements with the "draggable" class
interact('.draggable')  
  .draggable({
    // enable inertial throwing
    inertia: true,
    // keep the element within the area of it's parent
    restrict: {
      restriction: "parent",
      endOnly: true,
      elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
    },
    // enable autoScroll
    autoScroll: true,
    // call this function on every dragmove event
    onmove: dragMoveListener,
  });

  function dragMoveListener (event) {
    var target = event.target,
        // keep the dragged position in the data-x/data-y attributes
        x = (parseFloat(target.getAttribute('data-x')) || 0) + event.dx,
        y = (parseFloat(target.getAttribute('data-y')) || 0) + event.dy;

    // translate the element
    target.style.webkitTransform =
    target.style.transform =
      'translate(' + x + 'px, ' + y + 'px)';

    // update the posiion attributes
    target.setAttribute('data-x', x);
    target.setAttribute('data-y', y);
  }

Here we use the onmove event to move the box according to the dx and dyprovided by interact when the element is dragged.

Implementing a simple drag-drop

Now to the above draggable, we’ll add a drop zone into which the two draggable boxes can be dropped.

<div id="dropzone" class="dropzone">You can drop the boxes here</div>

Similar to a draggable, we first create an interact instance. Then we call the dropzone method on to tell interact that, that div is to be considered as a dropzone. The dropzone method accepts a json object with configuration options and callbacks.

// enable draggables to be dropped into this
interact('.dropzone').dropzone({  
  // Require a 50% element overlap for a drop to be possible
  overlap: 0.50,

  // listen for drop related events:

  ondropactivate: function (event) {
    // add active dropzone feedback
    event.target.classList.add('drop-active');
  },
  ondragenter: function (event) {
    var draggableElement = event.relatedTarget,
        dropzoneElement = event.target;

    // feedback the possibility of a drop
    dropzoneElement.classList.add('drop-target');
  },
  ondragleave: function (event) {
    // remove the drop feedback style
    event.target.classList.remove('drop-target');
  },
  ondrop: function (event) {
    event.relatedTarget.textContent = 'Dropped';
  },
  ondropdeactivate: function (event) {
    // remove active dropzone feedback
    event.target.classList.remove('drop-active');
    event.target.classList.remove('drop-target');
  }
});

Each event provides two important properties. relatedTarget which gives us the DOM object of the draggable that is being dragged. And target which gives us the DOM object of the dropzone. We can use this to provide visual feedback for the user when he/she is dragging.

Demo:

https://jsfiddle.net/niranjan94/yqwc4hqz/2/

Continue Reading

Optimization with SASS

The problem with using CSS is that it can become repetitive when used in large projects. Like using same values of margins, paddings, radius. SASS provides a way to store these values in ONE variable and to use that variable instead.

SASS provides various concepts that optimize CSS. Like discussed above using variables for all the color names and fonts will remove the repetition. A piece of code from Open-Event-Webapp is shown below for declaring variable names.

//config.scss

@charset "UTF-8";
 
// Colors
$black: #000;
$white: #fff;
$red: #e2061c;
$gray-light: #c9c8c8;
$gray: #838282;
$gray-dark: #777;
$gray-extra-dark: #757575;
$gray-extra-light:#e7e7e7 !default;
$gray-perfect :#ddd;
$blue: #253652;
$orange: #e12b00;
$vivid-blue: #2196F3;
$pure-orange: #ff8700;
$light-purple: #ebccd1;
$red-dark: #e52d27;
$light-black: #333333;
$light-skyblue : #b7cdff !default;
$gray-trackshade: #999;
$blue-shade: #2482d3;
$dark-black: rgba(22, 22, 22, 0.99) !default;
$black-main:#232323 !default;
$timeroom-color:rgba(0,0,0,.10) !default;
$session-color: $gray-trackshade !default;
$header-color: #f8f8f8 !default;
$main-background: #fff !default;

// Corp-Colors
$corp-color: $white !default;
$corp-color-dark: darken($corp-color, 15%) !default;
$corp-color-second: $red !default;
$corp-color-second-dark: darken($corp-color-second, 15%) !default;

Nesting

SASS supports nesting concept. The nested SCSS changes to CSS when compiled. A good approach follows nesting elements to three-degree maximum.

// Nesting in application.scss session-list (Open-Event-Webapp)

.session{

  &-list {

    li{
     cursor: pointer;
      }
   .label {
     background: #ff8700;
     border-color: $light-purple;
     color: #FFFFFF;
     font-weight: 500;
     font-size: 80%;
     margin-left: -8px;
    }
   .session-title {
      margin-top: 0.1em;
       .session-link {
         font-size: 12px;
        }
    }
 }
 &-location{
 text-align: left;
 }

}

The output generated after compilation will be

//Code from schedule.css in Open-event-webapp

.session-list li {
 cursor: pointer; 
}

.session-list .label {
 background: #ff8700;
 border-color: #ebccd1;
 color: #FFFFFF;
 font-weight: 500;
 font-size: 80%;
 margin-left: -8px; 
}

.session-list .session-title {
 margin-top: 0.1em; 
}

 .session-list .session-title .session-link {
 font-size: 12px;
 }

.session-location {
 text-align: left; 
}

Mixins

Mixins are another way to optimize the SASS code. Mixins are just like functions that let us pass the values as parameters to make the code more flexible.

// Simple mixin for button
@mixin btn($color, $width, $height) {
 display: block;
 font: 16px “Open sans”, arial;
 color: $color;
 width: $width;
 height: $height;
}

Using these type of simple approaches make CSS more effective and efficient. It will enhance the workflow and help us to write cleaner code.

Continue Reading

Implementing Admin Trash in Open Event

So last week I had the task of implementing a trash system for the Admin. It was observed that sometimes a user may delete an item and then realize that the item needs to be restores. Thus a trash system works well in this case. Presently the items that are being moved to the trash are:

  • Deleted Users
  • Deleted Events
  • Deleted Sessions

So it works like this. I added a column in_trash to the tables User, Event and Sessions to mark whether the item is in the trash or not

in_trash = db.Column(db.Boolean, default=False)

So depending on whether the value is True or False the item will be in the trash of the admin. Thus for a normal user on deleting an event, user or session a message would flash that the item is deleted and the item would not be shown in the table list of the user. However it would not be deleted from the database.

trash4.png

trash5.png

Thus for the user the item is deleted. The item’s in_trash property is set to True and it gets moved to the trash. The items are displayed in the “Deleted Items” section of the Admin panel

trash1trash2trash3

The items deleted are displayed in the trash and as soon as they deleted in the trash they are deleted from the database permanently. A message will flash for the Admin when it is deleted

trash11

trash10.png

Thus the trash is implemented. 🙂

Two more things are left:

  • To restore items from trash
  • To automatically delete the items in trash after an inactivity of 30 days

This will soon be implemented 🙂

Continue Reading

Responsive UI: Modifying Designs with Device Width

An important feature of websites these days with the advancement of smartphones is being responsive with device size. We nowadays not only worry about the various width of laptop or desktop, which don’t vary by a huge amount but also need to worry about tablets and phones which have a much lesser width. The website’s UI should not break and should be as easy to use on phones as it is on desktops/laptops. Using frameworks like bootstraps, Semantic-UI solves this problem to a large extent. But what if we need to modify certain parts by our own in case of mobile devices? How do we do that?

Continue Reading

Building interactive elements with HTML and javascript: Resizing

{ Repost from my personal blog @ https://blog.codezero.xyz/building-interactive-elements-with-html-and-javascript-resizing }

Unlike draggable, HTML/js does not provide us with a direct spec for allowing users to graphically resize HTML DOM elements. So, we’ll be using mouse events and pointer locations to achieve the ability of resizing.

We’ll start with a box div.

<div id="box">  
    <div>Resize me !</div>
</div>  

A little bit of CSS magic to make it look a little bit better and square.

#box {
    position: relative;
    width: 130px;
    height: 130px;
    background-color: #2196F3;
    color: white;
    display:flex;
    justify-content:center;
    align-items:center;
    border-radius: 10px;
}

Now, we need a handle element. The user will be using this handle element to drag and resize the box.

<div id="box">  
    <div>Resize me !</div>
    <div id="handle">
    </div>
</div>  

Now, we just have an invisible div. Let’s give it some color, make it square. We also have to position it at one corner of the box.

#handle {
    background-color: #727272;
    width: 10px;
    height: 10px;
    cursor: se-resize;
    position:absolute;
    right: 0;
    bottom: 0;
}

The parent div#box has the CSS property position: relative and by setting div#handle the property position:absolute, we have the ability to position the handle absolutely with respect to its parent.

Also, note the cursor: se-resize property. This instructs the browser to set the cursor to the resize cursor () when the user is over it.

Now, it’s upto to javascript to take over. :wink:

var resizeHandle = document.getElementById('handle');  
var box = document.getElementById('box');  

For resizing, the user would click on the handle and drag it. So, we need to start resizing the moment the user presses and holds on the handle. Let’s setup a function to listen for the mousedown event.

resizeHandle.addEventListener('mousedown', initialiseResize, false);  

the initialiseResize function should do two things:

  1. Resize the box every time the mouse pointer moves.
  2. Listen for mouseup event so that the event listeners can be removed as soon as the user is done resizing.
function initialiseResize(e) {  
    window.addEventListener('mousemove', startResizing, false);
    window.addEventListener('mouseup', stopResizing, false);
}
function startResizing(e) {  
    // Do resize here
}
function stopResizing(e) {  
    window.removeEventListener('mousemove', startResizing, false);
    window.removeEventListener('mouseup', stopResizing, false);
}

To resize the box according to the user’s mouse pointer movements, we’ll be taking the current x and y coordinates of the mouse pointer (in pixels) and change the box’s height and width accordingly.

function startResizing(e) {  
   box.style.width = (e.clientX) + 'px';
   box.style.height = (e.clientY) + 'px';
}

e.clientX gives the mouse pointer’s X coordinate and e.clientY gives the mouse pointer’s Y coordinate

Now, this works. But this would only work as expected if the box is placed in the top-left corner of the page. We’ll have to compensate for the box’s left and top offsets. (position from the left and top edges of the page)

function startResizing(e) {  
   box.style.width = (e.clientX - box.offsetLeft) + 'px';
   box.style.height = (e.clientY - box.offsetTop) + 'px';
}

There you go :smile: We can now resize the box !

https://jsfiddle.net/niranjan94/w8k1ffju/embedded

Continue Reading

Working with Absolute Positioning

During the past week, I have done a lot of work for making the feature that allow the users to view the schedule of events according to track and time. The toughest part was to have a headstart to think of the mockup that fulfills this criterion.

After the mockup, I started coding it and realized that I have to add CSS by using Javascript. The frontend needs to calculate the top of each pop-up that appears when the track is hovered and to append the box just below it.

a

The interesting part was to calculate the top each time the element is hovered and to place the box at the right position by using jQuery.

Position Absolute 

The difficulty becomes maximum when we use “position : absolute “. As, it takes the element out of the layer. The element with absolute positioning is difficult to handle when it comes to responsiveness. Here also the pop-overs showing the speakers are to be made with absolute positioning.

The code snippet shows the calculation for exact position of the pop-up box.

$(document).ready(function(){

 $('.pop-box').hide();
 $('.item').hover(function (event) {

 event.preventDefault();
 event.stopPropagation();
 var track = $(event.target);
 var link = track.children(0);
 var offset =$(link).offset();

 var position= offset.top-link.height()-30;
 if( $(window).width()<600){
 var position= offset.top-link.height()-48; 
 }
 if(offset.top){
 
 $('.pop-box').hide();
 var p=$(this);
 $(p).next().show();
 var posY = event.pageY;
 nextOfpop=$(p).next();
 
 var toptrack = position ;

 $(nextOfpop).css({'top':toptrack
                 });

 $(document).mouseup(function (e)
 {
 var container = $(".pop-box");

  if (!container.is(e.target) 
  && container.has(e.target).length === 0 && (e.target)!=$('html').get(0)) 
   {
   container.hide();
   }
   });
  });
 })

This code sets the value of top of the pop-over in the variable position and copy it to toptrack that is passed to CSS to adjust the top dynamically.

Responsiveness for top

I was struggling a whole day to find out the best possible way for the responsiveness of track page. Obviously, the difficult part was the recalculation of top with the screen-size. Currently I have used $window.width() to check the width of screen and adjust the top on mobile. But, it will include more complexity when it is done for other screen sizes rather than mobile.

 if( $(window).width()<600){
 var position= offset.top-link.height()-48; 
 }

The tracks page is ready now with both light and dark theme.

10.png

That’s how the position absolute is handled with jQuery. To remove the complexity, all the CSS except the calculation of top is written with SASS.

Continue Reading
Close Menu