Using Transitions API with Email Validation in Open Event Organizer Android App

Transitions in Material Design apps provide visual continuity. As the user navigates the app, views in the app change state. Motion and transformation reinforce the idea that interfaces are tangible, connecting common elements from one view to the next.

In the Open Event Organizer Android App, we need a transition from the Get Started screen such that, if the user email is registered, we transition to the Login Screen otherwise, we Transition to the Sign Up Screen. And the transition should be such that the email field is continuously visible. One more condition is that, if the email field is even varied by one character, we need to transition back to the Get Started Screen.

To implement this, we need to use Shared Elements from the Android Transitions API.

What are shared elements?

A shared element transition determines how views that are present in two fragments transition between them. For example, an image that is displayed on an ImageView on both Fragment A and Fragment B transitions from A to B when B becomes visible.

Fade transition is used to fade a view and ChangeBounds transition is used to move a view without changing its size.

To make a transition, we use the setupTransitionAnimations() function like this:

(Note that we have created our own Fade and ChangeBounds transitions and not using XML)

   public void setupTransitionAnimation(Fragment fragment) {
       Fade exitFade = new Fade();
       exitFade.setDuration(100);
       this.setExitTransition(exitFade);
       fragment.setReturnTransition(exitFade);

       ChangeBounds changeBoundsTransition = new ChangeBounds();
       fragment.setSharedElementEnterTransition(changeBoundsTransition);

       Fade enterFade = new Fade();
       enterFade.setStartDelay(300);
       enterFade.setDuration(300);
       fragment.setEnterTransition(enterFade);
       this.setReenterTransition(enterFade);
   }

Now, in order to detect, if the email field is touched and even changed by one character, we use the TextWatcher like this:

       binding.emailLayout.getEditText().addTextChangedListener(new TextWatcher() {

           @Override
           public void beforeTextChanged(CharSequence s, int start, int count, int after) {
               //do nothing
           }

           @Override
           public void onTextChanged(CharSequence s, int start, int before, int count) {
               if (start != 0) {
                   sharedViewModel.setEmail(s.toString());
                   getFragmentManager().popBackStack();
               }
           }

           @Override
           public void afterTextChanged(Editable s) {
               //do nothing

           }

       }     

So basically, the moment the text is changed, we add the changed email to the SharedViewModel so that it can be used in the other fragments, and then to start the transition, we pop the back stack using getFragmentManager().popBackStack();

This is what the result looks like:

Resources

  • Google – Android developer blog post:

https://android-developers.googleblog.com/2018/02/continuous-shared-element-transitions.html

Continue Reading

Add check-in restrictions to Open Event Organizer App

The Open Event Organizer Android App has the ability to scan and check-in attendees holding different ticket types for an event. But often there are cases when the attendees holding a particular ticket type need to be check-in restricted. It can be because of reasons such as facilitating entry of premium ticket holders before general ticket holders, or not allowing general ticket holders in a VIP queue.

To facilitate this, we have a field called ‘is-checkin-restricted’ for the entity Ticket. So when it is set to true, any check ins for the holder of that particular ticket type will be restricted. Let’s look at how this was implemented in the Orga App.

This is what we want to achieve:

Even though we needed it to be present in the settings screen, we needed it to be dynamic in nature as the types of tickets are themselves dynamic. This meant that we couldn’t achieve this using the plain old preference themes. We must create a whole new fragment for it and try to make it as similar to a preference theme as possible.

We need the following to create a dynamic tickets fragment:

  1. The fragment itself, which should implement the interfaces:  Progressive, Erroneous  to show progress and error.
  2. An Adapter and a ViewHolder
  3. A ViewModel

The fragment CheckinRestriction is similar to the TicketsFragment for the most part except for the part where we need to restrict check in. In the fragment we are providing a checkbox at the top to restrict check-in for all ticket types. So we need to setup click listeners not just for the checkbox, but for the whole view as well, like this:

binding.restrictAll.setOnClickListener(v -> {
       restrictAll(!binding.restrictAllCheckbox.isChecked());
   });
binding.restrictAllCheckbox.setOnClickListener(v -> {
       //checkbox already checked
       restrictAll(binding.restrictAllCheckbox.isChecked());
   });

The restrictAll() method restricts check-in for all ticket types by updating the view and updating the tickets using the ViewModel:

private void restrictAll(boolean toRestrict) {
   binding.restrictAllCheckbox.setChecked(toRestrict);
   ticketSettingsViewModel.updateAllTickets(toRestrict);
   ticketsAdapter.notifyDataSetChanged();
}

It’s also important to note here how we are handling the clicks in the ViewHolder for each ticket item:

public void bind(Ticket ticket) {
   binding.setTicket(ticket);
   View.OnClickListener listener = v -> {
       ticket.isCheckinRestricted = ticket.isCheckinRestricted == null || !ticket.isCheckinRestricted;
       binding.ticketCheckbox.setChecked(ticket.isCheckinRestricted);
       updateTicketAction.push(ticket);
       binding.executePendingBindings();
   };
   itemView.setOnClickListener(listener);
   binding.ticketCheckbox.setOnClickListener(listener);
}

A method that is run each time in order to check if all the tickets are restricted and then accordingly tick the ‘restrict-all’ box.

private void checkRestrictAll() {
   if (ticketSettingsViewModel.getTickets() == null) {
       return;
   }
    boolean restrictAll = true;
    for (Ticket ticket : ticketSettingsViewModel.getTickets().getValue()) {
       if (ticket.isCheckinRestricted == null || !ticket.isCheckinRestricted) {
           restrictAll = false;
           break;
       }
   }
   binding.restrictAllCheckbox.setChecked(restrictAll);
}

This is all of the code we need apart from the boilerplate code in order to successfully build a check-in-restrictions fragment.

Read more of the code here

Resources:

Continue Reading

Implementing Checkout Times for Attendees on Open Event Server

As of this writing, Open Event Server did not have the functionality to add, manipulate and delete checkout times of attendees. Event organizers should have access to log and update attendee checkout times. So it was decided to implement this functionality in the server. This boiled down to having an additional attribute checkout_times in the ticket holder model of the server.

So the first step was to add a string column named checkout_times in the ticket holder database model, since this was going to be a place for comma-separated values (CSV) of attendee checkout times. An additional boolean attribute named is_checked_out was also added to convey whether an attendee has checked out or not. After the addition of these attributes in the model, we saved the file and performed the required database migration:

To create the migration file for the above changes:

$ python manage.py db migrate

To upgrade the database instance:

$ python manage.py db upgrade

Once the migration was done, the API schema file was modified accordingly:

class AttendeeSchemaPublic(SoftDeletionSchema):
    """
    Api schema for Ticket Holder Model
    """
    
    checkout_times = fields.Str(allow_none=True)  # ←
    is_checked_out = fields.Boolean()  # ←
    

After the schema change, the attendees API file had to have code to incorporate these new fields. The way it works is that when we receive an update request on the server, we add the current time in the checkout times CSV to indicate a checkout time, so the checkout times field is essentially read-only:

from datetime import datetime
...
class AttendeeDetail(ResourceDetail):
    def before_update_object(self, obj, data, kwargs):
        
        if 'is_checked_out' in data and data['is_checked_out']:
        ...
        else:
            if obj.checkout_times and data['checkout_times'] not in \
obj.checkout_times.split(","):
                data['checkout_times'] = '{},{},{}'.format(
                    obj.checkout_times,
                    data['checkout_times'],
                    datetime.utcnow())

 

This completes the implementation of checkout times, so now organizers can process attendee checkouts on the server with ease.

Resources

Continue Reading

Adding Tickets Relationship with Discount Codes in Open Event Server

Recently (as of this writing), it was discovered that the relationship between discount codes and tickets was not implemented yet in Open Event Server. It turns out that the server has two types of discount codes – discount codes for entire events and discount codes for individual tickets of a specific event. More information on how discount code themselves are implemented in the server can be found in this blog post from 2017 – Discount Codes in Open Event Server.

So, for implementing the relationship of discount codes with tickets, it was decided to be present only for discount codes that have the DiscountCodeSchemaTicket schema, since those are the discount codes that are used for individual tickets. As a first step, the `tickets` attribute of the discount code model was removed, as it was redundant. The already implemented used_for attribute did the same job, and with better validation. At the same time, discount code was added as an attribute.

In the ticket model file:

discount_code_id = db.Column(db.Integer, db.ForeignKey('discount_codes.id', ondelete='CASCADE'))
discount_code = db.relationship('DiscountCode', backref="tickets")

Also, in the __init__ constructor:

def __init__(self, ..., discount_code_id=None, ...):
    ...
    ...
    self.discount_code_id = discount_code_id

After that, we added a discount_code_id field in the ticket schema file:

discount_code_id = fields.Integer(allow_none=True)

In this file, we also removed the redundant tickets field.

Now, we migrated the Open Event Server database via the following commands:

$ python manage.py db migrate

then

$ python manage.py db upgrade

Next, in the discount code schema file, we added the tickets relationship. Note that this is a one-to-many relationship. One discount code (for tickets) can be mapped to many tickets. Here is the code for that relationship, in the discount code schema file, under the DiscountCodeSchemaTicket class:

tickets = Relationship(attribute='tickets',
self_view='v1.discount_code_tickets',
self_view_kwargs={'id': '<id>'},
related_view='v1.ticket_list',
related_view_kwargs={'discount_code_id': '<id>'},
schema='TicketSchemaPublic',
many=True,
type_='ticket')

For this, we, of course, imported the TicketSchemaPublic in this file first. After that, we created a DiscountCodeTicketRelationship class in the discount codes API file:

class DiscountCodeTicketRelationship(ResourceRelationship):
    """
    DiscountCode Ticket Relationship
    """
    decorators = (jwt_required,)
    methods = ['GET', 'PATCH']
    schema = DiscountCodeSchemaTicket
    data_layer = {'session': db.session, 'model': DiscountCode}

The next step was to add the query code to fetch the tickets related to a particular discount code from the database. For this, we added the following snippet to the query() method of the TicketList class in the tickets API file:

if view_kwargs.get('discount_code_id'):
    discount_code = safe_query(self, DiscountCode, 'id', view_kwargs['discount_code_id'], 'discount_code_id')
    # discount_code - ticket :: one-to-many relationship
    query_ = self.session.query(Ticket).filter_by(discount_code_id=discount_code.id)

The only thing that remains now is adding the API routes for this relationship. We do that in the project’s __init__.py file:

api.route(TicketList, 'ticket_list', '/events/<int:event_id>/tickets',
'/events/<event_identifier>/tickets', '/ticket-tags/<int:ticket_tag_id>/tickets',
'/access-codes/<int:access_code_id>/tickets', '/orders/<order_identifier>/tickets',
'/discount-codes/<int:discount_code_id>/tickets')

api.route(DiscountCodeTicketRelationship, 'discount_code_tickets',
'/discount-codes/<int:id>/relationships/tickets')

 

Many routes already map to TicketList, we added one for that comes from discount codes API. Now we can use Postman to check this relationship, and it indeed works as expected, as seen below!

Screen Shot 2018-08-21 at 1.54.22 PM

Here’s the end:

Screen Shot 2018-08-21 at 1.54.35 PM.png

References:

Continue Reading

Implementing Event Average Rating with SQLAlchemy

While implementing Open Event Server version 2, we decided to have a better way of ranking events by their quality. To define the “quality” of events, the programmers decided to accumulate the feedbacks of specific events and take the average of the ratings involved. Thus, the average rating of an event proves to be a good (enough) measure of its quality. While there are many ways to implement aggregate relationships in an app, here I demonstrate a rather modern methodology which insists on storing such aggregates once they’re computed.

Since there is always a space-time/computation tradeoff in software development, this task was no exception. At first, the straightforward idea that came to my mind was to query the Postgres database every time a request for average rating was made. This sounds simple, but with hundreds of events stored on a server, and potentially thousands of users querying for events, this seemed to be a computationally expensive approach. It was costly because the average rating aggregate would be computed for each request, and there could potentially be thousands of such concurrent requests. Therefore, a better idea is to compute the aggregate once, store it in the database (compromising space in the tradeoff mentioned above, but saving a large amount of computation at the same time), and update only when a change is made. In our specific case, the update should happen only when a new rating is added, a rating is deleted or an existing rating is modified. Since the advantages outnumbered the disadvantages, this was the strategy to be implemented.

The first step in implementing average rating was to modify the database model of events accordingly. For this, I performed the necessary imports in the events’ database model file:

from sqlalchemy_utils import aggregated
from app.models.feedback import Feedback

Now comes the tricky part. We want an average_rating column in the events table, that contains the mean rating of events. The values in this column should be updated every time a change is made to the feedbacks table. To perform this sort of functionality, the best, raw tool is a Postgres trigger. A trigger should be created that is fired after every update to the feedbacks table, which should update the average rating values in the events table. Here’s how the raw code of such a trigger looks like:

create or replace function UpdateAverageRating() returns trigger AS
$$
BEGIN
UPDATE events SET average_rating=(
SELECT avg(rating) FROM feedbacks
WHERE event_id=NEW.event_id
GROUP BY event_id
)

WHERE id = NEW.event_id
END
$$
language plpgsql

Fortunately, the translation of such a trigger into SQLAlchemy-speak is not only easy, but also very elegant. The imports I showed above already set the context for this translation.

The event model class looks like the following:

class Event(db.Model):
    """Event object table"""
    __tablename__ = 'events'
    __versioned__ = {
'exclude': ['schedule_published_on', 'created_at']
}
    id = db.Column(db.Integer, primary_key=True)
    identifier = db.Column(db.String)
    name = db.Column(db.String, nullable=False)
    external_event_url = db.Column(db.String)

    

    

    

The list of attributes continues, and to the end of this list, we now add a decorated method:




xcal_url = db.Column(db.String)
is_sponsors_enabled = db.Column(db.Boolean, default=False)
discount_code_id = db.Column(db.Integer, db.ForeignKey(
'discount_codes.id', ondelete='CASCADE'))

@aggregated('feedbacks', db.Column(db.Float))
def average_rating(self):
    return db.func.avg(Feedback.rating)

That’s it with the translation – this slick, decorated method can be thought of as a bridge between Python and the trigger shown earlier that’s usually implemented in the database itself. Once this method is added, we save the model file and perform a database migration:

$ python manage.py db migrate

This generates a migration file associated with our changes. This file shows the following alembic migration code:

"""empty message

Revision ID: 1471fe0d04ee
Revises: 49f3a33f5437
Create Date: 2018-06-08 19:32:47.485543

"""

from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '1471fe0d04ee'
down_revision = '49f3a33f5437'


def upgrade():
    op.add_column('events', sa.Column('average_rating', sa.Float(), nullable=True))
    op.add_column('events_version', sa.Column('average_rating', sa.Float(), autoincrement=False, nullable=True))

def downgrade():
    op.drop_column('events_version', 'average_rating')
    op.drop_column('events', 'average_rating')

Now that the file is generated, we upgrade our database state by utilizing this migration file:

$ python manage.py db upgrade

And here are the successful migration logs that immediately follow the upgrade command:

INFO  [alembic.runtime.migration] Context impl PostgresqlImpl.
INFO  [alembic.runtime.migration] Will assume transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade 49f3a33f5437 -> 1471fe0d04ee, empty message

 

This completes the implementation of the average rating attribute of events. We can use the same technique to implement other attributes in our server, like sum, and minimum, just to name a couple. We saw how SQLAlchemy so elegantly manages to map all the mapping from Python code to database commands. This is just one of the plethora of advantages of using database object-relational-mappers (ORMs), and Open Event Server utilizes them to full extent.

Resources:

Continue Reading

Adding System Messages on Open Event Server

The Open Event Server enables organizers to manage events from concerts to conferences and meetups. It offers features for events with several tracks and venues. Event managers can create invitation forms for speakers and build schedules in a drag and drop interface. The event information is stored in a database. The system provides API endpoints to fetch the data, and to modify and update it.

The Open Event Server is based on JSON 1.0 Specification and hence build on top of Flask Rest Json API (for building Rest APIs) and Marshmallow (for Schema).

In this blog, we will talk about how to add API for accessing the System Messages on Open Event Server. The focus is on its Model updation and it’s Schema creation.

Model Updation

For the System Messages, we’ll make update model as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to read email and notification formats used in Open Event application.

  1. First of all, there is the need to know that it has three columns notification_status, user_control_status and mail_status of type boolean.
  2. Next it has action attribute which is of type String.
  3. At last, we have hybrid properties email_message and notification_message which will return the format of email and notification respective to the action string.
  4. The hybrid properties depends on _email_message method and _notification_message method. These methods reads the MAILS and NOTIFS dictionaries and return there values corresponding to string of action key of corresponding record.

Schema Creation

For the System Messages, we’ll make our Schema as follows

Now, let’s try to understand this Schema.

In this feature, we are providing Admin the rights to read email and notification formats used in Open Event application.

  1. First of all, there is the need to know that it has three boolean properties notification_status, user_control_status and mail_status
  2. Next it has action attribute which is of type String and it’s value can be validated to have any one of the list provided in choices.
  3. At last, it has the String attributes email_message and notification_message which will return the action formats of email and notification concerning the action string provided.

So, we saw how System Messages Schema and Model is created / updated to allow Admin users to read it’s values.

Resources

Continue Reading

Add Edit user Modal in Open Event Frontend

This blog article will illustrate how the UI for the edit user modal is implemented  and Users API has been integrated into it in Open Event Frontend.

The admin can make any user the admin, sales admin or the marketer of the app. In the route admin/users there is an ember table where all the users are listed. In the table there exists a column named ‘Action Buttons’.

When the edit action button is clicked a modal appears on the screen. Them template of the modal is as follows:

class="content">
class="ui form">

class="ui header">{{t 'Provide admin access?'}}

class="grouped inline fields">
class="field"> {{ui-radio name="isAdmin" label="Yes" value=true onChange=(action (mut isAdmin))}} {{ui-radio name="isAdmin" label="No" value=false onChange=(action (mut isAdmin))}}
</div> <h4 class="ui header">{{t 'Custom system roles'}}</h4>
class="field"> {{ui-checkbox label="Sales Admin" onChange=(action (mut checked))}} {{ui-checkbox label="Marketer" onChange=(action (mut checked))}}
<button class="ui teal right floated submit button update-changes"> {{t 'Save'}} </button> </div> </div>

For the API integration the users model is used. The attributes isAdmin, isSalesAdmin, isMarketer from the model are used to send a patch request to the server. The modal has basically to parts. The first part consists of radio buttons through which the super admin has the rights to create a user an admin of the app or to remove his role as the admin. The second part consists of checkboxes through which the user can get the custom system role to be the sales admin or the marketer. A get request is sent to the user’s model in the server and the initial values of the modal are decided.

If the admin changes some value, he clicks on the save button in the modal and a patch request is sent to the server. The save function is written in the modal’s component.

actions: {
saveRole(id) {
this.get('store').findRecord('user', id).then(function(user) {
user.save();
});
this.set('isOpen', false);
},
toggleSalesAdmin(user) {
user.toggleProperty('isSalesAdmin');
},
toggleMarketer(user) {
user.toggleProperty('isMarketer');
},
createAdmin(user, isAdmin) {
user.set('isAdmin', isAdmin);
}
}

Resources

Continue Reading

Adding the User Settings Route in Admin User Route Open Event Frontend

This blog article will illustrate how the User settings API has been integrated into the admin users route  Open Event Frontend. The admin can change the contact info of some user, details about the email preferences for different events created by the user and the third party authentication details entered by the user.

To make the settings user link in the user link column of the users table functional new sub routes are added to the app’s user route as follows:

  • /admin/users/<user_id>/settings/contact-info
  • /admin/users/<user_id>/settings/email-preferences
  • /admin/users/<user_id>/settings/applications

The template for the index route which redirects to each of the settings route is:

class="ui grid">
class="row">
class="twelve wide column"> {{#tabbed-navigation}} {{#link-to 'admin.users.view.settings.contact-info' model.user.id class='item'}} {{t 'Contact Info'}} {{/link-to}} {{#link-to 'admin.users.view.settings.email-preferences' model.user.id class='item'}} {{t 'Email-Preferences'}} {{/link-to}} {{#link-to 'admin.users.view.settings.applications' model.user.id class='item'}} {{t 'Applications'}} {{/link-to}} {{/tabbed-navigation}}
</div> {{outlet}} </div>

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 events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the details of a given user like the email,  contact, various events created by the user to get the email and notification preferences. For getting each detail the current users model is returned and the values in the model are returned to the form.

For the contact-info sub route the values like the email and the contact number are fetched and are shown in the form. There is a save button in the form too. The admin can change this information and send a patch request to the server by clicking this button.

 updateContactInfo() {
this.set('isLoading', true);
let currentUser = this.get('model.user');
currentUser.save()
.then(() => {
this.get('notify').success(this.get('l10n').t('Your Contact Info has been updated'));
})
.catch(() => {
this.get('notify').error(this.get('l10n').t('An unexpected error occurred'));
})
.finally(() => {
this.set('isLoading', false);
});
}

For the email-preferences sub route the model has attributes like sessionSchedule, nextEvent etc.and the admin has the access to change the email-notifications for any event  created by any user. The client side has checkboxes to show the data to the user. The states of the checkboxes are determined by the data that we receive from the API. We also let the admin change the preferences of the email-notifications so that he can customise the notifications and keep the ones he wants some user to receive.

{{settings/email-preferences-section preferences=model}}

The sub route for email preferences:

export default Route.extend(AuthenticatedRouteMixin, {
titleToken() {
return this.get('l10n').t('Email Preferences');
},
model() {
const currentUser = this.modelFor('admin.users.view');
return currentUser.query('emailNotifications', { include: 'event' });
}
});

So, the admin has the access to change the information and the email notifications of a user.

Resources

Continue Reading

Adding Dredd Tests for Image Sizes on Open Event Flask Server

In this blog, we will talk about how to add dredd hooks for testing the API of Event Image Sizes and Speaker Image Sizes in Open Event Server. The focus is on adding the factory class and dredd hooks of these APIs using factory-boy python library and Dredd API testing framework.

Factory Creation

For the Event and Speaker Image Sizes, we’ll make our factory classes EventImageSizeFactory  and SpeakerImageSizeFactory as follows

Now, let’s try to understand this class.

In this class, we are writing the sample data two records of ImageSizes Model, these records corresponds to Event and Speaker Image Sizes.

  1. First of all, we inherit class factory.alchemy.SQLAlchemyModelFactory to build our sample data which for Image Sizes.
  2. Class Meta has model and sqlalchemy_session attributes. Model tells the factory class of to which model this factory class push the data to database and sqlalchemy_session is assigned with the current database session.
  3. Next, we add the attributes according to the model and Schema of Image Sizes.

Adding Dredd Hooks

For the ImageSizes, we’ll make our dredd hooks as follows

Now, let’s try to understand these tests.

In this tests, we check the API by matching the response after adding a record in these API to one which is present at API blueprint.

  1. First of all, we use decorator @hooks.before which means we first add a record in the database and then match the response we get from API say /v1/event-image-sizes with the response mentioned at Image Size > Event Image Size Details > Get Event Image Size Details in API blueprint.
  2. We create an instance of EventImageSizeFactory which is a record of model Image Sizes.
  3. This record is then returned as a response of API /v1/event-image-sizes and matches with the blueprint at Image Size > Event Image Size Details > Get Event Image Size Details

Similarly, we have added other dredd tests for PATCH method as well.

So, we saw how factory-boy python library and Dredd API testing framework helped us in testing the REST APIs on Open Event Server.

Resources

Continue Reading

Events API Integration on Admin User Route Open Event Frontend

This blog article will illustrate how the Events API has been integrated into the admin users route  Open Event Frontend, as well as how the action buttons are added to view, edit or delete the events of any user in the list by the admin.

To make the events user link in the user link column of 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('events', function() {
         this.route('list', { path: '/:event_status' });
       });
     });

The newly added route further contains a dynamic sub route called list. This nested route fulfills the requirement of filtering the various events 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 events or sessions and the route needs to be replaced with the desired events or sessions route accordingly.

The server is queried to fetch the events 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/events/list.js

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

After fetching the the events from the server, a proper ember table is called in the template file of this route, and all the actions like viewing and editing an event are declared in the template.

{{events/events-table
columns=columns data=model.events
useNumericPagination=true
moveToDetails=(action 'moveToDetails')
editEvent=(action 'editEvent')
openDeleteEventModal=(action 'openDeleteEventModal')
}}

In the controller the columns of the table for events are defined and all the actions are defined.

moveToDetails(id) {
this.transitionToRoute('events.view', id);
},
editEvent(id) {
this.transitionToRoute('events.view.edit.basic-details', id);
},
deleteEvent() {
this.set('isLoading', true);
this.store.findRecord('event', this.get('eventId'), { backgroundReload: false }).then(function(event) {
event.destroyRecord();
})

So, the admin can view the list of the events of a particular user and send a patch or delete request for any event.

Resources

Continue Reading
Close Menu
%d bloggers like this: