From 2350cef6541a6019f5c9e2961bd20cd36d6d2038 Mon Sep 17 00:00:00 2001 From: Payten Giles Date: Mon, 2 May 2022 14:19:46 +1000 Subject: [PATCH] Drop digitization_work_order plugin requirement by including all the bits we need --- Gemfile | 4 +- README.md | 14 +- .../controllers/spreadsheet_bulk_updater.rb | 10 + backend/model/bulk_updater_small_tree.rb | 7 + backend/model/trees_mixin.rb | 99 ++ backend/plugin_init.rb | 10 - frontend/assets/bulk_updater.css | 74 ++ frontend/assets/bulk_updater_table.js | 326 ++++++ frontend/assets/fattable.css | 120 +++ frontend/assets/fattable.js | 998 ++++++++++++++++++ .../spreadsheet_bulk_updater_controller.rb | 2 +- .../download_form.html.erb | 14 +- 12 files changed, 1654 insertions(+), 24 deletions(-) create mode 100644 backend/model/bulk_updater_small_tree.rb create mode 100644 backend/model/trees_mixin.rb create mode 100644 frontend/assets/bulk_updater_table.js create mode 100644 frontend/assets/fattable.css create mode 100644 frontend/assets/fattable.js diff --git a/Gemfile b/Gemfile index 867e2c8..d4344a6 100644 --- a/Gemfile +++ b/Gemfile @@ -1 +1,3 @@ -# Placeholder \ No newline at end of file +ASpaceGems.setup if defined? ASpaceGems + +gem "write_xlsx", "1.01.0" \ No newline at end of file diff --git a/README.md b/README.md index 783492f..e96231e 100644 --- a/README.md +++ b/README.md @@ -78,16 +78,22 @@ existing note, a new note will be created. ## Prerequisites -This plugin relies on the `digitization_work_order` plugin being enabled, -available here: - - https://github.com/hudmol/digitization_work_order. +### MySQL A MySQL database is also required. For instructions on how to setup MySQL and ArchivesSpace please see: https://archivesspace.github.io/tech-docs/provisioning/mysql.html +### `v1.4.x` and under: `digitization_work_order` plugin + +**This prerequisite was dropped as of release `v1.5.0`.** + +For all releases up to and and including `v1.4.x`, this plugin relies on the +`digitization_work_order` plugin being enabled, available here: + + https://github.com/hudmol/digitization_work_order. + ## Installation Download the latest release from the Releases tab in Github: diff --git a/backend/controllers/spreadsheet_bulk_updater.rb b/backend/controllers/spreadsheet_bulk_updater.rb index d24b797..d7663c8 100644 --- a/backend/controllers/spreadsheet_bulk_updater.rb +++ b/backend/controllers/spreadsheet_bulk_updater.rb @@ -30,4 +30,14 @@ class ArchivesSpaceService < Sinatra::Base ] end + Endpoint.get('/plugins/spreadsheet_bulk_updater/repositories/:repo_id/resources/:id/small_tree') + .description("Generate the archival object tree for a resource") + .params(["repo_id", :repo_id], + ["id", :id]) + .permissions([:view_repository]) + .returns([200, ""]) \ + do + json_response(BulkUpdaterSmallTree.for_resource(params[:id])) + end + end diff --git a/backend/model/bulk_updater_small_tree.rb b/backend/model/bulk_updater_small_tree.rb new file mode 100644 index 0000000..53e59fe --- /dev/null +++ b/backend/model/bulk_updater_small_tree.rb @@ -0,0 +1,7 @@ +class BulkUpdaterSmallTree + + def self.for_resource(resource_id) + Resource.get_or_die(resource_id).bulk_updater_quick_tree + end + +end diff --git a/backend/model/trees_mixin.rb b/backend/model/trees_mixin.rb new file mode 100644 index 0000000..6bb5ff4 --- /dev/null +++ b/backend/model/trees_mixin.rb @@ -0,0 +1,99 @@ +module Trees + def bulk_updater_quick_tree + links = {} + properties = {} + + root_type = self.class.root_type + node_type = self.class.node_type + + top_nodes = [] + + container_info = bulk_updater_fetch_container_info + + query = build_node_query + + offset = 0 + loop do + nodes = query.limit(NODE_PAGE_SIZE, offset) + + nodes.each do |node| + if node.parent_id + links[node.parent_id] ||= [] + links[node.parent_id] << [node.position, node.id] + else + top_nodes << [node.position, node.id] + end + + properties[node.id] = { + :title => node.display_string, + :uri => self.class.uri_for(node_type, node.id), + :ref_id => node[:ref_id], + :component_id => node[:component_id], + :container => container_info.fetch(node.id, nil), + } + + # Drop out nils to keep the object size as small as possible + properties[node.id].keys.each do |key| + properties[node.id].delete(key) if properties[node.id][key].nil? + end + end + + if nodes.empty? + break + else + offset += NODE_PAGE_SIZE + end + end + + result = { + :title => self.title, + :identifier => Identifiers.format(Identifiers.parse(self.identifier)), + :children => top_nodes.sort_by(&:first).map {|_, node| self.class.assemble_tree(node, links, properties)}, + :uri => self.class.uri_for(root_type, self.id) + } + + result + end + + + private + + def bulk_updater_containers_ds + TopContainer.linked_instance_ds + .join(:archival_object, :id => :instance__archival_object_id) + .left_join(:enumeration_value___top_container_type, :id => :top_container__type_id) + .left_join(:enumeration_value___sub_container_type_2, :id => :sub_container__type_2_id) + .left_join(:enumeration_value___sub_container_type_3, :id => :sub_container__type_3_id) + .filter(:archival_object__root_record_id => self.id) + .select(Sequel.as(:archival_object__id, :archival_object_id), + Sequel.as(:top_container__barcode, :top_container_barcode), + Sequel.as(:top_container_type__value, :top_container_type), + Sequel.as(:top_container__indicator, :top_container_indicator), + Sequel.as(:sub_container_type_2__value, :sub_container_type_2), + Sequel.as(:sub_container__indicator_2, :sub_container_indicator_2), + Sequel.as(:sub_container_type_3__value, :sub_container_type_3), + Sequel.as(:sub_container__indicator_3, :sub_container_indicator_3)) + end + + + def bulk_updater_fetch_container_info + result = {} + + bulk_updater_containers_ds.each do |row| + result[row[:archival_object_id]] = [ + # BoxType Indicator [Barcode] + [row[:top_container_type], + row[:top_container_indicator], + row[:top_container_barcode] ? ('[' + row[:top_container_barcode] + ']') : nil].compact.join(': '), + + # BoxType_2 Indicator_2 + [row[:sub_container_type_2], row[:sub_container_indicator_2]].compact.join(': '), + + # BoxType_3 Indicator_3 + [row[:sub_container_type_3], row[:sub_container_indicator_3]].compact.join(': '), + ].reject(&:empty?).join(', ') + end + + result + end +end diff --git a/backend/plugin_init.rb b/backend/plugin_init.rb index 4f8415a..bb4d8be 100644 --- a/backend/plugin_init.rb +++ b/backend/plugin_init.rb @@ -1,13 +1,3 @@ -unless AppConfig[:plugins].include?('digitization_work_order') - msg = [ - "Hi there, thanks for trying out this great plugin!", - "Currently it relies on the digitization_work_order plugin, which you can download here: https://github.com/hudmol/digitization_work_order.", - "Thanks again!", - ] - Log.error("\n\nWe hit an error while starting as_spreadsheet_bulk_updater:\n\n" + msg.join("\n") + "\n\n") - raise msg.join(' ') -end - if ASpaceEnvironment.demo_db? msg = [ "Hi there, thanks for trying out this great plugin!", diff --git a/frontend/assets/bulk_updater.css b/frontend/assets/bulk_updater.css index a61b49b..0f34e49 100644 --- a/frontend/assets/bulk_updater.css +++ b/frontend/assets/bulk_updater.css @@ -18,3 +18,77 @@ .bulk-update-column-selector td.label-cell label, .bulk-update-column-selector td.label-cell input[type=checkbox] { margin: 0; } + +#bulk_updater_table { + white-space: nowrap; +} + +#bulk_updater_table > .fattable-body-container > div > div { + padding: 6px 0 0 10px; +} + +#bulk_updater_table .work-order-spaces { + float: left; + height: 200%; + margin-top: -10px; +} + +#bulk_updater_table .work-order-space { + display: inline-block; + width: 15px; + border-left: 1px dotted #DDD; + height: 100%; + margin-left: 10px; +} +#bulk_updater_table .work-order-label { +} +#bulk_updater_table .work-order-checkbox-label { + width: 100%; + text-align: center; +} +#bulk_updater_table .work-order-has-children .work-order-label { + font-weight: bold; +} + +#bulk_updater_table .metadata-field-label { + font-weight: bold; + color: #888; +} + +#bulk_updater_table .metadata-field-label:after { + content: ':'; + margin-right: 4px; +} + +#bulk_updater_table .metadata-field-value { + margin-right: 8px; +} + +#bulk_updater_buttons .additional-options legend { + padding-top: 10px; + margin-bottom: 0; +} + +.bulk_updater_modal { + display: none; + position: fixed; + top: 80px; + background: #FFF; + width: 50%; + border: 2px solid #DDD; + margin: auto; + padding: 20px; +} + +.modal_overlay { + display: none; + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: #000; + opacity: 0.5; + filter: alpha(opacity=50); +} + diff --git a/frontend/assets/bulk_updater_table.js b/frontend/assets/bulk_updater_table.js new file mode 100644 index 0000000..dc6a4d7 --- /dev/null +++ b/frontend/assets/bulk_updater_table.js @@ -0,0 +1,326 @@ +(function(exports) { + + var TABLE_SETTINGS = { + row_height: 50, + header_height: 40, + column_widths: [100, 4500], + bottom_padding_px: 50, + }; + + var flattenTree = function (tree, level) { + if (level === undefined) { + level = 0; + } + + var root = $.extend({}, tree); + root['level'] = level; + root['selected'] = false; + delete root['children']; + + var result = [root] + + if (tree['children'] && tree['children'].length > 0) { + root['children'] = true + for (var i = 0; i < tree['children'].length; i++) { + result = result.concat(flattenTree(tree['children'][i], level + 1)); + } + } + + return result; + }; + + var getIndexForUri = function(tree, uri) { + var index; + + $.each(tree, function(i, record) { + if (record.uri == uri) { + index = i; + return false + } + }); + + if (index == null) { + throw "uri not found in tree: " + uri; + } + + return index; + } + + + var filterTree = function (tree) { + /* A stack of the ancestry of the current element. For example, a + record four levels of nesting deep will have an ancestry of [0, 1, 2, + 3], since its parent was three levels deep, its grandparent was two + levels, and so on. */ + var ancestorLevels = []; + + return tree.filter(function (elt) { + /* Wind back our list of ancestors to find the parent of the + current record. If we go from level 5 to level 2, we've moved + back up the tree a couple of steps and need to discard those + entries. */ + while (ancestorLevels[ancestorLevels.length - 1] >= elt.level) { + ancestorLevels.pop(); + } + + /* If the current record is selected, or if its parent was, we keep it. + Additionally, the root node is always kept. */ + var keepRecord = (elt['level'] == 0 || + elt['selected'] || + (elt['level'] - 1) === ancestorLevels[ancestorLevels.length - 1]); + + /* If the current record met the criteria, record it so its children + are also kept. */ + if (elt['level'] == 0 || elt['selected']) { + ancestorLevels.push(elt['level']) + } + + return keepRecord; + }); + }; + + + var renderTable = function (tree) { + + var parents_selected = [tree[0]['selected']]; + + // We only want to keep nodes that are either: the root node, selected, the immediate child of a selected node + var filtered_tree = filterTree(tree); + + var selected_count = filtered_tree.filter(function (elt) { return elt['selected']; }).length; + + $("#selectedCount").html(selected_count); + $("#selectedCountInModal").html(selected_count); + $(".submit-btn").prop("disabled", selected_count === 0); + + $("#bulk_updater_table").empty(); + + var tableData = new fattable.SyncTableModel(); + tableData.getCellSync = function(i,j) { + if (i >= filtered_tree.length) { + /* Past the end of the table, so we render a blank */ + return { + 'content': '', + 'rowId': i, + }; + } + + if (j === 0) { + /* First column: render a checkbox */ + return { + "content": "", + "rowId": i, + } + } + + /* Left-pad each item relative to its level */ + var spaces = '
'; + + for (var space = 0; space < filtered_tree[i]['level']; space++) { + spaces += ''; + } + + spaces += '
'; + + var metadata = ''; + + var fields = [ + {property: 'ref_id', label: 'Ref ID'}, + {property: 'component_id', label: 'Component ID'}, + {property: 'identifier', label: 'Collection Identifier'}, + {property: 'container', label: 'Container'} + ]; + + $.each(fields, function (idx, elt) { + if (filtered_tree[i][elt.property]) { + var fragment = $('
'); + $(fragment).find('.metadata-field-label').text(elt.label); + $(fragment).find('.metadata-field-value').text(filtered_tree[i][elt.property]); + metadata += $(fragment).html(); + } + }); + + var content = (spaces + + '
' + + '
' + + '
' + metadata + '
' + + '
'); + + if (filtered_tree[i]['children']) { + content = '' + content + ''; + } + + return { + "content": content, + "rowId": i + } + }; + + tableData.getHeaderSync = function(j) { + if (j == 0) { + return "Selected"; + } else if (j == 1) { + return "Record Title"; + } else { + return "Col" + j; + } + } + + var painter = new fattable.Painter(); + painter.fillCell = function(cellDiv, data) { + cellDiv.innerHTML = data.content; + if (data.rowId % 2 == 0) { + cellDiv.className = "even"; + } + else { + cellDiv.className = "odd"; + } + } + + painter.fillCellPending = function(cellDiv, data) { + cellDiv.textContent = ""; + cellDiv.className = "pending"; + } + + var table = fattable({ + "container": "#bulk_updater_table", + "model": tableData, + "nbRows": filtered_tree.length, + "rowHeight": TABLE_SETTINGS.row_height, + "headerHeight": TABLE_SETTINGS.header_height, + "painter": painter, + "columnWidths": TABLE_SETTINGS.column_widths + }); + + var idealHeight = $(window).height() - $('#bulk_updater_buttons').height() - TABLE_SETTINGS.bottom_padding_px; + $("#bulk_updater_table").height(idealHeight); + + window.onresize = function() { + table.setup(); + } + + return table; + } + + exports.initBulkUpdaterTable = function (tree) { + var flattened = flattenTree(tree); + + let bulkUpdaterFatTable = renderTable(flattened); + + $("#bulk_updater_table").on("click", ":input", function(event) { + var $checkbox = $(event.target); + + var rowid = parseInt($checkbox.data("rowid")); + var uri = $checkbox.val(); + + // update record 'selected' state + var index = getIndexForUri(flattened, uri); + + // only get fancy if we are a parent + if (flattened[index].children) { + var lastIndex = index + 1; + var allSelected = true; + var level = flattened[index].level; + + // find out if all children are selected + for (var i = index + 1; i < flattened.length; i++) { + if (flattened[i].level > level) { + lastIndex = i; + allSelected = allSelected && flattened[i].selected; + } else { + break; + } + } + + if (flattened[index].selected && allSelected) { + // if the parent is currently selected and all children are selected + // then keep parent selected but deselect the children + $checkbox.prop("checked", true); + for (var i = index + 1; i <= lastIndex; i++) { + flattened[i].selected = false; + } + } else { + // otherwise just set them all to match + flattened[index].selected = $checkbox.is(":checked"); + for (var i = index + 1; i <= lastIndex; i++) { + flattened[i].selected = $checkbox.is(":checked"); + } + } + } else { + flattened[index].selected = $checkbox.is(":checked"); + } + + var offsetTop = bulkUpdaterFatTable.scroll.scrollTop; + + bulkUpdaterFatTable = renderTable(flattened); + + // navigate back to the row you just clicked + bulkUpdaterFatTable.goTo(rowid, 0); + bulkUpdaterFatTable.scroll.setScrollXY(0, offsetTop); + }); + + + $("#cancelBulkUpdater").on("click", function(event) { + event.preventDefault(); + $(".modal_overlay").hide(); + $(".bulk_updater_modal").hide(); + }); + + $(".submit-btn").on("click", function() { + var self = $(this); + + if (self.attr('id') == 'showBulkUpdaterModal') { + $(".modal_overlay").show(); + $(".bulk_updater_modal").show(); + return; + } + + if (self.attr('id') == 'downloadBulkUpdater') { + $(".modal_overlay").hide(); + $(".bulk_updater_modal").hide(); + } + + var extras = []; + + $('.additional-options input[type="checkbox"]').each(function (idx, checkbox) { + if ($(checkbox).is(':checked')) { + extras.push($(checkbox).prop('name')); + } + }); + + var selected = []; + $.each(flattened, function(i, elt) { + if (elt['selected']) { + selected.push(elt['uri']); + } + }); + + var form = $("#bulk_updater_form"); + var form_fields = form.find(".report-fields").empty(); + + $(form_fields).append( + $("") + .attr("type", "hidden") + .attr("name", "selected") + .val(JSON.stringify(selected)) + ); + + $(form_fields).append( + $("") + .attr("type", "hidden") + .attr("name", "report_type") + .val(self.prop('id')) + ); + + $(form_fields).append( + $("") + .attr("type", "hidden") + .attr("name", "extras") + .val(JSON.stringify(extras)) + ); + + $(form).submit(); + + }); + }; +})(window); diff --git a/frontend/assets/fattable.css b/frontend/assets/fattable.css new file mode 100644 index 0000000..4ac04c7 --- /dev/null +++ b/frontend/assets/fattable.css @@ -0,0 +1,120 @@ +/* TODO add an offset back... but beware, as it hads + some padding in the scrolling */ +.fattable-h-scrollbar { + padding: 0; + background-color: transparent; + position: absolute; + height: auto; + bottom: 0px; + left: 0px; + right: 0px; + overflow-x: scroll; + overflow-y: hidden; +} +.fattable-h-scrollbar > div { + padding: 0 !important; +} +.fattable-v-scrollbar { + padding: 0; + position: absolute; + background-color: transparent; + width: auto; + top: 0px; + bottom: 0px; + right: 0px; + overflow-x: hidden; + overflow-y: scroll; +} +.fattable-v-scrollbar > div { + padding: 0 !important; +} +.fattable { + overflow: hidden; + height: 700px; + width: 100%; + position: relative; +} +.fattable ::-webkit-scrollbar { + width: 10px; + height: 10px; +} +.fattable ::-webkit-scrollbar-track { + -webkit-border-radius: 5px; + border-radius: 5px; + background: rgba(0, 0, 0, 0.1); +} +.fattable ::-webkit-scrollbar-thumb { + -webkit-border-radius: 5px; + border-radius: 5px; + background: rgba(0, 0, 0, 0.2); +} +.fattable ::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.4); +} +.fattable ::-webkit-scrollbar-thumb:window-inactive { + background: rgba(0, 0, 0, 0.05); +} +.fattable-moving { + cursor: move; +} +.fattable-viewport { + overflow: hidden; + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; +} +.fattable-viewport > div { + padding: 2px; + position: absolute; + font-size: 12px; + font-family: sans-serif; + overflow: hidden; + text-overflow: ellipsis; +} +.fattable-body-container { + position: absolute; + overflow: hidden; + /* overflow-x:scroll; + overflow-y:scroll;*/ + + bottom: 0px; + width: 100%; +} +.fattable-header-container { + position: absolute; + overflow: hidden; + width: 100%; + height: 100px; +} +.fattable-header-container .fattable-viewport { + height: 100%; +} +.fattable-header-container .fattable-viewport > div { + height: 100%; +} + +.fattable-header-container { + background-color: #EEE; + border-bottom: solid 2px #CCC; + color: #222; +} +.fattable-header-container > div > div { + border-right: solid 1px #CCC; + padding: 10px; +} +.fattable-body-container { + background-color: #F6F6F6; +} +.fattable-body-container > div > div { + border-right: solid 1px #CCC; + padding: 10px; +} +.fattable-body-container > div > div.even { + color: #222; + background-color: #FFF; +} +.fattable-body-container > div > div.pending { + background-color: #FAFFFC; +} diff --git a/frontend/assets/fattable.js b/frontend/assets/fattable.js new file mode 100644 index 0000000..e00891b --- /dev/null +++ b/frontend/assets/fattable.js @@ -0,0 +1,998 @@ +// Generated by CoffeeScript 1.9.2 +(function() { + "use strict"; + var EventRegister, LRUCache, PagedAsyncTableModel, Painter, Promise, ScrollBarProxy, SyncTableModel, TableModel, TableView, binary_search, bound, closest, cumsum, distance, domReadyPromise, fattable, getTranformPrefix, k, ns, onLoad, prefixedTransformCssKey, smallest_diff_subsequence, v, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty, + slice = [].slice; + + cumsum = function(arr) { + var cs, len, n, s, x; + cs = [0.0]; + s = 0.0; + for (n = 0, len = arr.length; n < len; n++) { + x = arr[n]; + s += x; + cs.push(s); + } + return cs; + }; + + bound = function(x, m, M) { + if (x < m) { + return m; + } else if (x > M) { + return M; + } else { + return x; + } + }; + + Promise = (function() { + function Promise() { + this.callbacks = []; + this.result = false; + this.resolved = false; + } + + Promise.prototype.then = function(cb) { + if (this.resolved) { + return cb(this.result); + } else { + return this.callbacks.push(cb); + } + }; + + Promise.prototype.resolve = function(result) { + var cb, len, n, ref, results; + this.resolved = true; + this.result = result; + ref = this.callbacks; + results = []; + for (n = 0, len = ref.length; n < len; n++) { + cb = ref[n]; + results.push(cb(result)); + } + return results; + }; + + return Promise; + + })(); + + domReadyPromise = new Promise(); + + onLoad = function() { + document.removeEventListener("DOMContentLoaded", onLoad); + return domReadyPromise.resolve(); + }; + + document.addEventListener("DOMContentLoaded", onLoad); + + getTranformPrefix = function() { + var el, len, n, ref, testKey; + el = document.createElement("div"); + ref = ["transform", "WebkitTransform", "MozTransform", "OTransform", "MsTransform"]; + for (n = 0, len = ref.length; n < len; n++) { + testKey = ref[n]; + if (el.style[testKey] !== void 0) { + return testKey; + } + } + }; + + prefixedTransformCssKey = getTranformPrefix(); + + TableModel = (function() { + function TableModel() {} + + TableModel.prototype.hasCell = function(i, j) { + return false; + }; + + TableModel.prototype.hasHeader = function(j) { + return false; + }; + + TableModel.prototype.getCell = function(i, j, cb) { + if (cb == null) { + cb = (function() {}); + } + return cb("getCell not implemented"); + }; + + TableModel.prototype.getHeader = function(j, cb) { + if (cb == null) { + cb = (function() {}); + } + return cb("getHeader not implemented"); + }; + + return TableModel; + + })(); + + SyncTableModel = (function(superClass) { + extend(SyncTableModel, superClass); + + function SyncTableModel() { + return SyncTableModel.__super__.constructor.apply(this, arguments); + } + + SyncTableModel.prototype.getCellSync = function(i, j) { + return i + "," + j; + }; + + SyncTableModel.prototype.getHeaderSync = function(j) { + return "col " + j; + }; + + SyncTableModel.prototype.hasCell = function(i, j) { + return true; + }; + + SyncTableModel.prototype.hasHeader = function(j) { + return true; + }; + + SyncTableModel.prototype.getCell = function(i, j, cb) { + if (cb == null) { + cb = (function() {}); + } + return cb(this.getCellSync(i, j)); + }; + + SyncTableModel.prototype.getHeader = function(j, cb) { + if (cb == null) { + cb = (function() {}); + } + return cb(this.getHeaderSync(j)); + }; + + return SyncTableModel; + + })(TableModel); + + LRUCache = (function() { + function LRUCache(size) { + this.size = size != null ? size : 100; + this.data = {}; + this.lru_keys = []; + } + + LRUCache.prototype.has = function(k) { + return this.data.hasOwnProperty(k); + }; + + LRUCache.prototype.get = function(k) { + return this.data[k]; + }; + + LRUCache.prototype.set = function(k, v) { + var idx, removeKey; + idx = this.lru_keys.indexOf(k); + if (idx >= 0) { + this.lru_keys.splice(idx, 1); + } + this.lru_keys.push(k); + if (this.lru_keys.length >= this.size) { + removeKey = this.lru_keys.shift(); + delete this.data[removeKey]; + } + return this.data[k] = v; + }; + + return LRUCache; + + })(); + + PagedAsyncTableModel = (function(superClass) { + extend(PagedAsyncTableModel, superClass); + + function PagedAsyncTableModel(cacheSize) { + if (cacheSize == null) { + cacheSize = 100; + } + this.pageCache = new LRUCache(cacheSize); + this.headerPageCache = new LRUCache(cacheSize); + this.fetchCallbacks = {}; + this.headerFetchCallbacks = {}; + } + + PagedAsyncTableModel.prototype.cellPageName = function(i, j) {}; + + PagedAsyncTableModel.prototype.headerPageName = function(j) {}; + + PagedAsyncTableModel.prototype.getHeader = function(j) { + var pageName; + pageName = this.headerPageName(j); + if (this.headerPageCache.has(pageName)) { + return cb(this.headerPageCache.get(pageName)(j)); + } else if (this.headerFetchCallbacks[pageName] != null) { + return this.headerFetchCallbacks[pageName].push([j, cb]); + } else { + this.headerFetchCallbacks[pageName] = [[j, cb]]; + return this.fetchHeaderPage(pageName, (function(_this) { + return function(page) { + var cb, len, n, ref, ref1; + _this.headerPageCache.set(pageName, page); + ref = _this.headerFetchCallbacks[pageName]; + for (n = 0, len = ref.length; n < len; n++) { + ref1 = ref[n], j = ref1[0], cb = ref1[1]; + cb(page(j)); + } + return delete _this.headerFetchCallbacks[pageName]; + }; + })(this)); + } + }; + + PagedAsyncTableModel.prototype.hasCell = function(i, j) { + var pageName; + pageName = this.cellPageName(i, j); + return this.pageCache.has(pageName); + }; + + PagedAsyncTableModel.prototype.getCell = function(i, j, cb) { + var pageName; + if (cb == null) { + cb = (function() {}); + } + pageName = this.cellPageName(i, j); + if (this.pageCache.has(pageName)) { + return cb(this.pageCache.get(pageName)(i, j)); + } else if (this.fetchCallbacks[pageName] != null) { + return this.fetchCallbacks[pageName].push([i, j, cb]); + } else { + this.fetchCallbacks[pageName] = [[i, j, cb]]; + return this.fetchCellPage(pageName, (function(_this) { + return function(page) { + var len, n, ref, ref1; + _this.pageCache.set(pageName, page); + ref = _this.fetchCallbacks[pageName]; + for (n = 0, len = ref.length; n < len; n++) { + ref1 = ref[n], i = ref1[0], j = ref1[1], cb = ref1[2]; + cb(page(i, j)); + } + return delete _this.fetchCallbacks[pageName]; + }; + })(this)); + } + }; + + PagedAsyncTableModel.prototype.fetchCellPage = function(pageName, cb) {}; + + PagedAsyncTableModel.prototype.getHeader = function(j, cb) { + if (cb == null) { + cb = (function() {}); + } + return cb("col " + j); + }; + + return PagedAsyncTableModel; + + })(TableModel); + + binary_search = function(arr, x) { + var a, b, m, v; + if (arr[0] > x) { + return 0; + } else { + a = 0; + b = arr.length; + while (a + 2 < b) { + m = (a + b) / 2 | 0; + v = arr[m]; + if (v < x) { + a = m; + } else if (v > x) { + b = m; + } else { + return m; + } + } + return a; + } + }; + + distance = function(a1, a2) { + return Math.abs(a2 - a1); + }; + + closest = function() { + var d, d_, len, n, res, vals, x, x_; + x = arguments[0], vals = 2 <= arguments.length ? slice.call(arguments, 1) : []; + d = Infinity; + res = void 0; + for (n = 0, len = vals.length; n < len; n++) { + x_ = vals[n]; + d_ = distance(x, x_); + if (d_ < d) { + d = d_; + res = x_; + } + } + return res; + }; + + Painter = (function() { + function Painter() {} + + Painter.prototype.setupCell = function(cellDiv) {}; + + Painter.prototype.setupHeader = function(headerDiv) {}; + + Painter.prototype.cleanUpCell = function(cellDiv) {}; + + Painter.prototype.cleanUpHeader = function(headerDiv) {}; + + Painter.prototype.cleanUp = function(table) { + var _, cell, header, ref, ref1, results; + ref = table.cells; + for (_ in ref) { + cell = ref[_]; + this.cleanUpCell(cell); + } + ref1 = table.columns; + results = []; + for (_ in ref1) { + header = ref1[_]; + results.push(this.cleanUpHeader(header)); + } + return results; + }; + + Painter.prototype.fillHeader = function(headerDiv, data) { + return headerDiv.textContent = data; + }; + + Painter.prototype.fillCell = function(cellDiv, data) { + return cellDiv.textContent = data; + }; + + Painter.prototype.fillHeaderPending = function(headerDiv) { + return headerDiv.textContent = "NA"; + }; + + Painter.prototype.fillCellPending = function(cellDiv) { + return cellDiv.textContent = "NA"; + }; + + return Painter; + + })(); + + smallest_diff_subsequence = function(arr, w) { + var l, start; + l = 1; + start = 0; + while (start + l < arr.length) { + if (arr[start + l] - arr[start] > w) { + start += 1; + } else { + l += 1; + } + } + return l; + }; + + EventRegister = (function() { + function EventRegister() { + this.boundEvents = []; + } + + EventRegister.prototype.bind = function(target, evt, cb) { + this.boundEvents.push([target, evt, cb]); + return target.addEventListener(evt, cb); + }; + + EventRegister.prototype.unbindAll = function() { + var cb, evt, len, n, ref, ref1, target; + ref = this.boundEvents; + for (n = 0, len = ref.length; n < len; n++) { + ref1 = ref[n], target = ref1[0], evt = ref1[1], cb = ref1[2]; + target.removeEventListener(evt, cb); + } + return this.boundEvents = []; + }; + + return EventRegister; + + })(); + + ScrollBarProxy = (function() { + function ScrollBarProxy(container1, headerContainer, W, H, eventRegister, visible, enableDragMove) { + var bigContentHorizontal, bigContentVertical, getDelta, onMouseWheel, onMouseWheelHeader, supportedEvent; + this.container = container1; + this.headerContainer = headerContainer; + this.W = W; + this.H = H; + this.visible = visible != null ? visible : true; + this.enableDragMove = enableDragMove != null ? enableDragMove : true; + this.verticalScrollbar = document.createElement("div"); + this.verticalScrollbar.className += " fattable-v-scrollbar"; + this.horizontalScrollbar = document.createElement("div"); + this.horizontalScrollbar.className += " fattable-h-scrollbar"; + if (this.visible) { + this.container.appendChild(this.verticalScrollbar); + this.container.appendChild(this.horizontalScrollbar); + } + bigContentHorizontal = document.createElement("div"); + bigContentHorizontal.style.height = 1 + "px"; + bigContentHorizontal.style.width = this.W + "px"; + bigContentVertical = document.createElement("div"); + bigContentVertical.style.width = 1 + "px"; + bigContentVertical.style.height = this.H + "px"; + this.horizontalScrollbar.appendChild(bigContentHorizontal); + this.verticalScrollbar.appendChild(bigContentVertical); + this.scrollbarMargin = Math.max(this.horizontalScrollbar.offsetHeight, this.verticalScrollbar.offsetWidth); + this.verticalScrollbar.style.bottom = this.scrollbarMargin + "px"; + this.horizontalScrollbar.style.right = this.scrollbarMargin + "px"; + this.scrollLeft = 0; + this.scrollTop = 0; + this.horizontalScrollbar.onscroll = (function(_this) { + return function() { + if (!_this.dragging) { + if (_this.scrollLeft !== _this.horizontalScrollbar.scrollLeft) { + _this.scrollLeft = _this.horizontalScrollbar.scrollLeft; + return _this.onScroll(_this.scrollLeft, _this.scrollTop); + } + } + }; + })(this); + this.verticalScrollbar.onscroll = (function(_this) { + return function() { + if (!_this.dragging) { + if (_this.scrollTop !== _this.verticalScrollbar.scrollTop) { + _this.scrollTop = _this.verticalScrollbar.scrollTop; + return _this.onScroll(_this.scrollLeft, _this.scrollTop); + } + } + }; + })(this); + if (this.enableDragMove) { + eventRegister.bind(this.container, 'mousedown', (function(_this) { + return function(evt) { + if (evt.button === 1) { + _this.dragging = true; + _this.container.className = "fattable-body-container fattable-moving"; + _this.dragging_dX = _this.scrollLeft + evt.clientX; + return _this.dragging_dY = _this.scrollTop + evt.clientY; + } + }; + })(this)); + eventRegister.bind(this.container, 'mouseup', (function(_this) { + return function(evt) { + _this.dragging = false; + return _this.container.className = "fattable-body-container"; + }; + })(this)); + eventRegister.bind(this.container, 'mousemove', (function(_this) { + return function(evt) { + var deferred; + deferred = function() { + var newX, newY; + if (_this.dragging) { + newX = -evt.clientX + _this.dragging_dX; + newY = -evt.clientY + _this.dragging_dY; + return _this.setScrollXY(newX, newY); + } + }; + return window.setTimeout(deferred, 0); + }; + })(this)); + eventRegister.bind(this.container, 'mouseout', (function(_this) { + return function(evt) { + if (_this.dragging) { + if ((evt.toElement == null) || (evt.toElement.parentElement.parentElement !== _this.container)) { + _this.container.className = "fattable-body-container"; + return _this.dragging = false; + } + } + }; + })(this)); + eventRegister.bind(this.headerContainer, 'mousedown', (function(_this) { + return function(evt) { + if (evt.button === 1) { + _this.headerDragging = true; + _this.headerContainer.className = "fattable-header-container fattable-moving"; + return _this.dragging_dX = _this.scrollLeft + evt.clientX; + } + }; + })(this)); + eventRegister.bind(this.container, 'mouseup', (function(_this) { + return function(evt) { + var captureClick; + if (evt.button === 1) { + _this.headerDragging = false; + _this.headerContainer.className = "fattable-header-container"; + evt.stopPropagation(); + captureClick = function(e) { + e.stopPropagation(); + return this.removeEventListener('click', captureClick, true); + }; + return _this.container.addEventListener('click', captureClick, true); + } + }; + })(this)); + eventRegister.bind(this.headerContainer, 'mousemove', (function(_this) { + return function(evt) { + var deferred; + deferred = function() { + var newX; + if (_this.headerDragging) { + newX = -evt.clientX + _this.dragging_dX; + return _this.setScrollXY(newX); + } + }; + return window.setTimeout(deferred, 0); + }; + })(this)); + eventRegister.bind(this.headerContainer, 'mouseout', (function(_this) { + return function(evt) { + if (_this.headerDragging) { + if ((evt.toElement == null) || (evt.toElement.parentElement.parentElement !== _this.headerContainer)) { + _this.headerContainer.className = "fattable-header-container"; + } + return _this.headerDragging = false; + } + }; + })(this)); + } + if (this.W > this.horizontalScrollbar.clientWidth) { + this.maxScrollHorizontal = this.W - this.horizontalScrollbar.clientWidth; + } else { + this.maxScrollHorizontal = 0; + } + if (this.H > this.verticalScrollbar.clientHeight) { + this.maxScrollVertical = this.H - this.verticalScrollbar.clientHeight; + } else { + this.maxScrollVertical = 0; + } + supportedEvent = "DOMMouseScroll"; + if (this.container.onwheel !== void 0) { + supportedEvent = "wheel"; + } else if (this.container.onmousewheel !== void 0) { + supportedEvent = "mousewheel"; + } + getDelta = (function() { + switch (supportedEvent) { + case "wheel": + return function(evt) { + var deltaX, deltaY, ref, ref1, ref2, ref3; + switch (evt.deltaMode) { + case evt.DOM_DELTA_LINE: + deltaX = (ref = -50 * evt.deltaX) != null ? ref : 0; + deltaY = (ref1 = -50 * evt.deltaY) != null ? ref1 : 0; + break; + case evt.DOM_DELTA_PIXEL: + deltaX = (ref2 = -1 * evt.deltaX) != null ? ref2 : 0; + deltaY = (ref3 = -1 * evt.deltaY) != null ? ref3 : 0; + } + return [deltaX, deltaY]; + }; + case "mousewheel": + return function(evt) { + var deltaX, deltaY, ref, ref1; + deltaX = 0; + deltaY = 0; + deltaX = (ref = evt.wheelDeltaX) != null ? ref : 0; + deltaY = (ref1 = evt.wheelDeltaY) != null ? ref1 : evt.wheelDelta; + return [deltaX, deltaY]; + }; + case "DOMMouseScroll": + return function(evt) { + var deltaX, deltaY; + deltaX = 0; + deltaY = 0; + if (evt.axis === evt.HORIZONTAL_AXI) { + deltaX = -50.0 * evt.detail; + } else { + deltaY = -50.0 * evt.detail; + } + return [deltaX, deltaY]; + }; + } + })(); + onMouseWheel = (function(_this) { + return function(evt) { + var deltaX, deltaY, has_scrolled, ref; + ref = getDelta(evt), deltaX = ref[0], deltaY = ref[1]; + has_scrolled = _this.setScrollXY(_this.scrollLeft - deltaX, _this.scrollTop - deltaY); + if (has_scrolled) { + return evt.preventDefault(); + } + }; + })(this); + onMouseWheelHeader = (function(_this) { + return function(evt) { + var _, deltaX, has_scrolled, ref; + ref = getDelta(evt), deltaX = ref[0], _ = ref[1]; + has_scrolled = _this.setScrollXY(_this.scrollLeft - deltaX, _this.scrollTop); + if (has_scrolled) { + return evt.preventDefault(); + } + }; + })(this); + eventRegister.bind(this.container, supportedEvent, onMouseWheel); + eventRegister.bind(this.headerContainer, supportedEvent, onMouseWheelHeader); + } + + ScrollBarProxy.prototype.onScroll = function(x, y) {}; + + ScrollBarProxy.prototype.setScrollXY = function(x, y) { + var has_scrolled; + has_scrolled = false; + if (x != null) { + x = bound(x, 0, this.maxScrollHorizontal); + if (this.scrollLeft !== x) { + has_scrolled = true; + this.scrollLeft = x; + } + } else { + x = this.scrollLeft; + } + if (y != null) { + y = bound(y, 0, this.maxScrollVertical); + if (this.scrollTop !== y) { + has_scrolled = true; + this.scrollTop = y; + } + } else { + y = this.scrollTop; + } + this.horizontalScrollbar.scrollLeft = x; + this.verticalScrollbar.scrollTop = y; + this.onScroll(x, y); + return has_scrolled; + }; + + return ScrollBarProxy; + + })(); + + TableView = (function() { + TableView.prototype.readRequiredParameter = function(parameters, k, default_value) { + if (parameters[k] == null) { + if (default_value === void 0) { + throw "Expected parameter <" + k + ">"; + } else { + return this[k] = default_value; + } + } else { + return this[k] = parameters[k]; + } + }; + + function TableView(parameters) { + var container; + container = parameters.container; + if (container == null) { + throw "container not specified."; + } + if (typeof container === "string") { + this.container = document.querySelector(container); + } else if (typeof container === "object") { + this.container = container; + } else { + throw "Container must be a string or a dom element."; + } + this.readRequiredParameter(parameters, "painter", new Painter()); + this.readRequiredParameter(parameters, "autoSetup", true); + this.readRequiredParameter(parameters, "model"); + this.readRequiredParameter(parameters, "nbRows"); + this.readRequiredParameter(parameters, "rowHeight"); + this.readRequiredParameter(parameters, "columnWidths"); + this.readRequiredParameter(parameters, "rowHeight"); + this.readRequiredParameter(parameters, "headerHeight"); + this.readRequiredParameter(parameters, "scrollBarVisible", true); + this.readRequiredParameter(parameters, "enableDragMove", true); + this.nbCols = this.columnWidths.length; + if ((" " + this.container.className + " ").search(/\sfattable\s/) === -1) { + this.container.className += " fattable"; + } + this.H = this.rowHeight * this.nbRows; + this.columnOffset = cumsum(this.columnWidths); + this.W = this.columnOffset[this.columnOffset.length - 1]; + this.columns = {}; + this.cells = {}; + this.eventRegister = new EventRegister(); + this.getContainerDimension(); + if (this.autoSetup) { + domReadyPromise.then((function(_this) { + return function() { + return _this.setup(); + }; + })(this)); + } + } + + TableView.prototype.getContainerDimension = function() { + this.w = this.container.offsetWidth; + this.h = this.container.offsetHeight - this.headerHeight; + this.nbColsVisible = Math.min(smallest_diff_subsequence(this.columnOffset, this.w) + 2, this.columnWidths.length); + return this.nbRowsVisible = Math.min((this.h / this.rowHeight | 0) + 2, this.nbRows); + }; + + TableView.prototype.leftTopCornerFromXY = function(x, y) { + var i, j; + i = bound(y / this.rowHeight | 0, 0, this.nbRows - this.nbRowsVisible); + j = bound(binary_search(this.columnOffset, x), 0, this.nbCols - this.nbColsVisible); + return [i, j]; + }; + + TableView.prototype.cleanUp = function() { + var ref; + this.eventRegister.unbindAll(); + if ((ref = this.scroll) != null) { + ref.onScroll = null; + } + this.painter.cleanUp(this); + this.container.innerHTML = ""; + this.bodyContainer = null; + return this.headerContainer = null; + }; + + TableView.prototype.setup = function() { + var c, el, i, j, n, o, onScroll, p, ref, ref1, ref2, ref3, ref4, ref5; + this.cleanUp(); + this.getContainerDimension(); + this.columns = {}; + this.cells = {}; + this.container.innerHTML = ""; + this.headerContainer = document.createElement("div"); + this.headerContainer.className += " fattable-header-container"; + this.headerContainer.style.height = this.headerHeight + "px"; + this.headerViewport = document.createElement("div"); + this.headerViewport.className = "fattable-viewport"; + this.headerViewport.style.width = this.w + "px"; + this.headerViewport.style.height = this.headerHeight + "px"; + this.headerContainer.appendChild(this.headerViewport); + this.bodyContainer = document.createElement("div"); + this.bodyContainer.className = "fattable-body-container"; + this.bodyContainer.style.top = this.headerHeight + "px"; + this.bodyViewport = document.createElement("div"); + this.bodyViewport.className = "fattable-viewport"; + this.bodyViewport.style.width = this.w + "px"; + this.bodyViewport.style.height = this.h + "px"; + for (j = n = ref = this.nbColsVisible, ref1 = this.nbColsVisible * 2; n < ref1; j = n += 1) { + for (i = o = ref2 = this.nbRowsVisible, ref3 = this.nbRowsVisible * 2; o < ref3; i = o += 1) { + el = document.createElement("div"); + this.painter.setupCell(el); + el.pending = false; + el.style.height = this.rowHeight + "px"; + this.bodyViewport.appendChild(el); + this.cells[i + "," + j] = el; + } + } + for (c = p = ref4 = this.nbColsVisible, ref5 = this.nbColsVisible * 2; p < ref5; c = p += 1) { + el = document.createElement("div"); + el.style.height = this.headerHeight + "px"; + el.pending = false; + this.painter.setupHeader(el); + this.columns[c] = el; + this.headerViewport.appendChild(el); + } + this.firstVisibleRow = this.nbRowsVisible; + this.firstVisibleColumn = this.nbColsVisible; + this.display(0, 0); + this.container.appendChild(this.bodyContainer); + this.container.appendChild(this.headerContainer); + this.bodyContainer.appendChild(this.bodyViewport); + this.refreshAllContent(); + this.scroll = new ScrollBarProxy(this.bodyContainer, this.headerContainer, this.W, this.H, this.eventRegister, this.scrollBarVisible, this.enableDragMove); + onScroll = (function(_this) { + return function(x, y) { + var _, cell, col, ref6, ref7, ref8; + ref6 = _this.leftTopCornerFromXY(x, y), i = ref6[0], j = ref6[1]; + _this.display(i, j); + ref7 = _this.columns; + for (_ in ref7) { + col = ref7[_]; + col.style[prefixedTransformCssKey] = "translate(" + (col.left - x) + "px, 0px)"; + } + ref8 = _this.cells; + for (_ in ref8) { + cell = ref8[_]; + cell.style[prefixedTransformCssKey] = "translate(" + (cell.left - x) + "px," + (cell.top - y) + "px)"; + } + clearTimeout(_this.scrollEndTimer); + _this.scrollEndTimer = setTimeout(_this.refreshAllContent.bind(_this), 200); + return _this.onScroll(x, y); + }; + })(this); + this.scroll.onScroll = onScroll; + return onScroll(0, 0); + }; + + TableView.prototype.refreshAllContent = function(evenNotPending) { + var cell, fn, header, i, j, k, n, ref, ref1, results; + if (evenNotPending == null) { + evenNotPending = false; + } + fn = (function(_this) { + return function(header) { + if (evenNotPending || header.pending) { + return _this.model.getHeader(j, function(data) { + header.pending = false; + return _this.painter.fillHeader(header, data); + }); + } + }; + })(this); + results = []; + for (j = n = ref = this.firstVisibleColumn, ref1 = this.firstVisibleColumn + this.nbColsVisible; n < ref1; j = n += 1) { + header = this.columns[j]; + fn(header); + results.push((function() { + var o, ref2, ref3, results1; + results1 = []; + for (i = o = ref2 = this.firstVisibleRow, ref3 = this.firstVisibleRow + this.nbRowsVisible; o < ref3; i = o += 1) { + k = i + "," + j; + cell = this.cells[k]; + if (evenNotPending || cell.pending) { + results1.push((function(_this) { + return function(cell) { + return _this.model.getCell(i, j, function(data) { + cell.pending = false; + return _this.painter.fillCell(cell, data); + }); + }; + })(this)(cell)); + } else { + results1.push(void 0); + } + } + return results1; + }).call(this)); + } + return results; + }; + + TableView.prototype.onScroll = function(x, y) {}; + + TableView.prototype.goTo = function(i, j) { + var targetX, targetY; + targetY = i != null ? this.rowHeight * i : void 0; + targetX = j != null ? this.columnOffset[j] : void 0; + return this.scroll.setScrollXY(targetX, targetY); + }; + + TableView.prototype.display = function(i, j) { + this.headerContainer.style.display = "none"; + this.bodyContainer.style.display = "none"; + this.moveX(j); + this.moveY(i); + this.headerContainer.style.display = ""; + return this.bodyContainer.style.display = ""; + }; + + TableView.prototype.moveX = function(j) { + var cell, col_width, col_x, dest_j, dj, fn, header, i, k, last_i, last_j, n, o, offset_j, orig_j, ref, ref1, ref2, shift_j; + last_i = this.firstVisibleRow; + last_j = this.firstVisibleColumn; + shift_j = j - last_j; + if (shift_j === 0) { + return; + } + dj = Math.min(Math.abs(shift_j), this.nbColsVisible); + for (offset_j = n = 0, ref = dj; n < ref; offset_j = n += 1) { + if (shift_j > 0) { + orig_j = this.firstVisibleColumn + offset_j; + dest_j = j + offset_j + this.nbColsVisible - dj; + } else { + orig_j = this.firstVisibleColumn + this.nbColsVisible - dj + offset_j; + dest_j = j + offset_j; + } + col_x = this.columnOffset[dest_j]; + col_width = this.columnWidths[dest_j] + "px"; + header = this.columns[orig_j]; + delete this.columns[orig_j]; + if (this.model.hasHeader(dest_j)) { + this.model.getHeader(dest_j, (function(_this) { + return function(data) { + header.pending = false; + return _this.painter.fillHeader(header, data); + }; + })(this)); + } else if (!header.pending) { + header.pending = true; + this.painter.fillHeaderPending(header); + } + header.left = col_x; + header.style.width = col_width; + this.columns[dest_j] = header; + fn = (function(_this) { + return function(cell) { + if (_this.model.hasCell(i, dest_j)) { + return _this.model.getCell(i, dest_j, function(data) { + cell.pending = false; + return _this.painter.fillCell(cell, data); + }); + } else if (!cell.pending) { + cell.pending = true; + return _this.painter.fillCellPending(cell); + } + }; + })(this); + for (i = o = ref1 = last_i, ref2 = last_i + this.nbRowsVisible; o < ref2; i = o += 1) { + k = i + "," + orig_j; + cell = this.cells[k]; + delete this.cells[k]; + this.cells[i + "," + dest_j] = cell; + cell.left = col_x; + cell.style.width = col_width; + fn(cell); + } + } + return this.firstVisibleColumn = j; + }; + + TableView.prototype.moveY = function(i) { + var cell, dest_i, di, fn, j, k, last_i, last_j, n, o, offset_i, orig_i, ref, ref1, ref2, row_y, shift_i; + last_i = this.firstVisibleRow; + last_j = this.firstVisibleColumn; + shift_i = i - last_i; + if (shift_i === 0) { + return; + } + di = Math.min(Math.abs(shift_i), this.nbRowsVisible); + for (offset_i = n = 0, ref = di; n < ref; offset_i = n += 1) { + if (shift_i > 0) { + orig_i = last_i + offset_i; + dest_i = i + offset_i + this.nbRowsVisible - di; + } else { + orig_i = last_i + this.nbRowsVisible - di + offset_i; + dest_i = i + offset_i; + } + row_y = dest_i * this.rowHeight; + fn = (function(_this) { + return function(cell) { + if (_this.model.hasCell(dest_i, j)) { + return _this.model.getCell(dest_i, j, function(data) { + cell.pending = false; + return _this.painter.fillCell(cell, data); + }); + } else if (!cell.pending) { + cell.pending = true; + return _this.painter.fillCellPending(cell); + } + }; + })(this); + for (j = o = ref1 = last_j, ref2 = last_j + this.nbColsVisible; o < ref2; j = o += 1) { + k = orig_i + "," + j; + cell = this.cells[k]; + delete this.cells[k]; + this.cells[dest_i + "," + j] = cell; + cell.top = row_y; + fn(cell); + } + } + return this.firstVisibleRow = i; + }; + + return TableView; + + })(); + + fattable = function(params) { + return new TableView(params); + }; + + ns = { + TableModel: TableModel, + TableView: TableView, + Painter: Painter, + PagedAsyncTableModel: PagedAsyncTableModel, + SyncTableModel: SyncTableModel, + bound: bound + }; + + for (k in ns) { + v = ns[k]; + fattable[k] = v; + } + + window.fattable = fattable; + +}).call(this); \ No newline at end of file diff --git a/frontend/controllers/spreadsheet_bulk_updater_controller.rb b/frontend/controllers/spreadsheet_bulk_updater_controller.rb index 8e5b3e9..853ab87 100644 --- a/frontend/controllers/spreadsheet_bulk_updater_controller.rb +++ b/frontend/controllers/spreadsheet_bulk_updater_controller.rb @@ -97,7 +97,7 @@ def escape_xml_characters(tree) def load_tree - JSONModel::HTTP::get_json(@uri + "/small_tree") + JSONModel::HTTP::get_json("/plugins/spreadsheet_bulk_updater#{@uri}/small_tree") end diff --git a/frontend/views/spreadsheet_bulk_updater/download_form.html.erb b/frontend/views/spreadsheet_bulk_updater/download_form.html.erb index 62e207f..5790119 100644 --- a/frontend/views/spreadsheet_bulk_updater/download_form.html.erb +++ b/frontend/views/spreadsheet_bulk_updater/download_form.html.erb @@ -1,8 +1,6 @@ -<%= stylesheet_link_tag "#{AppConfig[:frontend_prefix]}assets/work_order.css" %> +<%= stylesheet_link_tag "#{AppConfig[:frontend_prefix]}assets/bulk_updater.css" %> <%= stylesheet_link_tag "#{AppConfig[:frontend_prefix]}assets/fattable.css" %> <%= javascript_include_tag "#{AppConfig[:frontend_prefix]}assets/fattable.js" %> - -<%= stylesheet_link_tag "#{AppConfig[:frontend_prefix]}assets/bulk_updater.css" %> <%= javascript_include_tag "#{AppConfig[:frontend_prefix]}assets/bulk_updater.js" %>
@@ -21,9 +19,9 @@ Selected Records:

-
+
- <%= form_tag({:action => :download}, {:method => 'post', :id => "work_order_form"}) do %> + <%= form_tag({:action => :download}, {:method => 'post', :id => "bulk_updater_form"}) do %> <%= hidden_field_tag "resource", @uri %>
@@ -171,10 +169,10 @@ <% end %>
-<%= javascript_include_tag "#{AppConfig[:frontend_prefix]}assets/work_order_table.js" %> +<%= javascript_include_tag "#{AppConfig[:frontend_prefix]}assets/bulk_updater_table.js" %>