Adding Sessions and Events Statistics in the Admin Dashboard in Open Event Frontend

This blog article will illustrate how the admin statistics API for events and sessions is integrated and how the values of different types of sessions and events is added to the admin dashboard in   Open Event Frontend.

The primary end point of Open Event API with which we are concerned with for fetching the statistics are

GET /v1/admin/statistics/events

GET /v1/admin/statistics/events

import Route from '@ember/routing/route';

export default Route.extend({
 async model() {
   return {
     events: await this.get('store').queryRecord('admin-statistics-event', {
       filter: {
         name : 'id',
         op   : 'eq',
         val  : 1
       }
     })
sessions: await this.get('store').queryRecord('admin-statistics-session', {
       filter: {
         name : 'id',
         op   : 'eq',
         val  : 1
       }
     })
   };
 }
});

The route file helps to fetch the total count of each type of session and event through the queries written in the code. queryRecord is used instead of query because a single record is expected to be returned by the API. The view route is /admin.

The model needs to extend the base model class and all the attributes of the model will be number since the all the data obtained via these models from the API will be numerical statistics.

For Events:

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';export default ModelBase.extend({
draft     : attr('number'),
published : attr('number'),
past      : attr('number')
});

For Sessions:

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';export default ModelBase.extend({
confirmed : attr('number'),
accepted  : attr('number'),
submitted : attr('number'),
draft     : attr('number'),
rejected  : attr('number'),
pending   : attr('number')
});

Once we retrieve the values of the attributes from the queries written in the route file we display the values of pending, rejected, accepted sessions and published, draft, past events.

class="label"> {{t 'Accepted'}}
class="value">
class="ui teal label"> {{model.sessions.accepted}}
</div> </div>
class="ui small statistic">
class="label"> {{t 'Draft'}}
class="value">
class="ui yellow label"> {{model.sessions.pending}}
</div> </div>
class="ui small statistic">
class="label"> {{t 'Rejected'}}
class="value">
class="ui red label"> {{model.sessions.rejected}}

Resources

Continue ReadingAdding Sessions and Events Statistics in the Admin Dashboard in Open Event Frontend

Creating the View Route for Sessions in Open Event Frontend

This blog article will illustrate how the creation of the view route for sessions is done and how the sessions API is integrated with it on Open Event Frontend, which allows for the sessions and their associated data to be displayed. Also, it illustrates how the template for my-sessions is modified to make it possible to reuse it for the view route with desired changes.

The primary end point of Open Event API with which we are concerned with for fetching the the session details is

GET /v1/sessions/{session_identifier}

For displaying the complete session information on the view route, the session type,  speakers and session track are also required. All of these extra attributes have a relationship with a given session and hence can be fetched in a single query. Thus the model for the route is defined as follows:

