Maintain Aspect Ratio Mixin on Open Event Frontend

The welcome page of the Open-Event-Frontend is designed to contain cards that represent an event. A user is directed to the event-details page by clicking on the corresponding card. The page consists of an image that serves as the banner for the event and an overlapping div to provide some contrast against the image. A comment may also be added onto the image and along with the overlapping div it is wrapped in a container div.

Since we have given a specific height to the contrasting div, the background image shrinks according to the screen size but the contrasting div does not whenever we go from a large screen to a smaller screen.

Mobile view (before):-

We want our contrasting div also to resize in accordance to the image. To do it, we first define a sass mixin to maintain a common aspect ratio for image and overlapping div. Let us see it’s code.

 @mixin aspect-ratio($width, $height) {
    position: relative;
    &:before {
      display: block;
      content: "";
      width: 100%;
      padding-top: ($height / $width) * 100%;
    }
    > .content {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
    }
    > img {
      position: absolute;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
    }
  }

So what does this mixin actually doing is we are passing the height and width of the image and we are defining a pseudo element for our image and give it a margin top of (height/width)*100 since this value is related to image width (height: 0; padding-bottom: 100%; would also work, but then we have to adjust the padding-bottom value every time we change the width). Now we just position the content element and image as absolute with all four orientations set to 0. This just covers the parent element completely, no matter which size it has.

Now we can simply use our mixin by adding the following line of code in out container div

@include aspect-ratio(2, 1);

Here we want to maintain a 2:1 aspect ratio and the user is also expected to upload the image in the same aspect ratio. Therefore, we pass width as 2 and height as 1 to our mixin.

Now when we resize our screen both the image and the overlapping div resize maintaining 2:1 aspect ratio.

Mobile view (after):-

Resources

  • mademyday blog describes a method of using pseudo elements to maintain an element’s aspect ratio.
  • css-tricks snippet.
Continue ReadingMaintain Aspect Ratio Mixin on Open Event Frontend

Image Cropper On Ember JS Open Event Frontend

In Open Event Front-end, we have a profile page for every user who is signed in where they can edit their personal details and profile picture. To provide better control over profile editing to the user, we need an image cropper which allows the user to crop the image before uploading it as their profile picture. For this purpose, we are using a plugin called Croppie. Let us see how we configure Croppie in the Ember JS front-end to serve our purpose.

