Implementing Notification Action Buttons in Open Event Frontend

The Open-Event-Frontend allows the event organiser to create access codes for his or her event.  Access codes can be used to password protect hidden tickets reserved for sponsors, members of the press and media. Notifications are an important part of the project. We show each registered user notifications based on their activity. This blog post goes over the implementation of the notification action buttons in the notification panel.

Notification Action Model

The model for Notification action is very simple. It has the following variables:

  1. Subject: The subject of the notification. E.g. ‘event’, ‘order’ etc.
  2. actionType: The action that can be taken by the user for that notification. E.g: ‘view’, ‘submit’.
  3. subjectId: The id of the subject. In case of an event, it will store the event id. Similarly for other cases.
  4. Link: The link to be applied to the button.

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';
import { belongsTo } from 'ember-data/relationships';

export default ModelBase.extend({
  subject    : attr('string'),
  actionType : attr('string'),
  subjectId  : attr('number'),
  link       : attr('string'),

  notification: belongsTo('notification')
});

Action Button Title

We make use of ember computed property to determine the action button title. The title of the button depends on the subject and the actionType defined in the notification-action model. The actionType can be one of ‘download’, ‘submit’ and ‘view’. If the action type is ‘download’ and the subject is ‘invoice’, then the button title will be “Download Invoice”. Similarly, for other cases, we do the same.

buttonTitle: computed('subject', 'actionType', function() {
    let action;
    const actionType = this.get('actionType');
    switch (actionType) {
      case 'download':
        action = 'Download';
        break;

      case 'submit':
        action = 'Submit';
        break;

      default:
        action = 'View';
    }

    let buttonSubject;
    const subject = this.get('subject');
    switch (subject) {
      case 'event-export':
        buttonSubject = ' Event';
        break;

      case 'event':
        buttonSubject = ' Event';
        break;

      case 'invoice':
        buttonSubject = ' Invoice';
        break;

      case 'order':
        buttonSubject = ' Order';
        break;

      case 'tickets-pdf':
        buttonSubject = ' Tickets';
        break;

      case 'event-role':
        buttonSubject = ' Invitation Link';
        break;

      case 'session':
        buttonSubject = ' Session';
        break;

      case 'call-for-speakers':
        if (this.get('actionType') === 'submit') {
          buttonSubject = ' Proposal';
        } else {
          buttonSubject = ' Call for Speakers';
        }
        break;

      default:
        // Nothing here.
    }

    return action + buttonSubject;
  })

Action Button Route

The route that the button will lead to depends on the subject of the action. If the link is provided in the notification action, we simply set it on the button otherwise we use the subject to derive the route name. For e.g., if the subject is an event, then the route will be “events.view”.

/**
   * The route name to which the action button will direct the user to.
   */
  buttonRoute: computed('subject', function() {
    const subject = this.get('subject');
    let routeName;
    switch (subject) {
      case 'event-export':
        routeName = 'events.view';
        break;

      case 'event':
        routeName = 'events.view';
        break;

      case 'invoice':
        routeName = 'orders.view';
        break;

      case 'order':
        routeName = 'orders.view';
        break;

      default:
      // Nothing here.
    }
    return routeName;
  })

Template

We simply check if the link exists or not. If it does then we simply use it otherwise we use the computed button route name.

{{#if action.link}}
     {{#link-to action.link tagName='button' class='ui blue button'}}
         {{t action.buttonTitle}}
         {{/link-to}}
{{else}}
    {{#link-to action.buttonRoute action.subjectId tagName='button' class='ui blue button'}}
         {{t action.buttonTitle}}
         {{/link-to}}
{{/if}}

References

Adding Panel to Add Event Types in Admin Dashboard of Open Event Frontend

This blog will illustrate how to add a new section to admin dashboard of Open Event Frontend which allows admin to add event types. For this we need modals to display a form by which we can edit or add a new event type and we need to create a new route admin/content/events. To create a new route we use ember CLI command:

ember g route admin/content/events

The primary end point of Open Event API with which we are concerned with for creating a new event type or topic is:

GET/POST/DELETE        /v1/event-types

The model concerned with event types is:

 name : attr('string'),
 slug : attr('string'),

 events: hasMany('event')

 

This model is very basic and contains only name and slug and a relationship to event model. Next we want to fetch the existing event types and display them in table. We write queries which fetches data in event-type model in the route file admin/content/events.js.

import Route from '@ember/routing/route';
export default Route.extend({
 titleToken() {
   return this.get('l10n').t('Social Links');
 },
 async model() {
   return {
     'eventTopics': await this.get('store').query('event-topic', {}),
     'eventTypes': await this.get('store').query('event-type', {})
   };
 }
});

 

This will fetch the data in our model. Next we need to display this data in a template for which we define a table that will display each event type.

<button class="ui blue button {{if device.isMobile 'fluid'}}" {{action 'openNewEventTypeModal'}}>{{t 'Add New Event Type'}}</button> 

      <table class="ui celled table">
         <tbody>
           {{#each model.eventTypes as |eventType|}}
             <tr>
               <td>
                 {{eventType.name}}
               </td>
             </tr>
           {{/each}}
         </tbody>
       </table>

 

We have two buttons that are used to edit or delete a event type. Both buttons open up a modal to achieve this functionality. We also have a “Add new Event Type” button at the top. This buttons opens up a modal and sends out a action to its controller when user successfully fills up the name of the event type. Let us take a look at the code of our controller that saves/deletes our event type to server.

addEventType() {
     this.set('isLoading', true);
     this.get('eventType').save()
       .then(() => {
        // Success message
       })
       .catch(()=> {
        //failure message
       })
       .finally(() => {
         this.set('isLoading', false);
       });
   }

 

deleteEventType(eventType) {
     this.set('isLoading', true);
     eventType.destroyRecord()
       .then(() => {
        // Success
       })
       .catch(()=> {
        //failure
       })
       .finally(() => {
         this.set('isLoading', false);
       });
   }

 

In addNewEventType() function we take the data from the form and save the model, which eventually sends POST request to save the new Event Type on server. This returns a JavaScript promise and we handle it via then and catch. It goes to then block if promise resolves and goes to catch is promise rejects/fails.

Similarly in delete function we take the eventType which is passed as model of event-type object and call destroyRecord() function which eventually sends out a DELETE request to server and data gets deleted. Here also we handle the response via resolve and reject depicted with then and catch respectively.

Resources

Implementing Accepting and Rejecting Proposals in Open Event Frontend

This blog post will illustrate how to add buttons to accept and reject proposal and making them functional. Sessions tab in event dashboard communicates with the following APIs of Open Event Server.

  • GET                    /v1/sessions
  • PATCH              /v1/sessions
What is meant by accepting or rejecting a session in open event?

Sessions are part of event which include one or many speakers. Speakers can propose one or many sessions for a event. Now it is duty of organizer to accept some proposals and reject others. Open event provides two options to accept or reject a proposal i.e. with email or without email.

For this we need to send a key value pair which includes whether we want to send email or not along with other parameters which include current state of session and other important properties. A typical request to alter state of session looks like this.

{
  "data": {
    "attributes": {
      "title": "Micropython Session",
      "level": 1,
      "starts-at": "2017-06-01T10:00:00.500127+00:00",
      "ends-at": "2017-06-01T11:00:00.500127+00:00",
      "created-at": "2017-05-01T01:24:47.500127+00:00",
      "is-mail-sent": false,
      "send-email": true,
    },
    "type": "session",
    "id": "1"
  }
}
Implementing in frontend

We start by providing two buttons for a pending session. One to accept the session and other to reject the session.

On clicking either accept or reject button we get two options to choose i.e. with email and without email. Depending on what organizer chooses a action is fired from the template and sent to controller. Template code for these buttons looks something like this.

class=“ui vertical compact basic buttons”> {{#unless (eq record.state ‘accepted’)}} {{#ui-dropdown class=‘ui icon bottom right pointing dropdown button’}} class=“green checkmark icon”>

class=“menu”>

class=“item” {{action acceptProposal record true}}>{{t ‘With email’}}

 


class=“item” {{action acceptProposal record false}}>{{t ‘Without email’}}

 

      </div>
    {{/ui-dropdown}}
  {{/unless}}
  {{#unless (eq record.state 'rejected')}}
    {{#ui-dropdown class='ui icon bottom right pointing dropdown button'}}
      <i class="red remove icon"></i>

class=“menu”>

class=“item” {{action rejectProposal record true}}>{{t ‘With email’}}

 


class=“item” {{action rejectProposal record false}}>{{t ‘Without email’}}

 

      </div>
    {{/ui-dropdown}}
  {{/unless}}
</div>

We can see that for with email button we trigger accept proposal button with two parameters record and true. Record contains the instance of session and true signifies that we are sending email. Similar is the case with without email button. Controller for these actions looks something like this.

acceptProposal(session, sendEmail) {
      session.set('sendEmail', sendEmail);
      session.set('state', 'accepted');
      session.set('isMailSent', sendEmail);
      this.set('isLoading', true);
      session.save()
        .then(() => {
          sendEmail ? this.notify.success(this.get('l10n').t('Session has been accepted and speaker has been notified via email.'))
            : this.notify.success(this.get('l10n').t('Session has been accepted'));
        })
        .catch(() => {
          this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
        })
        .finally(() => {
          this.set('isLoading', false);
        });
    },
    rejectProposal(session, sendEmail) {
      session.set('sendEmail', sendEmail);
      session.set('state', 'rejected');
      session.set('isMailSent', sendEmail);
      this.set('isLoading', true);
      session.save()
        .then(() => {
          sendEmail ? this.notify.success(this.get('l10n').t('Session has been rejected and speaker has been notified via email.'))
            : this.notify.success(this.get('l10n').t('Session has been rejected'));
        })
        .catch(() => {
          this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
        })
        .finally(() => {
          this.set('isLoading', false);
        });
    }

For accepting with email we set sendEmail field to true and send the query to server. Similarly for reject proposal action we follow same procedure.

Conclusion

Implementing buttons like these, and defining proper actions like these we are able to change the state of session with options to send email or not.

Resources

Adding an Edit Route for Access Codes in Open Event Frontend

This blog post will illustrate how to add edit route to access code to allow organizers editing an access code. Editing access codes deals with the following API on Open Event Server.

PATCH          /v1/access-codes/{id}

First of all we need to add a route to allow editing of access codes. Our route will be /events/{event_id}/tickets/access-codes/edit/{access_code_id}. To generate a new route we need to run the command

ember g route events/view/tickets/access-codes/edit

This will generate new routes files and templates. Also this will add this route in router.js. In router.js we need to specify what we are passing as a parameter to route. For this we specify access_code_id to let ember know that this parameter will be passed with the URL. This is done so as to know which access code to update. Our route should look something like this /events/{event_id}/tickets/access-codes/edit/{access_code_id}. Final router.js file for access-codes part is given below:

//  app/router.js
 
this.route('access-codes', function() {
   this.route('list', { path: '/:access_status' });
   this.route('create');
   this.route('edit', { path: '/edit/:access_code_id' });
});

 

Next we need to pass model (data) to our edit route template. In our model we will be looking for a particular access code with an id and event tickets. After we get our event tickets, we then look for the event tickets which are already present in that access code. This is done so as to check the tickets in the template which are already present in that access code. So for this in afterModel hook we loop over all event tickets and whichever ticket is included in the access code tickets array we mark its isChecked property as true. This helps us to mark those tickets as checked in multiple select checkboxes of template.

// app/routes/events/view/tickets/access-codes/edit.js

model(params) {
   return RSVP.hash({
     accessCode : this.store.findRecord('access-code', params.access_code_id, {}),
     tickets    : this.modelFor('events.view').query('tickets', {})
   });
 },

 async afterModel(model) {
   let tickets = await model.accessCode.tickets;
   let allTickets = model.tickets;
   allTickets.forEach(ticket => {
     if (tickets.includes(ticket)) {
       ticket.set('isChecked', true);
     } else {
       ticket.set('isChecked', false);
     }
   });

 

The information about multiple select checkboxes in frontend has been discussed in this blog post. Now after we are done setting our edit route we need to redirect user to edit page when he/she clicks on the edit button in access code list. For this we define the necessary actions in template which will be triggered when user clicks on the icon. Code for the edit icon in access code list is given below.

// templates/components/ui-table/cell/events/view/tickets/access-codes/cell-actions.hbs

class="ui vertical compact basic buttons"> {{#ui-popup content=(t 'Edit') click=(action editAccessCode record.id) class='ui icon button' position='left center'}} class="edit icon"> {{/ui-popup}}

 

The editAccessCode action looks something like this.

// controller/events/view/tickets/access-codes/edit.js

editAccessCode(id) {
     this.transitionToRoute('events.view.tickets.access-codes.edit', id);
}

 

After clicking on the edit icon user is redirected to edit route where he/she can edit access code choosen. We use the same component that we chose for creating access code. To know more about create access code template component refer to this blog. Finally when saving the edited access code we call save action. This action is defined in the controllers. The action looks something like this.

// controllers/events/view/tickets/access-codes/edit.js

export default Controller.extend({
 actions: {
   save(accessCode) {
     accessCode.save()
       .then(() => {
         this.get('notify').success(this.get('l10n').t('Access code has been successfully updated.'));
         this.transitionToRoute('events.view.tickets.access-codes');
       })
       .catch(() => {
         this.get('notify').error(this.get('l10n').t('An unexpected error has occured. Discount code cannot be created.'));
       });
   }
 }
});

 

After the access code is saved. We redirect user back to access code list.

Resources

How We Implement Custom Forms for Sessions and Speaker Form in Open Event Frontend

In this blog we will see the implementation of custom form for session and speakers form. Since every event organiser requires different fields required to be filled by speakers for their sessions, for ex. Some event organiser may need GitHub profile whereas other may need LinkedIn profile so, we implemented custom form for session and speaker form in Open Event Frontend so that every organiser can choose fields he/she requires to be filled by his speakers who are filling their proposal. Custom form allows following features:

  1. Allows organiser to choose whether he wants a particular field or not.
  2. Organiser can make a field compulsory, so that no speaker can submit his proposal without filling that field.

So, to get started we define our getCustomForm() method in mixins and it looks something like this:

import Mixin from '@ember/object/mixin';
import MutableArray from '@ember/array/mutable';

export default Mixin.create(MutableArray, {

 getCustomForm(parent) {
   return [
     this.store.createRecord('custom-form', {
       fieldIdentifier : 'title',
       form            : 'session',
       type            : 'text',
       isRequired      : true,
       isIncluded      : true,
       isFixed         : true,
       event           : parent
     }),
     this.store.createRecord('custom-form', {
       fieldIdentifier : 'subtitle',
       form            : 'session',
       type            : 'text',
       isRequired      : false,
       isIncluded      : false,
       event           : parent
     }),
     this.store.createRecord('custom-form', {
       fieldIdentifier : 'shortAbstract',
       form            : 'session',
       type            : 'text',
       isRequired      : false,
       isIncluded      : true,
       event           : parent
     }),

...

 

Here we define all the possible fields with their properties. Every field has different properties that enables us to identify whether to choose or reject them. So to enable this these fields to be chosen by organiser we need to feed them into model.

In our session-speakers-step.js in components we assign these fields to our model thorugh this:

didInsertElement() {
   if (this.get('data.event.customForms')&&!his.get('data.event.customForms.length')) {
     this.set('data.event.customForms', this.getCustomForm(this.get('data.event')));
   }
}

 

This hook gets called when our component is rendered and custom form fields gets assigned to data.event.customForms in our model.

In our handlebars template we create sliders to enable organiser to choose whether he wants to enable a field or not.

In our template logic we write a loop that renders these sliders for each fields and manipulate them as organiser slides any slider for a field. Let’s take a look at the template code block:

       <tbody>
           {{#each customForm.session as |field|}}
             <tr class="{{if field.isIncluded 'positive'}}">
               <td class="{{if device.isMobile 'center' 'right'}} aligned">
                 <label class="{{if field.isFixed 'required'}}">
                   {{field.name}}
                 </label>
               </td>
               <td class="center aligned">
                 {{ui-checkbox class='slider'
                               checked=field.isIncluded
                               disabled=field.isFixed
                               onChange=(action (mut field.isIncluded))
                               label=(if device.isMobile (t 'Include'))}}
               </td>
               <td class="center aligned">
                 {{ui-checkbox class='slider'
                               checked=field.isRequired
                               disabled=field.isFixed
                               onChange=(action (mut field.isRequired))
                               label=(if device.isMobile (t 'Require'))}}
               </td>
             </tr>
           {{/each}}
         </tbody>

 

Every slider has a action attached to it that manipulates a property of the custom for and on proceeding it saves the custom form model to server.

Later on when rendering for for speaker we fetch details from server about which fields are required or not.

Resources

Open Event Frontend – Events Explore Page

This blog illustrates how the events explore page is implemented in the Open Event Frontend web app. The user can land on the events explore page by clicking on Browse Events button in the top panel on the home page, shown by the mouse tip in the following picture.

Here, the user can use the various filter options provided to search for the events as per his requirements, He/she can filter according to categories, sub-categories for each category, event type, and date range. A unique feature here is that the user can pick from the start date range options such as today, tomorrow, this week, this weekend, next week and many more. If neither of these fits his needs he can use custom dates as well. The user can also filter events using event location which is autocompleted using Google Maps API. Thus, searching for events is fast, easy and fun.

Let us see how this has been implemented.

Implementation

The explore routes has a method _loadEvents(params). Here, params is the various query parameters for filtering the events. This method forms the query, sends it to the server and returns the list of events returned by the server. The server uses Flask-REST-JSONAPI. It has a very flexible filtering system. It is completely related to the data layer used by the ResourceList manager. More information about this can be found here.

So, the filters are formed using syntax specified in the link mentioned above. We form an array filterOptions which stores the various filters. The default filter is that the event should be published:

let filterOptions = [
 {
   name : 'state',
   op  : 'eq',
   val  : 'published'
 }
];

Then we check for each filter option and check if it is present or not. If yes then we add it to filterOptions. An example as follows:

if (params.category) {
 filterOptions.push({
   name : 'event-topic',
   op  : 'has',
   val  : {
     name : 'name',
     op : 'eq',
     val : params.category
   }
 });
}

This is repeated for sub_category, event_type, location and start_date and end_date. An event is considered to fulfill the date filter if it satisfies any one of the given conditions:

  • If both start_date and end_date are mentioned:
    • Event start_date is after filter start date and before filter end date.
    • Or, event end date if after filter start date and before filter end date.
    • Or, event start date is before filter start date and event end date date is after filter end date.
  • If only start_date is mentioned, then if the event start date is after filter start date or event end date is after filter start date.

The code to this can be found here. For the date ranges mentioned above(today, tomorrow etc) the start dates and end dates are calculated using the moment.js library and then passed on as params.

The filteredEvents are passed in the route model.

async model(params) {
 return {
   eventTypes     : await this.store.findAll('event-type'),
   eventTopics    : await this.store.findAll('event-topic', { include: 'event-sub-topics' }),
   filteredEvents : await this._loadEvents(params)
 };
}

The variable is set in the controller and any change to the query params is observed for. The method _loadEvents is called whenever the query params change.

setupController(controller, model) {
 this._super(...arguments);
 controller.set('filteredEvents', model.filteredEvents);
 this.set('controller', controller);
},

actions: {
 async queryParamsDidChange(change, params) {
   if (this.get('controller')) {
     this.get('controller').set('filteredEvents', await this._loadEvents(params));
   }
 }
}

The template iterates over the filteredEvents and displays each one in a card.

Resources

Add Edit user Modal in Open Event Frontend

This blog article will illustrate how the UI for the edit user modal is implemented  and Users API has been integrated into it in Open Event Frontend.

The admin can make any user the admin, sales admin or the marketer of the app. In the route admin/users there is an ember table where all the users are listed. In the table there exists a column named ‘Action Buttons’.

When the edit action button is clicked a modal appears on the screen. Them template of the modal is as follows:

class="content">
class="ui form">

class="ui header">{{t 'Provide admin access?'}}

class="grouped inline fields">
class="field"> {{ui-radio name="isAdmin" label="Yes" value=true onChange=(action (mut isAdmin))}} {{ui-radio name="isAdmin" label="No" value=false onChange=(action (mut isAdmin))}}
</div> <h4 class="ui header">{{t 'Custom system roles'}}</h4>
class="field"> {{ui-checkbox label="Sales Admin" onChange=(action (mut checked))}} {{ui-checkbox label="Marketer" onChange=(action (mut checked))}}
<button class="ui teal right floated submit button update-changes"> {{t 'Save'}} </button> </div> </div>

For the API integration the users model is used. The attributes isAdmin, isSalesAdmin, isMarketer from the model are used to send a patch request to the server. The modal has basically to parts. The first part consists of radio buttons through which the super admin has the rights to create a user an admin of the app or to remove his role as the admin. The second part consists of checkboxes through which the user can get the custom system role to be the sales admin or the marketer. A get request is sent to the user’s model in the server and the initial values of the modal are decided.

If the admin changes some value, he clicks on the save button in the modal and a patch request is sent to the server. The save function is written in the modal’s component.

actions: {
saveRole(id) {
this.get('store').findRecord('user', id).then(function(user) {
user.save();
});
this.set('isOpen', false);
},
toggleSalesAdmin(user) {
user.toggleProperty('isSalesAdmin');
},
toggleMarketer(user) {
user.toggleProperty('isMarketer');
},
createAdmin(user, isAdmin) {
user.set('isAdmin', isAdmin);
}
}

Resources

Adding the User Settings Route in Admin User Route Open Event Frontend

This blog article will illustrate how the User settings API has been integrated into the admin users route  Open Event Frontend. The admin can change the contact info of some user, details about the email preferences for different events created by the user and the third party authentication details entered by the user.

To make the settings user link in the user link column of the users table functional new sub routes are added to the app’s user route as follows:

  • /admin/users/<user_id>/settings/contact-info
  • /admin/users/<user_id>/settings/email-preferences
  • /admin/users/<user_id>/settings/applications

The template for the index route which redirects to each of the settings route is:

class="ui grid">
class="row">
class="twelve wide column"> {{#tabbed-navigation}} {{#link-to 'admin.users.view.settings.contact-info' model.user.id class='item'}} {{t 'Contact Info'}} {{/link-to}} {{#link-to 'admin.users.view.settings.email-preferences' model.user.id class='item'}} {{t 'Email-Preferences'}} {{/link-to}} {{#link-to 'admin.users.view.settings.applications' model.user.id class='item'}} {{t 'Applications'}} {{/link-to}} {{/tabbed-navigation}}
</div> {{outlet}} </div>

Interestingly, the routes admin/users/view and admin/users/list are both dynamic and expect a parameter after /users/ hence, the app cannot distinguish between them on it’s own, thus explicit handling of the dynamic parameter of the routes was implemented, differentiating them on the basis of the route’s state as follows:

beforeModel(transition) {
this._super(...arguments);
const userState = transition.params[transition.targetName].users_status;
if (!['all', 'deleted', 'active'].includes(userState)) {
this.replaceWith('admin.users.view', userState);
}
}

Thus if the dynamic portion of the route doesn’t contain the parameters all, deleted or active, then it must be referring to a user’s events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the details of a given user like the email,  contact, various events created by the user to get the email and notification preferences. For getting each detail the current users model is returned and the values in the model are returned to the form.

For the contact-info sub route the values like the email and the contact number are fetched and are shown in the form. There is a save button in the form too. The admin can change this information and send a patch request to the server by clicking this button.

 updateContactInfo() {
this.set('isLoading', true);
let currentUser = this.get('model.user');
currentUser.save()
.then(() => {
this.get('notify').success(this.get('l10n').t('Your Contact Info has been updated'));
})
.catch(() => {
this.get('notify').error(this.get('l10n').t('An unexpected error occurred'));
})
.finally(() => {
this.set('isLoading', false);
});
}

For the email-preferences sub route the model has attributes like sessionSchedule, nextEvent etc.and the admin has the access to change the email-notifications for any event  created by any user. The client side has checkboxes to show the data to the user. The states of the checkboxes are determined by the data that we receive from the API. We also let the admin change the preferences of the email-notifications so that he can customise the notifications and keep the ones he wants some user to receive.

{{settings/email-preferences-section preferences=model}}

The sub route for email preferences:

export default Route.extend(AuthenticatedRouteMixin, {
titleToken() {
return this.get('l10n').t('Email Preferences');
},
model() {
const currentUser = this.modelFor('admin.users.view');
return currentUser.query('emailNotifications', { include: 'event' });
}
});

So, the admin has the access to change the information and the email notifications of a user.

Resources

Events API Integration on Admin User Route Open Event Frontend

This blog article will illustrate how the Events API has been integrated into the admin users route  Open Event Frontend, as well as how the action buttons are added to view, edit or delete the events of any user in the list by the admin.

To make the events user link in the user link column of the users table functional a new sub route is added to the app’s user route as follows:

this.route('users', function() {
     this.route('view', { path: '/:user_id' }, function() {
       this.route('events', function() {
         this.route('list', { path: '/:event_status' });
       });
     });

The newly added route further contains a dynamic sub route called list. This nested route fulfills the requirement of filtering the various events of a given user according to their states. Interestingly, the routes admin/users/view and admin/users/list are both dynamic and expect a parameter after /users/ hence, the app cannot distinguish between them on it’s own, thus explicit handling of the dynamic parameter of the routes was implemented, differentiating them on the basis of the route’s state as follows:

beforeModel(transition) {
this._super(...arguments);
const userState = transition.params[transition.targetName].users_status;
if (!['all', 'deleted', 'active'].includes(userState)) {
this.replaceWith('admin.users.view', userState);
}
}

Thus if the dynamic portion of the route doesn’t contain the parameters all, deleted or active, then it must be referring to a user’s events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the events of a given user by making use of the hasMany relationship a user has with his sessions. They are loaded in the route admin/users/view/events/list.js

model() {
const userDetails = this.modelFor('admin.users.view');
return this.store.findRecord('user', userDetails.id, {
include: 'events'
});

After fetching the the events from the server, a proper ember table is called in the template file of this route, and all the actions like viewing and editing an event are declared in the template.

{{events/events-table
columns=columns data=model.events
useNumericPagination=true
moveToDetails=(action 'moveToDetails')
editEvent=(action 'editEvent')
openDeleteEventModal=(action 'openDeleteEventModal')
}}

In the controller the columns of the table for events are defined and all the actions are defined.

moveToDetails(id) {
this.transitionToRoute('events.view', id);
},
editEvent(id) {
this.transitionToRoute('events.view.edit.basic-details', id);
},
deleteEvent() {
this.set('isLoading', true);
this.store.findRecord('event', this.get('eventId'), { backgroundReload: false }).then(function(event) {
event.destroyRecord();
})

So, the admin can view the list of the events of a particular user and send a patch or delete request for any event.

Resources

Adding a list view for the Sessions Public Page in Open Event Frontend

This blog article will describe how the sessions are listed in the public pages of an event in Open Event Frontend, which allows the user to view all the sessions of an event. The sessions are filtered as per date. The primary end point of Open Event API with which we are concerned with for fetching the the users details is GET /v1/events/{event_identifier}/sessions

The route of the public page fetches all the sessions of a particular events and filters them as per the criteria selected by the user. The user can view the sessions of a particular day, week or month. The user can also view the list of all the sessions. The query written in the route is:

async model(params) {
   const eventDetails = this.modelFor('public');
   let sessions =  null;
   if (params.session_status === 'today') {
     sessions = await this.get('store').query('session', {
       filter: [
         {
           and: [
             {
               name : 'event',
               op : 'has',
               val : {
                 name : 'identifier',
                 op : 'eq',
                 val : eventDetails.id
               }
             },
             {
               name : 'starts-at',
               op : 'ge',
               val : moment().startOf('day').toISOString()
             },
             {
               name : 'starts-at',
               op : 'lt',
               val : moment().endOf('day').toISOString()
             }
           ]
         }
       ]
     });
   } else {
     sessions = await this.get('store').query('session', {
       filter: [
         {
           name : 'event',
           op : 'has',
           val : {
             name : 'identifier',
             op : 'eq',
             val : eventDetails.id
           }
         }
       ]
     });
   }
   return {
     event  : eventDetails,
     session : sessions
   };
 }

The view route is located at app/e/{event_identifier}/sessions/all. This route will show all the sessions of the selected event. Similarly /week will show the sessions of a week and /month will show the sessions of a month.Four joint buttons are used in the UI of the public page to redirect to these routes.

To list the sessions ember component of session cards is used to include a session in a card with the details of the session like the time, abstract etc and also the session’s track and the details of the speakers like the name, information and social media accounts. In the template of the route this component is called and used in the UI within an ember component. In case there are no sessions that exist between a given time period, a helper text is displayed stating “No sessions exist for the given period”.

class="ui buttons"> {{#link-to 'public.sessions.list' model.event.id 'all' class="ui button"}}{{t 'All'}}{{/link-to}} {{#link-to 'public.sessions.list' model.event.id 'today' class="ui button"}}{{t 'Today'}}{{/link-to}} {{#link-to 'public.sessions.list' model.event.id 'week' class="ui button"}}{{t 'Week'}}{{/link-to}} {{#link-to 'public.sessions.list' model.event.id 'month' class="ui button"}}{{t 'Month'}}{{/link-to}}
class="ui raised very padded text container segment"> {{#each model.session as |session|}} {{public/session-item session=session}} {{else}}
class="ui disabled header">{{t 'No Sessions exist for this time period'}}
{{/each}} </div>

Resources