From 2503523575f5c04e18a51d11108ecc7bc83dee5c Mon Sep 17 00:00:00 2001 From: "Jeremy B. Merrill" Date: Sun, 27 Jul 2014 22:07:30 -0400 Subject: [PATCH 01/25] stuff --- webapp/static/js/pdf_view.js | 136 +++++++++++++++++++++++------------ 1 file changed, 92 insertions(+), 44 deletions(-) diff --git a/webapp/static/js/pdf_view.js b/webapp/static/js/pdf_view.js index b0ccabb7..38032862 100644 --- a/webapp/static/js/pdf_view.js +++ b/webapp/static/js/pdf_view.js @@ -12,53 +12,52 @@ $(document).ready(function() { }); $('.has-tooltip').tooltip(); +}); + +// TODO: make sure render fires after document ready or something + +Tabula.DataView = Backbone.View.extend({ //only one + // flash clipboard + // data modal + // switch extraction method button + // advanced options button + // download button - Tabula.tour = new Tour( - { - storage: false, - onStart: function(){ - $('a#help-start').text("Close Help"); - }, - onEnd: function(){ - $('a#help-start').text("Help"); - } - }); - - Tabula.tour.addSteps([ - { - content: "Click and drag to select each table in your document. Once you've selected it, a window to preview your data will appear, along with options to download it as a spreadsheet.", - element: ".page-image#page-1", - title: "Select Tables", - placement: 'right' - }, - { - element: "#all-data", - title: "Download Data", - content: "When you've selected all of the tables in your PDF, click this button to preview the data from all of the selections and download it.", - placement: 'left' - }, - { - element: "#should-preview-data-checkbox", - title: "Preview Data Automatically?", - content: "After you select each table on a page, a data preview window will appear automatically. If you want to select multiple tables without interruption, uncheck this box to suppress the preview window.", - placement: 'left' - }, - { - element: "#thumb-page-2", - title: "Page Shortcuts", - content: "Click a thumbnail to skip directly to that page.", - placement: 'right', - parent: 'body' - } - ]); }); -//make the "follow you around bar" actually follow you around. ("sticky nav") -$(document).ready(function() { - $('.followyouaroundbar').affix({top: 70 }); +Tabula.PDFView = Backbone.View.extend({ //only one + // directions bar (move stuff up) + + initialize: function(){ + // create a bunch of PageViews + } +}); + +Tabula.PageView = Backbone.View.extend({ // one per page of the PDF + // selector? +}); + +Tabula.SelectionView = Backbone.View.extend({ // maybe multiple per page of the PDF + // repeat lasso button +}); + +Tabula.ControlPanelView = Backbone.View.extend({ // only one + // autopreview data button + // clear all selections button + // download all button + + render: function(){ + //make the "follow you around bar" actually follow you around. ("sticky nav") + $('.followyouaroundbar').affix({top: 70 }); + } }); -Tabula.PDFView = Backbone.View.extend({ +Tabula.SidebarView = Backbone.View.extend({ // only one + // sidebar scroll + +}) + +Tabula.UI = Backbone.View.extend({ el : 'body', events : { 'click button.close#directions' : 'moveSelectionsUp', @@ -89,6 +88,7 @@ Tabula.PDFView = Backbone.View.extend({ lastQuery: [{}], lastSelection: undefined, pageCount: undefined, + components: {}, initialize: function(){ _.bindAll(this, 'render', 'createImgareaselects', 'getTablesJson', 'total_selections', @@ -96,6 +96,11 @@ Tabula.PDFView = Backbone.View.extend({ 'query_all_data', 'redoQuery', 'toggleAdvancedOptionsShown'); this.pageCount = $('img.page-image').length; this.setAdvancedOptionsShown(); + + this.components['pdf_view'] = new Tabula.PDFView(); + this.components['control_panel'] = new Tabula.ControlPanelView(); + this.components['sidebar'] = new Tabula.SidebarView(); + this.render(); this.updateExtractionMethodButton(); }, @@ -103,9 +108,52 @@ Tabula.PDFView = Backbone.View.extend({ render : function(){ query_parameters = {}; this.getTablesJson(); + + // render out the components, as necessary return this; }, + createTour: function(){ + Tabula.tour = new Tour( + { + storage: false, + onStart: function(){ + $('a#help-start').text("Close Help"); + }, + onEnd: function(){ + $('a#help-start').text("Help"); + } + }); + + Tabula.tour.addSteps([ + { + content: "Click and drag to select each table in your document. Once you've selected it, a window to preview your data will appear, along with options to download it as a spreadsheet.", + element: ".page-image#page-1", + title: "Select Tables", + placement: 'right' + }, + { + element: "#all-data", + title: "Download Data", + content: "When you've selected all of the tables in your PDF, click this button to preview the data from all of the selections and download it.", + placement: 'left' + }, + { + element: "#should-preview-data-checkbox", + title: "Preview Data Automatically?", + content: "After you select each table on a page, a data preview window will appear automatically. If you want to select multiple tables without interruption, uncheck this box to suppress the preview window.", + placement: 'left' + }, + { + element: "#thumb-page-2", + title: "Page Shortcuts", + content: "Click a thumbnail to skip directly to that page.", + placement: 'right', + parent: 'body' + } + ]); + } + queryWithToggledExtractionMethod: function(e){ // console.log("before", this.extractionMethod); @@ -716,7 +764,7 @@ Tabula.PDFView = Backbone.View.extend({ }); $(function () { - Tabula.pdf_view = new Tabula.PDFView(); + Tabula.ui = new Tabula.UI(); }); @@ -735,4 +783,4 @@ function isElementInViewport (el) { rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && /*or $(window).height() */ rect.right <= (window.innerWidth || document.documentElement.clientWidth) /*or $(window).width() */ ); -} \ No newline at end of file +} From 07176ba2b41146a3e4c5e405ca99aa3af5d722c8 Mon Sep 17 00:00:00 2001 From: "Jeremy B. Merrill" Date: Thu, 31 Jul 2014 09:01:09 -0400 Subject: [PATCH 02/25] putting my workspace on hte internet; this doesn't work yet ;-) --- webapp/static/js/handlebars-v1.3.0.js | 2746 +++++++++++++++++ webapp/static/js/pdf_view.js | 1041 ++++--- webapp/static/js/templates/fixie-sidebar.jst | 8 + webapp/static/js/templates/help_bar.jst | 7 + webapp/static/js/templates/page.jst | 13 + .../static/js/templates/thumbnail-sidebar.jst | 14 + webapp/views/layout.erb | 1 + webapp/views/pdf_view.html.erb | 90 +- 8 files changed, 3466 insertions(+), 454 deletions(-) create mode 100644 webapp/static/js/handlebars-v1.3.0.js create mode 100644 webapp/static/js/templates/fixie-sidebar.jst create mode 100644 webapp/static/js/templates/help_bar.jst create mode 100644 webapp/static/js/templates/page.jst create mode 100644 webapp/static/js/templates/thumbnail-sidebar.jst diff --git a/webapp/static/js/handlebars-v1.3.0.js b/webapp/static/js/handlebars-v1.3.0.js new file mode 100644 index 00000000..bec7085c --- /dev/null +++ b/webapp/static/js/handlebars-v1.3.0.js @@ -0,0 +1,2746 @@ +/*! + + handlebars v1.3.0 + +Copyright (C) 2011 by Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +@license +*/ +/* exported Handlebars */ +var Handlebars = (function() { +// handlebars/safe-string.js +var __module4__ = (function() { + "use strict"; + var __exports__; + // Build out our basic SafeString type + function SafeString(string) { + this.string = string; + } + + SafeString.prototype.toString = function() { + return "" + this.string; + }; + + __exports__ = SafeString; + return __exports__; +})(); + +// handlebars/utils.js +var __module3__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + /*jshint -W004 */ + var SafeString = __dependency1__; + + var escape = { + "&": "&", + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /[&<>"'`]/g; + var possible = /[&<>"'`]/; + + function escapeChar(chr) { + return escape[chr] || "&"; + } + + function extend(obj, value) { + for(var key in value) { + if(Object.prototype.hasOwnProperty.call(value, key)) { + obj[key] = value[key]; + } + } + } + + __exports__.extend = extend;var toString = Object.prototype.toString; + __exports__.toString = toString; + // Sourced from lodash + // https://github.com/bestiejs/lodash/blob/master/LICENSE.txt + var isFunction = function(value) { + return typeof value === 'function'; + }; + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value === 'function' && toString.call(value) === '[object Function]'; + }; + } + var isFunction; + __exports__.isFunction = isFunction; + var isArray = Array.isArray || function(value) { + return (value && typeof value === 'object') ? toString.call(value) === '[object Array]' : false; + }; + __exports__.isArray = isArray; + + function escapeExpression(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof SafeString) { + return string.toString(); + } else if (!string && string !== 0) { + return ""; + } + + // Force a string conversion as this will be done by the append regardless and + // the regex test will do this transparently behind the scenes, causing issues if + // an object's to string has escaped characters in it. + string = "" + string; + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + } + + __exports__.escapeExpression = escapeExpression;function isEmpty(value) { + if (!value && value !== 0) { + return true; + } else if (isArray(value) && value.length === 0) { + return true; + } else { + return false; + } + } + + __exports__.isEmpty = isEmpty; + return __exports__; +})(__module4__); + +// handlebars/exception.js +var __module5__ = (function() { + "use strict"; + var __exports__; + + var errorProps = ['description', 'fileName', 'lineNumber', 'message', 'name', 'number', 'stack']; + + function Exception(message, node) { + var line; + if (node && node.firstLine) { + line = node.firstLine; + + message += ' - ' + line + ':' + node.firstColumn; + } + + var tmp = Error.prototype.constructor.call(this, message); + + // Unfortunately errors are not enumerable in Chrome (at least), so `for prop in tmp` doesn't work. + for (var idx = 0; idx < errorProps.length; idx++) { + this[errorProps[idx]] = tmp[errorProps[idx]]; + } + + if (line) { + this.lineNumber = line; + this.column = node.firstColumn; + } + } + + Exception.prototype = new Error(); + + __exports__ = Exception; + return __exports__; +})(); + +// handlebars/base.js +var __module2__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var Utils = __dependency1__; + var Exception = __dependency2__; + + var VERSION = "1.3.0"; + __exports__.VERSION = VERSION;var COMPILER_REVISION = 4; + __exports__.COMPILER_REVISION = COMPILER_REVISION; + var REVISION_CHANGES = { + 1: '<= 1.0.rc.2', // 1.0.rc.2 is actually rev2 but doesn't report it + 2: '== 1.0.0-rc.3', + 3: '== 1.0.0-rc.4', + 4: '>= 1.0.0' + }; + __exports__.REVISION_CHANGES = REVISION_CHANGES; + var isArray = Utils.isArray, + isFunction = Utils.isFunction, + toString = Utils.toString, + objectType = '[object Object]'; + + function HandlebarsEnvironment(helpers, partials) { + this.helpers = helpers || {}; + this.partials = partials || {}; + + registerDefaultHelpers(this); + } + + __exports__.HandlebarsEnvironment = HandlebarsEnvironment;HandlebarsEnvironment.prototype = { + constructor: HandlebarsEnvironment, + + logger: logger, + log: log, + + registerHelper: function(name, fn, inverse) { + if (toString.call(name) === objectType) { + if (inverse || fn) { throw new Exception('Arg not supported with multiple helpers'); } + Utils.extend(this.helpers, name); + } else { + if (inverse) { fn.not = inverse; } + this.helpers[name] = fn; + } + }, + + registerPartial: function(name, str) { + if (toString.call(name) === objectType) { + Utils.extend(this.partials, name); + } else { + this.partials[name] = str; + } + } + }; + + function registerDefaultHelpers(instance) { + instance.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Exception("Missing helper: '" + arg + "'"); + } + }); + + instance.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + if (isFunction(context)) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if (isArray(context)) { + if(context.length > 0) { + return instance.helpers.each(context, options); + } else { + return inverse(this); + } + } else { + return fn(context); + } + }); + + instance.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var i = 0, ret = "", data; + + if (isFunction(context)) { context = context.call(this); } + + if (options.data) { + data = createFrame(options.data); + } + + if(context && typeof context === 'object') { + if (isArray(context)) { + for(var j = context.length; i 0) { + throw new Exception("Invalid path: " + original, this); + } else if (part === "..") { + depth++; + } else { + this.isScoped = true; + } + } else { + dig.push(part); + } + } + + this.original = original; + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + + // an ID is simple if it only has one part, and that part is not + // `..` or `this`. + this.isSimple = parts.length === 1 && !this.isScoped && depth === 0; + + this.stringModeValue = this.string; + }, + + PartialNameNode: function(name, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "PARTIAL_NAME"; + this.name = name.original; + }, + + DataNode: function(id, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "DATA"; + this.id = id; + }, + + StringNode: function(string, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "STRING"; + this.original = + this.string = + this.stringModeValue = string; + }, + + IntegerNode: function(integer, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "INTEGER"; + this.original = + this.integer = integer; + this.stringModeValue = Number(integer); + }, + + BooleanNode: function(bool, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "BOOLEAN"; + this.bool = bool; + this.stringModeValue = bool === "true"; + }, + + CommentNode: function(comment, locInfo) { + LocationInfo.call(this, locInfo); + this.type = "comment"; + this.comment = comment; + } + }; + + // Must be exported as an object rather than the root of the module as the jison lexer + // most modify the object to operate properly. + __exports__ = AST; + return __exports__; +})(__module5__); + +// handlebars/compiler/parser.js +var __module9__ = (function() { + "use strict"; + var __exports__; + /* jshint ignore:start */ + /* Jison generated parser */ + var handlebars = (function(){ + var parser = {trace: function trace() { }, + yy: {}, + symbols_: {"error":2,"root":3,"statements":4,"EOF":5,"program":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"sexpr":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"CLOSE_UNESCAPED":24,"OPEN_PARTIAL":25,"partialName":26,"partial_option0":27,"sexpr_repetition0":28,"sexpr_option0":29,"dataName":30,"param":31,"STRING":32,"INTEGER":33,"BOOLEAN":34,"OPEN_SEXPR":35,"CLOSE_SEXPR":36,"hash":37,"hash_repetition_plus0":38,"hashSegment":39,"ID":40,"EQUALS":41,"DATA":42,"pathSegments":43,"SEP":44,"$accept":0,"$end":1}, + terminals_: {2:"error",5:"EOF",14:"CONTENT",15:"COMMENT",16:"OPEN_BLOCK",18:"CLOSE",19:"OPEN_INVERSE",20:"OPEN_ENDBLOCK",22:"OPEN",23:"OPEN_UNESCAPED",24:"CLOSE_UNESCAPED",25:"OPEN_PARTIAL",32:"STRING",33:"INTEGER",34:"BOOLEAN",35:"OPEN_SEXPR",36:"CLOSE_SEXPR",40:"ID",41:"EQUALS",42:"DATA",44:"SEP"}, + productions_: [0,[3,2],[3,1],[6,2],[6,3],[6,2],[6,1],[6,1],[6,0],[4,1],[4,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,4],[7,2],[17,3],[17,1],[31,1],[31,1],[31,1],[31,1],[31,1],[31,3],[37,1],[39,3],[26,1],[26,1],[26,1],[30,2],[21,1],[43,3],[43,1],[27,0],[27,1],[28,0],[28,2],[29,0],[29,1],[38,1],[38,2]], + performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + + var $0 = $$.length - 1; + switch (yystate) { + case 1: return new yy.ProgramNode($$[$0-1], this._$); + break; + case 2: return new yy.ProgramNode([], this._$); + break; + case 3:this.$ = new yy.ProgramNode([], $$[$0-1], $$[$0], this._$); + break; + case 4:this.$ = new yy.ProgramNode($$[$0-2], $$[$0-1], $$[$0], this._$); + break; + case 5:this.$ = new yy.ProgramNode($$[$0-1], $$[$0], [], this._$); + break; + case 6:this.$ = new yy.ProgramNode($$[$0], this._$); + break; + case 7:this.$ = new yy.ProgramNode([], this._$); + break; + case 8:this.$ = new yy.ProgramNode([], this._$); + break; + case 9:this.$ = [$$[$0]]; + break; + case 10: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; + break; + case 11:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1].inverse, $$[$0-1], $$[$0], this._$); + break; + case 12:this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0-1].inverse, $$[$0], this._$); + break; + case 13:this.$ = $$[$0]; + break; + case 14:this.$ = $$[$0]; + break; + case 15:this.$ = new yy.ContentNode($$[$0], this._$); + break; + case 16:this.$ = new yy.CommentNode($$[$0], this._$); + break; + case 17:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 18:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 19:this.$ = {path: $$[$0-1], strip: stripFlags($$[$0-2], $$[$0])}; + break; + case 20:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 21:this.$ = new yy.MustacheNode($$[$0-1], null, $$[$0-2], stripFlags($$[$0-2], $$[$0]), this._$); + break; + case 22:this.$ = new yy.PartialNode($$[$0-2], $$[$0-1], stripFlags($$[$0-3], $$[$0]), this._$); + break; + case 23:this.$ = stripFlags($$[$0-1], $$[$0]); + break; + case 24:this.$ = new yy.SexprNode([$$[$0-2]].concat($$[$0-1]), $$[$0], this._$); + break; + case 25:this.$ = new yy.SexprNode([$$[$0]], null, this._$); + break; + case 26:this.$ = $$[$0]; + break; + case 27:this.$ = new yy.StringNode($$[$0], this._$); + break; + case 28:this.$ = new yy.IntegerNode($$[$0], this._$); + break; + case 29:this.$ = new yy.BooleanNode($$[$0], this._$); + break; + case 30:this.$ = $$[$0]; + break; + case 31:$$[$0-1].isHelper = true; this.$ = $$[$0-1]; + break; + case 32:this.$ = new yy.HashNode($$[$0], this._$); + break; + case 33:this.$ = [$$[$0-2], $$[$0]]; + break; + case 34:this.$ = new yy.PartialNameNode($$[$0], this._$); + break; + case 35:this.$ = new yy.PartialNameNode(new yy.StringNode($$[$0], this._$), this._$); + break; + case 36:this.$ = new yy.PartialNameNode(new yy.IntegerNode($$[$0], this._$)); + break; + case 37:this.$ = new yy.DataNode($$[$0], this._$); + break; + case 38:this.$ = new yy.IdNode($$[$0], this._$); + break; + case 39: $$[$0-2].push({part: $$[$0], separator: $$[$0-1]}); this.$ = $$[$0-2]; + break; + case 40:this.$ = [{part: $$[$0]}]; + break; + case 43:this.$ = []; + break; + case 44:$$[$0-1].push($$[$0]); + break; + case 47:this.$ = [$$[$0]]; + break; + case 48:$$[$0-1].push($$[$0]); + break; + } + }, + table: [{3:1,4:2,5:[1,3],8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[3]},{5:[1,16],8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],22:[1,13],23:[1,14],25:[1,15]},{1:[2,2]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],25:[2,9]},{4:20,6:18,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{4:20,6:22,7:19,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,8],22:[1,13],23:[1,14],25:[1,15]},{5:[2,13],14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],25:[2,13]},{5:[2,14],14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],25:[2,14]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],25:[2,15]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],25:[2,16]},{17:23,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:29,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:30,21:24,30:25,40:[1,28],42:[1,27],43:26},{17:31,21:24,30:25,40:[1,28],42:[1,27],43:26},{21:33,26:32,32:[1,34],33:[1,35],40:[1,28],43:26},{1:[2,1]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],25:[2,10]},{10:36,20:[1,37]},{4:38,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,7],22:[1,13],23:[1,14],25:[1,15]},{7:39,8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,21],20:[2,6],22:[1,13],23:[1,14],25:[1,15]},{17:23,18:[1,40],21:24,30:25,40:[1,28],42:[1,27],43:26},{10:41,20:[1,37]},{18:[1,42]},{18:[2,43],24:[2,43],28:43,32:[2,43],33:[2,43],34:[2,43],35:[2,43],36:[2,43],40:[2,43],42:[2,43]},{18:[2,25],24:[2,25],36:[2,25]},{18:[2,38],24:[2,38],32:[2,38],33:[2,38],34:[2,38],35:[2,38],36:[2,38],40:[2,38],42:[2,38],44:[1,44]},{21:45,40:[1,28],43:26},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],40:[2,40],42:[2,40],44:[2,40]},{18:[1,46]},{18:[1,47]},{24:[1,48]},{18:[2,41],21:50,27:49,40:[1,28],43:26},{18:[2,34],40:[2,34]},{18:[2,35],40:[2,35]},{18:[2,36],40:[2,36]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],25:[2,11]},{21:51,40:[1,28],43:26},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,3],22:[1,13],23:[1,14],25:[1,15]},{4:52,8:4,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,5],22:[1,13],23:[1,14],25:[1,15]},{14:[2,23],15:[2,23],16:[2,23],19:[2,23],20:[2,23],22:[2,23],23:[2,23],25:[2,23]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],25:[2,12]},{14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],25:[2,18]},{18:[2,45],21:56,24:[2,45],29:53,30:60,31:54,32:[1,57],33:[1,58],34:[1,59],35:[1,61],36:[2,45],37:55,38:62,39:63,40:[1,64],42:[1,27],43:26},{40:[1,65]},{18:[2,37],24:[2,37],32:[2,37],33:[2,37],34:[2,37],35:[2,37],36:[2,37],40:[2,37],42:[2,37]},{14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],25:[2,17]},{5:[2,20],14:[2,20],15:[2,20],16:[2,20],19:[2,20],20:[2,20],22:[2,20],23:[2,20],25:[2,20]},{5:[2,21],14:[2,21],15:[2,21],16:[2,21],19:[2,21],20:[2,21],22:[2,21],23:[2,21],25:[2,21]},{18:[1,66]},{18:[2,42]},{18:[1,67]},{8:17,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,4],22:[1,13],23:[1,14],25:[1,15]},{18:[2,24],24:[2,24],36:[2,24]},{18:[2,44],24:[2,44],32:[2,44],33:[2,44],34:[2,44],35:[2,44],36:[2,44],40:[2,44],42:[2,44]},{18:[2,46],24:[2,46],36:[2,46]},{18:[2,26],24:[2,26],32:[2,26],33:[2,26],34:[2,26],35:[2,26],36:[2,26],40:[2,26],42:[2,26]},{18:[2,27],24:[2,27],32:[2,27],33:[2,27],34:[2,27],35:[2,27],36:[2,27],40:[2,27],42:[2,27]},{18:[2,28],24:[2,28],32:[2,28],33:[2,28],34:[2,28],35:[2,28],36:[2,28],40:[2,28],42:[2,28]},{18:[2,29],24:[2,29],32:[2,29],33:[2,29],34:[2,29],35:[2,29],36:[2,29],40:[2,29],42:[2,29]},{18:[2,30],24:[2,30],32:[2,30],33:[2,30],34:[2,30],35:[2,30],36:[2,30],40:[2,30],42:[2,30]},{17:68,21:24,30:25,40:[1,28],42:[1,27],43:26},{18:[2,32],24:[2,32],36:[2,32],39:69,40:[1,70]},{18:[2,47],24:[2,47],36:[2,47],40:[2,47]},{18:[2,40],24:[2,40],32:[2,40],33:[2,40],34:[2,40],35:[2,40],36:[2,40],40:[2,40],41:[1,71],42:[2,40],44:[2,40]},{18:[2,39],24:[2,39],32:[2,39],33:[2,39],34:[2,39],35:[2,39],36:[2,39],40:[2,39],42:[2,39],44:[2,39]},{5:[2,22],14:[2,22],15:[2,22],16:[2,22],19:[2,22],20:[2,22],22:[2,22],23:[2,22],25:[2,22]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],25:[2,19]},{36:[1,72]},{18:[2,48],24:[2,48],36:[2,48],40:[2,48]},{41:[1,71]},{21:56,30:60,31:73,32:[1,57],33:[1,58],34:[1,59],35:[1,61],40:[1,28],42:[1,27],43:26},{18:[2,31],24:[2,31],32:[2,31],33:[2,31],34:[2,31],35:[2,31],36:[2,31],40:[2,31],42:[2,31]},{18:[2,33],24:[2,33],36:[2,33],40:[2,33]}], + defaultActions: {3:[2,2],16:[2,1],50:[2,42]}, + parseError: function parseError(str, hash) { + throw new Error(str); + }, + parse: function parse(input) { + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = "", yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == "undefined") + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === "function") + this.parseError = this.yy.parseError; + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || 1; + if (typeof token !== "number") { + token = self.symbols_[token] || token; + } + return token; + } + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == "undefined") { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + var errStr = ""; + if (!recovering) { + expected = []; + for (p in table[state]) + if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + (this.terminals_[symbol] || symbol) + "'"; + } else { + errStr = "Parse error on line " + (yylineno + 1) + ": Unexpected " + (symbol == 1?"end of input":"'" + (this.terminals_[symbol] || symbol) + "'"); + } + this.parseError(errStr, {text: this.lexer.match, token: this.terminals_[symbol] || symbol, line: this.lexer.yylineno, loc: yyloc, expected: expected}); + } + } + if (action[0] instanceof Array && action.length > 1) { + throw new Error("Parse Error: multiple actions possible at state: " + state + ", token: " + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) + recovering--; + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; + } + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = {first_line: lstack[lstack.length - (len || 1)].first_line, last_line: lstack[lstack.length - 1].last_line, first_column: lstack[lstack.length - (len || 1)].first_column, last_column: lstack[lstack.length - 1].last_column}; + if (ranges) { + yyval._$.range = [lstack[lstack.length - (len || 1)].range[0], lstack[lstack.length - 1].range[1]]; + } + r = this.performAction.call(yyval, yytext, yyleng, yylineno, this.yy, action[1], vstack, lstack); + if (typeof r !== "undefined") { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; + } + } + return true; + } + }; + + + function stripFlags(open, close) { + return { + left: open.charAt(2) === '~', + right: close.charAt(0) === '~' || close.charAt(1) === '~' + }; + } + + /* Jison generated lexer */ + var lexer = (function(){ + var lexer = ({EOF:1, + parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); + } + }, + setInput:function (input) { + this._input = input; + this._more = this._less = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = {first_line:1,first_column:0,last_line:1,last_column:0}; + if (this.options.ranges) this.yylloc.range = [0,0]; + this.offset = 0; + return this; + }, + input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) this.yylloc.range[1]++; + + this._input = this._input.slice(1); + return ch; + }, + unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length-len-1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length-1); + this.matched = this.matched.substr(0, this.matched.length-1); + + if (lines.length-1) this.yylineno -= lines.length-1; + var r = this.yylloc.range; + + this.yylloc = {first_line: this.yylloc.first_line, + last_line: this.yylineno+1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + oldLines[oldLines.length - lines.length].length - lines[0].length: + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + return this; + }, + more:function () { + this._more = true; + return this; + }, + less:function (n) { + this.unput(this.match.slice(n)); + }, + pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20)+(next.length > 20 ? '...':'')).replace(/\n/g, ""); + }, + showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c+"^"; + }, + next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) this.done = true; + + var token, + match, + tempMatch, + index, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (!this.options.flex) break; + } + } + if (match) { + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) this.yylineno += lines.length; + this.yylloc = {first_line: this.yylloc.last_line, + last_line: this.yylineno+1, + first_column: this.yylloc.last_column, + last_column: lines ? lines[lines.length-1].length-lines[lines.length-1].match(/\r?\n?/)[0].length : this.yylloc.last_column + match[0].length}; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[index],this.conditionStack[this.conditionStack.length-1]); + if (this.done && this._input) this.done = false; + if (token) return token; + else return; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line '+(this.yylineno+1)+'. Unrecognized text.\n'+this.showPosition(), + {text: "", token: null, line: this.yylineno}); + } + }, + lex:function lex() { + var r = this.next(); + if (typeof r !== 'undefined') { + return r; + } else { + return this.lex(); + } + }, + begin:function begin(condition) { + this.conditionStack.push(condition); + }, + popState:function popState() { + return this.conditionStack.pop(); + }, + _currentRules:function _currentRules() { + return this.conditions[this.conditionStack[this.conditionStack.length-1]].rules; + }, + topState:function () { + return this.conditionStack[this.conditionStack.length-2]; + }, + pushState:function begin(condition) { + this.begin(condition); + }}); + lexer.options = {}; + lexer.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + + + function strip(start, end) { + return yy_.yytext = yy_.yytext.substr(start, yy_.yyleng-end); + } + + + var YYSTATE=YY_START + switch($avoiding_name_collisions) { + case 0: + if(yy_.yytext.slice(-2) === "\\\\") { + strip(0,1); + this.begin("mu"); + } else if(yy_.yytext.slice(-1) === "\\") { + strip(0,1); + this.begin("emu"); + } else { + this.begin("mu"); + } + if(yy_.yytext) return 14; + + break; + case 1:return 14; + break; + case 2: + this.popState(); + return 14; + + break; + case 3:strip(0,4); this.popState(); return 15; + break; + case 4:return 35; + break; + case 5:return 36; + break; + case 6:return 25; + break; + case 7:return 16; + break; + case 8:return 20; + break; + case 9:return 19; + break; + case 10:return 19; + break; + case 11:return 23; + break; + case 12:return 22; + break; + case 13:this.popState(); this.begin('com'); + break; + case 14:strip(3,5); this.popState(); return 15; + break; + case 15:return 22; + break; + case 16:return 41; + break; + case 17:return 40; + break; + case 18:return 40; + break; + case 19:return 44; + break; + case 20:// ignore whitespace + break; + case 21:this.popState(); return 24; + break; + case 22:this.popState(); return 18; + break; + case 23:yy_.yytext = strip(1,2).replace(/\\"/g,'"'); return 32; + break; + case 24:yy_.yytext = strip(1,2).replace(/\\'/g,"'"); return 32; + break; + case 25:return 42; + break; + case 26:return 34; + break; + case 27:return 34; + break; + case 28:return 33; + break; + case 29:return 40; + break; + case 30:yy_.yytext = strip(1,2); return 40; + break; + case 31:return 'INVALID'; + break; + case 32:return 5; + break; + } + }; + lexer.rules = [/^(?:[^\x00]*?(?=(\{\{)))/,/^(?:[^\x00]+)/,/^(?:[^\x00]{2,}?(?=(\{\{|\\\{\{|\\\\\{\{|$)))/,/^(?:[\s\S]*?--\}\})/,/^(?:\()/,/^(?:\))/,/^(?:\{\{(~)?>)/,/^(?:\{\{(~)?#)/,/^(?:\{\{(~)?\/)/,/^(?:\{\{(~)?\^)/,/^(?:\{\{(~)?\s*else\b)/,/^(?:\{\{(~)?\{)/,/^(?:\{\{(~)?&)/,/^(?:\{\{!--)/,/^(?:\{\{![\s\S]*?\}\})/,/^(?:\{\{(~)?)/,/^(?:=)/,/^(?:\.\.)/,/^(?:\.(?=([=~}\s\/.)])))/,/^(?:[\/.])/,/^(?:\s+)/,/^(?:\}(~)?\}\})/,/^(?:(~)?\}\})/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:@)/,/^(?:true(?=([~}\s)])))/,/^(?:false(?=([~}\s)])))/,/^(?:-?[0-9]+(?=([~}\s)])))/,/^(?:([^\s!"#%-,\.\/;->@\[-\^`\{-~]+(?=([=~}\s\/.)]))))/,/^(?:\[[^\]]*\])/,/^(?:.)/,/^(?:$)/]; + lexer.conditions = {"mu":{"rules":[4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"com":{"rules":[3],"inclusive":false},"INITIAL":{"rules":[0,1,32],"inclusive":true}}; + return lexer;})() + parser.lexer = lexer; + function Parser () { this.yy = {}; }Parser.prototype = parser;parser.Parser = Parser; + return new Parser; + })();__exports__ = handlebars; + /* jshint ignore:end */ + return __exports__; +})(); + +// handlebars/compiler/base.js +var __module8__ = (function(__dependency1__, __dependency2__) { + "use strict"; + var __exports__ = {}; + var parser = __dependency1__; + var AST = __dependency2__; + + __exports__.parser = parser; + + function parse(input) { + // Just return if an already-compile AST was passed in. + if(input.constructor === AST.ProgramNode) { return input; } + + parser.yy = AST; + return parser.parse(input); + } + + __exports__.parse = parse; + return __exports__; +})(__module9__, __module7__); + +// handlebars/compiler/compiler.js +var __module10__ = (function(__dependency1__) { + "use strict"; + var __exports__ = {}; + var Exception = __dependency1__; + + function Compiler() {} + + __exports__.Compiler = Compiler;// the foundHelper register will disambiguate helper lookup from finding a + // function in a context. This is necessary for mustache compatibility, which + // requires that context functions in blocks are evaluated by blockHelperMissing, + // and then proceed as if the resulting value was provided to blockHelperMissing. + + Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, out = [], params, param; + + for (var i=0, l=opcodes.length; i 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + for (var alias in this.context.aliases) { + if (this.context.aliases.hasOwnProperty(alias)) { + this.source[1] = this.source[1] + ', ' + alias + '=' + this.context.aliases[alias]; + } + } + } + + if (this.source[1]) { + this.source[1] = "var " + this.source[1].substring(2) + ";"; + } + + // Merge children + if (!this.isChild) { + this.source[1] += '\n' + this.context.programs.join('\n') + '\n'; + } + + if (!this.environment.isSimple) { + this.pushSource("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return this.topStackName(); + }, + topStackName: function() { + return "stack" + this.stackSlot; + }, + flushInline: function() { + var inlineStack = this.inlineStack; + if (inlineStack.length) { + this.inlineStack = []; + for (var i = 0, len = inlineStack.length; i < len; i++) { + var entry = inlineStack[i]; + if (entry instanceof Literal) { + this.compileStack.push(entry); + } else { + this.pushStack(entry); + } + } + } + }, + isInline: function() { + return this.inlineStack.length; + }, + + popStack: function(wrapped) { + var inline = this.isInline(), + item = (inline ? this.inlineStack : this.compileStack).pop(); + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + if (!inline) { + if (!this.stackSlot) { + throw new Exception('Invalid stack pop'); + } + this.stackSlot--; + } + return item; + } + }, + + topStack: function(wrapped) { + var stack = (this.isInline() ? this.inlineStack : this.compileStack), + item = stack[stack.length - 1]; + + if (!wrapped && (item instanceof Literal)) { + return item.value; + } else { + return item; + } + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/\u2028/g, '\\u2028') // Per Ecma-262 7.3 + 7.8.4 + .replace(/\u2029/g, '\\u2029') + '"'; + }, + + setupHelper: function(paramSize, name, missingParams) { + var params = [], + paramsInit = this.setupParams(paramSize, params, missingParams); + var foundHelper = this.nameLookup('helpers', name, 'helper'); + + return { + params: params, + paramsInit: paramsInit, + name: foundHelper, + callParams: ["depth0"].concat(params).join(", "), + helperMissingParams: missingParams && ["depth0", this.quotedString(name)].concat(params).join(", ") + }; + }, + + setupOptions: function(paramSize, params) { + var options = [], contexts = [], types = [], param, inverse, program; + + options.push("hash:" + this.popStack()); + + if (this.options.stringParams) { + options.push("hashTypes:" + this.popStack()); + options.push("hashContexts:" + this.popStack()); + } + + inverse = this.popStack(); + program = this.popStack(); + + // Avoid setting fn and inverse if neither are set. This allows + // helpers to do a check for `if (options.fn)` + if (program || inverse) { + if (!program) { + this.context.aliases.self = "this"; + program = "self.noop"; + } + + if (!inverse) { + this.context.aliases.self = "this"; + inverse = "self.noop"; + } + + options.push("inverse:" + inverse); + options.push("fn:" + program); + } + + for(var i=0; i $(img).data('page')) { + var but_id = $(img).attr('id') + '-' + selection.id; + $('body').append(''); + var img_pos = $(img).offset(); + $('button#' + but_id) + .css({ + position: 'absolute', + top: img_pos.top + selection.y1 + selection.height - $('button#' + but_id).height() * 1.5, + left: img_pos.left + selection.x1 + selection.width + 5 + }) + .data('selection', selection); + } + + var coords = { + x1: selection.x1 * scale, + x2: selection.x2 * scale, + y1: selection.y1 * scale, + y2: selection.y2 * scale, + page: $(img).data('page') + }; + if(!this.noModalAfterSelect){ + this.doQuery(PDF_ID, [coords]); + } + this.toggleDownloadAllAndClearButtons(); + }, + + _onSelectCancel: function(img, selection, selectionId) { + $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selectionId).remove(); + $('#' + $(img).attr('id') + '-' + selectionId).remove(); + var but_id = $(img).attr('id') + '-' + selectionId; + $('button#' + but_id).remove(); + this.document_view.ui.control_panel_view.toggleClearAllAndRestorePredetectedTablesButtons(); + this.document_view.ui.control_panel_view.toggleDownloadAllAndClearButtons(); + }, */ + + rotate_page: function(t) { + alert('not implemented'); + }, + + delete_page: function(t) { + //var page_thumbnail = self.document_view.ui.sidebar.thumbnail_views[page_number] //TODO: make this work, delete the next line. + var page_thumbnail = $(t.target).parent().parent(); + //var page_number = this.page_number //TODO: make this work. + var page_number = page_thumbnail.data('page').split('-')[1]; + var that = this; + + if (!confirm('Delete page ' + page_number + '?')) return; + + $.post('/pdf/' + PDF_ID + '/page/' + page_number, + { _method: 'delete' }, + function () { + + // delete the deleted page's imgAreaSelect object + imgAreaSelects[page_number-1].remove(); + delete imgAreaSelects[page_number-1]; + + // move all the stuff for the following pages' imgAreaSelect objects up. + deleted_page_height = $('img.page-image#page-' + page_number).height(); + deleted_page_top = $('img.page-image#page-' + page_number).offset()["top"]; + + $('img.page-image#page-' + page_number) + .fadeOut(200, + function() { $(this).remove(); }); + page_thumbnail + .fadeOut(200, + function() { $(this).remove(); }); + + $('div.imgareaselect').each(function(){ + if( $(this).offset()["top"] > (deleted_page_top + deleted_page_height) ){ + $(this).offset({top: $(this).offset()["top"] - deleted_page_height }); + } + }); + that.pageCount -= 1; + }); + }, }); + +/* I'm not sure having a SelectionView makes sense. But, + * TODO: ssomething needs to manage the repeat lasso button + * AND, something should create the selection thumbnail (without managing it in three places) + */ +// Tabula.SelectionView = Backbone.View.extend({ // multiple per page of the PDF +// events: { +// 'click button.repeat-lassos': 'repeat_lassos', +// } +// page_view: null, //added on create. //TODO: do this. + +// repeat_lassos: function(e) { +// /* TODO: make this work. +// // var page_idx = page_view.page_number; +// // var selection_to_clone = this; +// */ +// var page_idx = parseInt($(e.currentTarget).attr('id').split('-')[1]); +// var selection_to_clone = $(e.currentTarget).data('selection'); + +// //remove the button +// $(e.currentTarget).fadeOut(500, function() { $(this).remove(); }); + +// $('#should-preview-data-checkbox').prop('checked', false); +// this.updateShouldPreviewDataAutomaticallyButton(); + +// imgAreaSelects.slice(page_idx).forEach(function(imgAreaSelectAPIObj) { +// if (imgAreaSelectAPIObj === false) return; +// imgAreaSelectAPIObj.cancelSelections(); +// imgAreaSelectAPIObj.createNewSelection(selection_to_clone.x1, selection_to_clone.y1, +// selection_to_clone.x2, selection_to_clone.y2); +// imgAreaSelectAPIObj.setOptions({show: true}); +// imgAreaSelectAPIObj.update(); +// this.showSelectionThumbnail(imgAreaSelectAPIObj.getImg(), +// selection_to_clone); +// }, this); +// }, +// }); + Tabula.ControlPanelView = Backbone.View.extend({ // only one - // autopreview data button - // clear all selections button - // download all button + events: { + 'click #should-preview-data-checkbox' : 'updateShouldPreviewDataAutomaticallyButton', + 'click #clear-all-selections': 'clear_all_selection', + 'click #restore-detected-tables': 'restore_detected_tables', + 'click #all-data': 'query_all_data', + 'click #repeat-lassos': 'repeat_lassos', + }, + ui: null, //added on create + className: 'followyouaroundbar', + + template: Handlebars.compile($('#templates #control-panel-template').html()), + + noModalAfterSelect: !$('#should-preview-data-checkbox').is(':checked'), + + updateShouldPreviewDataAutomaticallyButton: function(){ + this.noModalAfterSelect = !$('#should-preview-data-checkbox').is(':checked'); + }, + + repeat_lassos: function(){ + alert('not yet implemented'); + return; + /* TODO: + * get ui, get document_view, get first page_view: + * either: + * - repeat first selection + * - repeat all selections + */ + }, + + clear_all_selection: function(){ + _(imgAreaSelects).each(function(imgAreaSelectAPIObj){ + if (imgAreaSelectAPIObj === false) return; + imgAreaSelectAPIObj.cancelSelections(); + }); + }, + + restore_detected_tables: function(){ + for(var imageIndex=0; imageIndex < imgAreaSelects.length; imageIndex++){ + var pageIndex = imageIndex + 1; + this.drawDetectedTables( $('img#page-' + pageIndex), tableGuesses ); + } + this.toggleClearAllAndRestorePredetectedTablesButtons(); + }, + + toggleClearAllAndRestorePredetectedTablesButtons: function(){ + // if tables weren't autodetected, don't tease the user with an autodetect button that won't work. + var numOfSelectionsOnPage = this.ui.total_selections(); + if(!_(tableGuesses).isEmpty()){ + if(numOfSelectionsOnPage <= 0){ + $("#clear-all-selections").hide(); + $("#restore-detected-tables").show(); + }else{ + $("#clear-all-selections").show(); + $("#restore-detected-tables").hide(); + } + } + }, + + toggleDownloadAllAndClearButtons: function() { + if (this.ui.total_selections() > 0) { + $('#all-data, #clear-all-selections').removeAttr('disabled'); + } + else { + $('#all-data, #clear-all-selections').attr('disabled', 'disabled'); + } + }, + + initialize: function(){ + _.bindAll(this, 'updateShouldPreviewDataAutomaticallyButton', 'query_all_data', 'render'); + }, + + + /*TODO: get the parent, get the document_view, get all of its children, get all of their imgAreaSelects + * then loop over all of them. + */ + query_all_data : function(){ + all_coords = []; + imgAreaSelects.forEach(function(imgAreaSelectAPIObj){ + if (imgAreaSelectAPIObj === false) return; + + var thumb_width = imgAreaSelectAPIObj.getImg().width(); + var thumb_height = imgAreaSelectAPIObj.getImg().height(); + + var pdf_width = parseInt(imgAreaSelectAPIObj.getImg().data('original-width')); + var pdf_height = parseInt(imgAreaSelectAPIObj.getImg().data('original-height')); + var pdf_rotation = parseInt(imgAreaSelectAPIObj.getImg().data('rotation')); + + var scale = (Math.abs(pdf_rotation) == 90 ? pdf_height : pdf_width) / thumb_width; + + imgAreaSelectAPIObj.getSelections().forEach(function(selection){ + new_coord = { + x1: selection.x1 * scale, + x2: selection.x2 * scale, + y1: selection.y1 * scale, + y2: selection.y2 * scale, + page: imgAreaSelectAPIObj.getImg().data('page') + } + all_coords.push(new_coord); + }); + }); + this.doQuery(PDF_ID, all_coords); + }, + render: function(){ //make the "follow you around bar" actually follow you around. ("sticky nav") $('.followyouaroundbar').affix({top: 70 }); - } + + this.$el.html(this.template({ + })); + + + return this; + }, }); Tabula.SidebarView = Backbone.View.extend({ // only one - // sidebar scroll + tagName: 'ul', + className: 'thumbnail-list', +}); + +Tabula.ThumbnailView = Backbone.View.extend({ // one per page + 'events': { + //TODO: on load, create an empty div with class 'selection-show' to be the selection thumbnail. + 'load .thumbnail-list li img': function() { $(this).after($('
', { class: 'selection-show'})); }, + }, + tagName: 'li', + className: "thumbnail pdf-page", + id: function(){ + return 'thumb-page-' + this.model.get('number'); + }, + + // initialize: function(){ + // this.$img = this.$el.children('img'); + // this.img = this.$img[0]; + // }, + template: Handlebars.compile($('#templates #thumbnail-template').html()), + + initialize: function(){ + _.bindAll(this, 'render'); + }, + + render: function(){ + if(this.model.get('deleted')){ + return; + } + //TODO: use a real templating language. + this.$el.attr('data-page', this.model.get('number')) + .attr('data-original-width', this.model.get('width')) + .attr('data-original-height', this.model.get('height')) + .attr('data-rotation', this.model.get('rotation')); + this.$el.html(this.template({ + 'number': this.model.get('number'), + 'image_url': this.model.get('image_url') + })); + + if(this.model.number == 1){ + this.$el.find('img').attr('data-position', "right") + .attr('data-intro', "Click a thumbnail to skip directly to that page."); + } + + return this; + }, + + showSelectionThumbnail: function(selection) { + $el.append( $('
').css('display', 'block') ); + var sshow = $('#thumb-' + $img.attr('id') + ' #selection-show-' + selection.id); + var thumbScale = $('#thumb-' + $img.attr('id') + ' img').width() / $img.width(); + $(sshow).css('top', selection.y1 * thumbScale + 'px') + .css('left', selection.x1 * thumbScale + 'px') + .css('width', ((selection.x2 - selection.x1) * thumbScale) + 'px') + .css('height', ((selection.y2 - selection.y1) * thumbScale) + 'px'); + }, }) Tabula.UI = Backbone.View.extend({ - el : 'body', + el : '#tabula-app', + events : { - 'click button.close#directions' : 'moveSelectionsUp', - 'click a.tooltip-modal': 'tooltip', //$('a.tooltip-modal').tooltip(); + 'click a.tooltip-modal': 'tooltip', 'hide #data-modal' : function(){ clip.unglue('#copy-csv-to-clipboard'); }, - 'load .thumbnail-list li img': function() { $(this).after($('
', { class: 'selection-show'})); }, - 'click i.delete-page': 'deletePage', - 'click i.rotate-left i.rotate-right': 'rotatePage', - 'click button.repeat-lassos': 'repeat_lassos', - 'click a#help-start': function(){ Tabula.tour.ended ? Tabula.tour.restart(true) : Tabula.tour.start(true); }, - - //events for buttons on the follow-you-around bar. - 'click #should-preview-data-checkbox' : 'updateShouldPreviewDataAutomaticallyButton', - 'click #clear-all-selections': 'clear_all_selection', - 'click #restore-detected-tables': 'restore_detected_tables', - 'click #repeat-lassos': 'repeat_lassos', - 'click #all-data': 'query_all_data', - 'click .extraction-method-btn:not(.active)': 'queryWithToggledExtractionMethod', - 'click .toggle-advanced-options': 'toggleAdvancedOptionsShown', - 'click .download-dropdown': 'dropDownOrUp' }, - extractionMethod: "guess", $loading: $('#loading'), - PDF_ID: window.location.pathname.split('/')[2], colors: ['#f00', '#0f0', '#00f', '#ffff00', '#FF00FF'], - noModalAfterSelect: !$('#should-preview-data-checkbox').is(':checked'), lastQuery: [{}], lastSelection: undefined, pageCount: undefined, components: {}, + model: Document, + initialize: function(){ - _.bindAll(this, 'render', 'createImgareaselects', 'getTablesJson', 'total_selections', - 'toggleClearAllAndRestorePredetectedTablesButtons', 'updateShouldPreviewDataAutomaticallyButton', - 'query_all_data', 'redoQuery', 'toggleAdvancedOptionsShown'); - this.pageCount = $('img.page-image').length; - this.setAdvancedOptionsShown(); - - this.components['pdf_view'] = new Tabula.PDFView(); - this.components['control_panel'] = new Tabula.ControlPanelView(); - this.components['sidebar'] = new Tabula.SidebarView(); - - this.render(); - this.updateExtractionMethodButton(); + _.bindAll(this, 'render', 'redoQuery'); + + this.pdf_document = new Document({ + pdf_id: PDF_ID, + }); + + this.listenTo(this.pdf_document.page_collection, 'all', this.render); + this.listenTo(this.pdf_document.page_collection, 'add', this.addOne); + this.listenTo(this.pdf_document.page_collection, 'reset', this.addAll); + + + this.components['document_view'] = new Tabula.DocumentView({ui: this}); //creates page_views + this.components['control_panel'] = new Tabula.ControlPanelView({ui: this}); + this.components['sidebar_view'] = new Tabula.SidebarView({ui: this}); //TODO: create thumbnail_views + + this.pdf_document.page_collection.fetch(); + }, + + addOne: function(page) { + var page_view = new Tabula.PageView({model: page}); + var thumbnail_view = new Tabula.ThumbnailView({model: page}) + this.components['document_view'].$el.append(page_view.render().el); // is this a good idea? TODO: + this.components['sidebar_view'].$el.append(thumbnail_view.render().el); // is this a good idea? TODO: + }, + + addAll: function() { + Pages.each(this.addOne, this); + }, + + total_selections: function(){ + return _.reduce(imgAreaSelects, function(memo, s){ + if(s){ + return memo + s.getSelections().length; + }else{ + return memo; + } + }, 0); }, render : function(){ + $('#main-container').append(this.components['document_view'].render().el); + $('#control-panel-container').append(this.components['control_panel'].render().el); + $('.sidebar-nav.well').append(this.components['sidebar_view'].render().el); + query_parameters = {}; - this.getTablesJson(); + + this.pageCount = this.pdf_document.page_collection.size(); + + /* TODO: do this stuff somewhere + * this.setAdvancedOptionsShown(); + * this.updateExtractionMethodButton(); + */ // render out the components, as necessary return this; @@ -152,60 +710,11 @@ Tabula.UI = Backbone.View.extend({ parent: 'body' } ]); - } - - - queryWithToggledExtractionMethod: function(e){ - // console.log("before", this.extractionMethod); - this.extractionMethod = this.getOppositeExtractionMethod(); - // console.log("after", this.extractionMethod); - this.updateExtractionMethodButton(); - - this.redoQuery(); - }, - - - rotatePage: function(t) { - alert('not implemented'); - }, - - deletePage: function(t) { - var page_thumbnail = $(t.target).parent().parent(); - var page_number = page_thumbnail.data('page').split('-')[1]; - var that = this; - if (!confirm('Delete page ' + page_number + '?')) return; - $.post('/pdf/' + this.PDF_ID + '/page/' + page_number, - { _method: 'delete' }, - function () { - - // delete the deleted page's imgAreaSelect object - imgAreaSelects[page_number-1].remove(); - delete imgAreaSelects[page_number-1]; - - // move all the stuff for the following pages' imgAreaSelect objects up. - deleted_page_height = $('img.page-image#page-' + page_number).height(); - deleted_page_top = $('img.page-image#page-' + page_number).offset()["top"]; - - $('img.page-image#page-' + page_number) - .fadeOut(200, - function() { $(this).remove(); }); - page_thumbnail - .fadeOut(200, - function() { $(this).remove(); }); - - $('div.imgareaselect').each(function(){ - if( $(this).offset()["top"] > (deleted_page_top + deleted_page_height) ){ - $(this).offset({top: $(this).offset()["top"] - deleted_page_height }); - } - }); - that.pageCount -= 1; - }); - }, redoQuery: function(options) { //TODO: stash lastCoords, rather than stashing lastQuery and then parsing it. - this.doQuery(this.PDF_ID, + this.doQuery(PDF_ID, JSON.parse(this.lastQuery["coords"]), options); }, @@ -235,7 +744,7 @@ Tabula.UI = Backbone.View.extend({ show_intersections: show_intersections == true }); - $.get('/debug/' + this.PDF_ID + '/rulings', + $.get('/debug/' + PDF_ID + '/rulings', lq, _.bind(function(data) { $.each(data.rulings, _.bind(function(i, ruling) { @@ -259,7 +768,7 @@ Tabula.UI = Backbone.View.extend({ }, _debugRectangularShapes: function(image, url) { - image = $(image); + image = $(image); var imagePos = image.offset(); var newCanvas = $('',{'class':'debug-canvas'}) .attr('width', image.width()) @@ -294,11 +803,11 @@ Tabula.UI = Backbone.View.extend({ }, debugCharacters: function(image) { - return this._debugRectangularShapes(image, '/debug/' + this.PDF_ID + '/characters'); + return this._debugRectangularShapes(image, '/debug/' + PDF_ID + '/characters'); }, debugClippingPaths: function(image) { - return this._debugRectangularShapes(image, '/debug/' + this.PDF_ID + '/clipping_paths'); + return this._debugRectangularShapes(image, '/debug/' + PDF_ID + '/clipping_paths'); }, debugColumns: function(image) { @@ -343,103 +852,7 @@ Tabula.UI = Backbone.View.extend({ }, debugTextChunks: function(image) { - return this._debugRectangularShapes(image, '/debug/' + this.PDF_ID + '/text_chunks'); - }, - - /* functions for the follow-you-around bar */ - total_selections: function(){ - return _.reduce(imgAreaSelects, function(memo, s){ - if(s){ - return memo + s.getSelections().length; - }else{ - return memo; - } - }, 0); - }, - toggleClearAllAndRestorePredetectedTablesButtons: function(numOfSelectionsOnPage){ - // if tables weren't autodetected, don't tease the user with an autodetect button that won't work. - if(!_(tableGuesses).isEmpty()){ - if(numOfSelectionsOnPage <= 0){ - $("#clear-all-selections").hide(); - $("#restore-detected-tables").show(); - }else{ - $("#clear-all-selections").show(); - $("#restore-detected-tables").hide(); - } - } - }, - clear_all_selection: function(){ - _(imgAreaSelects).each(function(imgAreaSelectAPIObj){ - if (imgAreaSelectAPIObj === false) return; - imgAreaSelectAPIObj.cancelSelections(); - }); - }, - - restore_detected_tables: function(){ - for(var imageIndex=0; imageIndex < imgAreaSelects.length; imageIndex++){ - var pageIndex = imageIndex + 1; - this.drawDetectedTables( $('img#page-' + pageIndex), tableGuesses ); - } - this.toggleClearAllAndRestorePredetectedTablesButtons(this.total_selections()); - }, - - toggleDownloadAllAndClearButtons: function() { - if (this.total_selections() > 0) { - $('#all-data, #clear-all-selections').removeAttr('disabled'); - } - else { - $('#all-data, #clear-all-selections').attr('disabled', 'disabled'); - } - }, - - repeat_lassos: function(e) { - var page_idx = parseInt($(e.currentTarget).attr('id').split('-')[1]); - var selection_to_clone = $(e.currentTarget).data('selection'); - - $(e.currentTarget).fadeOut(500, function() { $(this).remove(); }); - - $('#should-preview-data-checkbox').prop('checked', false); - this.updateShouldPreviewDataAutomaticallyButton(); - - imgAreaSelects.slice(page_idx).forEach(function(imgAreaSelectAPIObj) { - if (imgAreaSelectAPIObj === false) return; - imgAreaSelectAPIObj.cancelSelections(); - imgAreaSelectAPIObj.createNewSelection(selection_to_clone.x1, selection_to_clone.y1, - selection_to_clone.x2, selection_to_clone.y2); - imgAreaSelectAPIObj.setOptions({show: true}); - imgAreaSelectAPIObj.update(); - this.showSelectionThumbnail(imgAreaSelectAPIObj.getImg(), - selection_to_clone); - }, this); - }, - - query_all_data : function(){ - all_coords = []; - imgAreaSelects.forEach(function(imgAreaSelectAPIObj){ - - if (imgAreaSelectAPIObj === false) return; - - var thumb_width = imgAreaSelectAPIObj.getImg().width(); - var thumb_height = imgAreaSelectAPIObj.getImg().height(); - - var pdf_width = parseInt(imgAreaSelectAPIObj.getImg().data('original-width')); - var pdf_height = parseInt(imgAreaSelectAPIObj.getImg().data('original-height')); - var pdf_rotation = parseInt(imgAreaSelectAPIObj.getImg().data('rotation')); - - var scale = (Math.abs(pdf_rotation) == 90 ? pdf_height : pdf_width) / thumb_width; - - imgAreaSelectAPIObj.getSelections().forEach(function(selection){ - new_coord = { - x1: selection.x1 * scale, - x2: selection.x2 * scale, - y1: selection.y1 * scale, - y2: selection.y2 * scale, - page: imgAreaSelectAPIObj.getImg().data('page') - } - all_coords.push(new_coord); - }); - }); - this.doQuery(this.PDF_ID, all_coords); + return this._debugRectangularShapes(image, '/debug/' + PDF_ID + '/text_chunks'); }, doQuery: function(pdf_id, coords, options) { @@ -517,17 +930,9 @@ Tabula.UI = Backbone.View.extend({ }); }, - showSelectionThumbnail: function(img, selection) { - $('#thumb-' + img.attr('id') + " a").append( $('
').css('display', 'block') ); - var sshow = $('#thumb-' + img.attr('id') + ' #selection-show-' + selection.id); - var thumbScale = $('#thumb-' + img.attr('id') + ' img').width() / img.width(); - $(sshow).css('top', selection.y1 * thumbScale + 'px') - .css('left', selection.x1 * thumbScale + 'px') - .css('width', ((selection.x2 - selection.x1) * thumbScale) + 'px') - .css('height', ((selection.y2 - selection.y1) * thumbScale) + 'px'); - }, - drawDetectedTables: function($img, tableGuesses){ + alert("not yet reimplemented"); return; //TODO: + //$img = $(e); var imageIndex = $img.data('page'); arrayIndex = imageIndex - 1; @@ -566,202 +971,24 @@ Tabula.UI = Backbone.View.extend({ imgAreaSelectAPIObj.update(); }, - /* pdfs//tables.json may or may not exist, depending on whether the user chooses to use table autodetection. */ - getTablesJson : function(){ - $.getJSON("/pdfs/" + this.PDF_ID + "/pages.json?_=" + Math.round(+new Date()).toString(), - _.bind(function(pages){ - $.getJSON("/pdfs/" + this.PDF_ID + "/tables.json", - _.bind(function(tableGuesses){ - this.createImgareaselects(tableGuesses, pages) - }, this)). - error( _.bind(function(){ this.createImgareaselects([], pages) }, this)); - }, this) ). - error( _.bind(function(){ this.createImgareaselects([], []) }, this)); - }, - - _onSelectStart: function(img, selection) { - this.showSelectionThumbnail($(img), selection); - }, - - _onSelectChange: function(img, selection) { - var sshow = $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selection.id); - var scale = $('#thumb-' + $(img).attr('id') + ' img').width() / $(img).width(); - $(sshow).css('top', selection.y1 * scale + 'px') - .css('left', selection.x1 * scale + 'px') - .css('width', ((selection.x2 - selection.x1) * scale) + 'px') - .css('height', ((selection.y2 - selection.y1) * scale) + 'px'); - - var b; - var but_id = $(img).attr('id') + '-' + selection.id; - if (b = $('button#' + but_id)) { - var img_pos = $(img).offset(); - $(b) - .css({ - top: img_pos.top + selection.y1 + selection.height - $('button#' + but_id).height() * 1.5, - left: img_pos.left + selection.x1 + selection.width + 5 - }) - .data('selection', selection); - } - }, - - _onSelectEnd: function(img, selection) { - if (selection.width == 0 && selection.height == 0) { - $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selection.id).css('display', 'none'); - } - if (selection.height * selection.width < 5000) return; - this.lastSelection = selection; - var thumb_width = $(img).width(); - var thumb_height = $(img).height(); - - var pdf_width = parseInt($(img).data('original-width')); - var pdf_height = parseInt($(img).data('original-height')); - var pdf_rotation = parseInt($(img).data('rotation')); - - var scale = (Math.abs(pdf_rotation) == 90 ? pdf_height : pdf_width) / thumb_width; - - // create button for repeating lassos, only if there are more pages after this - if (this.pageCount > $(img).data('page')) { - var but_id = $(img).attr('id') + '-' + selection.id; - $('body').append(''); - var img_pos = $(img).offset(); - $('button#' + but_id) - .css({ - position: 'absolute', - top: img_pos.top + selection.y1 + selection.height - $('button#' + but_id).height() * 1.5, - left: img_pos.left + selection.x1 + selection.width + 5 - }) - .data('selection', selection); - } - - var coords = { - x1: selection.x1 * scale, - x2: selection.x2 * scale, - y1: selection.y1 * scale, - y2: selection.y2 * scale, - page: $(img).data('page') - }; - if(!this.noModalAfterSelect){ - this.doQuery(this.PDF_ID, [coords]); - } - this.toggleDownloadAllAndClearButtons(); - }, - - _onSelectCancel: function(img, selection, selectionId) { - $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selectionId).remove(); - $('#' + $(img).attr('id') + '-' + selectionId).remove(); - var but_id = $(img).attr('id') + '-' + selectionId; - $('button#' + but_id).remove(); - this.toggleClearAllAndRestorePredetectedTablesButtons(this.total_selections()); - //TODO, if there are no selections, activate the restore detected tables button. - this.toggleDownloadAllAndClearButtons(); - - }, - - //skip if pages is "deleted" - createImgareaselects : function(tableGuessesTmp, pages){ - tableGuesses = tableGuessesTmp; - var selectsNotYetLoaded = _(pages).filter(function(page){ return !page['deleted']}).length; - var that = this; - - imgAreaSelects = $.map(pages, _.bind(function(page, arrayIndex){ - pageIndex = arrayIndex + 1; - if (page['deleted']) { - return false; - } - $image = $('img#page-' + pageIndex); - return $image.imgAreaSelect({ - handles: true, - instance: true, - allowOverlaps: false, - show: true, - multipleSelections: true, - - onSelectStart: _.bind(that._onSelectStart, that), - onSelectChange: that._onSelectChange, - onSelectEnd: _.bind(that._onSelectEnd, that), - onSelectCancel: _.bind(that._onSelectCancel, that), - onInit: _.bind(drawDetectedTablesIfAllAreLoaded, this) - }); - }, this)); - - function drawDetectedTablesIfAllAreLoaded(){ - selectsNotYetLoaded--; - if(selectsNotYetLoaded == 0){ - for(var imageIndex=0; imageIndex < imgAreaSelects.length; imageIndex++){ - var pageIndex = imageIndex + 1; - if(imgAreaSelects[imageIndex]){ //not undefined - this.drawDetectedTables( $('img#page-' + pageIndex), tableGuesses ); - } - } - } - } - }, - - /* simple display-related functions */ - - toggleAdvancedOptionsShown: function(){ - var $advancedOptions = $('#advanced-options'); - currentAdvancedOptions = $advancedOptions.is(":visible"); - if(currentAdvancedOptions){ - // currently shown, so hide it - localStorage.setItem("tabula-show-advanced-options", "false"); - }else{ - // currently hidden, so show it - localStorage.setItem("tabula-show-advanced-options", "true"); - } - this.setAdvancedOptionsShown(); - }, - - setAdvancedOptionsShown: function(){ - var showAdvancedOptions = localStorage.getItem("tabula-show-advanced-options"); - var $advancedOptions = $('#advanced-options'); - var $advancedShowButton = $('#basic-options .toggle-advanced-options'); - if(showAdvancedOptions === "true"){ - $advancedOptions.slideDown(); - $advancedShowButton.hide(); - }else{ - $advancedOptions.slideUp(); - $advancedShowButton.show(); - } - }, - - getOppositeExtractionMethod: function(){ - if (this.extractionMethod == "guess"){ - return; // this should never happen. - } - else if (this.extractionMethod == "original") { - return "spreadsheet"; - } - return "original"; - }, - - updateExtractionMethodButton: function(){ - $('#' + this.extractionMethod + '-method-btn').button('toggle'); - }, - - updateShouldPreviewDataAutomaticallyButton: function(){ - this.noModalAfterSelect = !$('#should-preview-data-checkbox').is(':checked'); - }, - - moveSelectionsUp: function(){ - $('div.imgareaselect').each(function(){ $(this).offset({top: $(this).offset()["top"] - $(directionsRow).height() }); }); - }, - - dropDownOrUp: function(e){ - var $el = $(e.currentTarget); - $ul = $el.parent().find('ul'); +}); - window.setTimeout(function(){ // if we upgrade to bootstrap 3.0 - // we don't need this gross timeout and can, instead, - // listen for the `dropdown's shown.bs.dropdown` event - if(!isElementInViewport($ul)){ - $el.addClass('dropup'); - $ul.addClass('bottom-up'); - } - }, 100); - } +// old fetch code +// /* pdfs//tables.json may or may not exist, depending on whether the user chooses to use table autodetection. */ +// getTablesJson : function(){ +// $.getJSON("/pdfs/" + PDF_ID + "/pages.json?_=" + Math.round(+new Date()).toString(), +// _.bind(function(pages){ +// $.getJSON("/pdfs/" + PDF_ID + "/tables.json", +// _.bind(function(tableGuesses){ +// this.render(); +// this.components['document_view'].createImgareaselects(tableGuesses, pages); +// //TODO: draw selections on thumbnails (also on lines below, in error callbacks) +// }, this)). +// error( _.bind(function(){ this.components['document_view'].createImgareaselects([], pages) }, this)); +// }, this) ). +// error( _.bind(function(){ this.components['document_view'].createImgareaselects([], []) }, this)); +// }, -}); $(function () { Tabula.ui = new Tabula.UI(); diff --git a/webapp/static/js/templates/fixie-sidebar.jst b/webapp/static/js/templates/fixie-sidebar.jst new file mode 100644 index 00000000..9dc04711 --- /dev/null +++ b/webapp/static/js/templates/fixie-sidebar.jst @@ -0,0 +1,8 @@ + + + + +
+
+ Help +
diff --git a/webapp/static/js/templates/help_bar.jst b/webapp/static/js/templates/help_bar.jst new file mode 100644 index 00000000..9901e9be --- /dev/null +++ b/webapp/static/js/templates/help_bar.jst @@ -0,0 +1,7 @@ +
+
+ +

How to use: make a rectangular selection over a table on the PDF pages. That's it!

+

Hint: table headers are (still) problematic. Try to exclude it from your selection.

+
+
diff --git a/webapp/static/js/templates/page.jst b/webapp/static/js/templates/page.jst new file mode 100644 index 00000000..0ebadf8c --- /dev/null +++ b/webapp/static/js/templates/page.jst @@ -0,0 +1,13 @@ +
+ <% pages.each_with_index do |p, i| %> + <% next if p['deleted'] %> +
+ + data-position="right" data-intro="Click and drag to select each table in your document. Once you've selected it, a window to preview your data will appear, along with options to download it as a spreadsheet." + <% end %> + > +
+
+ <% end %> +
diff --git a/webapp/static/js/templates/thumbnail-sidebar.jst b/webapp/static/js/templates/thumbnail-sidebar.jst new file mode 100644 index 00000000..32f7bc5f --- /dev/null +++ b/webapp/static/js/templates/thumbnail-sidebar.jst @@ -0,0 +1,14 @@ +
    + <% pages.each_with_index do |p, i| %> + <% next if p['deleted'] %> +
  • data-position="right" data-intro="Click a thumbnail to skip directly to that page." <% end %>> + + + + + +
    Page <%= i + 1 %> +
  • + <% end %> +
