| 1 | #!/usr/bin/python |
|---|
| 2 | # -*- encoding: utf-8 -*- |
|---|
| 3 | |
|---|
| 4 | from django import template |
|---|
| 5 | from django.template import Context |
|---|
| 6 | from django.template.loader import render_to_string |
|---|
| 7 | |
|---|
| 8 | register = template.Library() |
|---|
| 9 | |
|---|
| 10 | LOADER_URL = 'http://yui.yahooapis.com/2.5.1/build/' \ |
|---|
| 11 | 'yuiloader/yuiloader-beta-min.js' |
|---|
| 12 | |
|---|
| 13 | |
|---|
| 14 | def make_script_tag(script, src): |
|---|
| 15 | if src: |
|---|
| 16 | src_attrib = ' src="%s"' % src |
|---|
| 17 | else: |
|---|
| 18 | src_attrib = '' |
|---|
| 19 | return '<script type="text/javascript"%s>%s</script>\n' % ( |
|---|
| 20 | src_attrib, script) |
|---|
| 21 | |
|---|
| 22 | |
|---|
| 23 | def script(script): |
|---|
| 24 | return make_script_tag(script, None) |
|---|
| 25 | |
|---|
| 26 | |
|---|
| 27 | def script_src(src): |
|---|
| 28 | return make_script_tag('', src) |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | def context_setdefault(context, varname, value): |
|---|
| 32 | if varname not in context: |
|---|
| 33 | context[varname] = value |
|---|
| 34 | return context[varname] |
|---|
| 35 | |
|---|
| 36 | |
|---|
| 37 | @register.tag |
|---|
| 38 | def YUI(parser, token): |
|---|
| 39 | bits = token.split_contents()[1:] |
|---|
| 40 | if len(bits) < 1: |
|---|
| 41 | raise template.TemplateSyntaxError, \ |
|---|
| 42 | 'YUI requires at least the command name as an argument' |
|---|
| 43 | cmd = bits.pop(0) |
|---|
| 44 | |
|---|
| 45 | if cmd == 'loader': |
|---|
| 46 | if len(bits) > 2: |
|---|
| 47 | raise template.TemplateSyntaxError, \ |
|---|
| 48 | 'YUI loader takes zero, one or two arguments' |
|---|
| 49 | return YUILoaderNode(*bits) |
|---|
| 50 | |
|---|
| 51 | elif cmd == 'require': |
|---|
| 52 | nodelists_add_module = [] |
|---|
| 53 | nodelist_on_success = parser.parse(('addModule', 'endYUI',)) |
|---|
| 54 | while True: |
|---|
| 55 | token = parser.next_token() |
|---|
| 56 | if token.contents == 'addModule': |
|---|
| 57 | nodelists_add_module.append( |
|---|
| 58 | parser.parse(('addModule', 'endYUI',))) |
|---|
| 59 | else: |
|---|
| 60 | break |
|---|
| 61 | return YUIRequireNode(bits, nodelist_on_success, nodelists_add_module) |
|---|
| 62 | |
|---|
| 63 | elif cmd == 'alias': |
|---|
| 64 | aliases = [bit.split('=') for bit in bits] |
|---|
| 65 | return YUIAliasNode((varname, module) for varname, module in aliases) |
|---|
| 66 | # todo: tuple extraction error handling |
|---|
| 67 | |
|---|
| 68 | elif cmd == 'onDOMReady': |
|---|
| 69 | if bits: |
|---|
| 70 | raise template.TemplateSyntaxError, \ |
|---|
| 71 | 'YUI onDOMReady takes no arguments' |
|---|
| 72 | nodelist = parser.parse(('endYUI',)) |
|---|
| 73 | parser.delete_first_token() |
|---|
| 74 | return YUIOnDomReadyNode(nodelist) |
|---|
| 75 | |
|---|
| 76 | elif cmd == 'editor': |
|---|
| 77 | if len(bits) != 1: |
|---|
| 78 | raise template.TemplateSyntaxError, \ |
|---|
| 79 | 'YUI editor takes exactly one argument' |
|---|
| 80 | return YUIEditorNode(bits[0]) |
|---|
| 81 | |
|---|
| 82 | |
|---|
| 83 | def init(url): |
|---|
| 84 | return ''.join(( |
|---|
| 85 | script_src(url), |
|---|
| 86 | script('_DOMReady_funcs=[];' |
|---|
| 87 | 'function _onDOMReady(f){_DOMReady_funcs.push(f);}'))) |
|---|
| 88 | |
|---|
| 89 | ONDOMREADY = ('for(var i=0;i<_DOMReady_funcs.length;++i)' |
|---|
| 90 | '_DOMReady_funcs[i]();' |
|---|
| 91 | '_onDOMReady=function(f){f()};') |
|---|
| 92 | |
|---|
| 93 | |
|---|
| 94 | class YUILoaderNode(template.Node): |
|---|
| 95 | |
|---|
| 96 | def __init__(self, url=None, filter=None): |
|---|
| 97 | self.url = url and template.Variable(url) |
|---|
| 98 | self.filter = filter and template.Variable(filter) |
|---|
| 99 | |
|---|
| 100 | def render(self, context): |
|---|
| 101 | config = context_setdefault(context, '_yui', {}) |
|---|
| 102 | if self.filter is None: |
|---|
| 103 | config['filter'] = None |
|---|
| 104 | else: |
|---|
| 105 | config['filter'] = self.filter.resolve(context) |
|---|
| 106 | if self.url is None: |
|---|
| 107 | config['source'] = LOADER_URL |
|---|
| 108 | else: |
|---|
| 109 | config['source'] = self.url.resolve(context) |
|---|
| 110 | return YUIRequireNode(['dom', 'event']).render(context) |
|---|
| 111 | |
|---|
| 112 | |
|---|
| 113 | class YUIRequireNode(template.Node): |
|---|
| 114 | |
|---|
| 115 | def __init__(self, modules, |
|---|
| 116 | nodelist_on_success=None, nodelists_add_module=None): |
|---|
| 117 | self.modules = modules |
|---|
| 118 | self.nodelist_on_success = nodelist_on_success |
|---|
| 119 | self.nodelists_add_module = nodelists_add_module or () |
|---|
| 120 | |
|---|
| 121 | def render(self, context): |
|---|
| 122 | result = [] |
|---|
| 123 | success_scripts = [] |
|---|
| 124 | config = context_setdefault(context, '_yui', {}) |
|---|
| 125 | |
|---|
| 126 | if 'initialized' not in config: |
|---|
| 127 | url = config.setdefault('source', LOADER_URL) |
|---|
| 128 | result.append(init(url)) |
|---|
| 129 | success_scripts.append(ONDOMREADY) |
|---|
| 130 | config['initialized'] = True |
|---|
| 131 | result.append(script('_yui_loader = new YAHOO.util.YUILoader();')) |
|---|
| 132 | |
|---|
| 133 | if self.nodelist_on_success is not None: |
|---|
| 134 | success_scripts.append( |
|---|
| 135 | self.nodelist_on_success.render(context).strip()) |
|---|
| 136 | if success_scripts: |
|---|
| 137 | on_success = ',onSuccess:function(){%s}' % ''.join(success_scripts) |
|---|
| 138 | else: |
|---|
| 139 | on_success = '' |
|---|
| 140 | |
|---|
| 141 | if config.get('filter', None): |
|---|
| 142 | filtr = ',filter:"%s"' % config['filter'] |
|---|
| 143 | if config['filter'].lower() == 'debug': |
|---|
| 144 | filtr += ',allowRollup:false' |
|---|
| 145 | else: |
|---|
| 146 | filtr = '' |
|---|
| 147 | |
|---|
| 148 | add_module = ''.join( |
|---|
| 149 | '_yui_loader.addModule(%s);\n' % nodelist.render(context) |
|---|
| 150 | for nodelist in self.nodelists_add_module) |
|---|
| 151 | |
|---|
| 152 | result.append(script( |
|---|
| 153 | '%s_yui_loader.insert({require:[%s]%s%s});' % ( |
|---|
| 154 | add_module, |
|---|
| 155 | ','.join('"%s"' % m for m in self.modules), |
|---|
| 156 | filtr, |
|---|
| 157 | on_success))) |
|---|
| 158 | return ''.join(result) |
|---|
| 159 | |
|---|
| 160 | |
|---|
| 161 | class YUIAliasNode(template.Node): |
|---|
| 162 | def __init__(self, aliases): |
|---|
| 163 | self.aliases = aliases |
|---|
| 164 | def render(self, context): |
|---|
| 165 | assigned = context_setdefault(context, '_yui_assigned_aliases', set()) |
|---|
| 166 | assignments = [assignment for assignment in self.aliases |
|---|
| 167 | if assignment not in assigned] |
|---|
| 168 | if not assignments: |
|---|
| 169 | return '' |
|---|
| 170 | assigned.update(assignments) |
|---|
| 171 | return script('_onDOMReady(function(){%s});' % |
|---|
| 172 | ''.join('%s=%s;' % (varname, module) |
|---|
| 173 | for varname, module in assignments)) |
|---|
| 174 | |
|---|
| 175 | |
|---|
| 176 | class YUIOnDomReadyNode(template.Node): |
|---|
| 177 | def __init__(self, nodelist): |
|---|
| 178 | self.nodelist = nodelist |
|---|
| 179 | def render(self, context): |
|---|
| 180 | return script('_onDOMReady(%s);') % self.nodelist.render(context) |
|---|
| 181 | |
|---|
| 182 | |
|---|
| 183 | class YUIEditorNode(template.Node): |
|---|
| 184 | def __init__(self, textarea_id): |
|---|
| 185 | self.textarea_id = template.Variable(textarea_id) |
|---|
| 186 | |
|---|
| 187 | def render(self, context): |
|---|
| 188 | textarea_id = self.textarea_id.resolve(context) |
|---|
| 189 | return render_to_string( |
|---|
| 190 | 'yui/editor.html', |
|---|
| 191 | {'yui_editor_textarea_id': textarea_id}, |
|---|
| 192 | Context()) |
|---|