Serializing Java objects for REST API Requests in Open Event Organizer App

Open Event Organizer App is a client side application which uses REST API for network requests. The server supports sending and receiving of data only in JSONAPI spec, so, we needed to serialize java models into JSON objects and deserialize JSON data into java models following JSONAPI spec. To achieve this we followed the following steps.

Specifications

We will be using jasminb/jsonapi-converter which handles request/response parsing of models following JSONAPI Spec and Retrofit plugin of jackson converter to serializing JSON to Java Models and vice versa.

Let’s create a java model. We are using some annotations provided by Lombok library to avoid writing boilerplate code. @JsonNaming annotation is used to apply KebabCaseStrategy while serializing fields

@Data
@Type(“order”)
@AllArgsConstructor
@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy.class)
@Table(database = OrgaDatabase.class, allFields = true)
public class Order {

@PrimaryKey
@Id(LongIdHandler.class)
public Long id;

public float amount;
public String completedAt;
public String identifier;
public String paidVia;
public String paymentMode;
public String status;

@Relationship(“event”)
@ForeignKey(stubbedRelationship = true, onDelete = ForeignKeyAction.CASCADE)
public Event event;

public Order() { }
}

In the NetworkModule class, there is a method providesMappedClasses() containing a list of classes that needs to be serialized/deserialized. We need to add the above model in the list. Then, this list is provided to Singleton instance of JSONAPIConvertorFactory through Dagger. JSONAPIConvertorFactory uses the Retrofit ObjectMapper and maps the classes that are handled by this instance.

@Provides
Class[] providesMappedClasses() {
return new Class[]{Event.class, Attendee.class, Ticket.class, Order.class};
}

Further, various serialization properties can be used while building Singleton ObjectMapper instance. Adding any properties here ensures that these are applied to all the mapped classes by JSONAPIConvertorFactory. For eg, we are using the serialization property to throw an exception and fail whenever empty beans are encountered.

@Provides
@Singleton
ObjectMapper providesObjectMapper() {
return new ObjectMapper()
.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS)
// Handle constant breaking changes in API by not including null fields
// TODO: Remove when API stabilizes and/or need to include null values is there
.setSerializationInclusion(JsonInclude.Include.NON_ABSENT);
}

Resources

  1. Github Repository for jsonapi-converter https://github.com/jasminb/jsonapi-converter
  2. Github repository for Jackson Retrofit Plugin https://github.com/square/retrofit/tree/master/retrofit-converters/jackson
  3. Official Website for Project Lombok https://projectlombok.org/

Github Repository for Open-Event-Orga-App https://github.com/fossasia/open-event-orga-app

Continue Reading

Adding Preference Settings using Preference Fragment Compat

It is very much likely that one needs to add preferences to their app which span the entire application and therefore can be accessed anywhere in the app without storing anything in database or making global variables. For an instance, in Open Event Organizer App we added the preferences to store the privacy policy, cookie policy etc. The user can access these items in Settings Preference which in device settings. In this blog post we will see how to add preference settings to the app by storing the data in shared preferences.

Specifications

The benefit of storing the data in shared preference and not in local storage is that the access time for the data is drastically reduced and the data persists even when the app is closed. We will use this library which is built on top of official preference-v7 library.

Firstly, we will make a preference resource layout file and add the preference for privacy policy and cookie policy in the preference screen.

<PreferenceScreen xmlns:android=”http://schemas.android.com/apk/res/android”>

<Preference
android:key=”@string/privacy_policy_key”
android:title=”@string/privacy_policy” />

<Preference
android:key=”@string/cookie_policy_key”
android:title=”@string/cookie_policy” />

</PreferenceScreen>

Make a separate preference fragment class named LegalPreferenceFragment which extends PreferenceFragmentCompat. Then we will override onCreatePreferenceFix() method.

Inside this, we will create an instance of Preference Manager and set shared preference name for it and set the preference using the layout file. This enables us to use findPreference() method to retrieve the layout preferences by their key. After, retrieving the preference we will set onClick listener to launch activity with an intent to open browser for the url passed in data bundle.

@Override
public void onCreatePreferencesFix(@Nullable Bundle bundle, String rootKey) {
PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Constants.FOSS_PREFS);