model(params) {
return this.store.findRecord(‘session’, params.session_id, {
include: ‘session-type,speakers,track’
});

The view route is located at app/routes/my-sessions/view and the parent route, app/routes/my-sessions has another sub route within it called list. The list route shows upcoming and past sessions to the user based on the params passed to it. Thus a navigation is required to alternate between those two routes. However, this navigation should not be present in the view route. Thus the template my-sessions.hbs is modified as follows:

{{#if (and (not-includes session.currentRouteName ‘my-sessions.view’))}}
<h1 class=”ui header”>{{t ‘My Sessions’}}</h1>

{{#tabbed-navigation}}
{{#link-to ‘my-sessions.list’ ‘upcoming’ class=’item’}}
{{t ‘Upcomming Sessions’}}
{{/link-to}}
{{#link-to ‘my-sessions.list’ ‘past’ class=’item’}}
{{t ‘Past Sessions’}}
{{/link-to}}
{{/tabbed-navigation}}

</div>
{{outlet}}
</div>
{{else}}
{{outlet}}
{{/if}}

The session.currentRouteName property allows conditional rendering of the navigation component.

Finally the template for the view route is created:

   

{{#if (eq model.state ‘accepted’)}}

{{t ‘Accepted’}}

{{else if (eq model.state ‘pending’)}}

{{t ‘Pending’}}

{{else if (eq model.state ‘rejected’)}}

{{t ‘Rejected’}}

{{/if}}
</div>

{{t ‘From ‘}}{{moment-format model.startAt ‘ddd, MMM DD HH:mm A’}}{{t ‘ to ‘}}{{moment-format model.endsAt ‘ddd, MMM DD HH:mm A’}}

</div>

{{#if model.shortAbstract}}
<p> <i>{{model.shortAbstract}}</i> </p>
{{/if}}
{{#if model.sessionType.name}}
<h3 class=”ui left aligned header”>Session Type</h3>
<p>{{model.sessionType.name}}</p>
{{/if}}
{{#if model.track.name}}
<h3 class=”ui left aligned header”>Track</h3>
<p>{{model.track.name}}</p>
{{/if}}
{{#if model.slidesUrl}}
<h3 class=”ui left aligned header”>Slide</h3>
<a href=”{{model.slidesUrl}}”>{{t ‘Download’}}</a>
{{/if}}
</div>

Based on the state property of the session, the label displaying the status is appropriately coloured wherein green, yellow and red colours denote accepted, pending and rejected sessions respectively. Since most of the fields of the session model are optional in nature, all of the them are subjected to conditional checks for existence.

Resources

 

Continue ReadingCreating the View Route for Sessions in Open Event Frontend

Ember Data Integration In Badgeyay Frontend

Badgeyay is an open source utility to develop badges for events and tech conferences. Badgeyay project is divided into two components. Frontend part is designed with ember and backend part is designed with Flask and database as PostgreSQL and Firebase as PaaS.

After refactoring the backend API for generation of badges, now it is time to consume the API in frontend by ember, and the way to consume the api in ember front–end is with the use of in built ember-data library. Ember data behaves in a way similar to server side ORM’s (Object Relational Mappers). It is a very versatile library and can be equipped with variety of backend services. It can be used with REST as well as sockets and other transfer protocols for communication.

For better understanding the working of ember data, let’s see how to use the same to consume the File Upload endpoint in the backend.

Procedure

  1. Enabling CORS on server, to allow cross-domain requests to the API.
from flask_cors import CORS
CORS(app, resources={r"*": {"origins": "*"}})
  1. Creating Adapter for the model in frontend. In our case it is csv-file. In the adapter we need to specify the host and the path, because our backend api is not running on the same port.
import DS from 'ember-data';

const { RESTAdapter } = DS;

export default RESTAdapter.extend({
host : 'http://localhost:5000',
pathForType : () => {
return 'api/upload/file';
}
});
  1. After creating the adapter we need to create the record in the controller of the respective component. The record is like an object of a class, which when pushed to store will make a network request to backend (POST) and fetch the response from the backend. Backend response will provide the id to save in store
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';

export default Controller.extend({
routing : service('-routing'),
actions : {
mutateCSV(csvData) {
let csv_ = this.get('store').createRecord('csv-file', {
csvFile : csvData,
extension : 'csv'
});
csv_.save();
},

mutateText(txtData) {
console.log(txtData);
}
}
});

Model for the csv-file

import DS from 'ember-data';

const { Model, attr } = DS;

export default Model.extend({
csvFile : attr('string'),
extension : attr('string')
});
  1. Next is to create serializers for the model. Serializers gets triggered at two moments, first when the data is sent to the server and second when data is received from the server. Each time an independent function gets executed. As the naming conventions of the functions pretty much explains their role, but for the sake of clarification serialize function gets executed when we send request to the server and normalizeResponse gets executed when we are getting response from the server.
import DS from 'ember-data';

const { JSONAPISerializer } = DS;

export default JSONAPISerializer.extend({

serialize(snapshot, options) {
let json = this._super(...arguments);
json.csvFile = {
'csvFile' : json.data.attributes['csv-file'],
'extension' : json.data.attributes.extension
};

delete json.data;
return json;
},

normalizeResponse(store, primaryModelClass, payload, id, requestType) {
return payload;
}
});
  1. After receiving the response a promise is returned by the push method to save the record in the store and we can see the id is saved in the ember-data object.

Pull Request for the same is at this Link

Topics Involved

Working on the issue involve following topics:

  • Enabling CORS to accept cross-domain requests at server
  • Creating models in ember data
  • Passing action from controller to component
  • Modifying the Params and Response on the network sent by ember-data via serializers

 

Resources

  • Ember data repository – Link
  • Documentation for creating record in ember data – Link
  • API Doc for JSONAPIAdapter – Link
  • API Doc for JSONAPISerializer – Link
  • Property methods for serializer – serialize, normalizeResponse
Continue ReadingEmber Data Integration In Badgeyay Frontend

How App Social Links are specified in Open Event Frontend

This blog article will illustrate how the various social links are specified in the the footer of Open Event Frontend, using the settings API. Open Event Frontend, offers high flexibility to the admins regarding the settings of the App, and hence the media links are not hard coded, and can be changed easily via the admin settings panel.

The primary end point of Open Event API with which we are concerned with for fetching the settings  for the app is

GET /v1/settings

The model for settings has the following fields which concern the social links.

 googleUrl              : attr('string'),
 githubUrl              : attr('string'),
 twitterUrl             : attr('string')

Next we define them as segmented URL(s) so that they can make use of the link input widget.

segmentedTwitterUrl    : computedSegmentedLink.bind(this)('twitterUrl'),
 segmentedGoogleUrl     : computedSegmentedLink.bind(this)('googleUrl'),
 segmentedGithubUrl     : computedSegmentedLink.bind(this)('githubUrl'),

Now it is required for us to fetch the data from the API, by making the corresponding call to the API. Since the footer is present in every single page of the app, it is necessary that we make the call from the application route itself. Hence we add the following to the application route modal.

socialLinks: this.get('store').queryRecord('setting', {
})

Next we need to iterate over these social links, and add them to the footer as per their availability.So we will do so by first passing the model to the footer component, and then iterating over it in footer.hbs

{{footer-main socialLinks=model.socialLinks footerPages=footerPages}}


And thus we have passed the socialLinks portion of the model, under the alias socialLinks.Next, we iterate over them and each time check, if the link exists before rendering it.

<div class="three wide column">
     <div class="ui inverted link list">
       <strong class="item">{{t 'Connect with us'}}</strong>
       {{#if socialLinks.supportUrl}}
         <a class="item" href="{{socialLinks.supportUrl}}" target="_blank" rel="noopener noreferrer">
           <i class="info icon"></i> {{t 'Support'}}
         </a>
       {{/if}}
       {{#if socialLinks.facebookUrl}}
         <a class="item" href="{{socialLinks.facebookUrl}}" target="_blank" rel="noopener noreferrer">
           <i class="facebook f icon"></i> {{t 'Facebook'}}
         </a>
       {{/if}}
       {{#if socialLinks.youtubeUrl}}
         <a class="item" href="{{socialLinks.youtubeUrl}}" target="_blank" rel="noopener noreferrer">
           <i class="youtube icon"></i> {{t 'Youtube'}}
         </a>
       {{/if}}
       {{#if socialLinks.googleUrl}}
         <a class="item" href="{{socialLinks.googleUrl}}" target="_blank" rel="noopener noreferrer">
           <i class="google plus icon"></i> {{t 'Google +'}}
         </a>
       {{/if}}
     </div>
   </div>

Thus all the links in the app are easily manageable, from the admin settings menu, without the need of hard coding them. This approach also, makes it easy to preserve the configuration in a central location.

Resources

Continue ReadingHow App Social Links are specified in Open Event Frontend

Implementing Session and Speaker Creation From Event Panel In Open Event Frontend

Open-Event Front-end uses Ember data for handling Open Event Orga API which abides by JSON API specs. It allows the user to manage the event using the event panel of that event. This panel lets us create or update sessions & speakers. Each speaker must be associated with a session, therefore we save the session before saving the speaker.
In this blog we will see how to Implement the session & speaker creation via event panel. Lets see how we implemented it?

Passing the session & speaker models to the form
On the session & speaker creation page we need to render the forms using the custom form API and create new speaker and session entity. We create a speaker object here and we pass in the relationships for event and the user to it, likewise we create the session object and pass the event relationship to it.

These objects along with form which contains all the fields of the custom form, tracks which is a list of all the tracks & sessionTypes which is a list of all the session types of the event is passed in the model.

return RSVP.hash({
  event : eventDetails,
  form  : eventDetails.query('customForms', {
    'page[size]' : 50,
    sort         : 'id'
  }),
  session: this.get('store').createRecord('session', {
    event: eventDetails
  }),
  speaker: this.get('store').createRecord('speaker', {
    event : eventDetails,
    user  : this.get('authManager.currentUser')
  }),
  tracks       : eventDetails.query('tracks', {}),
  sessionTypes : eventDetails.query('sessionTypes', {})
});

We bind the speaker & session object to the template which has contains the session-speaker component for form validation. The request is only made if the form gets validated.

Saving the data

In Open Event API each speaker must be associated with a session, i.e we must define a session relationship for the speaker. To accomplish this we first save the session into the server and once it has been successfully saved we pass the session as a relation to the speaker object.

this.get('model.session').save()
  .then(session => {
    let speaker = this.get('model.speaker');
    speaker.set('session', session);
    speaker.save()
      .then(() => {
        this.get('notify').success(this.l10n.t('Your session has been saved'));
        this.transitionToRoute('events.view.sessions', this.get('model.event.id'));
      });
  })

We save the objects using the save method. After the speakers and sessions are save successfully we notify the user by showing a success message via the notify service.

Thank you for reading the blog, you can check the source code for the example here.

Resources

Continue ReadingImplementing Session and Speaker Creation From Event Panel In Open Event Frontend

Creating Dynamic Forms Using Custom-Form API in Open Event Front-end

In Open Event Front-end allows the the event creators to customise the sessions & speakers forms which are implemented on the Orga server using custom-form API. While event creation the organiser can select the forms fields which will be placed in the speaker & session forms.

In this blog we will see how we created custom forms for sessions & speakers using the custom-form API. Lets see how we did it.

Retrieving all the form fields

Each event has custom form fields which can be enabled on the sessions-speakers page, where the organiser can include/exclude the fields for speakers & session forms which are used by the organiser and speakers.

return this.modelFor('events.view').query('customForms', {});

We pass return the result of the query to the new session route where we will create a form using the forms included in the event.

Creating form using custom form API

The model returns an array of all the fields related to the event, however we need to group them according to the type of the field i.e session & speaker. We use lodash groupBy.

allFields: computed('fields', function() {
  return groupBy(this.get('fields').toArray(), field => field.get('form'));
})

For session form we run a loop allFields.session which is an array of all the fields related to session form. We check if the field is included and render the field.

{{#each allFields.session as |field|}}
  {{#if field.isIncluded}}
    <div class="field">
      <label class="{{if field.isRequired 'required'}}" for="name">{{field.name}}</label>
      {{#if (or (eq field.type 'text') (eq field.type 'email'))}}
        {{#if field.isLongText}}
          {{widgets/forms/rich-text-editor textareaId=(if field.isRequired (concat 'session_' field.fieldIdentifier '_required'))}}
        {{else}}
          {{input type=field.type id=(if field.isRequired (concat 'session_' field.fieldIdentifier '_required'))}}
        {{/if}}
      {{/if}}
    </div>
  {{/if}}
{{/each}}

We also use a unique id for all the fields for form validation. If the field is required we create a unique id as `session_fieldName_required` for which we add a validation in the session-speaker-form component. We also use different components for different types of fields eg. for a long text field we make use of the rich-text-editor component.

Thank you for reading the blog, you can check the source code for the example here.

Resources

Continue ReadingCreating Dynamic Forms Using Custom-Form API in Open Event Front-end

Keeping Order of tickets in Event Wizard in Sync with API on Open Event Frontend

This blog article will illustrate how the various tickets are stored and displayed in order the event organiser decides  on  Open Event Frontend and also, how they are kept in sync with the backend.

First we will take a look at how the user is able to control the order of the tickets using the ticket widget.

{{#each tickets as |ticket index|}}
  {{widgets/forms/ticket-input ticket=ticket
  timezone=data.event.timezone
  canMoveUp=(not-eq index 0)
  canMoveDown=(not-eq ticket.position (dec
  data.event.tickets.length))
  moveTicketUp=(action 'moveTicket' ticket 'up')
  moveTicketDown=(action 'moveTicket' ticket 'down')
  removeTicket=(confirm 'Are you sure you  wish to delete this 
  ticket ?' (action 'removeTicket' ticket))}}
{{/each}}

The canMoveUp and canMoveDown are dynamic properties and are dependent upon the current positions of the tickets in the tickets array.  These properties define whether the up or down arraow or both should be visible alongside the ticket to trigger the moveTicket action.

There is an attribute called position in the ticket model which is responsible for storing the position of the ticket on the backend. Hence it is necessary that the list of the ticket available should always be ordered by position. However, it should be kept in mind, that even if the position attribute of the tickers is changed, it will not actually change the indices of the ticket records in the array fetched from the API. And since we want the ticker order in sync with the backend, i.e. user shouldn’t have to refresh to see the changes in ticket order, we are going to return the tickets via a computed function which sorts them in the required order.

tickets: computed('data.event.tickets.@each.isDeleted', 'data.event.tickets.@each.position', function() {
   return this.get('data.event.tickets').sortBy('position').filterBy('isDeleted', false);
 })

The sortBy method ensures that the tickets are always ordered and this computed property thus watches the position of each of the tickets to look out for any changes. Now we can finally define the moveTicket action to enable modification of position for tickets.

moveTicket(ticket, direction) {
     const index = ticket.get('position');
     const otherTicket = this.get('data.event.tickets').find(otherTicket => otherTicket.get('position') === (direction === 'up' ? (index - 1) : (index + 1)));
     otherTicket.set('position', index);
     ticket.set('position', direction === 'up' ? (index - 1) : (index + 1));
   }

The moveTicket action takes two arguments, ticket and direction. It temporarily stores the position of the current ticket and the position of the ticket which needs to be swapped with the current ticket.Based on the direction the positions are swapped. Since the position of each of the tickets is being watched by the tickets computed array, the change in order becomes apparent immediately.

Now when the User will trigger the save request, the positions of each of the tickets will be updated via a PATCH or POST (if the ticket is new) request.

Also, the positions of all the tickets maybe affected while adding a new ticket or deleting an existing one. In case of a new ticket, the position of the new ticket should be initialised while creating it and it should be below all the other tickets.

addTicket(type, position) {
     const salesStartDateTime = moment();
     const salesEndDateTime = this.get('data.event.startsAt');
     this.get('data.event.tickets').pushObject(this.store.createRecord('ticket', {
       type,
       position,
       salesStartsAt : salesStartDateTime,
       salesEndsAt   : salesEndDateTime
     }));
   }

Deleting a ticket requires updating positions of all the tickets below the deleted ticket. All of the positions need to be shifted one place up.

removeTicket(deleteTicket) {
     const index = deleteTicket.get('position');
     this.get('data.event.tickets').forEach(ticket => {
       if (ticket.get('position') > index) {
         ticket.set('position', ticket.get('position') - 1);
       }
     });
     deleteTicket.deleteRecord();
   }

The tickets whose position is to be updated are filtered by comparison of their position from the position of the deleted ticket.

Resources

Continue ReadingKeeping Order of tickets in Event Wizard in Sync with API on Open Event Frontend

Implementing Order Statistics API on Tickets Route in Open Event Frontend

The order statistics API endpoints are used to display the statistics related to tickets, orders, and sales. It contains the details about the total number of orders, the total number of tickets sold and the amount of the sales. It also gives the detailed information about the pending, expired, placed and completed orders, tickets, and sales.

This article will illustrate how the order statistics can be displayed using the Order Statistics API in Open Event Frontend. The primary end point of Open Event API with which we are concerned with for statistics is

GET /v1/events/{event_identifier}/order-statistics

First, we need to create a model for the order statistics, which will have the fields corresponding to the API, so we proceed with the ember CLI command:

ember g model order-statistics-tickets

Next, we need to define the model according to the requirements. The model needs to extend the base model class. The code for the model looks like this:

import attr from 'ember-data/attr';
import ModelBase from 'open-event-frontend/models/base';

export default ModelBase.extend({
  orders  : attr(),
  tickets : attr(),
  sales   : attr()
});

As we need to display the statistics related to orders, tickets, and sales so we have their respective variables inside the model which will fetch and store the details from the API.

Now, after creating a model, we need to make an API call to get the details. This can be done using the following:

return this.modelFor('events.view').query('orderStatistics', {});

Since the tickets route is nested inside the event.view route so, first we are getting the model for event.view route and then we’re querying order statistics from the model.

The complete code can be seen here.

Now, we need to call the model inside the template file to display the details. To fetch the total orders we can write like this

{{model.orders.total}}

 

In a similar way, the total sales can be displayed like this.

{{model.sales.total}}

 

And total tickets can be displayed like this

{{model.tickets.total}}

 

If we want to fetch other details like the pending sales or completed orders then the only thing we need to replace is the total attribute. In place of total, we can add any other attribute depending on the requirement. The complete code of the template can be seen here.

The UI for the order statistics on the tickets route looks like this.

Fig. 1: The user interface for displaying the statistics

The complete source code can be seen here.

Resources:

Continue ReadingImplementing Order Statistics API on Tickets Route in Open Event Frontend

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 this page?') (action 'deletePage' data))}}>
{{t 'Delete'}}</button>

 

The code to delete the page looks like this

deletePage(data) {
    if (!this.get('isCreate')) {
      data.destroyRecord();
      this.set('isFormOpen', false);
    }
  }

In the above piece of code, we’re checking whether the form is in create mode or update mode and if it’s in create mode then we can destroy the record and then close the form.

The UI for the pop up looks like this.

Fig.3: The user interface for delete confirmation pop up

The code for the entire process of page creation to deletion can be checked here

To conclude, this is how we efficiently do the process of page creation, updating and deletion using the Open-Event-Orga pages API  ensuring that there is no unnecessary API call to fetch the data and no code duplication.

Resources:

Continue ReadingImplementing Pages API in Open Event Frontend

Implementing the tickets API at the ‘tickets/add-order’ route

In Open Event Frontend, we have the ‘tickets/add-order’ route for a specific event which facilitates us to add the order based on the tickets that we create at the time of creation of event. The tickets are listed at the ‘tickets/add-order’ route where we can select the tickets required for example, ‘free’, ‘paid’, the payment type and proceed to the ticket buyer’s info page.

This is how we achieved implementing the API:
We use table to show the data to the user, the columns of which are Ticket Type, Price, Quantity, Item Total something like:

So, the workflow to achieve this is as follows:

  • Query the tickets for current event.
  • Have a controller to calculate the ‘Grand Total’ of the individual tickets that the user wants to buy.
  • Show the tickets in our table.

Querying the tickets: Since we are using ember data, we query tickets by the following query in our model method of route.

  model() {
    return this.modelFor('events.view').query('tickets', {});
  }

Thus, the above query shows that we get the current event by actually querying the model for route ‘events.view’ which returns the current event and then query the tickets model so that we get the tickets associated with the current event.

Since there is no UI table support for ember data, we are using a custom table for all the tables in Open Event Frontend and pass the data to it. To render the data in tables, we follow the following approach.
In our controller, we have a columns property as:

columns: [
    {
      propertyName : 'name',
      title        : 'Ticket Type'
    },
    {
      propertyName   : 'price',
      title          : 'Price(US$)',
      disableSorting : true
    },
    {
      propertyName : '',
      title        : 'Quantity',
      template     : 'components/ui-table/cell/cell-input-number'
    },
    {
      propertyName : 'itemTotal',
      title        : 'Item Total'
    }
  ]

The propertyName maps the property of the objects returned from the server i.e in our case, the ‘tickets’. Thus, we pass this skeleton of columns and data from the model to our component so as to render the table in view.

  {{events/events-table columns=columns data=model
    useNumericPagination=true
    showGlobalFilter=true
    showPageSize=true
  }

Also, as seen from the image shown earlier in this blog post, we can see that we also need to calculate the ‘Grand Total’ of the total purchase. Thus, we have a computed property in controller to do this:

  total: computed('model.@each.itemTotal', function() {
    let sum = 0.0;
    this.get('model').forEach(ticket => {
      sum += ticket.get('itemTotal');
    });
    return sum;
  }),

We iterate over the each ‘itemTotal’ in the model and keep on adding it so that the total purchase gets added accordingly. Lastly we show the Grand Total to the user as seen in the image shown earlier in the blog.
Thus, the user can select the tickets and proceed towards the checkout.

Resources:
Ember data official guide
Blog on ember data by Andy Crum

Continue ReadingImplementing the tickets API at the ‘tickets/add-order’ route