Logo OCSI

Validate and submit a Newsletter Form : Day2

Today, I’m going to show you how to validate our form and persist the data into our custom object. To start, I’ll write a script that allows us to call upon the custom object manager to access our custom object. Think of it as a temporary database that clears after 30 days, although solutions exist to retain the data longer, such as using jobs to download the data in CSV format, for example.

var CustomObjectMgr = require('dw/object/CustomObjectMgr');
/**
 * @exports app_custom/cartridge/scripts/clientForm/storageService
 * @description - This function creates a custom object and stores the newsletter subscription details
 * @param newsletterForm
 * @returns {dw.object.CustomObject}
 */
function storeNewsletterObject(newsletterForm) {
    var CustomObject = CustomObjectMgr.createCustomObject('NewsletterSubscription', newsletterForm.email.value);
    CustomObject.custom.firstName = newsletterForm.fname.value;
    CustomObject.custom.lastName = newsletterForm.lname.value;
    return CustomObject;
}

module.exports = { storeNewsletterObject: storeNewsletterObject };

We’ll then call this script in our controller, which will activate when we validate our form. To persist our data, I’ll utilize dw.system.Transaction.here is a basic example of a controller which allows us to send our form

server.post('name-of-the-route', function(req, res, next) {
    var storageService = require('~/cartridge/scripts/storageService');
    var Transaction = require('dw/system/Transaction');
    // open a channel to send data to the server
    Transaction.begin();
    try {
        storageService.storeNewsletterObject(newsletterForm);
        res.render('template', {
            // pass in the form values if we went to use the values in the template
        });
        // send the data to the server and close the channel
        Transaction.commit();
    } catch (e) {
        // if there is an error, close the channel and send an error message no data
        // will be persisted in the server (our custom object)
        Transaction.rollback();
        // render a error form to an error template
    }
    next();
});

However, as you know, in any IT project, security and error handling are not to be overlooked. For this purpose, we will use dw/web/CSRFProtection to validate our token generated during the rendering of our form. Additionally, for errors, we will implement try/catch blocks to catch and redirect them to an error or success template. I’ve opted to work with a single template and perform redirects to the display route (Newsletter-Show) retrieved from my req.querystring . Of course, it’s advisable to implement loggers with dw/system/Logger, which will record errors in a file accessible from Administration>Site Development>Development Setup>Log Center. Note that loggers should be used cautiously and placed in sections prone to critical errors that could jeopardize the site’s integrity, as we only have 10 MB of log writing per 24 hours. This means that the log file resets every day at 12 PM.

/** Sample server side validation
 * Sample server side validation
 * @param {string} email
 */
function validateEmail(email) {
    var regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
    return regex.test(email);
}

/**
 * returns querystring from object to be used in redirect to the show url
 * @param params : object
 * @returns {string}
 */
function generateQuerystring(params) {
    var querystring = '';
    if (params) {
        var queryStringArray = [];
        // eslint-disable-next-line no-restricted-syntax
        for (var key in params) {
            if (Object.prototype.hasOwnProperty.call(params, key)) {
                queryStringArray.push(key + '=' + params[key]);
            }
        }
        querystring = queryStringArray.join('&');
    }
    return querystring;
}
server.post('Submit', server.middleware.https, function(req, res, next) {
    var storageService = require('~/cartridge/scripts/clientForm/storageService');
    var Transaction = require('dw/system/Transaction');
    var Logger = require('dw/system/Logger');
    if (csrfToken.validateRequest()) {
        var email = newsletterForm.email.value;
        if (validateEmail(email)) {
            try {
                Transaction.begin();
                storageService.storeNewsletterObject(newsletterForm);
                res.render('newsletter/newslettersignup', {
                    newsletterForm: newsletterForm,
                    success: 'Thank you for signing up!'
                });
                newsletterForm.clear();
                Transaction.commit();
            } catch (e) {
                Transaction.rollback();
                Logger.error('Error storing email: ' + e.message);
                res.redirect('Newsletter-Show?' + generateQuerystring({
                    error: 'Email already exists. Please use a different email address.'
                }));
            }
        } else {
            res.redirect('Newsletter-Show?' + generateQuerystring({
                error: 'Invalid email address'
            }));
        }
    } else {
        res.redirect('Newsletter-Show?' + generateQuerystring({
            error: 'CSRF token validation failed'
        }));
        Logger.warn('CSRF token validation failed');
    }
    next();
});

Now, our errors are directly displayed in the same template. You might wonder why I chose this approach. It’s to avoid regenerating a CSRF token on the submit route. Now, every time there’s an error, we return to the display page, keeping the same information entered by the user. We’ll clear the form only if everything goes smoothly.

A neat trick is to use the Toastify-JS library for floating notifications within conditional rendering using the <isif> tag

    <isscript>
        var assets = require('*/cartridge/scripts/assets');
        assets.addCss('/css/newsletter.css');
        assets.addCss('https://cdn.jsdelivr.net/npm/toastify-js/src/toastify.min.css');
    </isscript>
    <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/toastify-js"></script>

 <isif condition="${pdict.success}">
        <script>
            Toastify({
                text: '${pdict.success}',
                duration: 4000,
                close: true,
                autoClose: true,
                gravity: 'top',
                position: 'right',
                backgroundColor: 'green',
                stopOnFocus: true,
                style: {
                    marginTop: '15vh'
                }
            }).showToast();
        </script>
    </isif>
    <isif condition="${pdict.error}">
        <script>
            Toastify({
                text: '${pdict.error}',
                duration: 4000,
                close: true,
                autoClose: true,
                gravity: 'top',
                position: 'right',
                backgroundColor: 'red',
                stopOnFocus: true,
                style: {
                    marginTop: '15vh'
                }
            }).showToast();
        </script>
    </isif>

In the next article, we’ll discover how to send newsletter emails using SFRA hooks.

Ramzi Yousfi

Pour partager l'article :

Articles récents