Structure of Open Event Frontend

In Open Event Frontend, new contributors always fall into a dilemma of identifying the proper files where they have to make changes if they want to contribute. The project structure is quite complex and which is obvious because it is a large project. So, in this blog, we will walk through the structure of Open Event Frontend.

Following are the different folders of the project explained:

Root:
The root of the project contains folders like app, config, kubernetes, tests, scripts. Our main project is in the app folder where all the files are present. The config folder in the root has files related to the deployment of the app in development, production, etc. It also has the environment setup such as host, api keys, etc. Other files such as package.json, bower.json, etc are basically to store the current versions of the packages and to ease the installation of the project.

App:
The app folder has all the files and is mainly classified into the following folder:
adapters
components
controllers
helpers
Initializers
mixins
models
routes
serializers
services
styles
templates
transforms
utils

The folders with their significance are listed below:

Adapters: This folder contains the files for building URLs for our endpoints. Sometimes it happens to have a somewhat customised URL for an endpoint which we pass through adapter to modify it.
Components: This folder contains different components which we reuse in our app. For example, the image uploader component can be used at multiple places in our app, so we keep such elements in our components. This folder basically contains the js files of all the components(since when we generate a component, a js file and a hbs template is generated).
Controllers: This folder contains the controller associated with each route. Since the main principle of ember js is DDAU i.e data down actions up, all the actions are written in the files of this folder.
Helpers: Many a time it happens that, we want to format date, time, encode URL etc. There are some predefined helpers but sometimes custom helpers are also needed. All of them have been written in helpers folder.
Initializers: This folder has a file for now called ‘blanket.js’ which basically injects the services into our routes, components. So if you want to write any service and want to inject it into routes/components, it should go in here.
Mixins: In EmberJS the Mixin class can create objects whose properties and functions can be shared amongst other classes and instances. This allows for an easy way to share behavior between objects as well as design objects that may need multiple inheritance. All of them used for the application are in the mixins folder.
Models: This folder contains the schema’s for our data. Since we are using ember data, we need to have proper skeleton of the data. All of this goes it this folder. Observing this folder will show you some models like user, event, etc.
Routes: This folder contains the js files of the routes created. Routes handle which template to render and what to return from the model, etc.
Serializers: We use serializers to modify the data that ember sends automatically in a request. Consider we want to get a user with the help of user model, and don’t want to get the password attribute present in it. We can thus omit that by defining it in a serializer.
Services: Services are the ember objects which are available throughout the running time of the application. These are used to perform tasks like getting current user model, making third party API calls etc. All such services go in this folder.
Styles: As the name infers, all the style sheets go in here.
Templates: A template is generated with generation of each route and component. All of them go here. Thus, the markup will be written over here.
Transforms: Ember Data has a feature called transforms that allow you to transform values before they are set on a model or sent back to the server. In our case, we have a transform called moment.
Utils: This folder contains some functions exported as modules which are reusable. There is some JSON data as well.

References: Ember JS official guide: https://guides.emberjs.com/v2.17.0/
Blog posts: https://spin.atomicobject.com/2015/09/17/ember-js-clean/
http://www.programwitherik.com/ember-pods/

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

Implement Sessions API for the ‘admin/sessions’ Route in Open Event Frontend

In Open Event Frontend, under the ‘admin/sessions’ route, the admin can track the record of the sessions. The info which is shown along with the sessions is the speakers for the session, its title, submitted date, start time, end time. So to integrate the sessions API, we followed the following approach:

Firstly, we add a sessions model and establish its relationship with the speakers model since we need speaker names also:

export default ModelBase.extend({
  title         : attr('string'),
  subtitle      : attr('string'),
  startsAt      : attr('moment'),
  endsAt        : attr('moment'),
  j
  sessionType   : belongsTo('session-type'),
  microlocation : belongsTo('microlocation'),
  track         : belongsTo('track'),
  speakers      : hasMany('speaker'),
  event         : belongsTo('event'), // temporary
});

