/ Development  

Building a custom notification icon in runtime

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

Oh man, how to start on this blog post!? “Notifications”! We want it all, but the AppWorks runtime is not provided with this feature out of the box. At least, not for now; This can always change somewhere in the future!? 🤔 Let’s not wait for it but come in action with our craftsmanship! I normally ask myself questions on how to implement such feature, but…


Let get right into it…

…and introduce an entity with name ‘Notification’ with this set of properties:

Label Name Type Notes
Trigger type ntf_trigger_type Enum text Static values of ‘Event‘, ‘Time’, and ‘Condition’
Message ntf_message text
Is read ntf_is_read Boolean ‘true’ or ‘false

Make it all nice and publishable, so you can create instances in runtime by hand (for now).

For this post, we create instances of the notification entity manually, but in the end (out of scope for this post) we would like to automate this. So, notification instances will be created based on events like this:

  • On 'project' instance creation
  • When a 'task' is out of its due date
  • When the budget of a 'project' instance is exceeding its limitation
  • On any entity instance update where the owner is a certain user

I know…These are advanced conditional events managed via ‘Rule’ BBs triggering BPMs, but it should be possible with an interesting ‘Notification configuration’ entity model. Check the extension thoughts in the end of this post!

Let’s continue our post; Notifications are user related, so we connect them with a ‘toOne’ relation to the ‘User’ “identity entity” of the platform. I also expose the ‘Web service’ BB on this entity with all CRUD operations enabled (just for this demo); this includes the relationship operations. Next to these operations, I created a new ‘NotificationsByUserId’ operation for later usage which includes an ‘Integer’ type of parameter which (later on) equals the identity id of the related ‘toOne’ user…Continue the grind for this one!

bell_001

You know the drill on that last ‘Web service’ BB task…Yes, it requires a service container of the ‘Application Server’ from the ‘System Resource Manager’ artifact! Comment me if you don’t have a clue what I’m talking about. Ohhhh…Don’t forget to add that relationship to the ‘Create’ form so we ‘see’ what we’re doing in runtime. We can always remove it afterwards.

bell_002

Also, update the ‘All notifications’ list BB with the related user entity information (the ‘UserId’ and identity ‘Id’). Just like this:

bell_003

We also save the entity nicely in the ‘entities’ folder of our project…correct? I love you too! 😘

Now, let’s publish it all and create some instances of the notification entity. Make sure to use some variety of data with also other related user accounts. The next step is to assess our ‘NotificationsByUserId’ webservice operation!

bell_004

The ‘All notifications’ list will show all entity instances, but we only would like to get ‘notified’ on our own notifications! Well, we exposed a nice webservice for this which we can evaluate with the ‘Web Service Interface Explorer’ artifact. Find it and try it out with a valid ‘UserId’ as parameter:

1
2
3
4
5
6
7
<SOAP:Envelope>
<SOAP:Body>
<NotificationsByUserId>
<UserId>3</UserId>
</NotificationsByUserId>
</SOAP:Body>
</SOAP:Envelope>

This is our response which is the input for the next step where we built a custom notification ‘Bell’ icon!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<data>
<NotificationsByUserIdResponse>
<!-- first entry -->
<notification>
<notification-id>
<Id>1</Id>
<ItemId>080027c2550fa1ed8e08aaad8e923151.1</ItemId>
</notification-id>
<ntf_trigger_type>event</ntf_trigger_type>
<ntf_message>Hello words...</ntf_message>
<ntf_is_read>false</ntf_is_read>
...
<to_one_user>
<Identity-id>
<Id>3</Id>
<ItemId>F8B156E1037111E6E9CB0FBF3334FBBF.3</ItemId>
</Identity-id>
</to_one_user>
</notification>
<!-- second entry -->
<notification>
<notification-id>
<Id>2</Id>
<ItemId>080027c2550fa1ed8e08aaad8e923151.2</ItemId>
</notification-id>
<ntf_trigger_type>condition</ntf_trigger_type>
<ntf_message>Conditional message</ntf_message>
<ntf_is_read>true</ntf_is_read>
...
<to_one_user>
<Identity-id>
<Id>3</Id>
<ItemId>F8B156E1037111E6E9CB0FBF3334FBBF.3</ItemId>
</Identity-id>
</to_one_user>
</notification>
</NotificationsByUserIdResponse>
</data>

