Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: raphaelschroiff/NodeSpreadsheet
base: 61490a0a23
...
head fork: raphaelschroiff/NodeSpreadsheet
compare: 6e64d798ba
Checking mergeability… Don't worry, you can still create the pull request.
  • 2 commits
  • 182 files changed
  • 0 commit comments
  • 1 contributor
Showing with 22,757 additions and 0 deletions.
  1. +312 −0 app.js
  2. +11 −0 package.json
  3. +13 −0 public/code/index.js
  4. +931 −0 public/code/spreadsheet.js
  5. BIN  public/jQuery.sheet/images/arrow_down.png
  6. BIN  public/jQuery.sheet/images/arrow_out.png
  7. BIN  public/jQuery.sheet/images/arrow_refresh.png
  8. BIN  public/jQuery.sheet/images/arrow_up.png
  9. BIN  public/jQuery.sheet/images/decrease-font.png
  10. BIN  public/jQuery.sheet/images/disk.png
  11. BIN  public/jQuery.sheet/images/find.png
  12. BIN  public/jQuery.sheet/images/function.png
  13. BIN  public/jQuery.sheet/images/increase-font.png
  14. BIN  public/jQuery.sheet/images/logo.png
  15. BIN  public/jQuery.sheet/images/noparse.png
  16. BIN  public/jQuery.sheet/images/page_link.png
  17. BIN  public/jQuery.sheet/images/palette.png
  18. BIN  public/jQuery.sheet/images/palette_bg.png
  19. BIN  public/jQuery.sheet/images/sheet_col_add.png
  20. BIN  public/jQuery.sheet/images/sheet_col_add_multi.png
  21. BIN  public/jQuery.sheet/images/sheet_col_delete.png
  22. BIN  public/jQuery.sheet/images/sheet_get_range.png
  23. BIN  public/jQuery.sheet/images/sheet_row_add.png
  24. BIN  public/jQuery.sheet/images/sheet_row_add_multi.png
  25. BIN  public/jQuery.sheet/images/sheet_row_delete.png
  26. BIN  public/jQuery.sheet/images/stripes.png
  27. BIN  public/jQuery.sheet/images/table_delete.png
  28. BIN  public/jQuery.sheet/images/text_align_center.png
  29. BIN  public/jQuery.sheet/images/text_align_left.png
  30. BIN  public/jQuery.sheet/images/text_align_right.png
  31. BIN  public/jQuery.sheet/images/text_bold.png
  32. BIN  public/jQuery.sheet/images/text_italic.png
  33. BIN  public/jQuery.sheet/images/text_strikethrough.png
  34. BIN  public/jQuery.sheet/images/text_underline.png
  35. BIN  public/jQuery.sheet/images/update.gif
  36. +9,046 −0 public/jQuery.sheet/jquery-1.6.4.js
  37. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_dots-medium_100_4a749a_4x4.png
  38. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_flat_55_999999_40x100.png
  39. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_flat_75_aaaaaa_40x100.png
  40. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_gloss-wave_45_a8330b_500x100.png
  41. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-hard_30_4a749a_1x100.png
  42. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-hard_55_f8da4e_1x100.png
  43. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-soft_45_eeeeee_1x100.png
  44. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png
  45. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-soft_75_dddddd_1x100.png
  46. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
  47. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_000000_256x240.png
  48. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_0078ae_256x240.png
  49. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_f7a50d_256x240.png
  50. BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_fcd113_256x240.png
  51. +563 −0 public/jQuery.sheet/jquery-ui/theme/jquery-ui.css
  52. +19 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.accordion.css
  53. +11 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.all.css
  54. +53 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.autocomplete.css
  55. +11 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.base.css
  56. +38 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.button.css
  57. +41 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.core.css
  58. +68 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.datepicker.css
  59. +21 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.dialog.css
  60. +11 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.progressbar.css
  61. +20 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.resizable.css
  62. +10 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.selectable.css
  63. +24 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.slider.css
  64. +18 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.tabs.css
  65. +249 −0 public/jQuery.sheet/jquery-ui/theme/jquery.ui.theme.css
  66. +150 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery-ui-i18n.min.js
  67. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-af.min.js
  68. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ar-DZ.min.js
  69. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ar.min.js
  70. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-az.min.js
  71. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-bg.min.js
  72. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-bs.min.js
  73. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ca.min.js
  74. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-cs.min.js
  75. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-da.min.js
  76. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-de.min.js
  77. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-el.min.js
  78. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-en-AU.min.js
  79. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-en-GB.min.js
  80. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-en-NZ.min.js
  81. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-eo.min.js
  82. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-es.min.js
  83. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-et.min.js
  84. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-eu.min.js
  85. +3 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-fa.min.js
  86. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-fi.min.js
  87. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-fo.min.js
  88. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-fr-CH.min.js
  89. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-fr.min.js
  90. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-gl.min.js
  91. +3 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-he.min.js
  92. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-hr.min.js
  93. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-hu.min.js
  94. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-hy.min.js
  95. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-id.min.js
  96. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-is.min.js
  97. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-it.min.js
  98. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ja.min.js
  99. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ko.min.js
  100. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-kz.min.js
  101. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-lt.min.js
  102. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-lv.min.js
  103. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ml.min.js
  104. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ms.min.js
  105. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-nl.min.js
  106. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-no.min.js
  107. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-pl.min.js
  108. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-pt-BR.min.js
  109. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-pt.min.js
  110. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-rm.min.js
  111. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ro.min.js
  112. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ru.min.js
  113. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-sk.min.js
  114. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-sl.min.js
  115. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-sq.min.js
  116. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-sr-SR.min.js
  117. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-sr.min.js
  118. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-sv.min.js
  119. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-ta.min.js
  120. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-th.min.js
  121. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-tj.min.js
  122. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-tr.min.js
  123. +4 −0 public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-uk.min.js
  124. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-vi.min.js
  125. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-CN.min.js
  126. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-HK.min.js
  127. +2 −0  public/jQuery.sheet/jquery-ui/ui/i18n/jquery.ui.datepicker-zh-TW.min.js
  128. +414 −0 public/jQuery.sheet/jquery-ui/ui/jquery-ui.min.js
  129. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.blind.min.js
  130. +15 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.bounce.min.js
  131. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.clip.min.js
  132. +31 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.core.min.js
  133. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.drop.min.js
  134. +15 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.explode.min.js
  135. +13 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.fade.min.js
  136. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.fold.min.js
  137. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.highlight.min.js
  138. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.pulsate.min.js
  139. +20 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.scale.min.js
  140. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.shake.min.js
  141. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.slide.min.js
  142. +14 −0 public/jQuery.sheet/jquery-ui/ui/jquery.effects.transfer.min.js
  143. +30 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.accordion.min.js
  144. +32 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.autocomplete.min.js
  145. +27 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.button.min.js
  146. +18 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.core.min.js
  147. +83 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.datepicker.min.js
  148. +40 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.dialog.min.js
  149. +50 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.draggable.min.js
  150. +27 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.droppable.min.js
  151. +17 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.mouse.min.js
  152. +16 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.position.min.js
  153. +16 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.progressbar.min.js
  154. +49 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.resizable.min.js
  155. +22 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.selectable.min.js
  156. +33 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.slider.min.js
  157. +60 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.sortable.min.js
  158. +35 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.tabs.min.js
  159. +15 −0 public/jQuery.sheet/jquery-ui/ui/jquery.ui.widget.min.js
  160. +72 −0 public/jQuery.sheet/jquery.cookie.js
  161. +329 −0 public/jQuery.sheet/jquery.sheet.advancedfn.js
  162. +464 −0 public/jQuery.sheet/jquery.sheet.css
  163. +49 −0 public/jQuery.sheet/jquery.sheet.financefn.js
  164. +5,473 −0 public/jQuery.sheet/jquery.sheet.js
  165. +542 −0 public/jQuery.sheet/lang_de.js
  166. +544 −0 public/jQuery.sheet/lang_en.js
  167. +472 −0 public/jQuery.sheet/parser.js
  168. +501 −0 public/jQuery.sheet/parser.js~
  169. +501 −0 public/jQuery.sheet/parser_de_old.js
  170. +484 −0 public/jQuery.sheet/parser_en.js
  171. +16 −0 public/jQuery.sheet/plugins/g.raphael-min.js
  172. +37 −0 public/jQuery.sheet/plugins/jquery.colorPicker.css
  173. +1 −0  public/jQuery.sheet/plugins/jquery.colorPicker.min.js
  174. +13 −0 public/jQuery.sheet/plugins/jquery.elastic.min.js
  175. +7 −0 public/jQuery.sheet/plugins/raphael-min.js
  176. BIN  public/jQuery.sheet/stripes.png
  177. +60 −0 public/stylesheets/style.css
  178. +4 −0 settings.js
  179. +85 −0 sheetDocHelper.js
  180. +22 −0 views/index.html
  181. +11 −0 views/layout.html
  182. +96 −0 views/spreadsheet.html