setPreferencesFromResource(R.xml.legal_preferences, rootKey);

findPreference(getString(R.string.privacy_policy_key)).setOnPreferenceClickListener(preference -> {
BrowserUtils.launchUrl(getContext(), PRIVACY_POLICY_URL);
return true;
});
findPreference(getString(R.string.cookie_policy_key)).setOnPreferenceClickListener(preference -> {
BrowserUtils.launchUrl(getContext(), COOKIE_POLICY_URL);
return true;
});
}

References

  1. Preference Fragment Compat library by Takisoft https://github.com/Gericop/Android-Support-Preference-V7-Fix
  2. Android Preference Documentation https://developer.android.com/reference/android/preference/PreferenceGroup
Continue Reading

Implementing Timeline for Attendees Activity in Organizer App

Open Event Organizer App offers the functionality to Checkin/checkout attendees but the Organizer was unable to view when a particular attendee was checkin or checkout. We decided to implement a feature to view the timeline of checkin/checkout for each attendee.

Let’s begin by adding the dependency in build.gradle.

implementation “com.github.vipulasri:timelineview:”1.0.6”

In the recyclerview item layout add the TimeLineView layout. Following are some of the useful attributes.

  1. app:markerInCenter – This defines the position of the round marker within the layout. Setting it to true, position it in center.
  2. app:marker – Custom drawables can be set as marker.
<com.github.vipulasri.timelineview.TimelineView
android:id=”@+id/time_marker”
android:layout_width=”wrap_content”
android:layout_height=”match_parent”
app:marker=”@drawable/ic_marker_active”
app:line=”#aaa4a4″
app:lineSize=”2dp”
app:linePadding=”3dp”
app:markerInCenter=”true”
app:markerSize=”20dp” />

The ViewHolder class will extend the RecyclerView,ViewHolder class. In the constructor, we will add a parameter viewType and then set it to TimeLine Marker layout using method initLine.

public CheckInHistoryViewHolder(CheckInHistoryLayoutBinding binding, int viewType) {
super(binding.getRoot());
this.binding = binding;
binding.timeMarker.initLine(viewType);
}

In RecyclerViewAdapter, we will override the getItemViewType() method. Here we will use the getTimeLineViewType method which takes in position and total size of the recycler view list and returns a TimeLineView type object.

@Override
public int getItemViewType(int position) {
return TimelineView.getTimeLineViewType(position, getItemCount());
}

References

  1. TimeLineView library by VipulAsri https://github.com/vipulasri/Timeline-View
  2. Android Documentation for RecyclerViewAdapter https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter
  3. Android Documentation for RecyclerViewView https://developer.android.com/reference/android/support/v7/widget/RecyclerView
Continue Reading

Adding List Preference Settings using Preference Fragment Compat

In this blog post we will see how we can add a Preference List in Settings which will display a list of radio buttons in UI which user can choose from. In Open Event Orga App, the Organizer had a choice to switch between viewing Net Sales or Gross Sales in the App’s Dashboard. We decided to use a preference list to allow the user to select between using Net or Gross Sales.

The benefit of using Preference List and not any other storage media (like SQLite) to store the information is that, Preference List stores the information as key-value pair in SharedPreferences which makes it easy to store and extract small amount of data with strong consistency guarantees and using less time. Let’s move on to the implementation.

Implementation

Firstly add the dependency in build.gradle.

implementation “com.takisoft.fix:preference-v7:27.1.0.0”

In the preferences layout file, we will use checkboxes.

<PreferenceScreen xmlns:android=”http://schemas.android.com/apk/res/android”>

<CheckBoxPreference
android:key=”@string/gross_sales_key”
android:title=”@string/gross_sales”
android:defaultValue=”true” />

<CheckBoxPreference
android:key=”@string/net_sales_key”
android:title=”@string/net_sales”
android:defaultValue=”false” />
</PreferenceScreen>

We will create SalesDataSettings class which extends PreferenceFragmentCompat and override onCreatePreferenceFix method. We will request PreferenceManager and set SharedPreferencesName. The manager will be used to store and retrieve key-value pairs from SharedPreferences. Using setPreferencesFromResource we will attach the layout file to the fragment.

PreferenceManager manager = getPreferenceManager();
manager.setSharedPreferencesName(Constants.FOSS_PREFS);

setPreferencesFromResource(R.xml.sales_data_display, rootKey);

We are using CheckBox Preferences and modifying their behaviour to work as a Radio Preference List because Radio reference is not provided by Android. We are initializing two checkboxes and attaching a preference listener to unset all other checkboxes which one is selected.

CheckBoxPreference netSales = (CheckBoxPreference) findPreference(NET_SALES);
CheckBoxPreference grossSales = (CheckBoxPreference) findPreference(GROSS_SALES);

Preference.OnPreferenceChangeListener listener = (preference, newValue) -> {
String key = preference.getKey();

switch (key) {
case GROSS_SALES:
netSales.setChecked(false);
break;
case NET_SALES:
grossSales.setChecked(false);
break;
default:
break;
}
return (Boolean) newValue;
};

netSales.setOnPreferenceChangeListener(listener);
grossSales.setOnPreferenceChangeListener(listener);

We can load SalesDataDisplay Fragment class when a preference button is clicked using fragment transactions as shown below.

findPreference(getString(R.string.sales_data_display_key)).setOnPreferenceClickListener(preference -> {
getFragmentManager().beginTransaction()
.replace(R.id.fragment_container, SalesDataSettings.newInstance())
.addToBackStack(null)
.commit();
return true;
});

References

  1. Shared Preferences Documentation https://developer.android.com/reference/android/content/SharedPreferences
  2. Gericop Takisoft Android-Support-Preference-V7-Fix https://github.com/Gericop/Android-Support-Preference-V7-Fix
  3. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
Continue Reading

Performing Database Migrations using DbFlow

In Open Event Organizer Android App we decided to add database migrations for every change in database while development. Two of the reasons behind this –

  1. The users have some version of the app installed and then during development the developers had to modify the database, let’s say, add a table or modify existing ones. This makes the existing database incompatible with the new app. On adding database migration the upgradation of database takes place automatically and prevent the situation of reinstalling the app.
  2. Migrations makes it possible to rollback or upgrade to some particular database state. Thus, help in debugging certain changes in isolation.

Let’s discuss the implementation details. Consider the scenario when a user adds a new table named SpeakersCall. For creating migration for this change we need to add migration class inside OrgaDatabase class annotated with @Database. We will break it down to look closely at each step.

  1. Use @Migration annotation in DbFlow library and specify the new database version (by incrementing the existing version) and the database class.
  2. Extend BaseMigration and override migrate method.
  3. The logic used inside the migrate method can be different for different tasks. In the present case we first need to delete any existing table (if exists) with the name SpeakersCall and then recreate that table in database.
  4. Create an array of java classes which are created/modified.
  5. We wrap the SQL query inside a Database Wrapper class which prevents it from running recursively.
  6. FlowManager uses reflection to look up and construct the generated database holder class used in defining the structure for all databases used in this application. So we will getModelAdapter for the class to be recreated and use creation query returned by Model Adapter and execute it.
@Migration(version = 15, database = OrgaDatabase.class)
public static class MigrationTo15 extends BaseMigration {

@Override
public void migrate(@NonNull DatabaseWrapper databaseWrapper) {
Timber.d(“Running migration for DB version 14”);

Class<?>[] recreated = new Class[] {SpeakersCall.class};

for (Class<?> recreate: recreated) {
ModelAdapter modelAdapter = FlowManager.getModelAdapter(recreate);
databaseWrapper.execSQL(DROP_TABLE + modelAdapter.getTableName());
databaseWrapper.execSQL(modelAdapter.getCreationQuery());
}
}
}

Similarly, we can write migration for changing a column of table(s).

Continue Reading

Testing the ViewModels in Open Event Organizer App

In Open Event Organizer Android App we follow Test Driven Development Approach which means the features added in the app are tested thoroughly by unit tests. More tests would ensure better code coverage and fewer bugs. This blog explains how to write tests for Viewmodel class in MVVM architecture.

Specifications

