Archive

Archive for May, 2010

Drag-n-Drop from Desktop jQuery Plugin

May 21, 2010 5 comments

I created a jQuery plugin for uploading files using the Drag-n-drop from the desktop feature of the File API of HTML5. I’ve only used this with Firefox 3.6, which is one of the very few browsers that currently supports this feature. The plugin posts the file data the same way that a file picker input field would.

When you run the example page below, you should see the drop target div appear as a red box 100 by 100, and then when you drag a file from the desktop over the drop target it should turn green. Then you let go of the file and the page will post it to upload.xqy, just like the previous post where we used a file picker to upload.

The plugin takes three parameters:
1. The url to post the file to
2. The name of the field in the POST data that will contain the file data
3. Options for changing the class of the drop target element during drag events

Note: The code below is from an XQuery page which is why there are double braces (“{{” and “}}”) to escape the braces. Also you probably will want to serve the HTML page from a server, not the filesystem, because the browser doesn’t seem to actually post the data unless this is the case.

example HTML page

<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <style type="text/css">
            #dragtarget {{ width: 100; height: 100 }}
            .dragleave {{ background: red }}
            .dragenter {{ background: green }}
        </style>
        <script type="text/javascript" src="jquery-1.4.2.min.js"></script>
        <script type="text/javascript" src="jquery.dragUploadable.js"></script>
        <script type="text/javascript">
            $(function()
                {{ 
                	$('#dragtarget').dragUploadable("upload.xqy", "upload", {{
                        dragleaveClass: "dragleave",
                        dragenterClass: "dragenter"
     			    }});
                }});
        </script>
    </head>
    <body>
        <div id="dragtarget" class="dragleave">Drop Zone</div>
    </body>
</html>

upload.xqy (same as in previous post)

let $upload-fieldname := "upload"
let $image := xdmp:get-request-field( $upload-fieldname )
let $filename := xdmp:get-request-field-filename($upload-fieldname)
let $path := fn:concat("/somewhere/", $filename)
let $doc-saved := xdmp:save($path, $image)
return $image

Save the following as “jquery.dragUploadable.js”

        (function($){
             $.fn.dragUploadable = function(postURL, fieldName, options) {
            
              var defaults = {
                dragenterClass: "",
                dragleaveClass: ""
              };
              var options = $.extend(defaults, options);
                
              return this.each(function() {
                    obj = $(this);  
                    obj.bind("dragenter", function(event){
                        obj.removeClass(options.dragleaveClass);
                        obj.addClass(options.dragenterClass);
                        event.stopPropagation();
                        event.preventDefault();
                    }, false);
				    obj.bind("dragover", function(event){
				        event.stopPropagation();
				        event.preventDefault();
				        }, false);
				    obj.bind("dragleave", function(event){
                        obj.removeClass(options.dragenterClass);
                        obj.addClass(options.dragleaveClass);
                        event.stopPropagation();
                        event.preventDefault();
                    }, false);
				    obj.bind("drop", function(event){
				        var data = event.originalEvent.dataTransfer;
				        upload(postURL, fieldName, data);
				        event.stopPropagation();
                        event.preventDefault();
				      }, false);
              });
             };
            })(jQuery);

        
        function dropSetup() {
            var dropContainer = document.getElementById("output");
				
				dropContainer.addEventListener("dragenter", function(event){dropContainer.innerHTML = 'DROP';event.stopPropagation();event.preventDefault();}, false);
				dropContainer.addEventListener("dragover", function(event){event.stopPropagation();event.preventDefault();}, false);
				dropContainer.addEventListener("drop", upload, false);
        };

        function upload(postURL, fieldName, data) {

            var boundary = '------multipartformboundary' + (new Date).getTime();
            var dashdash = '--';
            var crlf     = '\r\n';
        
            /* Build RFC2388 string. */
            var builder = '';
        
            builder += dashdash;
            builder += boundary;
            builder += crlf;
            
            var xhr = new XMLHttpRequest();
            
            /* For each dropped file. */
            for (var i = 0; i < data.files.length; i++) {
                var file = data.files[i];
        
                /* Generate headers. */            
                builder += 'Content-Disposition: form-data; name="' + fieldName + '"';
                if (file.fileName) {
                  builder += '; filename="' + file.fileName + '"';
                }
                builder += crlf;
        
                builder += 'Content-Type: application/octet-stream';
                builder += crlf;
                builder += crlf; 
        
                /* Append binary data. */
                builder += file.getAsBinary();
                builder += crlf;
        
                /* Write boundary. */
                builder += dashdash;
                builder += boundary;
                builder += crlf;
            }
            
            /* Mark end of the request. */
            builder += dashdash;
            builder += boundary;
            builder += dashdash;
            builder += crlf;
        
            xhr.open("POST", postURL, true);
            xhr.setRequestHeader('content-type', 'multipart/form-data; boundary=' 
                + boundary);
            xhr.sendAsBinary(builder);        
            
            xhr.onload = function(event) { 
                /* Response from server */
                if (xhr.responseText) {
                    alert(xhr.responseText);
                }
              
            };

        }