After creating the model for sessions, we do the query to get the sessions.
Since, the sessions here are classified into the sections like ‘pending’, ‘accepted’, ‘rejected’, ‘deleted’, etc, we need to filter the response returned by the query:

if (params.sessions_state === 'pending') {
      filterOptions = [
        {
          name : 'state',
          op   : 'eq',
          val  : 'pending'
        }
      ];
    } else if (params.sessions_state === 'accepted') {
      filterOptions = [
        {
          name : 'state',
          op   : 'eq',
          val  : 'accepted'
        }
      ];
    }

Above code shows how we filter the response on the server side itself. Hence we pass the filterOptions array to the query as follows:

return this.get('store').query('session', {
      include      : 'event,speakers',
      filter       : filterOptions,
      'page[size]' : 10
});

Once we get data from the query, we just pass it to our controller to implement the columns.
Once we retrieve data from the query, we just pass it to our controller to implement the columns. Since, we are using a different way to render the ember data in the column, the approach goes as follows:
In our controller we do:

export default Controller.extend({
  columns: [
    {
      propertyName   : 'event.name',
      title          : 'Event Name',
      disableSorting : true
    },
    {
      propertyName : 'title',
      title        : 'Title'
    },
    {
      propertyName   : 'speakers',
      template       : 'components/ui-table/cell/cell-speakers',
      title          : 'Speakers',
      disableSorting : true
    }
  ]
});

I have shown here only limited columns, there are others too. Here, we are mapping the propertyName to the attribute returned by the server. Also the ‘title’ indicates the column name. We can also create a custom template(as a component) if we want customization while rendering the data in the rows and columns. For example, if we want to iterate the multiple speaker names for a session, we can do:

<div class="ui list">
  {{#each record.speakers as |speaker|}}
    <div class="item">
      {{speaker.name}}
    </div>
  {{/each}}
</div>

As a result, we can make custom templates for a particular property. Another example of formatting the response is:

<span>
  {{moment-format record.startsAt 'MMMM DD, YYYY - HH:mm A'}}
  {{moment-format record.endsAt 'MMMM DD, YYYY - HH:mm A'}}
</span>

Thus, we get the following view after integration of API:

Resources:

Ember data Official guide

Blog on medium: By “Embedly” publication

Implementing Notifications API in Open Event Frontend

In Open Event Frontend, at the index page of the application, we have a notification dropdown in which a user gets the notifications regarding the events, sessions, etc. Thus, a user gets notified for the particular event or session he wants to receive notifications about. While dealing with an issue, we had to integrate the API with the frontend. We achieved it as follows:

First, we create a model of notifications so that we have basic structure ready. It goes as follows:

export default ModelBase.extend({
  title      : attr('string'),
  message    : attr('string'),
  isRead     : attr('boolean', { defaultValue: false }),
  receivedAt : attr('moment'),

  user: belongsTo('user')
});

Thus, we have fields like title, message, isRead, receivedAt which we will get from the server response as JSON which we will need to show on the page. To show the notifications to the user, first we need to query the notifications for a specific user using ember data. Since we are querying the notifications for a specific user when he is logged in, we are also having relationship between user and notification as shown in the above notification model. In user model we do:

notifications: hasMany('notification')

Now, we query the notifications in our application route i.e routes/application.js

model() {
    if (this.get('session.isAuthenticated')) {
      return new RSVP.Promise((resolve, reject) => {
        this.store.findRecord('user', this.get('authManager.currentUser.id'), { reload: true })
          .then(user => {
            user.get('notifications').then(resolve).catch(reject);
          })
          .catch(reject);
      });
    }
  }

The reason why we used a RSVP promise here was because the authManager couldn’t load the user befor the notifications were queried and returned. Thus, we query the notifications by using currentUser from authManager. Thus, in our template, we iterate over our notifications as follows:

    {{#each notifications as  notification }}
      <div class="item">
        <div class="header">
          {{notification.title}}
        </div>
        <div class="content weight-600">
          {{notification.description}}
        </div>
        <div class="left floated content">
          {{moment-from-now notification.createdAt}}
        </div>
      </div>
    {{/each}}

The notifications are thus shown to the user when he clicks the icon in the nav-bar. As a result, we get the following notifications in the dropdown:

Resources:

Ember data official guide

Blog on Ember data by Embedly.

Implementing Email Notifications in Open Event Frontend

In Open Event Frontend, we have a ‘settings/email-preferences’ route where we give the user an access to change the email-notifications that he wants to subscribe for a specific event. For now, we are having three notifications for an event which the user can toggle to on and off. To achieve this, we did the following things:

First, we create a model for email-notifications so as to have a skeleton of the data that we are going to receive from the JSON API.

export default ModelBase.extend({

  /**
   * Attributes
   */

  nextEvent           : attr('boolean'),
  newPaper            : attr('boolean'),
  sessionAcceptReject : attr('boolean'),
  sessionSchedule     : attr('boolean'),
  afterTicketPurchase : attr('boolean'),

  /**
   * Relationships
   */
  user  : belongsTo('user'),
  event : belongsTo('event')
});

Thus, above code shows the attributes which we are going to receive via our JSON API and we will render the data accordingly on the page. We have established the relationship of the email-notifications with user and event so that in future wherever needed, we can query the records from either side. The client side has checkboxes to show the data to the user. Following is the format of the checkboxes:

<div class="row">
        <div class="column eight wide">
          {{t 'New Paper is Submitted to your Event'}}
        </div>
        <div class="ui column eight wide right aligned">
          {{ui-checkbox class='toggle' checked=preference.newPaper onChange=(pipe-action (action (mut preference.newPaper)) (action 'savePreference' preference))}}
        </div>
      </div>
      <div class="row">
        <div class="column eight wide">
          {{t 'Change in Schedule of Sessions in your Event'}}
        </div>
        <div class="ui column eight wide right aligned">
          {{ui-checkbox class='toggle' checked=preference.sessionSchedule onChange=(pipe-action (action (mut preference.sessionSchedule)) (action 'savePreference' preference))}}
        </div>
      </div>

The states of the checkboxes are determined by the data that we receive from the API. For example, if for a record, the API returns:

nextEvent           : true,
newPaper            : false,
sessionAcceptReject : true,
sessionSchedule     : false,
afterTicketPurchase : false,

Then, the respective states will be shown by the checkbox and the user can toggle the states to change the email-preferences as they want.

Thus to get the data sent by the server to the client, we return it as a model and query it as:

model() {
    return this.get('authManager.currentUser').query('emailNotifications', { include: 'event' });
}

As we can see, as mentioned earlier, we kept the relationships so that we can query the email-notifications specific to the particular user or specific to particular event. Here, we are showing a user’s email-notifications and hence we queried it with the user relationship.
The authManager loads the currentUser and queries the email-notifications for a particular use. We also want the event details to show the email-preferences, hence we include the event model to be fetched in the query also.

We also let the user change the preferences of the email-notifications so that he can customise the notifications and keep the ones he wants to receive. We implement the updating of email-preferences API as follows:

Whenever a user toggles the checkbox, we are having an action as called ‘savePreference’, which handles the updation of the preferences.

 

{{ui-checkbox class='toggle' checked=preference.newPaper onChange=(pipe-action (action (mut preference.newPaper)) (action 'savePreference' preference))}}

savePreference(emailPreference) {
      emailPreference.save()
        .then(() => {
          this.get('notify').success(this.l10n.t('Email notifications updated successfully'));
        })
        .catch(() => {
          emailPreference.rollbackAttributes();
          this.get('notify').error(this.l10n.t('An unexpected error occurred.'));
        });
    }

We are passing the parameter(the whole preference object to the action), and then just performing a ‘save’ method on it which will send a PATCH request to the server to update the data.

Thus, in this way, the user can change the email-notification preferences in the Open Event Frontend.

Resources:
Ember data Official guide
Blog on Models and Ember data by Daniel Lavigne: Learning Ember.js Part 4: Models

Source code: https://github.com/fossasia/open-event-frontend/pull/537/files

Stubbed Routing Inbuilt Service used in Open Event Frontend

In Open Event Frontend, we have used services like ‘auth-manager’, ‘l10n’, ‘loader’, ‘sanitizer’, etc to ease our work with the help of predefined-functions in those services. However, while dealing with an issue in the project, there was a need to use ‘Routing’ as a service.

In the issue, we wanted to generate an access link dynamically from the access code entered by the user. The format of the access link was as follows:

“base_url + event_id + access_code”

So, for the above URL, we needed to have ‘event_id’ and ‘access_code’.

The ‘access_code’ can be readily accessed from the user’s input itself, whereas to get the event_id, we used the ‘Routing’ service in Ember.

Generally to use a service in Ember, it has to be written first,then registered, injected and then used.

‘Routing’ service in Ember is an inbuilt service unlike the ones listed at the beginning.

There is no need to write it. It can be simply registered, injected and used.

this.register('service:routing', routingStub);
this.inject.service('routing', { as: 'routing' });

where ‘register’ and ‘inject’ are the methods on Ember objects.

The integration tests in Open Event Frontend are written such that the services can be used without injecting, but the tests will fail. To pass those tests, we had to register and inject the service in the required component.

The Routing service could thus be registered and injected into the specific component( injection in the component’s integration test ) only but for future needs, this service might be needed in any other component too. For this purpose, this service was registered and injected in ‘component-helper.js’.

const routingStub = Service.extend({
  router: {
    router: {
      state: {
        params: {
          'events.view': {
            event_id: 1
          }
        }
      }
    },
    generate() {
      return 'http://dummy-url.com';
    }
  }
});


export default function(path, name, testCase = null) {
  moduleForComponent(path, name, {
    integration: true,

    beforeEach() {
      this.register('service:routing', routingStub);
      this.inject.service('routing', { as: 'routing' });
      this.register('service:l10n', L10n);
      this.inject.service('l10n', { as: 'l10n' });
      this.application = startApp();
      l10nTestHelper(this);
      run(() => fragmentTransformInitializer.initialize(getOwner(this)));
    }
  }
}

Stubbing a Service: This is a process of faking an app of importing a service when no path is available to import. Stubbing of a service is mainly done when one needs to deal with the testing of the app. In our case, the same is done. We have stubbed the ‘Routing’ service in order to deal with the testing part. It can be seen from the above code that we have generated a ‘routingStub’ which fakes the app while registering the service in the ‘beforeModel’. The next line of code shows the ‘injection’ of service into the app.

Now we are just left with one task i.e to pass ‘routing’ from our integration tests to the component.

test('it renders', function(assert) {
  this.render(hbs`{{forms/events/view/create-access-code routing=routing}}`);
  assert.ok(this.$().html().trim().includes('Save'));
});

Above code shows the same.

Thus we can stub the services in Ember when any component depends on them.

Resources:

Official Ember guide: https://guides.emberjs.com/v2.1.0/testing/testing-components

Blog by Todd Jordan: http://presentationtier.com/stubbing-services-in-emberjs-integration-tests/

Source codehttps://github.com/sumedh123/open-event-frontend/blob/0b193ca679ce3b51f65e19ee0d03ac6a679258de/tests/helpers/component-helper.js

Creating System Images UI in Open Event Frontend

In Open Event Frontend, under the ‘admin/content’ route, ‘system-images’ route is present in which a user can update the image of the event topic he has uploaded at the time of creating an event. We achieved this as follows:

First, we create a route called ‘system/images’.

ember g route admin/content/system-images

This will generate three files:
1) routes/admin/content/system-images.js (route)
2) templates/admin/content/system-images.hbs (template)
3) test/unit/routes/admin/content/system-images-test.js (test file)
We also create a subroute of system-images route so as to render the subtopics queried through API.

ember g route admin/content/system-images/list

This will generate three files:
1) routes/admin/content/system-images/list.js(subroute)
2) templates/admin/content/system-images/list.hbs(template)
3) test/unit/routes/admin/content/system-imageslist-test.js(test file)

