Thursday, January 27, 2022

Advanced email techniques (APEX Mail #9)

   




We have described the most important things about "Emails with Oracle APEX" in the previous blogs. To wrap up this series now, we would like to list a few more techniques, tips, and tricks that we have come across over time. So let's move on to the last topic number nine.

#9 Advanced email techniques

First of all, it should be mentioned that the following points are only based on experience and do not necessarily have to be implemented. But maybe it will help you to create your own email templates. The biggest problem with HTML emails is that the rules for HTML have not yet been standardized for all email clients. This sets limits and calls for creativity during development and should be tested as best as possible. Ideally with as many clients as possible.


Use HTML Tables

It is better to use a <BR> or <Table> tag instead of <P> or <SPAN>. This avoids inconsistent spacing since spacing has different margins in each email client. The safest way to have consistent spacing is to use tables. If you still need to use a <SPAN> tag, end it with a <BR> tag.


Use Inline CSS

If you're wondering why your CSS isn't working, it could be because most email clients remove the <header> of an HTML page so that external style sheets aren't rendered properly. As a solution, you can put a CSS inline with each element instead. Manage links by using an inline style for each link color. Add the colors in hexadecimal, using the longhand version #000000 instead of #000. Keep in mind that not every CSS element is supported in every email client.

In the following HTML code example, you can see how tables and inline CSS can be used:

 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
<table border="0" width="100%" cellspacing="0" cellpadding="0" style="padding: 20px 0 30px 0;">
 <tbody>
  <tr>
   <td style="color: #153643; font-family: Arial, sans-serif; font-size: 24px;"><strong>Lorem ipsum dolor sit amet!</strong></td>
  </tr>
  <tr>
   <td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 20px; padding: 20px 0 30px 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, eget accumsan dictum, nisi libero ultricies ipsum, in posuere mauris neque at erat.</td>
  </tr>
  <tr>
   <td>
    <table border="0" width="100%" cellspacing="0" cellpadding="0">
     <tbody>
      <tr>
       <td valign="top" width="260" style="padding: 20px 20px 20px 20px;">
        <table border="0" width="100%" cellspacing="0" cellpadding="0">
         <tbody>
          <tr>
           <td><img style="display: block;" src="https://bit.ly/3rmktSc" alt="" width="100%" height="auto" /></td>
          </tr>
          <tr>
           <td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 20px; padding: 25px 0 0 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, eget accumsan dictum, nisi libero ultricies ipsum, in posuere mauris neque at erat.</td>
          </tr>
         </tbody>
        </table>
       </td>
       <td style="font-size: 0; line-height: 0;" width="20">&nbsp;</td>
       <td valign="top" width="260" style="padding: 20px 20px 20px 20px;">
        <table border="0" width="100%" cellspacing="0" cellpadding="0">
         <tbody>
          <tr>
           <td><img style="display: block;" src="https://bit.ly/3IdtjsA" alt="" width="100%" height="auto" /></td>
          </tr>
          <tr>
           <td style="color: #153643; font-family: Arial, sans-serif; font-size: 16px; line-height: 20px; padding: 25px 0 0 0;">Lorem ipsum dolor sit amet, consectetur adipiscing elit. In tempus adipiscing felis, sit amet blandit ipsum volutpat sed. Morbi porttitor, eget accumsan dictum, nisi libero ultricies ipsum, in posuere mauris neque at erat.</td>
          </tr>
         </tbody>
        </table>
       </td>
      </tr>
     </tbody>
    </table>
   </td>
  </tr>
 </tbody>
</table>

The HTML body should look like this:




Use LOOPS for mass distribution

If you need to send many emails at once, you can solve this with a loop. So write a SQL query that selects all recipients and execute the procedure or function "apex_mail.send" in the loop. You can find a very good description here (Creating Email Campaign App with Oracle APEX).


A tiny example could be the following code snippet:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
begin
    for rec in (select contact_name, contact_email from contacts) 
    loop        
        apex_mail.send (
        p_to                 => rec.contact_email,
        p_template_static_id => 'HELLO_WORLD',
        p_placeholders       => '{' ||
        '    "CONTACT":'            || apex_json.stringify( rec.contact_name ) ||
        '}' );
    end loop;

    apex_mail.push_queue;
end;

Use different SQL techniques for dynamic content

As we explained in a previous blog, dynamic HTML content can be created by using PL/SQL. This can be very handy in many ways to make the content of your email dynamic, personalized, or even random.

For example, the following SQL snippet can be used to display a personalized order.

 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
50
51
52
53
54
55
56
declare
   l_order_table VARCHAR2(32767 CHAR);
begin
   l_order_table := l_order_table || '<table style="font-family: arial, sans-serif; border-collapse: collapse; width: 100%;">' 
                 || '<tr>'
                 || '<th style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding: 8px" >Position</th>'
                 || '<th style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding: 8px" >Article</th>'
                 || '<th style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding: 8px" >Quantity</th>'
                 || '<th style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding: 8px" >Unit price</th>'
                 || '<th style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding: 8px" >Total price</th>'
                 || '</tr>';

   for rec in (
       select ROW_NUMBER() OVER (ORDER BY article) AS position,
              article, 
              sum(quantity) as quantity, 
              sum(price) as price, 
              sum(quantity * price) as total
         from 
         (
         select 'Article 1' as article, round(dbms_random.value(1, 10),0) as quantity, round(dbms_random.value(1, 100),2) as price from dual
         union
         select 'Article 2' as article, round(dbms_random.value(1, 10),0) as quantity, round(dbms_random.value(1, 100),2) as price from dual
         union
         select 'Article 3' as article, round(dbms_random.value(1, 10),0) as quantity, round(dbms_random.value(1, 100),2) as price from dual
         union
         select 'Article 4' as article, round(dbms_random.value(1, 10),0) as quantity, round(dbms_random.value(1, 100),2) as price from dual
         union
         select 'Article 5' as article, round(dbms_random.value(1, 10),0) as quantity, round(dbms_random.value(1, 100),2) as price from dual
         )
        group by rollup(article)
   )
   loop
      if rec.article is not null then
        l_order_table := l_order_table || '<tr>'
                        || '<td style="border: 1px solid #dddddd; text-align: left; padding-left: 8px;">' || rec.position || '</td>'
                        || '<td style="border: 1px solid #dddddd; text-align: left; padding-left: 8px;">' || rec.article || '</td>'
                        || '<td style="border: 1px solid #dddddd; text-align: right; padding-left: 8px;">' || rec.quantity || '</td>'
                        || '<td style="border: 1px solid #dddddd; text-align: right; padding-left: 8px;">' || rec.price || '</td>'
                        || '<td style="border: 1px solid #dddddd; text-align: right; padding-left: 8px;">' || rec.total|| '</td>'
                        || '</tr>';
      else 
        l_order_table := l_order_table || '<tr>'
                        || '<td style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding-left: 8px; font-weight: bold;">Sum</td>'
                        || '<td style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: left; padding-left: 8px; font-weight: bold;"></td>'
                        || '<td style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: right; padding-left: 8px; font-weight: bold;"></td>'
                        || '<td style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: right; padding-left: 8px; font-weight: bold;">' || rec.price || '</td>'
                        || '<td style="background-color: #eeeeff; border: 1px solid #dddddd; text-align: right; padding-left: 8px; font-weight: bold;">' || rec.total || '</td>'
                        || '</tr>';
      end if;                  
   end loop;
   
   l_order_table := l_order_table || '</table>';

   :P1_ORDER_TABLE := l_order_table;
end;


And this is how the email output could be:




So, as you have seen and learned, there are many ways to create and send emails. However, you should always pay attention to some details and think in advance who the recipient group will be and what email clients you can expect.


Here is the demo app for reference.


Quellen:

Labels: , , ,

Wednesday, May 20, 2020

Drag and Drop Shuttle Box in Oracle Apex Application




In diesem Blog möchten wir zeigen wie man eine Shuttle Box mit ein wenig JavaScript und CSS zeitgemäßer wirken lassen kann. Hierfür verwenden wir unsere Drag and Drop Funktion, die wir in einem älteren Blog erstellt hatten und modifizieren diese noch um ein paar weitere Features. So wollen wir zum Beispiel ausgewählte Werte auch wieder entfernen und sortieren können. Die Ergebnisse speichern wir in einer APEX Collection, so dass wir diese in unsere Anwendung weiterverwenden können.

Zunächst benötigen wir für unser Beispiel eine Tabelle mit Werten, die wir selektieren möchten. Hierzu können wir die Beispieldatenbank "EMP/DEPT" verwenden, die ganz einfach im APEX Builder unter "SQL Workshop > Utilities > Sample Datasets" installiert werden kann.




Anschließend können wir in APEX eine neue Anwendung mit einer leeren Seite erstellen in der wir die Drag and Drop Funktion implementieren.

Für unsere Drag and Drop Funktion benötigen wir als erstes zwei „Custom Templates“. Diese werden unter „Shared Components > Templates“ erstellt. Als Template Type wählen wir „Report“ und erstellen dieses „From Scratch“. Unser erstes Template nennen wir beispielhaft „DragZone“. Wählen dann die Template Class „Custom 1“ und den Template Type „Named Column (row template)“ und klicken „Create“ um das Custom Template zu erstellen.




Anschließend erstellen wir auf gleicher Weise ein zweites Template welches wir „DropZone“ nennen.

Als nächstes müssen wir die Struktur von unserem Report definieren. Hierzu klicken wir als erstes auf das Template „DragZone“ und ersetzen den HTML Code für das „Row Template 1“ durch folgenden HTML Code.

<div>
    <li data-empno='#EMPNO#' data-ename='#ENAME#' class='draglist-el' draggable=true>#ENAME#<hr></li>
</div>

Da wir eine ungeordnete Liste benötigen, müssen wir noch zuvor ein HTML Open Tag hinzufügen bevor alle Listelemente angezeigt werden können und abschließend einen Close Tag. Hierzu in der Region „Before Rows“ folgende HTML Code einfügen.

<ul class='draglist' ondragstart='handleDrag(event)'>

 Und in der Region „After Rows“ den Close Tag.

</ul>

Final mit „Apply Changes“ die Änderungen speichern.


Als nächstes definieren wir unsere „DropZone“ und klicken dazu auf das Template „DropZone“ und ersetzen den HTML Code für das „Row Template 1“ durch folgenden HTML Code.

<div>
    <li data-empno='#EMPNO#' data-ename='#ENAME#' class='droplist-el' >
        <button type="button" data-empno='#EMPNO#' data-seq_id='#SEQ_ID#' class="del" style="padding-left:0px;border:none;background:none" ><span class="fa fa-trash" style="color:#0076df;"></span></button>
        <button type="button" data-seq_id='#SEQ_ID#' class="down" style="padding-left:0px;border:none;background:none" ><span class="fa fa-arrow-up" style="color:#0076df;"></span></button>
        <button type="button" data-seq_id='#SEQ_ID#' class="up"   style="padding-left:0px;border:none;background:none" ><span class="fa fa-arrow-down" style="color:#0076df;"></span></button>
        #ENAME#<hr></li>
</div>

An dieser Stelle haben wir zusätzlich einen Button zum entfernen, sowie jeweils einen Button zum Auf und Ab sortieren hinzugefügt.

Da wir wieder eine ungeordnete Liste benötigen, müssen wir noch zuvor ein HTML Open Tag hinzufügen bevor alle Listelemente angezeigt werden können und abschließend einen Close Tag. Hierzu in der Region „Before Rows“ folgende HTML Code einfügen.

<ul class='droplist' ondragstart='handleDrag(event)'>

 Und in der Region „After Rows“ den Close Tag.

</ul>

Final mit „Apply Changes“ die Änderungen speichern.


Jetzt sind unsere benötigten Templates fertig, so dass wir wieder zurück zum Page Editor gehen können. Dort benötigen wir als nächsten Schritt einen Classic Report für die DragZone. Als Source verwenden wir die erstellte Tabelle „EMP“ und sortieren diese nach „ENAME“.




Dann noch unter „Attributes“ das Custom Template „DragZone“ auswählen.





Als nächstes benötigen wir einen weiteren Classic Report für unsere „DropZone“. Da wir die ausgewählten Werte in einer „APEX Collection“ speichern, verwenden wir unter Source folgendes SQL Query.

select seq_id, c002 as ename from apex_collections where collection_name = 'DROPZONE'
order by seq_id

Dann noch unter „Attributes“ das Custom Template „DropZone“ auswählen.

Jetzt noch den Classic Report´s eine Static-Id hinzufügen („dragzone“ & „dropzone“) sowie bei beiden Reports folgende Einträge unter Custom Attributes einfügen:

ondragover='handleDragover(event)'   ondrop='handleDrop(event)'

Optional kann noch ein wenig CSS für die beiden Reports hingezufügt werden. Dazu im Page-Inline-CSS-Editor folgende Code einfügen.

.draglist{
      list-style-typenone;
}

.droplist{
      list-style-typedecimal-leading-zero;
}

.draglist-el {
      padding-top5px;
        padding-bottom5px;
}

.droplist-el {
      padding-top5px;
        padding-bottom5px;
}

hr {  
  margin-top5px
  margin-bottom0px
}


Für unsere Drag & Drop Funktion benötigen wir nun noch JavaScript Function´s, die wir im Code Editor für die „Function and Global Variable Declaration“ hinterlegen.

function handleDrag(event) {
    var data = {empno: event.target.dataset.empnoename: event.target.dataset.ename};
    var json_data = JSON.stringify(data);
    event.dataTransfer.setData('data'json_data);
}

function handleDragover(event){
    event.preventDefault();
}

function handleDrop(event){
    var empno = JSON.parse(event.dataTransfer.getData('data')).empno;
    var ename = JSON.parse(event.dataTransfer.getData('data')).ename;

    apex.server.process(
          'Update Collection',
          {
             x01: empno,
             x02: ename 
          },
          {
             success: function(){
                   apex.event.trigger('#dropzone''apexrefresh')
             },
             dataType: 'text'
          });
}

Da wir hier bei der „handleDrop“ Function einen APEX Process ausführen wollen, der die APEX Collection updaten soll, benötigen wir jetzt noch diesen Process.

Hierzu unter „Proccessing“ einen neuen „Ajax Callback“ Process hinzufügen den wir „Update Collection“ nennen. Im PL/SQL Editor folgenden Code einfügen:

apex_collection.add_member(p_collection_name => 'DROPZONE'
                          ,p_c001 => apex_application.g_x01 
                          ,p_c002 => apex_application.g_x02
                          );

Da unsere Anwendung die APEX Collection jedoch noch nicht erstellt hat benötigen wir noch einen „Page Load Process“ den wir „Create Collection“ nennen. Im PL/SQL Editor folgenden Code einfügen:

if APEX_COLLECTION.COLLECTION_EXISTS(p_collection_name => 'DROPZONE'then
    APEX_COLLECTION.TRUNCATE_COLLECTION(p_collection_name => 'DROPZONE');    
else    
    apex_collection.create_collection('DROPZONE');
end if

Zuletzt benötigen wir noch die Processes zum löschen und sortieren der DropZone Werte.

Hierzu erstellen wir 3 weitere „Ajax Callback Processes“. 
  • Delete Item
  • MoveUp Item
  • MoveDown Item
Zusätzlich benötigen wir noch ein Page Item indem wir die selektierte Id speichern. Dieses nennen wir „P1_ITEM“ und fügen es dem Classic Report „DropZone“ hinzu.

Für den Process „Delete Item“ geben wir folgenden PL/SQL Code ein:

APEX_COLLECTION.DELETE_MEMBER(
    p_collection_name => 'DROPZONE',
    p_seq => :P1_ITEM);
        
APEX_COLLECTION.RESEQUENCE_COLLECTION (
p_collection_name => 'DROPZONE'); 

Für den Process „MoveUp“ Item geben wir folgenden PL/SQL Code ein:

APEX_COLLECTION.MOVE_MEMBER_UP(
    p_collection_name => 'DROPZONE',
    p_seq => :P1_ITEM);

Für den Process „MoveDown“ Item geben wir folgenden PL/SQL Code ein:

APEX_COLLECTION.MOVE_MEMBER_DOWN(
    p_collection_name => 'DROPZONE',
    p_seq => :P1_ITEM);   

Jetzt noch 3 Dynamic Actions die den jeweiligen Process beim Klick auf das jeweilige Icon auslösen. 
  • Delete Item
  • MoveUp Item
  • MoveDown Item

Alle 3 DA´s müssen als Event „Click“ sowie den Selection Type „jQuery Selector“ hinterlegt bekommen. Zusätzlich noch den Event Scope auf „Dynamic“ setzen.

Bei der DA „Delete Item“ muss der jQuery Selector „.del“ eingegeben werden.
Bei „MoveUp Item“ muss der jQuery Selector „.up“ eingegeben werden.
Und bei „MoveDown Item“ muss der jQuery Selector „.down“ eingegeben werden.

Als „True“ Actions benötigen wir für jede Dynamic Actions folgende 3 Events.

1) Execute Javascript Code

$s('P1_ITEM'$(this.triggeringElement).data('seq_id'));

2) Execute PL/SQL Code
  • PL/SQL Code: null;
  • Items to Submit: P1_ITEM 
3) Execute Javascript Code (Delete Dropzone Item)

apex.server.process(
    'Delete Item',
    {
      x01: null
    },
    {
       success: function(){
             apex.event.trigger('#dropzone', 'apexrefresh')
       },
       dataType: 'text'
    });


     Execute Javascript Code (MoveUp Dropzone Item)

apex.server.process(
    'MoveUp Item',
    {
      x01: null
    },
    {
       success: function(){
             apex.event.trigger('#dropzone', 'apexrefresh')
       },
       dataType: 'text'
    });


     Execute Javascript Code (MoveDown Dropzone Item)

apex.server.process(
    'MoveDown Item',
    {
      x01: null
    },
    {
       success: function(){
             apex.event.trigger('#dropzone', 'apexrefresh')
       },
       dataType: 'text'
    });



Alle Änderungen speichern und die Anwendung ausführen :-)






Optionaler Tipp für Tabletts:

Um die Touchfähigkeit auf Tabletts zu gewährleisten muss noch folgende jQuery Datei zu den Static Files hochgeladen werden und auf der Seite wo die Drop Down Region ist hinterlegt werden (JavaScript File URLs).

https://github.com/furf/jquery-ui-touch-punch/blob/master/jquery.ui.touch-punch.js

jQuery UI Touch Punch verwendet simulierte Ereignisse, um Berührungsereignisse analog Mausereignisse zuzuordnen. 


Quellen:

Labels: , ,