PageRenderTime 98ms CodeModel.GetById 83ms app.highlight 12ms RepoModel.GetById 1ms app.codeStats 0ms

/addons/TemplatePanel.py

http://pyjamas.googlecode.com/
Python | 330 lines | 291 code | 23 blank | 16 comment | 15 complexity | d45348eba7b55a81df85c6c6f3d9fff3 MD5 | raw file
  1from __pyjamas__ import unescape
  2from RichTextEditor import RichTextEditor
  3import Window
  4from __pyjamas__ import encodeURIComponent
  5from EventDelegate import EventDelegate
  6from ui import Label
  7from pyjslib import List
  8from ui import ComplexPanel
  9from __pyjamas__ import console
 10from HTTPRequest import HTTPRequest
 11from ui import HTML, SimplePanel
 12import DOM
 13
 14class TemplateLoader:
 15    def __init__(self, panel, url):
 16        self.panel = panel
 17        
 18    def onCompletion(self, text):
 19        self.panel.setTemplateText(text)
 20        
 21    def onError(self, text, code):
 22        self.panel.onError(text, code)
 23
 24    def onTimeout(self, text):
 25        self.panel.onTimeout(text)
 26
 27class ContentSaveHandler:
 28    def __init__(self, templatePanel):
 29        self.templatePanel = templatePanel
 30        
 31    def onCompletion(self):
 32        self.templatePanel.onSaveComplete()
 33    
 34    def onError(self, error):
 35        Window.alert("Save failed: "+error)
 36
 37class PendingAttachOrInsert:        
 38    def __init__(self, name, widget):
 39        self.name = name
 40        self.widget = widget
 41    
 42class TemplatePanel(ComplexPanel):
 43    """

 44        Panel which allows you to attach or insert widgets into

 45        a pre-defined template.

 46        

 47        We don't do any caching of our own, since the browser will

 48        do caching for us, and probably more efficiently.

 49    """
 50    templateRoot = ""
 51    """Set staticRoot to change the base path of all the templates that are loaded; templateRoot should have a trailing slash"""
 52    
 53    def __init__(self, templateName, allowEdit=False):
 54        ComplexPanel.__init__(self)
 55        self.loaded = False # Set after widgets are attached

 56        self.widgetsAttached = False
 57        self.id = None
 58        self.templateName = None
 59        self.title = None
 60        self.elementsById = {}
 61        self.metaTags = {}
 62        self.body = None
 63        self.links = []
 64        self.forms = []
 65        self.metaTagList = []
 66        self.loadListeners = []
 67        self.toAttach = []
 68        self.toInsert = []
 69        self.setElement(DOM.createDiv())
 70        self.editor = None
 71        self.allowEdit = allowEdit
 72        if templateName:
 73            self.loadTemplate(templateName)
 74    
 75    def getTemplatePath(self, templateName):
 76        return self.templateRoot+'tpl/'+templateName+'.html'
 77    
 78    def loadTemplate(self, templateName):
 79        self.templateName = templateName
 80        self.id = templateName + str(hash(self))
 81        self.httpReq = HTTPRequest()
 82        self.httpReq.asyncGet(self.getTemplatePath(templateName), TemplateLoader(self))
 83    
 84    def getCurrentTemplate(self):
 85        """Return the template that is currently loaded, or is loading"""
 86        return self.templateName
 87        
 88    def isLoaded(self):
 89        """Return True if the template is finished loading"""
 90        return self.loaded
 91    
 92    def areWidgetsAttached(self):
 93        """Return True if the template is loaded and attachWidgets() has been called"""
 94        return self.widgetsAttached
 95    
 96    def setTemplateText(self, text):
 97        """

 98        Set the template text; if the template is not HTML, a subclass could override this

 99        to pre-process the text into HTML before passing it to the default implementation.

100        """
101        if self.allowEdit:
102            self.originalText = text
103        # If we have children, remove them all first since we are trashing their DOM

