serverbrowser.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  1. /*
  2. author: ApmeM (artem.votincev@gmail.com)
  3. date: 9-June-2010
  4. version: 1.4
  5. download: http://code.google.com/p/jq-serverbrowse/
  6. */
  7. (function($) {
  8. $.fn.serverBrowser = function(settings) {
  9. this.each(function() {
  10. var config = {
  11. // Event function
  12. // Appear when user click 'Ok' button, or doubleclick on file
  13. onSelect: function(file) {
  14. alert('You select: ' + file);
  15. },
  16. onLoad: function() {
  17. return config.basePath;
  18. },
  19. multiselect: false,
  20. // Image parameters
  21. // System images (loading.gif, unknown.png, folder.png and images from knownPaths) will be referenced to systemImageUrl
  22. // if systemImageUrl is empty or not specified - imageUrl will be taken
  23. // All other images (like images for extension) will be taken from imageUrl
  24. imageUrl: 'img/',
  25. systemImageUrl: '',
  26. showUpInList: false,
  27. // Path properties
  28. // Base path, that links should start from.
  29. // If opened path is not under this path, alert will be shown and nothing will be opened
  30. // Path separator, that will be used to split specified paths and join paths to a string
  31. basePath: 'C:',
  32. separatorPath: '/',
  33. // Paths, that will be displayed on the left side of the dialog
  34. // This is a link to specified paths on the server
  35. useKnownPaths: true,
  36. knownPaths: [{text:'Desktop', image:'desktop.png', path:'C:/Users/All Users/Desktop'},
  37. {text:'Documents', image:'documents.png', path:'C:/Users/All Users/Documents'}],
  38. // Images for known extension (like 'png', 'exe', 'zip'), that will be displayed with its real names
  39. // Images, that is not in this list will be referenced to 'unknown.png' image
  40. // If list is empty - all images is known.
  41. knownExt: [],
  42. // Server path to this plugin handler
  43. handlerUrl: 'browserDlg.txt',
  44. // JQuery-ui dialog settings
  45. title: 'Browse',
  46. width: 300,
  47. height: 300,
  48. position: ['center', 'top'],
  49. // Administrative parameters used to
  50. // help programmer or system administrator
  51. requestMethod: 'POST',
  52. };
  53. if (settings) $.extend(config, settings);
  54. // Required configuration elements
  55. // We need to set some configuration elements without user
  56. // For example there should be 2 buttons on the bottom,
  57. // And dialog should be opened after button is pressed, not when it created
  58. // Also we need to know about dialog resizing
  59. $.extend(config, {
  60. autoOpen: false,
  61. modal: true,
  62. buttons: [
  63. {
  64. text: $.i18n._("Cancel"),
  65. "class": "btn",
  66. click: function() {
  67. browserDlg.dialog("close");
  68. }
  69. },
  70. {
  71. text: $.i18n._("Open"),
  72. "class": "btn",
  73. click: function() {
  74. doneOk();
  75. }
  76. }
  77. ],
  78. resize: function(event, ui) {
  79. recalculateSize(event, ui);
  80. },
  81. });
  82. function systemImageUrl()
  83. {
  84. if (config.systemImageUrl.length == 0) {
  85. return config.imageUrl;
  86. } else{
  87. return config.systemImageUrl;
  88. }
  89. }
  90. var privateConfig = {
  91. // This stack array will store history navigation data
  92. // When user open new directory, old directory will be added to this list
  93. // If user want, he will be able to move back by this history
  94. browserHistory: [],
  95. // This array contains all currently selected items
  96. // When user select element, it will add associated path into this array
  97. // When user deselect element - associated path will be removed
  98. // Exception: if 'config.multiselect' is false, only one element will be stored in this array.
  99. selectedItems: [],
  100. }
  101. // Main dialog div
  102. // It will be converted into jQuery-ui dialog box using my configuration parameters
  103. // It contains 3 divs
  104. var browserDlg = $('<div title="' + config.title + '"></div>').css({'overflow': 'hidden'}).appendTo(document.body);
  105. browserDlg.dialog(config);
  106. // First div on the top
  107. // It contains textbox field and buttons
  108. // User can enter any paths he want to open in this textbox and press enter
  109. // There is 3 buttons on the panel:
  110. var enterPathDiv = $('<div></div>').addClass('ui-widget-content').appendTo(browserDlg).css({'height': '30px', 'width': '100%', 'padding-top': '7px'});
  111. var enterButton = $('<div></div>').css({'float': 'left', 'vertical-align': 'middle', 'margin-left': '6px'}).addClass('ui-corner-all').hover(
  112. function() { $(this).addClass('ui-state-hover'); },
  113. function() { $(this).removeClass('ui-state-hover'); }
  114. );
  115. var enterLabel = $('<span></span>').text($.i18n._('Look in')+': ').appendTo(enterButton.clone(false).appendTo(enterPathDiv));
  116. var enterText = $('<input type="text">').keypress(function(e) {
  117. if (e.keyCode == '13') {
  118. e.preventDefault();
  119. loadPath(enterText.val());
  120. }
  121. }).appendTo(enterButton.clone(false).appendTo(enterPathDiv));
  122. // Back button.
  123. // When user click on it, 2 last elements of the history pop from the list, and reload second of them.
  124. var enterBack = $('<div></div>').addClass('ui-corner-all ui-icon ui-icon-circle-arrow-w').click(function(){
  125. privateConfig.browserHistory.pop(); // Remove current element. It is not required now.
  126. var backPath = config.basePath;
  127. if(privateConfig.browserHistory.length > 0){
  128. backPath = privateConfig.browserHistory.pop();
  129. }
  130. loadPath(backPath);
  131. }).appendTo(enterButton.clone(true).appendTo(enterPathDiv));
  132. // Level Up Button
  133. // When user click on it, last element of the history will be taken, and '..' will be applied to the end of the array.
  134. var enterUp = $('<div></div>').addClass('ui-corner-all ui-icon ui-icon-arrowreturnthick-1-n').click(function(){
  135. backPath = privateConfig.browserHistory[privateConfig.browserHistory.length - 1];
  136. if(backPath != config.basePath){
  137. loadPath(backPath + config.separatorPath + '..');
  138. }
  139. }).appendTo(enterButton.clone(true).appendTo(enterPathDiv));
  140. // Second div is on the left
  141. // It contains images and texts for pre-defined paths
  142. // User just click on them and it will open pre-defined path
  143. var knownPathDiv = $('<div></div>').addClass('ui-widget-content').css({'text-align':'center', 'overflow': 'auto', 'float': 'left', 'width': '100px'});
  144. if(config.useKnownPaths){
  145. knownPathDiv.appendTo(browserDlg);
  146. $.each(config.knownPaths, function(index, path) {
  147. var knownDiv = $('<div></div>').css({'margin':'10px'}).hover(
  148. function() { $(this).addClass('ui-state-hover'); },
  149. function() { $(this).removeClass('ui-state-hover'); }
  150. ).click(function() {
  151. loadPath(path.path);
  152. }).appendTo(knownPathDiv);
  153. $('<img />').attr({ src: systemImageUrl() + config.separatorPath + path.image }).css({ width: '32px', margin: '5px 10px 5px 5px' }).appendTo(knownDiv);
  154. $('<br/>').appendTo(knownDiv);
  155. $('<span></span>').text(path.text).appendTo(knownDiv);
  156. });
  157. }
  158. // Third div is everywhere :)
  159. // It show files and folders in the current path
  160. // User can click on path to select or deselect it
  161. // Doubleclick on path will open it
  162. // Also doubleclick on file will select this file and close dialog
  163. var browserPathDiv = $('<div></div>').addClass('ui-widget-content').css({'float': 'right', 'overflow': 'auto'}).appendTo(browserDlg);
  164. // Now everything is done
  165. // When user will be ready - he just click on the area you select for this plugin and dialog will appear
  166. $(this).click(function() {
  167. privateConfig.browserHistory = [];
  168. var startpath = removeBackPath(config.onLoad());
  169. startpath = startpath.split(config.separatorPath);
  170. startpath.pop();
  171. startpath = startpath.join(config.separatorPath);
  172. if(!checkBasePath(startpath)){
  173. startpath = config.basePath;
  174. }
  175. loadPath(startpath);
  176. browserDlg.dialog('open');
  177. recalculateSize();
  178. });
  179. // Function check if specified path is a child path of a 'config.basePath'
  180. // If it is not - user should see message, that path invalid, or path should be changed to valid.
  181. function checkBasePath(path){
  182. if(config.basePath == '')
  183. return true;
  184. var confPath = config.basePath.split(config.separatorPath);
  185. var curPath = path.split(config.separatorPath);
  186. if(confPath.length > curPath.length)
  187. return false;
  188. var result = true;
  189. $.each(confPath, function(index, partConfPath) {
  190. if(partConfPath != curPath[index]){
  191. result = false;
  192. }
  193. });
  194. return result;
  195. }
  196. // Function remove '..' parts of the path
  197. // Process depend on config.separatorPath option
  198. // On the server side you need to check / or \ separators
  199. function removeBackPath(path){
  200. var confPath = config.basePath.split(config.separatorPath);
  201. var curPath = path.split(config.separatorPath);
  202. var newcurPath = [];
  203. $.each(curPath, function(index, partCurPath) {
  204. if(partCurPath == ".."){
  205. newcurPath.pop();
  206. }else{
  207. newcurPath.push(partCurPath);
  208. }
  209. });
  210. return newcurPath.join(config.separatorPath);
  211. }
  212. // This function will be called when user click 'Open'
  213. // It check if any path is selected, and call config.onSelect function with path list
  214. function doneOk(){
  215. var newCurPath = [];
  216. $.each(privateConfig.selectedItems, function(index, item) {
  217. newCurPath.push($.data(item, 'path'));
  218. });
  219. if(newCurPath.length == 0) {
  220. newCurPath.push(privateConfig.browserHistory.pop());
  221. }
  222. if(config.multiselect)
  223. config.onSelect(newCurPath);
  224. else {
  225. if(newCurPath.length == 1) {
  226. config.onSelect(newCurPath[0]);
  227. } else if(newCurPath.length > 1){
  228. alert('Plugin work incorrectly. If error repeat, please add issue into http://code.google.com/p/jq-serverbrowse/issues/list with steps to reproduce.');
  229. return;
  230. }
  231. }
  232. browserDlg.dialog("close");
  233. }
  234. // Function recalculate and set new width and height for left and right div elements
  235. // height have '-2' because of the borders
  236. // width have '-4' because of a border an 2 pixels space between divs
  237. function recalculateSize(event, ui){
  238. knownPathDiv.css({'height' : browserDlg.height() - enterPathDiv.outerHeight(true) - 2});
  239. browserPathDiv.css({'height' : browserDlg.height() - enterPathDiv.outerHeight(true) - 2,
  240. 'width' : browserDlg.width() - knownPathDiv.outerWidth(true) - 4});
  241. }
  242. // Function adds new element into browserPathDiv element depends on file parameters
  243. // If file.isError is set, error message will be displayed instead of clickable area
  244. // Clickable div contain image from extension and text from file parameter
  245. function addElement(file){
  246. var itemDiv = $('<div></div>').css({ margin: '2px' }).appendTo(browserPathDiv);
  247. if(file.isError)
  248. {
  249. itemDiv.addClass('ui-state-error ui-corner-all').css({padding: '0pt 0.7em'});
  250. var p = $('<p></p>').appendTo(itemDiv);
  251. $('<span></span>').addClass('ui-icon ui-icon-alert').css({'float': 'left', 'margin-right': '0.3em'}).appendTo(p);
  252. $('<span></span>').text(file.name).appendTo(p);
  253. }else
  254. {
  255. var fullPath = file.path + config.separatorPath + file.name;
  256. itemDiv.hover(
  257. function() { $(this).addClass('ui-state-hover'); },
  258. function() { $(this).removeClass('ui-state-hover'); }
  259. );
  260. var itemImage = $('<img />').css({ width: '16px', margin: '0 5px 0 0' }).appendTo(itemDiv);
  261. var itemText = $('<span></span>').text(file.name).appendTo(itemDiv);
  262. if (file.isFolder)
  263. itemImage.attr({ src: systemImageUrl() + 'folder.png' });
  264. else {
  265. ext = file.name.split('.').pop();
  266. var res = '';
  267. if (ext == '' || ext == file.name || (config.knownExt.length > 0 && $.inArray(ext, config.knownExt) < 0))
  268. itemImage.attr({ src: systemImageUrl() + 'unknown.png' });
  269. else
  270. itemImage.attr({ src: config.imageUrl + ext + '.png' });
  271. }
  272. $.data(itemDiv, 'path', fullPath);
  273. itemDiv.unbind('click').bind('click', function(e) {
  274. if(!$(this).hasClass('ui-state-active')) {
  275. if(!config.multiselect && privateConfig.selectedItems.length > 0) {
  276. $(privateConfig.selectedItems[0]).click();
  277. }
  278. privateConfig.selectedItems.push(itemDiv);
  279. }else{
  280. var newCurPath = [];
  281. $.each(privateConfig.selectedItems, function(index, item) {
  282. if($.data(item, 'path') != fullPath)
  283. newCurPath.push(item);
  284. });
  285. privateConfig.selectedItems = newCurPath;
  286. }
  287. $(this).toggleClass('ui-state-active');
  288. });
  289. itemDiv.unbind('dblclick').bind('dblclick', function(e) {
  290. if (file.isFolder){
  291. loadPath(fullPath);
  292. } else {
  293. privateConfig.selectedItems = [itemDiv];
  294. doneOk();
  295. }
  296. });
  297. }
  298. }
  299. // Main plugin function
  300. // When user enter path manually, select it from pre-defined path, or doubleclick in browser this function will call
  301. // It send a request on the server to retrieve child directories and files of the specified path
  302. // If path is not under 'config.basePath', alert will be shown and nothing will be opened
  303. function loadPath(path) {
  304. privateConfig.selectedItems = [];
  305. // First we need to remove all '..' parts of the path
  306. path = removeBackPath(path);
  307. // Then we need to check, if path based on 'config.basePath'
  308. if(!checkBasePath(path)) {
  309. alert('Path should be based from ' + config.basePath);
  310. return;
  311. }
  312. // Then we can put this path into history
  313. privateConfig.browserHistory.push(path);
  314. // Show it to user
  315. enterText.val(path);
  316. // And load
  317. $.ajax({
  318. url: config.handlerUrl,
  319. type: config.requestMethod,
  320. data: {
  321. action: 'browse',
  322. path: path,
  323. time: new Date().getTime()
  324. },
  325. beforeSend: function() {
  326. browserPathDiv.empty().css({ 'text-align': 'center' });
  327. $('<img />').attr({ src: systemImageUrl() + 'loading.gif' }).css({ width: '32px' }).appendTo(browserPathDiv);
  328. },
  329. success: function(files) {
  330. browserPathDiv.empty().css({ 'text-align': 'left' });
  331. if(path != config.basePath && config.showUpInList){
  332. addElement({name: '..', isFolder: true, isError: false, path: path});
  333. }
  334. $.each(files, function(index, file) {
  335. addElement($.extend(file, {path: path}));
  336. });
  337. },
  338. dataType: 'json'
  339. });
  340. }
  341. });
  342. return this;
  343. };
  344. })(jQuery);