smalltalk
/
osmo-st-all
Archived
1
0
Fork 0
This repository has been archived on 2022-02-17. You can view files and clone it, but cannot push or open issues or pull requests.
osmo-st-all/Core/Buildables/Application.st

344 lines
8.7 KiB
Smalltalk
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"======================================================================
|
| Iliad.Application class definition
|
======================================================================"
"======================================================================
|
| Copyright (c) 2008-2009
| Nicolas Petton <petton.nicolas@gmail.com>,
| Sébastien Audier <sebastien.audier@gmail.com>
|
| Some parts of this file reuse code from HttpView2 written by Giovanni
| Corriga and Göran Krampe http://www.squeaksource.com/HttpView2/
|
| This file is part of the Iliad framework.
|
| Permission is hereby granted, free of charge, to any person obtaining
| a copy of this software and associated documentation files (the
| 'Software'), to deal in the Software without restriction, including
| without limitation the rights to use, copy, modify, merge, publish,
| distribute, sublicense, and/or sell copies of the Software, and to
| permit persons to whom the Software is furnished to do so, subject to
| the following conditions:
|
| The above copyright notice and this permission notice shall be
| included in all copies or substantial portions of the Software.
|
| THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
| EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
| MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
| IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
| CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
| TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
| SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
======================================================================"
Widget subclass: Application [
| model page originalRoutePosition |
<category: 'Iliad-Core-Builders'>
<comment: 'I am the implementation of an application.
The first thing to do to develop web applications with Iliad is to subclass me.
You don''t have to bother about instantiating applications, the framework
will handle session and application instances.
In concrete subclasses, the class method #path should return the base path
(string) for the application.
Like other widgets, I have state, and can have decorators.
Unlike other widgets I know how to dispatch a request with #dispatch :
the view method corresponding to the url will be called.
View methods must:
- answer a view block, ie, a block closure taking an element as parameter.
- be in the ''views'' method protocol (with the default selector filter)
The default view method is #index.
The class inst var <selectorFilter> is used to filter view methods.
By default it allows all methods in the ''views'' protocol,
but you can override the class method #defaultSelectorFilter to supply
your own selectorFilter or plug it in using the class method #selectorFilter:
Applications can be in one the following modes: deployment, verbose or
debug. The default mode is verbose.
You can switch between modes with class methods in the <accessing-modes>
protocol.
When errors occur, the framework with handle them differently depending on
the application mode:
- in deployment mode, it will respond an error 500;
- in verbose mode, it will also respond an error 500, but with error details;
- in debug mode, a debugger window will open on the error;'>
Application class [
| selectorFilter mode |
path [
"Override this method in concrete subclasses.
It should return a string"
<category: 'accessing'>
^nil
]
selectorFilter [
<category: 'accessing'>
^selectorFilter ifNil: [self defaultSelectorFilter]
]
selectorFilter: aBlock [
<category: 'accessing'>
selectorFilter := aBlock
]
mode [
<category: 'accessing'>
^mode ifNil: [mode := #verbose]
]
mode: aSymbol [
<category: 'accessing'>
mode := aSymbol
]
verboseMode [
<category: 'accessing-modes'>
self mode: #verbose
]
debugMode [
<category: 'accessing-modes'>
self mode: #debug
]
deploymentMode [
<category: 'accessing-modes'>
self mode: #deployment
]
defaultSelectorFilter [
"Override this method to supply your own selectorFilter
or plug it in using #selectorFilter:"
<category: 'defaults'>
^[:selector |
(self canUnderstand: selector) and: [
(self
categoryOfElement: selector
inClassOrSuperclass: self) = 'views']]
]
categoryOfElement: aSelector inClassOrSuperclass: aClass [
"Find first category of <aSelector> up the superclass chain."
<category: 'private'>
^aClass ifNotNil: [
^(aClass whichCategoryIncludesSelector: aSelector) ifNil: [
self
categoryOfElement: aSelector
inClassOrSuperclass: aClass superclass]]
]
isDebugMode [
<category: 'testing'>
^self mode = #debug
]
isVerboseMode [
<category: 'testing'>
^self mode = #verbose
]
isDeploymentMode [
<category: 'testing'>
^self isDebugMode not and: [
self isVerboseMode not]
]
]
model [
<category: 'accessing'>
^model
]
model: anObject [
<category: 'accessing'>
model := anObject
]
page [
<category: 'accessing'>
^page
]
route [
<category: 'accessing'>
^self context route
]
selectorFilter [
<category: 'accessing'>
^self class selectorFilter
]
contents [
"Call #dispatch. A view block is expected from #dispatch"
<category: 'building'>
^[:e |
e build: self dispatch]
]
updatePage [
"Override to add elements to the page.
super should always be called"
<category: 'building'>
self page headElement javascript source: '/javascripts/jquery132min.js'.
self page headElement javascript source: '/javascripts/no_conflict.js'.
self page headElement javascript source: '/javascripts/iliad.js'.
self page headElement javascript source: '/javascripts/lightbox.js'.
self page headElement stylesheet href: '/stylesheets/lightbox.css'.
]
pageClass [
<category: 'defaults'>
^Page
]
allowedSelector: aSelector [
"Answer true if <aSelector> is ok to call from a URL.
Default implementation is to use the pluggable filter block."
<category: 'dispatching'>
^self selectorFilter copy value: aSelector
]
dispatch [
"Dispatch to correct method in this view.
If dispatchOverride returns something
different from nil, consider it handled."
<category: 'dispatching'>
self setRoutePosition.
^self dispatchOverride ifNil: [
self dispatchOn: self route next]
]
dispatchOn: aMethod [
"Dispatch to correct method:
- If <aMethod> is empty we call #index
- If the selector is allowed to be executed then we just call it"
<category: 'dispatching'>
| m |
(aMethod isNil or: [aMethod isEmpty])
ifTrue: [m := #index]
ifFalse: [m := aMethod asSymbol].
(self allowedSelector: m)
ifTrue: [^self perform: m]
ifFalse: [DispatchError signal]
]
dispatchOverride [
"Handle special urls. Subclass implementors
should call super first and see if it was handled."
<category: 'dispatching'>
^nil
]
redirectTo: anUrlString [
"Abort all other request handling.
Redirect to anUrlString"
<category: 'redirecting'>
self session
redirectUrl: anUrlString;
redirect
]
redirectToIndex [
"Abort all other request handling.
Redirect to the index view of this class"
<category: 'redirecting'>
self redirectToLocal: 'index'
]
redirectToLocal: aView [
"Abort all other request handling.
Make a redirection to another view method in this application"
<category: 'redirecting'>
self redirectTo: self originalUrl , '/' , aView
]
index [
"default view method"
<category: 'views'>
^[:e | ]
]
respondOn: aResponse [
<category: 'converting'>
self build.
self updatePage.
self page respondOn: aResponse
]
defaultPageClass [
<category: 'defaults'>
^Page
]
isDebugMode [
<category: 'testing'>
^self class isDebugMode
]
isVerboseMode [
<category: 'testing'>
^self class isVerboseMode
]
isDeploymentMode [
<category: 'testing'>
^self class isDeploymentMode
]
newRootElement [
"Set a new page instance of #defaultPageClass.
Answer the #bodyElement of the page"
<category: 'private'>
page := self defaultPageClass new.
^page bodyElement
]
originalUrl [
"Answer an url String representing this application's
original position within the url of the request."
<category: 'private'>
| curPos result |
curPos := self route position.
self route position: originalRoutePosition.
result := self route currentPath.
^result
]
setRoutePosition [
<category: 'private'>
originalRoutePosition
ifNil: [originalRoutePosition := self route position]
ifNotNil: [self route position: originalRoutePosition]
]
]