All the configuration related to Croppie lies in a model called cropp-model.js.

 onVisible() {
    this.$('.content').css('height', '300px');
    this.$('img').croppie({
      customClass : 'croppie',
      viewport    : {
        width  : 400,
        height : 200,
        type   : 'square'
      },
      boundary: {
        width  : 600,
        height : 300
      }
    });
  },

  onHide() {
    this.$('img').croppie('destroy');
    const $img = this.$('img');
    if ($img.parent().is('div.croppie')) {
      $img.unwrap();
    }
  },

  actions: {
    resetImage() {
      this.onHide();
      this.onVisible();
    },
    cropImage() {
      this.$('img').croppie('result', 'base64', 'original', 'jpeg').then(result => {
        if (this.get('onImageCrop')) {
          this.onImageCrop(result);
        }
      });
    }

There are two functions: onVisible() and onHide(), which are called every time when we hit reset button in our image cropper model.

  • When a user pushes reset button, the onHide() function fires first which basically destroys a croppie instance and removes it from the DOM.
  • onVisible(), which fires next, sets the height of the content div. This content div contains our viewport and zoom control. We also add a customClass of croppie to the container in case we are required to add some custom styling. Next, we set the dimensions and the type of viewport which should be equal to the dimensions of the cropped image. We define type of cropper as ‘square’ (available choices are ‘square’ and ‘circle’). We set the dimensions of our boundary. The interesting thing to notice here is that we are setting only the height of the boundary because if we pass only the height of the boundary, the width will be will be calculated using the viewport aspect ratio. So it will fit in all the screen sizes without overflowing.

The above two functions are invoked when we hit the reset button. When the user is satisfied with the image and hits ‘looks good’ button, cropImage() function is called where we are get the resulting image by passing some custom options provided by croppie like base64 bit encoding and size of cropped image which we are set to ‘original’ here and the extension of image which is we set here as ‘.jpeg’. This function returns the image of desired format which we use to set profile image.

Resources

Continue ReadingImage Cropper On Ember JS Open Event Frontend

Using Semantic UI Modals in Open Event Frontend

Modals in semantic UI are used to display the content on the current view while temporarily blocking the interaction with the main view. In Open Event Frontend application, we’ve a ‘delete’ button which deletes the published event as soon as the button is pressed making all the data clean. Once the event is deleted by the user, it is not possible to restore it again. We encountered that this can create a whole lot of problem if the button is pressed unintentionally. So we thought of solving this by popping up a dialog box on button click with a warning and asking for user’s confirmation to delete the event. To implement the dialog box, we used semantic UI modals which helped in ensuring that the user correctly enters the name of the event in the given space to ensure that s/he wants to delete the event.

Since we want our modal to be customizable yet reusable so that it can be used anywhere in the project so we made it as the component called ‘event-delete-modal’. To do that we first need to start with the template.

The markup for the event-delete-modal looks like this:

<div class="content">
  <form class="ui form" autocomplete="off" {{action (optional formSubmit) on='submit' preventDefault=true}}>
    <div class="field">
      <div class="label">
        {{t 'Please enter the full name of the event to continue'}}
      </div>
      {{input type='text' name='confirm_name' value=confirmName required=true}}
    </div>
  </form>
</div>
<div class="actions">
  <button type="button" class="ui black button" {{action 'close'}}>
    {{t 'Cancel'}}
  </button>
  <button type="submit" class="ui red button" disabled={{isNameDifferent}} {{action deleteEvent}}>
    {{t 'Delete Event'}}
  </button>
</div>

The complete code for the template can be seen here.

The above code for the modal window is very similar to the codes which we write for creating the main window. We can see that the semantic UI collection “form” has also been used here for creating the form where the user can input the name of the event along with delete and cancel buttons. The delete button remains disabled until the time correct name of the event been written by the user to ensure that user really wants to delete the event. To cancel the modal we have used close callback method of semantic UI modal which itself closes it. Since the ‘isNameDifferent’ action is uniquely associated to this particular modal hence it’s been declared in the ‘event-delete-modal.js’ file.

The code for the  ‘isNameDifferent’ in ‘event-delete-modal.js’ file looks like this.

export default ModalBase.extend({
  isSmall         : true,
  confirmName     : '',
  isNameDifferent : computed('confirmName', function() {
    return this.get('confirmName').toLowerCase() !== this.get('eventName').toLowerCase();
  })
});

The complete code for the.js file can be seen here.

In the above piece of code, we have isSmall variable to ensure that our modal size is small so it can fit for all screen sizes and we have the implementation isNameDifferent() function. We can also notice that our modal is extending the ‘ModelBase’ class which has all the semantic UI modal settings and the callback methods to perform show, hide, close and open actions along with settings which will help modal to look the way we want it to be.  

The modal-base.js class looks like this.

  openObserver: observer('isOpen', function() {
   close() {
     this.set('isOpen', false);
   },
   actions: {
    close() {
      this.close();
    }
  },
  willInitSemantic(settings) {
    const defaultOptions = {
      detachable     : false,
      duration       : testing ? 0 : 200,
      dimmerSettings : {
        dimmerName : `${this.get('elementId')}-modal-dimmer`,
        variation  : 'inverted'
      },
      onHide: () => {
        this.set('isOpen', false);
        if (this.get('onHide')) {
          this.onHide();
        }
      },
      onVisible: () => {
        this.set('isOpen', true);
        this.$('[data-content]').popup({
          inline: true
        });
     }

The complete code for the .js file can be seen here.

In the above code, we can see that at the top we’ve an ‘openObserver’ function where we’re observing the behaviour of the modal and setting the variables according to the behavioural changes. Now, later we’re checking the status of those variables and performing the actions based on their value. For example, to close the modal we have a boolean variable ‘isOpen’ which is set to false now close() action will be called which closes the modal.  Similarly, in ‘willInitSemantic(settings)’ function we’re setting the modal’s setting like the effects, length, the details modal will display on popping out etc.
We’re here overriding the semantic UI moda settings like detachable, dimmerSettings, duration etc along with the callback methods like onHide(), onVisible() etc. to get the required results.

Finally, our event-delete-modal will look something like this.

Fig: Modal to delete the event

So to conclude this post, we can easily say that the modals are of great use. They can solve the purpose of displaying some alert or to input some values without interrupting the flow of the main page depending upon the requirements.

Additional Resources:

Blog post about Awesome Ember.js Form Components of Alex Speller: http://alexspeller.com/simple-forms-with-ember/

Continue ReadingUsing Semantic UI Modals in Open Event Frontend

Declaring Ember Data Model Defaults in Open Event Frontend

This article will illustrate the ways to declare the model defaults for JavaScript data types ‘boolean’, ‘string’, ‘number’, ‘object’ and ‘array’ as used in Open Event frontend project.

While working with ember-data models which define the properties and behaviour of data, in Open Event frontend project, we needed to preset the values for certain attributes if they are not supplied along with data. To preset the values in ember-data we need to specify the default values to the attribute. The process of setting the default values is very much similar to the way we handle it in other databases like MySQL and we might have certainly done it many times there. In ember, we set the values like this.

export default Model.extend({
 type : attr('string', { defaultValue: 'text' }),
 isRequired : attr('boolean', { defaultValue: false })
});


This way we could set values for attributes having data types, ‘boolean’, ‘string’ and ‘number’. But when it came to data types, ‘object’ and ‘array’ we were unable to set the default values in similar way reason being ember data does not provide support for ‘array’ and ‘object’ data types. Also, we tried to play around by not providing the first argument to the DS.attr() method, then Ember does not remain the value for that particular attribute empty or unhandled rather it forced to matching JavaScript type. The other and the obvious way which clicked to us was this.The attributes are properties defined to convert the JSON data coming from our server into a record, and serializing a record to save back to the server after it has been modified. In above code we defined that ‘type’  has a default value of ‘text’ and ‘isRequired’ has default value ‘false’ which will be present at the time of model’s creation.

export default Model.extend({
 startTime : attr('object', { defaultValue: {} })
});


But soon after executing the code, we got a warning which can be seen below.The above code implies that whenever we create a new
event model then it would be expected to have an attribute startTime with value {}.

Non primitive defaultValues are deprecated because they are shared between all instances. If you would like to use a complex object as a default value please provide a function that returns the complex object.

The warning explains that ember-data model extends from Ember.Object, which means that arrays and objects will be shared among all instances of that model hence the use of non-primitive (apart from boolean, number & string) default values are deprecated.

Now again we thought of something that can handle ‘array’ and ‘object’ data types. We thought of adding a function which would convert them to the custom type. To add that we needed to provide proper serialize and deserialize methods for processing the data properly which was a bit tedious process and increase the overall complexity of code both in terms of execution as well as understanding.

After reading the available resources, we got to know that the defaultValue accepts the function as well. So what we finally did was passed a function which will add the months and days to the date object based on the current time which can be calculated using moment.

export default Model.extend({
 startTime : attr('date', { defaultValue: () => moment().add(1, 'months').startOf('day').toDate() }) 
});


The ability to pass afunction to defaultValue proved very helpful. It suits best if we want to set custom defaults.That’s it! Passing the function will ensure that every
startTime attribute contains it’s own date instance.

Additional Resources:

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

Continue ReadingDeclaring Ember Data Model Defaults in Open Event Frontend

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

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

Creating a component for achieving an n-times running loop in Open Event Frontend

This blog article will illustrate how to to make a component in ember JS which allows you to iterate over a block of statements n-times which is being used in Open Event Frontend This is of great utility as the default ‘each’ helper of ember only allows us to iterate over objects and arrays, and not simply as a loop.So we begin by generating a component and calling it `n-times` and it is currently being used in the open event front-end project.

Even before implementing it, it is obvious to us that the content inside the loop can be any thing and the component should be able to iterate over them repeatedly. Thus this component will need to have a {{yield}} block inside it, and thus will always have to be used in block form, to allow the user to enter the content that we need to iterate over.
We begin by simply generating the boiler plate code of the component via Ember CLI.

$ ember generate component n-times

Now we proceed with the js code of the component. The only thing we need to ensure here is that no extra html tags are introduced because of our component, else it might break the flow of tags or disrupt the styling. Hence we make use of the tagName property to achieve the same.
The final code looks something like this:

import Ember from 'ember';

const { Component } = Ember;

export default Component.extend({
 tagName: ''
});

We just needed to make the tagName none as we don’t want an extra div. And this is the template code for the same component
For the template part we simply enclose the {{yield}} block inside each loop, and the value of times is expected to be passed at the time of calling the component.

{{#each (range 0 times) as |number|}}
{{yield number}}
{{/each}}

An important thing to note, we cannot have made this as a helper because the block form of helpers has been deprecated since Ember has updated hence component was the obvious choice.

This may now be simply used as

{{#n-times times=5}}

{{ui-checkbox label=(t 'Create')}}

{{ui-checkbox label=(t 'Read')}}

{{ui-checkbox label=(t 'Update')}}

{{ui-checkbox label=(t 'Delete')}}

{{/n-times}}

 

Resources

*Featured image is captured by the author of this post, it is under public domain without any restrictions.

Continue ReadingCreating a component for achieving an n-times running loop in Open Event Frontend

Using ember semantic UI radio buttons to render form elements selectively on Open Event Front End

This blog article will illustrate how ember semantic ui radio buttons have been used to render form elements selectively on Open Event Front End and in the process will learn, how to make use of the powerful binding features offered by ember semantic ui for radio buttons via the mut action.

So what do we have to begin with ?

The sample form which we want to create

A form which allows us to chose one of the modes of Paypal payments and displays corresponding fields for it.

What we want is that the radio button should allow us to make a choice and then display the corresponding  fields. Now that seems a trivial process, but there is some thought process which goes into this, to end up with the most efficient choice. So first just make the basic form where in all the fields are visible.

Now let’s learn how to make use of the mut action on the radio buttons. What it allows us to do is pass a parameter while calling it, and that parameter name is shared by all the radio buttons belonging to a particular group of radio buttons. And what that action does is, store the name of the currently selected radio button in it. So we can easily keep track of which button has been selected and use that variable in selective rendering of templates. The action is triggered whenever the radio button’s property changes and the trigger is aptly called onChange. So essentially the syntax boils down to this :

<!-- The first radio button -->
{{ui-radio label=(t 'Sandbox mode - Used during development and testing')
           name='paypal_integration_mode'
           value='sandbox'
           onChange=(action (mut selectedMode))}}
<!-- The second radio button -->
{{ui-radio label=(t 'Live mode - Used during production') 
           name='paypal_integration_mode'
           value='live'
           onChange=(action (mut selectedMode))}}

Now whichever button is selected it’s name will be stored in selectedMode in this case. And hence we can use the conditional helpers of handle bars to render elements based on the selected radio button.

The final code looks something like this:

  <.h3 class="ui header">{{t 'PayPal Credentials'}}<./h3>
    <.div class="sub header">
       {{t 'See here on how to obtain these keys.'}}
    <./div>
   <.h5 class="ui header">{{t 'PayPal Integration Mode'}}<./h5>
   <.div class="field">
     {{ui-radio label=(t 'Sandbox mode')
                name='paypal_integration_mode' 
                value='sandbox' 
                current='sandbox'
                onChange=(action (mut selectedMode))}}
   <./div>
   {{#unless (eq selectedMode 'live')}}
     <.div class="field">
       <.label>{{t 'Sandbox username'}}<./label>
       {{input type='text' name='sandbox_username'}}
     <./div>
     <.div class="field">
       <.label>{{t 'Sandbox password'}}<./label>
       {{input type='password' name='sandbox_password'}}
     <./div>
     <.div class="field">
       <.label>{t 'Sandbox signature'}}<./label>
       {{input type='text' name='sandbox_signature'}}
     <./div>
   {{/unless}}
   <.div class="field">
     {{ui-radio label=(t 'Live mode')
                name='paypal_integration_mode'
                value='live' 
                onChange=(action (mut selectedMode))}}
   <./div>
   {{#if (eq selectedMode 'live')}}
     <.div class="field">
      <.label>{{t 'Live username'}}<./label>
       {{input type='text' name='live_username'}}
     <./div>
     <.div class="field">
       <.label>{{t 'Live password'}}<./label>
       {{input type='password' name='live_password'}}
     <./div>
     <.div class="field">
       <.label>{{t 'Live signature'}}<./label>
       {{input type='text' name='live_signature'}}
     <./div>
   {{/if}}
   <.button class="ui teal button" type="submit">
   {{t 'Save'}}
   <./button>

 

Important tip

The action is triggered by the onChange action, hence the variable doesn’t have the value when the template is rendered for the very first time and hence at that instant, none of the fields will be rendered, to avoid that we have used both if and unless condition helpers instead of identical conditional helpers to cleverly avoid this situation. You can read about the ember radio buttons further more through the official documentation

Resources

Continue ReadingUsing ember semantic UI radio buttons to render form elements selectively on Open Event Front End

Making currency name and currency symbol helpers for Open Event Frontend

This blog article will illustrate how to make two helpers which will help us in getting the currency name and symbol from a dictionary, conveniently.The helpers will be used as  a part of currency form on Open Event Front End It also exemplifies the power of ember JS and why is it being used in this project via a counter example in which we try to do things the non ember way and get the required data without using those helpers.

So what do we have to begin with ?

The sample data which will be fetched from the API:

[
     {
       currency   : 'PLN',
       serviceFee : 10.5,
       maximumFee : 100.0
     },
     {
       currency   : 'NZD',
       serviceFee : 20.0,
       maximumFee : 500.0
     }
     //The list continues
]

The dictionary data format:

[
  {
    paypal : true,
    code   : 'PLN',
    symbol : 'zł',
    name   : 'Polish zloty',
    stripe : true
  },
  {
    paypal : true,
    code   : 'NZD',
    symbol : 'NZ$',
    name   : 'New Zealand dollar',
    stripe : true
  },
  {
    paypal : false,
    code   : 'INR',
    symbol : '₹',
    name   : 'Indian rupee',
    stripe : true
  }
]
// The list continues

And our primary goal is to fetch the corresponding name and symbol from the dictionary for a given currency code, easily and efficiently.

One might be tempted to get things done the easy way : via

{{get (find-by 'code' modal.name currencies) 'name'}}

and perhaps,

{{get(find-by 'code' modal.name currencies) 'symbol'}}

where currencies is the name of the imported array from the dictionary. But this might be hard to follow for a first time reader, and also in case we ever need this functionality to work in a different context, this is clearly not the most feasible choice. Hence helpers come into picture, they can be called anywhere and will have a much simpler syntax

Our goal is to make helpers such that the required functionality is achieved with a simpler syntax than the one shown previously.So we will simply generate the helpers’ boiler-plate code via ember CLI

$ ember generate helper currency-name
$ ember generate helper currency-symbol

Next we will import the currency format from the payment dictionary to match it against the name or symbol provided by the user. Now all that remains is finding the correct matching from the dictionary. We import the find function from lodash for that.

So, this is how they would look

import Ember from 'ember';
import { find } from 'lodash';
import { paymentCurrencies } from 'open-event-frontend/utils/dictionary/payment';

const { Helper } = Ember;

export function currencyName(params) {
  return find(paymentCurrencies, ['code', params[0]]).name;
}

export default Helper.helper(currencyName);

 

And for the currency symbol helper

import Ember from 'ember';
import { find } from 'lodash';
import { paymentCurrencies } from 'open-event-frontend/utils/dictionary/payment';

const { Helper } = Ember;

export function currencySymbol(params) {
  return find(paymentCurrencies, ['code', params[0]]).symbol;
}

export default Helper.helper(currencySymbol);

 

Now all we need to do use them is {{currency-name ‘USD’}} and {{currency-symbol ‘USD’}} to get the corresponding currency name and symbol. We use find from lodash here instead of the default even though it is similar in performance because it provides much better readability.

Resources

*Featured Image licensed under Creative Commons CC0 in public domain

Continue ReadingMaking currency name and currency symbol helpers for Open Event Frontend

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

Continue ReadingCreating a notification dropdown in semantic UI for Open Event Frontend