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

Implementing Tickets API on Open Event Frontend to Display Tickets

This blog article will illustrate how the tickets are displayed on the public event page in Open Event Frontend, using the tickets API. It will also illustrate the use of the add on, ember-data-has-query, and what role it will play in fetching data from various APIs. Our discussion primarily will involve the public/index route. The primary end point of Open Event API with which we are concerned with for fetching tickets for an event is

GET /v1/events/{event_identifier}/tickets

Since there are multiple  routes under public  including public/index, and they share some common event data, it is efficient to make the call for Event on the public route, rather than repeating it for each sub route, so the model for public route is:

model(params) {
return this.store.findRecord('event', params.event_id, { include: 'social-links' });
}

This modal takes care of fetching all the event data, but as we can see, the tickets are not included in the include parameter. The primary reason for this is the fact that the tickets data is not required on each of the public routes, rather it is required for the index route only. However the tickets have a has-many relationship to events, and it is not possible to make a call for them without calling in the entire event data again. This is where a really useful addon, ember-data-has-many-query comes in.

To quote the official documentation,

Ember Data‘s DS.Store supports querying top-level records using the query function.However, DS.hasMany and DS.belongsTo cannot be queried in the same way.This addon provides a way to query has-many and belongs-to relationships

So we can now proceed with the model for public/index route.

model() {
const eventDetails = this._super(...arguments);
return RSVP.hash({
  event   : eventDetails,
  tickets : eventDetails.query('tickets', {
    filter: [
      {
        and: [
          {
            name : 'sales-starts-at',
            op   : 'le',
            val  : moment().toISOString()
          },
          {
            name : 'sales-ends-at',
            op   : 'ge',
            val  : moment().toISOString()
          }
        ]
      }
    ]
  }),

We make use of this._super(…arguments) to use the event data fetched in the model of public route, eliminating the need for a separate API call for the same. Next, the ember-has-many-query add on allows us to query the tickets of the event, and we apply the filters restricting the tickets to only those, whose sale is live.
After the tickets are fetched they are passed onto the ticket list component to display them. We also need to take care of the cases, where there might be no tickets in case the event organiser is using an external ticket URL for ticketing, which can be easily handled via the is-ticketing-enabled property of events. And in case they are not enabled we don’t render the ticket-list component rather a button linked to the external ticket URL is rendered.  In case where ticketing is enabled the various properties which need to be computed such as the total price of tickets based on user input are handled by the ticket-list component itself.

{{#if model.event.isTicketingEnabled}}
  {{public/ticket-list tickets=model.tickets}}
{{else}}
<div class="ui grid">
  <div class="ui row">
      <a href="{{ticketUrl}}" class="ui right labeled blue icon button">
        <i class="ticket icon"></i>
        {{t 'Order tickets'}}
      </a>
  </div>
  <div class="ui row muted text">
      {{t 'You will be taken to '}} {{ticketUrl}} {{t ' to complete the purchase of tickets'}}
  </div>
</div>
{{/if}}

This is the most efficient way to fetch tickets, and also ensures that only the relevant data is passed to the concerned ticket-list component, without making any extra API calls, and it is made possible by the ember-data-has-many-query add on, with very minor changes required in the adapter and the event model. All that is required to do is make the adapter and the event model extend the RestAdapterMixin and ModelMixin provided by the add on, respectively.

Resources

Dynamic Segments in Open Event Frontend

In the Open Event Frontend project we have a page where we show all types of events. We have classified events into six classes like live, draft, past etc. Now each event type should have it’s own page describing it. What should we do now? Make six different routes corresponding to each class of event? Isn’t that too cumbersome. Is there any other method to do it?

Dynamic segment is the answer to the above question. Dynamic segment is that segment of the path for a route that will change based on content of  the page. Dynamic segments are frequently used in Open Event Frontend.

One such use is in /admin/events. Here we have button for different categories of events. We do not make separate routes for each of them, instead we use dynamic segments. We’ll have a single route and we will change the data in the route corresponding to the tab chosen.

Lets now add dynamic segments to /admin/events. First of all we’ll add the following code snippet in router.js.

this.route(‘events’, function() {
     this.route(‘list’, { path: ‘/:events_status’ });
     this.route(‘import’);
   });

Here : signifies the presence of dynamic segment and it is followed by an identifier. It’s the identifier by which the route matches the corresponding model to show.

Now as our route would show data depending upon the tab selected, we must change the title of page depending upon the same. For this we add the following code snippet in admin/events/list.js. Here we set the title of the page using titleToken() function and access the dynamic portion of url using params.events_status

export default Route.extend({
 titleToken() {
   switch (this.get(‘params.events_status’)) {
     case ‘live’:
       return this.l10n.t(‘Live’);
     case ‘draft’:
       return this.l10n.t(‘Draft’);
     case ‘past’:
       return this.l10n.t(‘Past’);
     case ‘deleted’:
       return this.l10n.t(‘Deleted’);
   }
 },
 model(params) {
   this.set(‘params’, params);
   return [{
   // Events data
   }];
 }
});

We’ll now link the tabs template/admin/events to the corresponding models using Link-to the helper.Here we have linked ‘Live’, ‘Draft’, ‘Past’, ‘Deleted’ buttons to dynamic segments. Let’s understand how it works.Let’s take example of Live button.Live button is linked to admin/events/list and this list is replaced by ‘live’. So our final route becomes admin/events/live.

<div class=“ui grid stackable”>
 <div class=“row”>
   <div class=“sixteen wide column”>
     {{#tabbed-navigation isNonPointing=true}}
       {{#link-to ‘admin.events.index’ class=’item’}}
         {{t ‘All Events’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘live’ class=’item’}}
         {{t ‘All Live’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘draft’ class=’item’}}
         {{t ‘All Draft’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘past’ class=’item’}}
         {{t ‘All Past’}}
       {{/link-to}}
       {{#link-to ‘admin.events.list’ ‘deleted’ class=’item’}}
         {{t ‘All Deleted’}}
       {{/link-to}}
       {{#link-to ‘admin.events.import’ class=’item’}}
         {{t ‘Import’}}
       {{/link-to}}
     {{/tabbed-navigation}}
   </div>
 </div>
 <div class=“row”>
   {{outlet}}
 </div>
</div>

Additional Resources

Creating Custom Validation Rules in Open Event Frontend

In the Open Event Frontend, users can create ‘Access codes’ for an event. Creation of access codes has a form located at the ‘tickets/access-codes/create’ which contains multiple form inputs such as ‘Number of Access tickets’, ‘status’, ‘Minimum tickets’, ‘Maximum tickets’, etc. To create an access code, a user has to fill the form. The form is validated at the time of submission. However, it should not happen that sometimes a user fills minimum number of tickets greater than maximum number of tickets. Semantic UI provides inbuilt validation for many inputs like the ‘text’, ‘email’, ‘password’, ‘number’ etc. But in some cases, it may happen that we need custom validation for some inputs before submitting the form.

         Number of tickets

             Min and max tickets

As shown in the above screenshot, the access code form has some inputs like ‘Min’, ‘Max’, ‘Number of access tickets’, etc. Here we need to ensure that the minimum number of tickets should not exceed the maximum number of tickets, also, the maximum number should not be greater than the total tickets, etc. To achieve this, we need to use the custom validation provided by Semantic UI.

To create a custom validation, we need to have a custom rule for it. To ensure the minimum value does not exceed the maximum value of the ticket, we first set up a rule for validation as follows:

min: {
identifier : 'min',
optional : true,
rules : [
{
type : 'number',
prompt : this.l10n.t('Please enter the proper number')
},
{
type : 'checkMaxMin',
prompt : this.l10n.t('Minimum value should not be greater than maximum')
}
]
},

In the above snippet, we introduced a new rule in the ‘min’ validation set of rules. The new rule called ‘checkMaxMin’ has to be defined so that it checks the necessary condition and returns the value desired.

$.fn.form.settings.rules.checkMaxMin = () => {
if (this.get('data.minQuantity') > this.get('data.maxQuantity')) {
return false;
}
return true;
};

 

Above code shows the rule which we defined in to check the required condition. It retrieves the data from the form by ‘this.get’ method and then checks whether the maximum tickets exceed the minimum. If they do, the function returns false, else it returns true. In this way, when a true value is returned, the form accepts the inputs else it rejects. The following screenshots illustrate the same:

In the same way, we can write custom validation for checking whether maximum tickets exceed the total number of tickets. Following is the code snippet and screenshot for the same.

max: {
identifier : 'max',
optional : true,
rules : [
{
type : 'number',
prompt : this.l10n.t('Please enter the proper number')
},
{
type : 'checkMaxMin',
prompt : this.l10n.t('Maximum value should not be less than minimum')
},
{
type : 'checkMaxTotal',
prompt : this.l10n.t('Maximum value should not be greater than number of tickets')
}
]
},

 

$.fn.form.settings.rules.checkMaxTotal = () => {
if (this.get('data.maxQuantity') > this.get('ticketsNumber')) {
return false;
}
return true;
};

Thus, in this way, we can use custom validation for some form inputs when we want to customise the inputs based on our requirements.

Resources:

Create Discount Code Component in Open-Event-Frontend

We in Open-Event-Frontend have given the event organiser the feature to create discount coupons for his or her event. Here the organiser can either enter the discount amount or discount percentage and can set even set the total number of coupons he wants to make available for his customers. We have also automatically generated an unique link for each discount coupon.

We’ll be creating a separate component create-discount-code for creating discount codes.To create the component we’ll run the following command

ember g component forms/events/view/create-discount-code

This will create

1.Create-discount-code.hbs

Here we have designed our form.We have nested all the fields used, inside semantic’s ui form class.Some of the helpers used in the form are

We have used the ember input helper in following way for all the input fields.The

attribute name,value corresponds to the id and value attached with the helper

{{input type=‘text’ name=‘discount_code’ value=data.code}}

Ember radio buttons are used by the organizer to select between discount

{{ui-radio label=(t ‘Amount (US$)’)
          name=‘discount_type’  
          value=‘amount’
          current=‘amount’
          onChange=(action (mut selectedMode))}}

 

We have given the organizer an additional option to set the validity of the discount code. For this we have used date-picker and time-picker component already present in Open-Event-Frontend in the following manner.

<div class=“fields”>
       <div class=“wide field {{if device.isMobile ‘sixteen’ ‘five’}}”>
         <label>{{t ‘Valid from’}}</label>
         {{widgets/forms/date-picker id=’start_date’ value=data.validFromDate rangePosition=’start’}}
         <div class=“ui hidden divider”></div>
         {{widgets/forms/time-picker id=’start_time’ value=data.validFromTime rangePosition=’start’}}
       </div>
       <div class=“wide field {{if device.isMobile ‘sixteen’ ‘five’}}”>
         <label>{{t ‘Expires on’}}</label>
         {{widgets/forms/date-picker id=‘end_date’ value=data.validTillDate rangePosition=‘end’}}
         <div class=“ui hidden divider”></div>
         {{widgets/forms/time-picker id=‘end_time’ value=data.validTillTime rangePosition=‘end’}}
       </div>
     </div>

The above snippet will the following output

2.Create-discount-code.js

Here we validate the form and provide it with an unique discount code url. We have generated the url using the event id and the discount code.

discountLink: computed(‘data.code’, function() {
 const params = this.get(‘routing.router.router.state.params’);
 return location.origin + this.get(‘routing.router’)
                         .generate(‘public’, params[‘events.view’]                          .event_id,
        { queryParams: { discount_code: this.get(‘data.code’) } });
}),
actions: {
 submit() {
   this.onValid(() => {
   });
 }
}

3.Create-discount-code-test.js

This is where we check whether our component is compatible with other components of the system or not. Here, for now, we are just making sure if our component renders or not, by checking the presence of ‘Save’.

import { test } from ’ember-qunit’;
import moduleForComponent from ‘open-event-frontend/tests/helpers/component-helper’;
import hbs from ‘htmlbars-inline-precompile’;

moduleForComponent(‘forms/events/view/create-discount-code’, ‘Integration | Component | forms/events/view/create discount code’);

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

Now, our component is ready, and the only part remaining is to place it in our application. We place it in app/templates/events/view/tickets/discount-codes/create.hbs in the given form.

{{forms/events/view/create-discount-code data=model}}

Here we have passed model from create-discount-code.js to data used in Create-discount-code.hbs

Now our create discount code page is up and running

Additional Resources

Adding Messaging Route in Ember.js for Admin UI of Open Event Frontend

In this blog post I am explaining how we implement a messages page for admins to keep track of all types of system messages sent to users in the Open Event Frontend. The page shows the types of messages sent out to various users at one place and as well as additional details. It offers configuration options to control which messages get sent out  as emails or notifications or both. And, the page shows when and what message should be sent via notification or mail.
To create the messages page we’ll run the following command

ember generate route admin/messages

This will create

This command will also add  this.route(‘messages’);  to router.js. As admin is the parent route for messages, messages will be nested inside admin in router.js

this.route(‘admin’, function(){
  this.route(‘messages’);
});

Let’s now understand the content of each of above files.

  1. Messages.js

In admin/messages.js we have used titletoken helper to set the title of the tab. Here we have created the message model and added attribute like recipient, trigger, emailMessage, notificationMessage, options and sentAt. We have returned this model from the js file to template.

import Ember from ’ember’;
const { Route } = Ember;
export default Route.extend({
 titleToken() {
   return this.l10n.t(‘Messages’);
 },
 model() {
   return [{
     recipient: [
       {
         name: ‘Organizer’
       },
       {
         name: ‘Speaker’
       }
     ],
     trigger      : ‘Title1’,
     emailMessage : {
       subject : ‘Email subject1’,
       message : ‘Hi, the schedule for session1 has been changed’
     },
     notificationMessage: {
       subject : ‘Notification subject1’,
       message : ‘Hi, the schedule for session1 has been changed’
     },
     option: {
       mail         : true,
       notification : false,
       userControl  : true
     },
     sentAt: new Date()
   }];
 }
});

 

  1. Messages.hbs

In template we have created a table and added classes like stackable and compact. Stackable class makes the table responsive and stacks all the rows for devices with smaller screen size. Compact class helps to show more number of rows at a time.

Then in the template we iterate through the model using a loop. Here we have used other semantic-ui elements like ui ordered list , ui header, ui-checkbox inside the table. For options column we have three attributes denoting how the admin wants to send the message to the user. Here we have grouped three fields using the class grouped fields .In each field we have used semantic’s  ui-checkbox .In check-box we are mutating values on click by using mut helper.

<div class=“grouped fields”>
 <div class=“field”>
   {{ui-checkbox checked=message.option.mail
                 label=(t ‘Mail’)      
                 onChange=(action (mut message.option.mail))}}
 </div>
 <div class=“field”>
   {{ui-checkbox checked=message.option.notification
                 label=(t ‘Notification’)  
               onChange=(action (mut message.option.notification))}}
 </div>

 <div class=“field”>
   {{ui-checkbox checked=message.option.userControl
                label=(t ‘User Control’)  
               onChange=(action (mut message.option.userControl))}}
 </div>
</div>

We are getting date object from js and to convert it into human readable format we have used moment like {{moment-format message.sentAt ‘dddd, DD MMMM YYYY’}}

  1. Messages-test.js
import { test } from ’ember-qunit’;
import moduleFor from ‘open-event-frontend/tests/helpers/unit-helper’;

moduleFor(‘route:admin/messages’, ‘Unit | Route | admin/messages’, []);

test(‘it exists’, function(assert) {
 let route = this.subject();
 assert.ok(route);
});

Using this we can test the existence of our route. These tests are run using the command ember t.

Our message page is ready now. The admin can have a check at all the messages sent to users.

Additional Resources

How Device Service Makes it Easy to Implement Responsive UI in Open Event Frontend

This blog article will illustrate how the device service which has been used frequently in Open Event Frontend, works to make the UI responsive and render it selectively based on device side. To quote the official documentation,

An Ember.Service is a long-lived Ember object that can be made available in different parts of your application.

The device service is precisely that. It is available universally across the app  and its chief purpose is to provide an object through out the app, which allows us to actively determine the device size at the instant of rendering.  This allows us to have a very easy implementation of a highly responsive UI and also makes it redundant to use  css media queries to achieve similar results. The services allow us to maintain a persistent connection and has to be injected. Like all other ember entities, the boiler plate code of a service may be generated via simply using Ember CLI.

$ ember generate service device

To begin with we define the various breakpoints (in terms of width of the screen) that we want in our app. We will be keeping this outside the service object to keep it lean and faster to loop over. We need to ensure that these break points are exactly the same ones used for semantic UI. This is because we want to be aware of what device the current width represents according to semantic UI because various components of semantic UI behave according to these device sizes. For instance various fields of a form may be stackable only for mobiles, and not for tablets.

const breakpoints = {
mobile: {
  max : 767,
  min : 0
},
tablet: {
  max : 991,
  min : 768
},
computer: {
  max : 1199,
  min : 992
},
largeMonitor: {
  max : 1919,
  min : 1200
},
widescreen: {
  min: 1920
}

Our goal is to iterate over these breakpoints and compare the window width with them, and then assign the device type to the required variable. So we begin with the iterating loop, Also we will always need to keep track of the current screen width, hence we define currentWidth property, whereas the deviceType property will keep track of the current device using currentWidth and the breakpoints. These all will be defined inside the service object. The logic for deviceType property is basically to iterate over all the breakpoints and then it checks if the current width of the document lies within the range of a particular breakpoint.

export default Service.extend({

currentWidth: document.body.clientWidth,

deviceType: computed('currentWidth', function() {
  let deviceType = 'computer';
  const currentWidth = this.get('currentWidth');
  forOwn(breakpoints, (value, key) => {
    if (currentWidth >= value.min && (!value.hasOwnProperty('max') || currentWidth <= value.max)) {
      deviceType = key;
    }
  });
  return deviceType;
}),
})

 

Now it is possible for us to use the deviceType property to calculate other useful properties for device type. For instance, we can add the following to the service object.The very point of using this service is the fact that, though semantic UI supports these breakpoints for devices of various sizes but it doesn’t allow us to use them as boolean properties on the basis of which we can decide, which content to render and which not. Also, since the breakpoints are exclusive, there is no possible overlapping of the properties by them being true simultaneously.Using equal operator is a safe way to compare a computed property.

isMobile       : equal('deviceType', 'mobile'),
isComputer     : equal('deviceType', 'computer'),
isTablet       : equal('deviceType', 'tablet'),
isLargeMonitor : equal('deviceType', 'largeMonitor'),
isWideScreen   : equal('deviceType', 'widescreen'),

One very important thing we should realise is, that even the the deviceType is observing currentWidth, however document.body.clientWidth is not binded, and thus currentWidth needs to be calculated, every time the window is resized, so we add an init() for the service. It will make sure that whenever the window is resized, the currentWidth object will be initialised.

init() {
this._super(...arguments);
$(window).resize(() => {
  debounce(this, () => {
    this.set('currentWidth', document.body.clientWidth);
  }, 200);
});}

This completes the service, now we can see from the following example how will this service be used. In this example we try to make the menu responsive, by using an icon only menu for mobile devices where as a full menu for larger ones. The properties of the service may simply be used via device.<property>.

{{#if device.isMobile}}
 <.div class= ui grouped  icon buttons>
   <.div class=”ui button><.i class=”checkmark icon><./div>
   <.div class=”ui button><.i class=”cancel icon><./div>
 <./div>
{{else}}
 <.div class= ui grouped buttons>
   <.div class=”ui button>Apply<./div>
   <.div class=”ui button>Cancel<./div>
 <./div>
{{/if}}

Resources

**image is licensed under free to use CC0 Public Domain

Creating a notification dropdown in semantic UI for Open Event Frontend

Semantic UI comes packaged with highly responsive components to cater to all front end needs. The area of front-end development is so large, it is never possible to cover all the possible requirements of a developer with pre built components. Currently there is no means to display notifications on the navbar in Open Event Front-end project. In this article we are going to build a notification dropdown from scratch which will be used there to display notifications. So we begin by generating a new component via ember CLI

$ ember generate component notification-dropdown

This should generate the boiler-plate code for our component, with the template file located at: templates/components/notification-dropdown.hbs and the JS file located at components/notification-dropdown.js  It is assumed that you already have a basic ember app with at least a navbar set up. The notification drop down will be integrated with the navbar as a separate component. This allows us great flexibility in terms of location of the navbar, and also helps us  in not cluttering the code in one file.

We will use the popup component of semantic ui as the underlying structure of our dropdown. I have used some dummy data stored in a separate file, you can use any dummy data you wish, either  by directly hardcoding it or importing it from a js file stored somewhere else. It’s preferred if the mock data is called from a js file, because it helps in simulating the server response in a much more genuine way.

We will make use of the floating label of semantic UI to display the number of unread notifications. A mail outline icon should make for a good choice to use the primary icon to denote the notifications. Also, the floating label will require additional styling to make it overlap with the icon perfectly.

For the header in the dropdown we can give a ‘mark all as read’ button aligned to the right and the ‘notification’ header to the left. Also for best user experience even on small devices, we will make each notification item clickable as a whole instead of individual clickable elements in it. A selection link list of semantic UI should be perfect to display individual notifications as it gives a hovering effect and also, allows us to display a header. Moving onto individual notification items, it will have 3 sub parts

  • A header
  • Description
  • Human friendly notification time

For the header we will use the ‘header’ class predefined in semantic UI for list items.We will use ‘content’ class for description which is again a predefined semantic UI class, And finally the time can be displayed via moment-from-now helper of ember to display the time in a human friendly format.

<.i class="mail outline icon">
<./i>
<.div class="floating ui teal circular mini label">{{notifications.length}}<./div>
<.div class="ui wide notification popup bottom left transition ">
 <.div class="ui basic inverted horizontal segments">
   <.div class="ui basic left aligned segment weight-800">
     <.p>{{t 'Notifications'}}<./p>
   <./div>
   <.div class="ui basic right aligned segment weight-400">
     <.a href="#">{{t 'Mark all as Read'}}<./a>
   <./div>
 <./div>
 <.div class="ui fluid link celled selection list">
   {{#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}}
 <./div>
<./div>

 

Now the next challenge is to make the popup scrollable, they are not scrollable by default and may result in an error if their height exceeds that of the view port. So we apply some styling now. While applying such custom styles we have to be really careful so as to not to apply the styling in general to all of semantic UI’s components. It is very easy to overlook,  and may cause some unwanted changes. It is best to wrap it in a container class, in this case we have chosen to go ahead with notification as the class name. Also, since the notification dropdown should work consistently across all mobile devices, we need to set its maximum height not in terms of pixels but in terms of viewport height. The following styling code takes care of that as well as the icon which we are using to display the notification count.

.notification.item {
 margin: 0 !important;
 .label {
   top: 1em;
   padding: 0.2em;
   margin: 0 0 0 -3.2em !important;

 }
}

.ui.notification.popup {
 padding: 2px;
 .list {
   width: auto;
   max-height: 50vh;
   overflow: hidden;
   overflow-y: auto;
   padding: 0;
   margin: 0;
   .header {
     margin-bottom:5px;
   }
   .content {
     margin-bottom:2px;
   }
   }
 }

 

All of this takes care of the styling. Next, we need to take care of initialising the notification popup. For this we need to go to the navbar component as it is the one who calls the notification dropdown component. And add this to it:

didInsertElement() {
   this._super.call(this);
   this.$('.notification.item').popup({
     popup : '.popup',
     on    : 'click'
   });
 },

 willDestroyElement() {
   this._super.call(this);
   this.$('.notification.item').popup('destroy');
 }

 

The didInsertElement() makes sure that notification pop up is not rendered or initialised before the navbar is. On the other hand, willDestoroyElement() makes sure to clean up and destroy the pop up initialisation. Attached below are some screenshots of what the notification dropdown should look like.

On a wide screen
On mobile screens

Resources

Efficient use of event card component on Open Event Frontend

Ember JS is a powerful framework when it comes to code reusability. Components are at it’s core and enable the developers to reuse the same code at different places. The event-card component on Open event-front-end is one such component. It is used on various routes across the app thereby illustrating the usefulness. However, in this article we are going to see how components can be made even more efficient by rendering them differently for different situations.Originally the component was used to display events in the card format on the public page.

And this was the code :

<.div class="ui fluid event card">
 <.a class="image" href="{{href-to 'public' event.identifier}}">
   {{widgets/safe-image src=(if event.large event.large event.placeholderUrl)}}
 <./a>
 <.div class="content">
   <.a class="header" href="{{href-to 'public' event.identifier}}">
     <.span>{{event.name}}<./span>
   <./a>
   <.div class="meta">
     <.span class="date">
       {{moment-format event.startTime 'ddd, MMM DD HH:mm A'}}
     <./span>
   <./div>
   <.div class="description">
     {{event.shortLocationName}}
   <./div>
 <./div>
 <.div class="extra content small text">
   <.span class="right floated">
     <.i role="button" class="share alternate link icon" {{action shareEvent event}}><./i>
   <./span>
   <.span>
     {{#each tags as |tag|}}
       <.a>{{tag}}<./a>
     {{/each}}
   <./span>
 <./div>
<./div>

Next we modify it in such a way that it is suitable to be displayed on the explore route as well, and that requires it to be such that is instead of being box-like it should be possible to render it such that it is wide and takes the width of the container it is in. So How do we determine which version should be rendered when. In ember it is really easy to pass on parameters to components while calling them, and they can make use of these paraemeters as they are being rendered. It is best if the name of the parameters is chosen logically, here we want to make it wide for selected routes so we name it : isWide

And the code after modification, would be something like this with isWide taken into account. We will just wrap it an extra div which will conditionally add an extra class ‘wide’ if isWide is true.

<.div class="{{if isWide 'event wide ui grid row'}}">
<.!-- Previous code -->
<./div>

//And the corresponding styling for wide class


.event.wide {
  border-radius: 0 !important;
  margin: 0 !important;

  .column {
    margin: 0;
    padding: 0 !important;
  }

  img {
    height: 170px !important;
    object-fit: cover;
  }

.main.content {

  height: 130px;

  display: block;

}

}

Next What we are going to do is, modify the component to become yieldable. So that they can also be used to display the tickets of a user! `{{yield}}` allows code outside the component to be rendered inside it.

Let’s make a change so that, if the event card component is rendered on the my tickets page, then instead of hashtags it should display the ticket details. Which we will conveniently provide to the component externally (via {{yield}} ) Next we need to determine which version of the component should be rendered when? The hasBlock helper enables us to do just that. So the final code should look something just like this 😉 The hasBlock helper allows us to differentiate between the yieldable and non yieldable forms of the component.

<.div class="{{if isWide 'event wide ui grid row'}}"
 {{#if isWide}}
   {{#unless device.isMobile}}
     <.div class="ui card three wide computer six wide tablet column">
       <.a class="image" href="{{href-to 'public' event.identifier}}">
         {{widgets/safe-image src=(if event.large event.large event.placeholderUrl)}}
       <./a>
     <./div>
   {{/unless}}
 {{/if}}
 <.div class="ui card {{unless isWide 'event fluid' 'thirteen wide computer ten wide tablet sixteen wide mobile column'}}">
   {{#unless isWide}}
     <.a class="image" href="{{href-to 'public' event.identifier}}">
       {{widgets/safe-image src=(if event.large event.large event.placeholderUrl)}}
     <./a>
   {{/unless}}
   <.div class="main content">
     <.a class="header" href="{{href-to 'public' event.identifier}}">
       <.span>{{event.name}}<./span>
     <./a>
     <.div class="meta">
       <.span class="date">
         {{moment-format event.startTime 'ddd, MMM DD HH:mm A'}}
       <./span>
     <./div>
     <.div class="description">
       {{event.shortLocationName}}
     <./div>
   <./div>
   <.div class="extra content small text">
     <.span class="right floated">
       <.i role="button" class="share alternate link icon" {{action shareEvent event}}><./i>
     <./span>
     <.span>
       {{#if hasBlock}}
         {{yield}}
       {{else}}
         {{#each tags as |tag|}}
           <.a>{{tag}}<./a>
         {{/each}}
       {{/if}}
     <./span>
   <./div>
 <./div>
<./div> 

And now the component can be used for displaying the tickets, with the area displaying hashtags now being replaced by the order details.