/*
author: ApmeM (artem.votincev@gmail.com)
date: 9-June-2010
version: 1.4
download: http://code.google.com/p/jq-serverbrowse/
*/
(function($) {
$.fn.serverBrowser = function(settings) {
this.each(function() {
var config = {
// Event function
// Appear when user click 'Ok' button, or doubleclick on file
onSelect : function(file) {
alert('You select: ' + file);
},
onLoad : function() {
return config.basePath;
},
multiselect : false,
// Image parameters
// System images (loading.gif, unknown.png, folder.png and
// images from knownPaths) will be referenced to systemImageUrl
// if systemImageUrl is empty or not specified - imageUrl will
// be taken
// All other images (like images for extension) will be taken
// from imageUrl
imageUrl : 'img/',
systemImageUrl : '',
showUpInList : false,
// Path properties
// Base path, that links should start from.
// If opened path is not under this path, alert will be shown
// and nothing will be opened
// Path separator, that will be used to split specified paths
// and join paths to a string
basePath : 'C:',
separatorPath : '/',
// Paths, that will be displayed on the left side of the dialog
// This is a link to specified paths on the server
useKnownPaths : true,
knownPaths : [ {
text : 'Desktop',
image : 'desktop.png',
path : 'C:/Users/All Users/Desktop'
}, {
text : 'Documents',
image : 'documents.png',
path : 'C:/Users/All Users/Documents'
} ],
// Images for known extension (like 'png', 'exe', 'zip'), that
// will be displayed with its real names
// Images, that is not in this list will be referenced to
// 'unknown.png' image
// If list is empty - all images is known.
knownExt : [],
// Server path to this plugin handler
handlerUrl : 'browserDlg.txt',
// JQuery-ui dialog settings
title : 'Browse',
width : 300,
height : 300,
position : [ 'center', 'center' ],
// Administrative parameters used to
// help programmer or system administrator
requestMethod : 'POST'
};
if (settings)
$.extend(config, settings);
// Required configuration elements
// We need to set some configuration elements without user
// For example there should be 2 buttons on the bottom,
// And dialog should be opened after button is pressed, not when it
// created
// Also we need to know about dialog resizing
$.extend(config, {
autoOpen : false,
modal : true,
buttons : {
"Open" : function() {
doneOk();
},
"Cancel" : function() {
browserDlg.dialog("close");
}
},
resize : function(event, ui) {
recalculateSize(event, ui);
}
});
function systemImageUrl() {
if (config.systemImageUrl.length == 0) {
return config.imageUrl;
} else {
return config.systemImageUrl;
}
}
var privateConfig = {
// This stack array will store history navigation data
// When user open new directory, old directory will be added to
// this list
// If user want, he will be able to move back by this history
browserHistory : [],
// This array contains all currently selected items
// When user select element, it will add associated path into
// this array
// When user deselect element - associated path will be removed
// Exception: if 'config.multiselect' is false, only one element
// will be stored in this array.
selectedItems : []
};
// Main dialog div
// It will be converted into jQuery-ui dialog box using my
// configuration parameters
// It contains 3 divs
var browserDlg = $('
').css( {
'overflow' : 'hidden'
}).appendTo(document.body);
browserDlg.dialog(config);
// First div on the top
// It contains textbox field and buttons
// User can enter any paths he want to open in this textbox and
// press enter
// There is 3 buttons on the panel:
var enterPathDiv = $('').addClass('ui-widget-content').appendTo(browserDlg).css( {
'height' : '30px',
'width' : '100%',
'padding-top' : '7px'
});
var enterButton = $('').css( {
'float' : 'left',
'vertical-align' : 'middle',
'margin-left' : '6px'
}).addClass('ui-corner-all').hover(function() {
$(this).addClass('ui-state-hover');
}, function() {
$(this).removeClass('ui-state-hover');
});
var enterLabel = $('').text('Look in: ').appendTo(enterButton.clone(false).appendTo(enterPathDiv));
var enterText = $('').keypress(function(e) {
if (e.keyCode == '13') {
e.preventDefault();
loadPath(enterText.val());
}
}).css('width', '200px').appendTo(enterButton.clone(false).appendTo(enterPathDiv));
// Back button.
// When user click on it, 2 last elements of the history pop from
// the list, and reload second of them.
var enterBack = $('').addClass('ui-corner-all ui-icon ui-icon-circle-arrow-w').click(function() {
privateConfig.browserHistory.pop(); // Remove current element.
// It is not required now.
var backPath = config.basePath;
if (privateConfig.browserHistory.length > 0) {
backPath = privateConfig.browserHistory.pop();
}
loadPath(backPath);
}).appendTo(enterButton.clone(true).appendTo(enterPathDiv));
// Level Up Button
// When user click on it, last element of the history will be taken,
// and '..' will be applied to the end of the array.
var enterUp = $('').addClass('ui-corner-all ui-icon ui-icon-arrowreturnthick-1-n').click(function() {
backPath = privateConfig.browserHistory[privateConfig.browserHistory.length - 1];
if (backPath != config.basePath) {
loadPath(backPath + config.separatorPath + '..');
}
}).appendTo(enterButton.clone(true).appendTo(enterPathDiv));
// Second div is on the left
// It contains images and texts for pre-defined paths
// User just click on them and it will open pre-defined path
var knownPathDiv = $('').addClass('ui-widget-content').css( {
'text-align' : 'center',
'overflow' : 'auto',
'float' : 'left',
'width' : '100px'
});
if (config.useKnownPaths) {
knownPathDiv.appendTo(browserDlg);
$.each(config.knownPaths, function(index, path) {
var knownDiv = $('').css( {
'margin' : '10px'
}).hover(function() {
$(this).addClass('ui-state-hover');
}, function() {
$(this).removeClass('ui-state-hover');
}).click(function() {
loadPath(path.path);
}).appendTo(knownPathDiv);
$('').attr( {
src : systemImageUrl() + config.separatorPath + path.image
}).css( {
width : '32px',
margin : '5px 10px 5px 5px'
}).appendTo(knownDiv);
$(' ').appendTo(knownDiv);
$('').text(path.text).appendTo(knownDiv);
});
}
// Third div is everywhere :)
// It show files and folders in the current path
// User can click on path to select or deselect it
// Doubleclick on path will open it
// Also doubleclick on file will select this file and close dialog
var browserPathDiv = $('').addClass('ui-widget-content').css( {
'float' : 'right',
'overflow' : 'auto'
}).appendTo(browserDlg);
// Now everything is done
// When user will be ready - he just click on the area you select
// for this plugin and dialog will appear
$(this).click(function() {
privateConfig.browserHistory = [];
var startpath = removeBackPath(config.onLoad());
startpath = startpath.split(config.separatorPath);
startpath.pop();
startpath = startpath.join(config.separatorPath);
if (!checkBasePath(startpath)) {
startpath = config.basePath;
}
loadPath(startpath);
browserDlg.dialog('open');
recalculateSize();
});
// Function check if specified path is a child path of a
// 'config.basePath'
// If it is not - user should see message, that path invalid, or
// path should be changed to valid.
function checkBasePath(path) {
if (config.basePath == '')
return true;
var confPath = config.basePath.split(config.separatorPath);
var curPath = path.split(config.separatorPath);
if (confPath.length > curPath.length)
return false;
var result = true;
$.each(confPath, function(index, partConfPath) {
if (partConfPath != curPath[index]) {
result = false;
}
});
return result;
}
// Function remove '..' parts of the path
// Process depend on config.separatorPath option
// On the server side you need to check / or \ separators
function removeBackPath(path) {
var confPath = config.basePath.split(config.separatorPath);
var curPath = path.split(config.separatorPath);
var newcurPath = [];
$.each(curPath, function(index, partCurPath) {
if (partCurPath == "..") {
newcurPath.pop();
} else {
newcurPath.push(partCurPath);
}
});
return newcurPath.join(config.separatorPath);
}
// This function will be called when user click 'Open'
// It check if any path is selected, and call config.onSelect
// function with path list
function doneOk() {
var newCurPath = [];
$.each(privateConfig.selectedItems, function(index, item) {
newCurPath.push($.data(item, 'path'));
});
if (newCurPath.length == 0) {
newCurPath.push(privateConfig.browserHistory.pop());
}
if (config.multiselect)
config.onSelect(newCurPath);
else {
if (newCurPath.length == 1) {
config.onSelect(newCurPath[0]);
} else if (newCurPath.length > 1) {
alert('Plugin work incorrectly. If error repeat, please add issue into http://code.google.com/p/jq-serverbrowse/issues/list with steps to reproduce.');
return;
}
}
browserDlg.dialog("close");
}
// Function recalculate and set new width and height for left and
// right div elements
// height have '-2' because of the borders
// width have '-4' because of a border an 2 pixels space between
// divs
function recalculateSize(event, ui) {
knownPathDiv.css( {
'height' : browserDlg.height() - enterPathDiv.outerHeight(true) - 2
});
browserPathDiv.css( {
'height' : browserDlg.height() - enterPathDiv.outerHeight(true) - 2,
'width' : browserDlg.width() - knownPathDiv.outerWidth(true) - 4
});
}
// Function adds new element into browserPathDiv element depends on
// file parameters
// If file.isError is set, error message will be displayed instead
// of clickable area
// Clickable div contain image from extension and text from file
// parameter
function addElement(file) {
var itemDiv = $('').css( {
margin : '2px'
}).appendTo(browserPathDiv);
if (file.isError) {
itemDiv.addClass('ui-state-error ui-corner-all').css( {
padding : '0pt 0.7em'
});
var p = $('').appendTo(itemDiv);
$('').addClass('ui-icon ui-icon-alert').css( {
'float' : 'left',
'margin-right' : '0.3em'
}).appendTo(p);
$('').text(file.name).appendTo(p);
} else {
var fullPath = file.path + config.separatorPath + file.name;
itemDiv.hover(function() {
$(this).addClass('ui-state-hover');
}, function() {
$(this).removeClass('ui-state-hover');
});
var itemImage = $('').css( {
width : '16px',
margin : '0 5px 0 0'
}).appendTo(itemDiv);
var itemText = $('').text(file.name).appendTo(itemDiv);
if (file.isFolder)
itemImage.attr( {
src : systemImageUrl() + 'folder.png'
});
else {
ext = file.name.split('.').pop();
var res = '';
if (ext == '' || ext == file.name || (config.knownExt.length > 0 && $.inArray(ext, config.knownExt) < 0))
itemImage.attr( {
src : systemImageUrl() + 'unknown.png'
});
else
itemImage.attr( {
src : config.imageUrl + ext + '.png'
});
}
$.data(itemDiv, 'path', fullPath);
itemDiv.unbind('click').bind('click', function(e) {
if (!$(this).hasClass('ui-state-active')) {
if (!config.multiselect && privateConfig.selectedItems.length > 0) {
$(privateConfig.selectedItems[0]).click();
}
privateConfig.selectedItems.push(itemDiv);
} else {
var newCurPath = [];
$.each(privateConfig.selectedItems, function(index, item) {
if ($.data(item, 'path') != fullPath)
newCurPath.push(item);
});
privateConfig.selectedItems = newCurPath;
}
$(this).toggleClass('ui-state-active');
});
itemDiv.unbind('dblclick').bind('dblclick', function(e) {
if (file.isFolder) {
loadPath(fullPath);
} else {
privateConfig.selectedItems = [ itemDiv ];
doneOk();
}
});
}
}
// Main plugin function
// When user enter path manually, select it from pre-defined path,
// or doubleclick in browser this function will call
// It send a request on the server to retrieve child directories and
// files of the specified path
// If path is not under 'config.basePath', alert will be shown and
// nothing will be opened
function loadPath(path) {
privateConfig.selectedItems = [];
// First we need to remove all '..' parts of the path
path = removeBackPath(path);
// Then we need to check, if path based on 'config.basePath'
if (!checkBasePath(path)) {
alert('Path should be based from ' + config.basePath);
return;
}
// Then we can put this path into history
privateConfig.browserHistory.push(path);
// Show it to user
enterText.val(path);
// And load
$.ajax( {
url : config.handlerUrl,
type : config.requestMethod,
data : {
action : 'browse',
path : path,
time : new Date().getTime()
},
beforeSend : function() {
browserPathDiv.empty().css( {
'text-align' : 'center'
});
$('').attr( {
src : systemImageUrl() + 'loading.gif'
}).css( {
width : '32px'
}).appendTo(browserPathDiv);
},
success : function(files) {
browserPathDiv.empty().css( {
'text-align' : 'left'
});
if (path != config.basePath && config.showUpInList) {
addElement( {
name : '..',
isFolder : true,
isError : false,
path : path
});
}
$.each(files, function(index, file) {
addElement($.extend(file, {
path : path
}));
});
},
dataType : 'json'
});
}
});
return this;
};
})(jQuery);