We will use JUnit4 to write unit tests and Mockito for creating mocks. The OrdersViewModel class returns the list of Order objects to the Fragment class. The objects are requested from OrderRepository class which fetches them from Network and Database. We will create a mock of OrderRepository class since it is out of context and contain logic that doesn’t depend on Orders Respository. Below is the getOrders method that we will test.

 public LiveData<List<Order>> getOrders(long id, boolean reload) {
if (ordersLiveData.getValue() != null && !reload)
return ordersLiveData;

compositeDisposable.add(orderRepository.getOrders(id, reload)
.compose(dispose(compositeDisposable))
.doOnSubscribe(disposable -> progress.setValue(true))
.doFinally(() -> progress.setValue(false))
.toList()
.subscribe(ordersLiveData::setValue,
throwable -> error.setValue(ErrorUtils.getMessage(throwable).toString())));

return ordersLiveData;
}

We will be using InstantTaskExecutorRule() which is a JUnit Test Rule that swaps the background executor used by the Architecture Components with a different one which executes each task synchronously. We will use setUp() method to load the RxJavaPlugins, RxAndroid plugins and reset them in tearDown method which will ensure each test runs independently from the other and avoid memory leaks. After doing this initialization and basic setup for tests we can begin code the method shouldLoadOrdersSuccessfuly() to test the getOrders method present in ViewModel class. Let’s see the step by step approach.

  1. Use Mockito.when to return Observables one by one from ORDERS_LIST whenever the method getOrders of the mock orderRepository is called.
  2. We will use Mockito.InOrder and pass orders, orderRepository and progress to check if they are called in a particular order.
  3. We will use .observeForever method to observe on LiveData objects and add a ArrayList on change.
  4. Finally, we will test and verify if the methods are called in order.
@Test
public void shouldLoadOrdersSuccessfully() {
when(orderRepository.getOrders(EVENT_ID, false))
.thenReturn(Observable.fromIterable(ORDERS_LIST));

InOrder inOrder = Mockito.inOrder(orders, orderRepository, progress);

ordersViewModel.getProgress().observeForever(progress);

orders.onChanged(new ArrayList<>());

ordersViewModel.getOrders(EVENT_ID, false);

inOrder.verify(orders).onChanged(new ArrayList<>());
inOrder.verify(orderRepository).getOrders(EVENT_ID, false);
inOrder.verify(progress).onChanged(true);
inOrder.verify(progress).onChanged(false);
}

Similar approach can be followed for writing tests to check other behaviour of the ViewModel.

References

  1. Official Documentation for testing. https://developer.android.com/reference/android/arch/core/executor/testing/InstantTaskExecutorRule
  2. Official Documentation for JUnit.  https://junit.org/junit4/
  3. Official documentation for Mockito.  http://site.mockito.org/
  4. Open Event Organizer App codebase.  https://github.com/fossasia/open-event-orga-app
Continue Reading

Swipe to Check In/Out in Open Event Organizer App

Open Event Organizer App didn’t provide any option for the Event Organizer to view the list of Attendees present under an Order and check them in/out the event. Therefore, we designed a system such that the Organizer can just swipe the attendee present under an order to check them in or out. In this blog post, I will discuss how we implemented this functionality in Open Event Organizer App without using any third party libraries.

Specifications

We will create a separate class SwipeController.java which extends ItemTouchHelper.SimpleCallback and provide the swiping functionalities to our plain old recyclerview. We will call the super constructor with ItemTouchHelper.LEFT and ItemTouchHelper.RIGHT as arguments to provide left as well as right movements in each recyclerview list item. The bitmaps and paint object initialized here will be used later in onDraw.

public SwipeController(OrderDetailViewModel orderDetailViewModel, OrderAttendeesAdapter orderAttendeesAdapter, Context context) {
super(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);
this.orderDetailViewModel = orderDetailViewModel;
this.orderAttendeesAdapter = orderAttendeesAdapter;

closeIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.close);
doneIcon = BitmapFactory.decodeResource(context.getResources(), R.drawable.done);

paintGreen.setColor(context.getResources().getColor(R.color.light_green_500));
paintRed.setColor(context.getResources().getColor(R.color.red_500));
}

