Implementing Pages API in Open Event Frontend

The pages endpoints are used to create static pages which such as about page or any other page that doesn’t need to be updated frequently and only a specific content is to be shown. This article will illustrate how the pages can be added or removed from the /admin/content/pages route using the pages API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for pages is GET /v1/pages First, we need to create a model for the pages, which will have the fields corresponding to the API, so we proceed with the ember CLI command: ember g model page Next, we need to define the model according to the requirements. The model needs to extend the base model class. The code for the page model looks like this: import attr from 'ember-data/attr'; import ModelBase from 'open-event-frontend/models/base'; export default ModelBase.extend({ name : attr('string'), title : attr('string'), url : attr('string'), description : attr('string'), language : attr('string'), index : attr('number', { defaultValue: 0 }), place : attr('string') }); As the page will have name, title, url which will tell the URL of the page, the language, the description, index and the place of the page where it has to be which can be either a footer or an event. The complete code for the model can be seen here. Now, after creating a model, we need to make an API call to get and post the pages created. This can be done using the following: return this.get('store').findAll('page'); The above line will check the store and find all the pages which have been cached in and if there is no record found then it will make an API call and cache the records in the store so that when called it can return it immediately. Since in the case of pages we have multiple options like creating a new page, updating a new page, deleting an existing page etc. For creating and updating the page we have a form which has the fields required by the API to create the page.  The UI of the form looks like this. Fig. 1: The user interface of the form used to create the page. Fig. 2: The user interface of the form used to update and delete the already existing page The code for the above form can be seen here. Now, if we click the items which are present in the sidebar on the left, it enables us to edit and update the page by displaying the information stored in the form and then the details be later updated on the server by clicking the Update button. If we want to delete the form we can do so using the delete button which first shows a pop up to confirm whether we actually want to delete it or not. The code for displaying the delete confirmation pop up looks like this. <button class="ui red button" {{action (confirm (t 'Are you sure you would like to delete…

Continue ReadingImplementing Pages API in Open Event Frontend

Implementing Search Functionality In Calendar Mode On Schedule Page In Open Event Webapp

The schedule page in the event websites generated by the Open Event Webapp displays all the sessions of an event in a chronological manner. There are two modes on the page: List and the Calendar Mode. The former displays all the sessions in the form of a list while the latter displays them in a graphical grid or a rectangular format.  Below are the screenshots of the modes: List Mode Calendar Mode The list mode of the page already supported the search feature. We needed to implement it in the calendar mode. The corresponding issue for this feature is here. The whole work can be seen here. First, we see the basic structure of the page in the calendar mode. <div class="{{slug}} calendar"> <!-- slug represents the currently selected date --> <!-- This div contains all the sessions scheduled on the selected date --> <div class="rooms"> <!-- This div contains all the rooms of an event --> <!-- Each particular room has a set of sessions associated with it on that particular date --> <div class="room"> <!-- This div contains the list of session happening in a particular room --> <div class="session"> <!-- This div contains all the information about a session --> <div class="session-name"> {{title}} </div> <!-- Title of the session --> <h4 class="text"> {{{description}}} </h4> <!-- Description of the session --> <!-- This div contains the info of the speakers presenting the session --> <div class="session-speakers-list"> <div class="speaker-name"><strong>{{{title}}}</div> <!-- Name of the speaker --> <div class="session-speakers-more"> {{position}} {{organisation}} </div> <!-- Position and organization of speaker--> </div> </div> </div> </div> </div> </div> The user will type the query in the search bar near the top of the page. The search bar has the class fossasia-filter. We set up a keyup event listener on that element so that whenever the user will press and release a key, we will invoke the event handler function which will display only those elements which match the current query entered in the search bar. This way, we are able to change the results of the search dynamically on user input. Whenever a single key is pressed and lifted off, the event is fired which invokes the handler and the session elements are filtered accordingly. Now the important part is how we actually display and hide the session elements. We actually compare few session attributes to the text entered in the search box. The text attributes that we look for are the title of the session, the name, position , and organization of the speaker(s) presenting the session. We check whether the text entered by the user in the search bar appears contiguously in any of the above-mentioned attributes or not. If it appears, then the session element is shown. Otherwise, its display is set to hidden. The checking is case insensitive. We also count the number of the visible sessions on the page and if it is equal to zero, display a message saying that no results were found. For example:- Suppose the user enters the string ‘wel’ in…

Continue ReadingImplementing Search Functionality In Calendar Mode On Schedule Page In Open Event Webapp

Implementing Users API to Display the Users at Admin Route in Open Event Frontend

This article will illustrate how the users are displayed and updated on the /admin/users route, their roles, user links etc. using the users API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for fetching the users is GET /v1/users First, we need to create a model for the user, which will have the fields corresponding to the API, so we proceed with the ember CLI command: ember g model user Next, we need to define the model according to the requirements. The model needs to extend the base model class. As a user can have multiple notifications, orders and  sessions etc. so we have to use ember data relationships “hasMany”. Hence, the model will have the following format. import ModelBase from 'open-event-frontend/models/base'; import { hasMany } from 'ember-data/relationships'; export default ModelBase.extend({ email : attr('string'), password : attr('string'), isVerified : attr('boolean', { readOnly: true }), isSuperAdmin : attr('boolean', { readOnly: true }), isAdmin : attr('boolean', { readOnly: true }), firstName : attr('string'), lastName : attr('string') }); The complete code for the model can be seen here Now, we need to load the data from the API using the above model, so we will send a GET request to the API to fetch the users. This can be easily achieved using this. return this.get('store').query('user', {'page[size]': 10 }); The above line is querying for the users from the store which is place where cache of all of the records that have been loaded by our application is there. If a route asks for a record, the store can return it immediately if it is there in the cache and we want to display only 10 users in a page so defined how many number of users has to be loaded at a time. Now we need to filter the users based on whether they are active or they have deleted their accounts. For this purpose, we need to pass filter to the query which will tell what type of users to be loaded at once. The next thing we need to do is to display the above data fetched from the API into an ember table. Ember table helps in allowing us to render very large data sets by only rendering the rows that are being displayed. For this, we defined a controller class which will help in letting the table know what all columns will be required to display and the attribute values they correspond in the API. We can also define the template for each column. The code for the controller class looks like this. import Ember from 'ember'; const { Controller } = Ember; export default Controller.extend({ columns: [ { propertyName : 'first-name', title : 'Name', disableSorting : true, disableFiltering : true }, { propertyName : 'email', title : 'Email', disableSorting : true, disableFiltering : true }, { propertyName : 'last-accessed-at', title : 'Last Accessed', template : 'components/ui-table/cell/admin/users/cell-last-accessed-at', disableSorting : true, disableFiltering : true } ] }); In the above…

Continue ReadingImplementing Users API to Display the Users at Admin Route in Open Event Frontend

Adding Service Workers In Generated Event Websites In Open Event Webapp

Open Event Webapp Generator takes in the event data in form of a JSON zip or an API endpoint as input and outputs an event website. Since the generated event websites are static, we can use caching of static assets to improve the page-load time significantly. All this has been made possible by the introduction of service workers in the browsers. Service workers are event-driven scripts (written in JavaScript) that have access to domain-wide events, including network fetches.With the help of service workers, we can cache all static resources, which could drastically reduce network requests and improve performance considerably, too. The service workers are like a proxy which sits between the browser and the network and intercept it. We will listen for fetch events on these workers and whenever a request for a resource is made, we intercept and process it first. Since our generated event sites are static, it makes no sense to fetch the assets again and again. When a user first loads a page, the script gets activated and try to cache all the static assets it can. On further reload of the page, whenever the browser request for an already stored asset, instead of fetching it from the network, it can directly give them back to the browser from the local store. Also, in the meantime, it is also caching all the new static assets so they won't be loaded or fetched again from the network later. Thus the performance of the app gets increased more and more with every reload. It becomes fully functional offline after a few page loads. The issue for this feature is here and the whole work can be seen here. To know more details about the basic functioning and the lifecycle of the service workers, check out this excellent Google Developers article. This blog mainly focuses on how we added service workers in the event websites. We create a new fallback HTML page offline.html to return in response to an offline user who requests a page which has not been cached yet. Similarly, for images, we have a fallback image named avatar.png which is returned when the requested image is not present in the local store and the network is down. Since these assets are integral to the functioning of the service worker, we cache them in the installation step itself. The whole service worker file can be seen here var urlsToCache = [ './css/bootstrap.min.css', './offline.html', './images/avatar.png' ]; self.addEventListener('install', function(event) { event.waitUntil( caches.open(CACHE_NAME).then(function(cache) { return cache.addAll(urlsToCache); }) ); }); All the other assets are cached lazily. Only when they are requested, we fetch them from the network and store it in the local store. This way, we avoid caching a large number of assets at the install step. Caching of several files in the install step is not recommended since if any of the listed files fails to download and cache, then the service worker won’t be installed! self.addEventListener('fetch', function(event) { event.respondWith(caches.match(event.request).then(function(response) { // Cache hit - return response if (response) { return response; } // Fetch resource from internet and put…

Continue ReadingAdding Service Workers In Generated Event Websites In Open Event Webapp

Customising URL Using Custom Adapters in Open Event Front-end

Open-Event Front-end uses Ember data for handling Open Event Orga API which abides by JSON API specs. The API has relationships which represent models in the database, however there are some API endpoints for which the URL is not direct. We make use of custom adapter to build a custom URL for the requests. In this blog we will see how to Implement relationships which do not have a model in the API server. Lets see how we implemented the admin-statistics-event API using custom adapter? Creating Order-statistics model To create a new model we use ember-cli command: ember g model admin-statistics-event The generated model: export default ModelBase.extend({ draft : attr('number'), published : attr('number'), past : attr('number') }) The API returns 3 attributes namely draft, published & past which represent the total number of drafted, live and past event in the system. The admin-statistics-event is an admin related model. Creating custom adapter To create a new adapter we use ember-cli command: ember g adapter event-statistics-event If we try to do a GET request the URL for the request will be ‘v1/admin-statistics-event’ which is an incorrect endpoint. We create a custom adapter to override the buildURL method. buildURL(modelName, id, snapshot, requestType, query) { let url = this._super(modelName, id, snapshot, requestType, query); url = url.replace('admin-statistics-event', 'admin/statistics/event'); return url; } We create a new variable url which holds the url generated by the buildURL method of the super adapter. We call the super method using ‘this._super’. We will now replace the ‘admin-statistics-event’ with ‘admin/statistics/event’ in url variable. We return the new url variable. This results in generation of correct URL for the request. Thank you for reading the blog, you can check the source code for the example here. Resources Official Ember Data documentation Ember custom adapter guide

Continue ReadingCustomising URL Using Custom Adapters in Open Event Front-end

Implementing Logging Functionality in Open Event Webapp

Open Event Webapp allows event organizers to generate an event website by providing JSON data in the form of a zip file or an API endpoint. The generation of event website is a multi step process and takes some time to complete. In order to see the ongoing progress of the process and catch any errors, logging feature is a must. The challenging part was to send the log messages in real time about the tasks being performed in the generator instead of sending it after some time when the task is already finished. To enable real time communication between the web server and the client, we use the Socket IO library. But before using that library for sending log messages from server to client, we have to design the architecture of the logging feature. The log statements can be of several types. It could be about the inception of a task, the successful completion of it or some error which occurred while performing the task. The log message object contains a field named type to show the type of the statements. We define three categories of logs messages:- 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…

Continue ReadingImplementing Logging Functionality in Open Event Webapp

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

Event Websites generated by Open Event Webapp may contain a large number of sessions presented by different speakers. The Sessions are divided into the different group based on their type of track. For example, few sessions may belong to Database Track, few may belong to Machine Learning Track and so on. It is natural that the user may want to filter the visible sessions on the basis of tracks. Before we implemented the tracks filter using the track names list, we had a sub navbar on the tracks page for jumping to the different tracks of the event on a particular day. Below are the screenshots of that feature. On Clicking the Design, Art, Community Track 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. 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> 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…

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

Handling Requests for hasMany Relationships in Open Event Front-end

In Open Event Front-end we use Ember Data and JSON API specs to integrate the application with the server. Ember Data provides an easy way to handle API requests, however it does not support a direct POST for saving bulk data which was the problem we faced while implementing event creation using the API. In this blog we will take a look at how we implemented POST requests for saving hasMany relationships, using an example of sessions-speakers route to see how we saved the tracks, microlocations & session-types. Lets see how we did it. Fetching the data from the server Ember by default does not support hasMany requests for getting related model data. However we can use external add on which enable the hasMany Get requests, we use ember-data-has-many-query which is a great add on for querying hasMany relations of a model. let data = this.modelFor('events.view.edit'); data.tracks = data.event.get('tracks'); data.microlocations = data.event.get('microlocations'); data.sessionTypes = data.event.get('sessionTypes'); return RSVP.hash(data); In the above example we are querying the tracks, microlocations & sessionTypes which are hasMany relationships, related to the events model. We can simply do a to do a GET request for the related model. data.event.get('tracks'); In the above example we are retrieving the all the tracks of the event. Sending a POST request for hasMany relationship Ember currently does not saving bulk data POST requests for hasMany relations. We solved this by doing a POST request for individual data of the hasMany array. We start with creating a `promises` array which contains all the individual requests. We then iterate over all the hasMany relations & push it to the `promises` array. Now each request is an individual promise. let promises = []; promises.push(this.get('model.event.tracks').toArray().map(track => track.save())); promises.push(this.get('model.event.sessionTypes').toArray().map(type => type.save())); promises.push(this.get('model.event.microlocations').toArray().map(location => location.save())); Once we have all the promises we then use RSVP to make the POST requests. We make use of all() method which takes an array of promises as parameter and resolves all the promises. If the promises are not resolved successfully then we simply notify the user using the notify service, else we redirect to the home page. RSVP.Promise.all(promises) .then(() => { this.transitionToRoute('index'); }, function() { this.get('notify').error(this.l10n.t(‘Data did not save. Please try again')); }); The result of this we can now retrieve & create new tracks, microlocations & sessionTypes on sessions-speakers route. Thank you for reading the blog, you can check the source code for the example here. Resources Official Ember Data documentation Official JSON API specs Blog on ember data by Pooyan Khosravy  

Continue ReadingHandling Requests for hasMany Relationships in Open Event Front-end

Making Autocomplete Box Compatible with the Search Bar using Angular in Susper

A major problem in Susper was that we were using the same components on different pages, with different styling properties. A major issue was that the Autocomplete box was not properly aligned in the index page and looked like this: This was happening because the autocomplete box width was set for 634 px, a width perfect for the search bar in the results page. The index page had a search bar of width 534 px, and the autocomplete box was too large for that. Here is how the issue was solved: Changing the suggestion box html code: id="sug-box" class="suggestion-box" *ngIf=”results.length> 0”> *ngFor="let result of results" class="query-suggestions"> [routerLink]="resultsearch" [queryParams]="{query: result}" class="suggestions">{{result}} </div> The code uses *ngIf which is why setting the autocomplete box width using the typescript files becomes impossible. *ngIf does not load the component into the DOM until there are results, hence we didnot have the autocomplete box in the DOM until after the query was typed in the search bar. That was why we could not set its width, hence it was decided to remove this attribute. Using the ‘hidecomponent emitter’ is a better option here (used in the typescript file). @Output() hidecomponent: EventEmitter<any> = new EventEmitter<any>(); if (this.results.length === 0) { this.hidecomponent.emit(1); } else { this.hidecomponent.emit(0); } See autocomplete.component.ts for the complete code. It is now required to dynamically change the id of the suggestion-box depending on the page it is on, and apply the correct CSS. Here is the html code: <div [id]="getID()" class="suggestion-box" *ngIf="results"> The id of the suggestion box will now depend on the value returned from the function getID(), defined as follows: getID() { if ( this.route.url.toString() === '/') {   return 'index-sug-box'; } else {   return 'sug-box' } } We first check if the route url is simply ‘/’ (which implies it is in the index page). If yes the id is set to index-sug-box otherwise to sug-box. Now we can write extra CSS properties for the index-sug-box id as follows: #index-sug-box{ width: 586px; } References: For basic javascript functions: https://www.w3schools.com/js/js_functions.asp To understand components in Angular: https://angular.io/api/core/Component  

Continue ReadingMaking Autocomplete Box Compatible with the Search Bar using Angular in Susper

Implementing Speakers Call API in Open Event Frontend

This article will illustrate how to display the speakers call details on the call for speakers page in the Open Event Frontend project using the Open Event Orga API. The API endpoints which will be mainly focussing on for fetching the speaker call details are: GET /v1/speakers-calls/{speakers_call_id} In the case of Open Event, the speakers are asked to submit their proposal beforehand if they are interested in giving some talk. For the same purpose, we have a section on the event’s website called as Call for Speakers on the event’s public page where the details about the speakers call are present along with the button Submit Proposal which redirects to the link where they can upload the proposal if the speakers call is open. Since the speakers call page is present on the event’s public page so the route which will be concerned with will be public/index route and its subroute public/index/cfs in the application. As the call for speakers details are nested within the events model so we need to first fetch the event and then from there we need to fetch the speaker-calls detail from the model. The code to fetch the event model looks like this: model(params) { return this.store.findRecord('event', params.event_id, { include: 'social-links' }); } The above model takes care of fetching all the data related to the event but, we can see that speakers call is not included as the parameter. The main reason behind this is the fact that the speakers is not required on each of the public route, rather it is required only for the subroute public/index/cfs route. Let’s see how the code for the speaker-call modal work to fetch the speaker calls detail from the above event model.   model() { const eventDetails = this.modelFor('public'); return RSVP.hash({ event : eventDetails, speakersCall : eventDetails.get('speakersCall') }); } In the above code, we made the use of this.modelFor(‘public’) to make the use of the event data fetched in the model of the public route, eliminating the separate API call for the getting the event details in the speaker call route. Next, using the ember’s get method we are fetching the speakers call data from the eventDetails and placing it inside the speakersCall JSON object for using it lately to display speakers call details in public/index subroute. Until now, we have fetched event details and speakers call details in speakers call subroute but we need to display this on the index page of the sub route. So we will pass the model from file cfs.hbs to call-for-speakers.hbs the code for which looks like this: {{public/call-for-speakers speakersCall=model.speakersCall}} The trickiest part in implementing the speakers call is to check whether the speakers call is open or closed. The code which checks whether the call for speaker has to be open or closed is: isOpen: computed('startsAt', 'endsAt', function() { return moment().isAfter(this.get('startsAt')) && moment().isBefore(this.get('endsAt')); }) In the above-computed property isOpen of speakers-call model, we are passing the starting time and the ending time of the speakers call. We are…

Continue ReadingImplementing Speakers Call API in Open Event Frontend