From our ‘system-images’ route, we render the ‘system-images’ template. We have a subroute of system-images route called as ‘list’ in which we render the subtopics available to us via API. The left side menu is the content of ‘system-images.hbs’ and the content on the right is it’s subroute i.e ‘list.hbs’. The ‘list’ subroute provides a facility to upload the system image. The API returns an array of objects containing subtopics as follows(single object is shown here, there will be multiple in the array)

{
            id          : 4545,
            name        : 'avatar',
            placeholder : {
              originalImageUrl : 'https://placeimg.com/360/360/any',
              copyright        : 'All rights reserved',
              origin           : 'Google Images'
            }
          },

Following is the content of our uploader i.e ‘list.hbs’ which is a subroute of the system-images.hbs.

<div class="ui segment">
  {{#each model as |subTopic|}}
    <h4>{{subTopic.name}}</h4>
    <img src="{{subTopic.placeholder.originalImageUrl}}" class="ui fluid image" alt={{subTopic.name}}>
    <div class="ui hidden divider"></div>
    <button class="ui button primary" {{action 'openModal' subTopic}} id="changebutton">{{t 'Change'}}</button>
  {{/each}}
</div>
{{modals/change-image-modal isOpen=isModalOpen subTopic=selectedSubTopic}}

We can see from the above template that we are iterating the response(subtopics) from the API. For now, we are just using the mock server response since we don’t have API ready for it. There is one ‘upload’ button which opens up the ‘change-image-modal’ to upload the image which looks as follows:

The ‘change-image-modal.hbs’ has a content as follows:

<div class="sixteen wide column">
        {{widgets/forms/image-upload
          needsCropper=true
          label=(t 'Update Image')
          id='user_image'
          aspectRatio=(if (eq subTopic.name 'avatar') (array 1 1))
          icon='photo'
          hint=(t 'Select Image')
          maxSizeInKb=10000
          helpText=(t 'For Cover Photos : 300x150px (2:1 ratio) image.
                    For Avatar Photos : 150x150px (1:1 ratio) image.')}}

        <form class="ui form">
          <div class="field">
            <label class="ui label">{{t 'Copyright information'}}</label>
            <div class="ui input">
              {{input type="text"}}
            </div>
          </div>
          <div class="field">
            <label class="ui label">{{t 'Origin information'}}</label>
            <div class="ui input">
              {{input type="text"}}
            </div>
          </div>
        </form>

      </div>

The above uploader has a custom ‘image-upload’ widget which we are using throughout the Open Event Frontend. Also, there are two input fields i.e ‘copyright’ and ‘origin’ information of the image. On clicking the ‘Select Image’ button and after selecting our image from the file input, we get a cropper for the image to be uploaded. The image can be cropped there according to the aspect ration maintained for it. The cropper looks like:

Thus, a user can update the image of the Event Topic that he created.

Resources:

Ember JS Official guide.

Mastering modals in Ember JS by Ember Guru.

Source codehttps://github.com/fossasia/open-event-frontend

 

Implementing JSON API for ‘settings/contact-info’ route in Open Event Frontend

In Open Event Frontend, under the settings route, there is a ‘contact-info’ route which allows the user to change his info (email and contact). Previously to achieve this we were using the mock response from the server. But since we have the JSON API now we could integrate and use the JSON API for it so as to let the user modify his/her email and contact info. In the following section, I will explain how it is built:

The first thing to do is to create the model for the user so as to have a skeleton of the database and include our required fields in it. The user model looks like:

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'),
  details   : attr('string'),
  contact   : attr('string'),
});

Above is the user’s model, however just the related fields are included here(there are more fields in user’s model). The above code shows that email and contact are two attributes which will accept ‘string’ values as inputs. We have a contact-info form located at ‘settings/contact-info’ route. It has two input fields as follows:

<form class="ui form {{if isLoading 'loading'}}" {{action 'submit' on='submit'}} novalidate>
  <div class="field">
    <label>{{t 'Email'}}</label>
    {{input type='email' name='email' value=data.email}}
  </div>
  <div class="field">
    <label>{{t 'Phone'}}</label>
    {{input type='text' name='phone' value=data.contact}}
  </div>
  <button class="ui teal button" type="submit">{{t 'Save'}}</button>
</form>

The form has a submit button which triggers the submit action. We redirect the submit action from the component to the controller so as to maintain ‘Data down, actions up’. The code is irrelevant, hence not shown here. Following is the action which is used to update the user which we are handling in the contact-info.js controller.

updateContactInfo() {
      this.set('isLoading', true);
      let currentUser = this.get('model');
      currentUser.save({
        adapterOptions: {
          updateMode: 'contact-info'
        }
      })
        .then(user => {
          this.set('isLoading', false);
          let userData = user.serialize(false).data.attributes;
          userData.id = user.get('id');
          this.get('authManager').set('currentUserModel', user);
          this.get('session').set('data.currentUserFallback', userData);
          this.get('notify', 'Updated information successfully');
        })
        .catch(() => {
        });
    }

We are returning the current user’s model from the route’s model method and storing it into ‘currentUser’ variable. Since we have data binding ember inputs in our contact-info form, the values will be grabbed automatically once the form submits. Thus, we can call ‘save’ method on the ‘currentUser’ model and pass an object called ‘adapterOptions’ which has key ‘updateMode’. We send this key to the ‘user’ serializer so that it picks only the attributes to be updated and omits the other ones. We have customized our user serializer as:

if (snapshot.adapterOptions && snapshot.adapterOptions.updateMode === 'contact-info') {
        json.data.attributes = pick(json.data.attributes, ['email', 'contact']);
}

The ‘save’ method on the ‘currentUser’ ‘currentUser.save()’ returns a promise. We resolve the promise by setting the ‘currentUserModel’ as the updated ‘user’ as returned by the promise. Thus, we are able to update the email and contact-info using the JSON API.

Resources:

Ember data official guide

Blog on models and Ember data by Embedly

Maintaining Aspect Ratio of Images while Uploading in Open Event Frontend

In Open Event Frontend, we are using the image-upload at many places such as the cover photo of the event on the create event page, also at the system images (in the admin routes) where the user gets to change the image uploaded by him earlier, and also at the ‘profile’ route where the user can change his photo. But at different places, different dimensions of photos are needed since we are keeping standard in Open Event Frontend.

Therefore the image needs to be in a size with the correct ratio at the time of uploading. We are using the cropper component  for achieving this. The cropper is a modal which pops up after the image upload modal so that a user can crop the image according to the aspect ratio kept specific for that purpose. It looks as follows:

While dealing with an issue in Open Event Frontend, we had to have change in aspect ratio for ‘avatar’ images, unlike the other images. So, we had to modify our cropper modal so as to have different aspect ratios for different images when needed.

We solved the above problem as follows:

In our image-modal, we pass the aspect ratio as a parameter. So in our case, we wanted to set the aspect ratio 1:1 for the ‘avatar’ images only. Following is the code snippet of what we wanted:

{{widgets/forms/image-upload
needsCropper=true
label=(t 'Update Image')
id='user_image'
aspectRatio=(if (eq subTopic.name 'avatar') (array 1 1))
icon='photo'
hint=(t 'Select Image')
maxSizeInKb=10000
helpText=(t 'For Cover Photos : 300x150px (2:1 ratio) image.
For Avatar Photos : 150x150px (1:1 ratio) image.')}}

Thus, we passed the ‘aspectRatio’ as a parameter to the ‘image-upload’ modal. The image-upload further calls the cropper modal and passes ‘aspectRatio’.

{{#if needsCropper}}
{{modals/cropper-modal isOpen=cropperModalIsShown imgData=imgData onImageCrop=(action 'imageCropped') aspectRatio=aspectRatio}}
{{/if}}

Thus, we can use the passed param i.e ‘aspectRatio’ in our cropper to modify the logic for the aspect ratio. Following is what we did so as to obtain the aspect Ratio of 1:1 for ‘avatar’ images only. The default aspect ratio for all other images is 2:1.

onVisible() {
let viewPort = {};
let factor = 150;
const aspectRatio = this.getWithDefault('aspectRatio', [2, 1]);
viewPort.width = aspectRatio[0] * factor;
viewPort.height = aspectRatio[1] * factor;
viewPort.type = 'square';
this.$('.content').css('height', '300px');
this.$('img').croppie({
customClass : 'croppie',
viewport : viewPort,
boundary : {
height: 250
}
});
},

As shown above, we have kept a multiplying factor which is fixed. According to the aspect ratio specified, we calculate and set the width and height in the viewport object.

Thus, following is the thing (Aspect Ratio 1:1 ) we achieved for the ‘avatar’ images:


Resources
Official Ember JS guide: https://guides.emberjs.com/v1.10.0/templates/actions/

Blog on making our own modals: http://ember.guru/2014/master-your-modals-in-ember-js

Source code: https://github.com/sumedh123/open-event-frontend/tree/system-images

Rendering Child Route Templates Independent of Parent Template in Open Event Frontend

In the Open Event Frontend, we are having routes and it’s child routes so as to render our content on the webpage. Child routes are needed when we want to have multiple routes under a single route as a subroute. In Ember js, the child routes of a route inherit the parent content in them by default . But, in some cases, we may not want the parent template’s content to get rendered into the child route and may want the child template to be entirely different. This issue deals with the same that how can we achieve the child template entirely different from the parent one.

                                                  events/tickets/index

                                                   events/tickets/attendees

                                                   events/tickets/orders

Above screenshots show the ‘events/tickets’ and it’s child routes. Since the ‘tickets’ is the parent and it contains the left side menu as it’s content which is common in all the child routes shown (orders, attendees, index). The content on the right of the side menu is the child’s own content. Now let’s take the case of ‘tickets/access-codes’ route which has a child route ‘tickets/access-codes/create’ where we do not want to have the parent content in it.

Following screenshots show the above thing that we want to replicate:

                                                 events/tickets/access-codes

This is the parent template (in the issue) whose child template had to be entirely different which is shown as follows:

                                                events/tickets/access-codes/create

By default, the content of the parent template i.e ‘tickets/access-codes’ would have been rendered in create.hbs which is it’s child as shown above.

To avoid this behavior, I used the current route path which helps to customize the contents to be rendered in the child. For this I implemented a check which matches the currentRouteName with the one which we want to render (child). If it does not match, then the content of the parent will be rendered along with the child, otherwise, a separate template (only child) without the parent’s content will be rendered. Following code illustrates the same:

{{#if (not-includes session.currentRouteName 'events.view.tickets.access-codes.create')}}
<!-- Things you want to show with the parent content in it.-->
{{else}}
<!-- Things you don't want parent to be in.-->
 {{outlet}}
{{/if}}

 

Conclusion:

In this way we can customize whether to include the parent’s content in child routes or completely render different child route independent of parent’s content in it.

Resources: