Generate QR-Based E-Business Cards in ServiceNow with Arabic/English Support and Image Download

ameersuhail
Tera Expert

Use Case

Many organizations want to provide employees with digital business cards, especially for remote interactions or events. A QR-based e-card allows scanning and saving contact details instantly on mobile devices, bridging the gap between physical cards and modern convenience.

 

Overview

In this article, I’ll walk through how to build a complete E-Business Card generator in ServiceNow that:

  • Pulls user profile details automatically.

  • Allow user to edit his information to be displayed on e-card.
  • Supports multilingual data (Arabic and English).

  • Generates a high-quality QR code with contact information (vCard).

  • Displays the card with a custom background.

  • Allows the user to download a high-resolution image of the card.

  • Ensures layout stability across devices and screen zoom levels.

  • Works well with Android and iOS contact scanning (including proper name parsing).

Solution Architecture

The solution includes:

  • A ServiceNow widget for form input and card preview.

  • A portal page to display the widget in portal.
  • A scoped custom table to stored the edited version of data.
  • A background image add in Images[db_image] table.

 

 
Widget Code:

HTML:

<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>

<div ng-class="{'rtl': isArabic}" ng-attr-dir="{{ isArabic ? 'rtl' : 'ltr' }}">

<!-- Form fields -->
<div class="form-box">
<div class="header-box">
{{labels.e_business_card}}
</div>
<div class="form-group">
<label>{{labels.arabic_name}}</label>
<input type="text" class="form-control" ng-model="card.arabic_name">

<label>{{labels.name}}</label>
<input type="text" class="form-control" ng-model="card.name">

<label>{{labels.arabic_title}}</label>
<input type="text" class="form-control" ng-model="card.arabic_title">

<label>{{labels.title}}</label>
<input type="text" class="form-control" ng-model="card.title">

<label>{{labels.phone}}</label>
<input type="tel" class="form-control" ng-model="card.phone">

<label>{{labels.email}}</label>
<input type="email" class="form-control" ng-model="card.email">
</div>

<!-- Generate QR -->
<div class="button-group">
<button class="btn btn-primary" ng-click="generateCard()">
{{labels.generateQR}}
</button>
</div>
</div>
<!-- E-card display -->
<div ng-if="dataSubmitted && data.qrUrl" class="ecard-preview">
<div id="ecard-download" class="ecard-container">
<img src="ecard_background_image.png" class="ecard-bg-img"> //replace with your image name
<div class="ecard-overlay">
<div class="ecard-info">
<h5 class="arabic-name">{{card.arabic_name}}</h5>
<h5 class="name">{{card.name}}</h5>
<p class="arabic-title">{{card.arabic_title}}</p>
<p class="title">{{card.title}}</p>
<p class="phone">{{card.phone}}</p>
<p class="email">{{card.email}}</p>
<img id="qr-img-render" src="{{data.qrUrl}}" class="qr-img" alt="QR Code">
</div>
</div>
</div>

<!-- Download button -->
<button class="btn btn-success" ng-click="downloadCard()" style="margin-top: 20px;">
{{labels.downloadQR}}
</button>
</div>
</div>



CSS:



.header-box {
width: 100%;
margin-bottom: 20px;
padding: 16px;
background: whitesmoke !important;
color: #4d4d4f;
font-size: 24px;
text-align: left;
font-weight: bold;
border-radius: 6px;
box-shadow: 0 0 5px rgba(0,0,0,0.1);
}

/* Form box */
.form-box {
position: relative; /* Enables absolute children if needed */
margin: 20px auto;
padding: 20px 25px;
border: 1px solid #ddd;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 0px 5px rgba(0,0,0,0.1);
max-width: 90%; /* Responsive width */
width: 100%;
box-sizing: border-box;
}

/* Label styling */
.form-group label {
display: block;
font-weight: bold;
margin-top: 15px;
margin-bottom: 5px;
color: #4d4d4f;
}

/* Input styling */
.form-control {
width: 100%;
padding: 10px 12px;
margin-bottom: 15px;
border: 1px solid #ccc;
border-radius: 6px;
font-size: 14px;
}

/* Button styling (optional tweak) */
.button-group {
text-align: left;
margin-top: 21px;
}

.button-group .btn {
padding: 10px 20px;
font-size: 16px;
}

.ecard-preview {
text-align: center;
margin-top: 20px;
font-family: 'Segoe UI', 'Tahoma', 'Arial', sans-serif;
}

.ecard-container {
position: relative;
width: 350px;
height: 600px;
margin: 0 auto;
border-radius: 10px;
overflow: hidden;
box-shadow: 0 0 5px #ccc;
}

.ecard-bg-img {
position: absolute;
width: 100%;
height: 100%;
top: 0; left: 0;
z-index: 0;
}

.ecard-overlay {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
z-index: 1;
}

.ecard-info {
position: relative;
width: 100%;
height: 100%;
z-index: 2;
}

.ecard-info h5, .ecard-info p {
position: absolute;
left: 50%;
transform: translateX(-50%);
white-space: nowrap;
/*overflow: hidden;*/
text-overflow: ellipsis;
}

.arabic-name { top: 23%; font-size: 15px; color: #b45533; }
.name { top: 26%; font-size: 15px; color: #b45533; }
.arabic-title { top: 30%; font-size: 12px; color: #000; }
.title { top: 32.5%; font-size: 12px; color: #000; }
.phone { top: 42%; font-size: 11px; color: #000; }
.email { top: 50%; font-size: 11px; color: #000; }
.qr-img { position: absolute; top: 56%; left: 50%; transform: translateX(-50%); width: 140px; height: 140px; }



Server Script:



(function() {
    var userID = gs.getUserID();

    if (!input) {
        var user = new GlideRecord('sys_user');
        if (user.get(userID)) {
            data.name = user.name.toString();
            data.arabic_name = user.u_arabic_name ? user.u_arabic_name.toString() : '';
            data.arabic_title = user.title.toString();
            data.phone = user.mobile_phone.toString();
            data.email = user.email.toString();

            // Detect language preference for client
            data.isArabic = gs.getSession().getLanguage() == 'ar';

            //gs.addErrorMessage("Add: "+data.isArabic);

            // Provide label translations
            data.labels = data.isArabic ? {
                arabic_name: "الاسم",
                name: "Name",
                arabic_title: "الوظيفة",
                title: "Job Title",
                phone: "الهاتف / Phone",
                email: "البريد الإلكتروني / Email",
                generateQR: "توليد رمز الاستجابة السريعة",
                scanToSave: "امسح للحفظ في جهات الاتصال",
                downloadQR: "تحميل رمز الاستجابة السريعة",
                e_business_card: "بطاقة عمل إلكترونية"
            } : {
                arabic_name: "الاسم",
                name: "Name",
                arabic_title: "الوظيفة",
                title: "Job Title",
                phone: "Phone / الهاتف",
                email: "Email / البريد الإلكتروني",
                generateQR: "Generate QR",
                scanToSave: "Scan to Save Contact",
                downloadQR: "Download E-Card",
                e_business_card: "E-Business Card"
            };
        }
        return;
    }

    if (input.action === 'save') {
        var gr = new GlideRecord('x_1234_ebusiness_card'); // Replace with your actual table name
        gr.initialize();
        gr.name = input.name;
        gr.arabic_name = input.arabic_name;
        gr.title = input.arabic_title;
        gr.u_eng_title = input.title;
        gr.phone = input.phone;
        gr.email = input.email;
        gr.user = userID;
        gr.unique_id = 'id-' + gs.generateGUID();
        gr.insert();

        // Build vCard string with + prefixed phone
        var isArabic = gs.getSession().getLanguage() == 'ar';

        // Build vCard string based on language
       
        var vcard;
        var companyName = "NCW";

        if (isArabic) {
            var fullName = input.arabic_name;
            var nameParts = fullName.trim().split(" ");

            var givenName = nameParts.slice(0, 2).join(" ");
            var familyName = nameParts.slice(2).join(" ");

            vcard =
                'BEGIN:VCARD\n' +
                'VERSION:3.0\n' +
                'FN:' + fullName + '\n' +
                'N:' + familyName + ';' + givenName + ';;;\n' +
                'TITLE:' + input.arabic_title + '\n' +
                'ORG:' + companyName + '\n' +
                'TEL:+' + input.phone + '\n' +
                'EMAIL:' + input.email + '\n' +
                'END:VCARD';
        } else {
            var fullName = input.name || "";
            var nameParts = fullName.trim().split(" ");

            var givenName = nameParts[0] || "";
            var familyName = nameParts.slice(1).join(" ") || "";

            vcard =
                'BEGIN:VCARD\n' +
                'VERSION:3.0\n' +
                'FN:' + fullName + '\n' +
                'N:' + familyName + ';' + givenName + ';;;\n' + // 👈 Added
                'TITLE:' + input.title + '\n' +
                'ORG:' + companyName + '\n' +
                'TEL:+' + input.phone + '\n' +
                'EMAIL:' + input.email + '\n' +
                'END:VCARD';
        }
        // var vcard =
        //     'BEGIN:VCARD\n' +
        //     'VERSION:3.0\n' +
        //     'FN:' + input.name + '\n' +
        //     'TITLE:' + input.arabic_title + '\n' +
        //     'TEL:+' + input.phone + '\n' +
        //     'EMAIL:' + input.email + '\n' +
        //     'END:VCARD';

        data.qrUrl = 'https://api.qrserver.com/v1/create-qr-code/?size=200x200&color=180-85-51&data=' +
            encodeURIComponent(vcard);

        return {
            qrUrl: data.qrUrl
        };
    }
})();

Client Controller:

function($scope) {
    $scope.card = {
        name: $scope.data.name,
        arabic_name: $scope.data.arabic_name,
        arabic_title: $scope.data.arabic_title,
        title: $scope.data.title,
        phone: $scope.data.phone,
        email: $scope.data.email
    };

    $scope.isArabic = $scope.data.isArabic;
    $scope.labels = $scope.data.labels;
    $scope.dataSubmitted = false;

    $scope.generateCard = function() {
        const nameFilled = $scope.card.name.trim() || "";
        const arabicNameFilled = $scope.card.arabic_name.trim() || "";
        const phoneFilled = $scope.card.phone.trim();
        const emailFilled = $scope.card.email.trim();

        const messages = $scope.isArabic ? {
            name: "يرجى إدخال الاسم أو الاسم العربي.",
            phone: "يرجى إدخال رقم الهاتف.",
            email: "يرجى إدخال البريد الإلكتروني."
        } : {
            name: "Please enter either name or Arabic name.",
            phone: "Please enter phone number.",
            email: "Please enter email address."
        };

        if (!nameFilled && !arabicNameFilled) {
            alert(messages.name);
            return;
        }

        if (!phoneFilled) {
            alert(messages.phone);
            return;
        }

        if (!emailFilled) {
            alert(messages.email);
            return;
        }
        // Ensure '+' prefix
        // if ($scope.card.phone && !$scope.card.phone.startsWith('+')) {
        //   $scope.card.phone = '+' + $scope.card.phone;
        // }

        $scope.server.get({
            action: "save",
            name: $scope.card.name,
            arabic_name: $scope.card.arabic_name,
            arabic_title: $scope.card.arabic_title,
            title: $scope.card.title,
            phone: $scope.card.phone,
            email: $scope.card.email
        }).then(function(response) {
            $scope.data.qrUrl = response.data.qrUrl;
            $scope.dataSubmitted = true;
        });
    };

    $scope.downloadCard = function() {
        const el = document.getElementById("ecard-download");

        // const scale = window.devicePixelRatio || 1;

        html2canvas(el, {
            useCORS: true,
            allowTaint: true,
            backgroundColor: null,
            scale: 4, // Fixed high scale for sharp image
            width: el.offsetWidth,
            height: el.offsetHeight,
            windowWidth: el.scrollWidth,
            windowHeight: el.scrollHeight
        }).then(function(canvas) {
            // Manually upscale the canvas
            const finalCanvas = document.createElement('canvas');
            finalCanvas.width = canvas.width;
            finalCanvas.height = canvas.height;

            const ctx = finalCanvas.getContext('2d');
            ctx.drawImage(canvas, 0, 0);

            const link = document.createElement("a");
            link.download = "ecard.png";
            link.href = finalCanvas.toDataURL("image/png", 1.0);
            link.click();
        });
    };
}
0 REPLIES 0