CHUVASH.eu

CHunky Universe of Vigourous Astonishing SHarepoint :)

Drag and Drop Image using Client Side Rendering

I continue my series about Client Side Rendering (CSR) and jsgrid. Today I want to try a custom field where users can drag and drop images. The inspiration comes from:

What I want to achieve is:

  1. A custom field that is rendered with jslink
  2. Users can drag and drop small pictures (thumbnails) into the field
  3. A base64 image representation is saved as the field value
  4. Optionally implement pasting images using Clipboard API

Step 1 Create a field with a custom jslink

Create a field of type Note. I am using the PnP Core Extensions to make it quickier:

dnd-001

My jslink file is very simple to begin with:

(function () {
    'use strict';
    function view(ctx, field) {
        return "hello";
    }

    var overrideContext = {};

    overrideContext.Templates = overrideContext.Templates || {};
    overrideContext.Templates.Fields = {
        'DragAndDropImage': {
            'View': view,
            'DisplayForm': view
            //'EditForm': verySimpleNewAndEdit,
            //'NewForm': verySimpleNewAndEdit
        }
    };

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
})();

This will result in the following display form. Just outputting “hello” indicates that my field is jslink are registered correctly:

dnd-002

Step 2. Ensure base64 works in the field

Not all fields will work. I have tried many of them and found that the field type Note with Plain text works for saving base64 images.

First download some sample icons at flaticons.com

dnd-003

Convert an icon to a base64 image using dataurl.net:

dnd-004

Save the string into the Drag And Drop Image field:

dnd-005

Now update the view template in the CSR overrides:

//http://stackoverflow.com/a/822486/632117
//in display form we have to strip the html elements that are pasted in by SharePoint
function strip(html) {
    var tmp = document.createElement("DIV");
    tmp.innerHTML = html;
    return tmp.textContent || tmp.innerText || '';
}
function renderImage(fieldValue) {
    if (fieldValue) {
        return ['<img style="max-width:100px;" src="',
            strip(fieldValue), '"/>'].join('');
    }
    return '';
}

function view(ctx, field) {
    //disable editing in Quick Edit mode
    if (ctx.inGridMode) {
        field.AllowGridEditing = false;
    }
    var fieldValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
    return renderImage(fieldValue);
}

Well, it works:

dnd-006dnd-007

 Step 3. Implement drag and drop

Let’s start with the edit template:

function renderDropZone(fieldValue) {
    var img = renderImage(fieldValue);
    return ['<div id="drop_zone">', img, '</div>'].join('');
}

function edit(ctx, field) {
    var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
    formCtx.registerGetValueCallback(formCtx.fieldName, function () {
        //will implement the logic later
        return '';
    });
    var fieldValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
    return renderDropZone(fieldValue);
}

var overrideContext = {};
overrideContext.Templates = overrideContext.Templates || {};
overrideContext.Templates.Fields = {
    'DragAndDropImage': {
        'View': view,
        'DisplayForm': view,
        'EditForm': edit,
        'NewForm': edit
    }
};

I have wrapped the image element with a div (id=drop_zone). Now I need some css to show it:

var additionalStyle = [
    '<style>',
    '#drop_zone { height: 100px; width:100px; background: #efe}',
    '</style>'
].join('');
document.write(additionalStyle);

Now there is a clear “drop zone”:
dnd-008
To make it as simple as possible, let’s follow oroboto’s drag and drop code. Here we go:

var processFiles = function (event) {
    var element = event.target;
    event.stopPropagation();
    event.preventDefault();
    removeDropZoneClass(event);

    // FileList object of File objects
    var files = event.dataTransfer.files;
    for (var i = 0, f; f = files[i]; i++) {
        var reader = new FileReader();

        // closure to capture file info
        reader.onload = (function (file, index) {
            return function (e) {
                var dataUri = e.target.result;
                var img = renderImage(dataUri);
                element.innerHTML = img;
            };
        })(f, i);

        // read file as data URI
        reader.readAsDataURL(f);
    }

};

var highlightDropZone = function (e) {
    e.stopPropagation();
    e.preventDefault();
    document.getElementById('drop_zone').classList.add('highlight');
}

var removeDropZoneClass = function (e) {
    document.getElementById('drop_zone').classList.remove('highlight');
}

function prepareDropZone(ctx) {
    if (ctx.inGridMode) {
        return;
    }
    // add event listeners if File API is supported
    var dropZone = document.getElementById('drop_zone');
    if (window.File &amp;amp;&amp;amp; window.FileReader &amp;amp;&amp;amp; window.FileList &amp;amp;&amp;amp; window.Blob) {
        dropZone.addEventListener('drop', processFiles, false);
        dropZone.addEventListener('dragover', highlightDropZone, false);
        dropZone.addEventListener('dragenter', highlightDropZone, false);
        dropZone.addEventListener('dragleave', removeDropZoneClass, false);
    } else {
        dropZone.innerHTML = 'The File APIs are not fully supported in this browser.';
        dropZone.className = 'highlight';
    }

};

// ... code omitted for brevity

overrideContext.Templates.OnPostRender = prepareDropZone;

That’s not bad, not bad:

dnd-009

The last thing in this step is to save the base64 value

formCtx.registerGetValueCallback(formCtx.fieldName, function () {
    var dropZone = document.getElementById('drop_zone');
    var img = dropZone.getElementsByTagName('img');
    return img.length ? img[0].src : '';
});

Step 4 Support pasting images

Drag And Drop is cool. But what about pasting images. Wouldn’t it be nice? The functionality is called Clipboard API. You can check which browsers support this on caniuse.com. The simplest example I’ve found is on stackoverflow: How does onpaste work

The code I use is as follows:

document.onpaste = function (event) {
    var items = (event.clipboardData || event.originalEvent.clipboardData).items;
    console.log(JSON.stringify(items)); // will give you the mime types
    var blob = items[0].getAsFile();
    var reader = new FileReader();
    reader.onload = function (e) {
        var dataUri = e.target.result;
        var img = renderImage(dataUri);
        dropZone.innerHTML = img;
    }; // data url!
    reader.readAsDataURL(blob);
}

Here is how pasting works:
dnd-011

Step 5 Error handling (Not done yet)

I need to ensure that

  • only images are allowed
  • only small images are allowed, otherwise base64 will be to heavy
  • proper handling of old browsers, we don’t need to support them, but the users should get good information about what they need to make it work
  • a help icon is shown, when clicked a help callout is shown about how it works

Step 6 Support Quick Edit (Not done yet)

We need code that works in Quick Edit:

  • need to write code for jsgrid
  • need handling of multiple drop zones on the same page (unique ids and smart handling of pasting)

Summary

This post is about discovering what’s possible to do with client side rendering and using today’s technologies like html5 and new javascript apis. I hope you’ve got some inspiration how normal sharepoint fields combined with Client Side Rendering (CSR) api can create more value in your project, in Office 365 and in SharePoint On Premises. Please leave feedback in comments.

Fulll source code

And finally, here comes the most up-to-date full source code for this custom Drag And Drop Image field:

(function () {
    'use strict';
    var additionalStyle = [
        '<style>',
        '#drop_zone { height: 100px; width:100px; background: #efe}',
        '#drop_zone.highlight {background:#fee;}',
        '</style>'
    ].join('');
    document.write(additionalStyle);


    var processFiles = function (event) {
        var element = event.target;
        event.stopPropagation();
        event.preventDefault();
        removeDropZoneClass(event);

        // FileList object of File objects
        var files = event.dataTransfer.files;
        for (var i = 0, f; f = files[i]; i++) {
            var reader = new FileReader();

            // closure to capture file info
            reader.onload = (function (file, index) {
                return function (e) {
                    var dataUri = e.target.result;
                    var img = renderImage(dataUri);
                    element.innerHTML = img;
                };
            })(f, i);

            // read file as data URI
            reader.readAsDataURL(f);
        }

    };

    var highlightDropZone = function (e) {
        e.stopPropagation();
        e.preventDefault();
        document.getElementById('drop_zone').classList.add('highlight');
    }

    var removeDropZoneClass = function (e) {
        document.getElementById('drop_zone').classList.remove('highlight');
    }

    function prepareDropZone(ctx) {
        if (ctx.inGridMode) {
            return;
        }
        // add event listeners if File API is supported
        var dropZone = document.getElementById('drop_zone');
        if (window.File &amp;amp;&amp;amp; window.FileReader &amp;amp;&amp;amp; window.FileList &amp;amp;&amp;amp; window.Blob) {
            dropZone.addEventListener('drop', processFiles, false);
            dropZone.addEventListener('dragover', highlightDropZone, false);
            dropZone.addEventListener('dragenter', highlightDropZone, false);
            dropZone.addEventListener('dragleave', removeDropZoneClass, false);
        } else {
            dropZone.innerHTML = 'The File APIs are not fully supported in this browser.';
            dropZone.className = 'highlight';
        }

        document.onpaste = function (event) {
            var items = (event.clipboardData 
                 || event.originalEvent.clipboardData).items;
            console.log(JSON.stringify(items)); // will give you the mime types
            var blob = items[0].getAsFile();
            var reader = new FileReader();
            reader.onload = function (e) {
                var dataUri = e.target.result;
                var img = renderImage(dataUri);
                dropZone.innerHTML = img;
            }; // data url!
            reader.readAsDataURL(blob);
        }
       
    };

    //http://stackoverflow.com/a/822486/632117
    //in display form we have 
    //to strip the html elements that are pasted in by SharePoint
    function strip(html) {
        var tmp = document.createElement('DIV');
        tmp.innerHTML = html;
        return tmp.textContent || tmp.innerText || '';
    }
    function renderImage(fieldValue) {
        if (fieldValue) {
            return ['<img style="max-width:100px;" src="',
                strip(fieldValue), '"/>'].join('');
        }
        return '';
    }
    
    function view(ctx, field) {
        //disable editing in Quick Edit mode
        if (ctx.inGridMode) {
            field.AllowGridEditing = false;
        }
        var fieldValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
        return renderImage(fieldValue);
    }
    
    function renderDropZone(fieldValue) {
        var img = renderImage(fieldValue);
        return ['Drop files here<div id="drop_zone"', img, '</div>'].join('');
    }

    function edit(ctx, field) {
        var formCtx = SPClientTemplates.Utility.GetFormContextForCurrentField(ctx);
        formCtx.registerGetValueCallback(formCtx.fieldName, function () {
            var dropZone = document.getElementById('drop_zone');
            var img = dropZone.getElementsByTagName('img');
            return img.length ? img[0].src : '';
        });
        var fieldValue = ctx.CurrentItem[ctx.CurrentFieldSchema.Name];
        return renderDropZone(fieldValue);
    }

    var overrideContext = {};
    overrideContext.Templates = overrideContext.Templates || {};
    overrideContext.Templates.Fields = {
        'DragAndDropImage': {
            'View': view,
            'DisplayForm': view,
            'EditForm': edit,
            'NewForm': edit
        }
    };

    overrideContext.Templates.OnPostRender = prepareDropZone;

    SPClientTemplates.TemplateManager.RegisterTemplateOverrides(overrideContext);
})();
Advertisements

One response to “Drag and Drop Image using Client Side Rendering

  1. Pingback: Client Side Rendering with Async dependencies - Bool Tech

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 )

Google+ photo

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

Connecting to %s

Вула Чăвашла

VulaCV - Чăвашла вулаттаракан сайт

Discovering SharePoint

And going crazy doing it

Bram de Jager talking Office 365, SharePoint and Azure

My view and thoughts on Productivity and more.

My programming life

and everything in between

SharePoint Development Lab by @avishnyakov

It is a good place to share some SharePoint stories and development practices.

SharePoint Dragons

Nikander & Margriet on SharePoint

Paul J. Swider - RealActivity

RealActivity is a specialized healthcare services and solution advisory firm.

Mai Omar Desouki - Avid SharePointer

Egyptian & Vodafoner - Senior SharePoint Consultant

Cameron Dwyer | Office 365, SharePoint, Outlook, OnePlace Solutions

Office 365, SharePoint, OnePlace Solutions & Life's Other Little Wonders

paul.tavares

Me and My doings!

Share SharePoint Points!!

By Mohit Vashishtha

Jimmy Janlén "Den Scrummande Konsulten"

Erfarenheter, synpunkter och raljerande om Scrum från Jimmy Janlén

Aryan Nava

BigData and Blockchain expert in Toronto

SPJoel

SharePoint for everyone

SharePointRyan

Ryan Dennis is a SharePoint Solution Architect with a passion for SharePoint and PowerShell

SharePoint 2020

The Vision for a Future of Clarity

Aharoni in Unicode

Treacle tarts for great justice

... And All That JS

JavaScript, Web Apps and SharePoint

%d bloggers like this: