2 new commits in galaxy-central: https://bitbucket.org/galaxy/galaxy-central/changeset/ce64f5d64ff4/ changeset: ce64f5d64ff4 user: carlfeberhard date: 2012-09-12 17:03:17 summary: Client side templates for history panel; IconButtonView in ui.js; copied BaseModel/View into base-mvc.js; stubs for annotations, tags MVC; cleaned up controllers/root.py affected #: 18 files diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 lib/galaxy/web/controllers/root.py --- a/lib/galaxy/web/controllers/root.py +++ b/lib/galaxy/web/controllers/root.py @@ -120,9 +120,9 @@ show_hidden = util.string_as_bool( show_hidden ) datasets = self.get_history_datasets( trans, history, show_deleted, show_hidden, show_purged ) + history_panel_template = "root/history.mako" # history panel -> backbone - history_panel_template = "root/history.mako" - #history_panel_template = "root/alternate_history.mako" + ##history_panel_template = "root/alternate_history.mako" return trans.stream_template_mako( history_panel_template, history = history, annotation = self.get_item_annotation_str( trans.sa_session, trans.user, history ), @@ -512,85 +512,3 @@ @web.expose def generate_error( self, trans ): raise Exception( "Fake error!" ) - - - -""" -import galaxy.web.framework.helpers as web_helpers -from functools import wraps -def debug_mako_template( template_fn ): - # Wrap a function that produces a mako template for better debugging - - # http://stackoverflow.com/questions/390409/how-do-you-debug-mako-templates - @wraps( template_fn ) - def wrapper( *args, **kwargs ): - try: - log.debug( 'rendering template' ) - return template_fn( *args, **kwargs ) - log.debug( 'done rendering template' ) - except Exception, ex: - log.error( "Mako Exception: " + str( ex ), exc_info=True ) - return exceptions.html_error_template().render() - return wrapper - -def prep_dataset( hda, trans ): - states = trans.app.model.Dataset.states - print states - STATES_INTERPRETED_AS_QUEUED = [ 'no state', '', None ] - ## dataset is actually a HistoryDatasetAssociation - #ported mostly from history_common - #post: return dictionary form - #??: move out of templates? - #??: gather data from model? - - - #TODO: clean up magic strings - - hda_dict = hda.get_api_value() - #TODO: use hda_dict.update to add these (instead of localvars) - def add_to_hda( **kwargs ): - hda_dict.update( kwargs ) - - # trans - encoded_hda_id = trans.security.encode_id( hda.id ) - add_to_hda( id=encoded_hda_id ) - - add_to_hda( state=hda.state ) - if hda.state in STATES_INTERPRETED_AS_QUEUED: - #TODO: magic string - add_to_hda( state='queued' ) - - # trans - current_user_roles = trans.get_current_user_roles() - add_to_hda( can_edit=( not ( hda.deleted or hda.purged ) ) ) - - # considered accessible if user can access or user isn't admin - # trans - accessible = trans.app.security_agent.can_access_dataset( current_user_roles, hda.dataset ) - accessible = trans.user_is_admin() or accessible - add_to_hda( accessible=accessible ) - - #TODO: move urls into js galaxy_paths (decorate) - deleted = hda.deleted - purged = hda.purged - dataset_purged = hda.dataset.purged - if not ( dataset_purged or purged ) and for_editing: - undelete_url = web_helpers.url_for( controller='dataset', action='undelete', dataset_id=encoded_hda_id ) - # trans - if trans.app.config.allow_user_dataset_purge: - purge_url = web_helpers.url_for( controller='dataset', action='purge', dataset_id=encoded_hda_id ) - - if not hda.visible: - unhide_url = web_helpers.url_for( controller='dataset', action='unhide', dataset_id=encoded_hda_id ) - - - print 'dataset:', dataset - - undelete_url = web_helpers.url_for( controller='dataset', action='undelete', dataset_id=encoded_hda_id ) - purge_url = web_helpers.url_for( controller='dataset', action='purge', dataset_id=encoded_hda_id ) - unhide_url = web_helpers.url_for( controller='dataset', action='unhide', dataset_id=encoded_hda_id ) - print 'urls:', '\n'.join( undelete_url, purge_url, unhide_url ) - - return hda_dict - -""" \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/libs/handlebars.full.js --- /dev/null +++ b/static/scripts/libs/handlebars.full.js @@ -0,0 +1,1550 @@ +// lib/handlebars/base.js +var Handlebars = {}; + +Handlebars.VERSION = "1.0.beta.6"; + +Handlebars.helpers = {}; +Handlebars.partials = {}; + +Handlebars.registerHelper = function(name, fn, inverse) { + if(inverse) { fn.not = inverse; } + this.helpers[name] = fn; +}; + +Handlebars.registerPartial = function(name, str) { + this.partials[name] = str; +}; + +Handlebars.registerHelper('helperMissing', function(arg) { + if(arguments.length === 2) { + return undefined; + } else { + throw new Error("Could not find property '" + arg + "'"); + } +}); + +var toString = Object.prototype.toString, functionType = "[object Function]"; + +Handlebars.registerHelper('blockHelperMissing', function(context, options) { + var inverse = options.inverse || function() {}, fn = options.fn; + + + var ret = ""; + var type = toString.call(context); + + if(type === functionType) { context = context.call(this); } + + if(context === true) { + return fn(this); + } else if(context === false || context == null) { + return inverse(this); + } else if(type === "[object Array]") { + if(context.length > 0) { + for(var i=0, j=context.length; i<j; i++) { + ret = ret + fn(context[i]); + } + } else { + ret = inverse(this); + } + return ret; + } else { + return fn(context); + } +}); + +Handlebars.registerHelper('each', function(context, options) { + var fn = options.fn, inverse = options.inverse; + var ret = ""; + + if(context && context.length > 0) { + for(var i=0, j=context.length; i<j; i++) { + ret = ret + fn(context[i]); + } + } else { + ret = inverse(this); + } + return ret; +}); + +Handlebars.registerHelper('if', function(context, options) { + var type = toString.call(context); + if(type === functionType) { context = context.call(this); } + + if(!context || Handlebars.Utils.isEmpty(context)) { + return options.inverse(this); + } else { + return options.fn(this); + } +}); + +Handlebars.registerHelper('unless', function(context, options) { + var fn = options.fn, inverse = options.inverse; + options.fn = inverse; + options.inverse = fn; + + return Handlebars.helpers['if'].call(this, context, options); +}); + +Handlebars.registerHelper('with', function(context, options) { + return options.fn(context); +}); + +Handlebars.registerHelper('log', function(context) { + Handlebars.log(context); +}); +; +// lib/handlebars/compiler/parser.js +/* Jison generated parser */ +var handlebars = (function(){ + +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"root":3,"program":4,"EOF":5,"statements":6,"simpleInverse":7,"statement":8,"openInverse":9,"closeBlock":10,"openBlock":11,"mustache":12,"partial":13,"CONTENT":14,"COMMENT":15,"OPEN_BLOCK":16,"inMustache":17,"CLOSE":18,"OPEN_INVERSE":19,"OPEN_ENDBLOCK":20,"path":21,"OPEN":22,"OPEN_UNESCAPED":23,"OPEN_PARTIAL":24,"params":25,"hash":26,"param":27,"STRING":28,"INTEGER":29,"BOOLEAN":30,"hashSegments":31,"hashSegment":32,"ID":33,"EQUALS":34,"pathSegments":35,"SEP":36,"$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:"OPEN_PARTIAL",28:"STRING",29:"INTEGER",30:"BOOLEAN",33:"ID",34:"EQUALS",36:"SEP"}, +productions_: [0,[3,2],[4,3],[4,1],[4,0],[6,1],[6,2],[8,3],[8,3],[8,1],[8,1],[8,1],[8,1],[11,3],[9,3],[10,3],[12,3],[12,3],[13,3],[13,4],[7,2],[17,3],[17,2],[17,2],[17,1],[25,2],[25,1],[27,1],[27,1],[27,1],[27,1],[26,1],[31,2],[31,1],[32,3],[32,3],[32,3],[32,3],[21,1],[35,3],[35,1]], +performAction: function anonymous(yytext,yyleng,yylineno,yy,yystate,$$,_$) { + +var $0 = $$.length - 1; +switch (yystate) { +case 1: return $$[$0-1] +break; +case 2: this.$ = new yy.ProgramNode($$[$0-2], $$[$0]) +break; +case 3: this.$ = new yy.ProgramNode($$[$0]) +break; +case 4: this.$ = new yy.ProgramNode([]) +break; +case 5: this.$ = [$$[$0]] +break; +case 6: $$[$0-1].push($$[$0]); this.$ = $$[$0-1] +break; +case 7: this.$ = new yy.InverseNode($$[$0-2], $$[$0-1], $$[$0]) +break; +case 8: this.$ = new yy.BlockNode($$[$0-2], $$[$0-1], $$[$0]) +break; +case 9: this.$ = $$[$0] +break; +case 10: this.$ = $$[$0] +break; +case 11: this.$ = new yy.ContentNode($$[$0]) +break; +case 12: this.$ = new yy.CommentNode($$[$0]) +break; +case 13: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) +break; +case 14: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) +break; +case 15: this.$ = $$[$0-1] +break; +case 16: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1]) +break; +case 17: this.$ = new yy.MustacheNode($$[$0-1][0], $$[$0-1][1], true) +break; +case 18: this.$ = new yy.PartialNode($$[$0-1]) +break; +case 19: this.$ = new yy.PartialNode($$[$0-2], $$[$0-1]) +break; +case 20: +break; +case 21: this.$ = [[$$[$0-2]].concat($$[$0-1]), $$[$0]] +break; +case 22: this.$ = [[$$[$0-1]].concat($$[$0]), null] +break; +case 23: this.$ = [[$$[$0-1]], $$[$0]] +break; +case 24: this.$ = [[$$[$0]], null] +break; +case 25: $$[$0-1].push($$[$0]); this.$ = $$[$0-1]; +break; +case 26: this.$ = [$$[$0]] +break; +case 27: this.$ = $$[$0] +break; +case 28: this.$ = new yy.StringNode($$[$0]) +break; +case 29: this.$ = new yy.IntegerNode($$[$0]) +break; +case 30: this.$ = new yy.BooleanNode($$[$0]) +break; +case 31: this.$ = new yy.HashNode($$[$0]) +break; +case 32: $$[$0-1].push($$[$0]); this.$ = $$[$0-1] +break; +case 33: this.$ = [$$[$0]] +break; +case 34: this.$ = [$$[$0-2], $$[$0]] +break; +case 35: this.$ = [$$[$0-2], new yy.StringNode($$[$0])] +break; +case 36: this.$ = [$$[$0-2], new yy.IntegerNode($$[$0])] +break; +case 37: this.$ = [$$[$0-2], new yy.BooleanNode($$[$0])] +break; +case 38: this.$ = new yy.IdNode($$[$0]) +break; +case 39: $$[$0-2].push($$[$0]); this.$ = $$[$0-2]; +break; +case 40: this.$ = [$$[$0]] +break; +} +}, +table: [{3:1,4:2,5:[2,4],6: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],24:[1,15]},{1:[3]},{5:[1,16]},{5:[2,3],7:17,8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,19],20:[2,3],22:[1,13],23:[1,14],24:[1,15]},{5:[2,5],14:[2,5],15:[2,5],16:[2,5],19:[2,5],20:[2,5],22:[2,5],23:[2,5],24:[2,5]},{4:20,6:3,8:4,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],24:[1,15]},{4:21,6:3,8:4,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],24:[1,15]},{5:[2,9],14:[2,9],15:[2,9],16:[2,9],19:[2,9],20:[2,9],22:[2,9],23:[2,9],24:[2,9]},{5:[2,10],14:[2,10],15:[2,10],16:[2,10],19:[2,10],20:[2,10],22:[2,10],23:[2,10],24:[2,10]},{5:[2,11],14:[2,11],15:[2,11],16:[2,11],19:[2,11],20:[2,11],22:[2,11],23:[2,11],24:[2,11]},{5:[2,12],14:[2,12],15:[2,12],16:[2,12],19:[2,12],20:[2,12],22:[2,12],23:[2,12],24:[2,12]},{17:22,21:23,33:[1,25],35:24},{17:26,21:23,33:[1,25],35:24},{17:27,21:23,33:[1,25],35:24},{17:28,21:23,33:[1,25],35:24},{21:29,33:[1,25],35:24},{1:[2,1]},{6:30,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],24:[1,15]},{5:[2,6],14:[2,6],15:[2,6],16:[2,6],19:[2,6],20:[2,6],22:[2,6],23:[2,6],24:[2,6]},{17:22,18:[1,31],21:23,33:[1,25],35:24},{10:32,20:[1,33]},{10:34,20:[1,33]},{18:[1,35]},{18:[2,24],21:40,25:36,26:37,27:38,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,38],28:[2,38],29:[2,38],30:[2,38],33:[2,38],36:[1,46]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],36:[2,40]},{18:[1,47]},{18:[1,48]},{18:[1,49]},{18:[1,50],21:51,33:[1,25],35:24},{5:[2,2],8:18,9:5,11:6,12:7,13:8,14:[1,9],15:[1,10],16:[1,12],19:[1,11],20:[2,2],22:[1,13],23:[1,14],24:[1,15]},{14:[2,20],15:[2,20],16:[2,20],19:[2,20],22:[2,20],23:[2,20],24:[2,20]},{5:[2,7],14:[2,7],15:[2,7],16:[2,7],19:[2,7],20:[2,7],22:[2,7],23:[2,7],24:[2,7]},{21:52,33:[1,25],35:24},{5:[2,8],14:[2,8],15:[2,8],16:[2,8],19:[2,8],20:[2,8],22:[2,8],23:[2,8],24:[2,8]},{14:[2,14],15:[2,14],16:[2,14],19:[2,14],20:[2,14],22:[2,14],23:[2,14],24:[2,14]},{18:[2,22],21:40,26:53,27:54,28:[1,41],29:[1,42],30:[1,43],31:39,32:44,33:[1,45],35:24},{18:[2,23]},{18:[2,26],28:[2,26],29:[2,26],30:[2,26],33:[2,26]},{18:[2,31],32:55,33:[1,56]},{18:[2,27],28:[2,27],29:[2,27],30:[2,27],33:[2,27]},{18:[2,28],28:[2,28],29:[2,28],30:[2,28],33:[2,28]},{18:[2,29],28:[2,29],29:[2,29],30:[2,29],33:[2,29]},{18:[2,30],28:[2,30],29:[2,30],30:[2,30],33:[2,30]},{18:[2,33],33:[2,33]},{18:[2,40],28:[2,40],29:[2,40],30:[2,40],33:[2,40],34:[1,57],36:[2,40]},{33:[1,58]},{14:[2,13],15:[2,13],16:[2,13],19:[2,13],20:[2,13],22:[2,13],23:[2,13],24:[2,13]},{5:[2,16],14:[2,16],15:[2,16],16:[2,16],19:[2,16],20:[2,16],22:[2,16],23:[2,16],24:[2,16]},{5:[2,17],14:[2,17],15:[2,17],16:[2,17],19:[2,17],20:[2,17],22:[2,17],23:[2,17],24:[2,17]},{5:[2,18],14:[2,18],15:[2,18],16:[2,18],19:[2,18],20:[2,18],22:[2,18],23:[2,18],24:[2,18]},{18:[1,59]},{18:[1,60]},{18:[2,21]},{18:[2,25],28:[2,25],29:[2,25],30:[2,25],33:[2,25]},{18:[2,32],33:[2,32]},{34:[1,57]},{21:61,28:[1,62],29:[1,63],30:[1,64],33:[1,25],35:24},{18:[2,39],28:[2,39],29:[2,39],30:[2,39],33:[2,39],36:[2,39]},{5:[2,19],14:[2,19],15:[2,19],16:[2,19],19:[2,19],20:[2,19],22:[2,19],23:[2,19],24:[2,19]},{5:[2,15],14:[2,15],15:[2,15],16:[2,15],19:[2,15],20:[2,15],22:[2,15],23:[2,15],24:[2,15]},{18:[2,34],33:[2,34]},{18:[2,35],33:[2,35]},{18:[2,36],33:[2,36]},{18:[2,37],33:[2,37]}], +defaultActions: {16:[2,1],37:[2,23],53:[2,21]}, +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; + if (typeof this.lexer.yylloc == "undefined") + this.lexer.yylloc = {}; + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + 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) + symbol = lex(); + action = table[state] && table[state][symbol]; + } + if (typeof action === "undefined" || !action.length || !action[0]) { + if (!recovering) { + expected = []; + for (p in table[state]) + if (this.terminals_[p] && p > 2) { + expected.push("'" + this.terminals_[p] + "'"); + } + var errStr = ""; + if (this.lexer.showPosition) { + errStr = "Parse error on line " + (yylineno + 1) + ":\n" + this.lexer.showPosition() + "\nExpecting " + expected.join(", ") + ", got '" + this.terminals_[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}; + 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; +} +};/* Jison generated lexer */ +var lexer = (function(){ + +var lexer = ({EOF:1, +parseError:function parseError(str, hash) { + if (this.yy.parseError) { + this.yy.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}; + return this; + }, +input:function () { + var ch = this._input[0]; + this.yytext+=ch; + this.yyleng++; + this.match+=ch; + this.matched+=ch; + var lines = ch.match(/\n/); + if (lines) this.yylineno++; + this._input = this._input.slice(1); + return ch; + }, +unput:function (ch) { + this._input = ch + this._input; + return this; + }, +more:function () { + this._more = true; + return this; + }, +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, + col, + lines; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i=0;i < rules.length; i++) { + match = this._input.match(this.rules[rules[i]]); + if (match) { + lines = match[0].match(/\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-1 : this.yylloc.last_column + match[0].length} + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + this._more = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, rules[i],this.conditionStack[this.conditionStack.length-1]); + if (token) return token; + else return; + } + } + if (this._input === "") { + return this.EOF; + } else { + 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.performAction = function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START +switch($avoiding_name_collisions) { +case 0: + if(yy_.yytext.slice(-1) !== "\\") this.begin("mu"); + if(yy_.yytext.slice(-1) === "\\") yy_.yytext = yy_.yytext.substr(0,yy_.yyleng-1), this.begin("emu"); + if(yy_.yytext) return 14; + +break; +case 1: return 14; +break; +case 2: this.popState(); return 14; +break; +case 3: return 24; +break; +case 4: return 16; +break; +case 5: return 20; +break; +case 6: return 19; +break; +case 7: return 19; +break; +case 8: return 23; +break; +case 9: return 23; +break; +case 10: yy_.yytext = yy_.yytext.substr(3,yy_.yyleng-5); this.popState(); return 15; +break; +case 11: return 22; +break; +case 12: return 34; +break; +case 13: return 33; +break; +case 14: return 33; +break; +case 15: return 36; +break; +case 16: /*ignore whitespace*/ +break; +case 17: this.popState(); return 18; +break; +case 18: this.popState(); return 18; +break; +case 19: yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2).replace(/\\"/g,'"'); return 28; +break; +case 20: return 30; +break; +case 21: return 30; +break; +case 22: return 29; +break; +case 23: return 33; +break; +case 24: yy_.yytext = yy_.yytext.substr(1, yy_.yyleng-2); return 33; +break; +case 25: return 'INVALID'; +break; +case 26: return 5; +break; +} +}; +lexer.rules = [/^[^\x00]*?(?=(\{\{))/,/^[^\x00]+/,/^[^\x00]{2,}?(?=(\{\{))/,/^\{\{>/,/^\{\{#/,/^\{\{\//,/^\{\{\^/,/^\{\{\s*else\b/,/^\{\{\{/,/^\{\{&/,/^\{\{![\s\S]*?\}\}/,/^\{\{/,/^=/,/^\.(?=[} ])/,/^\.\./,/^[\/.]/,/^\s+/,/^\}\}\}/,/^\}\}/,/^"(\\["]|[^"])*"/,/^true(?=[}\s])/,/^false(?=[}\s])/,/^[0-9]+(?=[}\s])/,/^[a-zA-Z0-9_$-]+(?=[=}\s\/.])/,/^\[[^\]]*\]/,/^./,/^$/]; +lexer.conditions = {"mu":{"rules":[3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26],"inclusive":false},"emu":{"rules":[2],"inclusive":false},"INITIAL":{"rules":[0,1,26],"inclusive":true}};return lexer;})() +parser.lexer = lexer; +return parser; +})(); +if (typeof require !== 'undefined' && typeof exports !== 'undefined') { +exports.parser = handlebars; +exports.parse = function () { return handlebars.parse.apply(handlebars, arguments); } +exports.main = function commonjsMain(args) { + if (!args[1]) + throw new Error('Usage: '+args[0]+' FILE'); + if (typeof process !== 'undefined') { + var source = require('fs').readFileSync(require('path').join(process.cwd(), args[1]), "utf8"); + } else { + var cwd = require("file").path(require("file").cwd()); + var source = cwd.join(args[1]).read({charset: "utf-8"}); + } + return exports.parser.parse(source); +} +if (typeof module !== 'undefined' && require.main === module) { + exports.main(typeof process !== 'undefined' ? process.argv.slice(1) : require("system").args); +} +}; +; +// lib/handlebars/compiler/base.js +Handlebars.Parser = handlebars; + +Handlebars.parse = function(string) { + Handlebars.Parser.yy = Handlebars.AST; + return Handlebars.Parser.parse(string); +}; + +Handlebars.print = function(ast) { + return new Handlebars.PrintVisitor().accept(ast); +}; + +Handlebars.logger = { + DEBUG: 0, INFO: 1, WARN: 2, ERROR: 3, level: 3, + + // override in the host environment + log: function(level, str) {} +}; + +Handlebars.log = function(level, str) { Handlebars.logger.log(level, str); }; +; +// lib/handlebars/compiler/ast.js +(function() { + + Handlebars.AST = {}; + + Handlebars.AST.ProgramNode = function(statements, inverse) { + this.type = "program"; + this.statements = statements; + if(inverse) { this.inverse = new Handlebars.AST.ProgramNode(inverse); } + }; + + Handlebars.AST.MustacheNode = function(params, hash, unescaped) { + this.type = "mustache"; + this.id = params[0]; + this.params = params.slice(1); + this.hash = hash; + this.escaped = !unescaped; + }; + + Handlebars.AST.PartialNode = function(id, context) { + this.type = "partial"; + + // TODO: disallow complex IDs + + this.id = id; + this.context = context; + }; + + var verifyMatch = function(open, close) { + if(open.original !== close.original) { + throw new Handlebars.Exception(open.original + " doesn't match " + close.original); + } + }; + + Handlebars.AST.BlockNode = function(mustache, program, close) { + verifyMatch(mustache.id, close); + this.type = "block"; + this.mustache = mustache; + this.program = program; + }; + + Handlebars.AST.InverseNode = function(mustache, program, close) { + verifyMatch(mustache.id, close); + this.type = "inverse"; + this.mustache = mustache; + this.program = program; + }; + + Handlebars.AST.ContentNode = function(string) { + this.type = "content"; + this.string = string; + }; + + Handlebars.AST.HashNode = function(pairs) { + this.type = "hash"; + this.pairs = pairs; + }; + + Handlebars.AST.IdNode = function(parts) { + this.type = "ID"; + this.original = parts.join("."); + + var dig = [], depth = 0; + + for(var i=0,l=parts.length; i<l; i++) { + var part = parts[i]; + + if(part === "..") { depth++; } + else if(part === "." || part === "this") { this.isScoped = true; } + else { dig.push(part); } + } + + this.parts = dig; + this.string = dig.join('.'); + this.depth = depth; + this.isSimple = (dig.length === 1) && (depth === 0); + }; + + Handlebars.AST.StringNode = function(string) { + this.type = "STRING"; + this.string = string; + }; + + Handlebars.AST.IntegerNode = function(integer) { + this.type = "INTEGER"; + this.integer = integer; + }; + + Handlebars.AST.BooleanNode = function(bool) { + this.type = "BOOLEAN"; + this.bool = bool; + }; + + Handlebars.AST.CommentNode = function(comment) { + this.type = "comment"; + this.comment = comment; + }; + +})();; +// lib/handlebars/utils.js +Handlebars.Exception = function(message) { + var tmp = Error.prototype.constructor.apply(this, arguments); + + for (var p in tmp) { + if (tmp.hasOwnProperty(p)) { this[p] = tmp[p]; } + } + + this.message = tmp.message; +}; +Handlebars.Exception.prototype = new Error; + +// Build out our basic SafeString type +Handlebars.SafeString = function(string) { + this.string = string; +}; +Handlebars.SafeString.prototype.toString = function() { + return this.string.toString(); +}; + +(function() { + var escape = { + "<": "<", + ">": ">", + '"': """, + "'": "'", + "`": "`" + }; + + var badChars = /&(?!\w+;)|[<>"'`]/g; + var possible = /[&<>"'`]/; + + var escapeChar = function(chr) { + return escape[chr] || "&"; + }; + + Handlebars.Utils = { + escapeExpression: function(string) { + // don't escape SafeStrings, since they're already safe + if (string instanceof Handlebars.SafeString) { + return string.toString(); + } else if (string == null || string === false) { + return ""; + } + + if(!possible.test(string)) { return string; } + return string.replace(badChars, escapeChar); + }, + + isEmpty: function(value) { + if (typeof value === "undefined") { + return true; + } else if (value === null) { + return true; + } else if (value === false) { + return true; + } else if(Object.prototype.toString.call(value) === "[object Array]" && value.length === 0) { + return true; + } else { + return false; + } + } + }; +})();; +// lib/handlebars/compiler/compiler.js +Handlebars.Compiler = function() {}; +Handlebars.JavaScriptCompiler = function() {}; + +(function(Compiler, JavaScriptCompiler) { + Compiler.OPCODE_MAP = { + appendContent: 1, + getContext: 2, + lookupWithHelpers: 3, + lookup: 4, + append: 5, + invokeMustache: 6, + appendEscaped: 7, + pushString: 8, + truthyOrFallback: 9, + functionOrFallback: 10, + invokeProgram: 11, + invokePartial: 12, + push: 13, + assignToHash: 15, + pushStringParam: 16 + }; + + Compiler.MULTI_PARAM_OPCODES = { + appendContent: 1, + getContext: 1, + lookupWithHelpers: 2, + lookup: 1, + invokeMustache: 3, + pushString: 1, + truthyOrFallback: 1, + functionOrFallback: 1, + invokeProgram: 3, + invokePartial: 1, + push: 1, + assignToHash: 1, + pushStringParam: 1 + }; + + Compiler.DISASSEMBLE_MAP = {}; + + for(var prop in Compiler.OPCODE_MAP) { + var value = Compiler.OPCODE_MAP[prop]; + Compiler.DISASSEMBLE_MAP[value] = prop; + } + + Compiler.multiParamSize = function(code) { + return Compiler.MULTI_PARAM_OPCODES[Compiler.DISASSEMBLE_MAP[code]]; + }; + + Compiler.prototype = { + compiler: Compiler, + + disassemble: function() { + var opcodes = this.opcodes, opcode, nextCode; + var out = [], str, name, value; + + for(var i=0, l=opcodes.length; i<l; i++) { + opcode = opcodes[i]; + + if(opcode === 'DECLARE') { + name = opcodes[++i]; + value = opcodes[++i]; + out.push("DECLARE " + name + " = " + value); + } else { + str = Compiler.DISASSEMBLE_MAP[opcode]; + + var extraParams = Compiler.multiParamSize(opcode); + var codes = []; + + for(var j=0; j<extraParams; j++) { + nextCode = opcodes[++i]; + + if(typeof nextCode === "string") { + nextCode = "\"" + nextCode.replace("\n", "\\n") + "\""; + } + + codes.push(nextCode); + } + + str = str + " " + codes.join(" "); + + out.push(str); + } + } + + return out.join("\n"); + }, + + guid: 0, + + compile: function(program, options) { + this.children = []; + this.depths = {list: []}; + this.options = options; + + // These changes will propagate to the other compiler components + var knownHelpers = this.options.knownHelpers; + this.options.knownHelpers = { + 'helperMissing': true, + 'blockHelperMissing': true, + 'each': true, + 'if': true, + 'unless': true, + 'with': true, + 'log': true + }; + if (knownHelpers) { + for (var name in knownHelpers) { + this.options.knownHelpers[name] = knownHelpers[name]; + } + } + + return this.program(program); + }, + + accept: function(node) { + return this[node.type](node); + }, + + program: function(program) { + var statements = program.statements, statement; + this.opcodes = []; + + for(var i=0, l=statements.length; i<l; i++) { + statement = statements[i]; + this[statement.type](statement); + } + this.isSimple = l === 1; + + this.depths.list = this.depths.list.sort(function(a, b) { + return a - b; + }); + + return this; + }, + + compileProgram: function(program) { + var result = new this.compiler().compile(program, this.options); + var guid = this.guid++; + + this.usePartial = this.usePartial || result.usePartial; + + this.children[guid] = result; + + for(var i=0, l=result.depths.list.length; i<l; i++) { + depth = result.depths.list[i]; + + if(depth < 2) { continue; } + else { this.addDepth(depth - 1); } + } + + return guid; + }, + + block: function(block) { + var mustache = block.mustache; + var depth, child, inverse, inverseGuid; + + var params = this.setupStackForMustache(mustache); + + var programGuid = this.compileProgram(block.program); + + if(block.program.inverse) { + inverseGuid = this.compileProgram(block.program.inverse); + this.declare('inverse', inverseGuid); + } + + this.opcode('invokeProgram', programGuid, params.length, !!mustache.hash); + this.declare('inverse', null); + this.opcode('append'); + }, + + inverse: function(block) { + var params = this.setupStackForMustache(block.mustache); + + var programGuid = this.compileProgram(block.program); + + this.declare('inverse', programGuid); + + this.opcode('invokeProgram', null, params.length, !!block.mustache.hash); + this.declare('inverse', null); + this.opcode('append'); + }, + + hash: function(hash) { + var pairs = hash.pairs, pair, val; + + this.opcode('push', '{}'); + + for(var i=0, l=pairs.length; i<l; i++) { + pair = pairs[i]; + val = pair[1]; + + this.accept(val); + this.opcode('assignToHash', pair[0]); + } + }, + + partial: function(partial) { + var id = partial.id; + this.usePartial = true; + + if(partial.context) { + this.ID(partial.context); + } else { + this.opcode('push', 'depth0'); + } + + this.opcode('invokePartial', id.original); + this.opcode('append'); + }, + + content: function(content) { + this.opcode('appendContent', content.string); + }, + + mustache: function(mustache) { + var params = this.setupStackForMustache(mustache); + + this.opcode('invokeMustache', params.length, mustache.id.original, !!mustache.hash); + + if(mustache.escaped && !this.options.noEscape) { + this.opcode('appendEscaped'); + } else { + this.opcode('append'); + } + }, + + ID: function(id) { + this.addDepth(id.depth); + + this.opcode('getContext', id.depth); + + this.opcode('lookupWithHelpers', id.parts[0] || null, id.isScoped || false); + + for(var i=1, l=id.parts.length; i<l; i++) { + this.opcode('lookup', id.parts[i]); + } + }, + + STRING: function(string) { + this.opcode('pushString', string.string); + }, + + INTEGER: function(integer) { + this.opcode('push', integer.integer); + }, + + BOOLEAN: function(bool) { + this.opcode('push', bool.bool); + }, + + comment: function() {}, + + // HELPERS + pushParams: function(params) { + var i = params.length, param; + + while(i--) { + param = params[i]; + + if(this.options.stringParams) { + if(param.depth) { + this.addDepth(param.depth); + } + + this.opcode('getContext', param.depth || 0); + this.opcode('pushStringParam', param.string); + } else { + this[param.type](param); + } + } + }, + + opcode: function(name, val1, val2, val3) { + this.opcodes.push(Compiler.OPCODE_MAP[name]); + if(val1 !== undefined) { this.opcodes.push(val1); } + if(val2 !== undefined) { this.opcodes.push(val2); } + if(val3 !== undefined) { this.opcodes.push(val3); } + }, + + declare: function(name, value) { + this.opcodes.push('DECLARE'); + this.opcodes.push(name); + this.opcodes.push(value); + }, + + addDepth: function(depth) { + if(depth === 0) { return; } + + if(!this.depths[depth]) { + this.depths[depth] = true; + this.depths.list.push(depth); + } + }, + + setupStackForMustache: function(mustache) { + var params = mustache.params; + + this.pushParams(params); + + if(mustache.hash) { + this.hash(mustache.hash); + } + + this.ID(mustache.id); + + return params; + } + }; + + JavaScriptCompiler.prototype = { + // PUBLIC API: You can override these methods in a subclass to provide + // alternative compiled forms for name lookup and buffering semantics + nameLookup: function(parent, name, type) { + if (/^[0-9]+$/.test(name)) { + return parent + "[" + name + "]"; + } else if (JavaScriptCompiler.isValidJavaScriptVariableName(name)) { + return parent + "." + name; + } + else { + return parent + "['" + name + "']"; + } + }, + + appendToBuffer: function(string) { + if (this.environment.isSimple) { + return "return " + string + ";"; + } else { + return "buffer += " + string + ";"; + } + }, + + initializeBuffer: function() { + return this.quotedString(""); + }, + + namespace: "Handlebars", + // END PUBLIC API + + compile: function(environment, options, context, asObject) { + this.environment = environment; + this.options = options || {}; + + this.name = this.environment.name; + this.isChild = !!context; + this.context = context || { + programs: [], + aliases: { self: 'this' }, + registers: {list: []} + }; + + this.preamble(); + + this.stackSlot = 0; + this.stackVars = []; + + this.compileChildren(environment, options); + + var opcodes = environment.opcodes, opcode; + + this.i = 0; + + for(l=opcodes.length; this.i<l; this.i++) { + opcode = this.nextOpcode(0); + + if(opcode[0] === 'DECLARE') { + this.i = this.i + 2; + this[opcode[1]] = opcode[2]; + } else { + this.i = this.i + opcode[1].length; + this[opcode[0]].apply(this, opcode[1]); + } + } + + return this.createFunctionContext(asObject); + }, + + nextOpcode: function(n) { + var opcodes = this.environment.opcodes, opcode = opcodes[this.i + n], name, val; + var extraParams, codes; + + if(opcode === 'DECLARE') { + name = opcodes[this.i + 1]; + val = opcodes[this.i + 2]; + return ['DECLARE', name, val]; + } else { + name = Compiler.DISASSEMBLE_MAP[opcode]; + + extraParams = Compiler.multiParamSize(opcode); + codes = []; + + for(var j=0; j<extraParams; j++) { + codes.push(opcodes[this.i + j + 1 + n]); + } + + return [name, codes]; + } + }, + + eat: function(opcode) { + this.i = this.i + opcode.length; + }, + + preamble: function() { + var out = []; + + // this 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. + this.useRegister('foundHelper'); + + if (!this.isChild) { + var namespace = this.namespace; + var copies = "helpers = helpers || " + namespace + ".helpers;"; + if(this.environment.usePartial) { copies = copies + " partials = partials || " + namespace + ".partials;"; } + out.push(copies); + } else { + out.push(''); + } + + if (!this.environment.isSimple) { + out.push(", buffer = " + this.initializeBuffer()); + } else { + out.push(""); + } + + // track the last context pushed into place to allow skipping the + // getContext opcode when it would be a noop + this.lastContext = 0; + this.source = out; + }, + + createFunctionContext: function(asObject) { + var locals = this.stackVars; + if (!this.isChild) { + locals = locals.concat(this.context.registers.list); + } + + if(locals.length > 0) { + this.source[1] = this.source[1] + ", " + locals.join(", "); + } + + // Generate minimizer alias mappings + if (!this.isChild) { + var aliases = [] + for (var alias in this.context.aliases) { + 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.source.push("return buffer;"); + } + + var params = this.isChild ? ["depth0", "data"] : ["Handlebars", "depth0", "helpers", "partials", "data"]; + + for(var i=0, l=this.environment.depths.list.length; i<l; i++) { + params.push("depth" + this.environment.depths.list[i]); + } + + if (asObject) { + params.push(this.source.join("\n ")); + + return Function.apply(this, params); + } else { + var functionSource = 'function ' + (this.name || '') + '(' + params.join(',') + ') {\n ' + this.source.join("\n ") + '}'; + Handlebars.log(Handlebars.logger.DEBUG, functionSource + "\n\n"); + return functionSource; + } + }, + + appendContent: function(content) { + this.source.push(this.appendToBuffer(this.quotedString(content))); + }, + + append: function() { + var local = this.popStack(); + this.source.push("if(" + local + " || " + local + " === 0) { " + this.appendToBuffer(local) + " }"); + if (this.environment.isSimple) { + this.source.push("else { " + this.appendToBuffer("''") + " }"); + } + }, + + appendEscaped: function() { + var opcode = this.nextOpcode(1), extra = ""; + this.context.aliases.escapeExpression = 'this.escapeExpression'; + + if(opcode[0] === 'appendContent') { + extra = " + " + this.quotedString(opcode[1][0]); + this.eat(opcode); + } + + this.source.push(this.appendToBuffer("escapeExpression(" + this.popStack() + ")" + extra)); + }, + + getContext: function(depth) { + if(this.lastContext !== depth) { + this.lastContext = depth; + } + }, + + lookupWithHelpers: function(name, isScoped) { + if(name) { + var topStack = this.nextStack(); + + this.usingKnownHelper = false; + + var toPush; + if (!isScoped && this.options.knownHelpers[name]) { + toPush = topStack + " = " + this.nameLookup('helpers', name, 'helper'); + this.usingKnownHelper = true; + } else if (isScoped || this.options.knownHelpersOnly) { + toPush = topStack + " = " + this.nameLookup('depth' + this.lastContext, name, 'context'); + } else { + this.register('foundHelper', this.nameLookup('helpers', name, 'helper')); + toPush = topStack + " = foundHelper || " + this.nameLookup('depth' + this.lastContext, name, 'context'); + } + + toPush += ';'; + this.source.push(toPush); + } else { + this.pushStack('depth' + this.lastContext); + } + }, + + lookup: function(name) { + var topStack = this.topStack(); + this.source.push(topStack + " = (" + topStack + " === null || " + topStack + " === undefined || " + topStack + " === false ? " + + topStack + " : " + this.nameLookup(topStack, name, 'context') + ");"); + }, + + pushStringParam: function(string) { + this.pushStack('depth' + this.lastContext); + this.pushString(string); + }, + + pushString: function(string) { + this.pushStack(this.quotedString(string)); + }, + + push: function(name) { + this.pushStack(name); + }, + + invokeMustache: function(paramSize, original, hasHash) { + this.populateParams(paramSize, this.quotedString(original), "{}", null, hasHash, function(nextStack, helperMissingString, id) { + if (!this.usingKnownHelper) { + this.context.aliases.helperMissing = 'helpers.helperMissing'; + this.context.aliases.undef = 'void 0'; + this.source.push("else if(" + id + "=== undef) { " + nextStack + " = helperMissing.call(" + helperMissingString + "); }"); + if (nextStack !== id) { + this.source.push("else { " + nextStack + " = " + id + "; }"); + } + } + }); + }, + + invokeProgram: function(guid, paramSize, hasHash) { + var inverse = this.programExpression(this.inverse); + var mainProgram = this.programExpression(guid); + + this.populateParams(paramSize, null, mainProgram, inverse, hasHash, function(nextStack, helperMissingString, id) { + if (!this.usingKnownHelper) { + this.context.aliases.blockHelperMissing = 'helpers.blockHelperMissing'; + this.source.push("else { " + nextStack + " = blockHelperMissing.call(" + helperMissingString + "); }"); + } + }); + }, + + populateParams: function(paramSize, helperId, program, inverse, hasHash, fn) { + var needsRegister = hasHash || this.options.stringParams || inverse || this.options.data; + var id = this.popStack(), nextStack; + var params = [], param, stringParam, stringOptions; + + if (needsRegister) { + this.register('tmp1', program); + stringOptions = 'tmp1'; + } else { + stringOptions = '{ hash: {} }'; + } + + if (needsRegister) { + var hash = (hasHash ? this.popStack() : '{}'); + this.source.push('tmp1.hash = ' + hash + ';'); + } + + if(this.options.stringParams) { + this.source.push('tmp1.contexts = [];'); + } + + for(var i=0; i<paramSize; i++) { + param = this.popStack(); + params.push(param); + + if(this.options.stringParams) { + this.source.push('tmp1.contexts.push(' + this.popStack() + ');'); + } + } + + if(inverse) { + this.source.push('tmp1.fn = tmp1;'); + this.source.push('tmp1.inverse = ' + inverse + ';'); + } + + if(this.options.data) { + this.source.push('tmp1.data = data;'); + } + + params.push(stringOptions); + + this.populateCall(params, id, helperId || id, fn, program !== '{}'); + }, + + populateCall: function(params, id, helperId, fn, program) { + var paramString = ["depth0"].concat(params).join(", "); + var helperMissingString = ["depth0"].concat(helperId).concat(params).join(", "); + + var nextStack = this.nextStack(); + + if (this.usingKnownHelper) { + this.source.push(nextStack + " = " + id + ".call(" + paramString + ");"); + } else { + this.context.aliases.functionType = '"function"'; + var condition = program ? "foundHelper && " : "" + this.source.push("if(" + condition + "typeof " + id + " === functionType) { " + nextStack + " = " + id + ".call(" + paramString + "); }"); + } + fn.call(this, nextStack, helperMissingString, id); + this.usingKnownHelper = false; + }, + + invokePartial: function(context) { + params = [this.nameLookup('partials', context, 'partial'), "'" + context + "'", this.popStack(), "helpers", "partials"]; + + if (this.options.data) { + params.push("data"); + } + + this.pushStack("self.invokePartial(" + params.join(", ") + ");"); + }, + + assignToHash: function(key) { + var value = this.popStack(); + var hash = this.topStack(); + + this.source.push(hash + "['" + key + "'] = " + value + ";"); + }, + + // HELPERS + + compiler: JavaScriptCompiler, + + compileChildren: function(environment, options) { + var children = environment.children, child, compiler; + + for(var i=0, l=children.length; i<l; i++) { + child = children[i]; + compiler = new this.compiler(); + + this.context.programs.push(''); // Placeholder to prevent name conflicts for nested children + var index = this.context.programs.length; + child.index = index; + child.name = 'program' + index; + this.context.programs[index] = compiler.compile(child, options, this.context); + } + }, + + programExpression: function(guid) { + if(guid == null) { return "self.noop"; } + + var child = this.environment.children[guid], + depths = child.depths.list; + var programParams = [child.index, child.name, "data"]; + + for(var i=0, l = depths.length; i<l; i++) { + depth = depths[i]; + + if(depth === 1) { programParams.push("depth0"); } + else { programParams.push("depth" + (depth - 1)); } + } + + if(depths.length === 0) { + return "self.program(" + programParams.join(", ") + ")"; + } else { + programParams.shift(); + return "self.programWithDepth(" + programParams.join(", ") + ")"; + } + }, + + register: function(name, val) { + this.useRegister(name); + this.source.push(name + " = " + val + ";"); + }, + + useRegister: function(name) { + if(!this.context.registers[name]) { + this.context.registers[name] = true; + this.context.registers.list.push(name); + } + }, + + pushStack: function(item) { + this.source.push(this.nextStack() + " = " + item + ";"); + return "stack" + this.stackSlot; + }, + + nextStack: function() { + this.stackSlot++; + if(this.stackSlot > this.stackVars.length) { this.stackVars.push("stack" + this.stackSlot); } + return "stack" + this.stackSlot; + }, + + popStack: function() { + return "stack" + this.stackSlot--; + }, + + topStack: function() { + return "stack" + this.stackSlot; + }, + + quotedString: function(str) { + return '"' + str + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + '"'; + } + }; + + var reservedWords = ( + "break else new var" + + " case finally return void" + + " catch for switch while" + + " continue function this with" + + " default if throw" + + " delete in try" + + " do instanceof typeof" + + " abstract enum int short" + + " boolean export interface static" + + " byte extends long super" + + " char final native synchronized" + + " class float package throws" + + " const goto private transient" + + " debugger implements protected volatile" + + " double import public let yield" + ).split(" "); + + var compilerWords = JavaScriptCompiler.RESERVED_WORDS = {}; + + for(var i=0, l=reservedWords.length; i<l; i++) { + compilerWords[reservedWords[i]] = true; + } + + JavaScriptCompiler.isValidJavaScriptVariableName = function(name) { + if(!JavaScriptCompiler.RESERVED_WORDS[name] && /^[a-zA-Z_$][0-9a-zA-Z_$]+$/.test(name)) { + return true; + } + return false; + } + +})(Handlebars.Compiler, Handlebars.JavaScriptCompiler); + +Handlebars.precompile = function(string, options) { + options = options || {}; + + var ast = Handlebars.parse(string); + var environment = new Handlebars.Compiler().compile(ast, options); + return new Handlebars.JavaScriptCompiler().compile(environment, options); +}; + +Handlebars.compile = function(string, options) { + options = options || {}; + + var compiled; + function compile() { + var ast = Handlebars.parse(string); + var environment = new Handlebars.Compiler().compile(ast, options); + var templateSpec = new Handlebars.JavaScriptCompiler().compile(environment, options, undefined, true); + return Handlebars.template(templateSpec); + } + + // Template is only compiled on first use and cached after that point. + return function(context, options) { + if (!compiled) { + compiled = compile(); + } + return compiled.call(this, context, options); + }; +}; +; +// lib/handlebars/runtime.js +Handlebars.VM = { + template: function(templateSpec) { + // Just add water + var container = { + escapeExpression: Handlebars.Utils.escapeExpression, + invokePartial: Handlebars.VM.invokePartial, + programs: [], + program: function(i, fn, data) { + var programWrapper = this.programs[i]; + if(data) { + return Handlebars.VM.program(fn, data); + } else if(programWrapper) { + return programWrapper; + } else { + programWrapper = this.programs[i] = Handlebars.VM.program(fn); + return programWrapper; + } + }, + programWithDepth: Handlebars.VM.programWithDepth, + noop: Handlebars.VM.noop + }; + + return function(context, options) { + options = options || {}; + return templateSpec.call(container, Handlebars, context, options.helpers, options.partials, options.data); + }; + }, + + programWithDepth: function(fn, data, $depth) { + var args = Array.prototype.slice.call(arguments, 2); + + return function(context, options) { + options = options || {}; + + return fn.apply(this, [context, options.data || data].concat(args)); + }; + }, + program: function(fn, data) { + return function(context, options) { + options = options || {}; + + return fn(context, options.data || data); + }; + }, + noop: function() { return ""; }, + invokePartial: function(partial, name, context, helpers, partials, data) { + options = { helpers: helpers, partials: partials, data: data }; + + if(partial === undefined) { + throw new Handlebars.Exception("The partial " + name + " could not be found"); + } else if(partial instanceof Function) { + return partial(context, options); + } else if (!Handlebars.compile) { + throw new Handlebars.Exception("The partial " + name + " could not be compiled when running in runtime-only mode"); + } else { + partials[name] = Handlebars.compile(partial); + return partials[name](context, options); + } + } +}; + +Handlebars.template = Handlebars.VM.template; +; diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/mvc/annotations.js --- /dev/null +++ b/static/scripts/mvc/annotations.js @@ -0,0 +1,35 @@ +/*============================================================================== +Backbone MV for Annotations + + +TODO: + +==============================================================================*/ +/** Single Tag model + */ +var Annotation = BaseModel.extend( LoggableMixin ).extend({ + + // uncomment this out see log messages + logger : console, + + toString : function(){ + return 'Annotation()'; + } +}); + +//------------------------------------------------------------------------------ +/** Single Tag view + */ +var AnnotationView = BaseView.extend( LoggableMixin ).extend({ + + // uncomment this out see log messages + logger : console, + + toString : function(){ + return 'AnnotationView()'; + } +}); + +//============================================================================== +/** YAGNI? A collection of Annotations, mainView? + */ diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/mvc/base-mvc.js --- /dev/null +++ b/static/scripts/mvc/base-mvc.js @@ -0,0 +1,271 @@ +/** + * Simple base model for any visible element. Includes useful attributes and ability + * to set and track visibility. + */ +var BaseModel = Backbone.RelationalModel.extend({ + defaults: { + name: null, + hidden: false + }, + + show: function() { + this.set("hidden", false); + }, + + hide: function() { + this.set("hidden", true); + }, + + is_visible: function() { + return !this.attributes.hidden; + } +}); + + +/** + * Base view that handles visibility based on model's hidden attribute. + */ +var BaseView = Backbone.View.extend({ + + initialize: function() { + this.model.on("change:hidden", this.update_visible, this); + this.update_visible(); + }, + + update_visible: function() { + if( this.model.attributes.hidden ){ + this.$el.hide(); + } else { + this.$el.show(); + } + } +}); + +//============================================================================== +/** + * Adds logging capabilities to your Models/Views + * can be used with plain browser console (or something more complex like an AJAX logger) + * + * add to your models/views at the definition using chaining: + * var MyModel = BaseModel.extend( LoggableMixin ).extend({ // ... }); + * + * or - more explicitly AFTER the definition: + * var MyModel = BaseModel.extend({ + * logger : console + * // ... + * this.log( '$#%& it! - broken already...' ); + * }) + * _.extend( MyModel.prototype, LoggableMixin ) + * + * NOTE: currently only uses the console.debug log function (as opposed to debug, error, warn, etc.) + */ +var LoggableMixin = { + // replace null with console (if available) to see all logs + logger : null, + + log : function(){ + return ( this.logger )?( this.logger.log.apply( this, arguments ) ) + :( undefined ); + } +}; + + +//============================================================================== +/** + * Base class for template loaders: + * The main interface is loader.getTemplates( templatesToLoad ) + * where templatesToLoad is in the form: + * { + * remoteTemplateFilename1: { + * templateFunctionName1 : templateID1, + * templateFunctionName2 : templateID2, + * ... + * }, + * remoteTemplateFilename2: { + * templateFunctionName3 : templateID3, + * templateFunctionName4 : templateID4, + * ... + * } + * } + * getTemplates will return a map of the templates in the form: + * { + * templateFunctionName1 : compiledTemplateFn1(), + * templateFunctionName2 : compiledTemplateFn2(), + * templateFunctionName3 : compiledTemplateFn3(), + * ... + * } + * + * Generally meant to be called for Backbone views, etc like this: + * BackboneView.templates = CompiledTemplateLoader( templatesToLoad ); + */ +var TemplateLoader = _.extend( {}, LoggableMixin, { + //TODO: incorporate caching of template functions (for use across objects) + //TODO: only require and use 2 level (or some variation) map templatesToLoad for the remote loader + + // comment next line out to suppress logging + //logger : console, + + //cachedTemplates : {}, + + getTemplateLoadFn : function(){ + throw( "There is no templateLoadFn. Make sure you're using a subclass of TemplateLoader" ); + }, + + // loop through templatesToLoad assuming it is a map in the form mentioned above + getTemplates : function( templatesToLoad, forceReload ){ + forceReload = forceReload || false; + this.log( this, 'getTemplates:', templatesToLoad, ', forceReload:', forceReload ); + + //!TODO: cache templates here + var templates = {}, + loader = this, + templateLoadFn = this.getTemplateLoadFn(); + + if( !templatesToLoad ){ return templates; } + jQuery.each( templatesToLoad, function( templateFile, templateData ){ + + //TODO: handle flatter map versions of templatesToLoad ({ name : id }) + jQuery.each( templateData, function( templateName, templateID ){ + loader.log( loader + ', templateFile:', templateFile, + 'templateName:', templateName, ', templateID:', templateID ); + templates[ templateName ] = templateLoadFn.call( loader, templateFile, templateName, templateID ); + }); + }); + return templates; + } +}); + + +//.............................................................................. +/** find the compiled template in Handlebars.templates by templateName + * and return the entire, requested templates map + */ +var CompiledTemplateLoader = _.extend( {}, TemplateLoader, { + getTemplateLoadFn : function(){ return this.loadCompiledHandlebarsTemplate; }, + + // override if new compiler + loadCompiledHandlebarsTemplate : function( templateFile, templateName, templateID ){ + //pre: compiled templates should have been loaded with the mako helper h.templates + // (although these could be dynamically loaded as well?) + this.log( 'getInDomTemplates, templateFile:', templateFile, + 'templateName:', templateName, ', templateID:', templateID ); + + if( !Handlebars.templates || !Handlebars.templates[ templateID ] ){ + throw( 'Template not found: Handlebars.' + templateID + + '. Check your h.templates() call in the mako file that rendered this page' ); + } + this.log( 'found template function:', templateID ); + // really this is just a lookup + return Handlebars.templates[ templateID ]; + } + + //TEST: Handlebars.full NOT runtime + //TEST: no Handlebars + //TEST: bad id + //TEST: Handlebars.runtime, good id +}); + +//.............................................................................. +/** find the NON-compiled template templateID in the DOM, compile it (using Handlebars), + * and return the entire, requested templates map + * (Note: for use with Mako.include and multiple templates) + */ +var InDomTemplateLoader = _.extend( {}, TemplateLoader, { + + // override or change if a new compiler (Underscore, etc.) is being used + compileTemplate : function( templateText ){ + // we'll need the compiler + if( !Handlebars || !Handlebars.compile ){ + throw( 'No Handlebars.compile found. You may only have Handlebars.runtime loaded.' + + 'Include handlebars.full for this to work' ); + } + // copy fn ref to this view under the templateName + this.log( 'compiling template:', templateText ); + return Handlebars.compile( templateText ); + }, + + findTemplateInDom : function( templateFile, templateName, templateID ){ + // assume the last is best + return $( 'script#' + templateID ).last(); + }, + + getTemplateLoadFn : function(){ return this.loadInDomTemplate; }, + + loadInDomTemplate : function( templateFile, templateName, templateID ){ + this.log( 'getInDomTemplate, templateFile:', templateFile, + 'templateName:', templateName, ', templateID:', templateID ); + + // find it in the dom by the id and compile + var template = this.findTemplateInDom( templateFile, templateName, templateID ); + if( !template || !template.length ){ + throw( 'Template not found within the DOM: ' + templateID + + '. Check that this template has been included in the page' ); + } + this.log( 'found template in dom:', template.html() ); + return this.compileTemplate( template.html() ); + } + + //TEST: no compiler + //TEST: good url, good id, in DOM + //TEST: good url, good id, NOT in DOM +}); + +//.............................................................................. +/** HTTP GET the NON-compiled templates, append into the DOM, compile them, + * and return the entire, requested templates map + * (for use with dynamically loaded views) + */ +var RemoteTemplateLoader = _.extend( {}, InDomTemplateLoader, { + templateBaseURL : 'static/scripts/templates/', + + getTemplateLoadFn : function(){ return this.loadViaHttpGet; }, + + loadViaHttpGet : function( templateFile, templateName, templateID ){ + var templateBaseURL = 'static/scripts/templates/'; + this.log( 'loadViaHttpGet, templateFile:', templateFile, + 'templateName:', templateName, ', templateID:', templateID, + 'templateBaseURL:', this.templateBaseURL ); + + //??: possibly not the best pattern here... + // try in-dom first (prevent loading the same templateFile for each of its templates) + var template = null; + try { + template = this.loadInDomTemplate( templateFile, templateName, templateID ); + + // if that didn't work, load the templateFile via GET,... + } catch( exception ){ + this.log( 'getInDomTemplate exception:' + exception ); + // handle no compiler exception + if( !Handlebars.compile ){ throw( exception ); } + //TEST: + + this.log( "Couldn't locate template in DOM: " + templateID ); + var loader = this; + var url = templateBaseURL + templateFile; + //??: async : false may cause problems in the long run + jQuery.ajax( url, { + method : 'GET', + async : false, + success : function( data ){ + loader.log( templateFile + ' loaded via GET. Attempting compile...' ); + //...move the templateFile into the DOM and try that again + $( 'body' ).append( data ); + template = loader.loadInDomTemplate( templateFile, templateName, templateID ); + }, + error : function( data, status, xhr ){ + throw( 'Failed to fetch ' + url + ':' + status ); + } + }); + } + if( !template ){ + throw( "Couldn't load or fetch template: " + templateID ); + } + return template; + } + + //TEST: no compiler + //TEST: good url, good id, already local + //TEST: good url, good id, remote load + //TEST: good url, bad template id + //TEST: bad url, error from ajax +}); diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/mvc/history.js --- a/static/scripts/mvc/history.js +++ b/static/scripts/mvc/history.js @@ -1,3 +1,4 @@ +if( console ){ console.debug( 'history mvc loaded' ); } /* Backbone.js implementation of history panel @@ -13,6 +14,8 @@ convert function comments to /** style complete comments + don't draw body until it's first unhide event + as always: where does the model end and the view begin? HistoryPanel HistoryCollection: (collection of History: 'Saved Histories') @@ -30,23 +33,12 @@ poly HistoryItemView on: can/cant_edit */ -_ = _; //============================================================================== -var Loggable = { - // replace null with console (if available) to see all logs - logger : null, - - log : function(){ - return ( this.logger )?( this.logger.debug.apply( this, arguments ) ) - :( undefined ); - } -}; -var LoggingModel = BaseModel.extend( Loggable ); -var LoggingView = BaseView.extend( Loggable ); //============================================================================== //TODO: move to Galaxy obj./namespace, decorate for current page (as GalaxyPaths) +/* var Localizable = { localizedStrings : {}, setLocalizedString : function( str, localizedString ){ @@ -58,6 +50,7 @@ } }; var LocalizableView = LoggingView.extend( Localizable ); +*/ //TODO: wire up to views //============================================================================== @@ -93,7 +86,8 @@ } //============================================================================== -var HistoryItem = LoggingModel.extend({ +//TODO: use initialize (or validate) to check purged AND deleted -> purged XOR deleted +var HistoryItem = BaseModel.extend( LoggableMixin ).extend({ // a single HDA model // uncomment this out see log messages @@ -141,7 +135,11 @@ }, toString : function(){ - return 'HistoryItem(' + ( this.get( 'name' ) || this.get( 'id' ) || '' ) + ')'; + var nameAndId = this.get( 'id' ) || ''; + if( this.get( 'name' ) ){ + nameAndId += ':"' + this.get( 'name' ) + '"'; + } + return 'HistoryItem(' + nameAndId + ')'; } }); @@ -161,20 +159,19 @@ //============================================================================== -var HistoryItemView = LoggingView.extend({ +var HistoryItemView = BaseView.extend( LoggableMixin ).extend({ // view for HistoryItem model above // uncomment this out see log messages - logger : console, + //logger : console, tagName : "div", className : "historyItemContainer", - // ................................................................................ SET UP initialize : function(){ this.log( this + '.initialize:', this, this.model ); - return this; + }, // ................................................................................ RENDER MAIN @@ -190,12 +187,8 @@ .addClass( 'historyItemWrapper' ).addClass( 'historyItem' ) .addClass( 'historyItem-' + state ); - itemWrapper.append( this._render_purgedWarning() ); - itemWrapper.append( this._render_deletionWarning() ); - itemWrapper.append( this._render_visibleWarning() ); - + itemWrapper.append( this._render_warnings() ); itemWrapper.append( this._render_titleBar() ); - this.body = $( this._render_body() ); itemWrapper.append( this.body ); @@ -215,52 +208,9 @@ }, // ................................................................................ RENDER WARNINGS - //TODO: refactor into generalized warning widget/view - //TODO: refactor the three following - too much common ground - _render_purgedWarning : function(){ - // Render warnings for purged - //this.log( this + '_render_purgedWarning' ); - var warning = null; - if( this.model.get( 'purged' ) ){ - warning = $( HistoryItemView.STRINGS.purgedMsg ); - } - //this.log( 'warning:', warning ); - return warning; - }, - - _render_deletionWarning : function(){ - //this.log( this + '_render_deletionWarning' ); - // Render warnings for deleted items (and links: undelete and purge) - //pre: this.model.purge_url will be undefined if trans.app.config.allow_user_dataset_purge=False - var warningElem = null; - if( this.model.get( 'deleted' ) ){ - var warning = ''; - - if( this.model.get( 'undelete_url' ) ){ - warning += HistoryItemView.TEMPLATES.undeleteLink( this.model.attributes ); - } - if( this.model.get( 'purge_url' ) ){ - warning += HistoryItemView.TEMPLATES.purgeLink( this.model.attributes ); - } - // wrap it in the standard warning msg - warningElem = $( HistoryItemView.TEMPLATES.warningMsg({ warning: warning }) ); - } - //this.log( 'warning:', warning ); - return warningElem; - }, - - _render_visibleWarning : function(){ - //this.log( this + '_render_visibleWarning' ); - // Render warnings for hidden items (and link: unhide) - var warningElem = null; - if( !this.model.get( 'visible' ) && this.model.get( 'unhide_url' ) ){ - var warning = HistoryItemView.TEMPLATES.hiddenMsg( this.model.attributes ); - - // wrap it in the standard warning msg - warningElem = $( HistoryItemView.TEMPLATES.warningMsg({ warning: warning }) ); - } - //this.log( 'warning:', warning ); - return warningElem; + _render_warnings : function(){ + // jQ errs on building dom with whitespace - if there are no messages, trim -> '' + return $( jQuery.trim( HistoryItemView.templates.messages( this.model.toJSON() ) ) ); }, // ................................................................................ RENDER TITLEBAR @@ -292,23 +242,21 @@ //TODO: move other data (non-href) into {} in view definition, cycle over those keys in _titlebuttons //TODO: move disabled span data into view def, move logic into _titlebuttons _render_displayButton : function(){ - // render the display icon-button - // show a disabled display if the data's been purged - if( this.model.get( 'purged' ) ){ - return $( '<span class="icon-button display_disabled tooltip" ' + - 'title="Cannot display datasets removed from disk"></span>' ); - } - var id = this.model.get( 'id' ), - displayBtnData = { - //TODO: localized title - title : 'Display data in browser', - //TODO: need generated url here - href : '/datasets/' + id + '/display/?preview=True', - target : ( this.model.get( 'for_editing' ) )?( 'galaxy_main' ):( '' ), - classes : [ 'icon-button', 'tooltip', 'display' ], - dataset_id : id - }; - return $( linkHTMLTemplate( displayBtnData ) ); + displayBtnData = ( this.model.get( 'purged' ) )?({ + // show a disabled display if the data's been purged + title : 'Cannot display datasets removed from disk', + icon_class : 'display', + enabled : false, + }):({ + // if not, render the display icon-button with href + title : 'Display data in browser', + //TODO: need generated url here + href : this.model.get( 'display_url' ), + target : ( this.model.get( 'for_editing' ) )?( 'galaxy_main' ):( null ), + icon_class : 'display', + }); + this.displayButton = new IconButtonView({ model : new IconButton( displayBtnData ) }); + return this.displayButton.render().$el; }, _render_editButton : function(){ @@ -317,44 +265,42 @@ purged = this.model.get( 'purged' ), deleted = this.model.get( 'deleted' ); + var editBtnData = { + title : 'Edit attributes', + href : this.model.get( 'edit_url' ), + target : 'galaxy_main', + icon_class : 'edit' + } + // disable if purged or deleted and explain why in the tooltip + //TODO: if for_editing if( deleted || purged ){ - if( !purged ){ - return $( '<span class="icon-button edit_disabled tooltip" ' + - 'title="Undelete dataset to edit attributes"></span>' ); - } else { - return $( '<span class="icon-button edit_disabled tooltip" ' + - 'title="Cannot edit attributes of datasets removed from disk"></span>' ); - } + editBtnData = { + enabled : false, + icon_class : 'edit' + }; + editBtnData.title = ( !purged )?( 'Undelete dataset to edit attributes' ): + ( 'Cannot edit attributes of datasets removed from disk' ); } - return $( linkHTMLTemplate({ - title : 'Edit attributes', - //TODO: need generated url here - href : '/datasets/' + id + '/edit', - target : 'galaxy_main', - classes : [ 'icon-button', 'tooltip', 'edit' ] - }) ); + this.editButton = new IconButtonView({ model : new IconButton( editBtnData ) }); + return this.editButton.render().$el; }, _render_deleteButton : function(){ - // render the delete icon-button - var id = this.model.get( 'id' ), - purged = this.model.get( 'purged' ), - deleted = this.model.get( 'deleted' ); - - //??: WHAAAA? can_edit == deleted?? - // yes! : (history_common.mako) can_edit=( not ( data.deleted or data.purged ) ) - if( purged || deleted ){ - return $( '<span title="Dataset is already deleted" ' + - 'class="icon-button delete_disabled tooltip"></span>' ); - } - return $( linkHTMLTemplate({ + if( !this.model.get( 'for_editing' ) ){ return null; } + + var deleteBtnData = ( this.model.get( 'delete_url' ) )?({ title : 'Delete', - //TODO: need generated url here - href : '/datasets/' + id + '/delete?show_deleted_on_refresh=False', + href : this.model.get( 'delete_url' ), target : 'galaxy_main', - id : 'historyItemDeleter-' + id, - classes : [ 'icon-button', 'tooltip', 'delete' ] - })); + id : 'historyItemDeleter-' + this.model.get( 'id' ), + icon_class : 'delete' + }):({ + title : 'Dataset is already deleted', + icon_class : 'delete', + enabled : false + }); + this.deleteButton = new IconButtonView({ model : new IconButton( deleteBtnData ) }); + return this.deleteButton.render().$el; }, _render_titleLink : function(){ @@ -441,7 +387,7 @@ }); warningMsgText += 'You may be able to ' + editLink + '.'; } - parent.append( $( HistoryItemView.TEMPLATES.warningMsg({ warning: warningMsgText }) ) ); + parent.append( $( HistoryItemView.templates.warningMsg({ warning: warningMsgText }) ) ); //...render the remaining body as STATES.OK (only diff between these states is the box above) this._render_body_ok( parent ); @@ -497,7 +443,8 @@ // if trans.user if( this.model.get( 'retag_url' ) && this.model.get( 'annotate_url' ) ){ - // tags, annotation buttons and display areas + // tag, annotate buttons + //TODO: move to tag, Annot MV var tagsAnnotationsBtns = $( '<div style="float: right;"></div>' ); tagsAnnotationsBtns.append( $( linkHTMLTemplate({ title : 'Edit dataset tags', @@ -514,17 +461,20 @@ actionBtnDiv.append( tagsAnnotationsBtns ); actionBtnDiv.append( '<div style="clear: both"></div>' ); - var tagArea = $( '<div class="tag-area" style="display: none">' ); - tagArea.append( '<strong>Tags:</strong>' ); - tagArea.append( '<div class="tag-elt"></div>' ); - actionBtnDiv.append( tagArea ); + // tag/annot display areas + this.tagArea = $( '<div class="tag-area" style="display: none">' ); + this.tagArea.append( '<strong>Tags:</strong>' ); + this.tagElt = $( '<div class="tag-elt"></div>' ); + actionBtnDiv.append( this.tagArea.append( this.tagElt ) ); var annotationArea = $( ( '<div id="${dataset_id}-annotation-area"' + ' class="annotation-area" style="display: none">' ) ); + this.annotationArea = annotationArea; annotationArea.append( '<strong>Annotation:</strong>' ); - annotationArea.append( ( '<div id="${dataset_id}-annotation-elt" ' + this.annotationElem = $( '<div id="' + this.model.get( 'id' ) + '-annotation-elt" ' + 'style="margin: 1px 0px 1px 0px" class="annotation-elt tooltip editable-text" ' - + 'title="Edit dataset annotation"></div>' ) ); + + 'title="Edit dataset annotation"></div>' ); + annotationArea.append( this.annotationElem ); actionBtnDiv.append( annotationArea ); } } @@ -564,10 +514,10 @@ }, _render_body : function(){ - this.log( this + '_render_body' ); + //this.log( this + '_render_body' ); var state = this.model.get( 'state' ), for_editing = this.model.get( 'for_editing' ); - this.log( 'state:', state, 'for_editing', for_editing ); + //this.log( 'state:', state, 'for_editing', for_editing ); //TODO: incorrect id (encoded - use hid?) var body = $( '<div/>' ) @@ -708,10 +658,86 @@ // ................................................................................ EVENTS events : { - 'click .historyItemTitle' : 'toggleBodyVisibility' + 'click .historyItemTitle' : 'toggleBodyVisibility', + 'click a.icon-button.tags' : 'loadAndDisplayTags', + 'click a.icon-button.annotate' : 'loadAndDisplayAnnotation' }, // ................................................................................ STATE CHANGES / MANIPULATION + loadAndDisplayTags : function( event ){ + //TODO: this is a drop in from history.mako - should use MV as well + this.log( this, '.loadAndDisplayTags', event ); + var tagArea = this.tagArea; + var tagElt = this.tagElt; + + // Show or hide tag area; if showing tag area and it's empty, fill it. + if( tagArea.is( ":hidden" ) ){ + if( !tagElt.html() ){ + // Need to fill tag element. + $.ajax({ + url: this.model.get( 'ajax_get_tag_url' ), + error: function() { alert( "Tagging failed" ) }, + success: function(tag_elt_html) { + console.debug( 'tag_elt_html:', tag_elt_html ); + tagElt.html(tag_elt_html); + tagElt.find(".tooltip").tooltip(); + tagArea.slideDown("fast"); + } + }); + } else { + // Tag element is filled; show. + tagArea.slideDown("fast"); + } + + } else { + // Hide. + tagArea.slideUp("fast"); + } + return false; + }, + + loadAndDisplayAnnotation : function( event ){ + //TODO: this is a drop in from history.mako - should use MV as well + this.log( this, '.loadAndDisplayAnnotation', event ); + var annotationArea = this.annotationArea, + annotationElem = this.annotationElem, + setAnnotationUrl = this.model.get( 'ajax_set_annotation_url' ); + + // Show or hide annotation area; if showing annotation area and it's empty, fill it. + this.log( 'annotationArea hidden:', annotationArea.is( ":hidden" ) ); + this.log( 'annotationElem html:', annotationElem.html() ); + if ( annotationArea.is( ":hidden" ) ){ + if( !annotationElem.html() ){ + // Need to fill annotation element. + $.ajax({ + url: this.model.get( 'ajax_get_annotation_url' ), + error: function(){ alert( "Annotations failed" ) }, + success: function( htmlFromAjax ){ + if( htmlFromAjax === "" ){ + htmlFromAjax = "<em>Describe or add notes to dataset</em>"; + } + annotationElem.html( htmlFromAjax ); + annotationArea.find(".tooltip").tooltip(); + + async_save_text( + annotationElem.attr("id"), annotationElem.attr("id"), + setAnnotationUrl, + "new_annotation", 18, true, 4 + ); + annotationArea.slideDown("fast"); + } + }); + } else { + annotationArea.slideDown("fast"); + } + + } else { + // Hide. + annotationArea.slideUp("fast"); + } + return false; + }, + toggleBodyVisibility : function(){ this.log( this + '.toggleBodyVisibility' ); this.$el.find( '.historyItemBody' ).toggle(); @@ -723,30 +749,8 @@ return 'HistoryItemView(' + modelString + ')'; } }); - -//------------------------------------------------------------------------------ HistoryItemView.TEMPLATES = {}; -//TODO: move next one out - doesn't belong -HistoryItemView.TEMPLATES.warningMsg = - _.template( '<div class=warningmessagesmall><strong><%= warning %></strong></div>' ); - - -//??TODO: move into functions? -HistoryItemView.TEMPLATES.undeleteLink = _.template( - 'This dataset has been deleted. ' + - 'Click <a href="<%= undelete_url %>" class="historyItemUndelete" id="historyItemUndeleter-{{ id }}" ' + - ' target="galaxy_history">here</a> to undelete it.' ); - -HistoryItemView.TEMPLATES.purgeLink = _.template( - ' or <a href="<%= purge_url %>" class="historyItemPurge" id="historyItemPurger-{{ id }}"' + - ' target="galaxy_history">here</a> to immediately remove it from disk.' ); - -HistoryItemView.TEMPLATES.hiddenMsg = _.template( - 'This dataset has been hidden. ' + - 'Click <a href="<%= unhide_url %>" class="historyItemUnhide" id="historyItemUnhider-{{ id }}" ' + - ' target="galaxy_history">here</a> to unhide it.' ); - //TODO: contains localized strings HistoryItemView.TEMPLATES.hdaSummary = _.template([ '<%= misc_blurb %><br />', @@ -754,13 +758,18 @@ 'database: <%= dbkeyHTML %>' ].join( '' )); - -//------------------------------------------------------------------------------ -HistoryItemView.STRINGS = {}; -HistoryItemView.STRINGS.purgedMsg = HistoryItemView.TEMPLATES.warningMsg( - { warning: 'This dataset has been deleted and removed from disk.' }); - +//============================================================================== +//HistoryItemView.templates = InDomTemplateLoader.getTemplates({ +HistoryItemView.templates = CompiledTemplateLoader.getTemplates({ + 'common-templates.html' : { + warningMsg : 'template-warningmessagesmall', + }, + 'history-templates.html' : { + messages : 'template-history-warning-messages2', + hdaSummary : 'template-history-hdaSummary' + } +}); //============================================================================== var HistoryCollection = Backbone.Collection.extend({ @@ -773,10 +782,10 @@ //============================================================================== -var History = LoggingModel.extend({ +var History = BaseModel.extend( LoggableMixin ).extend({ // uncomment this out see log messages - logger : console, + //logger : console, // values from api (may need more) defaults : { @@ -867,11 +876,11 @@ }); //------------------------------------------------------------------------------ -var HistoryView = LoggingView.extend({ +var HistoryView = BaseView.extend( LoggableMixin ).extend({ // view for the HistoryCollection (as per current right hand panel) // uncomment this out see log messages - logger : console, + //logger : console, // direct attachment to existing element el : 'body.historyPage', @@ -908,8 +917,7 @@ //============================================================================== -//USE_MOCK_DATA = true; -if( window.USE_MOCK_DATA ){ +function createMockHistoryData(){ mockHistory = {}; mockHistory.data = { @@ -986,6 +994,7 @@ notvisible : _.extend( _.clone( mockHistory.data.template ), { visible : false }), + hasDisplayApps : _.extend( _.clone( mockHistory.data.template ), { display_apps : { @@ -1041,7 +1050,8 @@ failed_metadata : _.extend( _.clone( mockHistory.data.template ), { state : HistoryItem.STATES.FAILED_METADATA }) - +/* +*/ }); $( document ).ready( function(){ diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/mvc/tags.js --- /dev/null +++ b/static/scripts/mvc/tags.js @@ -0,0 +1,66 @@ +/*============================================================================== +Backbone MV for Tags + + +TODO: +move rendering from tagging_common.py +move functionality from controllers/tag.py +?? - polymorph on class or simply class as attr? + +==============================================================================*/ +/** Single Tag model + */ +var Tag = BaseModel.extend( LoggableMixin ).extend({ + + // uncomment this out see log messages + logger : console, + + defaults : { + id : null, + itemClass : null + }, + + toString : function(){ + return 'Tag()'; + } +}); + +//------------------------------------------------------------------------------ +/** Single Tag view + */ +var TagView = BaseView.extend( LoggableMixin ).extend({ + + // uncomment this out see log messages + logger : console, + + toString : function(){ + return 'TagView()'; + } +}); + +//============================================================================== +/** A collection of Tags + */ +var TagCollection = Backbone.Collection.extend( LoggableMixin ).extend({ + model : Tag, + + // uncomment this out see log messages + logger : console, + + toString : function(){ + return 'TagCollection()'; + } +}); + +//------------------------------------------------------------------------------ +/** View for a TagCollection (and it's controls) - as per an hda's tag controls on the history panel + */ +var TagList = BaseView.extend( LoggableMixin ).extend({ + + // uncomment this out see log messages + logger : console, + + toString : function(){ + return 'TagList()'; + } +}); \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/mvc/ui.js --- a/static/scripts/mvc/ui.js +++ b/static/scripts/mvc/ui.js @@ -1,41 +1,12 @@ /** * -- Functions for creating large UI elements. -- */ - -/** - * Returns an IconButtonMenuView for the provided configuration. - * Configuration is a list of dictionaries where each dictionary - * defines an icon button. Each dictionary must have the following - * elements: icon_class, title, and on_click. - */ -var create_icon_buttons_menu = function(config, global_config) { - if (!global_config) { global_config = {}; } - - // Create and initialize menu. - var buttons = new IconButtonCollection( - _.map(config, function(button_config) { - return new IconButton(_.extend(button_config, global_config)); - }) - ); - - return new IconButtonMenuView( {collection: buttons} ); -}; - +// ============================================================================= /** * -- Utility models and views for Galaxy objects. -- */ /** - * Necessary Galaxy paths. - */ -var GalaxyPaths = Backbone.Model.extend({ - defaults: { - root_path: "", - image_path: "" - } -}); - -/** * Clickable button represented as an icon. */ var IconButton = Backbone.Model.extend({ @@ -43,14 +14,107 @@ title: "", icon_class: "", on_click: null, - tooltip_config: {} + tooltip_config: {}, + + isMenuButton : true, + id : null, + href : null, + target : null, + enabled : true, + visible : true + } + + //validate : function( attributes ){ + //TODO: validate href or on_click + //TODO: validate icon_class + //} +}); + + +/** + * + */ +var IconButtonView = Backbone.View.extend({ + tagName : 'a', + className : 'icon-button', + + // jsHint.shut( up ) + hrefVoidFn : [ 'javascript', ':void(0)' ].join( '' ), + fadeSpeed : 0, + + initialize : function(){ + // better rendering this way (for me anyway) + this.model.attributes.tooltip_config = { placement : 'bottom' }; + this.model.bind( 'change', this.render, this ); + }, + + render : function(){ + //NOTE: this is meant to be rerendered on a model change + // - since most of the rendering is attributes, it gets complicated + //TODO: template would be faster, more concise - more PITA + var disabled_class = this.model.attributes.icon_class + '_disabled'; + + if( !this.model.attributes.visible ){ + //TODO: fancy - but may not work as you expect + this.$el.fadeOut( this.fadeSpeed ); + } + + if( this.model.attributes.enabled ){ + if( this.$el.hasClass( disabled_class ) ){ + this.$el.removeClass( disabled_class ); + } + this.$el.addClass( this.model.attributes.icon_class ); + } else { + this.$el.removeClass( this.model.attributes.icon_class ); + this.$el.addClass( disabled_class ); + } + + if( this.model.attributes.isMenuButton ){ + this.$el.addClass( 'menu-button' ); + } else { + this.$el.removeClass( 'menu-button' ); + } + + this.$el.data( 'tooltip', false ); + this.$el.attr( 'data-original-title', null ); + this.$el.removeClass( 'tooltip' ); + if( this.model.attributes.title ){ + this.$el.attr( 'title', this.model.attributes.title ).addClass( 'tooltip' ); + this.$el.tooltip( this.model.attributes.tooltip_config ); + } + + this.$el.attr( 'id', ( this.model.attributes.id )?( this.model.attributes.id ):( null ) ); + this.$el.attr( 'target', ( this.model.attributes.target )?( this.model.attributes.target ):( null ) ); + this.$el.attr( 'href', ( this.model.attributes.href && !this.model.attributes.on_click )? + ( this.model.attributes.href ):( this.hrefVoidFn ) ); + + if( this.model.attributes.visible ){ + this.$el.fadeIn( this.fadeSpeed ); + } + return this; + }, + + events : { + 'click' : 'click' + }, + + click : function( event ){ + // if on_click pass to that function + if( this.model.attributes.on_click ){ + this.model.attributes.on_click( event ); + return false; + } + // otherwise, bubble up (to href or whatever) + return true; } }); + var IconButtonCollection = Backbone.Collection.extend({ model: IconButton }); +//------------------------------------------------------------------------------ /** * Menu with multiple icon buttons. Views are not needed nor used for individual buttons. */ @@ -82,6 +146,27 @@ }); /** + * Returns an IconButtonMenuView for the provided configuration. + * Configuration is a list of dictionaries where each dictionary + * defines an icon button. Each dictionary must have the following + * elements: icon_class, title, and on_click. + */ +var create_icon_buttons_menu = function(config, global_config) { + if (!global_config) { global_config = {}; } + + // Create and initialize menu. + var buttons = new IconButtonCollection( + _.map(config, function(button_config) { + return new IconButton(_.extend(button_config, global_config)); + }) + ); + + return new IconButtonMenuView( {collection: buttons} ); +}; + + +// ============================================================================= +/** * */ var Grid = Backbone.Collection.extend({ @@ -95,4 +180,101 @@ }); +// ============================================================================= +/** + * Necessary Galaxy paths. + */ +var GalaxyPaths = Backbone.Model.extend({ + defaults: { + root_path: "", + image_path: "" + } +}); +// ============================================================================= +/** Global string localization object (and global short form alias) + * set with either: + * GalaxyLocalization.setLocalizedString( original, localized ) + * GalaxyLocalization.setLocalizedString({ original1 : localized1, original2 : localized2 }) + * get with either: + * _l( original ) + */ +//TODO: move to Galaxy.Localization +var GalaxyLocalization = jQuery.extend({}, { + aliasName : '_l', + localizedStrings : {}, + + setLocalizedString : function( str_or_obj, localizedString ){ + // pass in either two strings (english, translated) or an obj (map) of english : translated attributes + //console.debug( this + '.setLocalizedString:', str_or_obj, localizedString ); + var self = this; + + // DRY non-duplicate assignment function + var setStringIfNotDuplicate = function( original, localized ){ + // do not set if identical - strcmp expensive but should only happen once per page per word + if( original !== localized ){ + self.localizedStrings[ original ] = localized; + } + }; + + if( jQuery.type( str_or_obj ) === "string" ){ + setStringIfNotDuplicate( str_or_obj, localizedString ); + + } else if( jQuery.type( str_or_obj ) === "object" ){ + jQuery.each( str_or_obj, function( key, val ){ + //console.debug( 'key=>val', key, '=>', val ); + // could recurse here but no reason + setStringIfNotDuplicate( key, val ); + }); + + } else { + throw( 'Localization.setLocalizedString needs either a string or object as the first argument,' + + ' given: ' + str_or_obj ); + } + }, + + localize : function( strToLocalize ){ + //console.debug( this + '.localize:', strToLocalize ); + // return the localized version if it's there, the strToLocalize if not + // try/catch cheaper than if in + try { + //var localized = this.localizedStrings[ strToLocalize ]; + //return localized; + return this.localizedStrings[ strToLocalize ]; + } catch( err ){ + //TODO??: potentially problematic catch all + //console.error( err ); + return strToLocalize; + } + }, + + toString : function(){ return 'GalaxyLocalization'; } +}); + +// global localization alias +window[ GalaxyLocalization.aliasName ] = function( str ){ return GalaxyLocalization.localize( str ); }; + +//TEST: setLocalizedString( string, string ), _l( string ) +//TEST: setLocalizedString( hash ), _l( string ) +//TEST: setLocalizedString( string === string ), _l( string ) +//TEST: _l( non assigned string ) + + +// ============================================================================= +/** UI icon-button (Backbone.View only - no model) + * + */ + + + + + + + + + + + + + + diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/common-templates.html --- /dev/null +++ b/static/scripts/templates/common-templates.html @@ -0,0 +1,3 @@ +<script type="text/template" class="template-common" id="template-warningmessagesmall"> + <div class=warningmessagesmall><strong>{{{warning}}}</strong></div> +</script> diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/compile_templates.py --- a/static/scripts/templates/compile_templates.py +++ b/static/scripts/templates/compile_templates.py @@ -3,24 +3,274 @@ # Script requires handlebars compiler be installed; use node package manager # to install handlebars. +"""%prog [options] [ specific .handlebars or .html template file to compile ] + Compiles handlebars templates into javascript for better load times and performance. + + Handlebars compilation errors will print to stderr. + NOTE!: you will need node.js, npm, and handlebars (installed from npm) in order to compile templates + See http://handlebarsjs.com/precompilation.html for more info. + + Should be called from the $GLX_ROOT/static/scripts/templates directory. + Compiled templates will be written to the compiled/ directory. + + If the --multi-ext option is passed a file extension string, the script will: + * search for *.<extension> in the current directory, + * extract each '<script type="text/template"> from the file and write them into a new handlebars file + named using the id of the script tag (eg. id="undeleteMsg" -> undeleteMsg.handlebars) + * these new handlebars scripts will be compiled along with any other handlebars templates in the dir + + additionally for each of those files, the script will: + * extract each '<script type="text/javascript"> from the file and write them ALL into a single js file + named using the prefix 'helpers-' and the basename of the enclosing file + (eg. common-templates.html -> helpers-common-templates.js ) + * these new handlebars scripts will be compiled along with any other handlebars templates in the dir + These files can be included as if they were templates (eg. h.templates( 'helpers-common-templates' ) ) + + This hopefully will allow both multiple templates and their associated Handlebars helper functions to be + included in one file. + + Currently, this works (best?) if: + * the multiple templates are within one html file + * each template is enclosed in a <script> tag with type="text/template" and some id attribute + * each helper fn is enclosed in a <script> tag with type="text/javascript" and some id attribute + e.g. <script type="text/template" id="history-deletedMsg" class="template">Deleted {{ msg }}</script> + +""" +# ------------------------------------------------------------------------------ + +TODO = """ +""" + import sys from glob import glob from subprocess import call from shutil import copyfile from os import path +from optparse import OptionParser +from HTMLParser import HTMLParser -cmd = "handlebars %s -f compiled/%s.js" +import logging +log = logging.getLogger( __name__ ) -# If specific scripts specified on command line, just pack them, otherwise pack -# all. +COMPILED_DIR = 'compiled' +COMPILED_EXT = '.js' +COMPILE_CMD_STR = "handlebars %s -f %s" +COMPILE_MINIMIZE_SWITCH = ' -m' -if len( sys.argv ) > 1: - to_pack = sys.argv[1:] -else: - to_pack = glob( "*.handlebars" ) +# both of these are off by default for backward compat +DEFAULT_MINIMIZATION = False +DEFAULT_MULTI_EXT = None #'.html' + +# ------------------------------------------------------------------------------ +class HTMLMultiTemplateParser( HTMLParser ): + """Parses multiple templates from an HTML file, saving them to a map of: + { id : template_text, ... } + + Templates must: + * be within the TEMPLATE_TAG + * TEMPLATE_TAG must have a type attribute + * that attr must == TEMPLATE_TYPE + * TEMPLATE_TAG cannot be nested within one another + * TEMPLATE_TAG must have an id attribute + """ + TEMPLATE_TAG = 'script' + TEMPLATE_TYPES = [ 'text/template' ] -for fname in to_pack: - fname_base = path.splitext( path.split( fname )[1] )[0] - print "%s --> compiled/%s.js" % ( fname, fname_base ) - out = call( cmd % ( fname, fname_base ), shell=True ) + HELPER_TAG = 'script' + HELPER_TYPES = [ 'text/javascript' ] + + def __init__( self ): + HTMLParser.__init__( self ) + self.templates = {} + self.curr_template_id = None + self.template_data = '' + + self.helpers = {} + self.curr_helper_id = None + self.helper_data = '' + + def is_template_tag( self, tag, attr_dict ): + # both tag and type attr must match + return ( ( tag == self.TEMPLATE_TAG ) + and ( 'type' in attr_dict ) + and ( attr_dict[ 'type' ] in self.TEMPLATE_TYPES ) ) + + def is_helper_tag( self, tag, attr_dict ): + # both tag and type attr must match + return ( ( tag == self.HELPER_TAG ) + and ( 'type' in attr_dict ) + and ( attr_dict[ 'type' ] in self.HELPER_TYPES ) ) + + def handle_starttag( self, tag, attrs ): + attr_dict = dict( attrs ) + if self.is_template_tag( tag, attr_dict ): + log.debug( "\t template tag: %s, %s", tag, str( attr_dict ) ); + + # as far as I know these tags can't/shouldn't nest/overlap + #pre: not already inside a template/helper tag + assert self.curr_template_id == None, "Found nested template tag: %s" % ( self.curr_template_id ) + assert self.curr_helper_id == None, "Found template tag inside helper: %s" % ( self.curr_helper_id ) + #pre: must have an id + assert 'id' in attr_dict, "No id attribute in template: " + str( attr_dict ) + + self.curr_template_id = attr_dict[ 'id' ] + + elif self.is_helper_tag( tag, attr_dict ): + log.debug( "\t helper tag: %s, %s", tag, str( attr_dict ) ); + + #pre: not already inside a template/helper tag + assert self.curr_helper_id == None, "Found nested helper tag: %s" % ( self.curr_helper_id ) + assert self.curr_template_id == None, "Found helper tag inside template: %s" % ( self.curr_template_id ) + #pre: must have an id + assert 'id' in attr_dict, "No id attribute in helper: " + str( attr_dict ) + + self.curr_helper_id = attr_dict[ 'id' ] + + def handle_endtag( self, tag ): + if( ( tag == self.TEMPLATE_TAG ) + and ( self.curr_template_id ) ): + log.debug( "\t ending template tag :", tag, self.curr_template_id ); + + # store the template data by the id + if self.template_data: + self.templates[ self.curr_template_id ] = self.template_data + + #! reset for next template + self.curr_template_id = None + self.template_data = '' + + elif( ( tag == self.HELPER_TAG ) + and ( self.curr_helper_id ) ): + log.debug( "\t ending helper tag :", tag, self.curr_template_id ); + + # store the template data by the id + if self.helper_data: + self.helpers[ self.curr_helper_id ] = self.helper_data + + #! reset for next template + self.curr_helper_id = None + self.helper_data = '' + + def handle_data(self, data): + data = data.strip() + if data: + if self.curr_template_id: + log.debug( "\t template text :", data ); + self.template_data += data + + elif self.curr_helper_id: + log.debug( "\t helper js fn :", data ); + self.helper_data += data + + +# ------------------------------------------------------------------------------ +def break_multi_template( multi_template_filename ): + """parse the multi template, writing each template into a new handlebars tmpl and returning their names""" + template_filenames = [] + parser = HTMLMultiTemplateParser() + + # parse the multi template + print "\nBreaking multi-template file %s into individual templates and helpers:" % ( multi_template_filename ) + with open( multi_template_filename, 'r' ) as multi_template_file: + # wish I could use a gen here + parser.feed( multi_template_file.read() ) + + # after breaking, write each indiv. template and save the names + for template_id, template_text in parser.templates.items(): + handlebar_template_filename = template_id + '.handlebars' + with open( handlebar_template_filename, 'w' ) as handlebar_template_file: + handlebar_template_file.write( template_text ) + + template_filenames.append( handlebar_template_filename ) + + # write all helpers to a 'helper-' prefixed js file in the compilation dir + if parser.helpers: + helper_filename = 'helpers-' + path.splitext( multi_template_filename )[0] + '.js' + helper_filename = path.join( COMPILED_DIR, helper_filename ) + with open( helper_filename, 'w' ) as helper_file: + for helper_fn_name, helper_fn in parser.helpers.items(): + print '(helper)', helper_fn_name + helper_file.write( helper_fn + '\n' ) + + print '\n'.join( template_filenames ) + return template_filenames + + +# ------------------------------------------------------------------------------ +def compile_template( template_filename, minimize=False ): + """compile the given template file (optionally minimizing the js) using subprocess. + + Use the basename of the template file for the outputed js. + """ + template_basename = path.splitext( path.split( template_filename )[1] )[0] + compiled_filename = path.join( COMPILED_DIR, template_basename + COMPILED_EXT ) + + command_string = COMPILE_CMD_STR % ( template_filename, compiled_filename ) + if minimize: + command_string += COMPILE_MINIMIZE_SWITCH + print command_string + return call( command_string, shell=True ) + + +# ------------------------------------------------------------------------------ +def main( options, args ): + """Break multi template files into single templates, compile all single templates. + + If args, compile that as a list of specific handlebars and/or multi template files. + """ + print "(Call this script with the '-h' option for more help)" + + handlebars_templates = [] + # If specific scripts specified on command line, just compile them, + if len( args ) >= 1: + handlebars_templates = filter( lambda( x ): x.endswith( '.handlebars' ), args ) + + # otherwise compile all in the current dir + else: + handlebars_templates = glob( '*.handlebars' ) + + # if desired, break up any passed-in or found multi template files + # adding the names of the new single templates to those needing compilation + if options.multi_ext: + multi_templates = [] + if len( args ) >= 1: + multi_templates = filter( lambda( x ): x.endswith( options.multi_ext ), args ) + else: + multi_templates = glob( '*' + options.multi_ext ) + + for multi_template_filename in multi_templates: + handlebars_templates.extend( break_multi_template( multi_template_filename ) ) + + # unique filenames only (Q&D) + handlebars_templates = list( set( handlebars_templates ) ) + + # compile the templates + print "\nCompiling templates:" + filenames_w_possible_errors = [] + for handlebars_template in handlebars_templates: + shell_ret = compile_template( handlebars_template, options.minimize ) + if shell_ret: + filenames_w_possible_errors.append( handlebars_template ) + + # report any possible errors + if filenames_w_possible_errors: + print "\nThere may have been errors on the following files:" + print ',\n'.join( filenames_w_possible_errors ) + print "\nCall this script with the '-h' for more help" + + +# ------------------------------------------------------------------------------ +if __name__ == '__main__': + optparser = OptionParser( usage=__doc__ ) + optparser.add_option( '-m', '--minimize', dest='minimize', action='store_true', default=DEFAULT_MINIMIZATION, + help=( 'minimize compiled template scripts via handlebars ' + + '(defaults to %s)' % DEFAULT_MINIMIZATION ) ) + optparser.add_option( '--multi-ext', dest='multi_ext', metavar="FILE_EXTENSION", default=DEFAULT_MULTI_EXT, + help=( 'indicates that files ending with the given string contain multiple ' + + 'templates and the script should break those into individual ' + + 'handlebars templates (defaults to "%s")' ) % DEFAULT_MULTI_EXT ) + + ( options, args ) = optparser.parse_args() + sys.exit( main( options, args ) ) + \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/compiled/template-history-hdaSummary.js --- /dev/null +++ b/static/scripts/templates/compiled/template-history-hdaSummary.js @@ -0,0 +1,25 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['template-history-hdaSummary'] = template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; + var buffer = "", stack1, foundHelper, functionType="function", escapeExpression=this.escapeExpression; + + + foundHelper = helpers.misc_blurb; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.misc_blurb; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "<br />\nformat: <span class=\""; + foundHelper = helpers.data_type; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.data_type; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\">"; + foundHelper = helpers.data_type; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.data_type; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "</span>, ',\ndatabase: "; + foundHelper = helpers.dbkeyHTML; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.dbkeyHTML; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1); + return buffer;}); +})(); \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/compiled/template-history-warning-messages.js --- /dev/null +++ b/static/scripts/templates/compiled/template-history-warning-messages.js @@ -0,0 +1,87 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['template-history-warning-messages'] = template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; + var buffer = "", stack1, functionType="function", escapeExpression=this.escapeExpression, self=this; + +function program1(depth0,data) { + + var buffer = "", stack1; + buffer += "\n<div class=warningmessagesmall><strong>\nThis dataset has been deleted.\n "; + stack1 = depth0.undelete_url; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(2, program2, data)}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n</strong></div>\n"; + return buffer;} +function program2(depth0,data) { + + var buffer = "", stack1, foundHelper; + buffer += "\n Click <a href=\""; + foundHelper = helpers.undelete_url; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.undelete_url; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\" class=\"historyItemUndelete\" id=\"historyItemUndeleter-"; + foundHelper = helpers.id; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\"\n target=\"galaxy_history\">here</a> to undelete it\n "; + stack1 = depth0.purge_url; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(3, program3, data)}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n "; + return buffer;} +function program3(depth0,data) { + + var buffer = "", stack1, foundHelper; + buffer += "\n or\n <a href=\""; + foundHelper = helpers.purge_url; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.purge_url; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\" class=\"historyItemPurge\" id=\"historyItemPurger-"; + foundHelper = helpers.id; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\"\n target=\"galaxy_history\">here</a> to immediately remove it from disk\n "; + return buffer;} + +function program5(depth0,data) { + + + return "\n<div class=warningmessagesmall><strong>\nThis dataset has been deleted and removed from disk.\n</strong></div>\n";} + +function program7(depth0,data) { + + var buffer = "", stack1; + buffer += "\n<div class=warningmessagesmall><strong>\nThis dataset has been hidden.\n "; + stack1 = depth0.undelete_url; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(8, program8, data)}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n</strong></div>\n"; + return buffer;} +function program8(depth0,data) { + + var buffer = "", stack1, foundHelper; + buffer += "\n Click <a href=\""; + foundHelper = helpers.unhide_url; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.unhide_url; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\" class=\"historyItemUnhide\" id=\"historyItemUnhider-"; + foundHelper = helpers.id; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.id; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + buffer += escapeExpression(stack1) + "\"\n target=\"galaxy_history\">here</a> to unhide it\n "; + return buffer;} + + stack1 = depth0.deleted; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(1, program1, data)}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n\n"; + stack1 = depth0.purged; + stack1 = helpers['if'].call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(5, program5, data)}); + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "\n\n"; + stack1 = depth0.visible; + stack1 = helpers.unless.call(depth0, stack1, {hash:{},inverse:self.noop,fn:self.program(7, program7, data)}); + if(stack1 || stack1 === 0) { buffer += stack1; } + return buffer;}); +})(); \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/compiled/template-warningmessagesmall.js --- /dev/null +++ b/static/scripts/templates/compiled/template-warningmessagesmall.js @@ -0,0 +1,15 @@ +(function() { + var template = Handlebars.template, templates = Handlebars.templates = Handlebars.templates || {}; +templates['template-warningmessagesmall'] = template(function (Handlebars,depth0,helpers,partials,data) { + helpers = helpers || Handlebars.helpers; + var buffer = "", stack1, foundHelper, functionType="function"; + + + buffer += "<div class=warningmessagesmall><strong>"; + foundHelper = helpers.warning; + if (foundHelper) { stack1 = foundHelper.call(depth0, {hash:{}}); } + else { stack1 = depth0.warning; stack1 = typeof stack1 === functionType ? stack1() : stack1; } + if(stack1 || stack1 === 0) { buffer += stack1; } + buffer += "</strong></div>"; + return buffer;}); +})(); \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/history-templates.html --- /dev/null +++ b/static/scripts/templates/history-templates.html @@ -0,0 +1,38 @@ +<script type="text/template" class="template-history" id="template-history-warning-messages"> +{{#if deleted}} +<div class=warningmessagesmall><strong> +This dataset has been deleted. + {{#if undelete_url}} + Click <a href="{{ undelete_url }}" class="historyItemUndelete" id="historyItemUndeleter-{{ id }}" + target="galaxy_history">here</a> to undelete it + {{#if purge_url}} + or + <a href="{{ purge_url }}" class="historyItemPurge" id="historyItemPurger-{{ id }}" + target="galaxy_history">here</a> to immediately remove it from disk + {{/if}} + {{/if}} +</strong></div> +{{/if}} + +{{#if purged}} +<div class=warningmessagesmall><strong> +This dataset has been deleted and removed from disk. +</strong></div> +{{/if}} + +{{#unless visible}} +<div class=warningmessagesmall><strong> +This dataset has been hidden. + {{#if undelete_url}} + Click <a href="{{ unhide_url }}" class="historyItemUnhide" id="historyItemUnhider-{{ id }}" + target="galaxy_history">here</a> to unhide it + {{/if}} +</strong></div> +{{/unless}} +</script> + +<script type="text/template" class="template-history" id="template-history-hdaSummary"> +{{ misc_blurb }}<br /> +format: <span class="{{ data_type }}">{{ data_type }}</span>, ', +database: {{ dbkeyHTML }} +</script> diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/template-history-hdaSummary.handlebars --- /dev/null +++ b/static/scripts/templates/template-history-hdaSummary.handlebars @@ -0,0 +1,3 @@ +{{ misc_blurb }}<br /> +format: <span class="{{ data_type }}">{{ data_type }}</span>, ', +database: {{ dbkeyHTML }} \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/template-history-warning-messages.handlebars --- /dev/null +++ b/static/scripts/templates/template-history-warning-messages.handlebars @@ -0,0 +1,30 @@ +{{#if deleted}} +<div class=warningmessagesmall><strong> +This dataset has been deleted. + {{#if undelete_url}} + Click <a href="{{ undelete_url }}" class="historyItemUndelete" id="historyItemUndeleter-{{ id }}" + target="galaxy_history">here</a> to undelete it + {{#if purge_url}} + or + <a href="{{ purge_url }}" class="historyItemPurge" id="historyItemPurger-{{ id }}" + target="galaxy_history">here</a> to immediately remove it from disk + {{/if}} + {{/if}} +</strong></div> +{{/if}} + +{{#if purged}} +<div class=warningmessagesmall><strong> +This dataset has been deleted and removed from disk. +</strong></div> +{{/if}} + +{{#unless visible}} +<div class=warningmessagesmall><strong> +This dataset has been hidden. + {{#if undelete_url}} + Click <a href="{{ unhide_url }}" class="historyItemUnhide" id="historyItemUnhider-{{ id }}" + target="galaxy_history">here</a> to unhide it + {{/if}} +</strong></div> +{{/unless}} \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 static/scripts/templates/template-warningmessagesmall.handlebars --- /dev/null +++ b/static/scripts/templates/template-warningmessagesmall.handlebars @@ -0,0 +1,1 @@ +<div class=warningmessagesmall><strong>{{{warning}}}</strong></div> \ No newline at end of file diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 templates/base.mako --- a/templates/base.mako +++ b/templates/base.mako @@ -26,7 +26,18 @@ ## <!--[if lt IE 7]> ## <script type='text/javascript' src="/static/scripts/libs/IE/IE7.js"></script> ## <![endif]--> - ${h.js( "libs/jquery/jquery", "libs/bootstrap", "galaxy.base", "libs/underscore", "libs/backbone/backbone", "libs/backbone/backbone-relational", "libs/handlebars.runtime", "mvc/ui" )} + + ${h.js( + "libs/jquery/jquery", + "libs/bootstrap", + "galaxy.base", + "libs/underscore", + "libs/backbone/backbone", + "libs/backbone/backbone-relational", + "libs/handlebars.runtime", + "mvc/ui" + )} + <script type="text/javascript"> // Set up needed paths. var galaxy_paths = new GalaxyPaths({ diff -r 244b4cb100d19ed7d005fabcf2390204b3630aa5 -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 templates/root/alternate_history.mako --- a/templates/root/alternate_history.mako +++ b/templates/root/alternate_history.mako @@ -54,7 +54,7 @@ <%def name="get_urls_for_hda( hda, encoded_data_id, for_editing )"><% from galaxy.datatypes.metadata import FileParameter - + data_dict = {} def add_to_data( **kwargs ): data_dict.update( kwargs ) @@ -79,7 +79,10 @@ #TODO??: better way to do urls (move into js galaxy_paths (decorate) - _not_ dataset specific) deleted = hda.deleted - purged = hda.purged or hda.dataset.purged + purged = hda.purged + + #purged = purged or hda.dataset.purged //?? + # all of these urls are 'datasets/data_id/<action> if not ( dataset_purged or purged ) and for_editing: add_to_data( undelete_url=h.url_for( controller='dataset', action='undelete', dataset_id=encoded_data_id ) ) @@ -106,7 +109,21 @@ display_url = h.url_for( controller='dataset', action='display', dataset_id=encoded_data_id, preview=True, filename='' ) add_to_data( display_url=display_url ) - if app.config.get_bool( 'enable_tracks', False ) and hda.ext in app.datatypes_registry.get_available_tracks(): + # edit attr button + if for_editing: + if not( hda.deleted or hda.purged ): + edit_url = h.url_for( controller='dataset', action='edit', dataset_id=encoded_data_id ) + add_to_data( edit_url=edit_url ) + + # delete button + if for_editing and not ( dataset_purged or purged ): + add_to_data( delete_url=h.url_for( controller='dataset', action='delete', dataset_id=encoded_data_id ) ) + ##TODO: loose end + ##show_deleted_on_refresh=show_deleted_on_refresh ) ) + #show_deleted_on_refresh=False ) ) + + #print app + if hda.ext in app.datatypes_registry.get_available_tracks(): # do these need _localized_ dbkeys? trackster_urls = {} if hda.dbkey != '?': @@ -117,11 +134,21 @@ trackster_urls[ 'hda-url' ] = data_url trackster_urls[ 'action-url' ] = h.url_for( controller='tracks', action='browser', dataset_id=encoded_data_id ) trackster_urls[ 'new-url' ] = h.url_for( controller='tracks', action='index', dataset_id=encoded_data_id, default_dbkey=hda.dbkey ) - add_to_data( trackster_url=trackster_url ) + add_to_data( trackster_url=trackster_urls ) if trans.user: - add_to_data( retag_url=( h.url_for( controller='tag', action='retag', item_class=hda.__class__.__name__, item_id=encoded_data_id ) ) ) + add_to_data( ajax_get_tag_url=( h.url_for( + controller='tag', action='get_tagging_elt_async', + item_class=hda.__class__.__name__, item_id=encoded_data_id ) ) ) + add_to_data( retag_url=( h.url_for( + controller='tag', action='retag', + item_class=hda.__class__.__name__, item_id=encoded_data_id ) ) ) + add_to_data( annotate_url=( h.url_for( controller='dataset', action='annotate', id=encoded_data_id ) ) ) + add_to_data( ajax_get_annotation_url=( h.url_for( + controller='dataset', action='get_annotation_async', id=encoded_data_id ) ) ) + add_to_data( ajax_set_annotation_url=( h.url_for( + controller='/dataset', action='annotate_async', id=encoded_data_id ) ) ) display_type_display_links = {} #TODO: this doesn't seem to get called with the hda I'm using. What would call this? How can I spoof it? @@ -182,8 +209,8 @@ data_dict.update( kwargs ) # trans + add_to_data( non_encoded_id=data.id ) encoded_data_id = trans.security.encode_id( data.id ) - #print data.name, encoded_data_id add_to_data( id=encoded_data_id ) # localize dbkey @@ -205,8 +232,8 @@ add_to_data( accessible=accessible ) url_dict = get_urls_for_hda( data, encoded_data_id, for_editing ) + data_dict.update( url_dict ) #print 'url_dict:', pformat( url_dict, indent=2 ) - data_dict.update( url_dict ) #print 'data_dict:', pformat( data_dict, indent=2 ), "\n" #print data_dict @@ -227,7 +254,7 @@ user_is_admin = trans.user_is_admin() user_roles = trans.get_current_user_roles() prepped_hdas = [ - prep_hda( hda, true ) for hda in datasets ] + prep_hda( hda, True ) for hda in datasets ] context_dict = { 'history' : { @@ -257,74 +284,66 @@ <%def name="javascripts()"> ${parent.javascripts()} - ${h.templates( "tool_link", "panel_section", "tool_search" )} - ${h.js( "galaxy.base", "json2", "autocomplete_tagging", "bootstrap", "mvc/tools", "mvc/history" )} + + ${h.js( + "libs/json2", "libs/jquery/jstorage", "libs/jquery/jquery.autocomplete", + ##"libs/handlebars.full", + "galaxy.autocom_tagging", + "mvc/base-mvc", "mvc/ui" + )} + + ${h.templates( + "helpers-common-templates", + "template-warningmessagesmall", + + "template-history-warning-messages", + "template-history-hdaSummary" + )} + + ## if using in-dom templates they need to go here (before the Backbone classes are defined) + ##NOTE: it's impossible(?) to include _ templates in this way bc they use identical delims as mako + ## (without altering Underscore.templateSettings) + ##<%include file="../../static/scripts/templates/common-templates.html" /> + ##<%include file="../../static/scripts/templates/history-templates.html" /> + + ${h.js( + "mvc/history" + ##"mvc/tags", "mvc/annotations" + )} <script type="text/javascript"> - ##//var localization = local( ${ create_localization_json( get_page_localized_strings() ) } ); + GalaxyLocalization.setLocalizedString( ${ create_localization_json( get_page_localized_strings() ) } ); + // add needed controller urls to GalaxyPaths + galaxy_paths.set( 'dataset_path', "${h.url_for( controller='dataset' )}" ) + // Init. on document load. var pageData = ${context_to_js()}; - // add needed controller urls to GalaxyPaths - galaxy_paths.set( 'dataset_path', "${h.url_for( controller='dataset' )}" ) + //USE_MOCK_DATA = true; $(function(){ if( console && console.debug ){ console.debug( 'using backbone.js in history panel' ); } - if( window.USE_MOCK_DATA ){ return; } + if( window.USE_MOCK_DATA ){ + if( console && console.debug ){ console.debug( '\t using mock data' ); } + createMockHistoryData(); + return; + } glx_history = new History( pageData.history ).loadDatasetsAsHistoryItems( pageData.hdas ); glx_history_view = new HistoryView({ model: glx_history }); glx_history_view.render(); - //hi = glx_history.items.at( 0 ); - //hi_view = new HistoryItemView({ model: hi }); - //$( 'body' ).append( hi_view.render() ); + hi = glx_history.items.at( 0 ); + hi_view = new HistoryItemView({ model: hi }); + $( 'body' ).append( hi_view.render() ); }); </script> - - <script type='text/javascript'> -function tag_handling(parent_elt) { - $(parent_elt).find("a.icon-button.tags").each( function() { - // Use links parameters but custom URL as ajax URL. - $(this).click( function() { - // Get tag area, tag element. - var history_item = $(this).parents(".historyItem"); - var tag_area = history_item.find(".tag-area"); - var tag_elt = history_item.find(".tag-elt"); - - // Show or hide tag area; if showing tag area and it's empty, fill it. - if ( tag_area.is( ":hidden" ) ) { - if (!tag_elt.html()) { - // Need to fill tag element. - var href_parms = $(this).attr("href").split("?")[1]; - var ajax_url = "${h.url_for( controller='tag', action='get_tagging_elt_async' )}?" + href_parms; - $.ajax({ - url: ajax_url, - error: function() { alert( "Tagging failed" ) }, - success: function(tag_elt_html) { - tag_elt.html(tag_elt_html); - tag_elt.find(".tooltip").tooltip(); - tag_area.slideDown("fast"); - } - }); - } else { - // Tag element is filled; show. - tag_area.slideDown("fast"); - } - } else { - // Hide. - tag_area.slideUp("fast"); - } - return false; - }); - }); -}; - </script></%def><%def name="stylesheets()"> ${parent.stylesheets()} - <style> + ${h.css("base", "history", "autocomplete_tagging" )} + <style>" .historyItemBody { display: none; } https://bitbucket.org/galaxy/galaxy-central/changeset/e2cb958b3f75/ changeset: e2cb958b3f75 user: carlfeberhard date: 2012-09-12 17:09:09 summary: clean debug statements from history.js affected #: 1 file diff -r ce64f5d64ff4a55d85bd0e9ba2c3ec712357c115 -r e2cb958b3f7596744bb08febc858fa25139fd1ac static/scripts/mvc/history.js --- a/static/scripts/mvc/history.js +++ b/static/scripts/mvc/history.js @@ -1,4 +1,3 @@ -if( console ){ console.debug( 'history mvc loaded' ); } /* Backbone.js implementation of history panel @@ -678,7 +677,7 @@ url: this.model.get( 'ajax_get_tag_url' ), error: function() { alert( "Tagging failed" ) }, success: function(tag_elt_html) { - console.debug( 'tag_elt_html:', tag_elt_html ); + this.log( 'tag_elt_html:', tag_elt_html ); tagElt.html(tag_elt_html); tagElt.find(".tooltip").tooltip(); tagArea.slideDown("fast"); Repository URL: https://bitbucket.org/galaxy/galaxy-central/ -- This is a commit notification from bitbucket.org. You are receiving this because you have the service enabled, addressing the recipient of this email.