104        for child in List(self.children):
105            self.remove(child)
106        
107        DOM.setInnerHTML(self.getElement(), text)
108        self.elementsById = {}
109        self.links = []
110        self.metaTags = {}
111        self.forms = []
112        self.metaTagList = []
113        
114        # Make the ids unique and store a pointer to each named element

115        for node in DOM.walkChildren(self.getElement()):
116            #console.log("Passing node with name %s", node.nodeName)

117            if node.nodeName == "META":
118                name = node.getAttribute("name")
119                content = node.getAttribute("content")
120                console.log("Found meta %o name %s content %s", node, name, content)
121                self.metaTags[name] = content
122                self.metaTagList.append(node)
123            elif node.nodeName == "BODY":
124                self.body = node
125            elif node.nodeName == "TITLE":
126                self.title = DOM.getInnerText(node)
127            elif node.nodeName == "FORM":
128                self.forms.append(node)
129
130            nodeId = DOM.getAttribute(node, "id")
131            if nodeId:
132                self.elementsById[nodeId] = node
133                DOM.setAttribute(node, "id", self.id+":"+node.id)
134            nodeHref = DOM.getAttribute(node, "href")
135            if nodeHref:
136                self.links.append(node)
137                
138        self.loaded = True
139        if self.attached:
140            self.attachWidgets()
141            self.widgetsAttached = True
142            
143        if self.allowEdit:
144            self.editor = None
145            self.editButton = Label("edit "+unescape(self.templateName))
146            self.editButton.addStyleName("link")
147            self.editButton.addStyleName("ContentPanelEditLink")
148            self.editButton.addClickListener(EventDelegate("onClick", self, self.onEditContentClick))
149            ComplexPanel.insert(self, self.editButton, self.getElement(), len(self.children))
150
151        self.notifyLoadListeners()
152    
153    def onError(self, html, statusCode):
154        if statusCode == 404 and self.allowEdit:
155            self.editor = None
156            self.originalText = ""
157            DOM.setInnerHTML(self.getElement(), '')
158            self.editButton = Label("create "+unescape(self.templateName))
159            self.editButton.addStyleName("link")
160            self.editButton.addStyleName("ContentPanelEditLink")
161            self.editButton.addClickListener(EventDelegate("onClick", self, self.onEditContentClick))
162            ComplexPanel.insert(self, self.editButton, self.getElement(), len(self.children))
163            return
164        
165        # Show the page we got in an iframe, which will hopefully show the error better than we can.

166        # DOM.setInnerHTML(self.getElement(), '<iframe src="'+self.getTemplatePath(self.templateName)+'"/>')

