Wednesday, May 29, 2013

RichTextArea how to implement native Drag & Drop?

What is the best way to implement native DnD for RichTextArea?  I need to support users dropping items into the RichTextArea that originated from outside the browser (from another web app).

Currently what I have partially works (in production seems to work about 50% of the time) in hosted mode it's only working on the first Drop (could be because of recent changes).  Currently we are doing this with JSNI.  I'll paste the full code below but the key parts are that on the drop event I need to get the 'text' data, e.g.

var data = event.dataTransfer.getData("Text");

Then I parse that data/URI and build a div which I then append to the original element.

Some of the things I'm not sure about are...what Document to be working with.  The original code used document in the JSNI method but I see online that $doc should be used.  Or I could pass into the JSNI method the document, e.g. 

 IFrameElement fe = getElement().cast();
 Document document = fe.getContentDocument();

Or ideally I'd prefer to do this with a more pure GWT solution and use less JSNI if that's possible.  How best to respond to these native drops?  Why would this code only be working for the first drop?  (Note, we have several instances of this widget in the application)  Here is the full code. (The first set of methods ininitDnD just parse URI...nothing interesting until the allowDrop/droppedHandler/addEvent methods)


// This will be called only once at startup.
    protected void onInitializeOnce() {
        super.onInitializeOnce();

        Document document = getContentDocument();
        BodyElement body = getBodyElement();
        initDnD(document, body);
    }

 private native void initDnD(Document document, Element elem) /*-{

        function strictEncodeURIComponent(string) {
            return encodeURIComponent(string).replace(/[!'()*]/g, escape);
        }

        encode = strictEncodeURIComponent;
        decode = decodeURIComponent;

        var parseURI = function (string) {
            var pos, t, parts = {};
            
            pos = string.indexOf('#');
            if (pos > -1) {
                // escaping?
                parts.fragment = string.substring(pos + 1) || null;
                string = string.substring(0, pos);
            }

            // extract query
            pos = string.indexOf('?');
            if (pos > -1) {
                // escaping?
                parts.query = string.substring(pos + 1) || null;
                string = string.substring(0, pos);
            }

            // extract protocol
            if (string.substring(0, 2) === '//') {
                // relative-scheme
                parts.protocol = '';
                string = string.substring(2);
                // extract "user:pass@host:port"
                string = parseURIAuthority(string, parts);
            } else {
                pos = string.indexOf(':');
                if (pos > -1) {
                    parts.protocol = string.substring(0, pos);
                    if (string.substring(pos + 1, pos + 3) === '//') {
                        string = string.substring(pos + 3);

                        // extract "user:pass@host:port"
                        string = parseURIAuthority(string, parts);
                    } else {
                        string = string.substring(pos + 1);
                        parts.urn = true;
                    }
                }
            }

            // what's left must be the path
            parts.path = string;

            // and we're done
            return parts;
        };
        var parseURIHost = function (string, parts) {
            // extract host:port
            var pos = string.indexOf('/'),
                    t;

            if (pos === -1) {
                pos = string.length;
            }

            if (string[0] === "[") {
                // IPv6 host - http://tools.ietf.org/html/draft-ietf-6man-text-addr-representation-04#section-6
                // I claim most client software breaks on IPv6 anyways. To simplify things, URI only accepts
                // IPv6+port in the format [2001:db8::1]:80 (for the time being)
                var bracketPos = string.indexOf(']');
                parts.hostname = string.substring(1, bracketPos) || null;
                parts.port = string.substring(bracketPos + 2, pos) || null;
            } else if (string.indexOf(':') !== string.lastIndexOf(':')) {
                // IPv6 host contains multiple colons - but no port
                // this notation is actually not allowed by RFC 3986, but we're a liberal parser
                parts.hostname = string.substring(0, pos) || null;
                parts.port = null;
            } else {
                t = string.substring(0, pos).split(':');
                parts.hostname = t[0] || null;
                parts.port = t[1] || null;
            }

            if (parts.hostname && string.substring(pos)[0] !== '/') {
                pos++;
                string = "/" + string;
            }

            return string.substring(pos) || '/';
        };
        var parseURIAuthority = function (string, parts) {
            string = parseURIUserinfo(string, parts);
            return parseURIHost(string, parts);
        };
        var parseURIUserinfo = function (string, parts) {
            // extract username:password
            var pos = string.indexOf('@'),
                    firstSlash = string.indexOf('/'),
                    t;

            // authority@ must come before /path
            if (pos > -1 && (firstSlash === -1 || pos < firstSlash)) {
                t = string.substring(0, pos).split(':');
                parts.username = t[0] ? decode(t[0]) : null;
                parts.password = t[1] ? decode(t[1]) : null;
                string = string.substring(pos + 1);
            } else {
                parts.username = null;
                parts.password = null;
            }

            return string;
        };
        var parseURIQuery = function (string) {
            if (!string) {
                return {};
            }

            // throw out the funky business - "?"[name"="value"&"]+
            string = string.replace(/&+/g, '&').replace(/^\?*&*|&+$/g, '');

            if (!string) {
                return {};
            }

            var items = {},
                    splits = string.split('&'),
                    length = splits.length;

            for (var i = 0; i < length; i++) {
                var v = splits[i].split('='),
                        name = encodeURIQuery(v.shift()),
                // no "=" is null according to http://dvcs.w3.org/hg/url/raw-file/tip/Overview.html#collect-url-parameters
                        value = v.length ? decodeURIQuery(v.join('=')) : null;

                if (items[name]) {
                    if (typeof items[name] === "string") {
                        items[name] = [items[name]];
                    }

                    items[name].push(value);
                } else {
                    items[name] = value;
                }
            }

            return items;
        };
        var encodeURIQuery = function (string) {
            return encode(string + "").replace(/%20/g, '+');
        };
        var decodeURIQuery = function (string) {
            return decode((string + "").replace(/\+/g, '%20'));
        };

        function allowDrop(event) {
            if (event.stopPropagation) {
                event.stopPropagation();
            }
            if (event.preventDefault) {
                event.preventDefault();
            }
            return false;
        }

        function droppedHandler(event) {
            // Try to use the 'text' everytime.
            try {
                alert("event= " + event);
                var data = event.dataTransfer.getData("Text");
                alert("data=" + data);
                if (data != null && data != "") {
                    try {
                        var parsedURI = parseURI(data);
                        var urlParameters = parseURIQuery(parsedURI.query);
                        var appsrc = urlParameters['appsrc'];
                        if (appsrc != null && appsrc == "COREFX") {
                            try {
                                // For some versions of IE, need to do this to prevent duplicates.
                                event.dataTransfer.setData('Text', '');
                            }
                            catch (e) {
                            }

                            if (event.preventDefault) {
                                event.preventDefault();
                            }

                            var title = data;
                            if (urlParameters['title'] != null) {
                                title = urlParameters['title'];
                            }
                            var newContentWrapper = document.createElement('div');
                            newContentWrapper.setAttribute('draggable', true);
                            newContentWrapper.setAttribute('ondragstart', "dragStart(event)");

                            var newContent = document.createElement('a');
                            newContent.setAttribute('href', data);
                            newContent.setAttribute('class', "external_system");

                            if (urlParameters['icon'] != null) {
                                var icon = decodeURI(urlParameters['icon']);
                                var newContentIcon = document.createElement('img');
                                newContentIcon.setAttribute('src', icon);
                                newContent.appendChild(newContentIcon);
                            }

                            var newContentTitle = document.createTextNode(title);
                            newContent.appendChild(newContentTitle);

                            newContentWrapper.appendChild(newContent);
                            var targetElement = event.target || event.srcElement;
                            targetElement.appendChild(newContentWrapper);
                            return false;
                        }
                    }
                    catch (e) {
                    }
                }
            }
            catch (error) {
            }
            return true;
        }

        var addEvent = (function () {
            if (document.addEventListener) {
                return function (el, type, fn) {
                    if (el && el.nodeName || el === window) {
                        el.addEventListener(type, fn, false);
                    } else if (el && el.length) {
                        for (var i = 0; i < el.length; i++) {
                            addEvent(el[i], type, fn);
                        }
                    }
                };
            } else {
                return function (el, type, fn) {
                    if (el && el.nodeName || el === window) {
                        el.attachEvent('on' + type, function (eventParam) {
                            if (!eventParam) {
                                eventParam = window.event;
                            }
                            return fn.call(el, eventParam);
                        });
                    } else if (el && el.length) {
                        for (var i = 0; i < el.length; i++) {
                            addEvent(el[i], type, fn);
                        }
                    }
                };
            }
        })();
        addEvent(elem, 'drop', droppedHandler);
        addEvent(elem, 'dragover', allowDrop);
    }-*/;

--
You received this message because you are subscribed to the Google Groups "Google Web Toolkit" group.
To unsubscribe from this group and stop receiving emails from it, send an email to google-web-toolkit+unsubscribe@googlegroups.com.
To post to this group, send email to google-web-toolkit@googlegroups.com.
Visit this group at http://groups.google.com/group/google-web-toolkit?hl=en.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

No comments:

Post a Comment