Let’s Create a Coupon App [Part 2: Coupon Management and Display]

Athina Hadjichristodoulou
The Web Tub
Published in
7 min readSep 14, 2023
Source: https://www.netdesigns.gr/wp-content/uploads/set-coupons.jpg

If you’re considering using Monaca to develop an application, you might find it challenging to come up with ideas when the time comes. Additionally, if you’re planning to build a complex app from scratch, it can be overwhelming to know where to begin.

That’s why in this article, we will guide you through the process of developing a simple app. As a first step, we encourage you to take on the challenge.

In this article, we will develop a coupon app using NCMB (NIFCLOUD mobile backend). We will create and manage coupons on the same app, and display coupons for users. In the first half of the project, we have already worked on the specifications of the app and the implementation of authentication. In the second half, we will create the process of managing and displaying coupon data.

Creation of Management Screen

The following operations are performed on the coupon management screen

  • Creating and editing new coupons
  • Viewing a list of coupons
  • Deleting coupons

Create and Edit Coupons

Coupon creation or edit screen

The process of creating and editing coupons is actually quite similar. If a coupon is selected on the list screen, it is considered as editing, and if no coupon is selected, it is considered as creating a new one. First, on the coupon form screen located at pages/admin/form.html, check whether the coupon data has been sent from the previous screen.

export default (props, { $f7, $on, $f7router }) => {
// Check if coupon information has been sent from the previous screen
const Coupon = ncmb.DataStore('Coupon');
const coupon = props.coupon || new Coupon;
}

Processing when the screen is displayed (for updating)

When updating coupon information, a picture (file name) of the coupon may be specified as previous information. In this case, the image data is downloaded from NCMB and displayed as a preview. At this time, if file downloading is performed using async/await, the screen will freeze during downloading, therefore the process is performed using Promise.

// Events on screen display
$on('pageBeforeIn', (e, page) => {
if (coupon.fileName) {
// Download photos from NCMB
ncmb.File.download(coupon.fileName, 'blob')
.then(async file => {
// Expand the data received in Blob format into a dataURI (function is in app.js)
const blob = await photoReader(file);
// Display in .preview
$f7.$el.find('#image').attr('src', blob);
})
}

// Convert blob format to URI data
const photoReader = async (file) =>{
let reader = new FileReader();
reader.readAsDataURL(file);
await new Promise(resolve => reader.onload = function(){ resolve() })
return reader.result;
}

New Save/ Update Process

New coupon data is saved and updated when the button is pressed. This calls the save function.

// Function is called when the Save btn is clicked
const save = async () => {}

The following processing is contained within the save function. First, the input data is applied to the NCMB data store object.

// Objectify input values
const params = serializeForm('form#new');
for (const key in params) {
coupon.set(key, params[key]);
}
// Converting date
for (const date of ['startDate', 'endDate']) {
if (coupon[date]) {
coupon.set(date, new Date(coupon[date]));
}
}

In addition, create an ACL (access control). In this case, only the administrator user is allowed to update, while other users are allowed to read only.

// Set ACL (access privilages)
const user = ncmb.User.getCurrentUser();
const acl = new ncmb.Acl();
acl
.setPublicReadAccess(true) // everyone can read
.setUserWriteAccess(user, true); // only the admin can edit

Next, if a file is specified, upload its contents to the file store.

const src = $f7.$el.find('.preview').attr('src');
if (src.indexOf('data:') > -1) {
// With file
const blob = await (await fetch(src)).blob();
// Generate file names without duplication
const fileName = (new Date()).getTime() + '.' + blob.type.split('/')[1];
// Upload the file
await ncmb.File.upload(fileName, blob, acl);
// Set file name
coupon.set('fileName', fileName)
}

Then, the presence or absence of objectId (unique key in the data store) is used to distinguish between updating and new storage. The data store is the database function in NCMB.

// Apply ACL
coupon.set('acl', acl);
// Separate new save and update processing based on the presence or absence of objectId (unique key)
if (coupon.objectId) {
await coupon.update(); // update
} else {
await coupon.save(); // create a new coupon
}

When you are done saving, return to the previous screen (list screen).

// Navigate back 
$f7router.back();

Listing of Coupons

Coupon listing screen

The coupon view of the administration screen is the file pages/admin/list.html. The following processes are performed when the screen is displayed.

  • Acquire coupon data
  • Display the acquired coupons in a list
  • Bind an event to the drawn coupon information

The variable coupons, which contains the list of coupons, is defined outside the scope because it is used by other functions.

let coupons;
// Even executed before screen display
$on('pageBeforeIn', async (e, page) => {
// Coupon data acquisition
coupons = await getCoupons();
// List acquired coupons
showCoupons();
// Bind events to coupon information drawn
bindCoupons();
});

Coupon Data Acquisition

Coupons are retrieved by searching the NCMB data store. No filtering criteria are attached, only the order and number of coupons are specified.

const getCoupons = () => {
const Coupon = ncmb.DataStore('Coupon');
return Coupon
.limit(100)
.order('createDate')
.fetchAll();
}

List Acquired Coupons

Coupons are drawn in the .coupons table tbody; the date format is specified in dayjs, but other than that it is simply an output to a table.

// List acquired coupons
const showCoupons = () => {
$f7.$el.find('.coupons table tbody').html(coupons.map(c => `
<tr data-object-id="${c.objectId}">
<td class="label-cell">${c.couponName}</td>
<td class="label-cell">${c.discount}</td>
<td class="label-cell">${dayjs(c.startDate.iso).format('M/D')}</td>
<td class="label-cell">${dayjs(c.endDate.iso).format('M/D')}</td>
</tr>`).join(''));
}

Bind events to coupon information drawn

The event is bound to the data drawn above. Once the tapped data has been narrowed down, the data is sent to the edit screen admin/pages/form.html. The process beyond that has been introduced already, so it is omitted here.

// Bind events to coupon information drawn
const bindCoupons = () => {
// Event when tapping data in a row
$f7.$el.find('.coupons table tbody tr').on('click', e => {
// Obtain objectId, a unique key
const objectId = $(e.target).parents('tr').data('object-id');
// Retrieve data to be edited
coupon = coupons.find(c => c.objectId === objectId);
// Send to edit screen
$f7router.navigate('/admin/form/', {
props: { coupon },
});
});
}

Delete Coupon

Deleting a coupon is performed on the form screen pages/admin/form.html. After the “Delete” button is pressed, a confirmation dialog is displayed, and if the user presses OK, the coupon is deleted.

// Process to delete a coupon
const deleteCoupon = async (e) => {
// Confirmation dialog
await new Promise((res, rej) => {
app.dialog.confirm('Are you sure you want to delete the coupon?', res, rej);
});
// Deletion
await coupon.delete();
// Return to previous screen
$f7router.back();
};

Creating User Screens

The screen for the user consists of the following two processes:

  • Display of a list of valid coupons
  • Display of details when a coupon is selected.

This is implemented in pages/list.html only.

List of Valid Coupons

Coupon listing screen for users

Coupon data is retrieved in the same way as when they are retrieved for the administration screen. This time only valid coupons (past the start date and prior to the end date) are included.

// Function to retrieve valid coupons
const getCoupons = async () => {
// Target data store
const Coupon = ncmb.DataStore('Coupon');
return Coupon
.limit(100) // 100 coupons is the limit
.lessThanOrEqualTo('startDate', new Date) // start date is before or equal to today
.greaterThanOrEqualTo('endDate', new Date) // end date is later or equal to today
.order('createDate')
.fetchAll();
};

The acquired data is then rendered in a card element in Framework 7.

const displayCoupons = () => {
$f7.$el.find('.use-coupons').html(coupons.map(c => `
<div class="card card-outline-md demo-card-header-pic" data-object-id="${c.objectId}">
<div style="color:${c.letterColor};font-size:20px;font-weight:bold;height:140px;" valign="bottom"
class="card-header photo" data-file-name="${c.fileName}">${c.couponName}</div>
<div class="card-content card-content-padding" style="background-color: ${c.backgroundColor};color: ${c.letterColor}">
<p class="date"> Valid until ${dayjs(c.endDate.iso).format('YYYY/M/D')}!</p>
<p>Discount: ${c.discount}%</p>
</div>
<div class="card-footer">
<a href="#" class="show-coupon link">Use</a>
</div>
</div>
`));
displayImage();
}

In addition, photos are acquired asynchronously and reflected on the screen.

const displayImage = () => {
$f7.$el.find('.use-coupons .photo').forEach(ele => {
const fileName = $(ele).data('file-name');
ncmb.File.download(fileName, 'blob')
.then(async (data) => {
const blob = await photoReader(data);
$(ele).css({
"background-image": `url('${blob}')`
})
});
});
}

View Details When you select a coupon

Set a click event for all the displayed coupons.

$f7.$el.find('.show-coupon').on('click', (e) => {}

First, the targeted coupon is identified.

const objectId = $(e.target).parents('.card').data('object-id');
const coupon = coupons.find(c => c.objectId === objectId);

Then change the content of the action modal to be displayed.

$('.sheet-modal-inner h2').text(coupon.couponName);
$('.sheet-modal-inner strong').text(coupon.discount + "% discount");
JsBarcode("#barcode", coupon.objectId);
new QRCode(document.getElementById("qrcode"), {
text: coupon.objectId,
width: 50,
height: 50,
colorDark: "#000000",
colorLight: "#ffffff",
correctLevel: QRCode.CorrectLevel.H
});

Then display the action sheet and you are done.

app.sheet.open('.coupon');

Summary

In this tutorial, we covered CRUD operations for coupons and implemented filtering of displayed items on the user interface. Additionally, by using Framework7’s action sheet, we achieved more interactive displays. QR codes and barcodes can also be easily implemented with the help of libraries.

With Monaca, you can easily develop apps like O2O (online-to-offline). We encourage you to make the most of Framework7 and Monaca for your app development projects.

You can find the project here.

--

--