167        
168    def onTimeout(self, text):
169        self.onError("Page loading timed out: "+text)
170        
171    def getElementsById(self):
172        """Return a dict mapping an id to an element with that id inside the template; useful for post-processing"""
173        return self.elementsById
174    
175    def getLinks(self):
176        """Return a list of all the A HREF= elements found in the template."""
177        return self.links
178    
179    def getForms(self):
180        """Return a list of all the FORM elements found in the template."""
181        return self.forms
182    
183    def onAttach(self):    
184        if not self.attached:
185            SimplePanel.onAttach(self)
186            if self.loaded and not self.widgetsAttached:
187                self.attachWidgets()
188                self.widgetsAttached = True
189                
190    def attachWidgets(self):
191        """

192        Attach and insert widgets into the DOM now that it has been loaded.  If any

193        widgets were attached before loading, they will have been queued and the 

194        default implementation will attach them.

195        

196        Override this in subclasses to attach your own widgets after loading.

197        """
198        for attach in self.toAttach:
199            self.attach(attach.name, attach.widget)
200        for insert in self.toInsert:
201            self.insert(insert.name, insert.widget)
202    
203    def getElementById(self, id):
204        return self.elementsById[id]
205        
206    def insert(self, id, widget):
207        """

208            Insert a widget into the element with the given id, at the end

209            of its children.

210        """
211        if not self.loaded:
212            self.toInsert.append(PendingAttachOrInsert(id, widget))
213        else:
214            element = self.getElementById(id)
215            if element:
216                self.adopt(widget, element)
217                self.children.append(widget)
218            else:
219                console.error("Page error: No such element "+id)
220            return widget
221    
222    def attachToElement(self, element, widget):    
223        events = DOM.getEventsSunk(widget.getElement())
224        widget.unsinkEvents(events)
225        widget.setElement(element)
226        widget.sinkEvents(events)
227        self.adopt(widget, None)
228        self.children.append(widget)
229    
230    def replaceElement(self, element, widget):
231        """

232        Replace an existing element with the given widget

233        """
234        DOM.getParent(element).replaceChild(widget.getElement(), element)
235        self.adopt(widget, None)
236        self.children.append(widget)
237        
238    def attach(self, id, widget):
239        """

240            Attach a widget onto the element with the given id; the element

241            currently associated with the widget is discarded.

242        """
243        if not self.loaded:
244            self.toAttach.append(PendingAttachOrInsert(id, widget))
245        else:
246            element = self.getElementById(id)
247            if element:
248                self.attachToElement(element, widget)
249            else:
250                console.error("Page error: No such element "+id)
251            return widget
252    
253    def getMeta(self, name):
254        """

255        Get the value of a meta-variable found in the template, or None if

256        no meta tags were found with the given name.

257        """
258        return self.metaTags.get(name)
259    
260    def getTitle(self):
261        """

262        Return a user-friendly title for the page

263        """
264        if self.title: return self.title
265        else: return self.templateName
266        
267    def addLoadListener(self, listener):
268        """

269        The listener should be a function or an object implementing onTemplateLoaded.

270        It will be called this TemplatePanel instance after the template has been

271        loaded and after attachWidgets() is called.

272        """
273        self.loadListeners.append(listener)
274        
275    def removeLoadListener(self, listener):
276        self.loadListeners.remove(listener)
277        
278    def notifyLoadListeners(self):
279        for listener in self.loadListeners:
280            if listener.onTemplateLoaded: listener.onTemplateLoaded(self)
281            else: listener(self)
282        
283    def onEditContentClick(self, sender):
284        if self.editor:
285            editor = self.editor
286            self.editor = None
287            ComplexPanel.remove(self, editor)
288            self.editButton.setText("edit "+unescape(self.templateName))
289        else:
290            self.editor = RichTextEditor(self.originalText)
291            self.editor.addSaveListener(self)
292            ComplexPanel.insert(self, self.editor, self.getElement(), len(self.children))
293            self.editButton.setText("close editor")
294    
295    def getTemplateSaveUrl(self, templateName):
296        """

297        Get the URL to post a template to when it is saved in the editor.

298        """
299        return self.getTemplatePath(templateName)
300    
301    def saveTemplateText(self, html):
302        """

303        Save the text.  This method can be overridden to use a different

304        save method.  The default is to POST to the template save URL, passing

305        a single parameter "content" with the html string.

306        

307        To change the target of the POST, override getTemplateSaveUrl().

308        

309        To preprocess the html, override this method in a subclass and perform

310        processing there.

311        """
312        HTTPRequest().asyncPost(self.getTemplateSaveUrl(self.templateName),
313                              "content="+encodeURIComponent(html),
314                              ContentSaveHandler(self))
315    def onSave(self, sender):
316        """

317        Called when the user clicks save in the content editor.

318        """
319        html = self.editor.getHTML()
320        self.saveTemplateText(html)
321    
322    def onSaveComplete(self):
323        """

324        Called when the template was successfully POSTed to the server; it reloads the template.

325        

326        Subclasses which don't use the default method of saving may want to call this after

327        they successfully save the template.

328        """
329        self.loadTemplate(self.templateName)
330