Replacing NetSuite’s Rich Textbox Control

As crazy as this sounds, NetSuite’s richtext control allows (and possibly creates) markup that won’t print. We decided to replace their control with tinyMCE.

Originally, I did this with client-side scripting. It was simple, because a client-side script would wait until the page loaded, look for all richtext controls, remove the DOM elements, replace them with a TEXTAREA and initialize tinyMCE. Doing it client-side let me continue to manage my page and scripts exactly the same as if I’d left the original (NetSuite’s) controls in place. No swapping, no renaming. Simple!

This worked great until we got a Chrome update this past week, then stuff completely unrelated to the rich textboxes stopped working. It was incredibly hard to diagnose, as the problems were intermittent. It turned out it had to do with delivering the first richtext control, and the replacing it while it was in the process of initializing. Somehow that messed up other NetSuite client-side JavaScript.

To fix this, I had to rethink how I was replacing the richtext control with tinyMCE. I had to do it server-side, to keep the original control from ever starting to render. It is impossible to remove a control server-side, but you can hide it. It is also impossible to have 2 fields with the same ID. That means you’ll need to do some renaming. Argh!

/**
 * Add a textarea to the form with the name custpage_ in place of custbody_, then hide the original.
 * @param {*} context 
 * @param {*} original_field_id 
 */
function swapRichTextControls(context, original_field_id) {
    var new_field_id = original_field_id.replace('custbody_', 'custpage_');
    var value = context.newRecord.getValue(original_field_id);

    var originalField = context.form.getField(original_field_id);

    var newField = context.form.addField({
        id: new_field_id,
        label: originalField.label,
        type: serverWidget.FieldType.TEXTAREA
    });

    newField.defaultValue = value

    context.form.insertField({
        field: newField,
        nextfield: original_field_id
    });

    originalField.updateDisplayType({ displayType: serverWidget.FieldDisplayType.HIDDEN });
}

I call swapRichTextControls from an event script. In my case all my richtext fields are custom fields, which start with “custbody_”. Since I can’t have 2 fields with the same name, and the convention is to name all custom fields with “custpage_”, I do that in a beforeLoad() function.

In the beforeSave() function, I take value from the newly created control and save it back in field that was previously associated with the original richtext control, the one I hid in the beforeLoad() function.

/**
 * On save, you need to move the value from the fake TEXTAREA back to the original field.
 * @param {*} context 
 * @param {*} original_field_id 
 */
function swapBackRichTextControls(context, original_field_id) {
    var new_field_id = original_field_id.replace('custbody_', 'custpage_');
    try {
        context.newRecord.setValue(
            original_field_id,
            context.newRecord.getValue(new_field_id)
        )
    }
    catch (err) {

    }
}

This takes care of delivering a standard TEXTAREA control in place of the richtext control that we wanted to get rid of. To turn it into a tinyMCE control, we need to include the tinyMCE library and initialize the TEXTAREA.

/**
 * Initialize tinyMCE on a TEXTAREA control.
 * @param {*} control_id Internal ID of the control. example: custpage_note_internal.
 */
initControl: function (control_id) {
    //console.log('initControl: ' + control_id);
    //if (!tinymce) console.log('tinymce cdn libraries are not included');
    tinymce.init({
        selector: '#' + control_id,
        plugins: 'link paste code media table lists hr help',
        default_link_target: '_blank',
        link_assume_external_targets: 'https',
        paste_data_images: true,
        menubar: 'edit view insert format table tools help',
        toolbar: 'numlist bullist link hr',
        browser_spellcheck: true,
        contextmenu: false,
        forced_root_block: false,
        init_instance_callback: function (editor) {
            editor.on('Change', function (e) {
                // This is where the contents of the TinyMCE control
                // are put back into the textarea where the server picks
                // them up as though they were in the original <input type="hidden">
                var realFieldId = e.target.id;
                $('#' + realFieldId).val(e.target.getContent());
            })
        }
    });
    var control_width = jQuery(window).width() * 0.30;
    jQuery('#'+control_id).css('width', control_width, 'padding-right', 20, 'padding-bottom', 20);
}

Here is the basics of how I include the tinyMCE CDN.

const tinymce_js = 'https://cdn.tiny.cloud/1/.../tinymce/5/tinymce.min.js';

function tinymce_client_includes() {
    client_includes_count++;
    return [

        ... Other library references ...
        tinymce_js,
    ].join("\n");
}

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s