diff --git a/webapp/views/layout.erb b/webapp/views/layout.erb index c9731c19..21e277ef 100644 --- a/webapp/views/layout.erb +++ b/webapp/views/layout.erb @@ -41,6 +41,7 @@ + diff --git a/webapp/views/pdf_view.html.erb b/webapp/views/pdf_view.html.erb index f80b4552..58a95268 100644 --- a/webapp/views/pdf_view.html.erb +++ b/webapp/views/pdf_view.html.erb @@ -1,57 +1,19 @@ -
+
-
-
-
- -

How to use: make a rectangular selection over a table on the PDF pages. That's it!

-

Hint: table headers are (still) problematic. Try to exclude it from your selection.

-
-
-
- <% pages.each_with_index do |p, i| %> - <% next if p['deleted'] %> -
- - data-position="right" data-intro="Click and drag to select each table in your document. Once you've selected it, a window to preview your data will appear, along with options to download it as a spreadsheet." - <% end %> - > -
-
- <% end %> -
+
+ + +
+ +
+
-
-
- - - - -
-
- Help -
+
@@ -122,6 +84,38 @@
+ + + @@ -148,3 +142,5 @@ }; new Spinner(SPINNER_OPTS).spin($('#spinner').get(0)); + + From d46c72d8c99f1236679ed581974f51288b68560a Mon Sep 17 00:00:00 2001 From: "Jeremy B. Merrill" Date: Thu, 7 Aug 2014 09:35:38 -0400 Subject: [PATCH 03/25] modal *mostly* works, buttons work --- webapp/static/css/tabula_web.css | 1 + webapp/static/js/ZeroClipboard.js | 303 ------ webapp/static/js/ZeroClipboard.min.js | 10 + webapp/static/js/jquery.imgareaselect.js | 5 +- webapp/static/js/pdf_view.js | 873 +++++++++++------- webapp/static/js/templates/fixie-sidebar.jst | 8 - webapp/static/js/templates/page.jst | 13 - .../static/js/templates/thumbnail-sidebar.jst | 14 - webapp/static/swf/ZeroClipboard.swf | Bin 1536 -> 4036 bytes webapp/tabula_web.rb | 39 +- webapp/views/pdf_view.html.erb | 116 +-- 11 files changed, 611 insertions(+), 771 deletions(-) delete mode 100644 webapp/static/js/ZeroClipboard.js create mode 100644 webapp/static/js/ZeroClipboard.min.js delete mode 100644 webapp/static/js/templates/fixie-sidebar.jst delete mode 100644 webapp/static/js/templates/page.jst delete mode 100644 webapp/static/js/templates/thumbnail-sidebar.jst diff --git a/webapp/static/css/tabula_web.css b/webapp/static/css/tabula_web.css index f1f024ff..392a0851 100644 --- a/webapp/static/css/tabula_web.css +++ b/webapp/static/css/tabula_web.css @@ -101,6 +101,7 @@ .toggle-advanced-options{ cursor: select; + color: #666; } #data-modal .modal-body { diff --git a/webapp/static/js/ZeroClipboard.js b/webapp/static/js/ZeroClipboard.js deleted file mode 100644 index 4968be9d..00000000 --- a/webapp/static/js/ZeroClipboard.js +++ /dev/null @@ -1,303 +0,0 @@ -/*! - * zeroclipboard - * The Zero Clipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie, and a JavaScript interface. - * Copyright 2012 Jon Rohan, James M. Greene, . - * Released under the MIT license - * http://jonrohan.github.com/ZeroClipboard/ - * v1.1.0 - */(function() { - "use strict"; - var ZeroClipboard = {}; - ZeroClipboard.Client = function(query) { - if (ZeroClipboard._client) return ZeroClipboard._client; - this.handlers = {}; - if (ZeroClipboard.detectFlashSupport()) this.bridge(); - if (query) this.glue(query); - ZeroClipboard._client = this; - }; - function _elementMouseOver() { - ZeroClipboard._client.setCurrent(this); - } - ZeroClipboard.Client.prototype.glue = function(query) { - function _addEventHandler(element, method, func) { - if (element.addEventListener) { - element.addEventListener(method, func, false); - } else if (element.attachEvent) { - element.attachEvent(method, func); - } - } - var elements = ZeroClipboard.$(query); - for (var i = 0; i < elements.length; i++) { - _addEventHandler(elements[i], "mouseover", _elementMouseOver); - } - }; - ZeroClipboard.Client.prototype.unglue = function(query) { - function _removeEventHandler(element, method, func) { - if (element.removeEventListener) { - element.removeEventListener(method, func, false); - } else if (element.detachEvent) { - element.detachEvent(method, func); - } - } - var elements = ZeroClipboard.$(query); - for (var i = 0; i < elements.length; i++) { - _removeEventHandler(elements[i], "mouseover", _elementMouseOver); - } - }; - ZeroClipboard.Client.prototype.bridge = function() { - this.htmlBridge = ZeroClipboard.$("#global-zeroclipboard-html-bridge"); - if (this.htmlBridge.length) { - this.htmlBridge = this.htmlBridge[0]; - this.flashBridge = document["global-zeroclipboard-flash-bridge"]; - return; - } - function noCache(path) { - return (path.indexOf("?") >= 0 ? "&" : "?") + "nocache=" + (new Date).getTime(); - } - var html = ' '; - this.htmlBridge = document.createElement("div"); - this.htmlBridge.id = "global-zeroclipboard-html-bridge"; - this.htmlBridge.setAttribute("class", "global-zeroclipboard-container"); - this.htmlBridge.setAttribute("data-clipboard-ready", false); - this.htmlBridge.style.position = "absolute"; - this.htmlBridge.style.left = "-9999px"; - this.htmlBridge.style.top = "-9999px"; - this.htmlBridge.style.width = "15px"; - this.htmlBridge.style.height = "15px"; - this.htmlBridge.style.zIndex = "9999"; - this.htmlBridge.innerHTML = html; - document.body.appendChild(this.htmlBridge); - this.flashBridge = document["global-zeroclipboard-flash-bridge"]; - }; - ZeroClipboard.Client.prototype.resetBridge = function() { - this.htmlBridge.style.left = "-9999px"; - this.htmlBridge.style.top = "-9999px"; - this.htmlBridge.removeAttribute("title"); - this.htmlBridge.removeAttribute("data-clipboard-text"); - ZeroClipboard.currentElement.removeClass("zeroclipboard-is-active"); - delete ZeroClipboard.currentElement; - }; - ZeroClipboard.Client.prototype.ready = function() { - return !!this.htmlBridge.getAttribute("data-clipboard-ready"); - }; - function _getCursor(el) { - var y = el.style.cursor; - if (el.currentStyle) y = el.currentStyle.cursor; else if (window.getComputedStyle) y = document.defaultView.getComputedStyle(el, null).getPropertyValue("cursor"); - if (y == "auto") { - var possiblePointers = [ "a" ]; - for (var i = 0; i < possiblePointers.length; i++) { - if (el.tagName.toLowerCase() == possiblePointers[i]) { - return "pointer"; - } - } - } - return y; - } - ZeroClipboard.Client.prototype.setCurrent = function(element) { - ZeroClipboard.currentElement = element; - this.reposition(); - if (element.getAttribute("data-clipboard-text")) { - this.setText(element.getAttribute("data-clipboard-text")); - } - if (element.getAttribute("title")) { - this.setTitle(element.getAttribute("title")); - } - if (_getCursor(element) == "pointer") { - this.setHandCursor(true); - } else { - this.setHandCursor(false); - } - }; - ZeroClipboard.Client.prototype.reposition = function() { - var pos = $(ZeroClipboard.currentElement).offset(); - pos.height = $(ZeroClipboard.currentElement).outerHeight(); - pos.width = $(ZeroClipboard.currentElement).outerWidth(); - this.htmlBridge.style.top = pos.top + "px"; - this.htmlBridge.style.left = pos.left + "px"; - this.htmlBridge.style.width = pos.width + "px"; - this.htmlBridge.style.height = pos.height + "px"; - this.htmlBridge.style.zIndex = 9999; - this.setSize(pos.width, pos.height); - }; - ZeroClipboard.Client.prototype.setText = function(newText) { - if (newText && newText !== "") { - this.htmlBridge.setAttribute("data-clipboard-text", newText); - if (this.ready()) this.flashBridge.setText(newText); - } - }; - ZeroClipboard.Client.prototype.setTitle = function(newTitle) { - if (newTitle && newTitle !== "") this.htmlBridge.setAttribute("title", newTitle); - }; - ZeroClipboard.Client.prototype.setSize = function(width, height) { - if (this.ready()) this.flashBridge.setSize(width, height); - }; - ZeroClipboard.Client.prototype.setHandCursor = function(enabled) { - if (this.ready()) this.flashBridge.setHandCursor(enabled); - }; - ZeroClipboard.version = "1.1.0"; - ZeroClipboard.moviePath = "ZeroClipboard.swf"; - ZeroClipboard._client = null; - ZeroClipboard.setMoviePath = function(path) { - this.moviePath = path; - }; - ZeroClipboard.destroy = function() { - var query = ZeroClipboard.$("#global-zeroclipboard-html-bridge"); - if (!query.length) return; - delete ZeroClipboard._client; - var bridge = query[0]; - bridge.parentNode.removeChild(bridge); - }; - ZeroClipboard.detectFlashSupport = function() { - var hasFlash = false; - try { - if (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")) { - hasFlash = true; - } - } catch (error) { - if (navigator.mimeTypes["application/x-shockwave-flash"]) { - hasFlash = true; - } - } - return hasFlash; - }; - ZeroClipboard.dispatch = function(eventName, args) { - ZeroClipboard._client.receiveEvent(eventName, args); - }; - ZeroClipboard.Client.prototype.on = function(eventName, func) { - var events = eventName.toString().split(/\s/g); - for (var i = 0; i < events.length; i++) { - eventName = events[i].toLowerCase().replace(/^on/, ""); - if (!this.handlers[eventName]) this.handlers[eventName] = []; - this.handlers[eventName].push(func); - } - if (this.handlers.noflash && !ZeroClipboard.detectFlashSupport()) { - this.receiveEvent("onNoFlash", null); - } - }; - ZeroClipboard.Client.prototype.addEventListener = function(eventName, func) { - this.on(eventName, func); - }; - ZeroClipboard.Client.prototype.receiveEvent = function(eventName, args) { - eventName = eventName.toString().toLowerCase().replace(/^on/, ""); - switch (eventName) { - case "load": - if (args && parseFloat(args.flashVersion.replace(",", ".").replace(/[^0-9\.]/gi, "")) < 10) { - this.receiveEvent("onWrongFlash", { - flashVersion: args.flashVersion - }); - return; - } - this.htmlBridge.setAttribute("data-clipboard-ready", true); - break; - case "mouseover": - ZeroClipboard.currentElement.addClass("hover"); - break; - case "mouseout": - ZeroClipboard.currentElement.removeClass("hover"); - this.resetBridge(); - break; - case "mousedown": - ZeroClipboard.currentElement.addClass("active"); - break; - case "mouseup": - ZeroClipboard.currentElement.removeClass("active"); - break; - } - if (this.handlers[eventName]) { - for (var idx = 0, len = this.handlers[eventName].length; idx < len; idx++) { - var func = this.handlers[eventName][idx]; - if (typeof func == "function") { - func(this, args); - } else if (typeof func == "string") { - window[func](this, args); - } - } - } - }; - ZeroClipboard.getDOMObjectPosition = function(obj) { - var info = { - left: 0, - top: 0, - width: obj.width ? obj.width : obj.offsetWidth, - height: obj.height ? obj.height : obj.offsetHeight, - zIndex: 9999 - }; - if (obj.style.zIndex) { - info.zIndex = parseInt(element.style.zIndex, 10); - } - while (obj) { - info.left += obj.offsetLeft; - info.left += obj.style.borderLeftWidth ? parseInt(obj.style.borderLeftWidth, 10) : 0; - info.top += obj.offsetTop; - info.top += obj.style.borderTopWidth ? parseInt(obj.style.borderTopWidth, 10) : 0; - obj = obj.offsetParent; - } - return info; - }; - function elementWrapper(element) { - if (!element || element.addClass) return element; - element.addClass = function(value) { - if (value && typeof value === "string") { - var classNames = (value || "").split(/\s+/); - var elem = this; - if (elem.nodeType === 1) { - if (!elem.className) { - elem.className = value; - } else { - var className = " " + elem.className + " ", setClass = elem.className; - for (var c = 0, cl = classNames.length; c < cl; c++) { - if (className.indexOf(" " + classNames[c] + " ") < 0) { - setClass += " " + classNames[c]; - } - } - elem.className = setClass.replace(/^\s+|\s+$/g, ""); - } - } - } - return this; - }; - element.removeClass = function(value) { - if (value && typeof value === "string" || value === undefined) { - var classNames = (value || "").split(/\s+/); - var elem = this; - if (elem.nodeType === 1 && elem.className) { - if (value) { - var className = (" " + elem.className + " ").replace(/[\n\t]/g, " "); - for (var c = 0, cl = classNames.length; c < cl; c++) { - className = className.replace(" " + classNames[c] + " ", " "); - } - elem.className = className.replace(/^\s+|\s+$/g, ""); - } else { - elem.className = ""; - } - } - } - return this; - }; - return element; - } - ZeroClipboard.$ = function(query) { - var ZeroClipboardSelect = function(s, n) { - return n.querySelectorAll(s); - }, result; - if (typeof Sizzle === "function") { - ZeroClipboardSelect = function(s, n) { - return Sizzle.uniqueSort(Sizzle(s, n)); - }; - } - if (typeof query === "string") { - result = ZeroClipboardSelect(query, document); - if (result.length === 0) result = [ document.getElementById(query) ]; - } - var newresult = []; - for (var i = 0; i < result.length; i++) { - if (result[i] !== null) newresult.push(elementWrapper(result[i])); - } - return newresult; - }; - if (typeof module !== "undefined") { - module.exports = ZeroClipboard; - } else { - window.ZeroClipboard = ZeroClipboard; - } -})(); diff --git a/webapp/static/js/ZeroClipboard.min.js b/webapp/static/js/ZeroClipboard.min.js new file mode 100644 index 00000000..fea2a1dd --- /dev/null +++ b/webapp/static/js/ZeroClipboard.min.js @@ -0,0 +1,10 @@ +/*! + * ZeroClipboard + * The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface. + * Copyright (c) 2014 Jon Rohan, James M. Greene + * Licensed MIT + * http://zeroclipboard.org/ + * v2.1.6 + */ +!function(a,b){"use strict";var c,d,e=a,f=e.document,g=e.navigator,h=e.setTimeout,i=e.encodeURIComponent,j=e.ActiveXObject,k=e.Error,l=e.Number.parseInt||e.parseInt,m=e.Number.parseFloat||e.parseFloat,n=e.Number.isNaN||e.isNaN,o=e.Math.round,p=e.Date.now,q=e.Object.keys,r=e.Object.defineProperty,s=e.Object.prototype.hasOwnProperty,t=e.Array.prototype.slice,u=function(){var a=function(a){return a};if("function"==typeof e.wrap&&"function"==typeof e.unwrap)try{var b=f.createElement("div"),c=e.unwrap(b);1===b.nodeType&&c&&1===c.nodeType&&(a=e.unwrap)}catch(d){}return a}(),v=function(a){return t.call(a,0)},w=function(){var a,c,d,e,f,g,h=v(arguments),i=h[0]||{};for(a=1,c=h.length;c>a;a++)if(null!=(d=h[a]))for(e in d)s.call(d,e)&&(f=i[e],g=d[e],i!==g&&g!==b&&(i[e]=g));return i},x=function(a){var b,c,d,e;if("object"!=typeof a||null==a)b=a;else if("number"==typeof a.length)for(b=[],c=0,d=a.length;d>c;c++)s.call(a,c)&&(b[c]=x(a[c]));else{b={};for(e in a)s.call(a,e)&&(b[e]=x(a[e]))}return b},y=function(a,b){for(var c={},d=0,e=b.length;e>d;d++)b[d]in a&&(c[b[d]]=a[b[d]]);return c},z=function(a,b){var c={};for(var d in a)-1===b.indexOf(d)&&(c[d]=a[d]);return c},A=function(a){if(a)for(var b in a)s.call(a,b)&&delete a[b];return a},B=function(a,b){if(a&&1===a.nodeType&&a.ownerDocument&&b&&(1===b.nodeType&&b.ownerDocument&&b.ownerDocument===a.ownerDocument||9===b.nodeType&&!b.ownerDocument&&b===a.ownerDocument))do{if(a===b)return!0;a=a.parentNode}while(a);return!1},C=function(a){var b;return"string"==typeof a&&a&&(b=a.split("#")[0].split("?")[0],b=a.slice(0,a.lastIndexOf("/")+1)),b},D=function(a){var b,c;return"string"==typeof a&&a&&(c=a.match(/^(?:|[^:@]*@|.+\)@(?=http[s]?|file)|.+?\s+(?: at |@)(?:[^:\(]+ )*[\(]?)((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]?b=c[1]:(c=a.match(/\)@((?:http[s]?|file):\/\/[\/]?.+?\/[^:\)]*?)(?::\d+)(?::\d+)?/),c&&c[1]&&(b=c[1]))),b},E=function(){var a,b;try{throw new k}catch(c){b=c}return b&&(a=b.sourceURL||b.fileName||D(b.stack)),a},F=function(){var a,c,d;if(f.currentScript&&(a=f.currentScript.src))return a;if(c=f.getElementsByTagName("script"),1===c.length)return c[0].src||b;if("readyState"in c[0])for(d=c.length;d--;)if("interactive"===c[d].readyState&&(a=c[d].src))return a;return"loading"===f.readyState&&(a=c[c.length-1].src)?a:(a=E())?a:b},G=function(){var a,c,d,e=f.getElementsByTagName("script");for(a=e.length;a--;){if(!(d=e[a].src)){c=null;break}if(d=C(d),null==c)c=d;else if(c!==d){c=null;break}}return c||b},H=function(){var a=C(F())||G()||"";return a+"ZeroClipboard.swf"},I={bridge:null,version:"0.0.0",pluginType:"unknown",disabled:null,outdated:null,unavailable:null,deactivated:null,overdue:null,ready:null},J="11.0.0",K={},L={},M=null,N={ready:"Flash communication is established",error:{"flash-disabled":"Flash is disabled or not installed","flash-outdated":"Flash is too outdated to support ZeroClipboard","flash-unavailable":"Flash is unable to communicate bidirectionally with JavaScript","flash-deactivated":"Flash is too outdated for your browser and/or is configured as click-to-activate","flash-overdue":"Flash communication was established but NOT within the acceptable time limit"}},O={swfPath:H(),trustedDomains:a.location.host?[a.location.host]:[],cacheBust:!0,forceEnhancedClipboard:!1,flashLoadTimeout:3e4,autoActivate:!0,bubbleEvents:!0,containerId:"global-zeroclipboard-html-bridge",containerClass:"global-zeroclipboard-container",swfObjectId:"global-zeroclipboard-flash-bridge",hoverClass:"zeroclipboard-is-hover",activeClass:"zeroclipboard-is-active",forceHandCursor:!1,title:null,zIndex:999999999},P=function(a){if("object"==typeof a&&null!==a)for(var b in a)if(s.call(a,b))if(/^(?:forceHandCursor|title|zIndex|bubbleEvents)$/.test(b))O[b]=a[b];else if(null==I.bridge)if("containerId"===b||"swfObjectId"===b){if(!cb(a[b]))throw new Error("The specified `"+b+"` value is not valid as an HTML4 Element ID");O[b]=a[b]}else O[b]=a[b];{if("string"!=typeof a||!a)return x(O);if(s.call(O,a))return O[a]}},Q=function(){return{browser:y(g,["userAgent","platform","appName"]),flash:z(I,["bridge"]),zeroclipboard:{version:Fb.version,config:Fb.config()}}},R=function(){return!!(I.disabled||I.outdated||I.unavailable||I.deactivated)},S=function(a,b){var c,d,e,f={};if("string"==typeof a&&a)e=a.toLowerCase().split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.on(c,a[c]);if(e&&e.length){for(c=0,d=e.length;d>c;c++)a=e[c].replace(/^on/,""),f[a]=!0,K[a]||(K[a]=[]),K[a].push(b);if(f.ready&&I.ready&&Fb.emit({type:"ready"}),f.error){var g=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=g.length;d>c;c++)if(I[g[c]]===!0){Fb.emit({type:"error",name:"flash-"+g[c]});break}}}return Fb},T=function(a,b){var c,d,e,f,g;if(0===arguments.length)f=q(K);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&Fb.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=K[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return Fb},U=function(a){var b;return b="string"==typeof a&&a?x(K[a])||null:x(K)},V=function(a){var b,c,d;return a=db(a),a&&!jb(a)?"ready"===a.type&&I.overdue===!0?Fb.emit({type:"error",name:"flash-overdue"}):(b=w({},a),ib.call(this,b),"copy"===a.type&&(d=pb(L),c=d.data,M=d.formatMap),c):void 0},W=function(){if("boolean"!=typeof I.ready&&(I.ready=!1),!Fb.isFlashUnusable()&&null===I.bridge){var a=O.flashLoadTimeout;"number"==typeof a&&a>=0&&h(function(){"boolean"!=typeof I.deactivated&&(I.deactivated=!0),I.deactivated===!0&&Fb.emit({type:"error",name:"flash-deactivated"})},a),I.overdue=!1,nb()}},X=function(){Fb.clearData(),Fb.blur(),Fb.emit("destroy"),ob(),Fb.off()},Y=function(a,b){var c;if("object"==typeof a&&a&&"undefined"==typeof b)c=a,Fb.clearData();else{if("string"!=typeof a||!a)return;c={},c[a]=b}for(var d in c)"string"==typeof d&&d&&s.call(c,d)&&"string"==typeof c[d]&&c[d]&&(L[d]=c[d])},Z=function(a){"undefined"==typeof a?(A(L),M=null):"string"==typeof a&&s.call(L,a)&&delete L[a]},$=function(a){return"undefined"==typeof a?x(L):"string"==typeof a&&s.call(L,a)?L[a]:void 0},_=function(a){if(a&&1===a.nodeType){c&&(xb(c,O.activeClass),c!==a&&xb(c,O.hoverClass)),c=a,wb(a,O.hoverClass);var b=a.getAttribute("title")||O.title;if("string"==typeof b&&b){var d=mb(I.bridge);d&&d.setAttribute("title",b)}var e=O.forceHandCursor===!0||"pointer"===yb(a,"cursor");Cb(e),Bb()}},ab=function(){var a=mb(I.bridge);a&&(a.removeAttribute("title"),a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.top="1px"),c&&(xb(c,O.hoverClass),xb(c,O.activeClass),c=null)},bb=function(){return c||null},cb=function(a){return"string"==typeof a&&a&&/^[A-Za-z][A-Za-z0-9_:\-\.]*$/.test(a)},db=function(a){var b;if("string"==typeof a&&a?(b=a,a={}):"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(b=a.type),b){!a.target&&/^(copy|aftercopy|_click)$/.test(b.toLowerCase())&&(a.target=d),w(a,{type:b.toLowerCase(),target:a.target||c||null,relatedTarget:a.relatedTarget||null,currentTarget:I&&I.bridge||null,timeStamp:a.timeStamp||p()||null});var e=N[a.type];return"error"===a.type&&a.name&&e&&(e=e[a.name]),e&&(a.message=e),"ready"===a.type&&w(a,{target:null,version:I.version}),"error"===a.type&&(/^flash-(disabled|outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{target:null,minimumVersion:J}),/^flash-(outdated|unavailable|deactivated|overdue)$/.test(a.name)&&w(a,{version:I.version})),"copy"===a.type&&(a.clipboardData={setData:Fb.setData,clearData:Fb.clearData}),"aftercopy"===a.type&&(a=qb(a,M)),a.target&&!a.relatedTarget&&(a.relatedTarget=eb(a.target)),a=fb(a)}},eb=function(a){var b=a&&a.getAttribute&&a.getAttribute("data-clipboard-target");return b?f.getElementById(b):null},fb=function(a){if(a&&/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)){var c=a.target,d="_mouseover"===a.type&&a.relatedTarget?a.relatedTarget:b,g="_mouseout"===a.type&&a.relatedTarget?a.relatedTarget:b,h=Ab(c),i=e.screenLeft||e.screenX||0,j=e.screenTop||e.screenY||0,k=f.body.scrollLeft+f.documentElement.scrollLeft,l=f.body.scrollTop+f.documentElement.scrollTop,m=h.left+("number"==typeof a._stageX?a._stageX:0),n=h.top+("number"==typeof a._stageY?a._stageY:0),o=m-k,p=n-l,q=i+o,r=j+p,s="number"==typeof a.movementX?a.movementX:0,t="number"==typeof a.movementY?a.movementY:0;delete a._stageX,delete a._stageY,w(a,{srcElement:c,fromElement:d,toElement:g,screenX:q,screenY:r,pageX:m,pageY:n,clientX:o,clientY:p,x:o,y:p,movementX:s,movementY:t,offsetX:0,offsetY:0,layerX:0,layerY:0})}return a},gb=function(a){var b=a&&"string"==typeof a.type&&a.type||"";return!/^(?:(?:before)?copy|destroy)$/.test(b)},hb=function(a,b,c,d){d?h(function(){a.apply(b,c)},0):a.apply(b,c)},ib=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=K["*"]||[],d=K[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},jb=function(a){var b=a.target||c||null,e="swf"===a._source;delete a._source;var f=["flash-disabled","flash-outdated","flash-unavailable","flash-deactivated","flash-overdue"];switch(a.type){case"error":-1!==f.indexOf(a.name)&&w(I,{disabled:"flash-disabled"===a.name,outdated:"flash-outdated"===a.name,unavailable:"flash-unavailable"===a.name,deactivated:"flash-deactivated"===a.name,overdue:"flash-overdue"===a.name,ready:!1});break;case"ready":var g=I.deactivated===!0;w(I,{disabled:!1,outdated:!1,unavailable:!1,deactivated:!1,overdue:g,ready:!g});break;case"beforecopy":d=b;break;case"copy":var h,i,j=a.relatedTarget;!L["text/html"]&&!L["text/plain"]&&j&&(i=j.value||j.outerHTML||j.innerHTML)&&(h=j.value||j.textContent||j.innerText)?(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h),i!==h&&a.clipboardData.setData("text/html",i)):!L["text/plain"]&&a.target&&(h=a.target.getAttribute("data-clipboard-text"))&&(a.clipboardData.clearData(),a.clipboardData.setData("text/plain",h));break;case"aftercopy":Fb.clearData(),b&&b!==vb()&&b.focus&&b.focus();break;case"_mouseover":Fb.focus(b),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseenter",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseover"})));break;case"_mouseout":Fb.blur(),O.bubbleEvents===!0&&e&&(b&&b!==a.relatedTarget&&!B(a.relatedTarget,b)&&kb(w({},a,{type:"mouseleave",bubbles:!1,cancelable:!1})),kb(w({},a,{type:"mouseout"})));break;case"_mousedown":wb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mouseup":xb(b,O.activeClass),O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_click":d=null,O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}));break;case"_mousemove":O.bubbleEvents===!0&&e&&kb(w({},a,{type:a.type.slice(1)}))}return/^_(?:click|mouse(?:over|out|down|up|move))$/.test(a.type)?!0:void 0},kb=function(a){if(a&&"string"==typeof a.type&&a){var b,c=a.target||null,d=c&&c.ownerDocument||f,g={view:d.defaultView||e,canBubble:!0,cancelable:!0,detail:"click"===a.type?1:0,button:"number"==typeof a.which?a.which-1:"number"==typeof a.button?a.button:d.createEvent?0:1},h=w(g,a);c&&d.createEvent&&c.dispatchEvent&&(h=[h.type,h.canBubble,h.cancelable,h.view,h.detail,h.screenX,h.screenY,h.clientX,h.clientY,h.ctrlKey,h.altKey,h.shiftKey,h.metaKey,h.button,h.relatedTarget],b=d.createEvent("MouseEvents"),b.initMouseEvent&&(b.initMouseEvent.apply(b,h),b._source="js",c.dispatchEvent(b)))}},lb=function(){var a=f.createElement("div");return a.id=O.containerId,a.className=O.containerClass,a.style.position="absolute",a.style.left="0px",a.style.top="-9999px",a.style.width="1px",a.style.height="1px",a.style.zIndex=""+Db(O.zIndex),a},mb=function(a){for(var b=a&&a.parentNode;b&&"OBJECT"===b.nodeName&&b.parentNode;)b=b.parentNode;return b||null},nb=function(){var a,b=I.bridge,c=mb(b);if(!b){var d=ub(e.location.host,O),g="never"===d?"none":"all",h=sb(O),i=O.swfPath+rb(O.swfPath,O);c=lb();var j=f.createElement("div");c.appendChild(j),f.body.appendChild(c);var k=f.createElement("div"),l="activex"===I.pluginType;k.innerHTML='"+(l?'':"")+'',b=k.firstChild,k=null,u(b).ZeroClipboard=Fb,c.replaceChild(b,j)}return b||(b=f[O.swfObjectId],b&&(a=b.length)&&(b=b[a-1]),!b&&c&&(b=c.firstChild)),I.bridge=b||null,b},ob=function(){var a=I.bridge;if(a){var b=mb(a);b&&("activex"===I.pluginType&&"readyState"in a?(a.style.display="none",function c(){if(4===a.readyState){for(var d in a)"function"==typeof a[d]&&(a[d]=null);a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b)}else h(c,10)}()):(a.parentNode&&a.parentNode.removeChild(a),b.parentNode&&b.parentNode.removeChild(b))),I.ready=null,I.bridge=null,I.deactivated=null}},pb=function(a){var b={},c={};if("object"==typeof a&&a){for(var d in a)if(d&&s.call(a,d)&&"string"==typeof a[d]&&a[d])switch(d.toLowerCase()){case"text/plain":case"text":case"air:text":case"flash:text":b.text=a[d],c.text=d;break;case"text/html":case"html":case"air:html":case"flash:html":b.html=a[d],c.html=d;break;case"application/rtf":case"text/rtf":case"rtf":case"richtext":case"air:rtf":case"flash:rtf":b.rtf=a[d],c.rtf=d}return{data:b,formatMap:c}}},qb=function(a,b){if("object"!=typeof a||!a||"object"!=typeof b||!b)return a;var c={};for(var d in a)if(s.call(a,d)){if("success"!==d&&"data"!==d){c[d]=a[d];continue}c[d]={};var e=a[d];for(var f in e)f&&s.call(e,f)&&s.call(b,f)&&(c[d][b[f]]=e[f])}return c},rb=function(a,b){var c=null==b||b&&b.cacheBust===!0;return c?(-1===a.indexOf("?")?"?":"&")+"noCache="+p():""},sb=function(a){var b,c,d,f,g="",h=[];if(a.trustedDomains&&("string"==typeof a.trustedDomains?f=[a.trustedDomains]:"object"==typeof a.trustedDomains&&"length"in a.trustedDomains&&(f=a.trustedDomains)),f&&f.length)for(b=0,c=f.length;c>b;b++)if(s.call(f,b)&&f[b]&&"string"==typeof f[b]){if(d=tb(f[b]),!d)continue;if("*"===d){h.length=0,h.push(d);break}h.push.apply(h,[d,"//"+d,e.location.protocol+"//"+d])}return h.length&&(g+="trustedOrigins="+i(h.join(","))),a.forceEnhancedClipboard===!0&&(g+=(g?"&":"")+"forceEnhancedClipboard=true"),"string"==typeof a.swfObjectId&&a.swfObjectId&&(g+=(g?"&":"")+"swfObjectId="+i(a.swfObjectId)),g},tb=function(a){if(null==a||""===a)return null;if(a=a.replace(/^\s+|\s+$/g,""),""===a)return null;var b=a.indexOf("//");a=-1===b?a:a.slice(b+2);var c=a.indexOf("/");return a=-1===c?a:-1===b||0===c?null:a.slice(0,c),a&&".swf"===a.slice(-4).toLowerCase()?null:a||null},ub=function(){var a=function(a){var b,c,d,e=[];if("string"==typeof a&&(a=[a]),"object"!=typeof a||!a||"number"!=typeof a.length)return e;for(b=0,c=a.length;c>b;b++)if(s.call(a,b)&&(d=tb(a[b]))){if("*"===d){e.length=0,e.push("*");break}-1===e.indexOf(d)&&e.push(d)}return e};return function(b,c){var d=tb(c.swfPath);null===d&&(d=b);var e=a(c.trustedDomains),f=e.length;if(f>0){if(1===f&&"*"===e[0])return"always";if(-1!==e.indexOf(b))return 1===f&&b===d?"sameDomain":"always"}return"never"}}(),vb=function(){try{return f.activeElement}catch(a){return null}},wb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)||a.classList.add(b),a;if(b&&"string"==typeof b){var c=(b||"").split(/\s+/);if(1===a.nodeType)if(a.className){for(var d=" "+a.className+" ",e=a.className,f=0,g=c.length;g>f;f++)d.indexOf(" "+c[f]+" ")<0&&(e+=" "+c[f]);a.className=e.replace(/^\s+|\s+$/g,"")}else a.className=b}return a},xb=function(a,b){if(!a||1!==a.nodeType)return a;if(a.classList)return a.classList.contains(b)&&a.classList.remove(b),a;if("string"==typeof b&&b){var c=b.split(/\s+/);if(1===a.nodeType&&a.className){for(var d=(" "+a.className+" ").replace(/[\n\t]/g," "),e=0,f=c.length;f>e;e++)d=d.replace(" "+c[e]+" "," ");a.className=d.replace(/^\s+|\s+$/g,"")}}return a},yb=function(a,b){var c=e.getComputedStyle(a,null).getPropertyValue(b);return"cursor"!==b||c&&"auto"!==c||"A"!==a.nodeName?c:"pointer"},zb=function(){var a,b,c,d=1;return"function"==typeof f.body.getBoundingClientRect&&(a=f.body.getBoundingClientRect(),b=a.right-a.left,c=f.body.offsetWidth,d=o(b/c*100)/100),d},Ab=function(a){var b={left:0,top:0,width:0,height:0};if(a.getBoundingClientRect){var c,d,g,h=a.getBoundingClientRect();"pageXOffset"in e&&"pageYOffset"in e?(c=e.pageXOffset,d=e.pageYOffset):(g=zb(),c=o(f.documentElement.scrollLeft/g),d=o(f.documentElement.scrollTop/g));var i=f.documentElement.clientLeft||0,j=f.documentElement.clientTop||0;b.left=h.left+c-i,b.top=h.top+d-j,b.width="width"in h?h.width:h.right-h.left,b.height="height"in h?h.height:h.bottom-h.top}return b},Bb=function(){var a;if(c&&(a=mb(I.bridge))){var b=Ab(c);w(a.style,{width:b.width+"px",height:b.height+"px",top:b.top+"px",left:b.left+"px",zIndex:""+Db(O.zIndex)})}},Cb=function(a){I.ready===!0&&(I.bridge&&"function"==typeof I.bridge.setHandCursor?I.bridge.setHandCursor(a):I.ready=!1)},Db=function(a){if(/^(?:auto|inherit)$/.test(a))return a;var b;return"number"!=typeof a||n(a)?"string"==typeof a&&(b=Db(l(a,10))):b=a,"number"==typeof b?b:"auto"},Eb=function(a){function b(a){var b=a.match(/[\d]+/g);return b.length=3,b.join(".")}function c(a){return!!a&&(a=a.toLowerCase())&&(/^(pepflashplayer\.dll|libpepflashplayer\.so|pepperflashplayer\.plugin)$/.test(a)||"chrome.plugin"===a.slice(-13))}function d(a){a&&(i=!0,a.version&&(l=b(a.version)),!l&&a.description&&(l=b(a.description)),a.filename&&(k=c(a.filename)))}var e,f,h,i=!1,j=!1,k=!1,l="";if(g.plugins&&g.plugins.length)e=g.plugins["Shockwave Flash"],d(e),g.plugins["Shockwave Flash 2.0"]&&(i=!0,l="2.0.0.11");else if(g.mimeTypes&&g.mimeTypes.length)h=g.mimeTypes["application/x-shockwave-flash"],e=h&&h.enabledPlugin,d(e);else if("undefined"!=typeof a){j=!0;try{f=new a("ShockwaveFlash.ShockwaveFlash.7"),i=!0,l=b(f.GetVariable("$version"))}catch(n){try{f=new a("ShockwaveFlash.ShockwaveFlash.6"),i=!0,l="6.0.21"}catch(o){try{f=new a("ShockwaveFlash.ShockwaveFlash"),i=!0,l=b(f.GetVariable("$version"))}catch(p){j=!1}}}}I.disabled=i!==!0,I.outdated=l&&m(l)c;c++)a=e[c].replace(/^on/,""),f[a]=!0,g[a]||(g[a]=[]),g[a].push(b);if(f.ready&&I.ready&&this.emit({type:"ready",client:this}),f.error){var h=["disabled","outdated","unavailable","deactivated","overdue"];for(c=0,d=h.length;d>c;c++)if(I[h[c]]){this.emit({type:"error",name:"flash-"+h[c],client:this});break}}}return this},Nb=function(a,b){var c,d,e,f,g,h=Hb[this.id]&&Hb[this.id].handlers;if(0===arguments.length)f=q(h);else if("string"==typeof a&&a)f=a.split(/\s+/);else if("object"==typeof a&&a&&"undefined"==typeof b)for(c in a)s.call(a,c)&&"string"==typeof c&&c&&"function"==typeof a[c]&&this.off(c,a[c]);if(f&&f.length)for(c=0,d=f.length;d>c;c++)if(a=f[c].toLowerCase().replace(/^on/,""),g=h[a],g&&g.length)if(b)for(e=g.indexOf(b);-1!==e;)g.splice(e,1),e=g.indexOf(b,e);else g.length=0;return this},Ob=function(a){var b=null,c=Hb[this.id]&&Hb[this.id].handlers;return c&&(b="string"==typeof a&&a?c[a]?c[a].slice(0):[]:x(c)),b},Pb=function(a){if(Ub.call(this,a)){"object"==typeof a&&a&&"string"==typeof a.type&&a.type&&(a=w({},a));var b=w({},db(a),{client:this});Vb.call(this,b)}return this},Qb=function(a){a=Wb(a);for(var b=0;b0,d=!a.target||c&&-1!==b.indexOf(a.target),e=a.relatedTarget&&c&&-1!==b.indexOf(a.relatedTarget),f=a.client&&a.client===this;return d||e||f?!0:!1},Vb=function(a){if("object"==typeof a&&a&&a.type){var b=gb(a),c=Hb[this.id]&&Hb[this.id].handlers["*"]||[],d=Hb[this.id]&&Hb[this.id].handlers[a.type]||[],f=c.concat(d);if(f&&f.length){var g,h,i,j,k,l=this;for(g=0,h=f.length;h>g;g++)i=f[g],j=l,"string"==typeof i&&"function"==typeof e[i]&&(i=e[i]),"object"==typeof i&&i&&"function"==typeof i.handleEvent&&(j=i,i=i.handleEvent),"function"==typeof i&&(k=w({},a),hb(i,j,[k],b))}return this}},Wb=function(a){return"string"==typeof a&&(a=[]),"number"!=typeof a.length?[a]:a},Xb=function(a){if(a&&1===a.nodeType){var b=function(a){(a||(a=e.event))&&("js"!==a._source&&(a.stopImmediatePropagation(),a.preventDefault()),delete a._source)},c=function(c){(c||(c=e.event))&&(b(c),Fb.focus(a))};a.addEventListener("mouseover",c,!1),a.addEventListener("mouseout",b,!1),a.addEventListener("mouseenter",b,!1),a.addEventListener("mouseleave",b,!1),a.addEventListener("mousemove",b,!1),Kb[a.zcClippingId]={mouseover:c,mouseout:b,mouseenter:b,mouseleave:b,mousemove:b}}},Yb=function(a){if(a&&1===a.nodeType){var b=Kb[a.zcClippingId];if("object"==typeof b&&b){for(var c,d,e=["move","leave","enter","out","over"],f=0,g=e.length;g>f;f++)c="mouse"+e[f],d=b[c],"function"==typeof d&&a.removeEventListener(c,d,!1);delete Kb[a.zcClippingId]}}};Fb._createClient=function(){Lb.apply(this,v(arguments))},Fb.prototype.on=function(){return Mb.apply(this,v(arguments))},Fb.prototype.off=function(){return Nb.apply(this,v(arguments))},Fb.prototype.handlers=function(){return Ob.apply(this,v(arguments))},Fb.prototype.emit=function(){return Pb.apply(this,v(arguments))},Fb.prototype.clip=function(){return Qb.apply(this,v(arguments))},Fb.prototype.unclip=function(){return Rb.apply(this,v(arguments))},Fb.prototype.elements=function(){return Sb.apply(this,v(arguments))},Fb.prototype.destroy=function(){return Tb.apply(this,v(arguments))},Fb.prototype.setText=function(a){return Fb.setData("text/plain",a),this},Fb.prototype.setHtml=function(a){return Fb.setData("text/html",a),this},Fb.prototype.setRichText=function(a){return Fb.setData("application/rtf",a),this},Fb.prototype.setData=function(){return Fb.setData.apply(this,v(arguments)),this},Fb.prototype.clearData=function(){return Fb.clearData.apply(this,v(arguments)),this},Fb.prototype.getData=function(){return Fb.getData.apply(this,v(arguments))},"function"==typeof define&&define.amd?define(function(){return Fb}):"object"==typeof module&&module&&"object"==typeof module.exports&&module.exports?module.exports=Fb:a.ZeroClipboard=Fb}(function(){return this||window}()); +//# sourceMappingURL=ZeroClipboard.min.map \ No newline at end of file diff --git a/webapp/static/js/jquery.imgareaselect.js b/webapp/static/js/jquery.imgareaselect.js index c31f8e3b..0866195a 100644 --- a/webapp/static/js/jquery.imgareaselect.js +++ b/webapp/static/js/jquery.imgareaselect.js @@ -57,9 +57,6 @@ $.imgAreaSelect = function (img, options) { /* Document element */ docElem = document.documentElement, - /* User agent */ - ua = navigator.userAgent, - /* Image position (relative to viewport) */ left, top, @@ -1732,7 +1729,7 @@ $.fn.imgAreaSelect = function (options) { $(this).data('imgAreaSelect').setOptions(options); } else if (!options.remove) { - /* No exising instance -- create a new one */ + /* No existing instance -- create a new one */ /* * If neither the "enable" nor the "disable" option is present, add diff --git a/webapp/static/js/pdf_view.js b/webapp/static/js/pdf_view.js index 7f13ab11..e250a765 100644 --- a/webapp/static/js/pdf_view.js +++ b/webapp/static/js/pdf_view.js @@ -4,23 +4,12 @@ var clip = null; PDF_ID = window.location.pathname.split('/')[2]; -TEMPLATES = [ - -] +// bootstrap 2 only, fix for multiple modal recursion error: http://stackoverflow.com/questions/13649459/twitter-bootstrap-multiple-modal-error +$.fn.modal.Constructor.prototype.enforceFocus = function () {}; -$(document).ready(function() { - ZeroClipboard.setMoviePath('/swf/ZeroClipboard.swf'); - clip = new ZeroClipboard.Client(); +ZeroClipboard.config( { swfPath: "/swf/ZeroClipboard.swf" } ); - clip.on('mousedown', function(client) { - client.setText($('table').table2CSV({delivery: null})); - $('#data-modal span').css('display', 'inline').delay(900).fadeOut('slow'); - }); - - $('.has-tooltip').tooltip(); -}); - -var Page = Backbone.Model.extend({ +Tabula.Page = Backbone.Model.extend({ // number: null, //set on initialize // width: null, //set on initialize // height: null, //set on initialize @@ -32,44 +21,321 @@ var Page = Backbone.Model.extend({ }, }); -var Pages = Backbone.Collection.extend({ - model: Page, +Tabula.Pages = Backbone.Collection.extend({ + model: Tabula.Page, url: null, //set on initialize initialize: function(){ this.url = '/pdfs/' + PDF_ID + '/pages.json?_=' + Math.round(+new Date()).toString(); }, -}) +}); -var Document = Backbone.Model.extend({ +Tabula.Document = Backbone.Model.extend({ page_collection: null, //set on initialize + selections: null, //set on initialize pdf_id: PDF_ID, //set on initialize + initialize: function(options){ + this.page_collection = new Tabula.Pages([], {pdf_document: this}) + this.selections = new Tabula.Selections([], {pdf_document: this}) + } +}); + +Tabula.Selection = Backbone.Model.extend({ + pdf_id: PDF_ID, + + initialize: function(){ + _.bindAll(this, 'queryForData', 'repeatLassos') + }, + + queryForData: function(){ + var page = Tabula.ui.pdf_document.page_collection.at(this.get('page_number') - 1); + //TODO: break out query handling into a new method. + var imageWidth = this.get('imageWidth'); + + var pdf_width = page.get('width'); + var pdf_height = page.get('height'); + var pdf_rotation = page.get('rotation'); + + var scale = (Math.abs(pdf_rotation) == 90 ? pdf_height : pdf_width) / imageWidth; + + var selection_coords = { + x1: this.get('x1') * scale, + x2: this.get('x2') * scale, + y1: this.get('y1') * scale, + y2: this.get('y2') * scale, + page: this.get('page_number'), + extraction_method: this.get('extractionMethod') || 'guess', + }; + Tabula.ui.query = new Tabula.Query({list_of_coords: [selection_coords], selection: this.get('extractionMethod')}); + Tabula.ui.createDataView(); + Tabula.ui.query.doQuery(); + }, + + repeatLassos: function(e) { + this.pdf_document.page_collection.each(function(page){ + if(this.get('page_number') < page.get('page_number')){ // for each page after this one, + imgAreaSelectAPIObj = this.ui.imgAreaSelects[page.get('page_number') - 1] + if (imgAreaSelectAPIObj === false) return; + + imgAreaSelectAPIObj.cancelSelections(); // notify the imgAreaSelect of the new selection + iasSelection = imgAreaSelectAPIObj.createNewSelection(this.get('x1'), + this.get('y1'), + this.get('x2'), + this.get('y2')); + imgAreaSelectAPIObj.setOptions({show: true}); + imgAreaSelectAPIObj.update(); + + new_selection = this.clone(); // and create a new Selection. + new_selection.set('page_number', page.get('page_number')); + new_selection.set('iasId', iasSelection.id); + this.collection.add(new_selection); + /* which causes thumbnails to be created, Download All button to know about these selections. */ + } + }); + + //TODO: notify UI to remove each repeated selection's Repeat Lassos button. + // Old code for that: + // //remove the button + // $(e.currentTarget).fadeOut(500, function() { $(this).remove(); }); + + }, +}); + +//TODO: write this to localStorage sometimes. +//TODO: get values here from localStroage on startup +Tabula.Options = Backbone.Model.extend({ + initialize: function(){ + this.set('multiselect_mode', true); + this.set('extraction_method', null); + this.set('show_advanced_options', localStorage.getItem("tabula-show-advanced-options")) + }, +}); + +/* What the hell are you doing here, Jeremy? + The canonical store of selections now needs to be in Backbone, not in imgareaselect. + The UI can listen to the Selections; imgAreaselect creates adds to the collection, + causing the thumbnail to be drawn. + + Clearing or repeating is much easier, because we don't have to mess around with the UI. + Querying all is likewise easy. + + We could also store extraction option info on the selections, if we want. + + On imgareaselect's _onSelectEnd, add the selection to Selections + + On Selections's remove (or change), find the right imgAreaSelect +*/ + +Tabula.Selections = Backbone.Collection.extend({ + model: Tabula.Selection, + url: null, //set on init initialize: function(){ - this.page_collection = new Pages([], {pdf_document: this}) + this.url = '/pdfs/' + PDF_ID + '/tables.json?_=' + Math.round(+new Date()).toString(); + _.bindAll(this, 'findByIasId', 'updateOrCreateByIasId'); + }, + + findByIasId: function(iasId, pageNumber){ + return this.find(function(selection){ return selection.get('iasId') == iasId && selection.get('page_number') == pageNumber}); + }, + + updateOrCreateByIasId: function(iasSelection, pageNumber, imageWidth){ + var selection = this.findByIasId(iasSelection.id, pageNumber); + if(selection){ // if it already exists + selection.set(iasSelection); + }else{ + new_selection_args = _.extend({'page_number': pageNumber, + 'imageWidth': imageWidth, + 'iasId': iasSelection.id, + 'extractionMethod': Tabula.ui.options.extraction_method, + 'pdf_document': this.pdf_document}, + _.omit(iasSelection, 'id')) + selection = new Tabula.Selection(new_selection_args); + this.add([selection]); + } + return selection; } -}) -// TODO: make sure render fires after document ready or something +}); + +Tabula.Query = Backbone.Model.extend({ + //has selections, data + //pertains to DataView + + // on modal exit, destroy this.ui.query + // on selection end or download all button, create this.ui.query + // in the modal, modify and requery. + + initialize: function(){ + // should be inited with list_of_coords + _.bindAll(this, 'doQuery'); + }, + + doQuery: function(options) { + this.query_data = { + 'coords': JSON.stringify(this.get('list_of_coords')), + 'extraction_method': Tabula.ui.options.get('extraction_method') + } + + this.trigger("tabula:query-start"); + $.ajax({ + type: 'POST', + url: '/pdf/' + PDF_ID + '/data', + data: this.query_data, + success: _.bind(function(resp) { + this.set('data', resp); + this.trigger("tabula:query-success"); + + // this only happens on the first select, when we don't know what the extraction method is yet + // (because it's set by the heuristic). + // TODO: only set this on the modal, not globally + // TODO: only do this if only one coord_set is in this query. + // TODO: let radio buttons all be unset. + Tabula.ui.options.set('extraction_method', resp[0]["extraction_method"]); + + if (options !== undefined && _.isFunction(options.success)){ + Tabula.ui.options.success(resp); + } + + }, this), + error: _.bind(function(xhr, status, error) { + Tabula.ui.components['data_view'].hideAndTrash(); + $('#modal-error textarea').html(xhr.responseText); + $('#modal-error').modal('show'); + if (options !== undefined && _.isFunction(options.error)) + options.error(resp); + }, this), + }); + }, +}) Tabula.DataView = Backbone.View.extend({ //only one, is the data modal. el: '#data-modal', + $loading: $('#loading'), + template: Handlebars.compile($('#templates #modal-footer-template').html()), events: { 'click .download-dropdown': 'dropDownOrUp', 'click .extraction-method-btn:not(.active)': 'queryWithToggledExtractionMethod', 'click .toggle-advanced-options': 'toggleAdvancedOptionsShown', + 'click .show-advanced-options': 'showAdvancedOptions', + 'click .hide-advanced-options': 'hideAdvancedOptions', + 'hidden': 'trash' //N.B.: Download button (and format-specific download buttons) are an HTML form. //TODO: handle flash clipboard thingy here. }, ui: null, //added on create extractionMethod: "guess", - initialize: function(){ - _.bindAll(this, 'render', 'toggleAdvancedOptionsShown'); + initialize: function(stuff){ + _.bindAll(this, 'render', 'renderLoading', 'renderFooter', 'renderTable', 'showAdvancedOptions','hideAdvancedOptions', 'dropDownOrUp', 'queryWithToggledExtractionMethod', 'getOppositeExtractionMethod', 'trash', 'hideAndTrash', 'renderUnlessMultiSelect'); + this.ui = stuff.ui; + this.listenTo(this.model, 'tabula:query-start', this.renderUnlessMultiSelect); + this.listenTo(this.model, 'tabula:query-success', this.renderUnlessMultiSelect); + this.$modalBody = this.$el.find('.modal-body'); + }, + + hideAndTrash: function(){ + this.$el.modal('hide'); + this.trash(); + }, + + trash: function(){ + this.undelegateEvents(); + Tabula.ui.trashDataView(); + return this; + }, + + renderLoading: function(){ + $('#switch-method').prop('disabled', true); + this.$modalBody.prepend(this.$loading.show()); + this.$el.find('.modal-body table').css('visibility', 'hidden'); + this.$modalBody.css('overflow', 'hidden'); + return this; + }, + + renderUnlessMultiSelect: function(){ + console.log(Tabula.ui.options.get('multiselect_mode')); + if(!Tabula.ui.options.get('multiselect_mode')){ + this.render(); + } }, render: function(){ - // do I need this, or will the modal() method suffice? - // or should I keep it for forwards-compatibility? + this.$el.modal('show'); //bootstrap stuff + + if(!this.model.get('data')){ + this.renderLoading(); + this.renderFooter(); + }else{ + this.renderTable(); + this.renderFooter(); + } + + this.$el.find('.has-tooltip').tooltip(); + + return this; + }, + + renderFooter: function(){ + + templateOptions = { + extractionMethodDisabled: _.isNull(this.model.data) ? 'disabled="disabled"' : '', + pdf_id: PDF_ID, + list_of_coords: this.model.get('list_of_coords'), + copyDisabled: Tabula.ui.flash_borked ? 'disabled="disabled" data-toggle="tooltip" title="'+Tabula.ui.flash_borken_message+'"' : '', + } + //TODO: on create, show/hide advanced options area as necessary from this.ui.options + + if (Tabula.ui.flash_borked){ + this.$el.find('#copy-csv-to-clipboard').addClass('has-tooltip'); + } + + $('#' + this.ui.options.get('extraction_method') + '-method-btn').button('toggle'); + this.$el.find(".modal-footer-container").html(this.template(templateOptions)); + }, + + renderTable: function(){ + this.$loading = this.$loading.detach(); + this.$el.find('.modal-body table').css('visibility', 'visible'); + this.$modalBody.css('overflow', 'auto'); + + var tableHTML = ''; + // this.data is a list of responses (because we sent a list of coordinate sets) + $.each(_.pluck(this.model.get('data'), 'data'), function(i, rows) { + $.each(rows, function(j, row) { + tableHTML += ''; + }); + }); + tableHTML += '
' + _.pluck(row, 'text').join('') + '
'; + this.$modalBody.html(tableHTML); + + if(!Tabula.ui.client){ + Tabula.ui.client = new ZeroClipboard(); + } + + Tabula.ui.client.on( 'ready', _.bind(function(event) { + Tabula.ui.client.clip( this.$el.find("#copy-csv-to-clipboard") ); + + Tabula.ui.client.on( 'copy', _.bind(function(event) { + var clipboard = event.clipboardData; + var tableData = this.$el.find('.modal-body table').table2CSV({delivery: null}) + clipboard.setData( 'text/plain', tableData ); + }, this) ); + + Tabula.ui.client.on( 'aftercopy', function(event) { + $('#data-modal #copy-csv-to-clipboard').css('display', 'inline').delay(900).fadeOut('slow'); + } ); + }, this) ); + + Tabula.ui.client.on( 'error', _.bind(function(event) { + //disable all clipboard buttons, add tooltip, event.message + Tabula.ui.flash_borked = true; + Tabula.ui.flash_borken_message = event.message; + this.$el.find('#copy-csv-to-clipboard').addClass('has-tooltip').tooltip(); + console.log( 'ZeroClipboard error of type "' + event.name + '": ' + event.message ); + ZeroClipboard.destroy(); + },this) ); + + return this; }, dropDownOrUp: function(e){ @@ -86,57 +352,37 @@ Tabula.DataView = Backbone.View.extend({ //only one, is the data modal. }, 100); }, - toggleAdvancedOptionsShown: function(){ + showAdvancedOptions: function(){ + this.ui.options.set('show_advanced_options', true); var $advancedOptions = $('#advanced-options'); - currentAdvancedOptions = $advancedOptions.is(":visible"); - if(currentAdvancedOptions){ - // currently shown, so hide it - localStorage.setItem("tabula-show-advanced-options", "false"); - }else{ - // currently hidden, so show it - localStorage.setItem("tabula-show-advanced-options", "true"); - } - this.setAdvancedOptionsShown(); + var $advancedShowButton = $('#basic-options .show-advanced-options'); + $advancedOptions.slideDown(); + $advancedShowButton.hide(); }, - - setAdvancedOptionsShown: function(){ - var showAdvancedOptions = localStorage.getItem("tabula-show-advanced-options"); - //TODO: cache $advancedOptions, $advancedShowButton on the data_view object. + hideAdvancedOptions: function(){ + this.ui.options.set('show_advanced_options', false); var $advancedOptions = $('#advanced-options'); - var $advancedShowButton = $('#basic-options .toggle-advanced-options'); - if(showAdvancedOptions === "true"){ - $advancedOptions.slideDown(); - $advancedShowButton.hide(); - }else{ - $advancedOptions.slideUp(); - $advancedShowButton.show(); - } + var $advancedShowButton = $('#basic-options .show-advanced-options'); + $advancedOptions.slideUp(); + $advancedShowButton.show(); }, queryWithToggledExtractionMethod: function(e){ - // console.log("before", this.extractionMethod); - this.extractionMethod = this.getOppositeExtractionMethod(); - // console.log("after", this.extractionMethod); - this.updateExtractionMethodButton(); - - this.redoQuery(); + this.ui.options.set('extraction_method', this.getOppositeExtractionMethod()); + Tabula.ui.query.set('extraction_method', this.getOppositeExtractionMethod()); + Tabula.ui.query.doQuery(); }, getOppositeExtractionMethod: function(){ - if (this.extractionMethod == "guess"){ + if (this.ui.options.get('extraction_method') == "guess"){ return; // this should never happen. } - else if (this.extractionMethod == "original") { + else if (this.ui.options.get('extraction_method') == "original") { return "spreadsheet"; } return "original"; }, - updateExtractionMethodButton: function(){ - $('#' + this.extractionMethod + '-method-btn').button('toggle'); - }, - - }); Tabula.DocumentView = Backbone.View.extend({ //only one @@ -144,6 +390,7 @@ Tabula.DocumentView = Backbone.View.extend({ //only one 'click button.close#directions' : 'moveSelectionsUp', }, ui: null, //added on create + page_views: [], /* when the Directions area is closed, the pages themselves move up, because they're just normally positioned. * The selections on those images, though, do not, and need to be moved up separately. @@ -155,50 +402,42 @@ Tabula.DocumentView = Backbone.View.extend({ //only one }); }, - initialize: function(){ + initialize: function(stuff){ _.bindAll(this, 'createImgareaselects', 'render'); + this.ui = stuff.ui; }, render: function(){ return this; }, - createImgareaselects: function(){ - return; // temporary no op. TODO: get rid of this. - } - //TODO: delegate creating imgareaselect to each child page_view. - /*createImgareaselects : function(tableGuessesTmp, pages){ + createImgareaselects : function(tableGuessesTmp, pages){ var selectsNotYetLoaded = _(pages).filter(function(page){ return !page['deleted']}).length; this.ui.tableGuesses = tableGuessesTmp; function drawDetectedTablesIfAllAreLoaded(){ selectsNotYetLoaded--; if(selectsNotYetLoaded == 0){ - for(var imageIndex=0; imageIndex < imgAreaSelects.length; imageIndex++){ + for(var imageIndex=0; imageIndex < this.ui.imgAreaSelects.length; imageIndex++){ var pageIndex = imageIndex + 1; - if(imgAreaSelects[imageIndex]){ //not undefined + if(this.ui.imgAreaSelects[imageIndex]){ //not undefined this.drawDetectedTables( $('img#page-' + pageIndex), tableGuesses ); } } } } - this.ui.imgAreaSelects = _(this.page_views).map(_.bind(function(page_view, i){ - page_view.createImgareaselect(null, pages[i], drawDetectedTablesIfAllAreLoaded); - }, this)); - }*/ - - // createImgareaselects : function(tableGuessesTmp, pages){ - // var that = this; - - // imgAreaSelects = $.map(pages, _.bind(function(page, arrayIndex){ - // }, this)); + this.ui.imgAreaSelects = _(this.page_collection).map(function(page_view, i){ + return page_view.createImgareaselect(null, drawDetectedTablesIfAllAreLoaded); + }); + } - // }, }); +//TODO: switch back to underscore templates, remove handlebars dependency + Tabula.PageView = Backbone.View.extend({ // one per page of the PDF document_view: null, //added on create className: 'row pdf-page', @@ -211,8 +450,10 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF 'click i.rotate-left i.rotate-right': 'rotate_page', }, - initialize: function(){ - _.bindAll(this, 'createImgareaselect', 'rotate_page', 'delete_page'); + initialize: function(stuff){ + this.ui = stuff.ui; + _.bindAll(this, 'createImgareaselect', 'rotate_page', 'delete_page', + '_onSelectStart', '_onSelectChange', '_onSelectEnd', '_onSelectCancel', 'render'); }, render: function(){ @@ -233,111 +474,97 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF .attr('data-intro', "Click and drag to select each table in your document. Once you've selected it, a window to preview your data will appear, along with options to download it as a spreadsheet."); } - this.createImgareaselect(); - return this; - }, - createImgareaselect: function(){ - return; //TODO: replace this no op with the real thing. + Tabula.ui.imgAreaSelects.push( this.createImgareaselect() ); //TODO: is this gross? + return this; }, - /*createImgareaselect: function(tableGuessesTmp, page, drawDetectedTablesIfAllAreLoaded){ - if (page['deleted']) { + createImgareaselect: function(tableGuessesTmp, drawDetectedTablesIfAllAreLoaded){ + if (this.model.get('deleted')) { return false; } - // $image = $('img#page-' + this.page_number); - $image = $el.children('img'); - return $image.imgAreaSelect({ + this.$image = this.$el.find('img'); + return this.$image.imgAreaSelect({ handles: true, instance: true, allowOverlaps: false, show: true, multipleSelections: true, - onSelectStart: _.bind(that._onSelectStart, that), - onSelectChange: that._onSelectChange, - onSelectEnd: _.bind(that._onSelectEnd, that), - onSelectCancel: _.bind(that._onSelectCancel, that), - onInit: _.bind(drawDetectedTablesIfAllAreLoaded, this) + onSelectStart: this._onSelectStart, + onSelectChange: this._onSelectChange, + onSelectEnd: this._onSelectEnd, + onSelectCancel: this._onSelectCancel, + onInit: drawDetectedTablesIfAllAreLoaded }); - } - - _onSelectStart: function(img, selection) { - self.document_view.ui.sidebar.thumbnail_views[this.page_number].showSelectionThumbnail(selection) - }, - - _onSelectChange: function(img, selection) { - var sshow = $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selection.id); - var scale = $('#thumb-' + $(img).attr('id') + ' img').width() / $(img).width(); - $(sshow).css('top', selection.y1 * scale + 'px') - .css('left', selection.x1 * scale + 'px') - .css('width', ((selection.x2 - selection.x1) * scale) + 'px') - .css('height', ((selection.y2 - selection.y1) * scale) + 'px'); - - var b; - var but_id = $(img).attr('id') + '-' + selection.id; - if (b = $('button#' + but_id)) { - var img_pos = $(img).offset(); - $(b) - .css({ - top: img_pos.top + selection.y1 + selection.height - $('button#' + but_id).height() * 1.5, - left: img_pos.left + selection.x1 + selection.width + 5 - }) - .data('selection', selection); - } }, - _onSelectEnd: function(img, selection) { - if (selection.width == 0 && selection.height == 0) { - $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selection.id).css('display', 'none'); - } - if (selection.height * selection.width < 5000) return; //TODO: this is dumb, get rid of it. - this.lastSelection = selection; - var thumb_width = $(img).width(); - var thumb_height = $(img).height(); + _onSelectStart: function(img, iasSelection) { + Tabula.ui.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); + }, - var pdf_width = parseInt($(img).data('original-width')); - var pdf_height = parseInt($(img).data('original-height')); - var pdf_rotation = parseInt($(img).data('rotation')); + _onSelectChange: function(img, iasSelection) { + Tabula.ui.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); + + // This is for moving the repeat lassos button, I think. -Jeremy 7/31/14 + var b; + var but_id = $(img).attr('id') + '-' + iasSelection.id; + if (b = $('button#' + but_id)) { + var img_pos = $(img).offset(); + $(b) + .css({ + top: img_pos.top + iasSelection.y1 + iasSelection.height - $('button#' + but_id).height() * 1.5, + left: img_pos.left + iasSelection.x1 + iasSelection.width + 5 + }) + .data('selection', iasSelection); + } + }, - var scale = (Math.abs(pdf_rotation) == 90 ? pdf_height : pdf_width) / thumb_width; + //TODO: deal with this. + _onSelectEnd: function(img, iasSelection) { + selection = Tabula.ui.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); - // create button for repeating lassos, only if there are more pages after this - if (this.pageCount > $(img).data('page')) { - var but_id = $(img).attr('id') + '-' + selection.id; - $('body').append(''); - var img_pos = $(img).offset(); - $('button#' + but_id) - .css({ - position: 'absolute', - top: img_pos.top + selection.y1 + selection.height - $('button#' + but_id).height() * 1.5, - left: img_pos.left + selection.x1 + selection.width + 5 - }) - .data('selection', selection); - } + //TODO: deal with invalid/too-small iasSelections somehow (including thumbnails) + if (iasSelection.width == 0 && iasSelection.height == 0) { + $('#thumb-' + $(img).attr('id') + ' #iasSelection-show-' + iasSelection.id).css('display', 'none'); + } + //TODO: this is dumb, get rid of it. (This just means that very small selections appear on the page + // but don't actually ever get queried.) + if (iasSelection.height * iasSelection.width < 5000) return; + + //TODO: deal with this. + // create button for repeating lassos, only if there are more pages after this + if (this.pageCount > $(img).data('page')) { + var but_id = $(img).attr('id') + '-' + iasSelection.id; + $('body').append(''); + var img_pos = $(img).offset(); + $('button#' + but_id) + .css({ + position: 'absolute', + top: img_pos.top + iasSelection.y1 + iasSelection.height - $('button#' + but_id).height() * 1.5, + left: img_pos.left + iasSelection.x1 + iasSelection.width + 5 + }) + .data('selection', iasSelection); + } - var coords = { - x1: selection.x1 * scale, - x2: selection.x2 * scale, - y1: selection.y1 * scale, - y2: selection.y2 * scale, - page: $(img).data('page') - }; - if(!this.noModalAfterSelect){ - this.doQuery(PDF_ID, [coords]); - } - this.toggleDownloadAllAndClearButtons(); + if(!this.shouldPreviewDataAutomatically){ + selection.queryForData(); + } + Tabula.ui.components['control_panel'].render(); // deal with buttons that need blurred out if there's zero selections, etc. }, - _onSelectCancel: function(img, selection, selectionId) { - $('#thumb-' + $(img).attr('id') + ' #selection-show-' + selectionId).remove(); - $('#' + $(img).attr('id') + '-' + selectionId).remove(); - var but_id = $(img).attr('id') + '-' + selectionId; - $('button#' + but_id).remove(); - this.document_view.ui.control_panel_view.toggleClearAllAndRestorePredetectedTablesButtons(); - this.document_view.ui.control_panel_view.toggleDownloadAllAndClearButtons(); - }, */ + _onSelectCancel: function(img, iasSelection, iasSelectionId) { + // remove repeat lassos button + var but_id = $(img).attr('id') + '-' + iasSelection.id; + $('button#' + but_id).remove(); + + // find and remove the canceled selection from the collection of selections. (triggering remove events). + var selection = Tabula.ui.pdf_document.selections.find(function(selection){ return selection.get('iasId') == iasSelectionId }); + Tabula.ui.pdf_document.selections.remove(selection); + + Tabula.ui.components['control_panel'].render(); // deal with buttons that need blurred out if there's zero selections, etc. + }, rotate_page: function(t) { alert('not implemented'); @@ -357,8 +584,8 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF function () { // delete the deleted page's imgAreaSelect object - imgAreaSelects[page_number-1].remove(); - delete imgAreaSelects[page_number-1]; + this.ui.imgAreaSelects[page_number-1].remove(); + delete this.ui.imgAreaSelects[page_number-1]; // move all the stuff for the following pages' imgAreaSelect objects up. deleted_page_height = $('img.page-image#page-' + page_number).height(); @@ -388,35 +615,10 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF */ // Tabula.SelectionView = Backbone.View.extend({ // multiple per page of the PDF // events: { -// 'click button.repeat-lassos': 'repeat_lassos', +// 'click button.repeat-lassos': 'repeatLassos', // } // page_view: null, //added on create. //TODO: do this. -// repeat_lassos: function(e) { -// /* TODO: make this work. -// // var page_idx = page_view.page_number; -// // var selection_to_clone = this; -// */ -// var page_idx = parseInt($(e.currentTarget).attr('id').split('-')[1]); -// var selection_to_clone = $(e.currentTarget).data('selection'); - -// //remove the button -// $(e.currentTarget).fadeOut(500, function() { $(this).remove(); }); - -// $('#should-preview-data-checkbox').prop('checked', false); -// this.updateShouldPreviewDataAutomaticallyButton(); - -// imgAreaSelects.slice(page_idx).forEach(function(imgAreaSelectAPIObj) { -// if (imgAreaSelectAPIObj === false) return; -// imgAreaSelectAPIObj.cancelSelections(); -// imgAreaSelectAPIObj.createNewSelection(selection_to_clone.x1, selection_to_clone.y1, -// selection_to_clone.x2, selection_to_clone.y2); -// imgAreaSelectAPIObj.setOptions({show: true}); -// imgAreaSelectAPIObj.update(); -// this.showSelectionThumbnail(imgAreaSelectAPIObj.getImg(), -// selection_to_clone); -// }, this); -// }, // }); Tabula.ControlPanelView = Backbone.View.extend({ // only one @@ -425,20 +627,20 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one 'click #clear-all-selections': 'clear_all_selection', 'click #restore-detected-tables': 'restore_detected_tables', 'click #all-data': 'query_all_data', - 'click #repeat-lassos': 'repeat_lassos', + 'click #repeat-lassos': 'repeatLassos', }, - ui: null, //added on create className: 'followyouaroundbar', template: Handlebars.compile($('#templates #control-panel-template').html()), - noModalAfterSelect: !$('#should-preview-data-checkbox').is(':checked'), + shouldPreviewDataAutomatically: !$('#should-preview-data-checkbox').is(':checked'), updateShouldPreviewDataAutomaticallyButton: function(){ - this.noModalAfterSelect = !$('#should-preview-data-checkbox').is(':checked'); + this.ui.options.set('multiselect_mode', !$('#should-preview-data-checkbox').is(':checked')) + this.render(); }, - repeat_lassos: function(){ + repeatFirstPageLassos: function(){ alert('not yet implemented'); return; /* TODO: @@ -450,44 +652,22 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one }, clear_all_selection: function(){ - _(imgAreaSelects).each(function(imgAreaSelectAPIObj){ + _(this.ui.imgAreaSelects).each(function(imgAreaSelectAPIObj){ if (imgAreaSelectAPIObj === false) return; imgAreaSelectAPIObj.cancelSelections(); }); }, restore_detected_tables: function(){ - for(var imageIndex=0; imageIndex < imgAreaSelects.length; imageIndex++){ + for(var imageIndex=0; imageIndex < this.ui.imgAreaSelects.length; imageIndex++){ var pageIndex = imageIndex + 1; this.drawDetectedTables( $('img#page-' + pageIndex), tableGuesses ); } this.toggleClearAllAndRestorePredetectedTablesButtons(); }, - toggleClearAllAndRestorePredetectedTablesButtons: function(){ - // if tables weren't autodetected, don't tease the user with an autodetect button that won't work. - var numOfSelectionsOnPage = this.ui.total_selections(); - if(!_(tableGuesses).isEmpty()){ - if(numOfSelectionsOnPage <= 0){ - $("#clear-all-selections").hide(); - $("#restore-detected-tables").show(); - }else{ - $("#clear-all-selections").show(); - $("#restore-detected-tables").hide(); - } - } - }, - - toggleDownloadAllAndClearButtons: function() { - if (this.ui.total_selections() > 0) { - $('#all-data, #clear-all-selections').removeAttr('disabled'); - } - else { - $('#all-data, #clear-all-selections').attr('disabled', 'disabled'); - } - }, - - initialize: function(){ + initialize: function(stuff){ + this.ui = stuff.ui _.bindAll(this, 'updateShouldPreviewDataAutomaticallyButton', 'query_all_data', 'render'); }, @@ -496,8 +676,8 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one * then loop over all of them. */ query_all_data : function(){ - all_coords = []; - imgAreaSelects.forEach(function(imgAreaSelectAPIObj){ + list_of_all_coords = []; + this.ui.imgAreaSelects.forEach(function(imgAreaSelectAPIObj){ if (imgAreaSelectAPIObj === false) return; var thumb_width = imgAreaSelectAPIObj.getImg().width(); @@ -517,21 +697,25 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one y2: selection.y2 * scale, page: imgAreaSelectAPIObj.getImg().data('page') } - all_coords.push(new_coord); + list_of_all_coords.push(new_coord); }); }); - this.doQuery(PDF_ID, all_coords); + this.doQuery(PDF_ID, list_of_all_coords); }, - render: function(){ - //make the "follow you around bar" actually follow you around. ("sticky nav") + // makes the "follow you around bar" actually follow you around. ("sticky nav") $('.followyouaroundbar').affix({top: 70 }); + var numOfSelectionsOnPage = this.ui.totalSelections(); + console.log('numOfSelectionsOnPage', numOfSelectionsOnPage); this.$el.html(this.template({ + 'if_multiselect_checked': this.ui.options.get('multiselect_mode') ? '' : 'checked="checked"', + 'disable_clear_all_selections': numOfSelectionsOnPage <= 0 ? 'disabled="disabled"' : '' , + 'disable_download_all': numOfSelectionsOnPage <= 0 ? 'disabled="disabled"' : '', + 'show_restore_detected-tables': this.ui.hasPredetectedTables() && numOfSelectionsOnPage <= 0, })); - return this; }, }); @@ -539,6 +723,29 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one Tabula.SidebarView = Backbone.View.extend({ // only one tagName: 'ul', className: 'thumbnail-list', + thumbnail_views: [], + ui: null, // defined on initialize + initialize: function(stuff){ + this.ui = stuff.ui; + _.bindAll(this, 'addThumbnail', 'removeThumbnail', 'changeThumbnail') + //TODO: the methods for handling these + this.listenTo(this.ui.pdf_document.selections, 'all', this.render); + this.listenTo(this.ui.pdf_document.selections, 'add', this.addThumbnail); // render a thumbnail selection + this.listenTo(this.ui.pdf_document.selections, 'change', this.changeThumbnail); // render a thumbnail selection + this.listenTo(this.ui.pdf_document.selections, 'remove', this.removeThumbnail); // remove a thumbnail selection + //TODO: this.listenTo(this.ui.pdf_document.selections, 'reset', this.addAll); // remove all, then render new ones + }, + addThumbnail: function (new_selection){ + //TODO: sometimes thumbnails get drawn on the wrong page!! + console.log(new_selection, new_selection.get('page_number')); + this.thumbnail_views[new_selection.get('page_number') - 1].createSelectionThumbnail(new_selection) + }, + changeThumbnail: function (selection){ + this.thumbnail_views[selection.get('page_number') - 1].changeSelectionThumbnail(selection) + }, + removeThumbnail: function (selection){ + this.thumbnail_views[selection.get('page_number') - 1].removeSelectionThumbnail(selection) + } }); Tabula.ThumbnailView = Backbone.View.extend({ // one per page @@ -553,13 +760,11 @@ Tabula.ThumbnailView = Backbone.View.extend({ // one per page }, // initialize: function(){ - // this.$img = this.$el.children('img'); - // this.img = this.$img[0]; // }, template: Handlebars.compile($('#templates #thumbnail-template').html()), initialize: function(){ - _.bindAll(this, 'render'); + _.bindAll(this, 'render', 'createSelectionThumbnail', 'changeSelectionThumbnail', 'removeSelectionThumbnail'); }, render: function(){ @@ -567,33 +772,49 @@ Tabula.ThumbnailView = Backbone.View.extend({ // one per page return; } - //TODO: use a real templating language. - this.$el.attr('data-page', this.model.get('number')) - .attr('data-original-width', this.model.get('width')) - .attr('data-original-height', this.model.get('height')) - .attr('data-rotation', this.model.get('rotation')); + // TODO: redundant? + // this.$el.attr('data-page', this.model.get('number')) + // .attr('data-original-width', this.model.get('width')) + // .attr('data-original-height', this.model.get('height')) + // .attr('data-rotation', this.model.get('rotation')); this.$el.html(this.template({ 'number': this.model.get('number'), 'image_url': this.model.get('image_url') })); - if(this.model.number == 1){ + if(this.model.get('number') == 1){ this.$el.find('img').attr('data-position', "right") .attr('data-intro', "Click a thumbnail to skip directly to that page."); } + // stash some selectors (which don't exist at init) + this.$img = this.$el.find('img'); + this.img = this.$img[0]; + return this; }, - showSelectionThumbnail: function(selection) { - $el.append( $('
').css('display', 'block') ); - var sshow = $('#thumb-' + $img.attr('id') + ' #selection-show-' + selection.id); - var thumbScale = $('#thumb-' + $img.attr('id') + ' img').width() / $img.width(); - $(sshow).css('top', selection.y1 * thumbScale + 'px') - .css('left', selection.x1 * thumbScale + 'px') - .css('width', ((selection.x2 - selection.x1) * thumbScale) + 'px') - .css('height', ((selection.y2 - selection.y1) * thumbScale) + 'px'); + createSelectionThumbnail: function(selection) { + var $sshow = $('
').css('display', 'block'); + this.$el.append( $sshow ); + this.changeSelectionThumbnail(selection); }, + + changeSelectionThumbnail: function(selection){ + var $sshow = this.$el.find('#selection-show-' + selection.cid); + var thumbScale = this.$img.width() / selection.get('imageWidth'); + + $sshow.css('top', selection.get('y1') * thumbScale + 'px') + .css('left', selection.get('x1') * thumbScale + 'px') + .css('width', ((selection.get('x2') - selection.get('x1')) * thumbScale) + 'px') + .css('height', ((selection.get('y2') - selection.get('y1')) * thumbScale) + 'px'); + }, + + removeSelectionThumbnail: function(selection){ + console.log('remove', selection); + var $sshow = this.$el.find('#selection-show-' + selection.cid); + $sshow.remove(); + } }) Tabula.UI = Backbone.View.extend({ @@ -601,50 +822,74 @@ Tabula.UI = Backbone.View.extend({ events : { 'click a.tooltip-modal': 'tooltip', - 'hide #data-modal' : function(){ clip.unglue('#copy-csv-to-clipboard'); }, 'click a#help-start': function(){ Tabula.tour.ended ? Tabula.tour.restart(true) : Tabula.tour.start(true); }, }, - $loading: $('#loading'), colors: ['#f00', '#0f0', '#00f', '#ffff00', '#FF00FF'], lastQuery: [{}], - lastSelection: undefined, pageCount: undefined, components: {}, + imgAreaSelects: [], - model: Document, + global_options: null, + + model: Tabula.Document, + + //TODO: add the little Help box above the pages. initialize: function(){ - _.bindAll(this, 'render', 'redoQuery'); + _.bindAll(this, 'render', 'hasPredetectedTables', 'addOne', 'addAll', 'totalSelections', + 'createDataView','trashDataView'); - this.pdf_document = new Document({ + this.pdf_document = new Tabula.Document({ pdf_id: PDF_ID, }); + this.options = new Tabula.Options(); + + this.createTour(); + this.listenTo(this.pdf_document.page_collection, 'all', this.render); this.listenTo(this.pdf_document.page_collection, 'add', this.addOne); this.listenTo(this.pdf_document.page_collection, 'reset', this.addAll); - this.components['document_view'] = new Tabula.DocumentView({ui: this}); //creates page_views this.components['control_panel'] = new Tabula.ControlPanelView({ui: this}); - this.components['sidebar_view'] = new Tabula.SidebarView({ui: this}); //TODO: create thumbnail_views - + this.components['sidebar_view'] = new Tabula.SidebarView({ui: this}); + this.pdf_document.page_collection.fetch(); }, + createDataView: function(){ + this.components['data_view'] = new Tabula.DataView({ui: this, model: Tabula.ui.query}); + }, + + trashDataView: function(){ + this.components['data_view'] = null; + }, + + hasPredetectedTables: function(){ + return false; !_(tableGuesses).isEmpty() + }, + addOne: function(page) { - var page_view = new Tabula.PageView({model: page}); - var thumbnail_view = new Tabula.ThumbnailView({model: page}) + var page_view = new Tabula.PageView({model: page, collection: this.pdf_document.page_collection}); + var thumbnail_view = new Tabula.ThumbnailView({model: page, collection: this.pdf_document.page_collection}) + this.components['document_view'].page_views.push(page_view); + this.components['sidebar_view'].thumbnail_views.push(thumbnail_view); this.components['document_view'].$el.append(page_view.render().el); // is this a good idea? TODO: this.components['sidebar_view'].$el.append(thumbnail_view.render().el); // is this a good idea? TODO: }, addAll: function() { - Pages.each(this.addOne, this); + this.pdf_document.page_collection.each(this.addOne, this); }, - total_selections: function(){ - return _.reduce(imgAreaSelects, function(memo, s){ + totalSelections: function(){ + if(_.isUndefined(this.imgAreaSelects)){ + return 0; + } + + return _.reduce(this.imgAreaSelects, function(memo, s){ if(s){ return memo + s.getSelections().length; }else{ @@ -658,13 +903,16 @@ Tabula.UI = Backbone.View.extend({ $('#control-panel-container').append(this.components['control_panel'].render().el); $('.sidebar-nav.well').append(this.components['sidebar_view'].render().el); + $('.has-tooltip').tooltip(); + + //TODO: render, but hide, the dataview too. + query_parameters = {}; this.pageCount = this.pdf_document.page_collection.size(); /* TODO: do this stuff somewhere * this.setAdvancedOptionsShown(); - * this.updateExtractionMethodButton(); */ // render out the components, as necessary @@ -712,13 +960,6 @@ Tabula.UI = Backbone.View.extend({ ]); }, - redoQuery: function(options) { - //TODO: stash lastCoords, rather than stashing lastQuery and then parsing it. - this.doQuery(PDF_ID, - JSON.parse(this.lastQuery["coords"]), - options); - }, - debugRulings: function(image, render, clean, show_intersections) { image = $(image); var imagePos = image.offset(); @@ -828,18 +1069,18 @@ Tabula.UI = Backbone.View.extend({ var scale = thumb_width / (Math.abs(pdf_rotation) == 90 ? pdf_height : pdf_width); - var coords = JSON.parse(this.lastQuery.coords); + var list_of_coords = JSON.parse(this.lastQuery.coords); - this.redoQuery({ + Tabula.ui.query.doQuery({ success: _.bind(function(data) { var colors = this.colors; - console.log(coords); + console.log(list_of_coords); $.each(data[0].vertical_separators, function(i, vert) { newCanvas.drawLine({ strokeStyle: colors[i % colors.length], strokeWidth: 1, - x1: vert * scale, y1: coords[0].y1 * scale, - x2: vert * scale, y2: coords[0].y2 * scale + x1: vert * scale, y1: list_of_coords[0].y1 * scale, + x2: vert * scale, y2: list_of_coords[0].y2 * scale }); }); }, this)}); @@ -855,88 +1096,14 @@ Tabula.UI = Backbone.View.extend({ return this._debugRectangularShapes(image, '/debug/' + PDF_ID + '/text_chunks'); }, - doQuery: function(pdf_id, coords, options) { - this.lastQuery = { - coords: JSON.stringify(coords) , - 'extraction_method': this.extractionMethod - }; - - $('#data-modal').modal(); - this.setAdvancedOptionsShown(); - $('#switch-method').prop('disabled', true); - $('#data-modal .modal-body').prepend(this.$loading.show()); - $('#data-modal .modal-body table').css('visibility', 'hidden'); - $('#data-modal .modal-body').css('overflow', 'hidden'); - - $.ajax({ - type: 'POST', - url: '/pdf/' + pdf_id + '/data', - data: this.lastQuery, - success: _.bind(function(resp) { - this.extractionMethod = resp[0]["extraction_method"]; - this.updateExtractionMethodButton(); - - //$('#data-modal').find('#loading').hide(); - this.$loading = this.$loading.detach(); - console.log("resp", resp); - console.log("Extraction method: ", this.extractionMethod); - - this.$loading = this.$loading.detach(); - $('#switch-method').prop('disabled', false); - $('#data-modal .modal-body table').css('visibility', 'visible'); - $('#data-modal .modal-body').css('overflow', 'auto'); - - - var tableHTML = ''; - $.each(_.pluck(resp, 'data'), function(i, rows) { - $.each(rows, function(j, row) { - tableHTML += ''; - }); - }); - tableHTML += '
' + _.pluck(row, 'text').join('') + '
'; - $('#data-modal .modal-body').html(tableHTML); - - $('#download-form').attr("action", '/pdf/' + pdf_id + '/data?format=csv'); - - $('div#hidden-fields').empty(); - _(_(this.lastQuery).pairs()).each(function(key_val){ - // - var new_hidden_field = $(""); - new_hidden_field.attr("name", key_val[0]); - new_hidden_field.attr("value", key_val[1]); - $('div#hidden-fields').append(new_hidden_field); - }); - $('#download-data').click(function(){ $('#download-form').attr("action", '/pdf/' + pdf_id + '/data?format=csv'); }); - $('#download-csv').click(function(){ $('#download-form').attr("action", '/pdf/' + pdf_id + '/data?format=csv'); }); - $('#download-tsv').click(function(){ $('#download-form').attr("action", '/pdf/' + pdf_id + '/data?format=tsv'); }); - $('#download-script').click(function(){ $('#download-form').attr("action", '/pdf/' + pdf_id + '/data?format=script'); }); - $('#download-bbox').click(function(){ $('#download-form').attr("action", '/pdf/' + pdf_id + '/data?format=bbox'); }); - - clip.glue('#copy-csv-to-clipboard'); - - - if (options !== undefined && _.isFunction(options.success)) - options.success(resp); - - }, this), - error: _.bind(function(xhr, status, error) { - $('#data-modal').modal('hide'); - $('#modal-error textarea').html(xhr.responseText); - $('#modal-error').modal(); - if (options !== undefined && _.isFunction(options.error)) - options.error(resp); - - }) - }); - }, - + // doQuery was here drawDetectedTables: function($img, tableGuesses){ alert("not yet reimplemented"); return; //TODO: //$img = $(e); var imageIndex = $img.data('page'); arrayIndex = imageIndex - 1; - var imgAreaSelectAPIObj = imgAreaSelects[arrayIndex]; + var imgAreaSelectAPIObj = this.ui.imgAreaSelects[arrayIndex]; var thumb_width = $img.width(); var thumb_height = $img.height(); @@ -962,7 +1129,7 @@ Tabula.UI = Backbone.View.extend({ //create a red box for this selection. if(selection){ //selection is undefined if it overlaps an existing selection. - this.showSelectionThumbnail($img, selection); + this.createSelectionThumbnail($img, selection); //TODO: api changed. } }); diff --git a/webapp/static/js/templates/fixie-sidebar.jst b/webapp/static/js/templates/fixie-sidebar.jst deleted file mode 100644 index 9dc04711..00000000 --- a/webapp/static/js/templates/fixie-sidebar.jst +++ /dev/null @@ -1,8 +0,0 @@ - - - - -
-
- Help -
diff --git a/webapp/static/js/templates/page.jst b/webapp/static/js/templates/page.jst deleted file mode 100644 index 0ebadf8c..00000000 --- a/webapp/static/js/templates/page.jst +++ /dev/null @@ -1,13 +0,0 @@ -
- <% pages.each_with_index do |p, i| %> - <% next if p['deleted'] %> -
- - data-position="right" data-intro="Click and drag to select each table in your document. Once you've selected it, a window to preview your data will appear, along with options to download it as a spreadsheet." - <% end %> - > -
-
- <% end %> -
diff --git a/webapp/static/js/templates/thumbnail-sidebar.jst b/webapp/static/js/templates/thumbnail-sidebar.jst deleted file mode 100644 index 32f7bc5f..00000000 --- a/webapp/static/js/templates/thumbnail-sidebar.jst +++ /dev/null @@ -1,14 +0,0 @@ -
    - <% pages.each_with_index do |p, i| %> - <% next if p['deleted'] %> -
  • data-position="right" data-intro="Click a thumbnail to skip directly to that page." <% end %>> - - - - - -
    Page <%= i + 1 %> -
  • - <% end %> -
diff --git a/webapp/static/swf/ZeroClipboard.swf b/webapp/static/swf/ZeroClipboard.swf index 8f3cc680331316e0d713512cab26394a7b2f6f59..d4e2561b366e131d3bae303acce90a137a4956e1 100644 GIT binary patch literal 4036 zcmV;#4?FNfS5ppe82|uy+Ko8*dmG1fGkXno36KCtQ4~c|ON%BYdPzcuWs8(#QzRjg zmIR51E{i4>z$~%SVi($7@UW~{Q6eW!(z{mg7ET*CZPcds-K0&AAKLHh2a=8WwLkW! z{uk<-UGS3Ae1SMSZ{EE3=H0Wa6(sx*LXDpx)P~V`;s8SE!{&d-2%T{Y#_;rbT3snw zwl@r`vwcP1FAon5EiW$*E}s~5+{K||r%#_AN*y0MetZBZ2E0|<*H;E??{MF_K)^Wl z@~&C-O~+Q*TF*HZ|7>4hU1}k}Ewo&5tw3ZUKSV8BqPFi19UD9bf(rRz!*NTxe@-u# zEi;8)-=gP63r>!zX^XC?C)Lp%{(w3pVZ?}njDq!Bt!bFrSIm`5l)IC?R@7d0Bc*G8t(d1pK`_5j9 zEgajobbGO)FVb@pSAxCmb)ci}({oc5OFjOErXEinI~l6MuxEx|@^&2q0X&Ds#gs$6y|9-UyY zl*!$td0(3GUDIBSq$_rwsV+Vw-x> zW^x@@2p*n~D+JzP-*e>dJo))%MlgvQ3=5V@d z>83qPSNx1^ttLa<7R$Kq>yewDH|Lv{M~b?~5OJ6p?KWehr-OOs`!sZ7!Z0mx(yWta zF2An3p6L0IKVo2Er!A_gHQm*3rjvcG}V65VLj@#_g zlCwnj5Gt0V7pOaF8;$~Qh3tSsz3xTJ$?4X>`w+S9sD!mSkaNw#B5m-NjWFnwh3Egsnx#PWeM+a`}1Mkn*=hVRIh2c8`cLwMCdjy|) zexvVJSPo@ebJ4Uttch?Eeh`s5YDBrE zs-~eH@mI^#G1NT;sGdCwlD1ClQ+s>WA4N9Es_G?LrquyG+RzbBLU$q>#y68(JQ#18gci*vlASVH$1+{*Is*~fxYM=U=y30Eqs_Ig|;@Y5f zVAf9hj40g0qcM;AJJm13X09$=nn=%zI$JgdJ2TKJ>1C%t<%yf4W3vnC$ywRk!bJm;YZajPSB3Kv zGqdD=xQHyedbw!kJvm3=n6znGO3>qOm<7Ms5P%n{xmffg1y^652D{%wRzb;8C^4FM%Buo<$hrZofiVmAp}}`9Im^@?(>=r9q)=r73H6BkIGWVo)iV#%Vpvw{8*%oDP*COwDAj$YFyvjaA8__M%_p zG&M}nL7Vz9$Xas|ifGoEo4%yLn5OqD(5D;jf;QpH(=a~59UT!DJZc$Is2Q~K6<;X& zC5v}`BRZy+^_&U0Xi|@JAnPHnRJ2UHvLej{EjQiAC zyL^7tr{N1v{Z69ZJ>MzI+wAd0cH`>SHbTt9o0c2jUZ;asUaw(x;wBVPX8Q8zEP?ac zi47NLFJG$TV$1CMoSRLv6TSHK_bmI+n}ZXh^H#2679iPS{IIFS-WOcJq~ zh%H3iN5obl?k8d!i5wu2b`m*AB1wWf2<{}fi{L{rzArC>@D&pK3O)$H zGKqbR<*#D^zX9W?VEitjK2l=?_mCW7~g~OeHi}$;~#@?%}()J_Oiz z0uH_l*b_c7FcuKvLq`=I0IYE4c{p+F0aOvJ zC>YmgQDp4!XIt<*fKjX+qsD_6HFaPV@5Ct4D9SQw6}y-*hcMc& zV${}+(ScVmYCkN>4d@W)(SVw{o&?|WYC_o8!SyEi&c1}uB`8=HFiPSm=y(t}3W!tj zVF7`wF^r87t4xLOVuV(Ez^Vm!|{OWsHL(;k`V;RIEsU1wd_%&zh=ZUuW>-b z^Pp}dtF84u(+6NJ2%Hjq3=(hj|1d?d`baEcHt{8a;_k;Kku4%6Xl3AToPgJ98e`EHQloCuK&!7EkIz zPVN?RG7xfjmynYnM0+d!I%aBRPZ@7yPXS)ayb0WWB|e}zt)-v!C*&<5r+c3zMJA%R z#MiV7+QllMxvDl<)uyW2J5}vcRl8i(u2i*5Rl8c%u2r?^sx}i~W~%pr zs;*F6OD7O>QEk2vB=J*?SdZ1_qfF{l6zdH&qaodnk{#fEzzLas7%4xMjD13MI(rl&>6&YNJ9dT&LYtJ94iJ<k#eHA=7 zo1Q~e{pz~*HLPE+ts7U@L(63zVsWjOTFVN?jhfK{rNzLTJK(Jy@b(V)?hbgTW+ZpO z01KW_IH>Q#wG>5JF_oQ9W#3C>7gE`5D$B#)P1G6uiZbZXfH5zGogm6_g4hstg0;*6 z45%0{h>FXq`Z-kfS_#*(x{>RB)}Mq{cog6~IAedkKA0oe%+SxE_n}_B73j5Jg_vRJ z6Z_*W5VX{K>Ne`%>`%Q0()#p5XN0fSj8>2X(LY)<_CM9zF5r=im#Bdkq31@UjT{!l z1n9M!Y6;A2;z7M6Y_J@GZlBfH)}b;$@3;dcyKRSW7O0;_MLU^EEi5gNsv^e{G5P_K4GP zKp%%bBX4BQb;yUi>3g{PWHTr9;Oe?zMM0Ad?VGr|`2!&*0Ak;NzIZWTnzyyj@LE;- zU0mI$YTw4yDI06QhchK+OcJ8WH`P+}4vVIE6L%J)fNtK2TnoqX!gWZbPqg315FDPR zCz$;`kwb~?mZ!S7NO6{UwZzN z%3j*scp8dYXXmsEkbm6}&a7wnJ*Y=aS2(B~?T_)Zr^T)?D-W@(+|P4i+-pV`$PRVU zyI`k{mjrPo2wYJ7LG&Ky#rPps#bQqAda}6zjva&*1?1WyeKMfv3l7I>xJU=5|>>L zu8(qNg*LQeYc>dbu+qT)^>Hda1#!vOfMky!W7X0i=G9+Y+lj zdcf|dZRsBv4>Mmt;Sr&OBU?9Cc`5mNP?P~x_Cw%DcZM&wWXryOb<2dGimZ9-z=HE4+H$%$G3Z<{?^2NX{Ep5Be@^H@g)0 zazGRu?`|K@?LFRQUgb|uU#G^$_?!4MOpX7-pK{;gsKM`e`3Xk+19XrTTO5k*BOF#6 z*I8_Y%<9;wwoU=6ZJ?IYh67{jr2uNJ!5hgxVeOysc@F0ES1|V^4sVYdhRo=~z$8G4 q0(=NwVMQruv>;C1I`OOZ6Zy{KVOI9d?ElO6{y#btH2OaX)bM*lY1Xp< literal 1536 zcmV+b2LJg(S5ppd2><|i+I>~)ZrjKep2OQADan@Ytl~IvqHMhJu2-^T<+5wXQJt0S zwKq<*h~rHWAgD&<&|*W80y*-fe;O>%=P3H8FVF|*0~9Fa1Z{s7*dL2NMcpB(TK0C) zfpq4ZZ_YV9m*Mc3C_fM~@kc^tfi$PC5kh`D^A{jwJ+z&*7tO8WanJLkHL-fU*yX&x zR;e5v9W5W-TMoj5%F62MYNcAM)M{m+QI1Z0PLIoebZ4=iIB2rS3f(?;1HTxrX(t%) z$BT<2ueNpWv_A;F#HDRjn8$j|=TT*4c||y~tu-eIdz9B{zwfygjonp_%TYJ5-X76I zR(3oZb=NCn#n^_s+++18cFgUcHK<>Gyh`UeAg!0MaNlfCnG z5+miT-egvBFfybz?Js= zbUD*H1I`0qw}jS9cFgr2}d+u-8D4ggd;;g6bB~w=H zoj+`16z?%-p-;W3%@Nq~#q3av6)we=L`AvP)x}BZdyF%M3tPpao@eypY+Hvc)KfIz zDb*DH#RTp5)rgBGN-uX+>bVC#`oW&CWkwXsXMxT1J!TC=#wW0p5l(}nX3(RqZ|(-v zX5o(S1WFhL-1N@ZW}n7g5%ET5TBX=ePCRa05B(sd{jO_8Mu#1^{+8=`W)jIO*XG?! z0=Joa(B--v(xVrmRU*KCm+I8E8(r74)yD2l<7r+jlYZ{HqLe-h&1bFsz0F^>zT13Z zjNtyu5!r0LdakE%|JzLLNF>yu)>|bG-bgBcNW&;?(sv?$aa^+v+NT}YbGgeRJWNTA zdi*InxjlLx$M52ky?d2fwfeBq8Mq#I{Y=`<0e8LV%B6F(mY$=9%Vaa1)5F;a#GU1e zq;6eSO3nhcxO>P_#q0ma$&HIIz*n5e2{E2q% zlH;lW38V1y(N7nkAQ#M8LnVdj!bev@CHj-XS98W(W3D;3Ikz?UwKSoEq#I^N$>t^| z^Hb{x5=sh!il8ANf{tLIoIy2(@g=m^V zh)zs#3j$<-U>1m;17b`-ObQAWAX#Cp8}hLFb!;b@=6rjyZMO2zo+@1}&A!Q;maetU zeb~xKt!Y^(NvcrFC%f0h?p6j;ULNx|F7nElpTEefWB%qvUK4!w?=Y;sTKZdcxI@}A zr6Ro|nbpKGdpyyIdfaH;9P$v_h9%&|3)WWW9S%n=?x(l>AaB6%`&uZ zL4p>@QhUZJivR7_$BLjTK<~U+5TLaXYqTZjlMQIyl~TO8A)Vt-H{^5t*@kkCmo`*^ zMGE<$lWTn*qsgI@P0`fQnMl#q(xc&4h9v1hiGWN{LBo7GtQLP~DW%8lva`Go@8o}+ zPEQl^;nb extraction_method_requested} - ) + tables = coords.map do |coord_set| + extraction_method_requested = ["guess", "spreadsheet", "original"].include?(coord_set['extraction_method']) ? coord_set['extraction_method'] : "guess" + coords_method_key = extraction_method_requested + coord_set.to_s + unless CACHE.has_key?(coords_method_key) + CACHE[coords_method_key] = Tabula.extract_table(pdf_path, + coord_set['page'].to_i, + [coord_set['y1'].to_f, + coord_set['x1'].to_f, + coord_set['y2'].to_f, + coord_set['x2'].to_f], + {:extraction_method => extraction_method_requested} + ) end + CACHE[coords_method_key] end + tables = tables.flatten(1) + case req.params['format'] when 'csv' res['Content-Type'] = 'text/csv' res['Content-Disposition'] = "attachment; filename=\"tabula-#{file_id}.csv\"" - tables = CACHE[coords_method_key].flatten(1) tables.each do |table| res.write table.to_csv end when 'tsv' res['Content-Type'] = 'text/tab-separated-values' res['Content-Disposition'] = "attachment; filename=\"tabula-#{file_id}.tsv\"" - tables = CACHE[coords_method_key].flatten(1) tables.each do |table| res.write table.to_tsv end @@ -274,7 +271,7 @@ def has_key?(k) res.write coords.to_json else res['Content-Type'] = 'application/json' - res.write CACHE[coords_method_key].flatten(1).to_json + res.write tables.to_json end end end diff --git a/webapp/views/pdf_view.html.erb b/webapp/views/pdf_view.html.erb index 58a95268..4d905668 100644 --- a/webapp/views/pdf_view.html.erb +++ b/webapp/views/pdf_view.html.erb @@ -13,7 +13,7 @@
-
+
@@ -38,63 +38,21 @@
Loading...
-
- -
- -
- -
+ + diff --git a/webapp/static/js/pdf_view.js b/webapp/static/js/pdf_view.js index ba9636b0..3e8aa854 100644 --- a/webapp/static/js/pdf_view.js +++ b/webapp/static/js/pdf_view.js @@ -1,4 +1,4 @@ -Tabula = {}; +Tabula = Tabula || {}; var clip = null; @@ -50,13 +50,13 @@ Tabula.Selection = Backbone.Model.extend({ queryForData: function(){ var selection_coords = this.toCoords(); - Tabula.ui.query = new Tabula.Query({list_of_coords: [selection_coords], extraction_method: this.get('extractionMethod')}); - Tabula.ui.createDataView(); - Tabula.ui.query.doQuery(); + Tabula.pdf_view.query = new Tabula.Query({list_of_coords: [selection_coords], extraction_method: this.get('extractionMethod')}); + Tabula.pdf_view.createDataView(); + Tabula.pdf_view.query.doQuery(); }, toCoords: function(){ - var page = Tabula.ui.pdf_document.page_collection.at(this.get('page_number') - 1); + var page = Tabula.pdf_view.pdf_document.page_collection.at(this.get('page_number') - 1); var imageWidth = this.get('imageWidth'); var original_pdf_width = page.get('width'); @@ -78,9 +78,9 @@ Tabula.Selection = Backbone.Model.extend({ }, repeatLassos: function() { - Tabula.ui.pdf_document.page_collection.each(_.bind(function(page){ + Tabula.pdf_view.pdf_document.page_collection.each(_.bind(function(page){ if(this.get('page_number') < page.get('number')){ // for each page after this one, - imgAreaSelectAPIObj = Tabula.ui.imgAreaSelects[page.get('number')] + imgAreaSelectAPIObj = Tabula.pdf_view.imgAreaSelects[page.get('number')] if (imgAreaSelectAPIObj === false) return; imgAreaSelectAPIObj.cancelSelections(); // notify the imgAreaSelect of the new selection @@ -145,7 +145,7 @@ Tabula.Selections = Backbone.Collection.extend({ var tables = []; selections = _(response).map(_.bind(function(page_tables, listIndex){ var pageIndex = listIndex + 1; - var pageView = Tabula.ui.components['document_view'].page_views[pageIndex]; + var pageView = Tabula.pdf_view.components['document_view'].page_views[pageIndex]; var page = pageView.model; var original_pdf_width = page.get('width'); @@ -197,7 +197,7 @@ Tabula.Selections = Backbone.Collection.extend({ new_selection_args = _.extend({'page_number': pageNumber, 'imageWidth': imageWidth, 'id': selectionId, - 'extractionMethod': Tabula.ui.options.extraction_method, + 'extractionMethod': Tabula.pdf_view.options.extraction_method, 'pdf_document': this.pdf_document}, _.omit(iasSelection, 'id', '$el')) selection = new Tabula.Selection(new_selection_args); @@ -213,8 +213,8 @@ Tabula.Query = Backbone.Model.extend({ //has selections, data //pertains to DataView - // on modal exit, destroy this.ui.query - // on selection end or download all button, create this.ui.query + // on modal exit, destroy this.pdf_view.query + // on selection end or download all button, create this.pdf_view.query // in the modal, modify and requery. initialize: function(){ @@ -225,7 +225,7 @@ Tabula.Query = Backbone.Model.extend({ doQuery: function(options) { this.query_data = { 'coords': JSON.stringify(this.get('list_of_coords')), - // ignored by backend 'extraction_method': Tabula.ui.options.get('extraction_method') + // ignored by backend 'extraction_method': Tabula.pdf_view.options.get('extraction_method') // because each element of list_of_coords has its own extraction_method key/value } @@ -243,7 +243,7 @@ Tabula.Query = Backbone.Model.extend({ _(_.zip(this.get('list_of_coords'), resp)).each(function(stuff, i){ var coord_set = stuff[0]; var resp_item = stuff[1]; - Tabula.ui.pdf_document.selections.get(coord_set.selection_id). + Tabula.pdf_view.pdf_document.selections.get(coord_set.selection_id). set('extraction_method', resp_item["extraction_method"]); coord_set["extraction_method"] = resp_item["extraction_method"]; }); @@ -251,13 +251,13 @@ Tabula.Query = Backbone.Model.extend({ this.trigger("tabula:query-success"); if (options !== undefined && _.isFunction(options.success)){ - Tabula.ui.options.success(resp); + Tabula.pdf_view.options.success(resp); } }, this), error: _.bind(function(xhr, status, error) { console.log("error!"); - Tabula.ui.components['data_view'].hideAndTrash(); + Tabula.pdf_view.components['data_view'].hideAndTrash(); $('#modal-error textarea').html(xhr.responseText); $('#modal-error').modal('show'); if (options !== undefined && _.isFunction(options.error)) @@ -273,7 +273,7 @@ Tabula.Query = Backbone.Model.extend({ Tabula.DataView = Backbone.View.extend({ //one per query object. el: '#data-modal', $loading: $('#loading'), - template: Handlebars.compile($('#templates #modal-footer-template').html()), + template: _.template($('#templates #modal-footer-template').html()), events: { 'click .download-dropdown': 'dropDownOrUp', 'click .extraction-method-btn:not(.active)': 'queryWithToggledExtractionMethod', @@ -283,12 +283,12 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. //N.B.: Download button (and format-specific download buttons) are an HTML form. //TODO: handle flash clipboard thingy here. }, - ui: null, //added on create + pdf_view: null, //added on create extractionMethod: "guess", initialize: function(stuff){ _.bindAll(this, 'render', 'renderLoading', 'renderFooter', 'renderTable', 'toggleAdvancedOptions', 'dropDownOrUp', 'queryWithToggledExtractionMethod', 'handleHidden', 'trash', 'hideAndTrash', 'setFormAction'); - this.ui = stuff.ui; + this.pdf_view = stuff.pdf_view; this.listenTo(this.model, 'tabula:query-start', this.render); this.listenTo(this.model, 'tabula:query-success', this.render); this.$modalBody = this.$el.find('.modal-body'); @@ -314,7 +314,7 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. }, trash: function(e){ - Tabula.ui.trashDataView(); + Tabula.pdf_view.trashDataView(); return this; }, @@ -349,15 +349,15 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. extractionMethodDisabled: _.isNull(this.model.data) || uniq_extraction_methods.length > 1 ? 'disabled="disabled"' : '', pdf_id: PDF_ID, list_of_coords: JSON.stringify(this.model.get('list_of_coords')), - copyDisabled: Tabula.ui.flash_borked ? 'disabled="disabled" data-toggle="tooltip" title="'+Tabula.ui.flash_borken_message+'"' : '', + copyDisabled: Tabula.pdf_view.flash_borked ? 'disabled="disabled" data-toggle="tooltip" title="'+Tabula.pdf_view.flash_borken_message+'"' : '', } - //on create, show/hide advanced options area as necessary from this.ui.options - if(this.ui.options.get('show_advanced_options')){ + //on create, show/hide advanced options area as necessary from this.pdf_view.options + if(this.pdf_view.options.get('show_advanced_options')){ this.$el.addClass("advanced-options-shown"); } - if (Tabula.ui.flash_borked){ + if (Tabula.pdf_view.flash_borked){ this.$el.find('#copy-csv-to-clipboard').addClass('has-tooltip'); } @@ -384,32 +384,32 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. tableHTML += ''; this.$modalBody.html(tableHTML); - if(!Tabula.ui.client){ + if(!Tabula.pdf_view.client){ try{ - Tabula.ui.client = new ZeroClipboard(); + Tabula.pdf_view.client = new ZeroClipboard(); }catch(e){ this.$el.find('#copy-csv-to-clipboard').hide(); } } - Tabula.ui.client.on( 'ready', _.bind(function(event) { - Tabula.ui.client.clip( this.$el.find("#copy-csv-to-clipboard") ); + Tabula.pdf_view.client.on( 'ready', _.bind(function(event) { + Tabula.pdf_view.client.clip( this.$el.find("#copy-csv-to-clipboard") ); - Tabula.ui.client.on( 'copy', _.bind(function(event) { + Tabula.pdf_view.client.on( 'copy', _.bind(function(event) { var clipboard = event.clipboardData; var tableData = this.$el.find('.modal-body table').table2CSV({delivery: null}) clipboard.setData( 'text/plain', tableData ); }, this) ); - Tabula.ui.client.on( 'aftercopy', function(event) { + Tabula.pdf_view.client.on( 'aftercopy', function(event) { $('#data-modal #copy-csv-to-clipboard').css('display', 'inline').delay(900).fadeOut('slow'); } ); }, this) ); - Tabula.ui.client.on( 'error', _.bind(function(event) { + Tabula.pdf_view.client.on( 'error', _.bind(function(event) { //disable all clipboard buttons, add tooltip, event.message - Tabula.ui.flash_borked = true; - Tabula.ui.flash_borken_message = event.message; + Tabula.pdf_view.flash_borked = true; + Tabula.pdf_view.flash_borken_message = event.message; this.$el.find('#copy-csv-to-clipboard').addClass('has-tooltip').tooltip(); console.log( 'ZeroClipboard error of type "' + event.name + '": ' + event.message ); ZeroClipboard.destroy(); @@ -435,8 +435,8 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. //TODO: this still doesn't quite work. Even numbered times I click it, it doesn't work. toggleAdvancedOptions: function(e){ console.log("toggle", 'e') - this.ui.options.set('show_advanced_options', !this.ui.options.get('show_advanced_options')); - if(this.ui.options.get('show_advanced_options')){ + this.pdf_view.options.set('show_advanced_options', !this.pdf_view.options.get('show_advanced_options')); + if(this.pdf_view.options.get('show_advanced_options')){ this.$el.addClass("advanced-options-shown"); }else{ this.$el.removeClass("advanced-options-shown"); @@ -447,9 +447,9 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. queryWithToggledExtractionMethod: function(e){ var extractionMethod = $(e.currentTarget).data('method'); - this.ui.options.set('extraction_method', extractionMethod); - Tabula.ui.query.setExtractionMethod(extractionMethod); - Tabula.ui.query.doQuery(); + this.pdf_view.options.set('extraction_method', extractionMethod); + Tabula.pdf_view.query.setExtractionMethod(extractionMethod); + Tabula.pdf_view.query.doQuery(); }, }); @@ -457,14 +457,14 @@ Tabula.DocumentView = Backbone.View.extend({ //only one events: { 'click button.close#directions' : 'closeDirections', }, - ui: null, //added on create + pdf_view: null, //added on create page_views: {}, /* when the Directions area is closed, the pages themselves move up, because they're just static positioned. * The selections on those images, though, do not move up, and need to be moved up separately, since they're fixed. */ closeDirections: function(){ - this.ui.options.set('show-directions', false); + this.pdf_view.options.set('show-directions', false); var directionsRow = $('#directionsRow') var height = directionsRow.height() @@ -476,7 +476,7 @@ Tabula.DocumentView = Backbone.View.extend({ //only one initialize: function(stuff){ _.bindAll(this, 'render', 'removePage'); - this.ui = stuff.ui; + this.pdf_view = stuff.pdf_view; this.listenTo(this.collection, 'remove', this.removePage) }, @@ -497,7 +497,7 @@ Tabula.DocumentView = Backbone.View.extend({ //only one //TODO: edit imgAreaSelect to: // (a) not be position fixed (so I don't have to move their location manualy) - // e.g. something like _(Tabula.ui.imgAreaSelects).each(function(ias){ ias.adjust(); }); + // e.g. something like _(Tabula.pdf_view.imgAreaSelects).each(function(ias){ ias.adjust(); }); // (b) listen on document, no matter how many exist on the page. page_view.imgAreaSelect.remove(); @@ -506,13 +506,12 @@ Tabula.DocumentView = Backbone.View.extend({ //only one }, render: function(){ - if(!this.ui.options.get('show-directions')){ + if(!this.pdf_view.options.get('show-directions')){ this.$el.find('#directionsRow').remove(); } return this; }, }); -//TODO: switch back to underscore templates, remove handlebars dependency Tabula.PageView = Backbone.View.extend({ // one per page of the PDF document_view: null, //added on create @@ -521,13 +520,13 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF id: function(){ return 'page-' + this.model.get('number'); }, - template: Handlebars.compile($('#templates #page-template').html()) , + template: _.template($('#templates #page-template').html()) , 'events': { 'click i.rotate-left i.rotate-right': 'rotate_page', }, initialize: function(stuff){ - this.ui = stuff.ui; + this.pdf_view = stuff.pdf_view; _.bindAll(this, 'createImgareaselect', 'rotate_page', 'createTables', '_onSelectStart', '_onSelectChange', '_onSelectEnd', '_onSelectCancel', 'render'); }, @@ -547,7 +546,7 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF } - Tabula.ui.imgAreaSelects[this.model.get('number')] = this.createImgareaselect() ; + Tabula.pdf_view.imgAreaSelects[this.model.get('number')] = this.createImgareaselect() ; return this; }, @@ -579,11 +578,11 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF }, _onSelectStart: function(img, iasSelection) { - Tabula.ui.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); + Tabula.pdf_view.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); }, _onSelectChange: function(img, iasSelection) { - Tabula.ui.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); + Tabula.pdf_view.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); // This is for moving the repeat lassos button, I think. -Jeremy 7/31/14 var b; @@ -600,7 +599,7 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF }, _onSelectEnd: function(img, iasSelection) { - var selection = Tabula.ui.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); + var selection = Tabula.pdf_view.pdf_document.selections.updateOrCreateByIasId(iasSelection, this.model.get('number'), this.$image.width()); // deal with invalid/too-small iasSelections somehow (including thumbnails) if (iasSelection.width == 0 && iasSelection.height == 0) { @@ -615,10 +614,10 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF iasSelection.$el.append(button); } - if(!Tabula.ui.options.get('multiselect_mode')){ + if(!Tabula.pdf_view.options.get('multiselect_mode')){ selection.queryForData(); } - Tabula.ui.components['control_panel'].render(); // deals with buttons that need blurred out if there's zero selections, etc. + Tabula.pdf_view.components['control_panel'].render(); // deals with buttons that need blurred out if there's zero selections, etc. }, // iasSelection @@ -629,10 +628,10 @@ Tabula.PageView = Backbone.View.extend({ // one per page of the PDF // find and remove the canceled selection from the collection of selections. (triggering remove events). var selectionId = (this.model.get('number') * 100000) + iasSelection.id; - var selection = Tabula.ui.pdf_document.selections.get(selectionId); - removed_selection = Tabula.ui.pdf_document.selections.remove(selection); + var selection = Tabula.pdf_view.pdf_document.selections.get(selectionId); + removed_selection = Tabula.pdf_view.pdf_document.selections.remove(selection); - Tabula.ui.components['control_panel'].render(); // deal with buttons that need blurred out if there's zero selections, etc. + Tabula.pdf_view.components['control_panel'].render(); // deal with buttons that need blurred out if there's zero selections, etc. }, rotate_page: function(t) { @@ -655,12 +654,12 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one }, className: 'followyouaroundbar', - template: Handlebars.compile($('#templates #control-panel-template').html()), + template: _.template($('#templates #control-panel-template').html()), shouldPreviewDataAutomatically: !$('#should-preview-data-checkbox').is(':checked'), updateShouldPreviewDataAutomaticallyButton: function(){ - this.ui.options.set('multiselect_mode', !$('#should-preview-data-checkbox').is(':checked')) + this.pdf_view.options.set('multiselect_mode', !$('#should-preview-data-checkbox').is(':checked')) this.render(); }, @@ -672,24 +671,24 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one }, clear_all_selection: function(){ - _(this.ui.imgAreaSelects).each(function(imgAreaSelectAPIObj){ + _(this.pdf_view.imgAreaSelects).each(function(imgAreaSelectAPIObj){ if (imgAreaSelectAPIObj === false) return; imgAreaSelectAPIObj.cancelSelections(); }); }, restore_detected_tables: function(){ - this.ui.pdf_document.selections.reset([]); - this.ui.pdf_document.selections.fetch(); + this.pdf_view.pdf_document.selections.reset([]); + this.pdf_view.pdf_document.selections.fetch(); }, initialize: function(stuff){ - this.ui = stuff.ui + this.pdf_view = stuff.pdf_view _.bindAll(this, 'updateShouldPreviewDataAutomaticallyButton', 'query_all_data', 'render'); }, query_all_data : function(){ - var list_of_all_coords = Tabula.ui.pdf_document.selections.invoke("toCoords"); + var list_of_all_coords = Tabula.pdf_view.pdf_document.selections.invoke("toCoords"); //TODO: figure out how to handle extraction methods when there are multiple selections // should it be set globally, or per selection? @@ -703,21 +702,21 @@ Tabula.ControlPanelView = Backbone.View.extend({ // only one // when you finish a query, then still pop up its data. // when you click or move an already-selected query, then you're "editing" it? // hmm. - Tabula.ui.query = new Tabula.Query({list_of_coords: list_of_all_coords, extraction_method: 'guess'}); - Tabula.ui.createDataView(); - Tabula.ui.query.doQuery(); + Tabula.pdf_view.query = new Tabula.Query({list_of_coords: list_of_all_coords, extraction_method: 'guess'}); + Tabula.pdf_view.createDataView(); + Tabula.pdf_view.query.doQuery(); }, render: function(){ // makes the "follow you around bar" actually follow you around. ("sticky nav") $('.followyouaroundbar').affix({top: 70 }); - var numOfSelectionsOnPage = this.ui.totalSelections(); + var numOfSelectionsOnPage = this.pdf_view.totalSelections(); this.$el.html(this.template({ - 'if_multiselect_checked': this.ui.options.get('multiselect_mode') ? '' : 'checked="checked"', + 'if_multiselect_checked': this.pdf_view.options.get('multiselect_mode') ? '' : 'checked="checked"', 'disable_clear_all_selections': numOfSelectionsOnPage <= 0 ? 'disabled="disabled"' : '' , 'disable_download_all': numOfSelectionsOnPage <= 0 ? 'disabled="disabled"' : '', - 'show_restore_detected_tables': (this.ui.hasPredetectedTables && numOfSelectionsOnPage <= 0) ? '' : 'display: none;', + 'show_restore_detected_tables': (this.pdf_view.hasPredetectedTables && numOfSelectionsOnPage <= 0) ? '' : 'display: none;', })); return this; @@ -728,17 +727,17 @@ Tabula.SidebarView = Backbone.View.extend({ // only one tagName: 'ul', className: 'thumbnail-list', thumbnail_views: {}, - ui: null, // defined on initialize + pdf_view: null, // defined on initialize initialize: function(stuff){ - this.ui = stuff.ui; + this.pdf_view = stuff.pdf_view; _.bindAll(this, 'addSelectionThumbnail', 'removeSelectionThumbnail', 'changeSelectionThumbnail', 'removeThumbnail') this.listenTo(this.collection, 'remove', this.removeThumbnail) - this.listenTo(this.ui.pdf_document.selections, 'all', this.render); - this.listenTo(this.ui.pdf_document.selections, 'add', this.addSelectionThumbnail); // render a thumbnail selection - this.listenTo(this.ui.pdf_document.selections, 'change', this.changeSelectionThumbnail); // render a thumbnail selection - this.listenTo(this.ui.pdf_document.selections, 'remove', this.removeSelectionThumbnail); // remove a thumbnail selection + this.listenTo(this.pdf_view.pdf_document.selections, 'all', this.render); + this.listenTo(this.pdf_view.pdf_document.selections, 'add', this.addSelectionThumbnail); // render a thumbnail selection + this.listenTo(this.pdf_view.pdf_document.selections, 'change', this.changeSelectionThumbnail); // render a thumbnail selection + this.listenTo(this.pdf_view.pdf_document.selections, 'remove', this.removeSelectionThumbnail); // remove a thumbnail selection }, addSelectionThumbnail: function (new_selection){ this.thumbnail_views[new_selection.get('page_number')].createSelectionThumbnail(new_selection) @@ -770,7 +769,7 @@ Tabula.ThumbnailView = Backbone.View.extend({ // one per page // initialize: function(){ // }, - template: Handlebars.compile($('#templates #thumbnail-template').html()), + template: _.template($('#templates #thumbnail-template').html()), initialize: function(){ _.bindAll(this, 'render', 'createSelectionThumbnail', 'changeSelectionThumbnail', 'removeSelectionThumbnail'); @@ -778,7 +777,7 @@ Tabula.ThumbnailView = Backbone.View.extend({ // one per page delete_page: function(){ if (!confirm('Delete page ' + this.model.get('number') + '?')) return; - Tabula.ui.pdf_document.page_collection.remove( this.model ); + Tabula.pdf_view.pdf_document.page_collection.remove( this.model ); }, render: function(){ @@ -821,7 +820,7 @@ Tabula.ThumbnailView = Backbone.View.extend({ // one per page } }) -Tabula.UI = Backbone.View.extend({ +Tabula.PDFView = Backbone.View.extend({ el : '#tabula-app', events : { @@ -855,9 +854,9 @@ Tabula.UI = Backbone.View.extend({ this.listenTo(this.pdf_document.page_collection, 'reset', this.addAll); this.listenTo(this.pdf_document.page_collection, 'remove', this.removePage); - this.components['document_view'] = new Tabula.DocumentView({el: '#main-container' , ui: this, collection: this.pdf_document.page_collection}); //creates page_views - this.components['control_panel'] = new Tabula.ControlPanelView({ui: this}); - this.components['sidebar_view'] = new Tabula.SidebarView({ui: this, collection: this.pdf_document.page_collection}); + this.components['document_view'] = new Tabula.DocumentView({el: '#main-container' , pdf_view: this, collection: this.pdf_document.page_collection}); //creates page_views + this.components['control_panel'] = new Tabula.ControlPanelView({pdf_view: this}); + this.components['sidebar_view'] = new Tabula.SidebarView({pdf_view: this, collection: this.pdf_document.page_collection}); this.pdf_document.page_collection.fetch({ success: _.bind(function(){ @@ -867,14 +866,26 @@ Tabula.UI = Backbone.View.extend({ }, this) }); }, this), + error: _.bind(function(){ + console.log('404'); //TODO: make this a real 404. + }), }); + + $('body'). // imgareaselect selections are fixed positioned in CSS, just attached to the body in DOM + on("click", ".imgareaselect-box .repeat-lassos", function(e){ + var selectionId = $(e.currentTarget).data('selectionId'); + var selection = Tabula.pdf_view.pdf_document.selections.get(selectionId); + selection.repeatLassos(); + }); + + }, removePage: function(removedPageModel){ $.post('/pdf/' + PDF_ID + '/page/' + removedPageModel.get('number'), { _method: 'delete' }, function () { - Tabula.ui.pageCount -= 1; + Tabula.pdf_view.pageCount -= 1; }); // removing the views is handled by the views themselves. @@ -885,7 +896,7 @@ Tabula.UI = Backbone.View.extend({ }, createDataView: function(){ - this.components['data_view'] = new Tabula.DataView({ui: this, model: Tabula.ui.query}); + this.components['data_view'] = new Tabula.DataView({pdf_view: this, model: Tabula.pdf_view.query}); }, trashDataView: function(){ @@ -1089,7 +1100,7 @@ Tabula.UI = Backbone.View.extend({ var list_of_coords = JSON.parse(this.lastQuery.coords); - Tabula.ui.query.doQuery({ + Tabula.pdf_view.query.doQuery({ success: _.bind(function(data) { var colors = this.colors; console.log(list_of_coords); @@ -1115,18 +1126,6 @@ Tabula.UI = Backbone.View.extend({ }, }); -$(function () { - Tabula.ui = new Tabula.UI(); - - $('body'). // imgareaselect selections are fixed positioned in CSS, just attached to the body in DOM - on("click", ".imgareaselect-box .repeat-lassos", function(e){ - var selectionId = $(e.currentTarget).data('selectionId'); - var selection = Tabula.ui.pdf_document.selections.get(selectionId); - selection.repeatLassos(); - }); -}); - - function isElementInViewport (el) { //special bonus for those using jQuery diff --git a/webapp/static/js/tabula.js b/webapp/static/js/tabula.js new file mode 100644 index 00000000..0c1d2310 --- /dev/null +++ b/webapp/static/js/tabula.js @@ -0,0 +1,77 @@ +var Tabula; +Tabula = Tabula || {}; + +var TABULA_VERSION = "TODO"; + +var TabulaRouter = Backbone.Router.extend({ + routes: { + "": "upload", + "/": "upload", + "pdf/:file_id": "view", //renders navbar + "queue/:file_id": 'status', //TK, renders navbar + "error": 'uploadError' //TK, renders navbar + }, + + upload: function() { + $('#fork-me-ribbon').show(); + $.getJSON('/pdfs/workspace.json', function(workspace){ + if( workspace.length > 0){ + $('#uploaded-files-container').html( _.template( $('#uploaded-files-template').html() )({workspace: workspace })); + }else{ + $('#uploaded-files-container').html( $('

No uploaded files yet.

') ); + } + }) + $('#tabula').html( _.template( $('#upload-template').html() )({TABULA_VERSION: TABULA_VERSION }) ); + }, + + // TODO: requires interacting with resque. + // uploadError: function(){ + // $('body').prepend( _.template( $('#navbar-template').html() )({}) ); // navbar. + // //TODO: there's another errorMsg: "Sorry, your file upload could not be processed. Please double-check that the file you uploaded is a valid PDF file and try again." + // var errorMsg = "Sorry, the file you uploaded was not detected as a PDF. You must upload a PDF file. Please try again."; + // $('#tabula').html( _.template( $('#upload-error-template').html() )({message: errorMsg}) ); + // }, + + // status: function(file_id){ + // $('body').prepend( _.template( $('#navbar-template').html() )({}) ); // navbar. + // $('#tabula').html( _.template( $('#upload-status-template').html() )({}) ); + // $.ajax({ + // url: "/js/upload_status.js", + // dataType: "script", + // async: true, + // success: function(data, status, jqxhr){ + // //TODO: rewrite upload_status.js as a Backbone view. + // }, + // error: function(a,b,c){ + // console.log(a,b,c); + // } + // }); + // }, + + view: function(file_id) { + $('body').prepend( _.template( $('#navbar-template').html() )({}) ); // navbar. + $('#tabula').html( _.template( $('#pdf-view-template').html() )({}) ); + + $.ajax({ + url: "/js/pdf_view.js", + dataType: "script", + async: true, + success: function(data, status, jqxhr){ + Tabula.pdf_view = new Tabula.PDFView({pdf_id: file_id}); + }, + error: function(a,b,c){ + console.log(a,b,c); + } + }); + } +}); + +if(TABULA_VERSION.slice(0,3) == "rev"){ + $('#dev-mode-ribbon').show(); +} + + +$(function(){ + new TabulaRouter(); + Backbone.history.start({pushState: true}); +}); \ No newline at end of file diff --git a/webapp/static/js/templates/help_bar.jst b/webapp/static/js/templates/help_bar.jst deleted file mode 100644 index 9901e9be..00000000 --- a/webapp/static/js/templates/help_bar.jst +++ /dev/null @@ -1,7 +0,0 @@ -
-
- -

How to use: make a rectangular selection over a table on the PDF pages. That's it!

-

Hint: table headers are (still) problematic. Try to exclude it from your selection.

-
-
diff --git a/webapp/static/js/upload_status.js b/webapp/static/js/upload_status.js new file mode 100644 index 00000000..d7f349a4 --- /dev/null +++ b/webapp/static/js/upload_status.js @@ -0,0 +1,63 @@ +function statusComplete(file_id) { + if (!!window.spinobj) { + window.spinobj.stop() + }; + if (!!file_id) { + window.location = '/pdf/' + file_id; + } else { + window.location = '/pdf/<%= file_id %>'; + } +} + +function checkStatus() { + $.ajax({ + dataType: 'json', + url: '/queue/<%= upload_id %>/json?file_id=<%= file_id %>', + success: function(data, status, xhr) { + processStatus(data); + if (data.status == "error") { + window.location.reload(true); + } else if (data.pct_complete >= 100) { + statusComplete(data.file_id); + } else { + setTimeout(checkStatus, 1000); + } + }, + error: function(xhr, status, err) { + console.log(err); + } + }); +} +function processStatus(data) { + var msg = "" + if (data.message) { + msg += ": "; + msg += data.message; + } else if (data.pct_complete === 0) { + msg += ": waiting to be processed..." + } + console.log(data); + $("#message").text(msg); + $(".progress .bar").css("width", data.pct_complete + "%"); + $("#percent").html(data.pct_complete + "%"); +} + + +$(function() { + if(false) // already loaded{ + statusComplete(); + }else{ + window.spinpots = { + lines: 11, + length: 5, + width: 2, + radius: 4, + hwaccel: true, + top: '0', + left: 0 + }; + window.spintarget = document.getElementById('spinner'); + window.spinobj = new Spinner(window.spinpots).spin(window.spintarget); + checkStatus(); + } +}); diff --git a/webapp/static/js/ZeroClipboard.min.js b/webapp/static/js/vendor/ZeroClipboard.min.js similarity index 100% rename from webapp/static/js/ZeroClipboard.min.js rename to webapp/static/js/vendor/ZeroClipboard.min.js diff --git a/webapp/static/js/backbone-min.js b/webapp/static/js/vendor/backbone-min.js similarity index 100% rename from webapp/static/js/backbone-min.js rename to webapp/static/js/vendor/backbone-min.js diff --git a/webapp/static/js/bootstrap-tour.min.js b/webapp/static/js/vendor/bootstrap-tour.min.js similarity index 100% rename from webapp/static/js/bootstrap-tour.min.js rename to webapp/static/js/vendor/bootstrap-tour.min.js diff --git a/webapp/static/js/bootstrap.min.js b/webapp/static/js/vendor/bootstrap.min.js similarity index 100% rename from webapp/static/js/bootstrap.min.js rename to webapp/static/js/vendor/bootstrap.min.js diff --git a/webapp/static/js/handlebars-v1.3.0.js b/webapp/static/js/vendor/handlebars-v1.3.0.js similarity index 100% rename from webapp/static/js/handlebars-v1.3.0.js rename to webapp/static/js/vendor/handlebars-v1.3.0.js diff --git a/webapp/static/js/jcanvas.min.js b/webapp/static/js/vendor/jcanvas.min.js similarity index 100% rename from webapp/static/js/jcanvas.min.js rename to webapp/static/js/vendor/jcanvas.min.js diff --git a/webapp/static/js/jquery.csv-0.71.min.js b/webapp/static/js/vendor/jquery.csv-0.71.min.js similarity index 100% rename from webapp/static/js/jquery.csv-0.71.min.js rename to webapp/static/js/vendor/jquery.csv-0.71.min.js diff --git a/webapp/static/js/jquery.min.js b/webapp/static/js/vendor/jquery.min.js similarity index 100% rename from webapp/static/js/jquery.min.js rename to webapp/static/js/vendor/jquery.min.js diff --git a/webapp/static/js/spin.min.js b/webapp/static/js/vendor/spin.min.js similarity index 100% rename from webapp/static/js/spin.min.js rename to webapp/static/js/vendor/spin.min.js diff --git a/webapp/static/js/table2CSV.js b/webapp/static/js/vendor/table2CSV.js similarity index 100% rename from webapp/static/js/table2CSV.js rename to webapp/static/js/vendor/table2CSV.js diff --git a/webapp/static/js/underscore-min.js b/webapp/static/js/vendor/underscore-min.js similarity index 100% rename from webapp/static/js/underscore-min.js rename to webapp/static/js/vendor/underscore-min.js diff --git a/webapp/tabula_web.rb b/webapp/tabula_web.rb index 693eb8da..38ef4003 100644 --- a/webapp/tabula_web.rb +++ b/webapp/tabula_web.rb @@ -125,34 +125,16 @@ def has_key?(k) end on get do - on root do - workspace_file = File.join(TabulaSettings::DOCUMENTS_BASEPATH, 'workspace.json') - workspace = if File.exists?(workspace_file) - File.open(workspace_file) { |f| JSON.load(f) } - else - [] - end - - res.write view("index.html", - workspace: workspace) - end - - on 'pdfs' do run Rack::File.new(TabulaSettings::DOCUMENTS_BASEPATH) end + on root do + res.write File.read("webapp/index.html") + end + on "pdf/:file_id" do |file_id| - document_dir = File.join(TabulaSettings::DOCUMENTS_BASEPATH, file_id) - unless File.directory?(document_dir) - res.status = 404 - else - res.write view("pdf_view.html", - pages: File.open(File.join(document_dir, 'pages.json')) { |f| - JSON.parse(f.read) - }, - file_id: file_id) - end + res.write File.read("webapp/index.html") end end # /get @@ -164,7 +146,7 @@ def has_key?(k) unless is_valid_pdf?(req.params['file'][:tempfile].path) res.status = 400 res.write view("upload_error.html", - :message => "Sorry, the file you uploaded was not detected as a PDF. You must upload a PDF file. Please try again.") + :message => "") next # halt this handler end diff --git a/webapp/views/pdf_view.html.erb b/webapp/views/pdf_view.html.erb index e2c4a9c0..7421966b 100644 --- a/webapp/views/pdf_view.html.erb +++ b/webapp/views/pdf_view.html.erb @@ -129,10 +129,11 @@
- + + - - + + - - - + + + + - - - - + + + + + @@ -265,14 +267,8 @@

Uploaded files

- - - -


-

Tabula was created by Manuel Aristarán with the help of ProPublica, La Nación DATA and Knight-Mozilla OpenNews

+

Tabula was created by Manuel Aristarán, Mike Tigas and Jeremy B. Merrillwith the support of ProPublica, La Nación DATA, Knight-Mozilla OpenNews, The New York Times and The Knight Foundation

@@ -298,7 +294,7 @@

Welcome to Tabula!

<% _(workspace).each(function(file){ %>
  • <%= file['file'] %> - (<%= new Date(parseInt(file['time']) * 1000) %>) + (<%= new Date(parseInt(file['time']) * 1000).toUTCString().slice(5, -7) %>)
  • <% }) %> diff --git a/webapp/static/js/tabula.js b/webapp/static/js/tabula.js index 0c1d2310..af46d862 100644 --- a/webapp/static/js/tabula.js +++ b/webapp/static/js/tabula.js @@ -1,7 +1,7 @@ var Tabula; Tabula = Tabula || {}; -var TABULA_VERSION = "TODO"; +TABULA_VERSION = "TODO"; var TabulaRouter = Backbone.Router.extend({ routes: { From 066ee13aa02d66badabfd16473e51e53e37799ac Mon Sep 17 00:00:00 2001 From: "Jeremy B. Merrill" Date: Sat, 13 Sep 2014 17:41:10 -0400 Subject: [PATCH 21/25] fix to allow nested script tags in templates; fix loading spinner on extraction method switch --- webapp/index.html | 63 +++++++++++++++++++----------------- webapp/static/js/pdf_view.js | 9 +++--- webapp/static/js/tabula.js | 8 ++--- 3 files changed, 42 insertions(+), 38 deletions(-) diff --git a/webapp/index.html b/webapp/index.html index 9a22c337..74e772dc 100644 --- a/webapp/index.html +++ b/webapp/index.html @@ -1,5 +1,5 @@ - + @@ -13,8 +13,6 @@ - - @@ -127,7 +125,7 @@ -
    +
    @@ -163,33 +161,32 @@

    Extracted tabular data

    - - - - - - - + @@ -142,7 +142,7 @@

    Sorry, there's been an error

    From 00706f260e1bd2a46b077fa21ae8528928667d71 Mon Sep 17 00:00:00 2001 From: "Jeremy B. Merrill" Date: Sat, 27 Sep 2014 16:30:04 -0400 Subject: [PATCH 24/25] closes #202; ZeroClipboard copes better now with Flashblock --- webapp/static/js/pdf_view.js | 51 +++++++++++++++++------------------- 1 file changed, 24 insertions(+), 27 deletions(-) diff --git a/webapp/static/js/pdf_view.js b/webapp/static/js/pdf_view.js index 05841837..a86dcd47 100644 --- a/webapp/static/js/pdf_view.js +++ b/webapp/static/js/pdf_view.js @@ -298,7 +298,6 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. // they fire the same 'hidden' event as the modal. handleHidden: function(e){ if($(e.target).attr('id') == "data-modal" ) { - console.log('hidden', e); this.trash(); } }, @@ -391,29 +390,30 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. this.$el.find('#copy-csv-to-clipboard').hide(); } } - - Tabula.pdf_view.client.on( 'ready', _.bind(function(event) { - Tabula.pdf_view.client.clip( this.$el.find("#copy-csv-to-clipboard") ); - - Tabula.pdf_view.client.on( 'copy', _.bind(function(event) { - var clipboard = event.clipboardData; - var tableData = this.$el.find('.modal-body table').table2CSV({delivery: null}) - clipboard.setData( 'text/plain', tableData ); - }, this) ); - - Tabula.pdf_view.client.on( 'aftercopy', function(event) { - $('#data-modal #copy-csv-to-clipboard').css('display', 'inline').delay(900).fadeOut('slow'); - } ); - }, this) ); - - Tabula.pdf_view.client.on( 'error', _.bind(function(event) { - //disable all clipboard buttons, add tooltip, event.message - Tabula.pdf_view.flash_borked = true; - Tabula.pdf_view.flash_borken_message = event.message; - this.$el.find('#copy-csv-to-clipboard').addClass('has-tooltip').tooltip(); - console.log( 'ZeroClipboard error of type "' + event.name + '": ' + event.message ); - ZeroClipboard.destroy(); - },this) ); + if( !Tabula.pdf_view.flash_borked ){ + Tabula.pdf_view.client.on( 'ready', _.bind(function(event) { + Tabula.pdf_view.client.clip( this.$el.find("#copy-csv-to-clipboard") ); + + Tabula.pdf_view.client.on( 'copy', _.bind(function(event) { + var clipboard = event.clipboardData; + var tableData = this.$el.find('.modal-body table').table2CSV({delivery: null}) + clipboard.setData( 'text/plain', tableData ); + }, this) ); + + Tabula.pdf_view.client.on( 'aftercopy', function(event) { + $('#data-modal #copy-csv-to-clipboard').css('display', 'inline').delay(900).fadeOut('slow'); + } ); + }, this) ); + + Tabula.pdf_view.client.on( 'error', _.bind(function(event) { + //disable all clipboard buttons, add tooltip, event.message + Tabula.pdf_view.flash_borked = true; + Tabula.pdf_view.flash_borken_message = event.message; + this.$el.find('#copy-csv-to-clipboard').addClass('has-tooltip').tooltip(); + console.log( 'ZeroClipboard error of type "' + event.name + '": ' + event.message ); + ZeroClipboard.destroy(); + },this) ); + } return this; }, @@ -432,9 +432,7 @@ Tabula.DataView = Backbone.View.extend({ //one per query object. }, 100); }, - //TODO: this still doesn't quite work. Even numbered times I click it, it doesn't work. toggleAdvancedOptions: function(e){ - console.log("toggle", 'e') this.pdf_view.options.set('show_advanced_options', !this.pdf_view.options.get('show_advanced_options')); if(this.pdf_view.options.get('show_advanced_options')){ this.$el.addClass("advanced-options-shown"); @@ -1104,7 +1102,6 @@ Tabula.PDFView = Backbone.View.extend({ Tabula.pdf_view.query.doQuery({ success: _.bind(function(data) { var colors = this.colors; - console.log(list_of_coords); $.each(data[0].vertical_separators, function(i, vert) { newCanvas.drawLine({ strokeStyle: colors[i % colors.length], From 174e99262bcc185fb2373a6a0e79ddbd390dacb1 Mon Sep 17 00:00:00 2001 From: "Jeremy B. Merrill" Date: Fri, 14 Nov 2014 17:30:01 -0700 Subject: [PATCH 25/25] alert (in alert() and ocnsole) if a PDF has no text elements (i.e. is a scan) --- webapp/static/js/pdf_view.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/webapp/static/js/pdf_view.js b/webapp/static/js/pdf_view.js index a86dcd47..84fdac62 100644 --- a/webapp/static/js/pdf_view.js +++ b/webapp/static/js/pdf_view.js @@ -28,6 +28,17 @@ Tabula.Pages = Backbone.Collection.extend({ initialize: function(){ this.url = '/pdfs/' + PDF_ID + '/pages.json?_=' + Math.round(+new Date()).toString(); }, + hasText: function(){ + has_text = false; + for (i=0;i < this.size();i++){ + if(this.at(i).get('hasText')){ + has_text = true; + break; + } + } + return has_text; + } + }); @@ -859,6 +870,12 @@ Tabula.PDFView = Backbone.View.extend({ this.pdf_document.page_collection.fetch({ success: _.bind(function(){ + + if(!this.pdf_document.page_collection.hasText()){ + console.log("Scanned PDF detected") + alert("Is this PDF scanned? It has no embedded text that Tabula can understand. Unfortunately, this means Tabula won't work. Sorry. ") + } + this.pdf_document.selections.fetch({ success: _.bind(function(){ this.hasPredetectedTables = true;