Next, we will override getMovementFlags method. This method decides the allowed movement directions for each recyclerview item. The deciding logic is that, if an attendee is checked in then the allowed movement is left to check out and if an attendee is checked out then the allowed movement is right to check in. If neither of the above case, then both movements are allowed.

@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = 0;

If (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition()) == null)
makeMovementFlags(dragFlags, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT);

if (orderDetailViewModel.getCheckedInStatus(

viewHolder.getAdapterPosition())) {
return makeMovementFlags(dragFlags, ItemTouchHelper.LEFT);
} else {
return makeMovementFlags(dragFlags, ItemTouchHelper.RIGHT);
}
}

The onChildDraw method involves the code doing actual drawing. The variables used in code are discussed below.

  1. ActionState – Checks the state of the recycler view item. We proceed with the below logic if the item is being swiped.
  2. dX – The distance by which the item is swiped. Positive for left and negative for right.
  3. Background – Background of the viewholder. Rectangular in shape and dimensions changed with change in dX.
  4. IconDest – Calculates the position where the icons (close icon or done icon) is placed in canvas
  5. Canvas – Java Canvas on which the drawing is done. We set the background and draw the bitmaps on their location in canvas.
@Override
public void onChildDraw(Canvas canvas, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
View itemView = viewHolder.itemView;
float height = (float) itemView.getBottom() – (float) itemView.getTop();
float width = height / 3;
RectF background;
Paint paint;
Bitmap icon;
RectF iconDest;

if (dX > 0) {
background = new RectF((float) itemView.getLeft(), (float) itemView.getTop(), dX,
(float) itemView.getBottom());
paint = paintGreen;
icon = doneIcon;
iconDest = new RectF((float) itemView.getLeft() + width,
(float) itemView.getTop() + width, (float) itemView.getLeft() + 2 * width,
(float) itemView.getBottom() – width);
} else {
background = new RectF((float) itemView.getRight() + dX, (float) itemView.getTop(),
(float) itemView.getRight(), (float) itemView.getBottom());
paint = paintRed;
icon = closeIcon;
iconDest = new RectF((float) itemView.getRight() – 2 * width,
(float) itemView.getTop() + width, (float) itemView.getRight() – width,
(float) itemView.getBottom() – width);
}

canvas.drawRect(background, paint);
canvas.drawBitmap(icon, null, iconDest, paint);
}
super.onChildDraw(canvas, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}

Now after the item is swiped out or in, we need to restore its original state again. For this we override the onSwiped method and call notifyItemChanged(). Also, the changes in UI (showing green side strip for checked in and red side strip for checked out) are done by. We call the toggleCheckin() method in ViewModel to toggle the checking status of the attendee in server and local database.

@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();

orderDetailViewModel.toggleCheckIn(position);
orderAttendeesAdapter.notifyItemChanged(position);
}

Last but not the least, we will override the onMove method to return false. Since we are not supporting drag and drop features therefore this method will never be called.

@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
return false;
}

Resources

  1. Codebase for Open Event Organizer App https://github.com/fossasia/open-event-orga-app
  2. Official documentation for ItemTouchHelper.SimpleCallback https://developer.android.com/reference/android/support/v7/widget/helper/ItemTouchHelper.SimpleCallback
Continue Reading

Using Contextual Action Bar as Selection Toolbar

In Open Event Android Organizer App we were using Android Action Bar as Toolbar to allow deleting and editing of the list items but this implementation was buggy and not upto the industry standards. So we decided to implement this using Contextual Action Bar as a selection toolbar.

Specifications

We are using MVP Architecture so there will be Fragment class to interact with the UI and Presenter class to handle all the business logic and interact with network layer.

The SessionsFragment is the Fragment class for displaying List of Sessions to the user. We can long press any item to select it, entering into contextual action bar mode. Using this we will be able to select multiple items from list by a click and delete/edit them from toolbar.

To enter in Contextual Action Bar Mode use

ActionMode actionMode = getActivity().startActionMode(actionCallback);

To exit Contextual Action Bar Mode use

if (actionMode != null)
actionMode.finish();

We will implement Action.Callback interface in out fragment class. It’s contains three method declarations –

  1. onCreateActionMode – This method is executed when the contextual action bar is  created. We will inflate the toolbar menu using MenuInflator and set new status bar color.
  2. onPrepareActionMode – This method is executed after onCreateActionMode and also whenever the Contextual Action Bar is invalidated so we will set the visibility of delete button in toolbar here and return true to ignify that we have made some changes.
  3. onActionItemClicked – This method is executed whenever a menu item in toolbar is clicked. We will call the function showDeleteDialog.
  4. onDestroyActionMode – This method is executed whenever user leaves the contextual action bar mode by pressing back button on toolbar or back button in keyboard etc. Here we will make unselect all the selected list items and the status bar color.
public ActionMode.Callback actionCallback = new ActionMode.Callback() {
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.menu_sessions, menu);if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
statusBarColor = getActivity().getWindow().getStatusBarColor();
getActivity().getWindow().setStatusBarColor(getResources().getColor(R.color.color_top_surface));
}
return true;
}@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
MenuItem menuItemDelete = menu.findItem(R.id.del);
MenuItem menuItemEdit = menu.findItem(R.id.edit);
menuItemEdit.setVisible(editMode);
menuItemDelete.setVisible(deletingMode);
return true;
}

@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.del:
showDeleteDialog();
break;
case R.id.edit:
getPresenter().updateSession();
break;
default:
return false;
}
return false;
}

@Override
public void onDestroyActionMode(ActionMode mode) {
actionMode = null;
getPresenter().resetToolbarToDefaultState();
getPresenter().unselectSessionList();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
//return to “old” color of status bar
getActivity().getWindow().setStatusBarColor(statusBarColor);
}
}
};

The showDeleteDialog method shows a AlertDialog to the user before deleting the selected items in the list. User can click on Ok to delete the items or on Cancel to close the dialog.

public void showDeleteDialog() {
if (deleteDialog == null)
deleteDialog = new AlertDialog.Builder(context)
.setTitle(R.string.delete)
.setMessage(String.format(getString(R.string.delete_confirmation_message),
getString(R.string.session)))
.setPositiveButton(R.string.ok, (dialog, which) -> {
getPresenter().deleteSelectedSessions();
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
dialog.dismiss();
})
.create();deleteDialog.show();
}

When user presses Ok then we call the method deleteSelectedSessions in presenter to delete the sessions.

Resources

  1. Offical doumentation for Contextual Action Bar by Google https://developer.android.com/guide/topics/ui/menus#CAB
  2. Official Documentation for Alert Dialog by Google https://developer.android.com/guide/topics/ui/dialogs
  3. Codebase for Open Event OrganIzer App on Github https://github.com/fossasia/open-event-orga-app/
Continue Reading

Adding a Countdown to Orders Page in Open Event Frontend

This blog post will illustrate how you can add a countdown to orders page which on finishing expires the ticket in Open Event. In Open Event we allow some predefined time for users to fill in their details and once the time expires order gets expired and tickets get released. Users can order the tickets again if they want.

We start by adding a createdAt field to orders model so that we can keep track of remaining time. To calculate the time when the order should expire we add predefined time in which user should fill their details to createdAt time. In this way, we get the time when the order will expire.

So now to calculate the remaining time we just subtract the expiring time from current time. And then we render this data into the template. We define getRemainingTime property in our template and fetch the data for that property with help of javascript.

To see the template code visit this link.

The challenge here is to update the time remaining after every second. For this, we take the help of ember runloop. The run.later() function of ember runloop helps us to calculate the property after every second and set it. Code for setting the remaining time with the help of javascript is given below.

// app/components/forms/orders/order-form.js

getRemainingTime: computed('data', function() {
    let willExpireAt = this.get('data.createdAt').add(10, 'minutes');
    this.timer(willExpireAt, this.get('data.identifier'));
  }),

  timer(willExpireAt, orderIdentifier) {
    run.later(() => {
      let currentTime = moment();
      let diff = moment.duration(willExpireAt.diff(currentTime));
      this.set('getRemainingTime', moment.utc(diff.asMilliseconds()).format('mm:ss'));
      if (diff > 0) {
        this.timer(willExpireAt, orderIdentifier);
      } else {
        this.get('data').reload();
        this.get('router').transitionTo('orders.expired', orderIdentifier);
      }
    }, 1000);
  }

 

As given in the code. We pass expiring time and order’s model instance to the timer function. Timer function calculates the remaining time and sets it to getRemainingTime property of template. Timer function runs after every second with the help of run.later() function of ember runloop. To format the remaining time into MM:SS we take help of moment.js library and format the data accordingly.

Once the remaining time is less than zero (time expires) we reload the model and transition current route to expired route. We do not have to set order status as expired inside the FE. Server sets the order as expired after the predefined time. So we just reload the model from the server and we get the updated status of the order.

Resources:
Continue Reading

Adding Helper and Adding Action Buttons to Orders List in Open Event Frontend

This blog post will illustrate how to add a helper to orders list and add action buttons to orders list to delete and cancel an order in Open Event Frontend. To cancel or delete an order item we need to communicate to the server. The API endpoints to which we communicate are:

  • PATCH        /v1/orders/{order_identifier}
  • DELETE    /v1/orders/{orders_identifier}

We will define the action buttons in ui-table component of open event frontend. We will use the cell-actions file to define the cell buttons that will be present in cell-actions column. The following handlebars code will render the buttons on website.

//components/ui-table/cell/events/view/tickets/orders/cell-actions.hbs

class="ui vertical compact basic buttons"> {{#if (and (not-eq record.status 'cancelled') (can-modify-order record))}} {{#ui-popup content=(t 'Cancel order') click=(action (confirm (t 'Are you sure you would like to cancel this Order?') (action cancelOrder record))) class='ui icon button' position='left center'}} class="delete icon"> {{/ui-popup}} {{/if}} {{#if (can-modify-order record)}} {{#ui-popup content=(t 'Delete order') click=(action (confirm (t 'Are you sure you would like to delete this Order?') (action deleteOrder record))) class='ui icon button' position='left center'}} class="trash icon"> {{/ui-popup}} {{/if}} {{#ui-popup content=(t 'Resend order confirmation') class='ui icon button' position='left center'}} class="mail outline icon"> {{/ui-popup}}

 

In above code you can see two things. First is can-modify-order which is a helper. Helper is used to simplify conditional logics which cannot be easily placed in handlebars. Second thing is action. There are two actions defined: cancelOrder and deleteOrder. We will see implementation of these later. First let’s see how we define can-modify-order helper.

In can-modify-order helper we want to return true or false in case we want cancel button and delete button to display or not respectively. We write the code of can-modify-order in helpers/can-modify-order.js file. When we want to get result from this helper we call it from handlebars file and pass any parameter that we want to use in helper. Code for can-modify-order helper is given below.

// helpers/can-modify-order.js

import Helper from '@ember/component/helper';

export function canModifyOrder(params) {
 let [order] = params;
 if (order.amount !== null && order.amount > 0) {
   // returns false if order is paid and completed
   return order.status !== 'completed';
 }
 // returns true for free ticket
 return true;
}

export default Helper.helper(canModifyOrder);

 

We extract the parameter and store it in order variable. We see if it satisfies our conditions we return true else false.

Now lets see how we can define actions to perform delete and cancel action on a order. We define these actions in controllers section of app. After performing suitable operation with order we call save to update modified order and destroyRecord() to delete an order. Let see the code implementation for these actions.

actions: {
   deleteOrder(order) {
     this.set('isLoading', true);
     order.destroyRecord()
       .then(() => {
         this.get('model').reload();
         this.notify.success(this.get('l10n').t('Order has been deleted successfully.'));
       })
       .catch(() => {
         this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
       })
       .finally(() => {
         this.set('isLoading', false);
       });
   },
   cancelOrder(order) {
     this.set('isLoading', true);
     order.set('status', 'cancelled');
     order.save()
       .then(() => {
         this.notify.success(this.get('l10n').t('Order has been cancelled successfully.'));
       })
       .catch(() => {
         this.notify.error(this.get('l10n').t('An unexpected error has occurred.'));
       })
       .finally(() => {
         this.set('isLoading', false);
       });
   }

 
After defining these actions, buttons in the orders list start working. In this way, we can make use of helper to simplify the conditional logic inside templates and define proper actions.

Resources:
Continue Reading
Close Menu
%d bloggers like this: