""" webpages.cobra This is the beginning of a framework for generating or serving web content. Initially, its only aim is to spit out static HTML files while saving work by making use of OO inheritance to share common page parts. It's partly inspired by "Webware for Python", an open source project that was founded in 2000 by the author of Cobra. The current code is informative and can be extended. But currently this "program" isn't doing anything. TODO [ ] Finish Page class [ ] Make something for the typical page format of header, sidebar, content and footer. DeluxePage [ ] Get a real example going [ ] Consider a way to use these classes on non-Cobra content that a non-programer could author [ ] Incorporate extras.web.PageExtrasMixIn """ @ref 'System.Web' use System.Web interface IToHtml """ Exposes a .toHtml property which returns a string containing HTML source body representing the object that received the message. Implementations should incorporate the HTML of their "subobjects" as appropriate. If a property is inappropriate for your class because the HTML source could be potentially voluminous and/or take a long time to compute then implement IWriteHtml instead. The "Cobra Web Pages" framework supports both. See also: HtmlUtils """ get toHtml as String interface IWriteHtml """ Exposes a .writeHtml method whose duty is to write HTML source that represents the object, to a TextWriter. Implementations should incorporate the HTML of their "subobjects" as appropriate. If the HTML source is likely to be small, or you simply wish to return a string rather than write to a TextWriter then implement IWriteHtml. The "Cobra Web Pages" framework supports both. See also: HtmlUtils """ def writeHtml(dest as TextWriter) class HtmlUtils shared def toHtml(obj) as String """ Returns the HTML source for the given object. Recognizes objects that implement IToHtml and IWriteHtml. """ if obj is nil return 'nil' else if obj inherits IToHtml return obj.toHtml else if obj inherits IWriteHtml sw = StringWriter() obj.writeHtml(sw) return sw.toString else return obj.toString def writeHtml(dest as TextWriter, obj) """ Writes the HTML source for the given object. Recognizes objects that implement IToHtml and IWriteHtml. """ if obj is nil dest.write('nil') else if obj inherits IWriteHtml obj.writeHtml(dest) else if obj inherits IToHtml dest.write(obj.toHtml) else dest.write(obj.toString) class HtmlWriter inherits BaseObject implements IWriteHtml """ HtmlWriter provides a concrete implementation of IWriteHtml with some conveniences that you can inherit and make use of. Subclasses should override .writeHtml. """ get dest from var as TextWriter? def writeHtml(dest as TextWriter) _dest = dest try .writeHtml finally _dest = nil def writeHtml pass ## Self util ## def writeLine(s as String) _dest.writeLine(s) def htmlEncode(s as String) as String return HttpUtility.htmlEncode(s) to ! def htmlDecode(s as String) as String return HttpUtility.htmlDecode(s) to ! def urlEncode(s as String) as String return HttpUtility.urlEncode(s) to ! def urlDecode(s as String) as String return HttpUtility.urlDecode(s) to ! class BaseObject """ A base class common to all the classes in this framework. May come in handy down the road. """ pass class Transaction inherits BaseObject """ This class would hold the request, response, session, application and servlet references. """ pass class EndResponse inherits Exception """ Used internally by Servlet. When subclassing servlets, invoke .endResponse to do so. """ pass class Servlet inherits BaseObject test s = Servlet() assert s.name=='Servlet' s.name = 'foo' assert s.name=='foo' s.run(nil) assert not s.didEndResponse var _name as String? var _txn as Transaction? var _dest as TextWriter? var _didEndResponse as bool cue init base.init pro name as String """ Returns the name of the servlet, which is its class name by default. """ get return _name ? .getType.name set _name = value get dest from _dest def run(txn as Transaction?) _txn = txn _dest = Console.out # TODO: take from _txn if possible _didEndResponse = false try try .setUp .respond catch EndResponse _didEndResponse = true .tearDown finally _dest = nil _txn = nil def setUp pass def respond pass def tearDown pass get didEndResponse from var def log(message as String) """ Log important messages from the developer. These go to the console. """ .log(Console.out, message) def log(tw as TextWriter, message as String) message = '([.name]) ([DateTime.now]) [message]' tw.writeLine(message) def write(stuff as vari Object) for item in stuff _dest.write(.itemToString(item)) def itemToString(item) as String """ Used by .write to convert objects to strings. Returns 'nil' for `nil`. """ return if(item is nil, 'nil', item.toString) def writeLine(s as String) _dest.writeLine(s) def urlEncode(s as String) as String return HttpUtility.urlEncode(s) to ! def urlDecode(s as String) as String return HttpUtility.urlDecode(s) to ! def endResponse """ When this method is called during .setUp or .respond, servlet processing will end immediately, and the accumulated response will be sent. Note that .tearDown will still be called, providing a chance to clean up or free any resources. Also, it can check the boolean .didEndResponse to see if that took place. """ throw EndResponse() class HTTPServlet inherits Servlet """ This would get fleshed out if this class hierarchy became part of an application server. See "Webware for Python". """ pass class Page inherits HTTPServlet implements IWriteHtml """ Page is a type of HTTPServlet that is more convenient for servlets which represent HTML pages generated in response to GET and POST requests. In fact, this is the most common type of servlet. Subclasses typically override .writeHeader, .writeBody or .writeBodyParts, and writeFooter. They might also choose to override .writeHtml entirely. When developing a full-blown website, it's common to create a subclass of `Page` called SitePage which defines the common look and feel of the website and provides site-specific convenience methods. Then all other pages in your application inherit from SitePage. """ test p = Page() assert p.name == 'Page' assert p.title == 'Page' assert p.htTitle == 'Page' sw = StringWriter() p.writeHtml(sw) s = sw.toString.trim assert s.contains('') assert s.contains('') assert s.contains('') assert s.endsWith('') get title as String """ Returns the title of the page absent any HTML tags. Subclasses often override this method to provide a custom title. This implementation returns .name. """ return .name get htTitle as String """ Returns the page title as HTML. Subclasses sometimes override this to provide an HTML enhanced version of the title. This is the property that should be used when including the page title in the actual page contents. This implementation returns .title. """ return .title get bodyArgs as MLArgs? return nil def writeHtml """ Writes all the HTML for the page. Subclasses may override this method (which is invoked by .respond) or more commonly its constituent methods, .writeDocType, .writeHead and .writeBody. You will want to override this method if: * you want to format the entire HTML page yourself * if you want to send an HTML page that has already been generated * if you want to use a template that generates the entire page * if you want to send non-HTML content (be sure to call `.response.setHeader('Content-type', 'mime/type')` in this case). """ .writeDocType .writeLine('') .writeHead .writeBody .writeLine('') get docTypeTransitional as String """ Returns """ return '' def writeDocType """ Writes the DOCTYPE tag. Invoked by .writeHtml to write the `` tag. By default this gives the HTML 4.01 Transitional DOCTYPE, which is a good guess for what most people send. Be warned, though, that some browsers render HTML differently based on the DOCTYPE (particular newer browsers, like Mozilla, do this). Subclasses may override to specify something else. You can find out more about doc types by searching for DOCTYPE on the web, or visiting: http://www.toHtmlhelp.com/tools/validator/doctype.toHtml """ .writeLine(.docTypeTransitional) def writeHead """ Writes the `` portion of the page by writing the `...` tags and invoking `writeHeadParts` in between. """ .writeLine('') .writeHeadParts .writeLine('') def writeHeadParts """ Writes the parts inside the `...` tags. Invokes .writeTitle and .writeStyleSheet. Subclasses can override this to add additional items and should invoke super. """ .writeTitle .writeStyleSheet def writeTitle """ Writes the `` portion of the page. Uses .title, which is what you should override. """ .writeLine('\t<title>[.title]') def writeStyleSheet """ Write the style sheet for the page. This default implementation will write: '\t' ...if .styleSheetUrl returns a non-blank string. Subclasses can also override if necessary. """ for url in .styleSheetUrls .writeLine('\t') get styleSheetUrls as String* """ Yields the stylesheet URLs for this page. Used by .writeStyleSheet. This default implementation yields nothing. Subclasses can override. For example: yield 'styles-global.css' yield 'styles-section.css' """ yield break def writeBody """ Writes the `` portion of the page by writing the `...` (making use of .bodyArgs) and invoking .writeBodyParts in between. """ bodyArgs = .bodyArgs .writeLine(if(bodyArgs and bodyArgs.count, '', '')) .writeBodyParts .writeLine('') def writeBodyParts """ Write the parts included in the element. Invokes .writeContent. Subclasses should only override this method to provide additional page parts such as a header, sidebar and footer, that a subclass doesn't normally have to worry about writing. For writing page-specific content, subclasses should override .writeContent instead. This method is intended to be overridden by your SitePage. See DeluxePage for an example override of this method. Invoked by .writeBody. """ .writeContent def writeContent """ Write the unique, central content for the page. Subclasses should override this method (not invoking base) to write their unique page content. Invoked by .writeBodyParts. """ .writeLine('

This page has not yet customized its content.

') def htmlEncode(s as String) as String return HttpUtility.htmlEncode(s) to ! def htmlDecode(s as String) as String return HttpUtility.htmlDecode(s) to ! ## implements IWriteHtml ## def writeHtml(dest as TextWriter) _dest = dest try .writeHtml finally _dest = nil def saveHtml(fileName as String) using f = File.createText(fileName) .writeHtml(f) class MLArgs inherits BaseObject implements IToHtml """ An MLArgs instance is a dictionary of named arguments, that when printed (toString) yield an argument string suitable for HTML, XML, SGML, et al: href="http://foo.com" class="menuLink" The motiviation for MLArgs is that if you store these arguments as in a single string: 'href="http://foo.com" class="menuLink"' ...then inspecting and updating individual arguments requires extra effort. For example, if a subclass wishes to set the 'class' argument, after its base class has initialized the arguments, it may need to parse the string. Using MLArgs, the subclass can simply say: .linkArgs['class'] = 'submenuLink' And outputing MLArgs is just as convenient as using a string: dest.write('') You can do the same thing using the .tag method: dest.write(.linkArgs.tag('a')) Markup languages don't have a concept of nil while Cobra does. Consequently, key/value pairs whose values are nil are not included in the output. Also, setting a key to nil deletes it. These policies can be useful when merging arguments via .copyAndSet and .copyIfSet. See also: Tag """ test args = MLArgs() assert args.count==0 args['width'] = 50 assert args.count == 1 assert args['width'] == 50 assert args.toString == 'width="50"' args['color'] = 'blue' assert args.toString == 'color="blue" width="50"' shared pro willSortKeysDefault from var = true var _values as Dictionary # TODO: Make this IDictionary var _willSortKeys as bool cue init base.init _init cue init(d as IDictionary) test args = MLArgs(Dictionary()) assert args.count == 0 args = MLArgs({'x': '1', 'y': '2'}) assert args.count == 2 and args['y'] == '2' body base.init _init() for key in d.keys .put(key, d[key]) cue init(keyValues as vari Object) test args = MLArgs('x', 1) assert args['x'] == 1 args = MLArgs('x', 1, 'y', 2) assert args.count == 2 and args['y'] == 2 body base.init _init() .set(keyValues) cue init(keyValues as String) test args = MLArgs('width=100 height=200') assert args['width'] == '100' and args['height'] == '200' body base.init _init() # TODO: does not parse correctly if values have spaces in them for part as String in keyValues.split(nil) i = part.indexOf('=') key = part.substring(0, i) value = part.substring(i+1) if value.startsWith('"') and value.endsWith('"') value = value.substring(1, value.length-2) # TODO: parse int? this[key] = value def _init _values = Dictionary() _willSortKeys = _willSortKeysDefault pro [key as String] as dynamic """ This indexer will never return nil (nor can you pass nil to it). See also: remove, put, set """ get return _values[key] set _values[key] = value def put(key as String, value as dynamic) require key.length value is not this not value inherits MLArgs body _values[key] = value def get(key as String) as dynamic return _values[key] def containsKey(key as String) as bool return _values.containsKey(key) def set(keyValues as vari Object?) as MLArgs """ Sets the args key value pairs as passed in the arguments. Returns `this` as a convenience. """ test args = MLArgs() args.set('key', 'value') assert args['key'] == 'value' args.set('key', 'value2', 'anotherKey', 5) assert args['key'] == 'value2' and args['anotherKey'] == 5 assert args.containsKey('anotherKey') args.set('anotherKey', nil) assert not args.containsKey('anotherKey') body for i in 0 : keyValues.length : 2 key = keyValues[i] to String assert key.length, keyValues value = keyValues[i+1] if value .put(key, value) else .remove(key) return this def remove(key as String) as bool return _values.remove(key) get count as int return _values.count pro willSortKeys from var def clear _values.clear assert .count==0 # ensure def toString as String is override # @@ 2001-05-05 CE: Obviously, we need better quoting here and potentially some escaping specific to *ML parts = List() for key in _values.keys value = _values[key] parts.add('[key]="[value]"') if _willSortKeys parts.sort return String.join(' ', parts.toArray) to ! get toHtml as String return .toString def tag(tagName as String) as String test args = MLArgs('href', 'http://cobra-language.com/') assert args.tag('a') == '' body return '<[tagName] [this]>' def copyAndSet(keyValues as vari Object) as MLArgs # TODO: should be ...as same """ Copies the arguments, sets the values in the copy and returns that copy. Example: args = args.copyAndSet('width', '100%') """ test args = MLArgs('x', 1) args = args.copyAndSet('x', 2, 'y', 3) assert args['x'] == 2 and args['y'] == 3 args = args.copyAndSet('__replace__', 1, 'x', 3) assert args.count == 1 and args['x'] == 3 body # check for __replace__ for i in 0 : keyValues.length : 2 if '__replace__' == keyValues[i] args = .memberwiseClone to MLArgs args.clear args.set(keyValues).remove('__replace__') return args copy = .memberwiseClone to MLArgs return copy.set(keyValues) def copyIfSet(keyValues as vari Object) as MLArgs """ Like `copyAndSet` but returns this if there are no keyValues. """ if keyValues.length return .copyAndSet(keyValues) else return this class Tag inherits MLArgs test t = Tag('br') assert t.toHtml == '
' and t.toHtml == t.toString t = Tag('input', 'type', 'submit', 'value', 'Ok') assert t.toHtml=='' assert t.toHtml==t.toString t = Tag('input', 'type', 'text', 'name', 'username', 'size', '20') assert t.toHtml=='' cue init(tagName as String) base.init _tagName = tagName cue init(tagName as String, keyValues as String) base.init(keyValues) _tagName = tagName cue init(tagName as String, keyValues as vari Object) base.init(keyValues) _tagName = tagName pro tagName from var as String def toString as String args = base.toString if args.length return '<[_tagName] [args]>' else return '<[_tagName]>'