Categories: Tips n' Tricks

Uploading Files Through HTML Using MarkLogic

May 20, 2010 2 comments

Here’s an example of how to upload an image through an HTML page and save it to the filesystem using MarkLogic:

upload.html

<html xmlns="http://www.w3.org/1999/xhtml">
    <body>
        <form method="post" action="upload.xqy" enctype="multipart/form-data">
            <input name="upload" type="file" />
            <input type="submit" value="Upload" />
        </form>
    </body>
</html>

upload.xqy

let $upload-fieldname := "upload"
let $image := xdmp:get-request-field( $upload-fieldname )
let $filename := xdmp:get-request-field-filename($upload-fieldname)
let $path := fn:concat("/somewhere/", $filename)
let $doc-saved := xdmp:save($path, $image)
return $image

Note that xdmp:get-request-field() returns the binary content of the upload field and xdmp:get-request-field-filename() gets the filename of the file being uploaded.

FYI: I was using Firefox 3.6. I hear that IE puts the full filepath in for the filename, so be aware of different browser behaviors.

Categories: Tips n' Tricks

The value of fn:doc() [with no parameters]

May 18, 2010 2 comments

UPDATE: I totally got it wrong that MarkLogic was the only implementation that could do something like fn:doc() without parameters. Rob Whitby schooled me that fn:collection() with no parameters is in the spec and that this is implemented by other XQuery engines. So the information below is not quite accurate. But the advantages of being able to query the entire database as a single document remain. End update.

—————–

I started learning XQuery using the MarkLogic platform and sometimes it’s not immediately clear to me what is an advantage because of XQuery and what is an advantage because of MarkLogic. Recently I have been tinkering with other XQuery engines and environments and I have learned a lot from the differences of implementations. One thing I have really come to appreciate is MarkLogic’s implementation of fn:doc().

According to the XQuery spec, the fn:doc() function takes one parameter which is the URI of a document in the database. If you pass it the empty sequence, you get the empty sequence back. This effectively means that you have to know the document you want to get before you use this function. But when you have a lot of documents in the database, you may not know which document has the data you are looking for.

For example, if you have a lot of books that you want to keep in the database, you would have to put all the book data into one document and then use XPath to find the data you want:

books.xml

<books>
    <book>
        <title>Where the Red Fern Grows</title>
    </book>
    <book>
        <title>To Kill a Mockingbird</title>
    </book>
</books>

XQuery code:

let $books := fn:doc("/books.xml")
return $books/book[title eq "Where the Red Fern Grows"]

As far as I can tell, this is how Oracle, Sausalito, eXist, and Zorba are implemented. Although these implementations may have other ways to search the database (as does MarkLogic), I would much rather use XPath to select data, rather than have to do a search across elements. I don’t want to have to do a search to select elements.

MarkLogic has implement fn:doc() so that if you don’t pass it a parameter it includes all documents in the database that match your XPath expression. So rather than having to know ahead of time the document you want to get, or having to use a search function to select the elements you want, you can just use fn:doc() with no parameter and the entire database acts like a single document in terms of node selection.

For example, using the same example, this query returns the book whose title match “Where the Red Fern Grows” regardless of what document it is in:

book_1.xml

<book>
    <title>Where the Red Fern Grows</title>
</book>

book_2.xml

<book>
    <title>To Kill a Mockingbird</title>
</book>

XQuery code:

fn:doc()/book[title eq "Where the Red Fern Grows"]

This is very powerful because then you can have each book be its own document in the database, which means you can have an arbitrary number of “book” documents being inserted\updated independently and not interfere with other “book” documents.

But to even take it further, you can use the shorthand for the “descendant-or-self” axis in XPath (“//”) to select all book elements in all levels of all documents in the entire database that match your query.

fn:doc()//book[title eq "Where the Red Fern Grows"]

This provides lots of flexibility in data design and allows you to use all the power of XPath to select whatever you want without having to worry about documents at all. This means you never have to know a URI. Everything is retrieved from the database by value only. All you have to deal with are elements and attributes, not URIs or documents, unless you want to. It’s just a gigantic cloud of data.

I suspect, but I don’t know for sure, that the reason that the MarkLogic server can do this is because it indexes every document automatically. If it didn’t, using fn:doc() with no parameter would mean that the server would have to open every document in the database, perform the XPath expression on it, consolidate the results and return them. I doubt performance for that would be very good.

Until other implementations support fn:doc() with no parameters, MarkLogic is really going to be the superior implementation of XQuery, especially for large datasets. This is another example of how the MarkLogic server as a hybrid database\app server\search server can do things that really no other technology can, and I don’t know if that is always understood or appreciated when it is evaluated.

Categories: commentary, newbie track

5 Things I Learned at the MarkLogic User Conference

May 12, 2010 1 comment

This was the first MarkLogic conference I’ve been to, and I’ve been developing with MarkLogic for less than a year. So this really was a good opportunity for me to learn more about the company, the industry, other customers, and the future of the technology. Read more…

Categories: commentary

Non-obtrusive i18n

May 5, 2010 1 comment

Note: This approach builds on the concept of Non-Obstrusive HTML Replacing discussed earlier. I’d recommend reading that before reading this post.

Treating an HTML page as XML really starts to open up some powerful abilities. Following on the theme of being “non-obtrusive” to the HTML file, Internationalization (i18n) can be achieved in very elegant way. Read more…

Categories: Uncategorized

Non-obtrusive HTML Replacing (non-ob)

I created a page about a new approach for creating dynamic pages. I think this has real potential and is something that only XQuery can do. Java, Rails, PHP, and .NET can’t do this approach in a performant way.

http://xquerywebappdev.wordpress.com/non-obtrusive-html-replacing-non-ob/

Categories: Tips n' Tricks

Elegance

In order to deal with the increased complexity of computer systems and logic, different conceptual models have been developed. Of course one of the most popular is the notion of “objects” that model or represent something in the code. Frameworks also mask complexity so that the developer only needs to work with a framework, not the details underneath. The direction of programming is towards less complexity, or at least masking the complexity so that higher-order problems can be solved. Read more…

Categories: commentary

Don’t forget to check the functx functions

More than once I have written a utility function only to find out afterwards that the same functionality was covered by a functx function, and written better than mine. So just a tip to always check the available functx functions before writing your own (see http://www.xqueryfunctions.com/xq/).

The functx functions are included in the MarkLogic server. To import the functx module, use this import statement:

import module namespace functx = "http://www.functx.com" at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy";

I often use substring-after-last() to get the filename from a complete file path.

import module namespace functx = "http://www.functx.com" at "/MarkLogic/functx/functx-1.0-nodoc-2007-01.xqy";

let $file-path := "/a/b/c.xml"

return functx:substring-after-last($file-path, "/")
=> c.xml

Categories: Tips n' Tricks

Be aware of the different XQuery dialects in MarkLogic

May 1, 2010 3 comments

When I first started learning XQuery, I read the XQuery book by O’Reilley. But I didn’t remember reading about how to do “appy” things like setting HTTP response codes, reading request parameters, logging, alerting, reading from the filesystem etc. So I read the O’Reilley book again, and then I got pretty nervous because I still couldn’t find how to do those things. After asking around I came to realize that there is the XQuery specification, and then there are the functions that MarkLogic provides to do the “appy” things. But that’s not even quite right. There are different dialects of XQuery, including one from MarkLogic that extends the language itself, and then there also are functions MarkLogic provides to that enable you to do the “appy” things. In fact, without MarkLogic’s enhancements to the language and “appy” functions it provides, it would not be possible to use XQuery for the application code of a mature application. Read more…

Categories: newbie track
Follow

Get every new post delivered to your Inbox.