Skip to content

Commit

Permalink
Merge branch 'release/0.5.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Jaxolotl committed Jul 8, 2020
2 parents 56142fe + d3ee3f8 commit 2e94ccb
Show file tree
Hide file tree
Showing 13 changed files with 231 additions and 30 deletions.
214 changes: 209 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,211 @@
# Welcome
# About USSD Flow mock app

This is still a draft public version of an app to easily mock up USSD flows for demos.
Improvements, cleanup, instructions will come ASAP.
A Basic flow is added by default for now.
Hi there :), welcome.
This is a simple app designed for technical and non technical users. The objective is to provide a tool to wire USSD flows for visual demos very quickly and without the need to have more than a shallow JavaScript knowledge and/or a keen eye and smart hands . If you're a seasoned engineer you may understand the code flow by reading it without going through the documentation; otherwise take a look at the guide below.

[See demo flow here](src/index.html)
Improvements, cleanup and more details will come time to time.
A Basic flow is added by default for you to have a "template".

[See the demo flow here](https://jaxolotl.github.io/ussd-flow-mock/src/index.html)

## How to define a flow

- The flow system consists in a combination of simple display functions and arrays.
- There's only one mandatory display `function` named `displayHomeMenu` (see [here](#the-display-functions))
- Each display `function` should return a `render` `function` (see [here](#the-render-function))
- All subsequent display functions are optional and they're part of the flow you'll design

### Learning by doing

Theory tends to be hermetic, let's see the minimum information required and then proceed with some concrete examples based on the sample flow provided on the demo.

#### The display functions

These are the functions of your flow, they are very simple and there's only one mandatory function to define, the `displayHomeMenu`, every other function is up to you and it will depend on the flow you want to design. The rules are simple:

##### Naming convention

I propose to use the prefix "display" (e.g. `displayProducts`, `displayPets`) but it's just an arbitrary convention, you can use whatever name you want (e.g. `doTheListThing`, `x`)

##### Display functions definition

All display functions MUST return the render function. (see [here](#the-render-function) for details)

```javascript
function displayWhatever () {
return render({
content: `The whatever content`,
dataset: theWhateverDataset,
status: `the status`
})
}
```

#### The render `function`

The render `function` accepts an object argument to tells the app the following:

- what to show: `content`
- which is the data to control the next move: `dataset`
- which is the display status: `status`

##### `content` property

Any arbitrary string you want to print on the display. No particular limitations with this except for very lengthy strings not being displayed nicely; up to 20 consecutive chars with no spaces are beautifully displayed but it will depend on the viewport size.

##### `dataset` property

Two kind of datasets (Array, Function) are supported and have a specific restriction.

###### `dataset` : `Array` of objects

It must be an array of objects where each object is an item of a list of options and should contain two properties:

- `title`: A `string` with the option text
- `confirm`: a display `function` to be invoked when the option is selected

Each entry of the dataset has an implicit numeric index starting from 0, this index will be used when you select an option number on the "answer/send" action.

If you use the `datasetToList` helper (as shown below), it will return a string to be used for the content using the index as prefix for each option item. An empty entry won't be rendered allowing you to define non-consecutive options (e.g. starting from 1 instead of 0, or skipping a number, like 1,2,4,7)

```javascript
const home = [
null, // empty entry to display a menu starting from 1 instead of 0
{ title: "Request a shipment", confirm: displaySenders }, // an option with a confirm action `displaySenders` defined elsewhere
{ title: "Check shipment status" }, // an option with no confirm action, no-op
{ title: "Repeat recent jobs" }, // no-op
{ title: "Register new user or location" }, // no-op
{ title: "Help / Other services" }, // no-op
]

function displayHomeMenu {
return render({
content: `Menu
${datasetToList(home)}`,
dataset: home
})
}
```

<table>
<tr>
<th width="20%"> </th>
<th width="20%"> Home menu </th>
<th width="20%"> Answer prompt </th>
<th width="20%"> Option selection </th>
<th width="20%"> Sender menu </th>
</tr>
<tr>
<td> status </td>
<td> `options` </td>
<td> `answerStandby` </td>
<td> `answering` </td>
<td> `options` </td>
</tr>
<tr>
<td> screenshot </td>
<td><img src="docs/home_sample_01.png" /></td>
<td><img src="docs/answer_prompt.png" /></td>
<td><img src="docs/answer_choice.png" /></td>
<td><img src="docs/senders_sample.png" /></td>
</tr>
<tr>
<td>explanation</td>
<td>Default home menu is displayed</td>
<td>User clicked on the "Answer" action and the answer selection page is displayed automatically</td>
<td>User types the number 1 on the num pad of the UI corresponding to the option 1 of the home menu</td>
<td>The option 1 has a `confirm` value assigned to the `displaySenders` display function which is invoked and the
content displayed</td>
</tr>
</table>

###### `dataset` : `Function` detail

The previous flow is designed when you have a preset of options for the user to select. What if you need arbitrary actions? Let's say instead of having predefined option numbers you want to simulate entering an arbitrary value and whatever value you type the same "page" will be displayed after that.

```javascript
function displayArbitrarySender () {
return render({
content: "Enter sender phone number",
dataset: displayPickupLocations, // note! it's the reference of a function!
status: 'answerStandby' // see status property explanation
})
}

function displayPickupLocations () {
return render({
content: `Enter pickup location
${datasetToList(pickUpLocations)}`,
dataset: pickUpLocations
})
}

var locations = [
{ title: "Saved location 1" },
{ title: "Saved location 2" },
{ title: "Saved location 3" },
{ title: "Find public location near me" },
{ title: "Enter a location code" },
{ title: "MTN branch code" },
]

var pickUpLocations = locations.map(item => { return { ...item, confirm: displayReceivers } });
```

<table>
<tr>
<th width="25%"> </th>
<th width="25%"> Enter number </th>
<th width="25%"> Typing number </th>
<th width="25%"> confirm action invoked </th>
</tr>
<tr>
<td> status </td>
<td> `answerStandby` </td>
<td> `answering` </td>
<td> `options` </td>
</tr>
<tr>
<td> screenshot </td>
<td><img src="docs/enter_sender_number.png" /></td>
<td><img src="docs/typing_sender_number.png" /></td>
<td><img src="docs/pickup_location.png" /></td>
</tr>
<tr>
<td>explanation</td>
<td>Enter sender page is displayed</td>
<td>User types an arbitrary value and hits "Send"</td>
<td>The dataset is defined as a function so the `displayPickupLocations` is invoked and the content displayed</td>
</tr>
</table>

##### `status` property

The status property control how the content will be displayed and which actions are available. There are 3 values available (`options`, `answerStandby`, `final`) for you to use (of course there are more but they are for internal purpose, I wouldn't recommend you to use them)

<table>
<tr>
<th width="33%"> `options`</th>
<th width="33%"> `answerStandby` </th>
<th width="33%"> `final` </th>
</tr>
<td> <img src="docs/home_sample_01.png" /> </td>
<td><img src="docs/enter_sender_number.png" /></td>
<td><img src="docs/thanks.png" /></td>
</tr>
<tr>
<td>This is the default status if you don't pass along an explicit one, it'll infer you're presenting a list of options each one of them with a numeric value to be selected.</td>
<td>This is for an arbitrary value to be sent</td>
<td>This is for a final page, no other actions can be performed except for "Quit" which will take you back to the home page</td>
</tr>
</table>

## Writing your own flow

You can replace the `sample_flow.js` file referent in the [index.html](src/index.html) at line 75 ( see the `<!-- REPLACE THE SAMPLE FLOW FILE WITH YOURS-->` comment) with another file, OR you can edit the [sample_flow.js](src/assets/sample_flow.js) file with your own code, is up to you.

## Show me some action please!

[See it in action here](https://jaxolotl.github.io/ussd-flow-mock/src/index.html)

PS. If something's not working or you have any suggestion, please go to the repo and file an issue so I can track it. Thanks in advance.
Binary file added docs/answer_choice.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/answer_prompt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/enter_sender_number.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/home_sample_01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/pickup_location.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/senders_sample.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/thanks.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/typing_sender_number.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ussd-flow-mock",
"version": "0.3.0",
"version": "0.5.0",
"description": "Minimal JavaScript/HTML/CSS app to mock up USSD flows for demos",
"repository": {
"type": "git",
Expand Down
2 changes: 1 addition & 1 deletion src/assets/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const showTransition = async ({ transitionSpeed = 1000 } = {}) => {
return;
}

const render = async ({ content = '', dataset = [], status = DEFAULT_STATUS } = {}) => {
const render = async ({ content = '', dataset, status = DEFAULT_STATUS } = {}) => {

await showTransition();

Expand Down
39 changes: 17 additions & 22 deletions src/assets/sample_flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,79 +3,78 @@
* FLOW DEFINITION
*/


const displayHomeMenu = () => {
function displayHomeMenu () {
return render({
content: `Menu
${datasetToList(home)}`,
dataset: home
})
}

const displaySenders = () => {
function displaySenders () {
return render({
content: `Enter sender
${datasetToList(senders)}`,
dataset: senders
})
}

const displayReceivers = () => {
function displayReceivers() {
return render({
content: `Enter receiver
${datasetToList(receivers)}`,
dataset: receivers
})
}

const displayPickupLocations = () => {
function displayPickupLocations () {
return render({
content: `Enter pickup location
${datasetToList(pickUpLocations)}`,
dataset: pickUpLocations
})
}

const displayDropOffLocations = () => {
function displayDropOffLocations () {
return render({
content: `Enter drop off location
${datasetToList(dropOffLocations)}`,
dataset: dropOffLocations
})
}

const displayGoods = () => {
function displayGoods () {
return render({
content: `Select what's being shipped
${datasetToList(goods)}`,
dataset: goods
})
}

const displayThanks = () => {
function displayThanks () {
return render({
content: "Thank you for requesting a shipment",
status: 'final'
})
}

const displayArbitrarySender = () => {
function displayArbitrarySender () {
return render({
content: "Enter sender phone number",
dataset: displayPickupLocations,
status: 'answerStandby'
})
}

const displayArbitraryReceiver = () => {
function displayArbitraryReceiver () {
return render({
content: "Enter receiver phone number",
dataset: displayDropOffLocations,
status: 'answerStandby'
})
}

const home = [
var home = [
null,
{ title: "Request a shipment", confirm: displaySenders },
{ title: "Check shipment status" },
Expand All @@ -84,7 +83,7 @@ const home = [
{ title: "Help / Other services" },
]

const people = [
var people = [
{ title: "Me", },
{ title: "Doreen Gashuga" },
{ title: "Pacific Tuyishime" },
Expand All @@ -93,17 +92,17 @@ const people = [
{ title: "Manuel Arzuah" },
]

const senders = [
var senders = [
...people.map(item => { return { ...item, confirm: displayPickupLocations } }),
{ title: "Someone else", confirm: displayArbitrarySender },
]

const receivers = [
var receivers = [
...people.map(item => { return { ...item, confirm: displayDropOffLocations } }),
{ title: "Someone else", confirm: displayArbitraryReceiver },
]

const locations = [
var locations = [
{ title: "Saved location 1" },
{ title: "Saved location 2" },
{ title: "Saved location 3" },
Expand All @@ -112,15 +111,11 @@ const locations = [
{ title: "MTN branch code" },
]

const pickUpLocations = [
...locations.map(item => { return { ...item, confirm: displayReceivers } }),
]
var pickUpLocations = locations.map(item => { return { ...item, confirm: displayReceivers } });

const dropOffLocations = [
...locations.map(item => { return { ...item, confirm: displayGoods } }),
]
var dropOffLocations = locations.map(item => { return { ...item, confirm: displayGoods } });

const goods = [
var goods = [
{ title: "Loose goods", confirm: displayThanks, },
{ title: "Packaged goods", confirm: displayThanks, },
{ title: "Livestock", confirm: displayThanks, },
Expand Down
4 changes: 3 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"
integrity="sha256-4+XzXVhsDmqanXGHaHvgh1gMQKX40OUvDEBTu8JcmNs=" crossorigin="anonymous"></script>

<script src="assets/sample_flow.js"></script>
<script src="assets/index.js"></script>

<!-- REPLACE THE SAMPLE FLOW FILE WITH YOURS-->
<script src="assets/sample_flow.js"></script>
</body>

</html>

0 comments on commit 2e94ccb

Please sign in to comment.