Implementing Access Codes in Open Event Frontend

The Open-Event-Frontend allows the event organiser to create access codes for his or her event.  Access codes can be used to password protect hidden tickets reserved for sponsors, members of the press and media. This blog post explains how we have integrated access codes creation in the frontend utilising the various features of Ember JS and Semantic UI.

Create Access code component

We will be creating a separate component for creating access code. To create it we will use the following command:

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

This will create the following files:

  1. components/forms/events/view/create-access-code.js
  2. templates/components/forms/events/view/create-access-code.hbs
  3. tests/integration/components/forms/events/view/create-access-code-test.js

Create-access-code.hbs

This file includes the handlebar syntax to design the front end of the access code component. The whole template is nested inside the Semantic UI’s form class. Some of the helpers used are as follows:

  • Ember Input Helper: It has been used extensively throughout the template in order to take input from the event organizer. For e.g.:
{{input type=‘text’ name=‘access_code’ value=data.code}}
  • Semantic Radio Button: The semantic radio button has been used in order to allow the organizer to select the state of the access-code. He/She can choose if the access-code is active or inactive.

<div class="grouped inline fields">
    <label class="required">{{t 'Status'}}</label>
    <div class="field">
      {{ui-radio current=data.isActive name='status' label='Active' value='true' onChange=(action (mut data.isActive))}}
    </div>
    <div class="field">
      {{ui-radio name='status' label='Inactive' value='false' current=data.isActive onChange=(action (mut data.isActive))}}
    </div>
  </div>
  • Date Time Picker: The organizer can set the validity of the access code as well. We have used date-picker and time-picker components which were already created in the project. They have been used in the following way:

<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>

Create-access-code.js

We use this file as the core of the component and handle the following use cases:

  1. Validation of the input. We show warning if something is wrong.
  2. Actions used by the various elements of the templates.
  3. Providing the link for the access-code.
  4. Saving the access-code.

accessCode : '',
  accessUrl  : computed('data.code', function() {
    const params = this.get('router._router.currentState.routerJsState.params');
    this.set('data.accessUrl', location.origin + this.get('router').urlFor('public', params['events.view'].event_id, { queryParams: { access_code: this.get('data.code') } })) ;
    return this.get('data.accessUrl');
  }),
  actions: {
    toggleAllSelection(allTicketTypesChecked) {
      this.set('allTicketTypesChecked', allTicketTypesChecked);
      if (allTicketTypesChecked) {
        this.set('data.tickets', this.get('data.event.tickets').slice());
      }
    },
    updateTicketSelections(newSelection) {
      if (newSelection.length === this.get('data.event.tickets').length) {
        this.set('allTicketTypesChecked', true);
      } else {
        this.set('allTicketTypesChecked', false);
      }
    },
    submit(data) {
      this.onValid(() => {
        data.save()
          .then(() => {
            this.get('notify').success(this.get('l10n').t('Access code has been successfully created.'));
            this.get('router').transitionTo('events.view.tickets.access-codes');
          })
          .catch(() => {
            this.get('notify').error(this.get('l10n').t('An unexpected error has occurred. Access code cannot be created.'));
          });
      });
    }
  }

Create-access-code-test.js

We can specify the tests in order to test the compatibility of the component here. For now, we will just write a simple test which checks if the component is rendered or not.

import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';

module('Unit | Controller | events/view/tickets/access codes/create', function(hooks) {
  setupTest(hooks);


  test('it exists', function(assert) {
    let controller = this.owner.lookup('controller:events/view/tickets/access-codes/create');
    assert.ok(controller);
  });
});

Now that we are done, setting up our component, we just need to add it in our application. We can achieve that using the following:

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

The model passed to the component is fetched from the create-access-code.js file.

References

Filtering and Session API Integration on Admin User Route Open Event Frontend

This blog article will illustrate how the Session API has been integrated into the admin users route  Open Event Frontend, as well as how it’s now possible to filter active and deleted users using the new filters implemented.

To make the sessions buttons on the users table functional a new sub route is added to the app’s user route as follows:

this.route('users', function() {
     this.route('view', { path: '/:user_id' }, function() {
       this.route('sessions', function() {
         this.route('list', { path: '/:session_status' });
       });
     });
     this.route('list', { path: '/:users_status' });

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

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

Thus if the dynamic portion of the route doesn’t contain the parameters all, deleted or active, then it must be referring to a user’s sessions and the route needs to be replaced with the desired sessions route accordingly. Also, the template admin/users.hbs needs to be changed to display the navigation bar  only when required. It is efficiently handled by an IF condition as follows:

{{#if (and (not-includes session.currentRouteName ‘admin.users.user’) (not-includes session.currentRouteName ‘admin.users.view.sessions.list’))}}

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

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

After fetching the the sessions from the server, the existing session-card  component is reused in the route’s template to display the sessions.

{{#if model.sessions}}
{{#each model.sessions as |session|}}
{{session-card session=session}}

{{/each}}
{{else}}

{{t ‘No session proposals found for the events’}}

{{/if}}
</div>
</div>

Also, in the admin/users route the filtering of deleted users was not functional. Thus  the property deleted-at of the users model which stores the timestamp of the deletion of a user was utilised. deleted-at is null for a user which is active. Hence the active and deleted users can be filtered as :

if (params.users_status === 'active') {
filterOptions = [
{
name : 'deleted-at',
op : 'eq',
val  : null
}
];
} else if (params.users_status === 'deleted') {
filterOptions = [
{
name : 'deleted-at',
op : 'ne',
val  : null
}
];
}
return this.get('store').query('user', {
get_trashed  : true,
filter       : filterOptions,
'page[size]' : 10
});

It’s important to pass the get_trashed parameter as true in the query as the the deleted user records are actually soft deleted records and will be fetched only when explicitly queried for.

Resources

Implementing Event Image Size and Speaker Image Size APIs in Open Event Frontend

This blog article will illustrate how the Image Sizes APIs concerning event and speaker images are integrated in  Open Event Frontend, which allows for dynamic configurations of storing speaker and event images. The primary end points of Open Event API with which we are concerned with for fetching the event and speaker image sizes are

GET /v1/event-image-sizes

And

GET /v1/speaker-image-sizes

These endpoints are accessible only to a user with has administrator privileges as the customisation of image sizes is possible only on the admin dashboard. The image sizes are independent in regards to relationships and don’t have any related fields.

The model for the admin image settings route is defined as follows:

export default ModelBase.extend(CustomPrimaryKeyMixin, {
 thumbnailSizeQuality     : attr('number'),
 type                     : attr('string'),
 smallSizeWidthHeight     : attr('number'),
 smallSizeQuality         : attr('number'),
 iconSizeQuality          : attr('number'),
 iconSizeWidthHeight      : attr('number'),
 thumbnailSizeWidthHeight : attr('number')
});

The form which allows user to select image sizes, is in a separate component, and initially both the speaker and event image sizes are passed onto the component as a part of the entire model, so they can be separated later as per the requirement.

{{forms/admin/settings/images-form image=model save=’saveImages’ isLoading=isLoading}}

Most of the fields specify the units in which the numerical input concerning the image dimensions will be interpreted by the server and standard min and max validations are applied to the fields to ensure genuine and legitimate values can pass through the frontend.

<h3 class=”ui header”>{{t ‘Large Size’}}</h3>


{{input type=’number’ name=’large_width’ value=image.eventImageSize.fullWidth min=1}}

{{input type=’number’ name=’large_height’ value=image.eventImageSize.fullHeight min=1}}

{{input type=’number’ name=’large_quality’ value=image.eventImageSize.fullQuality min=1}}

</div>

{{ui-checkbox label=(t ‘Standard aspect ratio is 13:5. Tick to maintain aspect ratio.’) class=’checkbox’ name=’large_ratio’ checked=image.eventImageSize.fullAspect onChange=(action (mut image.eventImageSize.fullAspect))}}

{{t ‘Standard Size of the available area is 1300px X 500px’}}
<p>{{t ‘Found in :’}}</p>

{{t ‘Background Header Image in Public Event Page’}}

</div>

Furthermore, to ensure a user does not accidentally change the values inside the form, an action is triggered while transitioning away from the route which rollbacks any unsaved changes to the image sizes.

actions: {
willTransition() {
this.get('controller.model').forEach(image => {
image.rollbackAttributes();
});
}
}

Resources

Creating the View Route for Users in Open Event Frontend

This blog article will describe how the users view user route is created in Open Event Frontend, which allows the admin to view a user whenever view action button in the user table is clicked.

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

GET /v1/users/{user_id}

The complete user information on the view route is displayed, which includes the name, email, details about the user. All these are the attributes of the model user. Thus the model for the route is defined as follows:

model(params) {
return this.store.findRecord('user', params.session_id);

The view route is located at app/admin/users/<user_id> and the parent route, app/users has another sub route within it called list. The list route shows all, active, deleted users. This list has a column of action buttons.

This list can only be accessed by the admin. Whenever the view button in the Actions column is clicked the admin gets redirected to the view users route.

actions: {
    moveToUserDetails(id) {
      this.transitionToRoute('admin.users.view', id);
    }
}

The user profile form is:

{{widgets/forms/image-upload
imageUrl=user.avatarUrl
needsCropper=true
label=(t ‘User Image’)
id=’user_image’
icon=’photo’
hint=(t ‘Select User Image’)
maxSizeInKb=10000
aspectRatio=(array 1 1)
helpText=(t ‘We recommend using at least a 1000x1000px (1:1 ratio) image’)}}


{{input type=’text’ id=’name’ value=user.firstName}}

{{input type=’text’ id=’last_name’ value=user.lastName}}

{{widgets/forms/rich-text-editor id=’details’ value=user.details}}

The view route shows the following information about the user: Name, Family name, Email, Image of the user, Details of the user. Thus the admin can view all the users registered in the application.

Resources

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

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

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

GET /v1/admin/statistics/events

GET /v1/admin/statistics/events

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

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

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

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

For Events:

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

For Sessions:

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

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

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

Resources

Creating the View Route for Sessions in Open Event Frontend

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

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

GET /v1/sessions/{session_identifier}

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

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

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

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

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

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

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

Finally the template for the view route is created:

   

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

{{t ‘Accepted’}}

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

{{t ‘Pending’}}

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

{{t ‘Rejected’}}

{{/if}}
</div>

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

</div>

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

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

Resources

 

Implementation of Badge Size Feature in Badgeyay Front-end

Badgeyay project is divided into two parts i.e front-end with Ember JS and back-end with REST-API programmed in Python.

Badgeyay has many features related to enhancement in the generation of badges. It gives the choice of uploading data entries i.e by CSV or manually. There are options available for choosing Badge Background and font specifications. But there is an important feature missing which will make the service more user-friendly in terms of creation of badges for different types of events i.e, Badge Size.

Badge Size feature is implemented in Backend. I need to send the data in the backend in the desired format for creation of Badges with different sizes.

In this Blog, I will be discussing how I implemented Badge Size feature in Badgeyay Frontend in my Pull Request.

Let’s get started and understand it step by step.

Step 1:

Create Badge Size component with Ember CLI.

 

$ ember g  component  badge-component/badge-size

 

Step 2:

Write the HTML required in the badge-size component:

 

// templates/components/badge-component/badge-size.hbs

class="inline fields">
class="field">
class="ui radio checkbox" {{ action 'mutateBadgeSize' 'A3' }}> name="size" value="A3" type="radio"> A3
</div>
class="field">
class="ui radio checkbox" {{ action 'mutateBadgeSize' 'A4' }}> name="size" value="A4" type="radio"> A4
</div>
class="field">
class="ui radio checkbox" {{ action 'mutateBadgeSize' 'A5' }}> name="size" value="A5" type="radio"> A5
</div>
class="field">
class="ui radio checkbox" {{ action 'mutateBadgeSize' 'A6' }}> name="size" value="A6" type="radio"> A6
</div> </div>

 

Step 3:

Integrate the Badge Size component with creating badges component.

 

// templates/create-badges.hbs
…………………………….
class="ui raised segment">
class="ui form width-container">

Select from one of the Badge Sizes

{{#ui-accordion class="styled fluid"}}
class="title"> class="plus icon"> Badge Size
class="content">
class="center aligned"> {{ badge-component/badge-size sendBadgeSize=(action 'mutateBadgeSize') }} // Injecting Action
</div> {{/ui-accordion}} </div> </div> ………………………….

 

Step 4: Define the actions that are injected into the component.

 

// badge-component/badge-size.js

import Component from '@ember/component';

export default Component.extend({
  init() {
    this._super(...arguments);    // Initialize
  },

  actions: {
    mutateBadgeSize(value) {
      this.get('sendBadgeSize')(value);  // Get values
    }
  }
});

 

// controllers/create-badges.js
...............
     let badgeData = {
        uid        : _this.uid,
        badge_size : 'A3'  // Default Badge Size 
      };

      if (_this.defBadgeSize !== '' && _this.defBadgeSize !== undefined) {
        badgeData.badge_size = _this.defBadgeSize;
      }
...................
   mutateBadgeSize(value) {
      this.set('defBadgeSize', value);
    },
................

 

I have implemented the Feature to choose Badge Size in the frontend. Now, the user can choose Badge size also for Badge customization.

Step 5::

Now run the server to see the implemented changes by the following command.

 

$ ember serve

 

  • Badge Size Component

  • Payload when A5 Size Chosen for Badge Generation

Now, we are done with the implementation of Badge Size feature in Badgeyay Frontend.

Resources:

  • Ember Docs –  Link
  • Badgeyay Repository – Link
  • Issue Link – Link

Implementing dynamic forms to edit a speaker using Custom Forms in Open Event Frontend

Open Event Frontend allows the organizer of an event to customise the sessions and speakers form using the custom form API. While creating the event the organiser can select the form fields which he wants to place in the sessions and speakers form. This blog will illustrate how a form to edit the details of a speaker is created. Only those fields are included which were included by the person who created the event during the sessions and speakers creation section.

The first step is to retrieve the fields of the form. Each event has custom form fields which can be enabled on the sessions-speakers page, where the organiser can include/exclude the fields for speakers & session forms.

A query is written in the javascript file of the route admin/speakers/edit to retrieve the required details and create a form. The second query helps to determine the speaker id and include the model of speaker and the attribute values of the speaker with that specific id.

import Route from '@ember/routing/route';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';

export default Route.extend(AuthenticatedRouteMixin, {
 titleToken(model) {
   var speakerName = model.get('name');
   return this.get('l10n').t('Edit Speaker-'.concat(speakerName));
 },
 async model(params) {
   const eventDetails = this.modelFor('events.view');
   return {
     event : eventDetails,
     form  : await eventDetails.query('customForms', {
       'page[size]' : 50,
       sort         : 'id'
     }),
     speaker: await this.get('store').findRecord('speaker', params.speaker_id)
   };
 }
});

In the frontend we call the form of session and speakers. With the speaker-id being passed from the route, a form is created with the values entered by the user during the speaker creation and the other attributes marked included in the session-speakers wizard.

{{forms/session-speaker-form fields=model.form data=model save=(action 'save') isSpeaker=true includeSpeaker=true isSessionSpeaker=true isLoading=isLoading}}

Finally whenever user edits a speaker and clicks on the save button patch endpoint of the speakers API is called and the new details are saved.

Resources

  • Official Ember Model Table docs: http://onechiporenko.github.io/ember-models-table/v.1
  • Official Ember Data documentation: https://github.com/emberjs/data

How forms are created and the validations are added in Open Event Frontend

This blog article will illustrate how validations are added to a form  in Open Event Frontend, in a standard format. The form to Edit a Speaker is created in the route /event/<event_identifier>/speakers/edit. For the creation of a form an ember component is generated.

$ ember g component forms/events/view/edit-speaker

This command results in the generation of:

  1. An ember component edit-speaker.js to add the validation rules of the form.
  2. A handlebar edit-speaker.hbs where the HTML code is written.

First the form for editing a speaker is created. All the fields are added.

<form class=”ui form {{if isLoading ‘loading’}}” {{action ‘submit’ on=’submit’}}>


{{input type=’text’ id=’title’ value=data.name}}

{{input type=’text’ id=’email’ value=data.email}}

{{widgets/forms/image-upload
label=(t ‘Photo’)
imageUrl=data.photoUrl
icon=’image’
hint=(t ‘Select Photo’)
maxSizeInKb=1000}}

{{input type=’text’ id=’organisation’ value=data.organisation}}

{{input type=’text’ id=’position’ value=data.position}}

{{input type=’text’ id=’country’ value=data.country}}

{{widgets/forms/rich-text-editor value=data.shortBiography name=’shortBiography’}}

{{input type=’text’ id=’website’ value=data.website}}

{{input type=’text’ id=’twitter’ value=data.twitter}}

<button type=”submit” class=”ui teal submit button update-changes”>
{{t ‘Save Speaker’}}
</button>
</form>

Then validation rules for the fields included in the form are added in the component. The validations of a form are stored as objects, where the  identifier attribute determines which field to apply the validation conditions to. The rules array contains all the rules to be applied to the determined object. Within rules, the type represents the kind of validation, whereas the prompt attribute determines what message shall be displayed in case there is a violation of the validation rule. These validations are in turn implemented by the FormMixin.

import { protocolLessValidUrlPattern, validTwitterProfileUrlPattern } from ‘open-event-frontend/utils/validators’;

export default Component.extend(FormMixin, {

getValidationRules() {
return {
inline : true,
delay  : false,
on     : ‘blur’,
fields : {
email: {
identifier : ’email’,
rules      : [
{
type   : ’empty’,
prompt : this.get(‘l10n’).t(‘Please enter your email ID’)
},
{
type   : ’email’,
prompt : this.get(‘l10n’).t(‘Please enter a valid email ID’)
}
]
},
twitter: {
identifier : ‘twitter’,
optional   : true,
rules      : [
{
type   : ‘regExp’,
value  : validTwitterProfileUrlPattern,
prompt : this.get(‘l10n’).t(‘Please enter a valid twitter url’)
},
{
type   : ‘regExp’,
value  : protocolLessValidUrlPattern,
prompt : this.get(‘l10n’).t(‘Please enter a valid url’)
}
]
},
website: {
identifier : ‘website’,
optional   : ‘true’,
rules      : [
{
type   : ‘regExp’,
value  : protocolLessValidUrlPattern,
prompt : this.get(‘l10n’).t(‘Please enter a valid url’)
}
]
}
}
};
}

Then for adding the validation for the URLs of the speaker’s website and his twitter account regular expressions are used. They are used to perform pattern-matching.

export const  validTwitterProfileUrlPattern = new RegExp(
‘^twitter\\.com\\/([a-zA-Z0-9_]+)$’
);

 

export const protocolLessValidUrlPattern = new RegExp(
‘^’
// user:pass authentication
+ ‘(?:\\S+(?::\\S*)[email protected])?’
+ ‘(?:’
// IP address exclusion
// private & local networks
+ ‘(?!(?:10|127)(?:\\.\\d{1,3}){3})’
+ ‘(?!(?:169\\.254|192\\.168)(?:\\.\\d{1,3}){2})’
+ ‘(?!172\\.(?:1[6-9]|2\\d|3[0-1])(?:\\.\\d{1,3}){2})’
// IP address dotted notation octets
// excludes loopback network 0.0.0.0
// excludes reserved space >= 224.0.0.0
// excludes network & broacast addresses
// (first & last IP address of each class)
+ ‘(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])’
+ ‘(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}’
+ ‘(?:\\.(?:[1-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))’
+ ‘|’
// host name
+ ‘(?:(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)’
// domain name
+ ‘(?:\\.(?:[a-z\\u00a1-\\uffff0-9]-*)*[a-z\\u00a1-\\uffff0-9]+)*’
// TLD identifier
+ ‘(?:\\.(?:[a-z\\u00a1-\\uffff]{2,}))’
// TLD may end with dot
+ ‘\\.?’
+ ‘)’
// port number
+ ‘(?::\\d{2,5})?’
// resource path
+ ‘(?:[/?#]\\S*)?’
+ ‘$’, ‘i’
);

Resources

  • EmberJS Mixins–Official ember documentation: https://guides.emberjs.com/v2.2.0/models
  • Kravvitz, Regular Expression Rules: http://forums.devshed.com/javascript-development/493764-regexp-match-url-pattern-post1944160.html#post1944160

Open Event Frontend – Updating Ember Models Table from V1 to V2

FOSSASIA‘s Open Event Frontend uses the Ember Models Table for rendering all its tables. This provides features like easy sorting, pagination etc. Another major feature is that it can be modified to meet our styling needs. As we use Semantic UI for styling, we added the required CSS classes to our table.

In version 1 this was done by overriding the classes, as shown below :

const defaultMessages = {
  searchLabel            : 'Search:',
  searchPlaceholder      : 'Search',


  ..... more to follow 
};

const defaultIcons = {
  sortAsc         : 'caret down icon',
  sortDesc        : 'caret up icon',
  columnVisible   : 'checkmark box icon',
  
  ..... more to follow  
};

const defaultCssClasses = {
  outerTableWrapper              : 'ui ui-table',
  innerTableWrapper              : 'ui segment column sixteen wide inner-table-wrapper',
  table                          : 'ui tablet stackable very basic table',
  globalFilterWrapper            : 'ui row',

 ... more to follow
};

const assign = Object.assign || assign;

export default TableComponent.extend({
  layout,

  _setupMessages: observer('customMessages', function() {
    const customIcons = getWithDefault(this, 'customMessages', {});
    let newMessages = {};
    assign(newMessages, defaultMessages, customIcons);
    set(this, 'messages', O.create(newMessages));
  }),

  _setupIcons() {
    const customIcons = getWithDefault(this, 'customIcons', {});
    let newIcons = {};
    assign(newIcons, defaultIcons, customIcons);
    set(this, 'icons', O.create(newIcons));
  },

  _setupClasses() {
    const customClasses = getWithDefault(this, 'customClasses', {});
    let newClasses = {};
    assign(newClasses, defaultCssClasses, customClasses);
    set(this, 'classes', O.create(newClasses));
  },

  simplePaginationTemplate: 'components/ui-table/simple-pagination',

  ........
});

And was used in the template as follows:

<div class="{{classes.outerTableWrapper}}">
  <div class="{{classes.globalFilterDropdownWrapper}}">

But in version 2, some major changes were introduced as follows:

  1. All partials inside a models-table were replaced with components
  2. models-table can now be used with block content
  3. New themes mechanism introduced for styling

Here, I will talk about how the theming mechanism has been changed. As I mentioned above, in version 1 we used custom classes and icons. In version 2 the idea itself has changed. A new type called Theme was added. It provides four themes out of the box – SemanticUI, Bootstrap4, Bootstrap3, Default.

We can create our custom theme based on any of the predefined themes. To suit our requirements we decided to modify the SemanticUI theme. We created a separate file to keep our custom theme so that code remains clean and short.

import Default from 'ember-models-table/themes/semanticui';

export default Default.extend({
 components: {
   'pagination-simple'    : 'components/ui-table/simple-pagination',
   'numericPagination'    : 'components/ui-table/numeric-pagination',
   .....  
 },

 classes: {
   outerTableWrapper              : 'ui ui-table',
   innerTableWrapper              : 'ui segment column sixteen wide inner-table-wrapper',
   .....
 },

 icons: {
   sortAsc         : 'caret down icon',
   sortDesc        : 'caret up icon',
   ......
 },

 messages: {
   searchLabel            : 'Search:',
   .....
 }
});

So a theme mostly consists of four main parts:

  • Components
  • Classes
  • Icons
  • Messages

The last three are same as customClasses and customIcons and customMessages in version 1. Components is the map for components used internally in the models-table. In case you need to use a custom component, that can be done as follows:

Make a new JavaScript file and provide its path in your theme file.

import DefaultDropdown from '../../columns-dropdown';
import layout from 'your layout file path';
export default DefaultDropdown.extend({
  layout
});

Now just create the theme file object and pass it to themeInstance in the ui-table file (can also be passed in the template and the controller, but this has to be done for each table individually).

import TableComponent from 'ember-models-table/components/models-table';
import layout from 'open-event-frontend/templates/components/ui-table';
import Semantic from 'open-event-frontend/themes/semantic';

export default TableComponent.extend({
 layout,

 themeInstance: Semantic.create()
});

Hence, version 2 introduces many new styling options and requires some refactoring for those who were using version 1. It is totally worth it though considering how easy and well managed it is now.

References