What about the ‘All notifications’ list at runtime? Well, you can secure it so only the ‘Administration’ role can access it. The end-user only needs a new filtered list for his “own” notifications! This filtering is a bit out of scope for the post as it requires a small workaround trick to make it happen. Why? Well, you need a dynamic filter which can only read a specific value not directly available to make a satisfying check. In small steps to get you a simple glimpse on this thought:

  • Create a ‘helper’ property ntf_user_id (not shown anywhere!)
  • We set the value of this property with an event type of Rule BB on the relation change ‘toOneUser’; You can name it e_on_rel_change_set_user_id. So, when you “set” a new related user for the notification it will go off!
  • The rule will start a small short-lived BPM (bpm_set_user_id) calling a ‘special’ runtime reference webservice with name GetUserDetails of ‘Method Set Notification 1.0’ which will update our ‘helper’ property with a valid value. Why this specific service? Well, it’s the only service returning a valid ‘UserId’ which we can check in the filtered list! A quick double-check BPM from my side:

bell_005

With the “consolidated” view of the message map for your reference:

bell_006

  • Our new list will have a filter like this ntf_user_id equals to $(targetUser()). The value of ‘targetUser()’ will return the current logged in UserId, so now you know why we need to do it like this as there is no other way to dynamically check a related user entity instance…Comment me if you don’t agree on this one and if you see another entry?

Nice, everything cleared out. Our notification list is ready; Time for a small customization hack to show a solid bell icon when we have an “unread” notification on our name! 😎


The bell icon

For this part of the post, I follow my own HTML5 post and end up with a structure like this in my project:

bell_007

This is the start content of the ‘playground.htm’ file:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>
<html lang="en">
<head>
<title>Notification bar</title>
<link rel="stylesheet" href="../../app/start/web/startup.css" type="text/css" />
<script src="/cordys/thirdparty/jquery/jquery.js" type="text/javascript"></script>
<script src="/cordys/html5/cordys.html5sdk.js" type="text/javascript"></script>
<script src="./../js/playground.js" type="text/javascript"></script>
</head>
<body>
<h1>Hello Notifications</h1>
</body>
</html>

This is the start content for ‘playground.js’ file:

1
2
3
$(document).ready(function() {
console.log('Hello world...');
});

Other notes:

  • Here you can find the bell SVG images; Just download them and upload them to the project folder.
  • After publication, you can check these URLs for content validation:
    1 http://192.168.56.107:8080/home/appworks_tips/nl-bos-gen-assets/html/playground.htm
    2 http://192.168.56.107:8080/home/appworks_tips/nl-bos-gen-assets/js/playground.js
    3 http://192.168.56.107:8080/home/appworks_tips/nl-bos-gen-assets/images/svg/bell-regular.svg
    4 http://192.168.56.107:8080/home/appworks_tips/nl-bos-gen-assets/images/svg/bell-solid.svg

Open the <F12> developer tools of the browser to double-check the jQuery document ready console log! Once done, we go to the next step where we create a new homepage (name it hp_main) with ‘Lists’, ‘Results’, and ‘Web Content’ panels; The last one is of height 7%. This homepage will replace the default homepage of the platform.

bell_008

This is the relative URL (including the current ‘userId’) behind the ‘Web Content’ panel: ./../../../nl-bos-gen-assets/html/playground.htm?userId={User.Identity.Id}

In runtime, you’ll see this result:

bell_009

If you’re using a regular test account (like we good low-coders do!), you need to set the runtime security on the homepage document context menu!

Next step…A simple one! Show the bell icon! It’s a small update in the body of the HTML file:

1
2
3
4
5
6
<body>
<div style="float: right; margin-right: 15px; margin-top: 10px;">
<img id="regular" src = "../images/svg/bell-regular.svg" alt="No notifications" style="width: 32px;"/>
<img id="solid" src = "../images/svg/bell-solid.svg" alt="Notifications!" style="width: 32px; display: none;"/>
</div>
</body>

Publish the file and refresh the browser; Or even better…Use an ‘Incognito’ browser where things just refresh nicer without annoying caches! The only thing left is the JavaScript implementation with this logic in mind:

  1. Get a grip of the ‘userId’ parameter sent with the homepage ‘Web Content’ panel
  2. Call our ‘NotificationsUnReadByUserId’ webservice with the ‘userId’ as parameter value
  3. On success, we can check if an unread notification is found and switch to the ‘solid’ bell icon!
  4. Finally, we want to poll (every 30 sec.) the webservice for new unread notifications…

Double check this simple and readable implementation (watch the case-sensitivity for ‘userId’!):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
$(document).ready(function() {
console.log('Hello world...');

$.urlParam = function(name) {
var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(window.location.href);
if (results == null) {
return null;
} else {
return results[1] || 0;
}
}
var userId = decodeURIComponent($.urlParam('userId'));
console.log("userId:", userId);

function handler() {
$.cordys.ajax({
method: "NotificationsByUserId",
namespace: "http://schemas/aw_tipsgeneric/notification/operations",
parameters: {
"UserId": userId
},
success: successFunction,
error: errorFunction
});

function successFunction(response) {
console.log("Response:", JSON.stringify(response));
$.each(response, function(entry, data) {
if (entry.includes("notification")) {
if (String(data.ntf_is_read).toLowerCase() == "false") {
//found unread notification
$("#regular").css("display", "none");
$("#solid").css("display", "inline");
} else {
$("#regular").css("display", "inline");
$("#solid").css("display", "none");
}
}
});
}

function errorFunction(error) {
console.log("Error:", error);
}
}

handler();
setInterval(handler, 30 * 1000);
});

This is a copy of the JSON response of my success function for reference:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"notification":{
"notification-id":{
"Id":"327681", "ItemId":"080027c2550fa1ed8caaf031231d98fc.327681"
},
"ntf_trigger_type":"event", "ntf_message":"hello", "ntf_is_read":"false",
"Title":{
"Value":"notification-327681"
},
"to_one_user":{
"Identity-id":{
"Id":"4", "ItemId":"F8B156E1037111E6E9CB0FBF3334FBBF.4"
}
}
},
"notification":{
"notification-id":{
"Id":"655361", "ItemId":"080027c2550fa1ed8caaf031231d98fc.655361"
},
"ntf_trigger_type":"event", "ntf_message":"world", "ntf_is_read":"true",
"Title":{
"Value":"notification-655361"
},
"to_one_user":{
"Identity-id":{
"Id":"3", "ItemId":"F8B156E1037111E6E9CB0FBF3334FBBF.3"
}
}
}
}

Well, looks like the trick is working…at least for my post! 😎

bell_010


Extension points

This post shows a simple demonstration of a nice notification bell, but off-course we can extend this with these features:

  • Use a nice bootstrap badge as notification “Badge” which includes a number of unread items!
  • Make the refresh poll interval configurable via the global project configuration entity.
  • Make the icon clickable, so it opens a notifications homepage or a panel layout (as modal popup!?).
  • Built a notification Entity Relation Diagram like this to make it configurable when notification instances are created. With this implementation you can create ‘Rule’ BBs starting BPMs checking the configurations and create ‘Notification’ instances based on the input. I know…It’s next level and requires BPM craftsmanship with webservice calls but could be an interesting solution for your project!

bell_011

Use your imagination and creativity!


A niceeeee “DONE” 😎 where we implemented a simple and efficient bell notification for the end-users when and ‘unread’ notification is waiting for action. It’s now all up to you to implement the extensions, so it can fit into your own solution. Have a happy crafting weekend, and I see you in a new post for next week…cheers!

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?