View
312 app.js
@@ -0,0 +1,312 @@
+var express = require('express');
+var hbs = require('hbs');
+var sharejs = require('share').server;
+var colorconverter = require("color-convert")();
+
+var url = require('url');
+var fs = require('fs');
+var os = require('os');
+
+var path = require('path');
+var settings = require('./settings');
+var sheetDocHelper = require('./sheetDocHelper.js');
+
+//dictinary with opened docuemnts
+var documentDict = {};
+// Check for time outs every 5 seconds
+setInterval(timeOut, 5000);
+
+//added exception handler so the node doesn't terminate on an exception
+process.on('uncaughtException', function(err) {
+ console.log(err);
+});
+
+/**
+* returns a function which creates a different color each time it is called
+*/
+function getColorGenerator() {
+ var hsv = [90, 90, 90];
+ var seed = 0;
+ return function () {
+ hsv[0] = (hsv[0]+60) % 360;
+
+ seed++;
+
+ if (seed % 6 === 0) {
+ hsv[0] = (hsv[0]+30) % 360;
+ hsv[1] = (hsv[1]+30) % 100;
+ hsv[2] = (hsv[2]+20) % 100;
+ }
+
+ if (hsv[1] < 45) hsv[1] = 45;
+ if (hsv[2] < 50) hsv[2] = 50;
+
+ var rgb = colorconverter.hsv(hsv[0], hsv[1], hsv[2]).rgb();
+ return 'rgb('+rgb[0]+','+rgb[1]+','+rgb[2]+')';
+ };
+}
+
+/**
+* remove a document
+*
+* @param {String} docName name of the document
+* @param {Function} [callback] will be called when removal is done
+*/
+function removeDocument(docName, callback) {
+ if (documentDict[docName].state === "open") {
+ documentDict[docName].state = "closing";
+ server.model.getSnapshot(docName, function(error, data) {
+ if (error && callback) {
+ callback(error);
+ }
+ else {
+ server.model.delete(docName, function(error, data) {
+ if (error && callback) {
+ callback(error);
+ }
+ else {
+ var callbackQueue = [];
+ if (documentDict[docName].closedCallback) {
+ callbackQueue.push(documentDict[docName].closedCallback);
+ }
+ if(callback) {
+ callbackQueue.push(callback);
+ }
+ delete documentDict[docName];
+ console.log('deleted document ' + docName);
+ for (var i = callbackQueue.length - 1; i >= 0; i--) {
+ callbackQueue[i]();
+ }
+ }
+ });
+ }
+ });
+ }
+}
+
+/**
+* creates a new document with the given name
+*
+* @param {String} docName name of the document
+* @param {Function} [callback] will be called when creation is done
+*/
+function createDocument(docName, callback) {
+ var content = sheetDocHelper.createShareDoc();
+ documentDict[docName] = {users: {}, state: "opening", getNewColor: getColorGenerator()};
+
+ server.model.create(docName, 'json', function (error) {
+ server.model.applyOp(docName, {op:[{oi:content, p:[]}], v:0}, function(error) {
+ if (error) {
+ documentDict[docName].state = "closed";
+ callback(error);
+ }
+ else {
+ documentDict[docName].state = "open";
+ callback();
+ }
+ });
+ });
+}
+
+/**
+* add a new user to the lists in the document and on the server
+*
+* @param {String} docName name of the document
+* @param {Object} agent the ShareJS use agent of the new user
+* @param {Function} [callback] will be called when the new user is added
+*/
+function addUser(docName, agent, callback) {
+ var userName = agent.name;
+ var userObject;
+ var userColor;
+
+ if (!callback) {
+ callback = function () {};
+ }
+
+ //add the user to the list in the documentDict
+ documentDict[docName].users[userName] = {
+ agent: agent,
+ timeout: settings.PING_TIMEOUT * 60000
+ };
+
+ userColor = documentDict[docName].getNewColor();
+
+ //add the user to the user list in the document
+ server.model.getSnapshot(docName, function(error, data) {
+ if (error) {
+ callback(error);
+ }
+ else {
+ userObject = data.snapshot.users[userName];
+ if (!userObject) {
+ userObject = {"name":userName, "color": userColor, "selection":""};
+ server.model.applyOp(docName, {op:[{p:['users', userName], oi:userObject}], v:data.v}, function(error) {
+ if (error) {
+ callback(error);
+ }
+ else {
+ callback(null);
+ }
+ });
+ }
+ }
+ });
+}
+
+/**
+* remove the user from the lists in the document and on the server and closes the document if all users are disconnected
+*
+* @param {String} docName name of the document
+* @param {String} userName user who will be disconnected
+*/
+function removeUser(docName, userName) {
+ //remove the user from the user list in the document
+ server.model.getSnapshot(docName, function(error, data) {
+ if (error) {
+ console.log(error);
+ }
+ else {
+ var userObject = data.snapshot.users[userName];
+ if (userObject) {
+ server.model.applyOp(docName, {op:[{p:['users', userName], od:userObject}], v:data.v}, function(error) {
+ if (error) {
+ console.log(error);
+ }
+ });
+ }
+ //disconnect the user and remove him from the list in the documentDict
+ delete documentDict[docName].users[userName];
+
+ if (Object.keys(documentDict[docName].users).length === 0) {
+ //no users editing the document - remove it
+ removeDocument(docName);
+ }
+ }
+ });
+}
+
+/**
+ * decrease the timers for all users and check if they are 0
+ *
+ */
+function timeOut() {
+ for(var docName in documentDict) {
+ for(var userName in documentDict[docName].users) {
+ documentDict[docName].users[userName].timeout -= 5000;
+ if (documentDict[docName].users[userName].timeout <= 0) {
+ removeUser(docName, userName);
+ console.log('user ' + userName + ' disconnected (timeout)');
+ }
+ }
+ }
+}
+
+
+
+var server = express();
+//server.use(express.logger());
+server.engine('html', hbs.__express);
+server.set('view engine', 'hbs');
+server.set('views', __dirname + '/views');
+server.use(express.static(__dirname + '/public'));
+server.use(express.bodyParser());
+server.use(server.router);
+
+// Action for getting the document contents
+server.get('/doc/get/:docName', function(req, res, next) {
+ var docName = req.params.docName;
+
+ server.model.getSnapshot(docName, function(error, data) {
+ if (error) {
+ res.statusCode = 500;
+ res.end(error);
+ }
+ else {
+ res.end(JSON.stringify(data.snapshot));
+ }
+ });
+});
+
+server.get('/spreadsheet/:docName', function(req, res, next) {
+ var docName = req.params.docName;
+
+ res.render('spreadsheet.html', {docName: docName});
+ if (!documentDict[docName]) {
+ createDocument(docName, function (error) {
+ console.log(error ? error : 'created document '+docName);
+ });
+ }
+});
+
+server.get('/', function(req, res, next) {
+ res.render('index.html', {documents: Object.keys(documentDict)});
+});
+
+var options = {
+ browserChannel: {cors:settings.RT_SERVER_HOST},
+ rest: null,
+ db: {type: 'memory'},
+ /**
+ * Gets called everytime before a user tries to connect or submit an operation
+ * (see https://github.com/josephg/ShareJS/wiki/User-access-control)
+ *
+ * @param {Object} agent Stores an ID and the name of the client
+ * @param {Object} action Stores the type of the action. Must be either accepted or rejected.
+ */
+ auth: function (agent, action) {
+ if (action.name == 'connect') {
+ agent.name = agent.authentication ? agent.authentication : 'Unknown';
+ action.accept();
+ }
+ else if (action.name == 'open') {
+ //addUser(action.docName, agent);
+ addUser(action.docName, agent, function(error) {
+ if (error) {
+ console.log(error);
+ }
+ console.log('user '+ agent.name + ' added');
+ action.accept();
+ });
+
+ if (documentDict[action.docName]) {
+ documentDict[action.docName].state = "open";
+ }
+
+ }
+ else if (action.name == 'create') {
+ action.accept();
+ }
+ else if (action.name == 'submit op') {
+ if(action.op[0].p[0] == 'users') {
+ if (action.op[0].p[1] !== agent.name ) {
+ //reject modifications from other users on user object
+ console.log('forbidden to modify user data! (' + agent.name + '!=' + action.op[0].p[1]);
+ action.reject();
+ }
+ else {
+ if (action.op[0].p.length == 2 && action.op[0].od !== undefined) {
+ //user removed - disconnect him
+ removeUser(action.docName, agent.name);
+ console.log('user ' + agent.name + 'disconnected');
+ }
+ action.accept();
+ }
+ }
+ else {
+ action.accept();
+ }
+
+ documentDict[action.docName].users[agent.name].timeout = settings.PING_TIMEOUT * 1000;
+ }
+ else {
+ action.accept();
+ }
+ console.log(agent.name+': '+action.name);
+ }
+ };
+
+// Attach the sharejs REST and Socket.io interfaces to the server
+sharejs.attach(server, options);
+server.listen(settings.RT_SERVER_PORT);
+console.log('Server running at '+ os.hostname() +':'+settings.RT_SERVER_PORT);
View
11 package.json
@@ -0,0 +1,11 @@
+{
+ "name":"nodespreadsheet",
+ "node":"0.8",
+ "author":"Raphael Schroiff",
+ "dependencies": {
+ "share": "0.5",
+ "request": "2.0",
+ "express": "3.0",
+ "color-convert": ""
+ }
+}
View
13 public/code/index.js
@@ -0,0 +1,13 @@
+$(function() {
+ var userName = $.cookie('userName');
+ if (!userName) {
+ $('#userNameInput').value(userName);
+ }
+
+
+ $('#userNameButton')
+ .button()
+ .click(function( event ) {
+ $.cookie('userName', $('#userNameInput').value(), { expires: 7, path: '/' });
+ });
+});
View
931 public/code/spreadsheet.js
@@ -0,0 +1,931 @@
+var sharejs_doc;
+var PING_INTERVAL = 5; //in seconds
+var connection;
+var userName = 'TestUser';
+var sheetEditable = true;
+
+/**
+ * get the value of a cookie
+ * @param {String} name the name of the cookie
+ */
+function readCookie(name) {
+ var nameEQ = name + "=";
+ var ca = document.cookie.split(';');
+ for(var i=0;i < ca.length;i++) {
+ var c = ca[i];
+ while (c.charAt(0)==' ') c = c.substring(1,c.length);
+ if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length,c.length);
+ }
+ return null;
+}
+
+
+$(function() {
+
+ //create the dialog which will be shown on disconnect
+ var dialog_options = {
+ modal: true,
+ autoOpen: false,
+ title: spreadsheetTexts.misc.disconnected,
+ buttons: {}
+ };
+ dialog_options.buttons[spreadsheetTexts.misc.reload] = function() {
+ location.reload();
+ };
+ $("#disconnectedDialog p").text(spreadsheetTexts.misc.disconnectMessage);
+ $("#disconnectedDialog").dialog(dialog_options);
+
+ //open the shareJS document and initiate the jQuery.sheet
+ connection = sharejs.open(docName, 'json', {authentication: userName}, function(error, doc) {
+ sharejs_doc = doc;
+
+ //Setup Ping to the RT-server to indicate that the connection is still alive
+ setInterval(function () {
+ var op = {p:['users', userName, 'connected'], oi:true};
+ sharejs_doc.submitOp(op);
+ }, PING_INTERVAL * 1000);
+
+ //initialize the sheet contents
+ initSpreadsheet(sharejs_doc);
+
+ //register callback for remote changes on the document
+ doc.on('remoteop', function(op) {
+ for (var i=0; i<op.length; i++) {
+ onDocChanged(op[i]);
+ }
+ });
+ });
+
+ //show a dialog when the connection to the RT-Server is lost
+ connection.on("error", function(e) {
+ $("#disconnectedDialog").dialog('open');
+ });
+
+ //register callbacks for events from jQuery.sheet
+ $('.jQuerySheet').live("beforeCellEdit", beforeCellEditEvent);
+ $('.jQuerySheet').live("afterCellEdit", afterCellEditEvent);
+ $('.jQuerySheet').live("cellEditAbandon", cellEditAbandonEvent);
+ $('.jQuerySheet').live("addRow", addRowEvent);
+ $('.jQuerySheet').live("deleteRow", deleteRowEvent);
+ $('.jQuerySheet').live("addColumn", addColumnEvent);
+ $('.jQuerySheet').live("deleteColumn", deleteColumnEvent);
+ $('.jQuerySheet').live("resizeColumn", resizeColumnEvent);
+ $('.jQuerySheet').live("resizeRow", resizeRowEvent);
+ $('.jQuerySheet').live("addSheet", addSheetEvent);
+ $('.jQuerySheet').live("deleteSheet", deleteSheetEvent);
+ $('.jQuerySheet').live("selectionChanged", selectionChangedEvent);
+ $('.jQuerySheet').live("formulaRefreshed", formulaRefreshedEvent);
+
+ $('.jQuerySheet').live("sheetOpened", function(event, a, b, c) {
+ var functionList = [];
+ var paramList = [];
+
+ //set the localized texts in toolbar and jQuery.sheet
+ for (var item in spreadsheetTexts.toolbar) {
+ $('#toolbar_'+item).attr('title', spreadsheetTexts.toolbar[item]);
+ $('#toolbar_'+item+' img').attr('alt', spreadsheetTexts.toolbar[item]);
+ }
+ $('#toolbar_export').text(spreadsheetTexts.toolbar['export']);
+
+ //attach the user list and show it
+ $('.jQuerySheet').append($("#userDialog"));
+ $("#userDialog").show();
+
+ //insert the formula button
+ $('<td></td>').append($('#formula_button'))
+ .css({'width': '16px', 'padding':'2px'})
+ .insertBefore('.jSheetControls_formulaParent');
+
+ for(var fn in spreadsheetTexts.functions) {
+ fn_object = spreadsheetTexts.functions[fn];
+ fn_string = '='+fn_object.name+'(';
+ paramList = [];
+
+ //add localized function to jQuery.sheet
+ jQuery.sheet.fn[fn_object.name] = jQuery.sheet.fn[fn];
+
+ //build a function description and add it to autocomplete list
+ for(var param in fn_object.params) {
+ var param_object = fn_object.params[param];
+
+ if (param_object.optional) {
+ paramList.push('['+param_object.name+']');
+ }
+ else {
+ paramList.push(param_object.name);
+ }
+ }
+
+ fn_string = fn_string + paramList.join(', ') + ')';
+ functionList.push(fn_string);
+ }
+
+ functionList.sort();
+
+ autoComplete = $('.jSheetControls_formula').autocomplete({
+ source: functionList
+ });
+
+ $("#formula_button").click(function() {
+ //show or hide the autocomlpete list
+ if ($('ul.ui-autocomplete').is(':visible')) {
+ $('.jSheetControls_formula').autocomplete('close');
+ }
+ else {
+ $('.jSheetControls_formula').autocomplete('option', 'minLength', 0 );
+ $('.jSheetControls_formula').autocomplete('search');
+ $('.jSheetControls_formula').autocomplete('option', 'minLength', 1 );
+ }
+ });
+ });
+});
+
+/**
+ * initialize the sheet contents, register callbacks for doc changes
+ *
+ * @param{Object} doc the ShareJS document for the spreadsheet
+ */
+function initSpreadsheet(doc) {
+ var sheet_count,
+ row_count,
+ col_count,
+ sheets = doc.at('sheets'),
+ rows = doc.at('sheets', 0, 'rows'),
+ cols = doc.at('sheets', 0, 'columns'),
+ users = doc.at('users'),
+ cells,
+ value;
+
+
+ sheet_count = sheets.get().length;
+ sheet_count = sheet_count ? sheet_count : 1;
+
+ row_count = rows.get().length;
+ row_count = row_count ? row_count : 15;
+
+ col_count = cols.get().length;
+ col_count = col_count ? col_count : 5;
+
+ // Initiate jQuery.sheet
+ $('#jQuerySheet').sheet({
+ title : docName,
+ buildSheet: col_count+'x'+row_count,
+ editable : sheetEditable,
+ resizable: sheetEditable,
+ inlineMenu: $('#inlineMenu').html(),
+ barMenus: false,
+ menu: false,
+ minSize: {rows: 3, cols: 3},
+ numberFormatDE: true
+ });
+
+ //Hide the tabs for multiple sheets (multiple sheets are not supported yet)
+ $('.jSheetTabContainer').hide();
+
+ //set localized texts for jQuery.sheet
+ $.sheet.instance[0].msg = spreadsheetTexts.msg;
+
+ //Init the color-pickers
+ $('.colorPickerCell').colorPicker().change(function(){
+ cellSetStyle('background-color', $(this).val());
+ });
+ $('.colorPickerFont').colorPicker().change(function(){
+ cellSetStyle('color', $(this).val());
+ });
+
+ //column sizes
+ for (var col=0; col<col_count; col++) {
+ var col_info = doc.at('sheets', 0, 'columns', col);
+ $.sheet.instance[0].setColumnSize(col, col_info.get().size);
+ }
+ $("#jSheet_0_0").width(0); //Fixes a bug wich may cause incorrect column widths
+
+ //put doc contents into the sheet
+ sheet = 0;
+ rows = doc.at('sheets', sheet, 'rows');
+ row_count = rows.get().length;
+ row_count = row_count ? row_count : 15;
+
+ for (var row=0; row<row_count; row++) {
+ cells = doc.at('sheets', sheet, 'rows', row, 'cells');
+ col_count = cells.get().length;
+ col_count = col_count ? col_count : 5;
+ var size = doc.at('sheets', sheet, 'rows', row, 'size');
+ $.sheet.instance[0].setRowSize(row, size.get());
+
+ for (var col=0; col<col_count; col++) {
+ var cell = doc.at('sheets', sheet, 'rows', row, 'cells', col);
+
+ //set value and formatting of the cell
+ value = cell.get().value;
+ if (value.charAt(0) == '=') {
+ //Translate funtion names into localized versions
+ for (var fn in spreadsheetTexts.functions) {
+ if (spreadsheetTexts.functions.hasOwnProperty(fn)) {
+ value = value.replace(new RegExp(fn+'\\(' ,'g'), spreadsheetTexts.functions[fn].name+'(');
+ }
+ }
+ }
+ $.sheet.instance[0].setCellValue(row, col, sheet, value);
+
+ for (var style in cell.get().style) {
+
+ if (style == 'font-size') {
+ var font_size = cell.get().style[style];
+ $.sheet.instance[0].setFontSize(sheet, row, col, font_size);
+ }
+ else if (style == 'background-color') {
+ var color = cell.get().style[style];
+ $.sheet.instance[0].cellSetStyle(sheet, row, col, style, color);
+ }
+ else if (style == 'color') {
+ var color = cell.get().style[style];
+ $.sheet.instance[0].cellSetStyle(sheet, row, col,style, color);
+ }
+ else if (style == 'flags') {
+ //style-flags
+ for (var style in cell.get().style.flags) {
+ $.sheet.instance[0].addCellStyle(row, col, sheet, style);
+ }
+ }
+ }
+
+ if (cell.get().lockedBy) {
+ //release lock if it is an old lock from the same user
+ if (cell.get().lockedBy == userName) {
+ op = {p:['sheets', sheet, 'rows', row, 'cells', col, 'lockedBy'], od:userName};
+ sharejs_doc.submitOp(op);
+ }
+ else {
+ $.sheet.instance[0].lockCell(row, col, sheet);
+ }
+ }
+ }
+ }
+
+ //mark the selection of other users
+ for (var user in users.get()) {
+ if (user && (user != userName)) {
+ userColor = col = doc.at('users', user, 'color').get();
+ row = doc.at('users', user, 'selection', 'row').get();
+ col = doc.at('users', user, 'selection', 'col').get();
+ if ((col != void 0) && (row != void 0) && (userColor != void 0)) {
+ $.sheet.instance[0].setCellSelection(row, col, 0, userColor);
+ }
+ }
+ }
+
+ updateUserList();
+
+ //select the last selected cell or A1 at the beginning
+ if (sheetEditable) {
+ setTimeout(function() {
+ var selection = sharejs_doc.at('users', userName, 'selection').get(),
+ row = 0,
+ col = 0;
+ if (selection) {
+ row = selection.row;
+ col = selection.col;
+ }
+ $.sheet.instance[0].cellEdit(jQuery($.sheet.instance[0].getTd(0, row, col)), 0);
+ });
+ }
+}
+
+/**
+ * Gets called before the user starts editing a cell
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {Object} data stores row, column, and content of the cell
+ */
+function beforeCellEditEvent(event, source, data) {
+ var sheet = $.sheet.instance[0].i,
+ cell = sharejs_doc.at('sheets', sheet, 'rows', data.row, 'cells', data.col),
+ op;
+ if (sheet >= 0 && data.row >=0 && data.col >=0) {
+ if(!cell.get().lockedBy) {
+ op = {p:['sheets', sheet, 'rows', data.row, 'cells', data.col, 'lockedBy'], oi:userName};
+ sharejs_doc.submitOp(op);
+ }
+ }
+}
+
+/**
+ * Gets called after a cell was edited by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {Object } data stores row, column, and content of the cell
+ */
+function afterCellEditEvent(event, source, data) {
+ var sheet = $.sheet.instance[0].i,
+ cell = sharejs_doc.at('sheets', sheet, 'rows', data.row, 'cells', data.col),
+ user,
+ old,
+ value,
+ op;
+
+ if (sheet >= 0 && data.row >=0 && data.col >=0) {
+ user = cell.get().lockedBy;
+ old = cell.get().value;
+
+ if(!old) {
+ old = '';
+ }
+ value = $.sheet.instance[0].getCellValueOrFormula(data.row, data.col, sheet);
+ if (value.charAt(0) == '=') {
+ //Translate localized funtion names into original (English) versions
+ for (var fn in spreadsheetTexts.functions) {
+ if (spreadsheetTexts.functions.hasOwnProperty(fn)) {
+ value = value.replace(new RegExp(spreadsheetTexts.functions[fn].name+'\\(' ,'g'), fn+'(');
+ }
+ }
+ }
+ op = {p:['sheets', sheet, 'rows', data.row, 'cells', data.col, 'value'], od:old, oi:value};
+ sharejs_doc.submitOp(op);
+
+ if(user) {
+ //release lock
+ op = {p:['sheets', sheet, 'rows', data.row, 'cells', data.col, 'lockedBy'], od:user};
+ sharejs_doc.submitOp(op);
+ }
+ }
+}
+
+/**
+ * Gets called after the user cancels a cell editing
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {Object} data stores row, column, and content of the cell
+ */
+function cellEditAbandonEvent(event, source, data) {
+ if (data.col < 0 || data.row < 0) return;
+ var sheet = $.sheet.instance[0].i,
+ cell = sharejs_doc.at('sheets', sheet, 'rows', data.row, 'cells', data.col),
+ user = cell.get().lockedBy,
+ old = cell.get().value,
+ value,
+ op;
+
+ if(!old) {
+ old = '';
+ }
+ value = $.sheet.instance[0].getCellValueOrFormula(data.row, data.col, sheet);
+ op = {p:['sheets', sheet, 'rows', data.row, 'cells', data.col, 'value'], od:old, oi:value};
+ sharejs_doc.submitOp(op);
+
+ if(cell.get().lockedBy) {
+ //release lock
+ op = {p:['sheets', sheet, 'rows', data.row, 'cells', data.col, 'lockedBy'], od:user};
+ sharejs_doc.submitOp(op);
+ }
+}
+
+/**
+ * Gets called after a sheet was inserted by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number|string} [pos Position where the sheet has been inserted]
+ */
+function addSheetEvent(event, source, pos) {
+ //not used yet
+}
+/**
+ * Gets called after a sheet was deleted by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number|string} [pos Position where the sheet has been deleted]
+ */
+function deleteSheetEvent(event, source, pos) {
+ //not used yet
+}
+
+/**
+ * Gets called after jQuery.sheet upddates a formula (e.g. because a row affecting the fomula has been deleted)
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {Number} row Position of the formula
+ * @param {Number} col Position of the formula
+ * @param {String} formula the new value of the formula
+ */
+function formulaRefreshedEvent(event, source, row, col, formula) {
+ var sheet = $.sheet.instance[0].i,
+ cell = sharejs_doc.at('sheets', sheet, 'rows', row, 'cells', col),
+ old = cell.get().value,
+ op;
+
+ old = old ? old : '';
+ if (old !== formula) {
+ op = {p:['sheets', sheet, 'rows', row, 'cells', col, 'value'], od:old, oi:formula};
+ sharejs_doc.submitOp(op);
+ }
+}
+
+/**
+ * Gets called after a row was inserted by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number|string} [pos Position where the row has been inserted]
+ * @param {boolean} [isBefore] true, iff the new row was iserted before the row at pos
+ */
+function addRowEvent(event, source, pos, isBefore, rowCount) {
+ var sheet = $.sheet.instance[0].i,
+ row = pos ? pos : $.sheet.instance[0].rowLast,
+ colCount = $.sheet.instance[0].sheetSize().width+1,
+ //TODO: testen, ob die spalten anzahl auch mit merged cells richtig ist
+ value = [],
+ op;
+
+ if (isBefore) {
+ row--;
+ }
+ if (isNaN(row)) {
+ row = $.sheet.instance[0].sheetSize().height-1;
+ }
+ row++;
+
+ for (col=0; col<colCount; col++) {
+ value.push({value: "", style: {flags: {styleLeft:true}}});
+ }
+
+ //insert new cells
+ op = {p:['sheets', sheet, 'rows', row], li:{'cells': value, size: 20}};
+ sharejs_doc.submitOp(op);
+}
+
+/**
+ * Gets called after a row was deleted by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number|string} [pos Position where the row has been deleted]
+ */
+function deleteRowEvent(event, source, pos, isBefore, rowCount) {
+ var sheet = $.sheet.instance[0].i,
+ row = pos ? pos : $.sheet.instance[0].rowLast,
+ colCount = $.sheet.instance[0].sheetSize().width+1,
+ //TODO: testen, ob die spalten anzahl auch mit merged cells richtig ist
+ value,
+ op;
+
+ if (isBefore) {
+ row--;
+ }
+ if (isNaN(row)) {
+ row = $.sheet.instance[0].sheetSize().height-1;
+ }
+ if (row < 0) {
+ row = 0;
+ }
+
+ value = sharejs_doc.at('sheets', sheet, 'rows', row).get();
+ op = {p:['sheets', sheet, 'rows', row], ld:value};
+ sharejs_doc.submitOp(op);
+}
+
+/**
+ * Gets called after a column was inserted by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number|string} [pos Position where the column has been inserted]
+ * @param {boolean} [isBefore] true, iff the new column was iserted before the column at pos
+ */
+function addColumnEvent(event, source, pos, isBefore, number) {
+ var sheet = $.sheet.instance[0].i,
+ col = pos ? pos : $.sheet.instance[0].colLast,
+ rowCount = $.sheet.instance[0].sheetSize().height+1,
+ value = {value: "", style: {flags: {styleLeft:true}}},
+ op;
+
+ if (isBefore) {
+ col--;
+ }
+ if (isNaN(col)) {
+ col = $.sheet.instance[0].sheetSize().width-1;
+ }
+ col++;
+
+ //add column
+ op = {p:['sheets', sheet, 'columns', col], li:{size:120}};
+ sharejs_doc.submitOp(op);
+ //add cells into new column
+ for (var row=0; row<rowCount; row++) {
+ op = {p:['sheets', sheet, 'rows', row, 'cells', col], li:value};
+ sharejs_doc.submitOp(op);
+ }
+}
+
+/**
+ * Gets called after a column was deleted by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number|string} [pos Position where the column has been deleted]
+ */
+function deleteColumnEvent(event, source, pos, isBefore, number) {
+ var sheet = $.sheet.instance[0].i,
+ col = pos ? pos : $.sheet.instance[0].colLast,
+ rowCount = $.sheet.instance[0].sheetSize().height+1,
+ value,
+ op;
+
+ if (isBefore) {
+ col--;
+ }
+ if (isNaN(col)) {
+ col = $.sheet.instance[0].sheetSize().width;
+ }
+ if (col < 0) {
+ col = 0;
+ }
+
+ //remove column
+ op = {p:['sheets', sheet, 'columns', col], ld:{size:120}};
+ sharejs_doc.submitOp(op);
+ //remove cells in column
+ for (var row=0; row<rowCount; row++) {
+ value = sharejs_doc.at('sheets', sheet, 'rows', row, 'cells', col).get();
+ op = {p:['sheets', sheet, 'rows', row, 'cells', col], ld:value};
+ sharejs_doc.submitOp(op);
+ }
+}
+
+/**
+ * Gets called after a column was resized by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number} col Position of the resized column
+ * @param {number} size New size of the column
+ */
+function resizeColumnEvent(event, source, col, size) {
+ var sheet = $.sheet.instance[0].i;
+
+ op = {p:['sheets', sheet, 'columns', col, 'size'], oi:size , od:size};
+ sharejs_doc.submitOp(op);
+}
+
+ /**
+ * Gets called after a row was resized by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number} row Position of the resized column
+ * @param {number} size New size of the column
+ */
+function resizeRowEvent(event, source, row, size) {
+ var sheet = $.sheet.instance[0].i;
+
+ op = {p:['sheets', sheet, 'rows', row, 'size'], oi:size , od:size};
+ sharejs_doc.submitOp(op);
+}
+
+/**
+ * Gets called after a cell was selected by the user
+ *
+ * @param {Object} event jQuery event
+ * @param {Object} source jQuery sheet which emits the event
+ * @param {number} row Row of the selected cell
+ * @param {number} row Column of the selected cell
+ */
+function selectionChangedEvent(event, source, row, col) {
+ var sheet = $.sheet.instance[0].i,
+ oldSelection = sharejs_doc.at('users', userName, 'selection').get(),
+ selection = {'row':row, 'col':col, 'sheet':sheet};
+ if ((oldSelection.row == selection.row) && (oldSelection.row == selection.row) && (oldSelection.row == selection.row)) {
+ //same cell selected as before
+ }
+ op = {p:['users', userName, 'selection'], oi:selection , od:oldSelection};
+ sharejs_doc.submitOp(op);
+}
+
+/**
+ * In-/decreases the font size for the selected cells in the sheet
+ * and commits the change operation
+ *
+ * @param {string} direction 'up' or 'down'
+ */
+ function resizeCellFont(direction) {
+ var uiCell = $.sheet.instance[0].obj.cellHighlighted(),
+ sheet = $.sheet.instance[0].i;
+
+ //set the font-size on jQuery.Sheet
+ $.sheet.instance[0].fontReSize(direction);
+
+ uiCell.each(function(i) {
+ var cell = jQuery(this),
+ loc = $.sheet.instance[0].getTdLocation(cell),
+ curr_size = (cell.css("font-size") + '').replace("px",""),
+ op;
+
+ op = {p:['sheets', sheet, 'rows', loc.row, 'cells', loc.col, 'style', 'font-size'], od:"", oi:curr_size};
+ sharejs_doc.submitOp(op);
+ });
+ }
+
+/**
+ * toggles a style-flag (e.g. styleBold, styleCenter) for the selected cells in the sheet and commits
+ * the change operation. The style-flags are CSS-Classes which are defined in jquery.sheet.css
+ *
+ * @param {string} style style-flag that will be applied to the cell
+ * @param {string} [removeStyle] style-flag that will be removed from the cell
+ */
+function toggleCellStyle(style, removeStyle) {
+ var cellRange = $.sheet.instance[0].highlightedLast;
+ var tmp;
+ var sheet = $.sheet.instance[0].i;
+ var op, path;
+ var flagsObject;
+
+ //Get the start and end of the selection
+ var rowStart = cellRange.rowStart == -1 ? cellRange.rowLast : cellRange.rowStart;
+ var rowEnd = cellRange.rowEnd == -1 ? cellRange.rowLast : cellRange.rowEnd;
+ var colStart = cellRange.colStart == -1 ? cellRange.colLast : cellRange.colStart;
+ var colEnd = cellRange.colEnd == -1 ? cellRange.colLast : cellRange.colEnd;
+
+ if (rowStart > rowEnd) {
+ tmp = rowEnd;
+ rowEnd = rowStart;
+ rowStart = tmp;
+ }
+ if (colStart > colEnd) {
+ tmp = colEnd;
+ colEnd = colStart;
+ colStart = tmp;
+ }
+
+ //toggle the style on jQuery.Sheet
+ $.sheet.instance[0].cellStyleToggle(style, removeStyle);
+
+ for (row=rowStart; row<=rowEnd; row++) {
+ for (col=colStart; col<=colEnd; col++) {
+ path = ['sheets', sheet, 'rows', row, 'cells', col, 'style', 'flags'];
+ flagsObject = sharejs_doc.at(path).get();
+
+ if (style in flagsObject) {
+ //remove style
+ op = {p:path.concat(style), od:"true"};
+ }
+ else {
+ //add style
+ op = {p:path.concat(style), oi:"true"};
+ }
+
+ sharejs_doc.submitOp(op);
+
+ //apply removeStyle to remote clients
+ if (removeStyle){
+ removeStyle.split(' ').forEach(function(style) {
+ if (style in flagsObject) {
+ op = {p:path.concat(style), od:"true"};
+ sharejs_doc.submitOp(op);
+ }
+ });
+ }
+ }
+ }
+}
+
+/**
+ * sets a style for the selected cells in the sheet and commits
+ * the operation (currently only used for colors)
+ *
+ * @param {string} style HTML style attribute that will be applied to the cell
+ * @param {string} value The value of the style attribute
+ */
+function cellSetStyle(style, value) {
+ var cellRange = $.sheet.instance[0].highlightedLast;
+ var tmp;
+ var sheet = $.sheet.instance[0].i;
+ var op, path, oldStyle;
+ var styleObject;
+
+ //Get the start and end of the selection
+ var rowStart = cellRange.rowStart == -1 ? cellRange.rowLast : cellRange.rowStart;
+ var rowEnd = cellRange.rowEnd == -1 ? cellRange.rowLast : cellRange.rowEnd;
+ var colStart = cellRange.colStart == -1 ? cellRange.colLast : cellRange.colStart;
+ var colEnd = cellRange.colEnd == -1 ? cellRange.colLast : cellRange.colEnd;
+
+ if (rowStart > rowEnd) {
+ tmp = rowEnd;
+ rowEnd = rowStart;
+ rowStart = tmp;
+ }
+ if (colStart > colEnd) {
+ tmp = colEnd;
+ colEnd = colStart;
+ colStart = tmp;
+ }
+
+ //set the style on jQuery.Sheet
+ $.sheet.instance[0].cellChangeStyle(style, value);
+
+ for (row=rowStart; row<=rowEnd; row++) {
+ for (col=colStart; col<=colEnd; col++) {
+ path = ['sheets', sheet, 'rows', row, 'cells', col, 'style'];
+ styleObject = sharejs_doc.at(path).get();
+
+ if (style in styleObject) {
+ //remove old style
+ oldStyle = styleObject[style];
+ }
+
+ //add style
+ op = {p:path.concat(style), oi:value, od: oldStyle};
+
+ sharejs_doc.submitOp(op);
+ }
+ }
+}
+
+/**
+ * update the list of active users of the document
+ *
+ * @param {string} name name of the user
+ * @param {string} [color] color of the user
+ */
+function updateUserList() {
+ var users, name, color, item, userCount;
+
+ users = sharejs_doc.snapshot.users;
+ userCount = 0;
+
+ //clear the list
+ $('#userList').children().remove();
+
+ //diaplay the current user in the first item
+ color = '#6089af';
+ item = $(userName + ' (Ich)');
+ item.css('color', color);
+ $('<li></li>').css('color', color).append(item).appendTo($('#userList'));
+
+ for (name in users) {
+ if (name && users.hasOwnProperty(name)) {
+ userCount++;
+
+ if (name != userName) {
+ item = $(name);
+ item.css('color', users[name].color);
+ $('<li></li>').css('color', users[name].color).append(item).appendTo($('#userList'));
+ }
+
+ }
+ }
+
+ //update the number of users in the text of the dialog
+ if (userCount == 1) {
+ $('#userDialog p').text(userCount + spreadsheetTexts.misc.userListTitleSingular);
+ }
+ else {
+ $('#userDialog p').text(userCount + spreadsheetTexts.misc.userListTitle);
+ }
+}
+
+/**
+ * Gets called when another client commits an operation on the
+ * 'sheet' doc
+ *
+ * @param {Object} op The operation commited by teh remote client
+ */
+function onDocChanged(op, oldSnapshot) {
+ var path = op.p,
+ sheet = path[1],
+ row = path[3],
+ col = path[5],
+ style = path[7],
+ isBefore = false,
+ value,
+ color;
+
+ if (path[0] == 'users') {
+ if (path.length == 3) {
+ if (path[2] == 'selection') {
+ //update selection of other users
+ color = sharejs_doc.at('users', path[1], 'color').get();
+
+ if (op.od) {
+ $.sheet.instance[0].removeCellSelection(op.od.row, op.od.col, op.od.sheet);
+ }
+ if (op.oi) {
+ $.sheet.instance[0].setCellSelection(op.oi.row, op.oi.col, op.oi.sheet, color);
+ }
+ }
+ }
+ else if (path.length == 2) {
+ if (op.od !== void 0) {
+ //user removed
+ updateUserList();
+ if (path[1] == userName) {
+ //current user got diconnected from the server
+ $("#disconnectedDialog").dialog('open');
+ }
+ else {
+ //remove selection of user
+ $.sheet.instance[0].removeCellSelection(op.od.selection.row, op.od.selection.col, op.od.selection.sheet);
+ }
+ }
+ else if (op.oi !== void 0) {
+ //user addeed
+ updateUserList();
+ }
+ }
+ }
+ else if (path.length == 2) {
+ if (op.li !== void 0) {
+ //sheet inserted
+ //not used
+ }
+ else if (op.ld !== void 0) {
+ //sheet deleted
+ //not used
+ }
+ }
+ else if (path[2] == 'columns' ) {
+ col = path[3];
+ if (path[4] == 'size' && op.oi !== void 0) {
+ //column resized
+ $.sheet.instance[0].setColumnSize(col, op.oi);
+ }
+ else if (path.length == 4 && op.li !== void 0) {
+ //column inserted
+ if (col === 0) {
+ col++;
+ isBefore = true;
+ }
+ $.sheet.instance[0].controlFactory.addCells(col-1, isBefore, null, 1, 'col');
+ }
+ else if (path.length == 4 && op.ld !== void 0) {
+ //column deleted
+ $.sheet.instance[0].deleteColumnAt(col);
+ }
+ }
+ else if (path[2] == 'rows' ) {
+ if (path[4] == 'cells') {
+ if (path[6] == 'value' && op.oi !== void 0 && op.od !== void 0) {
+ //cell value changed
+ value = op.oi;
+ if (value.charAt(0) == '=') {
+ //Translate funtion names into localized versions
+ for (var fn in spreadsheetTexts.functions) {
+ if (spreadsheetTexts.functions.hasOwnProperty(fn)) {
+ value = value.replace(new RegExp(fn+'\\(' ,'g'), spreadsheetTexts.functions[fn].name+'(');
+ }
+ }
+ }
+ $.sheet.instance[0].setCellValue(row, col, sheet, value);
+ }
+ else if (path[6] == 'style') {
+ if (style == 'font-size') {
+ //font size changed
+ $.sheet.instance[0].setFontSize(sheet, row, col, op.oi);
+ }
+ if (style == 'background-color') {
+ $.sheet.instance[0].cellSetStyle(sheet, row, col, style, op.oi);
+ }
+ if (style == 'color') {
+ $.sheet.instance[0].cellSetStyle(sheet, row, col,style, op.oi);
+ }
+ if (style == 'flags') {
+ style = path[8];
+ if (op.oi !== void 0) {
+ //style added to cell
+ $.sheet.instance[0].addCellStyle(row, col, sheet, style);
+ }
+ if (op.od !== void 0) {
+ //style removed from cell
+ $.sheet.instance[0].removeCellStyle(row, col, sheet, style);
+ }
+ }
+ }
+ else if (path[6] == 'lockedBy' && op.oi !== void 0) {
+ $.sheet.instance[0].lockCell(row, col, sheet);
+ }
+ else if (path[6] == 'lockedBy' && op.od !== void 0) {
+ $.sheet.instance[0].releaseCell(row, col, sheet);
+ }
+ }
+ else if (path[4] == 'size') {
+ //row resized
+ $.sheet.instance[0].setRowSize(row, op.oi);
+ }
+ else if (path.length == 4 && op.li !== void 0) {
+ //row inserted
+ if (row === 0) {
+ row++;
+ isBefore = true;
+ }
+ $.sheet.instance[0].controlFactory.addCells(row-1, isBefore, null, 1, 'row');
+ }
+ else if (path.length == 4 && op.ld !== void 0) {
+ //row deleted
+ $.sheet.instance[0].deleteRowAt(row);
+ }
+ }
+}
View
BIN  public/jQuery.sheet/images/arrow_down.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/arrow_out.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/arrow_refresh.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/arrow_up.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/decrease-font.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/disk.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/find.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/function.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/increase-font.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/noparse.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/page_link.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/palette.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/palette_bg.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_col_add.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_col_add_multi.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_col_delete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_get_range.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_row_add.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_row_add_multi.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/sheet_row_delete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/stripes.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/table_delete.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/text_align_center.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/text_align_left.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
View
BIN  public/jQuery.sheet/images/text_align_right.png
Diff not rendered
View
BIN  public/jQuery.sheet/images/text_bold.png
Diff not rendered
View
BIN  public/jQuery.sheet/images/text_italic.png
Diff not rendered
View
BIN  public/jQuery.sheet/images/text_strikethrough.png
Diff not rendered
View
BIN  public/jQuery.sheet/images/text_underline.png
Diff not rendered
View
BIN  public/jQuery.sheet/images/update.gif
Diff not rendered
View
9,046 public/jQuery.sheet/jquery-1.6.4.js
9,046 additions, 0 deletions not shown
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_dots-medium_100_4a749a_4x4.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_flat_55_999999_40x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_flat_75_aaaaaa_40x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_gloss-wave_45_a8330b_500x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-hard_30_4a749a_1x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-hard_55_f8da4e_1x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-soft_45_eeeeee_1x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-soft_75_cccccc_1x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_highlight-soft_75_dddddd_1x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_000000_256x240.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_0078ae_256x240.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_f7a50d_256x240.png
Diff not rendered
View
BIN  public/jQuery.sheet/jquery-ui/theme/images/ui-icons_fcd113_256x240.png
Diff not rendered
View
563 public/jQuery.sheet/jquery-ui/theme/jquery-ui.css
@@ -0,0 +1,563 @@
+/*!
+ * jQuery UI CSS Framework 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+
+/*!
+ * jQuery UI CSS Framework 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Theming/API
+ *
+ * To view and modify this theme, visit http://jqueryui.com/themeroller/?ffDefault=Verdana,Arial,sans-serif&fwDefault=normal&fsDefault=1.1em&cornerRadius=3px&bgColorHeader=cccccc&bgTextureHeader=03_highlight_soft.png&bgImgOpacityHeader=75&borderColorHeader=b8b8b8&fcHeader=000000&iconColorHeader=000000&bgColorContent=fcfdfd&bgTextureContent=06_inset_hard.png&bgImgOpacityContent=100&borderColorContent=b8b8b8&fcContent=222222&iconColorContent=0078ae&bgColorDefault=eeeeee&bgTextureDefault=03_highlight_soft.png&bgImgOpacityDefault=45&borderColorDefault=b8b8b8&fcDefault=000000&iconColorDefault=000000&bgColorHover=dddddd&bgTextureHover=03_highlight_soft.png&bgImgOpacityHover=75&borderColorHover=b8b8b8&fcHover=000000&iconColorHover=000000&bgColorActive=4a749a&bgTextureActive=04_highlight_hard.png&bgImgOpacityActive=30&borderColorActive=6089af&fcActive=ffffff&iconColorActive=000000&bgColorHighlight=4a749a&bgTextureHighlight=10_dots_medium.png&bgImgOpacityHighlight=100&borderColorHighlight=6089af&fcHighlight=ffffff&iconColorHighlight=f7a50d&bgColorError=a8330b&bgTextureError=12_gloss_wave.png&bgImgOpacityError=45&borderColorError=cd270a&fcError=ffffff&iconColorError=fcd113&bgColorOverlay=aaaaaa&bgTextureOverlay=01_flat.png&bgImgOpacityOverlay=75&opacityOverlay=30&bgColorShadow=999999&bgTextureShadow=01_flat.png&bgImgOpacityShadow=55&opacityShadow=45&thicknessShadow=0px&offsetTopShadow=5px&offsetLeftShadow=5px&cornerRadiusShadow=5px
+ */
+
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif; font-size: 1em; }
+.ui-widget-content { border: 1px solid #b8b8b8; background: #fcfdfd url(images/ui-bg_inset-hard_100_fcfdfd_1x100.png) 50% bottom repeat-x; color: #222222; }
+.ui-widget-content a { color: #222222; }
+.ui-widget-header { border: 1px solid #b8b8b8; background: #cccccc url(images/ui-bg_highlight-soft_75_cccccc_1x100.png) 50% 50% repeat-x; color: #000000; font-weight: bold; }
+.ui-widget-header a { color: #000000; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #b8b8b8; background: #eeeeee url(images/ui-bg_highlight-soft_45_eeeeee_1x100.png) 50% 50% repeat-x; font-weight: normal; color: #000000; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #000000; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #b8b8b8; background: #dddddd url(images/ui-bg_highlight-soft_75_dddddd_1x100.png) 50% 50% repeat-x; font-weight: normal; color: #000000; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #000000; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #6089af; background: #4a749a url(images/ui-bg_highlight-hard_30_4a749a_1x100.png) 50% 50% repeat-x; font-weight: normal; color: #ffffff; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #ffffff; text-decoration: none; }
+.ui-widget :active { outline: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #6089af; background: url(images/ui-bg_dots-medium_100_4a749a_4x4.png) 50% 50% repeat; color: #ffffff; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #ffffff; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd270a; background: #a8330b url(images/ui-bg_gloss-wave_45_a8330b_500x100.png) 50% top repeat-x; color: #ffffff; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #ffffff; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #ffffff; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_0078ae_256x240.png); }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_0078ae_256x240.png); }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_000000_256x240.png); }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_000000_256x240.png); }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_000000_256x240.png); }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_000000_256x240.png); }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_f7a50d_256x240.png); }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_fcd113_256x240.png); }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-off { background-position: -96px -144px; }
+.ui-icon-radio-on { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 3px; -webkit-border-top-left-radius: 3px; -khtml-border-top-left-radius: 3px; border-top-left-radius: 3px; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 3px; -webkit-border-top-right-radius: 3px; -khtml-border-top-right-radius: 3px; border-top-right-radius: 3px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 3px; -webkit-border-bottom-left-radius: 3px; -khtml-border-bottom-left-radius: 3px; border-bottom-left-radius: 3px; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 3px; -webkit-border-bottom-right-radius: 3px; -khtml-border-bottom-right-radius: 3px; border-bottom-right-radius: 3px; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa url(images/ui-bg_flat_75_aaaaaa_40x100.png) 50% 50% repeat-x; opacity: .30;filter:Alpha(Opacity=30); }
+.ui-widget-shadow { margin: 5px 0 0 5px; padding: 0px; background: #999999 url(images/ui-bg_flat_55_999999_40x100.png) 50% 50% repeat-x; opacity: .45;filter:Alpha(Opacity=45); -moz-border-radius: 5px; -khtml-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; }/*!
+ * jQuery UI Resizable 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Resizable#theming
+ */
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}/*!
+ * jQuery UI Selectable 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Selectable#theming
+ */
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+/*!
+ * jQuery UI Accordion 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Accordion#theming
+ */
+/* IE/Win - Fix animation bug - #4615 */
+.ui-accordion { width: 100%; }
+.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; }
+.ui-accordion .ui-accordion-li-fix { display: inline; }
+.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; }
+.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; }
+.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; }
+.ui-accordion .ui-accordion-content-active { display: block; }
+/*!
+ * jQuery UI Autocomplete 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Autocomplete#theming
+ */
+.ui-autocomplete { position: absolute; cursor: default; }
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+/*
+ * jQuery UI Menu 1.8.22
+ *
+ * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Menu#theming
+ */
+.ui-menu {
+ list-style:none;
+ padding: 2px;
+ margin: 0;
+ display:block;
+ float: left;
+}
+.ui-menu .ui-menu {
+ margin-top: -3px;
+}
+.ui-menu .ui-menu-item {
+ margin:0;
+ padding: 0;
+ zoom: 1;
+ float: left;
+ clear: left;
+ width: 100%;
+}
+.ui-menu .ui-menu-item a {
+ text-decoration:none;
+ display:block;
+ padding:.2em .4em;
+ line-height:1.5;
+ zoom:1;
+}
+.ui-menu .ui-menu-item a.ui-state-hover,
+.ui-menu .ui-menu-item a.ui-state-active {
+ font-weight: normal;
+ margin: -1px;
+}
+/*!
+ * jQuery UI Button 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Button#theming
+ */
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+/*!
+ * jQuery UI Dialog 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Dialog#theming
+ */
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+/*!
+ * jQuery UI Slider 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Slider#theming
+ */
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }/*!
+ * jQuery UI Tabs 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Tabs#theming
+ */
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+.ui-tabs .ui-tabs-hide { display: none !important; }
+/*!
+ * jQuery UI Datepicker 1.8.22
+ *
+ * Copyright 2012, AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://docs.jquery.com/UI/Datepicker#theming
+ */
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding