Making the Viewer
HTML5 currently does not have a way to display multipage documents embedded in a web application. In this CodeProject lab we are going to build an UI control to display multiple pages in a single container. When creating a reusable component in Visual Studio I prefer to start with a blank web application.
Select File > New Project > Web > Blank Web Application
To begin creating the viewer object I like to think about how I want to use it and the simplest way to set up a viewer dynamically for the user is something like this:
var viewer = CreateViewer("#myViewerDiv");
viewer.OpenFile("default");
We use a factory method to instantiate the viewer and a simple viewer API method to open the images. All right, easy enough, so now we can create the js library file.
Select the project in the solution explorer and add a new Javascript file to the project. Once this is created we can make the CreateViewer function’s skeleton
function CreateViewer(target){
var viewerobj = {
ui:$(target),
OpenFile: function(path) {}
};
viewerobj.ui.addClass("ViewerUI");
}
With function we have now marked a div as the viewer and applied a style class to it so we can appropriately style it. For starters I recommend this stylesheet:
.ViewerUI
{
width:98vw;
height:600px;
border: 1px solid black;
background-color: lightgray;
overflow-y:auto;
overflow-x:scroll;
}
We can now run the application to see the application of the styles
Good! We are making progress. Now we need to load the images into the div from the server. To accomplish this we will need to make some sort of serverside functionality to gather and return the images. For this I am going to use a asp.net generic handler object.
Select your project in the solution explorer and add a new Generic Handler object
public void ProcessRequest(HttpContext context)
{
string directorypath = context.Server.MapPath("/Images/");
string target = context.Request.QueryString["target"];
context.Response.Write(GetFolder(directorypath,target));
}
Then we need to create the GetFolder
function which will return the relevant data to the clientside of the images in a specific folder on the server. The relevant data for each image for the purposes of the clientside are:
- ImageURL (to be able to open the image)
- ImageSize (to be able to scale the img tag)
I prefer complexities of data to be handled on the serverside, so in the function below I am creating a JSON string for the server's return data while getting all of the images in the folder.
public string GetFolder(string basedir, string target)
{
string[] images = Directory.GetFiles(basedir+"\\"+target);
StringBuilder sb = new StringBuilder();
sb.Append("{images:[");
int emptysize = sb.Length;
foreach (string path in images)
{
string filename = Path.GetFileName(path);
sb.Append("{");
sb.Append(CreateImageObject(new Bitmap(path),"\\\\Images\\\\"+target+"\\\\"+filename));
sb.Append("}");
sb.Append(",");
}
if(sb.Length!=emptysize)
sb.Remove(sb.Length-1, 1);
sb.Append("]}");
return sb.ToString();
}
I apologize for the verbosity of the code above, but I felt it was important for the purpose of this exersise to not use any short cuts to create a JSON string that could be consumed on the client side for this purpose. So the last function needed on the client side is CreateImageObject
, that simply formats the required data for each image in the folder to be displayed in the viewer.
private string CreateImageObject(Bitmap bitmap, string filename)
{
StringBuilder sb = new StringBuilder();
sb.Append("width:");
sb.Append(bitmap.Width);
sb.Append(",");
sb.Append("height:");
sb.Append(bitmap.Height);
sb.Append(",");
sb.Append("filename:");
sb.Append("\"");
sb.Append(filename);
sb.Append("\"");
return sb.ToString();
}
With that function in place the serverside is complete. We now have a function that will take a folder name and return a JSON string describing the images contained in the folder. All that is left is to have the client call for the information and add the images to the DOM. To do this we go back to the clientside js file:
OpenFile: function(path) {
var send = { Open: "folder", target: imageTarget }
ui.children().remove();
$.get("ViewerHandler.ashx", send, imageDataRecieved);
}
The function is three simple steps. Make a data object to send to the server, clear the viewer, then send the request with a callback function. The callback function then will be used to consume the returned data object finally creating the viewer. The most important function of this viewer is to provide properly sized images dynamically based on the space the viewer has to work with.
function imageDataRecieved(data,status,jqXHR)
{
var images = eval(data);
var viewerWidth = _vobj.ui.width()-10-20; images.forEach(function (image) {
var ratio = viewerWidth / image.width;
var img = jQuery("<img></img>");
img.attr("src", image.filename);
img.css("width", viewerWidth);
img.css("height", image.height * ratio);
img.css("border", "1px solid black");
img.css("margin-left", "5px");
img.css("margin-bottom", "5px");
_vobj.ui.append(img); });
}
This final function takes the JSON string, converts it into usable data and then puts each image into the viewer after sizing it to fit properly while maintaining each images aspect ratio. With the last function in place running the page yields our first functional viewer.
Extending the viewer
Extending a viewer has the most to do with what needs to be done with it. One of the main reasons to have a html viewer is to add support for image formats that browsers do not support natively. The most common formats for scanned documents are PDF and TIFF. To add support for those formats the server would have to be able to open those files and create temporary single page web native image files for each page contained within.
Atalasoft provides PDF and TIFF decoders and a pre-built viewer to display those file formats. Visit www.atalasoft.com/webdocumentviewer to learn more.
If there is anything else that you would need a viewer to do, please post in the comments and I may be able to follow up and create more extensions for this basic viewer in later posts.