diff --git a/Chapters/08-Tasks/tasks.md b/Chapters/08-Tasks/tasks.md index cd37c13..c6c3e56 100644 --- a/Chapters/08-Tasks/tasks.md +++ b/Chapters/08-Tasks/tasks.md @@ -1,7 +1,30 @@ -## Tasks @cha:tasks % +Embedding a ContactView into another component.>file://figures/iAddress-ListAndEditor.png|width=80|label=ref:listAndItem+ In Seaside, it is possible to define components whose responsibility is to represent the flow of control between existing components. These components are called tasks. In this chapter, we explain how you can define a task. We also show how Seaside supports application control flow by isolating certain paths from others. We will start by presenting a little game, the number guessing game. Then, we will implement two small hotel registration applications using the calendar component to illustrate tasks. ### Sequencing Components Tasks are used to encapsulate a process or control flow. They do not directly render XHTML, but may do so via the components that they call. Tasks are defined as subclasses of `WATask`, which implements the key method `WATask>>go`, which is invoked as soon as a task is displayed and can call other components. Let's start by building our first example: a number guessing game \(which was one of the first Seaside tutorials\). In this game, the computer selects a random number between 1 and 100 and then proceeds to repeatedly prompt the user for a guess. The computer reports whether the guess is too high or too low. The game ends when the user guesses the number. !!note Those of you who remember learning to program in BASIC will recognise this as one of the common exercises to demonstrate simple user interaction. As you will see below, in Seaside it remains a simple exercise, despite the addition of the web layer. This comes as a stark contrast to other web development frameworks, which would require pages of boilerplate code to deliver such straightforward functionality. We create a subclass of `WATask` and implement the `go` method: ``` WATask subclass: #GuessingGameTask - instanceVariableNames: '' - classVariableNames: '' - package: 'SeasideBook' ``` ``` GuessingGameTask >> go +## Tasks + +@cha:tasks + +% +Embedding a ContactView into another component.>file://figures/iAddress-ListAndEditor.png|width=80|anchor=ref:listAndItem+ + +In Seaside, it is possible to define components whose responsibility is to represent the flow of control between existing components. These components are called tasks. In this chapter, we explain how you can define a task. We also show how Seaside supports application control flow by isolating certain paths from others. We will start by presenting a little game, the number guessing game. Then, we will implement two small hotel registration applications using the calendar component to illustrate tasks. + +### Sequencing Components + + +Tasks are used to encapsulate a process or control flow. They do not directly render XHTML, but may do so via the components that they call. Tasks are defined as subclasses of `WATask`, which implements the key method `WATask>>go`, which is invoked as soon as a task is displayed and can call other components. + +Let's start by building our first example: a number guessing game \(which was one of the first Seaside tutorials\). In this game, the computer selects a random number between 1 and 100 and then proceeds to repeatedly prompt the user for a guess. The computer reports whether the guess is too high or too low. The game ends when the user guesses the number. + +!!note Those of you who remember learning to program in BASIC will recognise this as one of the common exercises to demonstrate simple user interaction. As you will see below, in Seaside it remains a simple exercise, despite the addition of the web layer. This comes as a stark contrast to other web development frameworks, which would require pages of boilerplate code to deliver such straightforward functionality. + +We create a subclass of `WATask` and implement the `go` method: + +``` +WATask << #GuessingGameTask + package: 'SeasideBook' +``` + + +``` +GuessingGameTask >> go | number guess | number := 100 atRandom. [ guess := (self request: 'Enter your guess') asNumber. @@ -10,17 +33,63 @@ guess > number ifTrue: [ self inform: 'Your guess was too high' ]. guess = number ] whileFalse. - self inform: 'You got it!' ``` The method `go` randomly draws a number. Then, it asks the user to guess a number and gives feedback depending on the input number. The methods `request:` and `inform:` create components \(`WAInputDialog` and `WAFormDialog`\) on the fly, which are then displayed by the task. Note that unlike the components we've developed previously, this class has no `renderContentOn:` method, just the method `go`. Its purpose is to drive the user through a sequence of steps. Register the application \(as 'guessinggame'\) and give it a go. Figure *@ref:game-interaction@* shows a typical execution. ![Guessing Game interaction.](figures/GuessingGame.png width=80&label=ref:game-interaction) Why not try modifying the game to count the number of guesses that were needed? This example demonstrates that with Seaside you can use plain Smalltalk code \(conditionals, loops, etc.,\) to define the control flow of your application. You do not have to use yet another language or build a scary XML state-machine, as required in other frameworks. In some sense, tasks are simply components that start their life in a callback. Because tasks are indeed components \(`WATask` is a subclass of `WAComponent`\), all of the facilities available to components, such as `call:` and `answer:` messages, are available to tasks as well. This allows you to combine components and tasks, so your `LoginUserComponent` can call a `RegisterNewUserTask`, and so on. !!note Important Tasks do not render themselves. Don't override `renderContentOn:` in your tasks. Their purpose is simply to sequence through other views. !!note Important If you are reusing components in a task -- that is, you store them in instance variables instead of creating new instances in the `go` method -- be sure to return these instances in the #children method so that they are backtracked properly and you get the correct control flow. ### Hotel Reservation: Tasks vs. Components To compare when to use a task or a component, let's build a minimal hotel reservation application using a task and a component with children. Using a task, it is easy to reuse components and build a flow. Here is a small application that illustrates how to do this. We want to ask the user to specify starting and ending reservation dates. We will define a new subclass of `WATask` with two instance variables `startDate` and `endDate` of the selected period. ``` WATask subclass: #HotelTask - instanceVariableNames: 'startDate endDate' - classVariableNames: '' - package: 'Calendars' ``` We define a method `go` that will first create a calendar with selectable dates after today, then create a second calendar with selectable days after the one selected during the first interaction, and finally we will display the dates selected as shown in *@ref:hotel@*. ``` HotelTask >> go + self inform: 'You got it!' +``` + + + +The method `go` randomly draws a number. Then, it asks the user to guess a number and gives feedback depending on the input number. The methods `request:` and `inform:` create components \(`WAInputDialog` and `WAFormDialog`\) on the fly, which are then displayed by the task. Note that unlike the components we've developed previously, this class has no `renderContentOn:` method, just the method `go`. Its purpose is to drive the user through a sequence of steps. + +Register the application \(as 'guessinggame'\) and give it a go. Figure *@ref:game-interaction@* shows a typical execution. + +![Guessing Game interaction. %width=80&anchor=ref:game-interaction](figures/GuessingGame.png ) + +Why not try modifying the game to count the number of guesses that were needed? + +This example demonstrates that with Seaside you can use plain Smalltalk code \(conditionals, loops, etc.,\) to define the control flow of your application. You do not have to use yet another language or build a scary XML state-machine, as required in other frameworks. In some sense, tasks are simply components that start their life in a callback. + +Because tasks are indeed components \(`WATask` is a subclass of `WAComponent`\), all of the facilities available to components, such as `call:` and `answer:` messages, are available to tasks as well. This allows you to combine components and tasks, so your `LoginUserComponent` can call a `RegisterNewUserTask`, and so on. + +!!note Important Tasks do not render themselves. Don't override `renderContentOn:` in your tasks. Their purpose is simply to sequence through other views. + +!!note Important If you are reusing components in a task -- that is, you store them in instance variables instead of creating new instances in the `go` method -- be sure to return these instances in the #children method so that they are backtracked properly and you get the correct control flow. + + +### Hotel Reservation: Tasks vs. Components + + +To compare when to use a task or a component, let's build a minimal hotel reservation application using a task and a component with children. Using a task, it is easy to reuse components and build a flow. Here is a small application that illustrates how to do this. We want to ask the user to specify starting and ending reservation dates. We will define a new subclass of `WATask` with two instance variables `startDate` and `endDate` of the selected period. + +``` +WATask << #HotelTask + slots: { #startDate . #endDate}; + package: 'Calendars' +``` + + +We define a method `go` that will first create a calendar with selectable dates after today, then create a second calendar with selectable days after the one selected during the first interaction, and finally we will display the dates selected as shown in Figure *@ref:hotel@*. + +``` +HotelTask >> go startDate := self call: (WAMiniCalendar new canSelectBlock: [ :date | date > Date today ]). endDate := self call: (WAMiniCalendar new canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ]). self inform: 'from ' , startDate asString , ' to ' , endDate asString , ' ' , (endDate - startDate) days asString , - ' days' ``` ![ A simple reservation based on task.](figures/hotel.png width=80&label=ref:hotel) Note that you could add a confirmation step and loop until the user is OK with his reservation. Now this solution is not satisfying because the user cannot see both calendars while making his selection. Since we can't render components in our task, it's not easy to remedy the situation. We could use the message `addMessage: aString` to add a message to a component but this is still not good enough. This example demonstrates that tasks are about flow and not about presentation. ``` HotelTask >> go + ' days' +``` + + +![ A simple reservation based on task. % width=80&anchor=ref:hotel](figures/hotel.png) + + +Note that you could add a confirmation step and loop until the user is OK with his reservation. + +Now this solution is not satisfying because the user cannot see both calendars while making his selection. Since we can't render components in our task, it's not easy to remedy the situation. We could use the message `addMessage: aString` to add a message to a component but this is still not good enough. This example demonstrates that tasks are about flow and not about presentation. + +``` +HotelTask >> go startDate := self call: (WAMiniCalendar new canSelectBlock: [ :date | date > Date today ]; addMessage: 'Select your starting date'; @@ -30,11 +99,34 @@ addMessage: 'Select your leaving date'; yourself). self inform: (endDate - startDate) days asString , ' days: from ' , - startDate asString , ' to ' , endDate asString , ' ' ``` ### Mini Inn: Embedding Components Let's solve the same problem using component embedding. We define a component with two calendars and two dates. The idea is that we want to always have the two mini-calendars visible on the same page and provide some feedback to the user as shown by *@ref:resa@*. ``` WAComponent subclass: #MiniInn - instanceVariableNames: 'calendar1 calendar2 startDate endDate' - classVariableNames: '' - package: 'Calendars' ``` Since we want to show the two calendars on the same page we return them as children. ``` MiniInn >> children - ^ Array with: calendar1 with: calendar2 ``` We initialize the calendars and make sure that we store the results of their answers. ``` MiniInn >> initialize + startDate asString , ' to ' , endDate asString , ' ' +``` + + +### Mini Inn: Embedding Components + + +Let's solve the same problem using component embedding. We define a component with two calendars and two dates. The idea is that we want to always have the two mini-calendars visible on the same page and provide some feedback to the user as shown by *@ref:resa@*. + +``` +WAComponent << #MiniInn + slots: { #calendar1 . #calendar2 . #startDate . #endDate}; + package: 'Calendars' +``` + + +Since we want to show the two calendars on the same page we return them as children. + +``` +MiniInn >> children + ^ Array with: calendar1 with: calendar2 +``` + + +We initialize the calendars and make sure that we store the results of their answers. + +``` +MiniInn >> initialize super initialize. calendar1 := WAMiniCalendar new. calendar1 @@ -43,7 +135,14 @@ calendar2 := WAMiniCalendar new. calendar2 canSelectBlock: [ :date | startDate isNil or: [ startDate < date ] ]; - onAnswer: [ :date | endDate := date ] ``` Finally, we render the application, and this time we can provide some simple feedback to the user. The feedback is simple but this is just to illustrate our point. ``` MiniInn >> renderContentOn: html + onAnswer: [ :date | endDate := date ] +``` + + +Finally, we render the application, and this time we can provide some simple feedback to the user. The feedback is simple but this is just to illustrate our point. + +``` +MiniInn >> renderContentOn: html html heading: 'Starting date'. html render: calendar1. startDate isNil @@ -53,4 +152,17 @@ (startDate isNil not and: [ endDate isNil not ]) ifTrue: [ html text: (endDate - startDate) days asString , ' days from ' , startDate asString , ' to ' , - endDate asString , ' ' ] ``` ![A simple reservation with feedback.](figures/resa.png width=80&label=ref:resa) ### Summary In this chapter, we presented tasks, subclasses of Task. Tasks are components that do not render themselves but are used to build application flow based on the composition of other components. We saw that the composition is expressed in plain Pharo. \ No newline at end of file + endDate asString , ' ' ] +``` + + +![A simple reservation with feedback. % width=80&anchor=ref:resa](figures/resa.png) + +### Summary + +In this chapter, we presented tasks, subclasses of `Task`. Tasks are components that do not render themselves but are used to build application flow based on the composition of other components. We saw that the composition is expressed in plain Pharo. + + + + + diff --git a/Chapters/09-Slime/slime.md b/Chapters/09-Slime/slime.md index d0528d0..9d61e83 100644 --- a/Chapters/09-Slime/slime.md +++ b/Chapters/09-Slime/slime.md @@ -1 +1,63 @@ -## Writing good Seaside Code @cha:slime This short chapter explains how you can improve the quality of your code and your programming skills in general by running _Slime_, a Seaside-specific code critics tool. For example, Slime can detect if you do not follow the canonical forms for brush usage that we presented in Chapter . It will also help you identify other potential bugs early on, and help you produce better code. Furthermore, if you work on a Seaside library, it is able to point out portability issues between the different Smalltalk dialects. ### A Seaside Program Checker Slime analyzes your Seaside code and reveals potential problems. Slime is an extension of _Code Critics_ that is shipped with the _Refactoring Browser_. Code Critics, also called _SmallLint_, is available with the Refactoring Browser originally developed by John Brant and Don Roberts. Lukas Renggli and the Seaside community extended Code Critics to check common errors or bad style in Seaside code. The refactoring tools and Slime are available in the One-Click Image and we encourage you to use them to improve your code quality. Pay attention that the rules are not bulletproof and by no means complete. It could well be that you encounter false positives or places where it misses some serious problems, but it should give you an idea where your code might need some further investigation. Here are some of the problems that Slime detects: **Possible Bugs.** This group of rules detects severe problems that are most certainly serious bugs in the source code: - The message `with:` is not last in the cascade, - Instantiates new component while generating HTML, - Manually invokes `renderContentOn:`, - Uses the wrong output stream, - Misses call to super implementation, - Calls functionality not available while generating output, and - Calls functionality not available within a framework callback. **Bad style.** These rules detect some less severe problems that might pose maintainability problems in the future but that do not cause immediate bugs. - Extract callback code to separate method, - Use of deprecated API, and - Non-standard object initialization. **Suboptimal Code.** This set of rules suggests optimization that can be applied to code without changing its behavior. - Unnecessary block passed to brush. **Non-Portable Code.** While this set of rules is less important for application code, it is central to the Seaside code base itself. The framework runs without modification on many different Smalltalk platforms, which differ in the syntax and the libraries they support. To avoid that contributors from a specific platform accidentally submit code that only works with their platform we've added some rules that check for compatibility. The rules in this category include: - Invalid object initialization, - Uses curly brace arrays, - Uses literal byte arrays, - Uses method annotations, - Uses non-portable class, - Uses non-portable message, - ANSI booleans, - ANSI collections, - ANSI conditionals, - ANSI convertor, - ANSI exceptions, and - ANSI streams. ### Summary In this chapter, we presented tasks, subclasses of Task. Tasks are components that do not render themselves but are used to build application flow based on the composition of other components. We saw that the composition is expressed in plain Pharo. \ No newline at end of file +## Writing good Seaside Code +@cha:slime + +This short chapter explains how you can improve the quality of your code and your programming skills in general by running _Slime_, a Seaside-specific code critics tool. For example, Slime can detect if you do not follow the canonical forms for brush usage that we presented in Chapter XXX. It will also help you identify other potential bugs early on, and help you produce better code. Furthermore, if you work on a Seaside library, it is able to point out portability issues between the different Smalltalk dialects. + +### A Seaside Program Checker + + +Slime analyzes your Seaside code and reveals potential problems. Slime is an extension of _Code Critics_ that is shipped with the _Refactoring Browser_. Code Critics, also called _SmallLint_, is available with the Refactoring Browser originally developed by John Brant and Don Roberts. Lukas Renggli and the Seaside community extended Code Critics to check common errors or bad style in Seaside code. The refactoring tools and Slime are available in the One-Click Image and we encourage you to use them to improve your code quality. + +Pay attention that the rules are not bulletproof and by no means complete. It could well be that you encounter false positives or places where it misses some serious problems, but it should give you an idea where your code might need some further investigation. + +Here are some of the problems that Slime detects: + +**Possible Bugs.** This group of rules detects severe problems that are most certainly serious bugs in the source code: + +- The message `with:` is not last in the cascade, +- Instantiates new component while generating HTML, +- Manually invokes `renderContentOn:`, +- Uses the wrong output stream, +- Misses call to super implementation, +- Calls functionality not available while generating output, and +- Calls functionality not available within a framework callback. + + +**Bad style.** These rules detect some less severe problems that might pose maintainability problems in the future but that do not cause immediate bugs. + +- Extract callback code to separate method, +- Use of deprecated API, and +- Non-standard object initialization. + + +**Suboptimal Code.** This set of rules suggests optimization that can be applied to code without changing its behavior. + +- Unnecessary block passed to brush. + + +**Non-Portable Code.** While this set of rules is less important for application code, it is central to the Seaside code base itself. The framework runs without modification on many different Smalltalk platforms, which differ in the syntax and the libraries they support. To avoid that contributors from a specific platform accidentally submit code that only works with their platform we've added some rules that check for compatibility. The rules in this category include: + +- Invalid object initialization, +- Uses curly brace arrays, +- Uses literal byte arrays, +- Uses method annotations, +- Uses non-portable class, +- Uses non-portable message, +- ANSI booleans, +- ANSI collections, +- ANSI conditionals, +- ANSI convertor, +- ANSI exceptions, and +- ANSI streams. + + + +### Summary + + +In this chapter, we presented tasks, subclasses of Task. Tasks are components that do not render themselves but are used to build application flow based on the composition of other components. We saw that the composition is expressed in plain Pharo. + + + + + diff --git a/Chapters/11-Sudoko/sudoko.md b/Chapters/11-Sudoko/sudoko.md index a3953b6..df64d4f 100644 --- a/Chapters/11-Sudoko/sudoko.md +++ b/Chapters/11-Sudoko/sudoko.md @@ -1,15 +1,82 @@ -## A Web Sudoku Player @cha:sudoko In this chapter we will build a Sudoku web application as shown in Figure *@fig:upTo6@*. This gives us another opportunity to revisit how we build a simple application in Seaside. ![Just started playing.](figures/upTo6.png width=70&label=fig:upTo6) ### The solver For the Sudoku model we use the ML-Sudoku package developed by Martin Laubach which is available on SqueakSource. We thank him for allowing us to use this material. To load the package, open a Monticello browser and click on the _+Repository_ button. Select HTTP as the type of repository and specify it as follows: ``` MCHttpRepository +## A Web Sudoku Player + +@cha:sudoko + +In this chapter we will build a Sudoku web application as shown in Figure *@fig:upTo6@*. This gives us another opportunity to revisit how we build a simple application in Seaside. + +![Just started playing. %width=70&anchor=fig:upTo6](figures/upTo6.png ) + + +### The solver + + +For the Sudoku model we use the ML-Sudoku package developed by Martin Laubach which is available on SqueakSource. We thank him for allowing us to use this material. To load the package, open a Monticello browser and click on the _+Repository_ button. Select HTTP as the type of repository and specify it as follows: + + +``` +MCHttpRepository location: 'http://www.squeaksource.com/MLSudoku' user: '' - password: '' ``` Click on the _Open_ button, select the most recent version and click _Load_. ML-Sudoku is composed of 4 classes: `MLSudoku`, `MLBoard`, `MLCell`, and `MLPossibilitySet`. The class responsibilities are distributed as follows: - `MLSudoku` is the Sudoku solver. It knows how to solve a Sudoku. - `MLCell` knows its neighbors and their location on the game board. A cell does not know its possibility set, see `MLPossibilitySet` below. - `MLBoard` contains the cells and their possibility sets. - ` MLPossibilitySet` is a list of possible numbers between 1 and 9 that can go into a cell. These are the values that are possible without violating the Sudoku rule that each row, column and 3-by-3 sub-grid contains each number once. ### Sudoku First we define the class `WebSudoku` which is the Sudoku UI. We will create this class in the new `ML-WebSudoku` category: ``` WAComponent subclass: #WebSudoku - instanceVariableNames: 'sudoku' - classVariableNames: '' - package: 'ML-WebSudoku' ``` This component will contain a Sudoku solver \(an instance of `MLSudoku`\) which we will refer to using the instance variable `sudoku`. We initialize this variable by defining the following `WebSudoku>>initialize` method. ``` WebSudoku >> initialize + password: '' +``` + + +Click on the _Open_ button, select the most recent version and click _Load_. + +ML-Sudoku is composed of 4 classes: `MLSudoku`, `MLBoard`, `MLCell`, and `MLPossibilitySet`. The class responsibilities are distributed as follows: + +- `MLSudoku` is the Sudoku solver. It knows how to solve a Sudoku. +- `MLCell` knows its neighbors and their location on the game board. A cell does not know its possibility set, see `MLPossibilitySet` below. +- `MLBoard` contains the cells and their possibility sets. +- ` MLPossibilitySet` is a list of possible numbers between 1 and 9 that can go into a cell. These are the values that are possible without violating the Sudoku rule that each row, column and 3-by-3 sub-grid contains each number once. + + +### Sudoku + + +First we define the class `WebSudoku` which is the Sudoku UI. We will create this class in the new `ML-WebSudoku` category: + +``` +WAComponent << #WebSudoku + slots: {sudoku}; + package: 'ML-WebSudoku' +``` + + +This component will contain a Sudoku solver \(an instance of `MLSudoku`\) which we will refer to using the instance variable `sudoku`. We initialize this variable by defining the following `WebSudoku>>initialize` method. + +``` +WebSudoku >> initialize super initialize. - sudoku := MLSudoku new ``` **Describing and registering the application.** Now we add a few methods to the _class_ side of our component. We describe the application by defining the `description` method. We register our component as an application by defining the class `initialize` method and declare that the component `WebSudoku` can be a standalone application by having `canBeRoot` return true: ``` WebSudoku class >> description - ^ 'Play Sudoku' ``` ``` WebSudoku class >> initialize - WAAdmin register: self asApplicationAt: 'sudoku' ``` ``` WebSudoku class >> canBeRoot - ^ true ``` Finally we define a CSS style for the Sudoku component: ``` WebSudoku >> style + sudoku := MLSudoku new +``` + + +**Describing and registering the application.** Now we add a few methods to the _class_ side of our component. We describe the application by defining the `description` method. We register our component as an application by defining the class `initialize` method and declare that the component `WebSudoku` can be a standalone application by having `canBeRoot` return true: + +``` +WebSudoku class >> description + ^ 'Play Sudoku' +``` + + + +``` +WebSudoku class >> initialize + WAAdmin register: self asApplicationAt: 'sudoku' +``` + + +``` +WebSudoku class >> canBeRoot + ^ true +``` + + +Finally we define a CSS style for the Sudoku component: + +``` +WebSudoku >> style ^ '#sudokuBoard .sudokuHeader { font-weight: bold; color: white; @@ -47,23 +114,80 @@ #sudokuBoard a:hover { color: black; -}' ``` ### Rendering the Sudoku Grid What we need to do is to render a table that looks like a Sudoku grid. We start by defining a method that creates a table and uses style tags for formatting. ``` WebSudoku >> renderHeaderFor: aString on: html +}' +``` + + +### Rendering the Sudoku Grid + + +What we need to do is to render a table that looks like a Sudoku grid. We start by defining a method that creates a table and uses style tags for formatting. + +``` +WebSudoku >> renderHeaderFor: aString on: html html tableData class: 'sudokuHeader'; class: 'sudokuHBorder'; class: 'sudokuVBorder'; - with: aString ``` ``` WebSudoku >> renderContentOn: html - self renderHeaderFor: 'This is a test' on: html ``` Make sure that you invoked the class side `initialize` method and then run the application by visiting [http://localhost:8080/sudoku](http://localhost:8080/sudoku). Your browser should display the string _This is a test_. We need two helper methods when creating the labels for the `x` and `y` axis of our Sudoku grid. You don't need to actually add these helper methods yourself, they were already loaded when you loaded the Sudoku package: ``` Integer >> asSudokuCol + with: aString +``` + + +``` +WebSudoku >> renderContentOn: html + self renderHeaderFor: 'This is a test' on: html +``` + + +Make sure that you invoked the class side `initialize` method and then run the application by visiting [http://localhost:8080/sudoku](http://localhost:8080/sudoku). Your browser should display the string _This is a test_. + +We need two helper methods when creating the labels for the `x` and `y` axis of our Sudoku grid. You don't need to actually add these helper methods yourself, they were already loaded when you loaded the Sudoku package: + +``` +Integer >> asSudokuCol "Label for columns" - ^ ($1 to: $9) at: self ``` ``` Integer >> asSudokuRow + ^ ($1 to: $9) at: self +``` + + +``` +Integer >> asSudokuRow "Label for rows" - ^ ($A to: $I) at: self ``` First we print a space to get our labels aligned and then draw the label for each column. ``` WebSudoku >> renderBoardOn: html + ^ ($A to: $I) at: self +``` + + +First we print a space to get our labels aligned and then draw the label for each column. + +``` +WebSudoku >> renderBoardOn: html html table: [ html tableRow: [ self renderHeaderFor: '' on: html. 1 to: 9 do: [ :col | - self renderHeaderFor: col asSudokuCol on: html ] ] ] ``` We make sure that the method `WebSudoku>>renderContentOn:` invokes the board rendering method we just defined. ``` WebSudoku >> renderContentOn: html - self renderBoardOn: html ``` % +rowOfNumbers|width=50%+ ![The column labels look like this.](figures/rowofnumbers.png width=60&label=fig:rowOfNumbers) If you run the application again, you should see the column labels as shown in Figure *@fig:rowOfNumbers@*. We now draw each row with its label, identifying the cells with the product of its row and column number. ``` WebSudoku >> renderBoardOn: html + self renderHeaderFor: col asSudokuCol on: html ] ] ] +``` + + +We make sure that the method `WebSudoku>>renderContentOn:` invokes the board rendering method we just defined. + +``` +WebSudoku >> renderContentOn: html + self renderBoardOn: html +``` + + + + +![The column labels look like this. % width=60&anchor=fig:rowOfNumbers](figures/rowofnumbers.png) + + +If you run the application again, you should see the column labels as shown in Figure *@fig:rowOfNumbers@*. + +We now draw each row with its label, identifying the cells with the product of its row and column number. + +``` +WebSudoku >> renderBoardOn: html html table: [ html tableRow: [ self renderHeaderFor: '' on: html. @@ -72,13 +196,40 @@ html tableRow: [ self renderHeaderFor: row asSudokuRow on: html. 1 to: 9 do: [ :col | - html tableData: (col * row) asString ] ] ] ] ``` If you have entered everything correctly, your page should now look like Figure *@fig:tableOfNumbers@*. % +tableOfNumbers|width=50%+ ![Row labels are letters, column labels are numbers.](figures/table-of-numbers.png width=50&label=fig:tableOfNumbers) Now we define the method `renderCellAtRow:col:on:` that sets the style tags and redefine the `renderContentOn:` as follows so that it uses our style sheet. ``` WebSudoku >> renderContentOn: html + html tableData: (col * row) asString ] ] ] ] +``` + + +If you have entered everything correctly, your page should now look like Figure *@fig:tableOfNumbers@*. + +% +tableOfNumbers|width=50%+ + +![Row labels are letters, column labels are numbers. %width=50&anchor=fig:tableOfNumbers](figures/table-of-numbers.png ) + + + +Now we define the method `renderCellAtRow:col:on:` that sets the style tags and redefine the `renderContentOn:` as follows so that it uses our style sheet. + +``` +WebSudoku >> renderContentOn: html html div id: 'sudokuBoard'; - with: [ self renderBoardOn: html ] ``` ``` WebSudoku >> renderCellAtRow: rowInteger col: colInteger on: html + with: [ self renderBoardOn: html ] +``` + + +``` +WebSudoku >> renderCellAtRow: rowInteger col: colInteger on: html html tableData class: 'sudokuHBorder' if: rowInteger \\ 3 == 0; - class: 'sudokuVBorder' if: colInteger \\ 3 == 0 ``` You also need to change `WebSudoku>>renderBoardOn:` so that it uses our new method `WebSudoku>>renderCellAtRow:col:on:`. ``` WebSudoku >> renderBoardOn: html + class: 'sudokuVBorder' if: colInteger \\ 3 == 0 +``` + + +You also need to change `WebSudoku>>renderBoardOn:` so that it uses our new method `WebSudoku>>renderCellAtRow:col:on:`. + +``` +WebSudoku >> renderBoardOn: html html table: [ html tableRow: [ self renderHeaderFor: '' on: html. @@ -86,11 +237,32 @@ 1 to: 9 do: [ :row | html tableRow: [ self renderHeaderFor: row asSudokuRow on: html. - 1 to: 9 do: [ :col | self renderCellAtRow: row col: col on: html ] ] ] ] ``` If you refresh your page again, you should finally see a styled Sudoku grid as show in *@fig:empty-sudoku@*. % +empty-sudoku|width=70%+ ![The Sudoku board with the style sheet applied.](figures/empty.png width=70&label=fig:empty-sudoku) Next we will use a small helper method `asCompactString` that given a collection returns a string containing all the elements printed one after the other without spaces. Again, you do not need to type this method, it was loaded with the ML-Sudoku code. ``` Collection >> asCompactString + 1 to: 9 do: [ :col | self renderCellAtRow: row col: col on: html ] ] ] ] +``` + + +If you refresh your page again, you should finally see a styled Sudoku grid as shown in Figure *@fig:empty-sudoku@*. + +% +empty-sudoku|width=70%+ + +![The Sudoku board with the style sheet applied. % width=70&anchor=fig:empty-sudoku](figures/empty.png) + + +Next we will use a small helper method `asCompactString` that given a collection returns a string containing all the elements printed one after the other without spaces. Again, you do not need to type this method, it was loaded with the ML-Sudoku code. + +``` +Collection >> asCompactString | stream | stream := WriteStream on: String new. self do: [ :each | ws nextPutAll: each printString ]. - ^ stream contents ``` We define a new method `renderCellContentAtRow:col:on:` that uses `asCompactString` to display the contents of a cell. Each cell displays its possibility set. These are the values that may legally appear in that cell. ``` WebSudoku >> renderCellContentAtRow: rowInteger col: colInteger on: html + ^ stream contents +``` + + +We define a new method `renderCellContentAtRow:col:on:` that uses `asCompactString` to display the contents of a cell. Each cell displays its possibility set. These are the values that may legally appear in that cell. + +``` +WebSudoku >> renderCellContentAtRow: rowInteger col: colInteger on: html | currentCell possibilites | currentCell := MLCell row: rowInteger col: colInteger. possibilites := sudoku possibilitiesAt: currentCell. @@ -99,12 +271,39 @@ ifFalse: [ html span class: 'sudokuPossibilities'; - with: possibilites asCompactString ] ``` We make sure that the `renderCellAtRow:col:on:` invokes the method rendering cell contents. ``` WebSudoku >> renderCellAtRow: rowInteger col: colInteger on: html + with: possibilites asCompactString ] +``` + + +We make sure that the `renderCellAtRow:col:on:` invokes the method rendering cell contents. + +``` +WebSudoku >> renderCellAtRow: rowInteger col: colInteger on: html html tableData class: 'sudokuHBorder' if: rowInteger \\ 3 = 0; class: 'sudokuVBorder' if: colInteger \\ 3 = 0; - with: [ self renderCellContentAtRow: rowInteger col: colInteger on: html ] ``` Refresh your application again, and your grid should appear as in *@fig:with-possibilities@*. % +with-possibilities|width=80%+ ![The Sudoku grid is showing the possible values for each cell.](figures/incomplete.png width=70&label=fig:with-possibilities) ### Adding Input Now we will change our application so that we can enter numbers into the cells of the Sudoku grid. We define the method `setCell:to:` that changes the state of a cell and we extend the method `renderCellContentAtRow:col:on:` to use this new method. ``` WebSudoku >> setCell: aCurrentCell to: anInteger - sudoku atCell: aCurrentCell removeAllPossibilitiesBut: anInteger ``` ``` WebSudoku >> renderCellContentAtRow: rowInteger col: colInteger on: html + with: [ self renderCellContentAtRow: rowInteger col: colInteger on: html ] +``` + +Refresh your application again, and your grid should appear as in *@fig:with-possibilities@*. + +% +with-possibilities|width=80%+ +![The Sudoku grid is showing the possible values for each cell. % width=70&anchor=fig:with-possibilities](figures/incomplete.png) + + +### Adding Input + + +Now we will change our application so that we can enter numbers into the cells of the Sudoku grid. We define the method `setCell:to:` that changes the state of a cell and we extend the method `renderCellContentAtRow:col:on:` to use this new method. + +``` +WebSudoku >> setCell: aCurrentCell to: anInteger + sudoku atCell: aCurrentCell removeAllPossibilitiesBut: anInteger +``` + + +``` +WebSudoku >> renderCellContentAtRow: rowInteger col: colInteger on: html | currentCell possibilities | currentCell := MLCell row: rowInteger col: colInteger. possibilities := sudoku possibilitiesAt: currentCell. @@ -122,17 +321,81 @@ integerValue := value asInteger. integerValue isNil ifFalse: [ (possibilities includes: integerValue) - ifTrue: [ self setCell: currentCell to: integerValue ] ] ] ] ``` The above code renders a text input box within a form tag, in each cell where there are more than one possibilities. Now you can type a value into the Sudoku grid and press return to save it, as seen in *@fig:incomplete@*. As you enter new values, you will see the possibilities for cells automatically be automatically reduced. % +incomplete|width=70%+ ![A partially filled Sudoku grid.](figures/incomplete.png width=70&label=fig:incomplete) Now we can also ask the Sudoku model to solve itself by modifying the method `renderContentOn:`. We first check whether the Sudoku grid is solved and if not, we add an anchor whose callback will solve the puzzle. ``` WebSudoku>>renderContentOn: html + ifTrue: [ self setCell: currentCell to: integerValue ] ] ] ] +``` + + +The above code renders a text input box within a form tag, in each cell where there are more than one possibilities. Now you can type a value into the Sudoku grid and press return to save it, as seen in Figure *@fig:incomplete@*. As you enter new values, you will see the possibilities for cells automatically be automatically reduced. + + +![A partially filled Sudoku grid. % width=70&anchor=fig:incomplete](figures/incomplete.png) + + + +Now we can also ask the Sudoku model to solve itself by modifying the method `renderContentOn:`. We first check whether the Sudoku grid is solved and if not, we add an anchor whose callback will solve the puzzle. + +``` +WebSudoku>>renderContentOn: html html div id: 'sudokuBoard'; with: [ self renderBoardOn: html. sudoku solved ifFalse: [ html break. html anchor callback: [ sudoku := sudoku solve ]; - with: 'Solve' ] ] ``` Note that the solver uses backtracking, i.e., it finds a missing number by trying a possibility and if it fails to find a solution, it restarts with a different number. To backtrack the solver works on copies of the Sudoku grid, throwing away grids that don't work and restarting. This is why we need to assign the result of sending the message `solve` since it returns a new Sudoku grid. Figure *@fig:complete@* shows the result of clicking on Solve. ![A solved Sudoku grid.](figures/complete.png width=70&label=fig:complete) ### Back Button Now let's play a bit. Suppose we have entered the values 1 through 6 as shown in Figure *@fig:upTo6@* and we want to replace the 6 with 7. If we press the _Back_ button, change the 6 to 7 and press return, we get 6 instead of 7. The problem is that we need to copy the state of the Sudoku grid before making the cell assignment in `setCell:to:`. ``` WebSudoku >> setCell: currentCell to: anInteger + with: 'Solve' ] ] +``` + + +Note that the solver uses backtracking, i.e., it finds a missing number by trying a possibility and if it fails to find a solution, it restarts with a different number. To backtrack the solver works on copies of the Sudoku grid, throwing away grids that don't work and restarting. This is why we need to assign the result of sending the message `solve` since it returns a new Sudoku grid. Figure *@fig:complete@* shows the result of clicking on Solve. + +![A solved Sudoku grid. % width=70&anchor=fig:complete](figures/complete.png) + + +### Back Button + + +Now let's play a bit. Suppose we have entered the values 1 through 6 as shown in Figure *@fig:upTo6@* and we want to replace the 6 with 7. If we press the _Back_ button, change the 6 to 7 and press return, we get 6 instead of 7. The problem is that we need to copy the state of the Sudoku grid before making the cell assignment in `setCell:to:`. + +``` +WebSudoku >> setCell: currentCell to: anInteger sudoku := sudoku copy atCell: currentCell - removeAllPossibilitiesBut: anInteger ``` If you change the definition of `setCell:to:` and try to replace 6 with 7 you will get a stack error similar to that shown in *@fig:error@*. ![Error when trying to replace 6 by 7.](figures/error.png width=70&label=fig:error) If you click on the debug link at the top of the stack trace and then look at your Pharo image, you will see that it has opened a debugger. Now you can check the problem. If you select the expression ``` self possibilitiesAt: aCell ``` and print it, you will get a possibility set with 6, which is the previous value you gave to the cell. The code of the method `MLSudoku>>verifyPossibility:for:` raises an error if the new value is not among the possible values for that cell. ``` MLSudoku >> verifyPossibility: anInteger for: aCell + removeAllPossibilitiesBut: anInteger +``` + + +If you change the definition of `setCell:to:` and try to replace 6 with 7 you will get a stack error similar to that shown in *@fig:error@*. + +![Error when trying to replace 6 by 7. % width=70&anchor=fig:error](figures/error.png) + + +If you click on the debug link at the top of the stack trace and then look at your Pharo image, you will see that it has opened a debugger. Now you can check the problem. If you select the expression + +``` +self possibilitiesAt: aCell +``` + + +and print it, you will get a possibility set with 6, which is the previous value you gave to the cell. The code of the method `MLSudoku>>verifyPossibility:for:` raises an error if the new value is not among the possible values for that cell. + +``` +MLSudoku >> verifyPossibility: anInteger for: aCell ((self possibilitiesAt: aCell) includes: anInteger) - ifFalse: [ Error signal ] ``` In fact when you pressed the _Back_ button, the Sudoku UI was refreshed but its model was still holding the old values. What we need to do is to indicate to Seaside that when we press the _Back_ button the state of the model should be kept in sync and rollback to the corresponding older version. We do so by defining the method `states` which returns the elements that should be kept in sync. ``` WebSudoku >> states - ^ Array with: self ``` ### Summary While the Sudoku solver introduces some subtleties because of its backtracking behavior, this application shows the power of Seaside to manage state. Now you have a solid basis for building a really powerful Sudoku online application. Have a look at the class MLSudoku. Extend the application by loading challenging Sudoku grids that are defined by a string. \ No newline at end of file + ifFalse: [ Error signal ] +``` + + +In fact when you pressed the _Back_ button, the Sudoku UI was refreshed but its model was still holding the old values. What we need to do is to indicate to Seaside that when we press the _Back_ button the state of the model should be kept in sync and rollback to the corresponding older version. We do so by defining the method `states` which returns the elements that should be kept in sync. + +``` +WebSudoku >> states + ^ Array with: self +``` + + +### Summary + + +While the Sudoku solver introduces some subtleties because of its backtracking behavior, this application shows the power of Seaside to manage state. + +Now you have a solid basis for building a really powerful Sudoku online application. Have a look at the class MLSudoku. Extend the application by loading challenging Sudoku grids that are defined by a string. \ No newline at end of file diff --git a/Chapters/12-Serving/serving.log b/Chapters/12-Serving/serving.log new file mode 100644 index 0000000..e69de29 diff --git a/Chapters/12-Serving/serving.md b/Chapters/12-Serving/serving.md index 7d9441d..10a4967 100644 --- a/Chapters/12-Serving/serving.md +++ b/Chapters/12-Serving/serving.md @@ -1,24 +1,240 @@ -## Serving Files @cha:serving % +Just started playing.>file://figures/upTo6.png|width=70|label=fig:upTo6+ Most web-based applications make heavy use of static resources. By \`\`static'' we mean resources whose contents are not sensitive to the context in which they are used. These resources are not dependent on the user or session state and while they may change from time to time they typically don't change during the time span of a single user's session. Static resources include for example images, style sheets and JavaScript files. Using these resources in a Seaside application need be no different from using them in any other web application development framework: when deploying your application you can serve these resources using a web server and reference them in your Seaside application, as described in Chapter *@cha:deployment@*. In addition, Seaside supports a more tightly integrated file serving technique, called _FileLibrary_, which has some advantages over using a separate web server. In this chapter we will cover how to reference external resources and how to use the integrated FileLibrary to serve them from your Smalltalk image. Note that using FileLibrary to serve static resources is often slower than using a dedicated web server. In Chapter we explain how to serve static files in a more efficient way using Apache. ### Images We illustrate the inclusion of static resources by displaying an external picture within an otherwise empty component as shown in Figure *@fig:external@*. Create a component and use the method `WAImageTag>>url:` to add a URL to an image as follows: ``` ComponentWithExternalResource >> renderContentOn: html - html image url: 'http://www.seaside.st/styles/logo-plain.png' ``` ![Including an external picture into your components.](figures/external.png width=70&label=fig:external) % +external-resource|width=70%+ If you have many static files that all live in the same location, it is annoying to have to repeat the base-path over and over again. In this case you should use `WAImageTag>>resourceUrl:` to provide the tail of the URL. ``` ComponentWithExternalResource >> renderContentOn: html - html image resourceUrl: 'styles/logo-plain.png' ``` To tell Seaside about the part of the URL that you left out in your rendering code you have to go to the application configuration page \(at [http://localhost:8080/config](http://localhost:8080/config)\) and specify the _Resource Base URL_ in the server settings. Just enter `http://www.seaside.st` as shown in Figure *@fig:baseUrl@*. Seaside will automatically prepend this string to all URLs specified using `resourceUrl:>>resourceUrl:`. This reduces your code size and can be very useful if you want to move the resource location during deployment. % +resource-base-url|width=60%+ ![Including an external picture into your components.](figures/resourcebase.png width=950&label=fig:baseUrl) Be careful where you put the slash. Normally directories in URLs end with a slash, that's why we specified the resource base URL ending with a slash. Thus, you should avoid putting a slash at the beginning of the URL fragments you pass to `resourceUrl:`. #### Serving a generated image Another interesting way to serve a picture is to use a dynamically generated picture from within your image \(see Figure *@fig:form@*\). It is possible to use `WAImageTag>>form:` to pass a Pharo `Form` directly to the image brush. ``` ComponentWithForm >> renderContentOn: html - html image form: aForm ``` That works reasonably well for simple graphics, however most visual things in Pharo are made using morphs. Luckily it is simple to convert a morph to a form: ``` ComponentWithForm >> renderContentOn: html +## Serving Files + +@cha:serving + +% +Just started playing.>file://figures/upTo6.png|width=70|anchor=fig:upTo6+ + +Most web-based applications make heavy use of static resources. By \`\`static'' we mean resources whose contents are not sensitive to the context in which they are used. These resources are not dependent on the user or session state and while they may change from time to time they typically don't change during the time span of a single user's session. Static resources include for example images, style sheets and JavaScript files. + +Using these resources in a Seaside application need be no different from using them in any other web application development framework: when deploying your application you can serve these resources using a web server and reference them in your Seaside application, as described in Chapter *@cha:deployment@*. + +In addition, Seaside supports a more tightly integrated file serving technique, called _FileLibrary_, which has some advantages over using a separate web server. In this chapter we will cover how to reference external resources and how to use the integrated FileLibrary to serve them from your Smalltalk image. Note that using FileLibrary to serve static resources is often slower than using a dedicated web server. In Chapter we explain how to serve static files in a more efficient way using Apache. + + +### Images + + +We illustrate the inclusion of static resources by displaying an external picture within an otherwise empty component as shown in Figure *@fig:external@*. Create +a component and use the method `WAImageTag>>url:` to add a URL to an image as follows: + +``` +ComponentWithExternalResource >> renderContentOn: html + html image url: 'http://www.seaside.st/styles/logo-plain.png' +``` + + +![Including an external picture into your components. % width=70&anchor=fig:external](figures/external.png) + + +If you have many static files that all live in the same location, it is annoying to have to repeat the base-path over and over again. In this case you should use `WAImageTag>>resourceUrl:` to provide the tail of the URL. + +``` +ComponentWithExternalResource >> renderContentOn: html + html image resourceUrl: 'styles/logo-plain.png' +``` + + + +To tell Seaside about the part of the URL that you left out in your rendering code you have to go to the application configuration page (at [http://localhost:8080/config](http://localhost:8080/config)) and specify the _Resource Base URL_ in the server settings. Just enter `http://www.seaside.st` as shown in Figure *@fig:baseUrl@*. Seaside will automatically prepend this string to all URLs specified using `resourceUrl:>>resourceUrl:`. This reduces your code size and can be very useful if you want to move the resource location during deployment. + +% +resource-base-url|width=60%+ + +![Including an external picture into your components. % width=950&anchor=fig:baseUrl](figures/resourcebase.png) + +Be careful where you put the slash. Normally directories in URLs end with a slash, that's why we specified the resource base URL ending with a slash. Thus, you should avoid putting a slash at the beginning of the URL fragments you pass to `resourceUrl:`. + + +#### Serving a generated image + + +Another interesting way to serve a picture is to use a dynamically generated picture from within your image (see Figure *@fig:form@*). It is possible to use `WAImageTag>>form:` to pass a Pharo `Form` directly to the image brush. + +``` +ComponentWithForm >> renderContentOn: html + html image form: aForm +``` + + +That works reasonably well for simple graphics, however most visual things in Pharo are made using morphs. Luckily it is simple to convert a morph to a form: + +``` +ComponentWithForm >> renderContentOn: html html image form: (EllipseMorph new color: Color orange; extent: 200 @ 100; borderWidth: 3; - imageForm) ``` You can also use `WAImageTag>>document:` as follows: ``` html image document: EllipseMorph new ``` % +resource-form-morph|width=70%+ ![Displaying Pharo graphical object..](figures/form-morph.png width=70&label=fig:form) Have a look at the example implemented in the class `WAScreenshot`. It demonstrates a much more sophisticated use of `WAImageTag>>form:` and presents the Pharo desktop as part of a web application. Furthermore it allows basic interactions with your windows from the web browser. ### Including CSS and Javascript So far, we've been including style information for our components by implementing the `style` method on our components. This is great for dynamic development, but there are a number of problems with this approach: - Seaside is generating a style sheet file each time your component is rendered. This takes time to generate. - Each generated stylesheet has the session key embedded in its URL, and so is seen as a unique file by your browser, and so loaded again. - As you integrate more components in your page, each is generating its own stylesheet, so you can end up with many resources to be downloaded for each page. Once your application's look and feel has begun to stabilise, you will want to think about using static stylesheets. These are typically included by using `link` tags in the `head` section of the XHTML document. This presents us with a problem: by the time your component gets sent `renderContentOn:`, the canvas has already generated the `head` section. Fortunately, Seaside provides a hook method called `WAComponent>>updateRoot:` which is sent to all components which are reachable directly or indirectly through children or a `call:` message -- which means basically to all visible components. This message is sent during the generation of the body of the `head` tag and can be extended to add elements to this tag. The argument to `updateRoot:` is an instance of `WAHtmlRoot` which supports the access to document elements such as ``, `<meta>`, `<javascript>` and `<stylesheet>` with their corresponding messages \(`WAHtmlRoot>>title`, `WAHtmlRoot>>meta`, `WAHtmlRoot>>javascript` and `WAHtmlRoot>>stylesheet`\). It also allows you to add attributes to the `<head>` or `<body>` tags using the messages `WAHtmlRoot>>headAttributes`, `WAHtmlRoot>>bodyAttributes`. In particular, `WAHtmlRoot` offers the possibility to add new styles or script using the messages `WAHtmlRoot>>addScript:` and `WAHtmlRoot>>addStyles:`. The object returned by both `stylesheet` and `javascript` understands `url:` which allows you to specify the URL of the stylesheet or JavaScript file. Suppose we have a stylesheet being served from [http://seaside.st/styles/main.css](http://seaside.st/styles/main.css). We could adopt this style in our document by extending `updateRoot:` as follows: ``` WAComponent subclass: #ComponentWithStyle - instanceVariableNames: '' - classVariableNames: '' - package: 'Serving-Files' ``` ``` ComponentWithStyle >> updateRoot: anHtmlRoot + imageForm) +``` + + +You can also use `WAImageTag>>document:` as follows: + +``` +html image document: EllipseMorph new +``` + + +![Displaying Pharo graphical object. % width=70&anchor=fig:form ](figures/form-morph.png) + + +Have a look at the example implemented in the class `WAScreenshot`. It demonstrates a much more sophisticated use of `WAImageTag>>form:` and presents the Pharo desktop as part of a web application. Furthermore, it allows basic interactions with your windows from the web browser. + +### Including CSS and Javascript + + +So far, we've been including style information for our components by implementing the `style` method on our components. This is great for dynamic development, but there are a number of problems with this approach: + +- Seaside generates a style sheet file each time your component is rendered. This takes time to generate. +- Each generated stylesheet has the session key embedded in its URL, and so is seen as a unique file by your browser, and so loaded again. +- As you integrate more components in your page, each generates its own stylesheet, so you can end up with many resources to be downloaded for each page. + + +Once your application's look and feel has begun to stabilize, you will want to think about using static stylesheets. These are typically included by using `link` tags in the `head` section of the XHTML document. This presents us with a problem: by the time your component gets sent `renderContentOn:`, the canvas has already generated the `head` section. + +Fortunately, Seaside provides a hook method called `WAComponent>>updateRoot:` which is sent to all components which are reachable directly or indirectly through children or a `call:` message -- which means basically to all visible components. This message is sent during the generation of the body of the `head` tag and can be extended to add elements to this tag. The argument to `updateRoot:` is an instance of `WAHtmlRoot` which supports the access to document elements such as `<title>`, `<meta>`, `<javascript>` and `<stylesheet>` with their corresponding messages (`WAHtmlRoot>>title`, `WAHtmlRoot>>meta`, `WAHtmlRoot>>javascript` and `WAHtmlRoot>>stylesheet`). It also allows you to add attributes to the `<head>` or `<body>` tags using the messages `WAHtmlRoot>>headAttributes`, `WAHtmlRoot>>bodyAttributes`. + +In particular, `WAHtmlRoot` offers the possibility to add new styles or script using the messages `WAHtmlRoot>>addScript:` and `WAHtmlRoot>>addStyles:`. + +The object returned by both `stylesheet` and `javascript` understands `url:` which allows you to specify the URL of the stylesheet or JavaScript file. Suppose we have a stylesheet being served from [http://seaside.st/styles/main.css](http://seaside.st/styles/main.css). We could adopt this style in our document by extending `updateRoot:` as follows: + + +``` +WAComponent << #ComponentWithStyle + package: 'Serving-Files' +``` + + +``` +ComponentWithStyle >> updateRoot: anHtmlRoot super updateRoot: anHtmlRoot. - anHtmlRoot stylesheet url: 'http://seaside.st/styles/main.css' ``` ``` ComponentWithStyle >> renderContentOn: html + anHtmlRoot stylesheet url: 'http://seaside.st/styles/main.css' +``` + + +``` +ComponentWithStyle >> renderContentOn: html html heading level: 1; with: 'Seaside'. - html text: 'This component uses the Seaside style.' ``` Running the example should give you the following *@fig:withStyle@*: % +withStyle|width=80%+ ![Application with enabled style sheet.](figures/withStyle.png width=80&label=fig:withStyle) Now we will show how you can replace the stylesheet using the FileLibrary. ### Working with File Libraries Seaside includes a library for serving files called _FileLibrary_. This solution is handy for rapid application development and is suitable for deployed applications which only make use of a small number of small files. It has the advantage that all of the resources are contained in your Smalltalk image and can be versioned with your favorite Smalltalk version management tools. However this also means that these resources are **not** reachable where most of your operating system's tools are accustomed to find things. FileLibrary has the primary advantage that it is a portable way to serve static contents directly from Seaside without the need to setup a standalone web server. See Chapter to read about Apache configuration for static file serving. #### Creating a File Library Setting up a file library is easy. Here are the steps you need to follow. 1. Put your static files in a directory. The location of the directory is not significant. From within the directory, the files can reference each other using their file names. 1. Create a file library by subclassing `WAFileLibrary`. For the rest of this text we assume its name is `MyFileLibrary`. 1. Add files to your file library. There are three ways to add files to your file library: - Programmatically. - Via the web interface. - By editing your `MyFileLibrary` directly in your image. **Adding files programmatically.** You can add files programmatically by using the class side methods `addAllFilesIn:` and `addFileAt:` in `MyFileLibrary`. For example: ``` MyFileLibrary addAllFilesIn: '/path/to/directory' -MyFileLibrary addFileAt: '/path/to/background.png' ``` **Adding files via the config interface.** Open the config application at [http://localhost:8080/config](http://localhost:8080/config) and click the \`\`configure'' link for file libraries as shown in Figure *@ref:configureFiles@*. This shows which file libraries are available. % +configureFiles|width=70%+ ![Configuring file libraries through the web interface: clicking on files - configure.](figures/configureFiles.png width=80&label=ref:configureFiles) Click the configure link for `MyFileLibrary` as shown in *@ref:configureMyFileLibrary@* right. % +configureMyFileLibrary|width=70%+ ![File libraries.](figures/configureMyFileLibrary.png width=80&label=ref:configureMyFileLibrary) There you can add a file by uploading it \(select the file, then click the _Add_ button as shown by *@ref:addingMyFileLibrary@*\). % +addingMyFileLibrary|width=70%+ ![File libraries.](figures/AddedToMyFileLibrary.png width=80&label=ref:addingMyFileLibrary) !!important When you add a file to a file library, Seaside creates a method with the file contents. If you find that there is an unusually long wait after pressing the _Add_ button, make sure that the system \(Squeak/Pharo\) isn't waiting for you to type your initials to confirm that you want to create a new method. **Adding a file by editing the class.** File libraries are just objects and \`\`files'' in the file library are just methods so you can always add and modify FileLibrary entries using your normal class browser but be sure to follow the method naming convention mentioned above. You'll probably find it pretty inconvenient to edit images within the browser though. Adding a file to a file library either programmatically or using the configuration interface defines a corresponding method in the file library class, with the file name determining the name of the method. The dot is removed and the first letter of the suffix is capitalized. For example, the file main.css becomes the method `MyFileLibrary>>mainCss`. This puts certain limitations on the allowed file names. For example, the main part of the file name may not be all digits. Once your files have been imported into the file library they are maintained independently from the files on your computer's file system. If you modify your files you will have to re-add them to the file library. Once your files are stored in a FileLibrary they will be available to be served through Seaside. #### Referencing FileLibrary files by URL How you use a file library depends on what you want to do with the files in it. As you've seen in the previous sections, using image, music, style sheets and JavaScript files requires knowing their URL. You can find the URL of any document in your file library by sending the class `WAFileLibrary class>>urlOf:`. For example, if you had added the file `picture.jpg` to your library and you want to display it in a component you would write something like: ``` MyClass>>renderContentOn: html - html image url: (MyFileLibrary urlOf: #pictureJpg) ``` The URL returned by `urlOf:` is relative to the current server. It does not contain the [http://servername.com/](http://servername.com/) - the so-called \`\`method_ and \`\`host_ - portion of the URL. Note that WAFileLibrary implements a class method called `/`, so the expression `MyFileLibrary / #pictureJpeg` is equivalent to `MyFileLibrary urlOf: #pictureJpeg`. Once you know the URL of the FileLibrary resources you can use them to include style sheets and JavaScript in your components as we have already discussed. ### Example of FileLibrary in Use We've gone on long enough without a working hands-on example. To illustrate how to use a file library, we will show how to add some resources to the WebCounter application we defined in the first chapter of this book \([http://localhost:8080/webcounter](http://localhost:8080/webcounter)\) or can also use the version that comes with Seaside \([http://localhost:8080/examples/counter](http://localhost:8080/examples/counter)\). First we create a new subclass of `WAFileLibrary` named `CounterLibrary` as follows: ``` WAFileLibrary subclass: #CounterLibrary - instanceVariableNames: '' - classVariableNames: '' - package: 'Test' ``` % +WACounterFileLibraryEmpty|width=70%+ First as you can see in Figure *@ref:counterLibraryEmpty@* the counter library is empty. ![An empty CounterLibrary.](figures/WACounterFileLibraryEmpty.png width=80&label=ref:counterLibraryEmpty) % +AddedCounterLibrary|width=70%+ ![Adding files to the CounterLibrary.](figures/WACounterFileLibraryAdded.png width=80&label=ref:counterLibraryAdded) We follow the steps presented in the previous section and associate two resources to our library \(see Figure *@ref:counterLibraryAdded@*\). One is an icon named `seaside.png` and the other is a CSS file named `seaside.css` -- you can download the ones from the Seaside website we mentioned before: seaside.png [http://www.seaside.st/styles/logo-plain.png](http://www.seaside.st/styles/logo-plain.png) \(rename once downloaded\). seaside.css [http://seaside.st/styles/main.css](http://seaside.st/styles/main.css) !!important Pay attention that the file name of your resources does not contain non-alphabetic characters since it may cause problems. Now we change the method `renderContentOn:` -- this shows how we access resources using the `urlOf:`. ``` WebCounter >> renderContentOn: html + html text: 'This component uses the Seaside style.' +``` + + +Running the example should give you the following Figure *@fig:withStyle@*: + +![Application with enabled style sheet. %width=80&anchor=fig:withStyle](figures/withStyle.png) + +Now we will show how you can replace the stylesheet using the FileLibrary. + +### Working with File Libraries + + +Seaside includes a library for serving files called _FileLibrary_. This solution is handy for rapid application development and is suitable for deployed applications which only make use of a small number of small files. It has the advantage that all of the resources are contained in your Smalltalk image and can be versioned with your favorite Smalltalk version management tools. However this also means that these resources are **not** reachable where most of your operating system's tools are accustomed to find things. + +FileLibrary has the primary advantage that it is a portable way to serve static contents directly from Seaside without the need to setup a standalone web server. See Chapter to read about Apache configuration for static file serving. + + +#### Creating a File Library + + +Setting up a file library is easy. Here are the steps you need to follow. + +1. Put your static files in a directory. The location of the directory is not significant. From within the directory, the files can reference each other using their file names. +1. Create a file library by subclassing `WAFileLibrary`. For the rest of this text we assume its name is `MyFileLibrary`. +1. Add files to your file library. There are three ways to add files to your file library: + - Programmatically. + - Via the web interface. + - By editing your `MyFileLibrary` directly in your image. + + +**Adding files programmatically.** You can add files programmatically by using the class side methods `addAllFilesIn:` and `addFileAt:` in `MyFileLibrary`. For example: + +``` +MyFileLibrary addAllFilesIn: '/path/to/directory' +MyFileLibrary addFileAt: '/path/to/background.png' +``` + + + +**Adding files via the config interface.** Open the config application at [http://localhost:8080/config](http://localhost:8080/config) and click the ''configure'' link for file libraries as shown in Figure *@ref:configureFiles@*. This shows which file libraries are available. + + +![Configuring file libraries through the web interface: clicking on files - configure. %width=80&anchor=ref:configureFiles](figures/configureFiles.png ) + +Click the configure link for `MyFileLibrary` as shown in *@ref:configureMyFileLibrary@* right. + + +![File libraries. % width=80&anchor=ref:configureMyFileLibrary](figures/configureMyFileLibrary.png) + + +There you can add a file by uploading it (select the file, then click the _Add_ button as shown by *@ref:addingMyFileLibrary@*). + +![File libraries. % width=80&anchor=ref:addingMyFileLibrary](figures/AddedToMyFileLibrary.png) + + +!!important When you add a file to a file library, Seaside creates a method with the file contents. If you find that there is an unusually long wait after pressing the _Add_ button, make sure that the system (Squeak/Pharo) isn't waiting for you to type your initials to confirm that you want to create a new method. + +**Adding a file by editing the class.** File libraries are just objects and ''files'' in the file library are just methods so you can always add and modify FileLibrary entries using your normal class browser but be sure to follow the method naming convention mentioned above. You'll probably find it pretty inconvenient to edit images within the browser though. + +Adding a file to a file library either programmatically or using the configuration interface defines a corresponding method in the file library class, with the file name determining the name of the method. The dot is removed and the first letter of the suffix is capitalized. For example, the file main.css becomes the method `MyFileLibrary>>mainCss`. This puts certain limitations on the allowed file names. For example, the main part of the file name may not be all digits. + +Once your files have been imported into the file library they are maintained independently from the files on your computer's file system. If you modify your files you will have to re-add them to the file library. + +Once your files are stored in a FileLibrary they will be available to be served through Seaside. + + +#### Referencing FileLibrary files by URL + + +How you use a file library depends on what you want to do with the files in it. As you've seen in the previous sections, using image, music, style sheets and JavaScript files requires knowing their URL. You can find the URL of any document in your file library by sending the class `WAFileLibrary class>>urlOf:`. For example, if you had added the file `picture.jpg` to your library and you want to display it in a component you would write something like: + +``` +MyClass>>renderContentOn: html + html image url: (MyFileLibrary urlOf: #pictureJpg) +``` + + + +The URL returned by `urlOf:` is relative to the current server. It does not contain the [http://servername.com/](http://servername.com/) - the so-called ''method_ and ''host_ - portion of the URL. Note that WAFileLibrary implements a class method called `/`, so the expression `MyFileLibrary / #pictureJpeg` is equivalent to `MyFileLibrary urlOf: #pictureJpeg`. + +Once you know the URL of the FileLibrary resources you can use them to include style sheets and JavaScript in your components as we have already discussed. + + + +### Example of FileLibrary in Use + + +We've gone on long enough without a working hands-on example. To illustrate how to use a file library, we will show how to add some resources to the WebCounter application we defined in the first chapter of this book ([http://localhost:8080/webcounter](http://localhost:8080/webcounter)) or can also use the version that comes with Seaside ([http://localhost:8080/examples/counter](http://localhost:8080/examples/counter)). First we create a new subclass of `WAFileLibrary` named `CounterLibrary` as follows: + +``` +WAFileLibrary << #CounterLibrary + package: 'Test' +``` + + + +First as you can see in Figure *@ref:counterLibraryEmpty@* the counter library is empty. + +![An empty CounterLibrary. % width=80&anchor=ref:counterLibraryEmpty](figures/WACounterFileLibraryEmpty.png) + + +![Adding files to the CounterLibrary. %width=80&anchor=ref:counterLibraryAdded](figures/WACounterFileLibraryAdded.png ) + + +We follow the steps presented in the previous section and associate two resources to our library (see Figure *@ref:counterLibraryAdded@*). One is an icon named `seaside.png` and the other is a CSS file named `seaside.css` -- you can download the ones from the Seaside website we mentioned before: + + + +seaside.png +[http://www.seaside.st/styles/logo-plain.png](http://www.seaside.st/styles/logo-plain.png) (rename once downloaded). + +seaside.css +[http://seaside.st/styles/main.css](http://seaside.st/styles/main.css) + +!!important Pay attention that the file name of your resources does not contain non-alphabetic characters since it may cause problems. + +Now we change the method `renderContentOn:` -- this shows how we access resources using the `urlOf:`. + +``` +WebCounter >> renderContentOn: html html image url: (CounterLibrary urlOf: #seasidePng). html heading: count. html anchor @@ -27,9 +243,27 @@ MyFileLibrary addFileAt: '/path/to/background.png' ``` **Adding files via the html space. html anchor callback: [ self decrease ]; - with: '--' ``` Next we implement `updateRoot:` so that our component contains a link to our style sheet: ``` WebCounter >> updateRoot: anHtmlRoot + with: '--' +``` + + +Next we implement `updateRoot:` so that our component contains a link to our style sheet: + +``` +WebCounter >> updateRoot: anHtmlRoot super updateRoot: anHtmlRoot. - anHtmlRoot stylesheet url: (CounterLibrary urlOf: #seasideCss) ``` This causes the look of our application to change. It now uses the CSS file we added to our file library as shown by *@ref:counterNoCss@*. % +counterNoCss|width=20%+ ![Counter with the updateRoot: method defined.](figures/counterNoCss.png width=80&label=ref:counterNoCss) Have a look at the XHTML source generated by Seaside by using your browser's View Source option. You will see that the links are added to the head section of the HTML document as shown below: ``` ... + anHtmlRoot stylesheet url: (CounterLibrary urlOf: #seasideCss) +``` + + +This causes the look of our application to change. It now uses the CSS file we added to our file library as shown by Figure *@ref:counterNoCss@*. + +![Counter with the updateRoot: method defined. %width=80&anchor=ref:counterNoCss](figures/counterNoCss.png ) + +Have a look at the XHTML source generated by Seaside by using your browser's View Source option. You will see that the links are added to the head section of the HTML document as shown below: + +``` +... <link rel="stylesheet" type="text/css" href="/files/CounterLibrary.css"/> </head> <body onload="onLoad()" onkeydown="onKeyDown(event)"> @@ -37,13 +271,163 @@ MyFileLibrary addFileAt: '/path/to/background.png' ``` **Adding files via the <h1>0</h1> <a href="http://localhost:8080/WebCounter?_s=UwGcN6vwGVmj9icD&_k=D6Daqxer&1">++</a>  <a href="http://localhost:8080/WebCounter?_s=UwGcN6vwGVmj9icD&_k=D6Daqxer&2">--</a> -... ``` ### Which method should I use? You have the following choices for serving static files with your Seaside application: - The default answer is pretty simple: if you don't know anything about web servers, use `FileLibrary`. - If you want to have your static resources versioned inside your Smalltalk image and don't have too many \(or too large\) resources, use `FileLibrary`. - If you prefer to keep your static resources on your file system where you can edit and version them with your favorite file-based tools but you don't want to run a separate web server, go read about how to serve static content from your image in . - Otherwise read Chapter about Apache file serving and configuration. ### A Word about Character Encodings Character encoding is an area that we programmers tend to avoid as much as possible, often fixing problems by trial and errors. With web-development you will sooner or later be bitten by character encoding bugs, no matter how you try to escape them. As soon as you are getting inputs from the user and displaying information in your web-browser, you will be confronted with character encoding problems. However, the basic concepts are simple to understand and the difficulty often lies in the extra layers that typically a web developer does not have to worry about such as the web-rendering engine, the web server and the input keyboard. In this section we'll present the two basic concepts you have to understand -_ character sets_ and _character encodings_. This should help you avoid most problems. Then we will tell you how these are supported in Seaside. In addition, we strongly suggest to read the Chapter about encodings written by Sven van Caekenberghe in the "Entreprise Pharo: a Web Perspective" book. Historically the difference between character sets and character encoding was minor, since a standard specified what characters were available as well as how they encoded. Unicode and ISO 10646 \(Universal Character Set\) changed this situation by clearly separating the two concepts. Such a separation is essential: on one hand you have the character sets you can manipulate and on the other hand you have how they are represented physically \(encoded\). #### Character Sets A character set is really just that, a set of characters. These are the characters of your alphabet. For practical reasons each character is identified by a _code point_ e.g. \$A is identified by the code point 65. Examples of character sets are ASCII, ISO-8859-1, Unicode or UCS \(Universal Character Set\). - **ASCII** \(American Standard Code for Information Interchange\) contains 128 characters. It was designed following several constraints such that it would be easy to go from a lowercase character to its uppercase equivalent. You can get the list of characters at [http://en.wikipedia.org/wiki/Ascii](http://en.wikipedia.org/wiki/Ascii). ASCII was designed with the idea in mind that other countries could plug their specific characters in it but it somehow failed. ASCII was extended in Extended ASCII which offers 256 characters. - **ISO-8859-1** \(ISO/IEC 8859-1\) is a superset of ASCII to which it adds 128 new characters. Also called **Latin-1** or **latin1**, it is the standard alphabet of the latin alphabet, and is well-suited for Western Europe, Americas, parts of Africa. Since ISO-8859-1 did not contain certain characters such as the Euro sign, it was updated into ISO-8859-15. However, ISO-8859-1 is still the default encoding of documents delivered via HTTP with a MIME type beginning with "text/". [http://www.utoronto.ca/webdocs/HTMLdocs/NewHTML/iso\_table.html](http://www.utoronto.ca/webdocs/HTMLdocs/NewHTML/iso_table.html) shows in particular ISO-8859-1. - **Unicode** is a superset of Latin-1. To accelerate the early adoption of Unicode, the first 256 code points are identical to ISO-8859-1. A character is not described via its glyph but identified by its code point, which is usually referred to using "U+" followed by its hexadecimal value. Note that Unicode also specifies a set of rules for normalization, collation bi-directional display order and much more. - **UCS** -- the \`Universal Character Set' specified by the ISO/IEC 10646 International Standard contains a hundred thousand characters. Each character is unambiguously identified by a name and an integer also called its code point. [http://www.fileformat.info/info/charset/index.htm](http://www.fileformat.info/info/charset/index.htm) shows several character sets. % +StringHierarchy|width=40%+ ![The Pharo String Library.](figures/StringHierarchy.png width=80&label=fig:StringHierarchy) #### In Pharo. Now let us see the concepts exist in Pharo. The `String`, `ByteString`, `WideString` class hierarchy is roughly equivalent to the `Integer`, `SmallInteger`, `LargeInteger` hierarchy. The class `Integer` is the abstract superclass of `SmallInteger` which represents number with ranges between -1073741824 and 1073741823, and `LargeInteger` which represents all the other numbers. In Pharo, the class `String` is the abstract superclass of the classes `ByteString` \(ISO-8859-1\) and `WideString` \(Unicode minus ISO-8859-1\) as shown in Figure *@fig:StringHierarchy@*. Such classes are about character sets and not encodings. #### Encodings An encoding is a mapping between a character \(or its code point\) and a sequence of bytes, and vice versa. **Simple Mappings.** The mapping can be a one-to-one mapping between the character and the byte that represents it. If _and only if_ your character set has 255 or less entries you can directly map each character by its index to a single byte. This is the case for ASCII and ISO-8859-1. In the latest version of Pharo, the `Character` class represents a character by storing its Unicode. Since Unicode is a superset of latin1, you can create latin1 strings by specifying their direct values. When a `String` is composed only of ASCII or latin1 characters, it is encoded in a `ByteString` \(a collection of bytes each one representing a character\). ```testcase=true String with: (Character value: 65) with: (Character value: 66) ->>> 'AB' ``` ```testcase=true 'AB' class. ->>> ByteString ``` ```testcase=true String with: (Character value: 16r5B) with: (Character value: 16r5D) ->>> '[]' ``` ```testcase=true String with: (Character value: 16rA9) ->>> the copyright character © ``` ```testcase=true Character value: 16rFC. ->>> the u-umlaut character ü ``` The characters `Character value: 16r5B` \(`[`\) and `Character value: 65` \(`A`\) are both available in ASCII and ISO-8859-1. Now `Character value: 16rA9` displays © the copyright sign which is only available in ISO-8859-1, similarly `Character value: 16rFC` displays ü. **Other Mappings.** As we already mentioned Unicode is a large superset of Latin-1 with over hundred thousand of characters. Unicode cannot simply be encoded on a single byte. There exist several character encodings for Unicode: the Unicode Transformation Format \(UTF\) encodings, and the Universal Character Set \(UCS\) encodings. The number in the encodings name indicates the number of bits in one code point \(for UTF encodings\) or the number of bytes per code point \(for UCS\) encodings. UTF-8 and UTF-16 are probably the most commonly used encodings. UCS-2 is an obsolete subset of UTF-16; UCS-4 and UTF-32 are functionally equivalent. - **UTF-8** \(8-bits UCS/Unicode Transformation Format\) is a variable length character encoding for Unicode. The Dollar Sign \(`$`\) is Unicode U+0024. UTF-8 is able to represent any character of the Unicode character sets, but it is backwards compatible with ASCII. It uses 1 byte for all ASCII characters, which have the same code values as in the standard ASCII encoding, and up to 4 bytes for other characters. - **UCS-2** which is now obsolete used 2 bytes for all the characters but it could not encode all the Unicode standard. - **UTF-16** extends UCS-2 to encode character missing from UCS-2. It is a variable size encoding using two bytes in most cases. There are two variants -- the little endian and big endian versions: `16rFC 16r00` `16r00 16rFC` are variant representations of the same encoded character. If you want to know more on character sets and character encodings, we suggest you read the Unicode Standard book, currently describing the version 5.0. #### In Seaside and Pharo !!todo update this to refer and use Zinc Now let us see how these principles apply to Pharo. The Unicode introduction started with version 3.8 of Squeak and it is slowly consolidated. You can still develop applications with different encodings with Seaside. There is an important rule in Seaside about the encoding: \`\`do unto Seaside as you would have Seaside do unto you''. This means that if you run an encoded adapter web server such as `WAKomEncoded`, Seaside will give you strings in the specified encoding but also expect from you strings in that encoding. In Squeak encoding, each character is represented by an instance of `Character`. If you have non-Latin-1 characters, you'll end up with instances of `WideString`. If all your Characters are in Latin-1, you'll have `ByteStrings`. **WAKomEncoded.** `WAKomEncoded` takes one or more bytes of UTF-8 and maps them to a single character \(and vice versa\). This allows it to support all 100,000 characters in Unicode. The following code shows how to start the encoding adapter. `"Start on a different port from your standard (WAKom) port" -`WAKomEncoded startOn: 8081 **WAKom.** Now what `WAKom` does, is a one to one mapping from bytes to characters. This works fine if and only if your character set has 255 or less entries and your encoding maps one to one. Examples for such combination are ASCII and ISO-8859-1 \(latin-1\). If you run a non-encoded web server adapter like `WAKom`, Seaside will give you strings in the encoding of the web page \(!\) and expect from you strings in the encoding of the web page. **Example.** If you have the character `ä` in a UTF-8 encoded page and you run an encoding server adapter like `WAKomEncoded` this character is represented by the Squeak string: ``` String with: (Character value: 16rE4) ``` However if you run an adapter like `WAKom`, the same character `ä` is represented by the string: ``` String with: (Character value: 16rC3) with: (Character value: 16rA4) ``` Yes, that is a string with two Characters! How can this be? Because `ä` \(the Unicode character U+00E4\) is encoded in UTF-8 with the two byte sequence `0xC3` `0xA4` and WAKom does not interpret that, it just serves the two bytes. !!important **Use UTF-8.** Try to use UTF-8 for your external encodings because it supports Unicode. So you can have access to the largest character set. Then use `WAKomEncoded`; this way your internal string will be encoded on WideString. `WAKomEncoded` will do the conversion of the response/answer between WideString and UTF-8. To see if your encoding works, go to [http://localhost:8080/tests/alltests](http://localhost:8080/tests/alltests) and then to the \`\`Encoding'' test \(select `WAEncodingTest`\). There's a link there to a page with a lot of foreign characters, pick the most foreign text you can find and paste it into the upper input field, submit the field and repeat it for the lower field. **Telling the browser the encoding.** So now that you decided which encoding to use and that Seaside will send pages to the browser in that encoding, you will have to tell the browser which encoding you decided to use. Seaside does this automatically for you. Override `charSet` in your session class \(the default is `'utf-8'` in Pharo\). In Seaside 3.0 this is a configuration setting in the application. The charset will make sure that the generated html specifies the encodings as shown below. ``` Content-Type:text/html;charset=utf-8 +... +``` + + + +### Which method should I use? + + +You have the following choices for serving static files with your Seaside application: + +- The default answer is pretty simple: if you don't know anything about web servers, use `FileLibrary`. +- If you want to have your static resources versioned inside your Smalltalk image and don't have too many (or too large) resources, use `FileLibrary`. +- If you prefer to keep your static resources on your file system where you can edit and version them with your favorite file-based tools but you don't want to run a separate web server, go read about how to serve static content from your image in . +- Otherwise read Chapter about Apache file serving and configuration. + + + + +### A Word about Character Encodings + + +Character encoding is an area that we programmers tend to avoid as much as possible, often fixing problems by trial and errors. With web-development you will sooner or later be bitten by character encoding bugs, no matter how you try to escape them. As soon as you are getting inputs from the user and displaying information in your web-browser, you will be confronted with character encoding problems. However, the basic concepts are simple to understand and the difficulty often lies in the extra layers that typically a web developer does not have to worry about such as the web-rendering engine, the web server and the input keyboard. + +In this section we'll present the two basic concepts you have to understand -_ character sets_ and _character encodings_. This should help you avoid most problems. Then we will tell you how these are supported in Seaside. In addition, we strongly suggest to read the Chapter about encodings written by Sven van Caekenberghe in the "Entreprise Pharo: a Web Perspective" book. + +Historically the difference between character sets and character encoding was minor, since a standard specified what characters were available as well as how they encoded. Unicode and ISO 10646 (Universal Character Set) changed this situation by clearly separating the two concepts. Such a separation is essential: on one hand you have the character sets you can manipulate and on the other hand you have how they are represented physically (encoded). + + +#### Character Sets + + +A character set is really just that, a set of characters. These are the characters of your alphabet. For practical reasons each character is identified by a _code point_ e.g. \$A is identified by the code point 65. + +Examples of character sets are ASCII, ISO-8859-1, Unicode or UCS (Universal Character Set). +- **ASCII** (American Standard Code for Information Interchange) contains 128 characters. It was designed following several constraints such that it would be easy to go from a lowercase character to its uppercase equivalent. You can get the list of characters at [http://en.wikipedia.org/wiki/Ascii](http://en.wikipedia.org/wiki/Ascii). ASCII was designed with the idea in mind that other countries could plug their specific characters in it but it somehow failed. ASCII was extended in Extended ASCII which offers 256 characters. +- **ISO-8859-1** (ISO/IEC 8859-1) is a superset of ASCII to which it adds 128 new characters. Also called **Latin-1** or **latin1**, it is the standard alphabet of the latin alphabet, and is well-suited for Western Europe, Americas, parts of Africa. Since ISO-8859-1 did not contain certain characters such as the Euro sign, it was updated into ISO-8859-15. However, ISO-8859-1 is still the default encoding of documents delivered via HTTP with a MIME type beginning with "text/". [http://www.utoronto.ca/webdocs/HTMLdocs/NewHTML/iso\_table.html](http://www.utoronto.ca/webdocs/HTMLdocs/NewHTML/iso_table.html) shows in particular ISO-8859-1. +- **Unicode** is a superset of Latin-1. To accelerate the early adoption of Unicode, the first 256 code points are identical to ISO-8859-1. A character is not described via its glyph but identified by its code point, which is usually referred to using "U+" followed by its hexadecimal value. Note that Unicode also specifies a set of rules for normalization, collation bi-directional display order and much more. +- **UCS** -- the \`Universal Character Set' specified by the ISO/IEC 10646 International Standard contains a hundred thousand characters. Each character is unambiguously identified by a name and an integer also called its code point. + + +[http://www.fileformat.info/info/charset/index.htm](http://www.fileformat.info/info/charset/index.htm) shows several character sets. + +% +StringHierarchy|width=40%+ + +![The Pharo String Library. % width=80&anchor=fig:StringHierarchy](figures/StringHierarchy.png) + + +#### In Pharo. + + Now let us see the concepts exist in Pharo. The `String`, `ByteString`, `WideString` class hierarchy is roughly equivalent to the `Integer`, `SmallInteger`, `LargeInteger` hierarchy. The class `Integer` is the abstract superclass of `SmallInteger` which represents number with ranges between -1073741824 and 1073741823, and `LargeInteger` which represents all the other numbers. In Pharo, the class `String` is the abstract superclass of the classes `ByteString` (ISO-8859-1) and `WideString` (Unicode minus ISO-8859-1) as shown in Figure *@fig:StringHierarchy@*. Such classes are about character sets and not encodings. + +#### Encodings + + + +An encoding is a mapping between a character (or its code point) and a sequence of bytes, and vice versa. + +**Simple Mappings.** The mapping can be a one-to-one mapping between the character and the byte that represents it. If _and only if_ your character set has 255 or less entries you can directly map each character by its index to a single byte. This is the case for ASCII and ISO-8859-1. + +In the latest version of Pharo, the `Character` class represents a character by storing its Unicode. Since Unicode is a superset of latin1, you can create latin1 strings by specifying their direct values. When a `String` is composed only of ASCII or latin1 characters, it is encoded in a `ByteString` (a collection of bytes each one representing a character). + +```testcase=true +String with: (Character value: 65) with: (Character value: 66) +>>> 'AB' +``` + + + +```testcase=true +'AB' class. +>>> ByteString +``` + + +```testcase=true +String with: (Character value: 16r5B) with: (Character value: 16r5D) +>>> '[]' +``` + + + +```testcase=true +String with: (Character value: 16rA9) +>>> the copyright character © +``` + + +```testcase=true +Character value: 16rFC. +>>> the u-umlaut character ü +``` + + +The characters `Character value: 16r5B` (`[`) and `Character value: 65` (`A`) are both available in ASCII and ISO-8859-1. Now `Character value: 16rA9` displays © the copyright sign which is only available in ISO-8859-1, similarly `Character value: 16rFC` displays ü. + +**Other Mappings.** +As we already mentioned Unicode is a large superset of Latin-1 with over hundred thousand of characters. Unicode cannot simply be encoded on a single byte. There exist several character encodings for Unicode: the Unicode Transformation Format (UTF) encodings, and the Universal Character Set (UCS) encodings. + +The number in the encodings name indicates the number of bits in one code point (for UTF encodings) or the number of bytes per code point (for UCS) encodings. UTF-8 and UTF-16 are probably the most commonly used encodings. UCS-2 is an obsolete subset of UTF-16; UCS-4 and UTF-32 are functionally equivalent. + +- **UTF-8** (8-bits UCS/Unicode Transformation Format) is a variable length character encoding for Unicode. The Dollar Sign (`$`) is Unicode U+0024. UTF-8 is able to represent any character of the Unicode character sets, but it is backwards compatible with ASCII. It uses 1 byte for all ASCII characters, which have the same code values as in the standard ASCII encoding, and up to 4 bytes for other characters. +- **UCS-2** which is now obsolete used 2 bytes for all the characters but it could not encode all the Unicode standard. +- **UTF-16** extends UCS-2 to encode character missing from UCS-2. It is a variable size encoding using two bytes in most cases. There are two variants -- the little endian and big endian versions: `16rFC 16r00` `16r00 16rFC` are variant representations of the same encoded character. + + +If you want to know more on character sets and character encodings, we suggest you read the Unicode Standard book, currently describing the version 5.0. + +#### In Seaside and Pharo + + + +!!todo update this to refer and use Zinc + +Now let us see how these principles apply to Pharo. The Unicode introduction started with version 3.8 of Squeak and it is slowly consolidated. You can still develop applications with different encodings with Seaside. There is an important rule in Seaside about the encoding: ''do unto Seaside as you would have Seaside do unto you''. This means that if you run an encoded adapter web server such as `WAKomEncoded`, Seaside will give you strings in the specified encoding but also expect from you strings in that encoding. In Squeak encoding, each character is represented by an instance of `Character`. If you have non-Latin-1 characters, you'll end up with instances of `WideString`. If all your Characters are in Latin-1, you'll have `ByteStrings`. + +**WAKomEncoded.** `WAKomEncoded` takes one or more bytes of UTF-8 and maps them to a single character (and vice versa). This allows it to support all 100,000 characters in Unicode. The following code shows how to start the encoding adapter. + +`"Start on a different port from your standard (WAKom) port" +`WAKomEncoded startOn: 8081 + + +**WAKom.** Now what `WAKom` does, is a one to one mapping from bytes to characters. This works fine if and only if your character set has 255 or less entries and your encoding maps one to one. Examples for such combination are ASCII and ISO-8859-1 (latin-1). + +If you run a non-encoded web server adapter like `WAKom`, Seaside will give you strings in the encoding of the web page (!) and expect from you strings in the encoding of the web page. + + +**Example.** If you have the character `ä` in a UTF-8 encoded page and you run an encoding server adapter like `WAKomEncoded` this character is represented by the Squeak string: + +``` +String with: (Character value: 16rE4) +``` + + +However, if you run an adapter like `WAKom`, the same character `ä` is represented by the string: + +``` +String with: (Character value: 16rC3) with: (Character value: 16rA4) +``` + + +Yes, that is a string with two Characters! How can this be? Because `ä` (the Unicode character U+00E4) is encoded in UTF-8 with the two byte sequence `0xC3` `0xA4` and WAKom does not interpret that, it just serves the two bytes. + +!!important **Use UTF-8.** Try to use UTF-8 for your external encodings because it supports Unicode. So you can have access to the largest character set. Then use `WAKomEncoded`; this way your internal string will be encoded on WideString. `WAKomEncoded` will do the conversion of the response/answer between WideString and UTF-8. + +To see if your encoding works, go to [http://localhost:8080/tests/alltests](http://localhost:8080/tests/alltests) and then to the \`\`Encoding'' test (select `WAEncodingTest`). There's a link there to a page with a lot of foreign characters, pick the most foreign text you can find and paste it into the upper input field, submit the field and repeat it for the lower field. + +**Telling the browser the encoding.** So now that you decided which encoding to use and that Seaside will send pages to the browser in that encoding, you will have to tell the browser which encoding you decided to use. Seaside does this automatically for you. Override `charSet` in your session class (the default is `'utf-8'` in Pharo). In Seaside 3.0 this is a configuration setting in the application. + +The charset will make sure that the generated html specifies the encodings as shown below. + +``` +Content-Type:text/html;charset=utf-8 <meta content="text/html;charset=utf-8" -http-equiv="Content-Type"/> ``` Now you should understand a little more about character encodings and how Seaside deals with them. Pay attention that the contents of uploaded files are not encoded even if you use WAKomEncoded. In addition you have to be aware that you may have other parts of your application that will have to deal with such issues: LDAP, Database, Host OS, etc. \ No newline at end of file +http-equiv="Content-Type"/> +``` + + +Now you should understand a little more about character encodings and how Seaside deals with them. Pay attention that the contents of uploaded files are not encoded even if you use WAKomEncoded. In addition you have to be aware that you may have other parts of your application that will have +to deal with such issues: LDAP, Database, Host OS, etc. \ No newline at end of file diff --git a/Chapters/13-Sessions/sessions.md b/Chapters/13-Sessions/sessions.md index 3ee2551..35d06c3 100644 --- a/Chapters/13-Sessions/sessions.md +++ b/Chapters/13-Sessions/sessions.md @@ -1,18 +1,134 @@ -## Managing Sessions @cha:session When a user interacts with a Seaside application for the first time, a new _session_ object is automatically instantiated. This instance lasts as long as the user interacts with the application. Eventually, after the user has not interacted with the session for a while, it will time-out -- we say that the session _expires_. The session is internally used by Seaside to remember page-views and action callbacks. Most of the time developers don't need to worry about sessions. In some cases the session can be a good place to keep information that should be available globally. The session is typically used to keep information about the current user or open database connections. For simple applications, you might consider keeping that information within your components. However, if big parts of your code need access to such objects it might be easier to use a custom session class instead. Having your own session class can be also useful when you need to clean-up external resources upon session expiry, or when you need extra behavior that is performed for every request. In this chapter you will learn how to access the current session, debug a session, define your own session to implement a simple login, recover from session expiration, and how to define bookmarkable urls. ### Accessing the Current Session From within your components the current session is always available by sending `self session`. This can happen during the rendering phase or while processing the callbacks: you get the same object in either case. To demonstrate a way to access the current session, quickly add the following code to a rendering method in your application: ``` html anchor +## Managing Sessions +@cha:session + +When a user interacts with a Seaside application for the first time, a new _session_ object is automatically instantiated. This instance lasts as long as the user interacts with the application. Eventually, after the user has not interacted with the session for a while, it will time-out -- we say that the session _expires_. The session is internally used by Seaside to remember page-views and action callbacks. Most of the time developers don't need to worry about sessions. + +In some cases the session can be a good place to keep information that should be available globally. The session is typically used to keep information about the current user or open database connections. For simple applications, you might consider keeping that information within your components. However, if big parts of your code need access to such objects it might be easier to use a custom session class instead. + +Having your own session class can be also useful when you need to clean-up external resources upon session expiry, or when you need extra behavior that is performed for every request. + +In this chapter you will learn how to access the current session, debug a session, define your own session to implement a simple login, recover from session expiration, and how to define bookmarkable urls. + +### Accessing the Current Session + + +From within your components the current session is always available by sending `self session`. This can happen during the rendering phase or while processing the callbacks: you get the same object in either case. To demonstrate a way to access the current session, quickly add the following code to a rendering method in your application: + +``` +html anchor callback: [ self show: (WAInspector current on: self session) ]; - with: 'Inspect Session' ``` This displays a link that opens a Seaside inspector on the session. Click the link and explore the contents of the active session. To get an inspector within your image you can use the code `self session inspect`. In both cases you should be able to navigate through the object. In rare cases it might be necessary to access the current session from outside your component tree. Think twice before doing that though: it is considered to be extremely bad coding style to depend on the session from outside your component tree. Anyway, in some cases it might come in handy. In such a case, you can use the following expressions: ``` WARequestContext value session ``` But again you should avoid accessing the session from outside of the component tree. #### Accessing the Session from the Debugger In older versions of Seaside, session objects could not be inspected from the debugger as normal objects. If you tried to evaluate `self session` the debugger would answer `nil` instead of the expected session object. This is because sessions are only accessible from within your web application process, and the Smalltalk debugger lives somewhere else. In Seaside 3.0 this problem is fixed on most platforms. If this doesn't work for you, then you need to use a little workaround to access the session from within the debugger. Put the following expression into your code to open an inspector from within the web application and halt the application by opening a debugger: ``` self session inspect; halt ``` ### Customizing the Session for Login We will now implement an extremely simple login facility to show how to use a custom session. We will enhance the `miniInn` application we developed in Chapter and add a login facility. When a user interacts with a Seaside application for the first time, an instance of the application's session class is created. The class `WASession` is the default session class, but this can be changed for each application, allowing you to store key information on this class. Different parts of the system will then be able to take advantage of the information to offer different services to the user. We will define our own session class and use it to store user login information. We will add login functionality to our existing component. The login functionality could also be supported by using a task and/or a specific login component. The principle is the same: you use the session to store some data that is accessible from everywhere within the current session. In our application we want to store whether the user is logged in. Therefore we create a subclass called `InnSession` of the class `WASession` and we will associate such a new session class to our hotel application. We add the instance variable `user` to the session to hold the identity of the user who is currently logged in. ``` WASession subclass: #InnSession - instanceVariableNames: 'user' - classVariableNames: '' - package: 'SeasideBook' ``` We define some utility methods to query the user login information. ``` InnSession >> login: aString - user := aString ``` ``` InnSession >> logout - user := nil ``` ``` InnSession >> isLoggedIn - ^ user isNil not ``` ``` InnSession >> user - ^ user ``` Now you need to associate the session we just created with your existing application; you can either use the configuration panel or register the new application setup programmatically. **Configuration Panel.** To access the configuration panel of your application go to [http://localhost:8080/config/](http://localhost:8080/config/). In the list select your application \(probably called \`miniinn'\) and click on its associated _configure_ link. You should get to the configuration panel which lists several things such as: the library your application uses \(see *@ref:/book/web-20@*\); and its general configuration such as its root component \(see *@cha:deployment@*\). Click on the drop-down list by _Session Class_ -- if there is only text here, press the _override_ link first . Among the choices you should find the class `InnSession`. Select it and you should get the result shown in *@fig:innSessionConfig@*. Now _Save_ your changes. % +innSessionConfig|width=70%+ ![The session of miniInn is now InnSession.](figures/innSessionConfig.png width=80&label=fig:innSessionConfig) **Configuring the application programmatically.** To change the associated session of an application, we can set the preference `#sessionClass` using the message `WASession>>preferencesAt:put:`. We can do that by redefining the _class_ `initialize` method of the application as follows. Since this method is invoked automatically only when the application is loaded, make sure that you evaluate it manually after changing it. ``` MiniInn class >> initialize + with: 'Inspect Session' +``` + + + +This displays a link that opens a Seaside inspector on the session. Click the link and explore the contents of the active session. To get an inspector within your image you can use the code `self session inspect`. In both cases you should be able to navigate through the object. + +In rare cases, it might be necessary to access the current session from outside your component tree. Think twice before doing that though: it is considered to be extremely bad coding style to depend on the session from outside your component tree. Anyway, in some cases it might come in handy. In such a case, you can use the following expressions: + +``` +WARequestContext value session +``` + + +But again you should avoid accessing the session from outside of the component tree. + + +#### Accessing the Session from the Debugger + + +In older versions of Seaside, session objects could not be inspected from the debugger as normal objects. If you tried to evaluate `self session` the debugger would answer `nil` instead of the expected session object. This is because sessions are only accessible from within your web application process, and the Smalltalk debugger lives somewhere else. In Seaside 3.0 this problem is fixed on most platforms. + +If this doesn't work for you, then you need to use a little workaround to access the session from within the debugger. Put the following expression into your code to open an inspector from within the web application and halt the application by opening a debugger: + +``` +self session inspect; halt +``` + + + +### Customizing the Session for Login + + +We will now implement an extremely simple login facility to show how to use a custom session. We will enhance the `miniInn` application we developed in Chapter and add a login facility. + +When a user interacts with a Seaside application for the first time, an instance of the application's session class is created. The class `WASession` is the default session class, but this can be changed for each application, allowing you to store key information on this class. Different parts of the system will then be able to take advantage of the information to offer different services to the user. + +We will define our own session class and use it to store user login information. We will add login functionality to our existing component. + The login functionality could also be supported by using a task and/or a specific login component. The principle is the same: you use the session to store some data that is accessible from everywhere within the current session. + +In our application we want to store whether the user is logged in. Therefore we create a subclass called `InnSession` of the class `WASession` and we will associate such a new session class to our hotel application. We add the instance variable `user` to the session to hold the identity of the user who is currently logged in. + +``` +WASession << #InnSession + slots: { #user}; + package: 'SeasideBook' +``` + + +We define some utility methods to query the user login information. + +``` +InnSession >> login: aString + user := aString +``` + + +``` +InnSession >> logout + user := nil +``` + + +``` +InnSession >> isLoggedIn + ^ user isNil not +``` + + +``` +InnSession >> user + ^ user +``` + + +Now you need to associate the session we just created with your existing application; you can either use the configuration panel or register the new application setup programmatically. + +**Configuration Panel.** To access the configuration panel of your application go to [http://localhost:8080/config/](http://localhost:8080/config/). In the list select your application \(probably called \`miniinn'\) and click on its associated _configure_ link. You should get to the configuration panel which lists several things such as: the library your application uses \(see *@ref:/book/web-20@*\); and its general configuration such as its root component \(see *@cha:deployment@*\). + +Click on the drop-down list by _Session Class_ -- if there is only text here, press the _override_ link first . Among the choices you should find the class `InnSession`. Select it and you should get the result shown in Figure *@fig:innSessionConfig@*. Now _Save_ your changes. + + +![The session of miniInn is now InnSession. %width=80&anchor=fig:innSessionConfig](figures/innSessionConfig.png ) + +**Configuring the application programmatically.** To change the associated session of an application, we can set the preference `#sessionClass` using the message `WASession>>preferencesAt:put:`. We can do that by redefining the _class_ `initialize` method of the application as follows. Since this method is invoked automatically only when the application is loaded, make sure that you evaluate it manually after changing it. + +``` +MiniInn class >> initialize | application | application := WAAdmin register: self asApplicationAt: 'miniInn'. - application preferenceAt: #sessionClass put: InnSession ``` To access the current session use the message `WAComponent>>session`. We define the methods `login` and `logout` in our component. ``` MiniInn >> login - self session login: (self request: 'Enter your name:') ``` ``` MiniInn >> logout - self session logout ``` Then we define the method `renderLogin:` which, depending on the session state, offers the possibility to either login or logout. ``` MiniInn >> renderLogin: html + application preferenceAt: #sessionClass put: InnSession +``` + + +To access the current session use the message `WAComponent>>session`. We define the methods `login` and `logout` in our component. + +``` +MiniInn >> login + self session login: (self request: 'Enter your name:') +``` + + +``` +MiniInn >> logout + self session logout +``` + + +Then we define the method `renderLogin:` which, depending on the session state, offers the possibility to either login or logout. + +``` +MiniInn >> renderLogin: html self session isLoggedIn ifTrue: [ html text: 'Logged in as: ' , self session user , ' '. @@ -22,8 +138,21 @@ ifFalse: [ html anchor callback: [ self login ]; - with: 'Login'] ``` We define a dummy method `renderSpecialPrice:` to demonstrate behavior only available for users that are logged in. ``` MiniInn >> renderSpecialPrice: html - html text: 'Dear ' , self session user, ', you can benefit from our special prices!' ``` Then we redefine the method `renderContentOn:` to present the new functionality. ``` MiniInn>>renderContentOn: html + with: 'Login'] +``` + + +We define a dummy method `renderSpecialPrice:` to demonstrate behavior only available for users that are logged in. + +``` +MiniInn >> renderSpecialPrice: html + html text: 'Dear ' , self session user, ', you can benefit from our special prices!' +``` + +Then we redefine the method `renderContentOn:` to present the new functionality. + +``` +MiniInn>>renderContentOn: html self renderLogin: html. html heading: 'Starting date'. html render: calendar1. @@ -35,13 +164,88 @@ html text: (endDate - startDate) days asString , ' days from ', startDate asString, ' to ', endDate asString, ' ' ]. self session isLoggedIn "<-- Added" - ifTrue: [ self renderSpecialPrice: html ] ``` Figures *@fig:Session1@*, *@fig:Session2@* and *@fig:Session3@* illustrate the behavior we just implemented. The user may log in using the top level link. Once logged in, extra information is available to the user. ![With Session.](figures/Session1.png width=60&label=fig:Session1) ![ With Session: Enter your name.](figures/Session2.png width=60&label=fig:Session2) ![With Session: Starting Date and Ending Date.](figures/Session3.png width=60&label=fig:Session3) ### LifeCycle of a Session It is important to understand the lifecycle of a session to know which hooks to customize. *@fig:lifetime@* depicts the lifetime of a session: 1. When the user accesses a Seaside application for the first time a new session instance is created and the root component is instantiated. Seaside sends the message `WAComponent>>initialRequest:` to the active component tree, just before triggering the rendering of the components. Specializing the method `initialRequest:` enables developers to inspect the head fields of the first request to an application, and to parse and restore state if necessary. 1. All subsequent requests are processed the same way. First, Seaside gives the components the ability to process the callbacks that have been defined during the last rendering pass. These callbacks are usually triggered by clicking a link or submitting a form. Then Seaside calls `WAComponent>>updateUrl:` of all visible components. This gives the developer the ability to modify the default URL automatically generated by Seaside. Then Seaside redirects the user to the new URL. This redirect is important, because it avoids processing the callbacks unnecessarily when the user hits the _Back_ button. Finally Seaside renders the component tree. 1. If the session is not used for an extended period of time, Seaside automatically expires it and calls the method `WASession>>unregistered`. If the user bookmarked the application, or comes back to the expired session for another reason, a new session is spawned and the lifecycle of the session starts from the beginning. ![Life cycle of a session.](figures/lifetimeofsession.png width=60&label=fig:lifetime) ### Catching the Session Expiry Notification Sessions last a certain period of time if there are no requests coming in, after which they expire. The default is 600 seconds or 10 minutes. You can change this value to any other number using the configuration interface, or programmatically using the following expression: ``` "Set the session timeout to 1200 seconds (20 minutes)" + ifTrue: [ self renderSpecialPrice: html ] +``` + + +Figures *@fig:Session1@*, *@fig:Session2@* and *@fig:Session3@* illustrate the behavior we just implemented. The user may log in using the top-level link. Once logged in, extra information is available to the user. + +![With Session.% width=60&anchor=fig:Session1](figures/Session1.png) + +![ With Session: Enter your name. % width=60&anchor=fig:Session2](figures/Session2.png) + +![With Session: Starting Date and Ending Date. % width=60&anchor=fig:Session3](figures/Session3.png) + + +### LifeCycle of a Session + + +It is important to understand the lifecycle of a session to know which hooks to customize. +*@fig:lifetime@* depicts the lifetime of a session: +1. When the user accesses a Seaside application for the first time a new session instance is created and the root component is instantiated. Seaside sends the message `WAComponent>>initialRequest:` to the active component tree, just before triggering the rendering of the components. Specializing the method `initialRequest:` enables developers to inspect the head fields of the first request to an application, and to parse and restore state if necessary. +1. All subsequent requests are processed the same way. First, Seaside gives the components the ability to process the callbacks that have been defined during the last rendering pass. These callbacks are usually triggered by clicking a link or submitting a form. Then Seaside calls `WAComponent>>updateUrl:` of all visible components. This gives the developer the ability to modify the default URL automatically generated by Seaside. Then Seaside redirects the user to the new URL. This redirect is important, because it avoids processing the callbacks unnecessarily when the user hits the _Back_ button. Finally Seaside renders the component tree. +1. If the session is not used for an extended period of time, Seaside automatically expires it and calls the method `WASession>>unregistered`. If the user bookmarked the application, or comes back to the expired session for another reason, a new session is spawned and the lifecycle of the session starts from the beginning. + + +![Life cycle of a session. % width=60&anchor=fig:lifetime](figures/lifetimeofsession.png) + + +### Catching the Session Expiry Notification + + +Sessions last a certain period of time if there are no requests coming in, after which they expire. The default is 600 seconds or 10 minutes. You can change this value to any other number using the configuration interface, or programmatically using the following expression: + +``` +"Set the session timeout to 1200 seconds (20 minutes)" anApplication cache expiryPolicy configuration - at: #cacheTimeout put: 1200 ``` Depending on the type of your application you might want to increase this number. In industrial settings 10 minutes \(600 seconds\) has shown to be quite practical: it is a good compromise between user convenience and memory usage. When a session expires Seaside sends the message `WASession>>unregistered` to `WASession`. You can override this method to clean up your session, for example if you have open files or database connections. In our small example this is not really necessary, but to illustrate the functionally we will now logout the user automatically when the session expires: ``` InnSession >> unregistered + at: #cacheTimeout put: 1200 +``` + + +Depending on the type of your application you might want to increase this number. In industrial settings 10 minutes \(600 seconds\) has shown to be quite practical: it is a good compromise between user convenience and memory usage. + +When a session expires Seaside sends the message `WASession>>unregistered` to `WASession`. You can override this method to clean up your session, for example if you have open files or database connections. In our small example this is not really necessary, but to illustrate the functionally we will now logout the user automatically when the session expires: + +``` +InnSession >> unregistered super unregistered. - user := nil ``` Note that at the time the message `unregistered` is sent, there is no way to inform the user in the web browser about the session expiry. The message `unregistered` is called asynchronously by the Seaside server thread and there is no open connection that you could use to send something to the client -- in fact the user may have already closed the browser window. We will see in the next section how to recover if the user does try to return to the session. ### Manually Expiring Sessions In some cases developers might want to expire a session manually. This is useful for example after a user has logged out, as it frees all the memory that was allocated during the session. More important it makes it impossible to use the _Back_ button to get into the previously authenticated user-account and do something malicious. A session can be marked for expiry by sending the message `WASession>>expire` to a `WASession`. Note that calling `expire` will not cause the session to disappear immediately, it is just marked as expired and not accessible from the web anymore. At a later point in time Seaside will call `unregistered` and the garbage collector eventually frees the occupied memory. Let us apply it to our hotel application: we change our MiniInn application to automatically expire the session when the user logs out. ``` InnSession >> logout + user := nil +``` + + +Note that at the time the message `unregistered` is sent, there is no way to inform the user in the web browser about the session expiry. The message `unregistered` is called asynchronously by the Seaside server thread and there is no open connection that you could use to send something to the client -- in fact the user may have already closed the browser window. We will see in the next section how to recover if the user does try to return to the session. + +### Manually Expiring Sessions + +In some cases developers might want to expire a session manually. This is useful for example after a user has logged out, as it frees all the memory that was allocated during the session. More important it makes +it impossible to use the _Back_ button to get into the previously authenticated user-account and do something malicious. + +A session can be marked for expiry by sending the message `WASession>>expire` to a `WASession`. Note that calling `expire` will not cause the session to disappear immediately, it is just marked as expired and not accessible from the web anymore. At a later point in time Seaside will call `unregistered` and the garbage collector eventually frees the occupied memory. + +Let us apply it to our hotel application: we change our MiniInn application to automatically expire the session when the user logs out. + +``` +InnSession >> logout user := nil. - self expire ``` Note that expiring a session without redirecting the user to a different location will automatically start a new session within the same application. Here we change that behavior to make it point to the Seaside web site as follows. ``` InnSession >> logout + self expire +``` + + +Note that expiring a session without redirecting the user to a different location will automatically start a new session within the same application. Here we change that behavior to make it point to the Seaside web site as follows. + +``` +InnSession >> logout user := nil. self expire. - self redirectTo: 'http://www.seaside.st' ``` If the user tries to get back to the application, he is automatically redirected to a new session. ### Summary Sessions are Seaside's central mechanism for remembering user specific interaction state. Sessions are identified using the `_s` parameter in the URL. As an application developer there is normally no need to access or change the session, because it is used internally by Seaside to manage the callbacks and to store the component tree. In certain cases it might be useful to change the behavior of the default implementation or to make information accessible from anywhere in the application. Pay attention that if components depend on the presence of a specific session class, you introduce strong coupling between the component and the session. Such sessions act as global variables and should not be overused. \ No newline at end of file + self redirectTo: 'http://www.seaside.st' +``` + + +If the user tries to get back to the application, he is automatically redirected to a new session. + +### Summary + + +Sessions are Seaside's central mechanism for remembering user specific interaction state. Sessions are identified using the `_s` parameter in the URL. As an application developer there is normally no need to access or change the session, because it is used internally by Seaside to manage the callbacks and to store the component tree. In certain cases it might be useful to change the behavior of the default implementation or to make information accessible from anywhere in the application. + +Pay attention that if components depend on the presence of a specific session class, you introduce strong coupling between the component and the session. Such sessions act as global variables and should not be overused. \ No newline at end of file diff --git a/Chapters/14-RSS/rss.md b/Chapters/14-RSS/rss.md index 37c4fa1..c8d08dc 100644 --- a/Chapters/14-RSS/rss.md +++ b/Chapters/14-RSS/rss.md @@ -1,4 +1,13 @@ -## A Really Simple Syndication @cha:rss RSS is a special XML format used to publish frequently updated content, such as blog posts, news items or podcasts. Users don't need to check their favorite web site for updates. Rather, they can subscribe to a URL and be notified about changes automatically. This is can be done using a dedicated tool called _feed reader_ or _aggregator_, but most web browsers integrate this capability as part of their core functionality. The RSS XML format is very much like XHTML, but much simpler. As standardised in the [RSS 2.0 Specification](http://cyber.law.harvard.edu/rss/rss.html), RSS essentially is composed of two parts, the _channel_ and the _news item_ specifications. While the channel describes some general properties of the news feed, the items contain the actual stories that change over time. Below we see an example of such a feed. We will see how the same feed is presented within a feed reader. ``` <?xml version="1.0" encoding="utf-8"?> +## A Really Simple Syndication +@cha:rss + + +RSS is a special XML format used to publish frequently updated content, such as blog posts, news items, or podcasts. Users don't need to check their favorite website for updates. Rather, they can subscribe to a URL and be notified about changes automatically. This is can be done using a dedicated tool called _feed reader_ or _aggregator_, but most web browsers integrate this capability as part of their core functionality. + +The RSS XML format is very much like XHTML, but much simpler. As standardized in the [RSS 2.0 Specification](http://cyber.law.harvard.edu/rss/rss.html), RSS essentially is composed of two parts, the _channel_ and the _news item_ specifications. While the channel describes some general properties of the news feed, the items contain the actual stories that change over time. Below we see an example of such a feed. We will see how the same feed is presented within a feed reader. + +``` +<?xml version="1.0" encoding="utf-8"?> <rss version="2.0"> <channel> <title>Seaside ToDo @@ -17,44 +26,186 @@ 7 September 2008 - ``` ### Creating a News Feed There is a Seaside package extension that helps us to build such feeds in a manner similar to what we used to build XHTML for component rendering. Load the packages `RSS-Core`, `RSS-Examples`, and `RSS-Tests-Core` from the Seaside repository. ``` Metacello new + +``` + + +### Creating a News Feed + + +There is a Seaside package extension that helps us to build such feeds in a manner similar to what we used to build XHTML for component rendering. + +Load the packages `RSS-Core`, `RSS-Examples`, and `RSS-Tests-Core` from the Seaside repository. + +``` +Metacello new githubUser: 'SeasideSt' project: 'Seaside' commitish: 'master' path: 'repository'; baseline: 'Seaside3'; - loads: #('RSS' 'RSS Tests' 'RSS Examples') ``` Let's create a news feed for our todo items. **Define the Feed Component.** The package defines a root class named `RRComponent` that allows you to describe both the news feed channel \(title, description, language, date of publication\) and also the news items. Therefore, the next step is to create a new subclass of `RRComponent` named `ToDoRssFeed`. This will be the entry point of our feed generator. In our example, we don't need extra instance variables. ``` RRComponent subclass: #ToDoRssFeed - instanceVariableNames: '' - classVariableNames: '' - package: 'ToDo-RSS' ``` **Register the Component as Entry Point.** Next we need to register the component at a fixed URL. The aggregator will use this URL to access the feed. We do this by adding a class side initialize method. Don't forget to evaluate the code. ``` ToDoRssFeed class >> initialize + loads: #('RSS' 'RSS Tests' 'RSS Examples') +``` + + +Let's create a news feed for our todo items. + +**Define the Feed Component.** The package defines a root class named `RRComponent` that allows you to describe both the news feed channel \(title, description, language, date of publication\) and also the news items. Therefore, the next step is to create a new subclass of `RRComponent` named `ToDoRssFeed`. This will be the entry point of our feed generator. In our example, we don't need extra instance variables. + +``` +RRComponent << #ToDoRssFeed + package: 'ToDo-RSS' +``` + + +**Register the Component as Entry Point.** Next we need to register the component at a fixed URL. The aggregator will use this URL to access the feed. We do this by adding a class side initialize method. Don't forget to evaluate the code. + +``` +ToDoRssFeed class >> initialize (WAAdmin register: RRRssHandler at: 'todo.rss') - rootComponentClass: self ``` At this point we can begin to download our feed at [http://localhost:8080/todo.rss](http://localhost:8080/todo.rss), however it is mostly empty except for some standard markup as shown by the following RSS file. !!note Your browser may be set up to handle RSS feeds automatically, so you may have difficulty in examining the raw source. ``` + rootComponentClass: self +``` + + +At this point we can begin to download our feed at [http://localhost:8080/todo.rss](http://localhost:8080/todo.rss), however it is mostly empty except for some standard markup as shown by the following RSS file. + +!!note Your browser may be set up to handle RSS feeds automatically, so you may have difficulty in examining the raw source. + +``` + - ``` ### Render the Channel Definition Next we create the contents of the feed. To do so we need to access our model and pass the data to the RSS renderer. As a first step we render the required tags of the channel element. ``` ToDoRssFeed >> model - ^ ToDoList default ``` ``` ToDoRssFeed >> renderContentOn: rss - self renderChannelOn: rss ``` ``` ToDoRssFeed >> renderChannelOn: rss + +``` + + + +### Render the Channel Definition + + +Next we create the contents of the feed. To do so we need to access our model and pass the data to the RSS renderer. As a first step we render the required tags of the channel element. + +``` +ToDoRssFeed >> model + ^ ToDoList default +``` + + +``` +ToDoRssFeed >> renderContentOn: rss + self renderChannelOn: rss +``` + + +``` +ToDoRssFeed >> renderChannelOn: rss rss title: self model title. rss link: 'http://localhost:8080/todo'. - rss description: 'There are always things left to do.' ``` A full list of all available tags is available in the following table. | RSS Tag | Selector | Description | | --- | --- | --- | | `title` | `title:` | The name of the channel \(required\). | | `link` | `link:` | The URL to website corresponding to the channel \(required\). | | `description` | `description:` | Phrase or sentence describing the channel \(required\). | | `language` | `language:` | The language the channel is written in. | | `copyright` | `copyright:` | Copyright notice for content in the channel. | | `managingEditor` | `managingEditor:` | Email address for person responsible for editorial content. | | `webMaster` | `webMaster:` | Email address for person responsible for technical issues. | | `pubDate` | `pubDate:` | The publication date for the content in the channel. | | `lastBuildDate` | `lastBuildDate:` | The last time the content of the channel changed. | | `category` | `category:` | Specify one or more categories that the channel belongs to. | | `generator` | `generator:` | A string indicating the program used to generate the channel. | ### Rendering News Items Finally, we want to render the todo items. Each news item is enclosed within a `item` tag. We will display the title and show the due date as part of the description. Also we prepend the string `(done)`, if the item has been completed. ``` ToDoRssFeed >> renderContentOn: rss + rss description: 'There are always things left to do.' +``` + + +A full list of all available tags is available in the following table. + + +| RSS Tag | Selector | Description | +| --- | --- | --- | +| `title` | `title:` | The name of the channel \(required\). | +| `link` | `link:` | The URL to website corresponding to the channel \(required\). | +| `description` | `description:` | Phrase or sentence describing the channel \(required\). | +| `language` | `language:` | The language the channel is written in. | +| `copyright` | `copyright:` | Copyright notice for content in the channel. | +| `managingEditor` | `managingEditor:` | Email address for person responsible for editorial content. | +| `webMaster` | `webMaster:` | Email address for person responsible for technical issues. | +| `pubDate` | `pubDate:` | The publication date for the content in the channel. | +| `lastBuildDate` | `lastBuildDate:` | The last time the content of the channel changed. | +| `category` | `category:` | Specify one or more categories that the channel belongs to. | +| `generator` | `generator:` | A string indicating the program used to generate the channel. | + +### Rendering News Items + + +Finally, we want to render the todo items. Each news item is enclosed within a `item` tag. We will display the title and show the due date as part of the description. Also we prepend the string `(done)`, if the item has been completed. + +``` +ToDoRssFeed >> renderContentOn: rss self renderChannelOn: rss. self model items - do: [ :each | self renderItem: each on: rss ] ``` ``` ToDoRssFeed >> renderItem: aToDoItem on: rss + do: [ :each | self renderItem: each on: rss ] +``` + + +``` +ToDoRssFeed >> renderItem: aToDoItem on: rss rss item: [ rss title: aToDoItem title. rss description: [ aToDoItem done ifTrue: [ rss text: '(done) ' ]. - rss render: aToDoItem due ] ] ``` Doing so will generate the required XML structure for the item tag. ``` + rss render: aToDoItem due ] ] +``` + + +Doing so will generate the required XML structure for the item tag. + +``` + Smalltalk (done) 5 March 2008 - ``` At the minimum, a title or a description must be present. All the other sub-elements are optional. | RSS Tag | Selector | Description | | --- | --- | --- | | `title` | `title` | The title of the item. | | `link` | `link` | The URL of the item. | | `description` | `description` | Phrase or sentence describing the channel. | | `author` | `author` | The item synopsis. | | `category` | `category` | Includes the item in one or more categories. | | `comments` | `comments` | URL of a page for comments relating to the item. | | `enclosure` | `enclosure` | Describes a media object that is attached to the item. | | `guid` | `guid` | A string that uniquely identifies the item. | | `pubDate` | `pubDate` | Indicates when the item was published. | | `source` | `source` | The RSS channel that the item came from. | ### Subscribe to the Feed Now we have done all that is required to let users subscribe. Figure *@feed-vienna@* shows how the feed is presented to the user in the feed reader when the URL was added manually. ![The ToDo Feed subscribed.](figures/feed-vienna.png width=70&label=feed-vienna) One remaining thing to do is to tell the users of our todo application where they can subscribe to the RSS feed. Of course we could simply put an anchor at the bottom our web application, however there is a more elegant solution. We override the method `WAComponent>>updateRoot:` in our Seaside component to add a link to our feed into the XHTML head. Most modern web browser will pick up this tag and show the RSS logo in the toolbar to allow people to register for the feed with one click. ``` ToDoListView >> updateRoot: aHtmlRoot + +``` + + +At the minimum, a title or a description must be present. All the other sub-elements are optional. + + +| RSS Tag | Selector | Description | +| --- | --- | --- | +| `title` | `title` | The title of the item. | +| `link` | `link` | The URL of the item. | +| `description` | `description` | Phrase or sentence describing the channel. | +| `author` | `author` | The item synopsis. | +| `category` | `category` | Includes the item in one or more categories. | +| `comments` | `comments` | URL of a page for comments relating to the item. | +| `enclosure` | `enclosure` | Describes a media object that is attached to the item. | +| `guid` | `guid` | A string that uniquely identifies the item. | +| `pubDate` | `pubDate` | Indicates when the item was published. | +| `source` | `source` | The RSS channel that the item came from. | + +### Subscribe to the Feed + + +Now we have done all that is required to let users subscribe. Figure *@feed-vienna@* shows +how the feed is presented to the user in the feed reader when the URL was added manually. + +![The ToDo Feed subscribed. % width=70&anchor=feed-vienna](figures/feed-vienna.png) + +One remaining thing to do is to tell the users of our todo application where they can subscribe to the RSS feed. Of course we could simply put an anchor at the bottom our web application, however there is a more elegant solution. We override the method `WAComponent>>updateRoot:` in our Seaside component to add a link to our feed into the XHTML head. Most modern web browser will pick up this tag and show the RSS logo in the toolbar to allow people to register for the feed with one click. + +``` +ToDoListView >> updateRoot: aHtmlRoot super updateRoot: aHtmlRoot. aHtmlRoot link beRss; title: self model title; - url: 'http://localhost:8080/todo.rss' ``` Note the use of the message `beRss` tells the web browser that the given link points to an RSS feed. In Firefox you may have to add the relationship property to make the rss logo visible. ``` updateRoot: aHtmlRoot + url: 'http://localhost:8080/todo.rss' +``` + + +Note the use of the message `beRss` tells the web browser that the given link points to an RSS feed. + +In Firefox you may have to add the relationship property to make the rss logo visible. + +``` +updateRoot: aHtmlRoot super updateRoot: aHtmlRoot. aHtmlRoot link beRss; relationship: 'alternate'; title: self model title; - url: 'http://localhost:8080/todo.rss' ``` ### Summary In Seaside you don't manipulate tags directly. The elegant generation of RSS feeds nicely shows how the canvas can be extended to produce something other than XHTML. In particular, it is important to see that Seaside is not limited to serve XHTML but can be extended to serve SVG, WAP and RSS. \ No newline at end of file + url: 'http://localhost:8080/todo.rss' +``` + + +### Summary + + +In Seaside you don't manipulate tags directly. The elegant generation of RSS feeds nicely shows how the canvas can be extended to produce something other than XHTML. In particular, it is important to see that Seaside is not limited to serve XHTML but can be extended to serve SVG, WAP and RSS. \ No newline at end of file diff --git a/Chapters/15-REST/rest.md b/Chapters/15-REST/rest.md index 7349e27..c2ffdca 100644 --- a/Chapters/15-REST/rest.md +++ b/Chapters/15-REST/rest.md @@ -1,18 +1,112 @@ -## REST @cha:rest Seaside is not built around REST services by default, to increase programmer productivity and make to development much more fun. In some cases, it might be necessary to provide a REST API to increase the usability and interoperability of a web application though. Luckily Seaside provides a _Seaside REST_ package to fill the gap and to allow one to mix both approaches. In this chapter, we show how to integrate web applications with Seaside REST services. We start with a short presentation of REST. Then we define a simple REST service for the todo application we implemented in Chapter . We finish this chapter by inspecting how HTTP requests and responses work. We want to thank Olivier Auverlot for providing us with an initial draft of this chapter in French. ### REST in a Nutshell REST \(Representational State Transfer\) refers to an architectural model for the design of web services. It was defined by Roy Fielding in his dissertation on [Architectural Styles and the Design of Network-based Software Architectures](http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm). The REST architecture is based on the following simple ideas: - REST uses URIs to refer to and to access resources. - REST is built on top of the stateless HTTP 1.1 protocol. - REST uses HTTP commands to define operations. This last point is essential in REST architecture. HTTP commands have precise semantics: - GET lists or retrieves a resource at a given URI. - PUT replaces or updates a resource at a given URI. - POST creates a resources at a given URI. - DELETE removes the resources at a given URI. Seaside takes a different approach by default: Seaside generates URIs automatically, Seaside keeps state on the server, and Seaside does not interact well with HTTP commands. While the approach of Seaside simplifies a lot of things in web development, sometimes it is necessary to play with the rules. REST is used by a large number of web products and adhering to the REST standard might increase the usability of an application. REST applications with Seaside can take two shapes: The first approach creates or extends the interoperability of an existing application by adding a REST API. Web browsers and other client applications can \(programmatically\) access the functionality and data of an application server, see *@fig:first-architecture@*. ![First architecture: adding REST to an existing application.](figures/1st-architecture.png width=70&label=fig:first-architecture) A second approach consists of using REST as the back-end of an application and make it a fundamental element of its architecture. All objects are exposed via REST services to potential clients as well as to the other parts of the application such as its Seaside user-interface, see *@fig:second-architecture@*. ![Second architecture: REST centric core.](figures/2nd-architecture.png width=70&label=fig:second-architecture) This second approach offers a low coupling and eases deployment. Load-balacing and fail-over mechanisms can easily be put in place and the application can be distributed over multiple machines. With Seaside and its Rest package you can implement both architectures. In this chapter we are going to look at the first example only, that is we will extend an existing application with a REST API. ### Getting Started with REST To get started load the package `Seaside-Rest-Core`, and if you are on Pharo `Seaside-Pharo-Rest-Core`. All packages are available from the `Seaside30Addons` repository and you can load then easily with the following Gofer script: !!todo check that and update to github and ``` Gofer new +## REST +@cha:rest + +Seaside is not built around REST services by default, to increase programmer productivity and make to development much more fun. In some cases, it might be necessary to provide a REST API to increase the usability and interoperability of a web application though. Luckily Seaside provides a _Seaside REST_ package to fill the gap and to allow one to mix both approaches. + +In this chapter, we show how to integrate web applications with Seaside REST services. We start with a short presentation of REST. Then we define a simple REST service for the todo application we implemented in Chapter . We finish this chapter by inspecting how HTTP requests and responses work. We want to thank Olivier Auverlot for providing us with an initial draft of this chapter in French. + + +### REST in a Nutshell + +REST (Representational State Transfer) refers to an architectural model for the design of web services. It was defined by Roy Fielding in his dissertation on [Architectural Styles and the Design of Network-based Software Architectures](http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm). The REST architecture is based on the following simple ideas: + +- REST uses URIs to refer to and to access resources. +- REST is built on top of the stateless HTTP 1.1 protocol. +- REST uses HTTP commands to define operations. + + +This last point is essential in REST architecture. HTTP commands have precise semantics: + +- GET lists or retrieves a resource at a given URI. +- PUT replaces or updates a resource at a given URI. +- POST creates a resources at a given URI. +- DELETE removes the resources at a given URI. + + +Seaside takes a different approach by default: Seaside generates URIs automatically, Seaside keeps state on the server, and Seaside does not interact well with HTTP commands. While the approach of Seaside simplifies a lot of things in web development, sometimes it is necessary to play with the rules. REST is used by a large number of web products and adhering to the REST standard might increase the usability of an application. + +REST applications with Seaside can take two shapes: The first approach creates or extends the interoperability of an existing application by adding a REST API. Web browsers and other client applications can (programmatically) access the functionality and data of an application server, see Figure *@fig:first-architecture@*. + + +![First architecture: adding REST to an existing application. % width=70&anchor=fig:first-architecture](figures/1st-architecture.png) + +A second approach consists of using REST as the back-end of an application and make it a fundamental element of its architecture. All objects are exposed via REST services to potential clients as well as to the other parts of the application such as its Seaside user-interface, see Figure *@fig:second-architecture@*. + + +![Second architecture: REST centric core. % width=70&anchor=fig:second-architecture](figures/2nd-architecture.png) + +This second approach offers a low coupling and eases deployment. Load-balacing and fail-over mechanisms can easily be put in place and the application can be distributed over multiple machines. + +With Seaside and its Rest package you can implement both architectures. In this chapter we are going to look at the first example only, that is we will extend an existing application with a REST API. + + +### Getting Started with REST + + +To get started load the package `Seaside-Rest-Core`, and if you are on Pharo `Seaside-Pharo-Rest-Core`. All packages are available from the `Seaside30Addons` repository and you can load then easily with the following Gofer script: + +!!todo check that and update to github and + +``` +Gofer new squeaksource: 'Seaside30Addons'; package: 'Seaside-REST-Core'; package: 'Seaside-Pharo-REST-Core'; package: 'Seaside-Tests-REST-Core'; - load. ``` Recent Seaside images already contain the REST packages preloaded. #### Defining a Handler We are going to extend the todo application from Chapter *@cha:todoApp@* with a REST API. We will first build a service that returns a textual list of todo items. Our REST handler, named `ToDoHandler`, should be declared by defining a Seaside class which inherits from `WARestfulHandler`. This way we indicate to Seaside that `ToDoHandler` is a REST handler. The todo items will be accessed through the same model as the existing todo application: `ToDoList default`. This means we do not need to specify additional state in our handler class. ``` WARestfulHandler subclass: #ToDoHandler - instanceVariableNames: '' - classVariableNames: '' - package: 'ToDo-REST' ``` !!note With Seaside-REST, we do not subclass from `WAComponent` that is reserved to the generation of stateful graphical components, but you should subclass from WARestfulHandler. Last we need to initialize our hander by defining a class-side initialization method. We register the handler at the entry point `todo-api` so that it is reachable at [http://localhost:8080/todo-api](http://localhost:8080/todo-api). Don't forget to call the method to make sure the handler is properly registered. ``` ToDoHandler class >> initialize - WAAdmin register: self at: 'todo-api' ``` #### Defining a System The idea behind Seaside-REST is that each HTTP request triggers a method of the appropriate service implementation. All service methods are annotated with specific method annotations or pragmas. It is possible to define a method that should be executed when the handler receives a GET request by adding the annotation `` to the method. As we will see in , a wide range of other annotations are supported to match other request types, content types, and the elements of the path and query arguments. To implement our todo service, we merely need to add the following method to `ToDoHandler` that returns the current todo items as a string: ``` ToDoHandler >> list + load. +``` + + +Recent Seaside images already contain the REST packages preloaded. + +#### Defining a Handler + + +We are going to extend the todo application from Chapter *@cha:todoApp@* with a REST API. We will first build a service that returns a textual list of todo items. + +Our REST handler, named `ToDoHandler`, should be declared by defining a Seaside class which inherits from `WARestfulHandler`. This way we indicate to Seaside that `ToDoHandler` is a REST handler. The todo items will be accessed through the same model as the existing todo application: `ToDoList default`. This means we do not need to specify additional state in our handler class. + +``` +WARestfulHandler << #ToDoHandler + package: 'ToDo-REST' +``` + + +!!note With Seaside-REST, we do not subclass from `WAComponent` that is reserved to the generation of stateful graphical components, but you should subclass from WARestfulHandler. + +Last we need to initialize our hander by defining a class-side initialization method. We register the handler at the entry point `todo-api` so that it is reachable at [http://localhost:8080/todo-api](http://localhost:8080/todo-api). Don't forget to call the method to make sure the handler is properly registered. + +``` +ToDoHandler class >> initialize + WAAdmin register: self at: 'todo-api' +``` + + +#### Defining a System + + +The idea behind Seaside-REST is that each HTTP request triggers a method of the appropriate service implementation. All service methods are annotated with specific method annotations or pragmas. + +It is possible to define a method that should be executed when the handler receives a GET request by adding the annotation `` to the method. As we will see in , a wide range of other annotations are supported to match other request types, content types, and the elements of the path and query arguments. + +To implement our todo service, we merely need to add the following method to `ToDoHandler` that returns the current todo items as a string: + +``` +ToDoHandler >> list ^ String streamContents: [ :stream | ToDoList default items do: [ :each | - stream nextPutAll: each title; crlf ] ] ``` The important thing here is the method annotation ``, the name of the method itself does not matter. The annotation declares that the method is associated with any GET request the service receives. Later on we will see how to define handlers for other types of requests. In a web browser enter the URL [http://localhost:8080/todo-api](http://localhost:8080/todo-api). You should get a file containing the list of existing todo items of your applications. If the file is empty verify that you have some todos on your application by trying it at [http://localhost:8080/todo](http://localhost:8080/todo). In case of problems, verify that the server is working using the Seaside Control Panel. If everything works well you should obtain a page with the list of todo items. To verify that our service works as expected we can also use _cURL_ or any other HTTP client to inspect the response: ``` $ curl -i curl -i http://localhost:8080/todo-api + stream nextPutAll: each title; crlf ] ] +``` + + +The important thing here is the method annotation ``, the name of the method itself does not matter. The annotation declares that the method is associated with any GET request the service receives. Later on we will see how to define handlers for other types of requests. + +In a web browser enter the URL [http://localhost:8080/todo-api](http://localhost:8080/todo-api). You should get a file containing the list of existing todo items of your applications. If the file is empty verify that you have some todos on your application by trying it at [http://localhost:8080/todo](http://localhost:8080/todo). In case of problems, verify that the server is working using the Seaside Control Panel. If everything works well you should obtain a page with the list of todo items. To verify that our service works as expected we can also use _cURL_ or any other HTTP client to inspect the response: + +``` +$ curl -i curl -i http://localhost:8080/todo-api HTTP/1.1 200 OK Content-Type: text/plain Content-Length: 71 @@ -21,7 +115,15 @@ Server: Zinc HTTP Components 1.0 Finish todo app chapter Annotate first chapter -Discuss cover design ``` By default Seaside tries to convert whatever the method returns into a response. In our initial example this was enough, but in many cases we want more control over how the response is built. We gain full control by asking the _request context_ to respond with a custom response. The following re-implementation of the `list` method has the same behavior as the previous one, but creates the response manually. ``` ToDoHandler >> list +Discuss cover design +``` + + + +By default Seaside tries to convert whatever the method returns into a response. In our initial example this was enough, but in many cases we want more control over how the response is built. We gain full control by asking the _request context_ to respond with a custom response. The following re-implementation of the `list` method has the same behavior as the previous one, but creates the response manually. + +``` +ToDoHandler >> list self requestContext respond: [ :response | @@ -29,19 +131,76 @@ Discuss cover design ``` By default Seaside tries to convert whatever the met response contentType: 'text/plain'. response nextPutAll: each title; - nextPutAll: String crlf ] ] ``` ### Matching Requests to Responses In the initial example we have seen how to define a service that catches all GET requests to the handler. In the following sections we will look at defining more complicated services using more elaborate patterns. In we are going to look at matching other request types, such as POST and PUT. In we are going to see how to serve different content types depending on the requested data. In we will see how to match path elements and in how to extract query parameters. #### HTTP Method Every service method must have a pragma that indicates the HTTP method on which it should be invoked. If we would like to add a service to create a todo item with a POST request, we could add the following method: ``` ToDoHandler >> create + nextPutAll: String crlf ] ] +``` + + +### Matching Requests to Responses + + +In the initial example we have seen how to define a service that catches all GET requests to the handler. In the following sections we will look at defining more complicated services using more elaborate patterns. In we are going to look at matching other request types, such as POST and PUT. In we are going to see how to serve different content types depending on the requested data. In we will see how to match path elements and in how to extract query parameters. + + +#### HTTP Method + +Every service method must have a pragma that indicates the HTTP method on which it should be invoked. + +If we would like to add a service to create a todo item with a POST request, we could add the following method: + +``` +ToDoHandler >> create ToDoList default items add: (ToDoItem new title: self requestContext request rawBody; yourself). - ^ 'OK' ``` We use the message `rawBody` to access the body of the request. The code creates a new todo item and sets its title. It then replies with a simple `OK` message. To give our new service a try we could use cURL. With the `-d` option we define the data to be posted to the service: ``` $ curl -d "Give REST a try" http://localhost:8080/todo-api -OK ``` If we list the todo items as implemented in the previous section we should see the newly created entry: ``` $ curl http://localhost:8080/todo-api + ^ 'OK' +``` + + +We use the message `rawBody` to access the body of the request. The code creates a new todo item and sets its title. It then replies with a simple `OK` message. + +To give our new service a try we could use cURL. With the `-d` option we define the data to be posted to the service: + +``` +$ curl -d "Give REST a try" http://localhost:8080/todo-api +OK +``` + + +If we list the todo items as implemented in the previous section we should see the newly created entry: + +``` +$ curl http://localhost:8080/todo-api Finish todo app chapter Annotate first chapter Discuss cover design -Give REST a try ``` Similarly Seaside supports the following request methods: | Request Method | Method Annotation | Description | | --- | --- | --- | | GET | `` | lists or retrieves a resource | | PUT | `` | replaces or updates a resource | | POST | `` | creates a resource | | DELETE | `` | removes a resource | | MOVE | `` | moves a resource | | COPY | `` | copies a resource | #### Content Type Using HTTP and Seaside-REST, we can also specify the format of the data that is requested or sent. To do that we use the `Accept` header of the HTTP request. Depending on it, the REST web service will adapt itself and provide the corresponding data type. We will take the previous example and we will modify it so that it serves the list of todo items not only as text, but also as JSON or XML. To do so define two new methods named `listJson` and `listXml`. Both methods will be a GET request, but additionally we annotate them with the mime type they produce using ``. This annotation specifies the type of the data returned by the method. A structured format like XML and JSON is friendly to other applications that would like to read the output. ``` ToDoHandler >> listJson +Give REST a try +``` + + +Similarly Seaside supports the following request methods: + + +| Request Method | Method Annotation | Description | +| --- | --- | --- | +| GET | `` | lists or retrieves a resource | +| PUT | `` | replaces or updates a resource | +| POST | `` | creates a resource | +| DELETE | `` | removes a resource | +| MOVE | `` | moves a resource | +| COPY | `` | copies a resource | + +#### Content Type + + +Using HTTP and Seaside-REST, we can also specify the format of the data that is requested or sent. To do that we use the `Accept` header of the HTTP request. Depending on it, the REST web service will adapt itself and provide the corresponding data type. + +We will take the previous example and we will modify it so that it serves the list of todo items not only as text, but also as JSON or XML. To do so define two new methods named `listJson` and `listXml`. Both methods will be a GET request, but additionally we annotate them with the mime type they produce using ``. This annotation specifies the type of the data returned by the method. A structured format like XML and JSON is friendly to other applications that would like to read the output. + +``` +ToDoHandler >> listJson @@ -51,7 +210,12 @@ Give REST a try ``` Similarly Seaside supports the following request methods: at: 'title' put: each title; at: 'done' put: each done; yourself) ] ]) - asJavascript ``` ``` ToDoHandler >> listXml + asJavascript +``` + + +``` +ToDoHandler >> listXml @@ -62,17 +226,41 @@ Give REST a try ``` Similarly Seaside supports the following request methods: ToDoList default items do: [ :each | xml tag: 'item' with: [ xml tag: 'title' with: each title. - xml tag: 'due' with: each due ] ] ] ] ``` While in the examples above we \(mis\)use the JSON and XML builders that come with our Seaside image. You might want to use any other framework or technique to build your output strings. By specifying the accept-header we can verify that our implementation serves the expected implementations: ```$ curl -H "Accept: text/json" http://localhost:8080/todo-api=true [{"title": "Finish todo app chapter", "done": false}, + xml tag: 'due' with: each due ] ] ] ] +``` + + +While in the examples above we (mis)use the JSON and XML builders that come with our Seaside image. You might want to use any other framework or technique to build your output strings. + +By specifying the accept-header we can verify that our implementation serves the expected implementations: + +```$ curl -H "Accept: text/json" http://localhost:8080/todo-api=true +[{"title": "Finish todo app chapter", "done": false}, {"title": "Annotate first chapter", "done": true}, {"title": "Discuss cover design", "done": false}, - {"title": "Give REST a try", "done": true}] ``` ``` $ curl -H "Accept: text/xml" http://localhost:8080/todo-api + {"title": "Give REST a try", "done": true}] +``` + + + +``` +$ curl -H "Accept: text/xml" http://localhost:8080/todo-api Finish todo app chapter false - ... ``` If the accept-header is missing or unknown, our old textual implementation is called. This illustrates that several methods can get a get annotation and that one is selected and executed depending on the information available in the request. We explain this point later. Similarly the client can specify the MIME type of data passed to the server using the content-type header. Such behavior only makes sense with PUT and POST requests and is specified using the `` annotation. The following example states that the data posted to the server is encoded as JSON. ``` ToDoHandler >> createJson + ... +``` + + +If the accept-header is missing or unknown, our old textual implementation is called. This illustrates that several methods can get a get annotation and that one is selected and executed depending on the information available in the request. We explain this point later. + +Similarly the client can specify the MIME type of data passed to the server using the content-type header. Such behavior only makes sense with PUT and POST requests and is specified using the `` annotation. The following example states that the data posted to the server is encoded as JSON. + +``` +ToDoHandler >> createJson @@ -83,15 +271,41 @@ Give REST a try ``` Similarly Seaside supports the following request methods: title: (json at: 'title'); done: (json at: 'done' ifAbsent: [ false ]); yourself). - ^ 'OK' ``` We can test the implementation with the following cURL query: ``` $ curl -H "Content-Type: text/json" \ + ^ 'OK' +``` + + +We can test the implementation with the following cURL query: + +``` +$ curl -H "Content-Type: text/json" \ -d '{"title": "Check out latest Seaside"}' \ http://localhost:8080/todo-api -OK ``` #### Request Path URIs are a powerful mechanism to specify hierarchical information. They allow one to specify and access to specific resources. Seaside-Rest offers a number of methods to support the manipulation of URIs. Some predefined methods are invoked by Seaside when you define them in your service. The method `list` we implemented in is executed when the URI does not contain any access path beside the one of the application. ``` ToDoHandler >> list +OK +``` + + +#### Request Path + + +URIs are a powerful mechanism to specify hierarchical information. They allow one to specify and access to specific resources. Seaside-Rest offers a number of methods to support the manipulation of URIs. Some predefined methods are invoked by Seaside when you define them in your service. + +The method `list` we implemented in is executed when the URI does not contain any access path beside the one of the application. + +``` +ToDoHandler >> list ^ String streamContents: [ :stream | ToDoList default items do: [ :each | - stream nextPutAll: each title; crlf ] ] ``` If we define services with methods that expect multiple arguments, the arguments get mapped to the unconsumed path elements. In the example below we use the first path element to identify a todo item by title, and then perform an action on it using the second path element: ``` ToDoHandler >> command: aTitleString action: anActionString + stream nextPutAll: each title; crlf ] ] +``` + + +If we define services with methods that expect multiple arguments, the arguments get mapped to the unconsumed path elements. In the example below we use the first path element to identify a todo item by title, and then perform an action on it using the second path element: + +``` +ToDoHandler >> command: aTitleString action: anActionString | item | @@ -103,34 +317,152 @@ OK ``` #### Request Path URIs are a powerful mechanism to specify hierarchic ifTrue: [ 'done' ] ifFalse: [ 'todo' ] ]. ... - ^ 'invalid command' ``` Now we can query the model like in the following examples: ``` $ curl http://localhost:8080/todo-api/Invalid/isDone + ^ 'invalid command' +``` + + +Now we can query the model like in the following examples: + +``` +$ curl http://localhost:8080/todo-api/Invalid/isDone unknown todo item $ curl http://localhost:8080/todo-api/Discuss+cover+design/isDone done $ curl http://localhost:8080/todo-api/Annotate+first+chapter/isDone -todo ``` #### Query Parameters So far we used the request type \(\), the content type \(\) and the request path \(\) to dispatch requests to methods. The last method which is also the most powerful one, is to dispatch on specific path elements and query parameters. Using the annotation `` we can define flexible masks to extract elements of an URI. The method containing method is triggered when the path matches verbatim. Variable parts in the path definition are enclosed in curly braces `{aString}` and will be assigned to method arguments. Variable repeated parts in the path definition are enclosed in stars `*anArray*` and will be assigned as an array to method arguments. The following example implements a search listing for our todo application. Note that the code is almost exactly the same as the one we had in our initial example, except that it filters for the query string: ``` ToDoHandler >> searchFor: aString +todo +``` + + +#### Query Parameters + +So far we used the request type (), the content type () and the request path () to dispatch requests to methods. The last method which is also the most powerful one, is to dispatch on specific path elements and query parameters. + +Using the annotation `` we can define flexible masks to extract elements of an URI. The method containing method is triggered when the path matches verbatim. Variable parts in the path definition are enclosed in curly braces `{aString}` and will be assigned to method arguments. Variable repeated parts in the path definition are enclosed in stars `*anArray*` and will be assigned as an array to method arguments. + +The following example implements a search listing for our todo application. Note that the code is almost exactly the same as the one we had in our initial example, except that it filters for the query string: + +``` +ToDoHandler >> searchFor: aString ^ String streamContents: [ :stream | ToDoList default items do: [ :each | (each title includesSubString: aString) - ifTrue: [ stream nextPutAll: each title; crlf ] ] ] ``` The method is executed when the client sends a GET request which starts with the path `/search` and contains the query parameter `query`. The expression `{aString}` makes sure that the method argument `aString` is bound to that request argument. Give it a try on the console. With the right query string only the todo items with the respective substring are printed: ``` $ curl http://localhost:8080/todo-api/search?query=REST -Give REST a try ``` #### Conflict Resolution Sometimes there are several methods which Seaside-REST could choose for a request, here's how it finds the "best" one: 1. Exact path matches like `/index.html` take precedence over partial `/index.{var}` or `{var}.html` or wildcard ones `{var}`. 1. Partial path matches like `/index.{var}` or `{var}.html` take precedence over wildcard ones `{var}`. 1. Partial single element matches `{var}` take precedence over multi element matches `*var*`. 1. Exact mime type matches like `text/xml` take precedence over partial `*/xml` or `xml/*`, wildcard `*/*` and missing ones. 1. Partial mime type matches like `*/xml` or `xml/*` take precedence over wildcard ones `*/*` or missing ones. 1. If the user agent supplies quality values for the Accept header, then that is taken into account as well. ### Handler and Filter So far, our REST service did not interact much with the existing Seaside todo application \(other than through the shared model\). Often it is however desired to have both -- the application and the REST services -- served from the same URL. To achieve this we have to subclass `WARestfulFilter` instead of `WARestfulHandler`. The `WARestfulFilter` simply wraps a Seaside application. That is, it handles REST requests exactly as the `WARestfulHandler`, but it can also delegate to the wrapped Seaside application. To update our existing service we rename `ToDoHandler` to `ToDoFilter` and change its superclass to `WARestfulFilter`. Now the class definition should look like: ``` WARestfulFilter subclass: #ToDoFilter - instanceVariableNames: '' - classVariableNames: '' - package: 'ToDo-REST' ``` A filter cannot be registered as a an independent entry point anymore, thus we should remove it from the dispatcher to avoid errors: ``` WAAdmin unregister: 'todo-api' ``` Instead we attach the filter the todo application itself. On the class-side of `ToDoListView` we adapt the `initialize` method to: ``` ToDoListView class >> initialize + ifTrue: [ stream nextPutAll: each title; crlf ] ] ] +``` + + +The method is executed when the client sends a GET request which starts with the path `/search` and contains the query parameter `query`. The expression `{aString}` makes sure that the method argument `aString` is bound to that request argument. + +Give it a try on the console. With the right query string only the todo items with the respective substring are printed: + +``` +$ curl http://localhost:8080/todo-api/search?query=REST +Give REST a try +``` + + +#### Conflict Resolution + + +Sometimes there are several methods which Seaside-REST could choose for a request, here's how it finds the "best" one: + +1. Exact path matches like `/index.html` take precedence over partial `/index.{var}` or `{var}.html` or wildcard ones `{var}`. +1. Partial path matches like `/index.{var}` or `{var}.html` take precedence over wildcard ones `{var}`. +1. Partial single element matches `{var}` take precedence over multi element matches `*var*`. +1. Exact mime type matches like `text/xml` take precedence over partial `*/xml` or `xml/*`, wildcard `*/*` and missing ones. +1. Partial mime type matches like `*/xml` or `xml/*` take precedence over wildcard ones `*/*` or missing ones. +1. If the user agent supplies quality values for the Accept header, then that is taken into account as well. + + + +### Handler and Filter + + +So far, our REST service did not interact much with the existing Seaside todo application (other than through the shared model). Often it is however desired to have both -- the application and the REST services -- served from the same URL. + +To achieve this we have to subclass `WARestfulFilter` instead of `WARestfulHandler`. The `WARestfulFilter` simply wraps a Seaside application. That is, it handles REST requests exactly as the `WARestfulHandler`, but it can also delegate to the wrapped Seaside application. + +To update our existing service we rename `ToDoHandler` to `ToDoFilter` and change its superclass to `WARestfulFilter`. Now the class definition should look like: + +``` +WARestfulFilter << #ToDoFilter + package: 'ToDo-REST' +``` + + +A filter cannot be registered as a an independent entry point anymore, thus we should remove it from the dispatcher to avoid errors: + +``` +WAAdmin unregister: 'todo-api' +``` + + +Instead we attach the filter the todo application itself. On the class-side of `ToDoListView` we adapt the `initialize` method to: + +``` +ToDoListView class >> initialize (WAAdmin register: self asApplicationAt: 'todo') - addFilter: ToDoFilter new ``` After evaluating the initialization code, the `ToDoFilter` is now executed whenever somebody accesses our application. The process is visualized in *@fig:request-handling@*. Whenever a request hits the filter \(1\), it processes the annotations \(2\). Eventually, if none of the annotated methods matched, it delegates to the wrapped application by invoking the method noRouteFound: \(3\). % +request-handling|width=50%+ ![Request handling of `WARestfulFilter` and `WAApplication`.](figures/REST3.png width=70&label=fig:request-handling) Unfortunately -- if you followed the creation of the REST API in the previous sections -- our `#list` service hides the application by consuming all requests to [http://localhost:8080/todo](http://localhost:8080/todo). We have two possibilities to fix the problem: 1. We remove the method `#list` so that `ToDoFilter` automatically calls `noRouteFound:` that eventually calls the application. 1. We add a new service that captures requests directed at our web application and explicitly dispatches them to the Seaside application. For example, the following code triggers the wrapped application whenever HTML is requested: ``` ToDoFilter >> app + addFilter: ToDoFilter new +``` + + +After evaluating the initialization code, the `ToDoFilter` is now executed whenever somebody accesses our application. The process is visualized in *@fig:request-handling@*. Whenever a request hits the filter (1), it processes the annotations (2). Eventually, if none of the annotated methods matched, it delegates to the wrapped application by invoking the method noRouteFound: (3). + +% +request-handling|width=50%+ +![Request handling of `WARestfulFilter` and `WAApplication` % width=70&anchor=fig:request-handling](figures/REST3.png) + +Unfortunately -- if you followed the creation of the REST API in the previous sections -- our `#list` service hides the application by consuming all requests to [http://localhost:8080/todo](http://localhost:8080/todo). We have two possibilities to fix the problem: + +1. We remove the method `#list` so that `ToDoFilter` automatically calls `noRouteFound:` that eventually calls the application. +1. We add a new service that captures requests directed at our web application and explicitly dispatches them to the Seaside application. For example, the following code triggers the wrapped application whenever HTML is requested: + + + +``` +ToDoFilter >> app - ^ self noRouteFound: self requestContext ``` This change leaves the existing API intact and lets users access our web application with their favorite web browser. This works, because browser request documents with the mime-type `text/html` by default. Of course, we can combine this technique with any other of the matching techniques we discussed in the previous chapters. ### Request and Response Accessing and exploring HTTP requests emitted by a client is an important task during the development of a REST web service. The request gives access to information about the client \(IP address, HTTP agent, ...\). To access the request we can add the expression `self requestContext request inspect` anywhere into Seaside code. This works inside a `WAComponent` as well as inside a `WARestfulHandler`. When the method is executed, you get an inspector on the current request as shown in *@fig:headerhttp_linux.png@*. % +headerhttp_linux.png|width=50%+ ![Inspecting a request.](figures/headerhttp_linux.png width=70&label=fig:headerhttp_linux.png) The following example uses the expression `headers at: 'content-type'` that returns the `Content-Type` present in the inspected HTTP client request. ``` ToDoHandler >> list + ^ self noRouteFound: self requestContext +``` + + +This change leaves the existing API intact and lets users access our web application with their favorite web browser. This works, because browser request documents with the mime-type `text/html` by default. Of course, we can combine this technique with any other of the matching techniques we discussed in the previous chapters. + +### Request and Response + + +Accessing and exploring HTTP requests emitted by a client is an important task during the development of a REST web service. The request gives access to information about the client (IP address, HTTP agent, ...). + +To access the request we can add the expression `self requestContext request inspect` anywhere into Seaside code. This works inside a `WAComponent` as well as inside a `WARestfulHandler`. + +When the method is executed, you get an inspector on the current request as shown in *@fig:headerhttp_linux.png@*. + + +![Inspecting a request. % width=70&anchor=fig:headerhttp_linux.png](figures/headerhttp_linux.png) + +The following example uses the expression `headers at: 'content-type'` that returns the `Content-Type` present in the inspected HTTP client request. + +``` +ToDoHandler >> list self requestContext request inspect. ^ String streamContents: [ :stream | - ... ``` In the case of the transmission of a form \(corresponding to the application/x-www-form-urlencoded MIME type\), we can access the submitted fields using the message `postFields`. It is also possible to customize the HTTP response. A common task is to set the HTTP response code to indicate a result to the client. For example we could implement a delete operation for our todo items as follows: ``` ToDoHandler >> delete: aString + ... +``` + + +In the case of the transmission of a form (corresponding to the application/x-www-form-urlencoded MIME type), we can access the submitted fields using the message `postFields`. + +It is also possible to customize the HTTP response. A common task is to set the HTTP response code to indicate a result to the client. + +For example we could implement a delete operation for our todo items as follows: + +``` +ToDoHandler >> delete: aString | item | @@ -142,4 +474,21 @@ Give REST a try ``` #### Conflict Resolution Sometimes there are several met ifTrue: [ response status: WARequest statusNotFound ] ifFalse: [ ToDoList default remove: item. - response status: WARequest statusOk ] ] ``` The different status codes are implemented on the class side of WARequest. They are grouped in five main families with numbers between 100 and 500. The most common one is status code `WARequest statusOk` \(200\) and `WARequest statusNotFound` \(404\). ### Advices and Conclusion This chapter shows that while Seaside provides a powerful way to build dynamic application using a stateful approach, it can also seamlessly integrate with existing stateless protocols. This chapter illustrated that an object-oriented model of an application in combination with Seaside is very powerful: You can develop flexible web interfaces as composable Seaside components, and you can easily enrich them with an API for interoperability with REST clients. Seaside provides you with the best of all worlds: the power of object-design, the flexibility and elegance of Seaside components, and the integration of traditional HTTP architectures. A piece of advice: - Do not use cookies with a REST service. Such service should respect the stateless philosophie of HTTP. Each request should be independent of others. - During the development, organize your tagged methods following the HTTP commands: \(GET, POST, PUT, DELETE, HEAD\). You can use protocols to access them faster. - A good service web should be able to produce different types of contents depending on the capabilities of the clients. Been able to produce different formats such as plain text \(text/plain\), XML \(text/xml\), or JSON \(text/json\) increases the interoperability of your web services. You should now have a better understanding of the possibilities offered by Seaside-REST and be ready to produce nice web services. \ No newline at end of file + response status: WARequest statusOk ] ] +``` + + +The different status codes are implemented on the class side of WARequest. They are grouped into five main families with numbers between 100 and 500. The most common one is status code `WARequest statusOk` (200) and `WARequest statusNotFound` (404). + +### Advices and Conclusion + + +This chapter shows that while Seaside provides a powerful way to build dynamic application using a stateful approach, it can also seamlessly integrate with existing stateless protocols. This chapter illustrated that an object-oriented model of an application in combination with Seaside is very powerful: You can develop flexible web interfaces as composable Seaside components, and you can easily enrich them with an API for interoperability with REST clients. Seaside provides you with the best of all worlds: the power of object design, the flexibility and elegance of Seaside components, and the integration of traditional HTTP architectures. + +A piece of advice: +- Do not use cookies with a REST service. Such service should respect the stateless philosophie of HTTP. Each request should be independent of others. +- During the development, organize your tagged methods following the HTTP commands: (GET, POST, PUT, DELETE, HEAD). You can use protocols to access them faster. +- A good service web should be able to produce different types of content depending on the capabilities of the clients. Being able to produce different formats such as plain text (text/plain), XML (text/xml), or JSON (text/json) increases the interoperability of your web services. + + +You should now have a better understanding of the possibilities offered by Seaside-REST and be ready to produce nice web services. diff --git a/Chapters/16-Deploy/deploy.md b/Chapters/16-Deploy/deploy.md index d85b737..7968957 100644 --- a/Chapters/16-Deploy/deploy.md +++ b/Chapters/16-Deploy/deploy.md @@ -1,6 +1,67 @@ -## Deployment @cha:deploy At some point you certainly want to go public with your web application. This means you need to find a server that is publicly reachable and that can host your Seaside application. If your application is successful, you might need to scale it to handle thousands of concurrent users. All this requires some technical knowledge. In *@ref:deployment-preparing@* we are going to have a look at some best practices before deploying an application. Next, in *@ref:deployment-apache@* we present how to setup your own server using Apache. Last but not least, in *@ref:maintaining@*, we demonstrate ways to maintain a deployed image. ### Preparing for Deployment @ref:deployment-preparing Because Pharo offers you an image-based development environment, deploying your application can be as simple as copying your development image to your server of choice. However, this approach has a number of drawbacks. We will review a number of these drawbacks and how to overcome them. #### Stripping down your image. The image you have been working in may have accumulated lots of code and tools that aren't needed for your final application; removing these will give you a smaller, cleaner image. How much you remove will depend on how many support tools you wish to include in your deployed image. @todo: we should mention the mini image Alternatively, you may find it easier to copy your application code into a pre-prepared, \`stripped-down' image. For Pharo we have had good experiences using the [Pharo Core](http://www.pharo-project.org/) or Pharo minimal images. #### Preparing Seaside. The first task in preparing Seaside for a server image is to remove all unused applications. To do this go to the _configuration_ application at [http://localhost:8080/config](http://localhost:8080/config) and click on _remove_ for all the entry points you don't need to be deployed. Especially make sure that you remove \(or password protect\) the _configuration_ application and the _code browser_ \(at [http://localhost:8080/tools/classbrowser](http://localhost:8080/tools/classbrowser)\), as these tools allow other people to access and potentially execute arbitrary code on your server. @note The best way to deploy is to build a fresh image without the development, the tests and admin tool packages loaded in the first place. That will ensure nothing dangerous is there at all. #### Disable Development Tools. If you still want the development tools loaded, then the best way is to remove `WADevelopmentConfiguration` from the shared configuration called "Application Defaults". You can do this by evaluating the code: ``` WAAdmin applicationDefaults - removeParent: WADevelopmentConfiguration instance ``` You can always add it back by evaluating: ``` WAAdmin applicationDefaults - addParent: WADevelopmentConfiguration instance ``` Alternatively you can use the configuration interface: In the configuration of any application select _Application Defaults_ from the list of the _Assigned parents_ in the _Inherited Configuration_ section and click on _Configure_. This opens an editor on the settings that are common to all registered applications. Remove `WAToolDecoration` from the list of _Root Decoration Classes_. #### Password Protection. If you want to limit access to deployed applications make sure that you password protect them. To password protect an application do the following: 1. Click on _Configure_ of the particular entry point. 1. In the section _Inherited Configuration_ click select `WAAuthConfiguration` from the drop down box and click on _Add_. This will will add the authentication settings below. 1. Set _login_ and _password_ in the _Configuration_ section below. 1. Click on _Save_. ![Configure an application for deployment.](figures/config.png width=80&label=fig:configuration) If you want to programmatically change the password of the Seaside configure application, adapt and execute the following code: ``` | application | +## Deployment +@cha:deploy + +At some point you certainly want to go public with your web application. This means you need to find a server that is publicly reachable and that can host your Seaside application. If your application is successful, you might need to scale it to handle thousands of concurrent users. All this requires some technical knowledge. + +In Section *@ref:deployment-preparing@* we are going to have a look at some best practices before deploying an application. Next, in Section *@ref:deployment-apache@* we present how to setup your own server using Apache. Last but not least, in Section *@ref:maintaining@*, we demonstrate ways to maintain a deployed image. + +### Preparing for Deployment +@ref:deployment-preparing + +Because Pharo offers you an image-based development environment, deploying your application can be as simple as copying your development image to your server of choice. However, this approach has a number of drawbacks. We will review a number of these drawbacks and how to overcome them. + +#### Stripping down your image. + + + The image you have been working in may have accumulated lots of code and tools that aren't needed for your final application; removing these will give you a smaller, cleaner image. How much you remove will depend on how many support tools you wish to include in your deployed image. + + @todo: we should mention the mini image + +Alternatively, you may find it easier to copy your application code into a pre-prepared, \`stripped-down' image. For Pharo we have had good experiences using the [Pharo Core](http://www.pharo-project.org/) or Pharo minimal images. + +#### Preparing Seaside. + + The first task in preparing Seaside for a server image is to remove all unused applications. To do this go to the _configuration_ application at [http://localhost:8080/config](http://localhost:8080/config) and click on _remove_ for all the entry points you don't need to be deployed. Especially make sure that you remove (or password protect) the _configuration_ application and the _code browser_ (at [http://localhost:8080/tools/classbrowser](http://localhost:8080/tools/classbrowser)), as these tools allow other people to access and potentially execute arbitrary code on your server. + +@note The best way to deploy is to build a fresh image without the development, the tests and admin tool packages loaded in the first place. That will ensure nothing dangerous is there at all. + +#### Disable Development Tools. + + If you still want the development tools loaded, then the best way is to remove `WADevelopmentConfiguration` from the shared configuration called "Application Defaults". You can do this by evaluating the code: + +``` +WAAdmin applicationDefaults + removeParent: WADevelopmentConfiguration instance +``` + + +You can always add it back by evaluating: + +``` +WAAdmin applicationDefaults + addParent: WADevelopmentConfiguration instance +``` + + +Alternatively you can use the configuration interface: In the configuration of any application select _Application Defaults_ from the list of the _Assigned parents_ in the _Inherited Configuration_ section and click on _Configure_. This opens an editor on the settings that are common to all registered applications. Remove `WAToolDecoration` from the list of _Root Decoration Classes_. + +#### Password Protection. + + If you want to limit access to deployed applications make sure that you password protect them. To password protect an application do the following: + +1. Click on _Configure_ of the particular entry point. +1. In the section _Inherited Configuration_ click select `WAAuthConfiguration` from the drop down box and click on _Add_. This will will add the authentication settings below. +1. Set _login_ and _password_ in the _Configuration_ section below. +1. Click on _Save_. + + +![Configure an application for deployment. % width=80&anchor=fig:configuration](figures/config.png) + + +If you want to programmatically change the password of the Seaside configure application, adapt and execute the following code: + +``` +| application | application := WADispatcher default handlerAt: 'config'. application configuration addParent: WAAuthConfiguration instance. @@ -8,20 +69,165 @@ application preferenceAt: #login put: 'admin'; preferenceAt: #passwordHash put: (GRPlatform current secureHashFor: 'seaside'). application - addFilter: WAAuthenticationFilter new. ``` Alternatively you can use the method `WAAdmin>>register:asApplicationAt:user:password:` to do all that for you when registering the application: ``` WAConfigurationTool class >> initialize - WAAdmin register: self asApplicationAt: 'config' user: 'admin' password: 'seaside' ``` Next we have a look at the configuration settings relevant for deployment. Click on _Configure_ of the application you are going to deploy. If you don't understand all the settings described here, don't worry, everything will become clearer in the course of the following sections. #### Resource Base URL. This defines the URL prefix for URLs created with `WAAnchorTag>>resourceUrl:`. This setting avoids you having to duplicate the base-path for URLs to resource files all over your application. You will find this setting useful if you host your static files on a different machine than the application itself or if you want to quickly change the resources depending on your deployment scenario. As an example, let's have a look at the following rendering code: `html image resourceUrl: 'logo-plain.png'`. If the resource base URL setting is set to [http://www.seaside.st/styles/](http://www.seaside.st/styles/), this will point to the image at [http://www.seaside.st/styles/logo-plain.png](http://www.seaside.st/styles/logo-plain.png). Note that this setting only affects URLs created with `WAImageTag>>resourceUrl:`, it does not affect the generated pages and URLs otherwise. Set it programmatically with: ``` application + addFilter: WAAuthenticationFilter new. +``` + + + +Alternatively you can use the method `WAAdmin>>register:asApplicationAt:user:password:` to do all that for you when registering the application: + +``` +WAConfigurationTool class >> initialize + WAAdmin register: self asApplicationAt: 'config' user: 'admin' password: 'seaside' +``` + + +Next we have a look at the configuration settings relevant for deployment. Click on _Configure_ of the application you are going to deploy. If you don't understand all the settings described here, don't worry, everything will become clearer in the course of the following sections. + +#### Resource Base URL. + + This defines the URL prefix for URLs created with `WAAnchorTag>>resourceUrl:`. This setting prevents you having to duplicate the base-path for URLs to resource files all over your application. You will find this setting useful if you host your static files on a different machine than the application itself or if you want to quickly change the resources depending on your deployment scenario. + +As an example, let's have a look at the following rendering code: `html image resourceUrl: 'logo-plain.png'`. If the resource base URL setting is set to [http://www.seaside.st/styles/](http://www.seaside.st/styles/), this will point to the image at [http://www.seaside.st/styles/logo-plain.png](http://www.seaside.st/styles/logo-plain.png). Note that this setting only affects URLs created with `WAImageTag>>resourceUrl:`, it does not affect the generated pages and URLs otherwise. + +Set it programmatically with: + +``` +application preferenceAt: #resourceBaseUrl - put: 'http://www.seaside.st/resources/' ``` #### Server Protocol, Hostname, Port and Base Path. Seaside creates absolute URLs by default. This is necessary to properly implement HTTP redirects. To be able to know what absolute path to generate, Seaside needs some additional information and this is what these settings are about. These settings will be useful if you are deploying Seaside behind an external front-end web-server like Apache. ![Configuration options for absolute URLs.](figures/deploy-url.png width=80&label=fig:deploy-url) Have a look at *@fig:deploy-url@* to see visually how these settings affect the URL. _Server Protocol_ lets you change between `http` and `https` \(Secure HTTP\). Note that this changes only the way the URL is generated, it does not implement HTTPS -- if you want secure HTTP you need to pass the requests through a HTTPS proxy. _Server Hostname_ and _Server Port_ define the hostname and port respectively. In most setups, you can leave these settings undefined, as Seaside is able to figure out the correct preferences itself. If you are using an older web server such as Apache 1 you have to give the appropriate values here. _Server Path_ defines the URL prefix that is used in the URLs. Again, this only affects how the URL is generated, it does not change the lookup of the application in Seaside. This setting is useful if you want to closely integrate your application into an existing web site or if you want to get rid or change the prefix `/appname` of your applications. Again, if you want to script the deployment, adapt and execute the following code: ``` application + put: 'http://www.seaside.st/resources/' +``` + + +#### Server Protocol, Hostname, Port, and Base Path. + + Seaside creates absolute URLs by default. This is necessary to properly implement HTTP redirects. To be able to know what absolute path to generate, Seaside needs some additional information and this is what these settings are about. These settings will be useful if you are deploying Seaside behind an external front-end web server like Apache. + + +![Configuration options for absolute URLs.](figures/deploy-url.png width=80&anchor=fig:deploy-url) + + +Have a look at *@fig:deploy-url@* to see visually how these settings affect the URL. _Server Protocol_ lets you change between `http` and `https` (Secure HTTP). Note that this changes only the way the URL is generated, it does not implement HTTPS -- if you want secure HTTP you need to pass the requests through a HTTPS proxy. _Server Hostname_ and _Server Port_ define the hostname and port respectively. In most setups, you can leave these settings undefined, as Seaside is able to figure out the correct preferences itself. If you are using an older web server such as Apache 1 you have to give the appropriate values here. _Server Path_ defines the URL prefix that is used in the URLs. Again, this only affects how the URL is generated, it does not change the lookup of the application in Seaside. This setting is useful if you want to closely integrate your application into an existing web site or if you want to get rid or change the prefix `/appname` of your applications. + +Again, if you want to script the deployment, adapt and execute the following code: + +``` +application preferenceAt: #serverProtocol put: 'http'; preferenceAt: #serverHostname put: 'localhost'; preferenceAt: #serverPort put: 8080; - preferenceAt: #serverPath put: '/' ``` ### Deployment with Apache @ref:deployment-apache In this section we discuss a typical server setup for Seaside using Debian Linux as operating system. Even if you are not on a Unix system, you might want to continue reading, as the basic principles are the same everywhere. Due to the deviation of different Linux and Apache distributions, the instructions given here cannot replace the documentation for your particular target system. #### Preparing the server Before getting started you need a server machine. This is a computer with a static IP address that is always connected to the Internet. It is not required that you have physical access to your machine. You might well decide to host your application on a virtual private server \(VPS\). It is important to note that you require superuser-level access to be able to run Smalltalk images. The ability to execute PHP scripts is not enough. We assume that your server already has a working Linux installation. In the following sections we use Debian Linux 5.0 \(Lenny\), however with minor adjustments you will also be able to deploy applications on other distributions. Before starting with the setup of your application, it is important to make sure that your server software is up-to-date. It is crucial that you always keep your server up to the latest version to prevent malicious attacks and well-known bugs in the software. To update your server execute the following commands from a terminal. Most commands we use in this chapter require administrative privileges, therefore we prepend them with sudo \(super user do\): ``` $ sudo apt-get update + preferenceAt: #serverPath put: '/' +``` + + + +### Deployment with Apache +@ref:deployment-apache + +In this section we discuss a typical server setup for Seaside using Debian Linux as an operating system. Even if you are not on a Unix system, you might want to continue reading, as the basic principles are the same everywhere. Due to the deviation of different Linux and Apache distributions, the instructions given here cannot replace the documentation for your particular target system. + +#### Preparing the server + + +Before getting started you need a server machine. This is a computer with a static IP address that is always connected to the Internet. It is not required that you have physical access to your machine. You might well decide to host your application on a virtual private server (VPS). It is important to note that you require superuser-level access to be able to run Smalltalk images. The ability to execute PHP scripts is not enough. + +We assume that your server already has a working Linux installation. +In the following sections, we use Debian Linux 5.0 (Lenny), however with +minor adjustments you will also be able to deploy applications on other +distributions. + +Before starting with the setup of your application, it is important to make sure that your server software is up-to-date. It is crucial that you always keep your server up to the latest version to prevent malicious attacks and well-known bugs in the software. + +To update your server execute the following commands from a terminal. Most commands we use in this chapter require administrative privileges, therefore we prepend them with sudo (super user do): + +``` +$ sudo apt-get update $ sudo apt-get upgrade Reading package lists... Done Building dependency tree... Done -0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. ``` #### Installing Apache Next we install Apache 2.2, an industry leading open-source web server. Depending on your requirements you might decide to install a different web server. Lighttpd, for example, might be better suited in a high performance environment. Some people prefer to use one of the web servers written in Smalltalk. This is a good choice during development and prototyping, as such a server is easy to setup and maintain. We strongly discourage to use such a setup for production applications due to the following reasons: - The web server is something accessible from outside the world and therefore exposed to malicious attacks. Apache is a proven industry standard used by more than 50% \(depending on the survey\) of all of today's web sites. - To listen on port 80, the standard port used by the HTTP protocol, the web server needs to run as root. Running a public service as root is a huge security issue. Dedicated web servers such as Apache drop their root privileges after startup. This allows them to listen to port 80 while not being root. Unfortunately this is not something that can be easily done from within the Smalltalk VM. - Smalltalk is relatively slow when reading files and processing large amounts of data \(the fact that everything is an object is rather a disadvantage in this case\). A web server running natively on the host platform is always faster by an order of magnitude. A standalone web server can take advantages of the underlying operating system and advise it to directly stream data from the file-system to the socket as efficiently as possible. Furthermore web servers usually provide highly efficient caching strategies. - Most of today's Smalltalk systems \(with the exception of GemStone\) are single threaded. This means that when your image is serving files, Seaside is blocked and cannot produce dynamic content at the same time. On most of today's multi-core systems you get much better performance when serving static files through Apache running in parallel to your Seaside application server. - External web servers integrate well with the rest of the world. Your web application might need to integrate into an existing site. Often a web site consists of static as well as dynamic content provided by different technologies. The seamingless integration of all these technologies is simple with Apache. Let's go and install Apache then. If you are running an older version you might want to consider upgrading, as it makes the integration with Seaside considerably simpler, although it is not strictly necessary. ``` $ sudo apt-get install apache2 ``` Ensure the server is running and make it come up automatically when the machine boots: ``` $ sudo apache2 -k restart -$ sudo update-rc.d apache2 defaults ``` #### Installing the VM !!todo update Depending on the Smalltalk dialect you are using, the installation of the VM is different. Installing Pharo on a Debian system is simple. Install Pharo by entering the following command on the terminal: ``` $ sudo apt-get install squeak-vm ``` Note that installing and running Squeak does not require you to have the _X Window System_ installed. Just tell the installer not to pull these dependencies in, when you are asked for it. Squeak remains runnable headless without a user-interface, this is what you want to do on most servers anyway. Up-to-date information on the status of the Squeak VM you find at [http://www.squeakvm.org/unix/](http://www.squeakvm.org/unix/). Now you should be able to start the VM. Typing the `squeak` command executes a helper script that allows one to install new images and sources in the current directory, and run the VM. The VM itself can be started using the `squeakvm` command. ``` $ squeakvm -help -$ squeakvm -vm-display-null imagename.image ``` You can find additional help on starting the VM and the possible command line parameters in the man pages: ``` $ man squeak ``` In the next section we are going to look at how we can run the VM as a daemon. #### Running the VM Before we hook up the Pharo side with the web server, we need a reliable way to start and keep the Smalltalk images running as daemon \(background process\). We have had positive experience using the _daemontools_, a free collection of tools for managing UNIX service written by Daniel J. Bernstein. Contrary to other tools like `inittab`, `ttys`, `init.d`, or `rc.local`, the `daemontools` are reliable and easy to use. Adding a new service means linking a directory with a script that runs your VM into a centralized place. Removing the service means removing the linked directory. Type the following command to install daemontools. Please refer to official website \([http://cr.yp.to/daemontools.html](http://cr.yp.to/daemontools.html)\) for additional information. ``` $ apt-get install daemontools-run ``` On the server create a new folder to carry all the files of your Seaside application. We usually put this folder into a subdirectory of `/srv` and name it according to our application `/srv/appname`, but this is up to you. Copy the deployment image you prepared in into that directory. Next we create a `run` script in the same directory: ``` #!/bin/bash +0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded. +``` + + +#### Installing Apache + + +Next we install Apache 2.2, an industry-leading open-source web server. Depending on your requirements you might decide to install a different web server. Lighttpd, for example, might be better suited in a high-performance environment. + +Some people prefer to use one of the web servers written in Smalltalk. This is a good choice during development and prototyping, as such a server is easy to set up and maintain. We strongly discourage to use such a setup for production applications due to the following reasons: + +- The web server is something accessible from outside the world and therefore exposed to malicious attacks. Apache is a proven industry standard used by more than 50% (depending on the survey) of all of today's web sites. +- To listen on port 80, the standard port used by the HTTP protocol, the web server needs to run as root. Running a public service as root is a huge security issue. Dedicated web servers such as Apache drop their root privileges after startup. This allows them to listen to port 80 while not being root. Unfortunately, this is not something that can be easily done from within the Smalltalk VM. +- Smalltalk is relatively slow when reading files and processing large amounts of data (the fact that everything is an object is rather a disadvantage in this case). A web server running natively on the host platform is always faster by an order of magnitude. A standalone web server can take advantage of the underlying operating system and advise it to directly stream data from the file-system to the socket as efficiently as possible. Furthermore, web servers usually provide highly efficient caching strategies. +- Most of today's Smalltalk systems (with the exception of GemStone) are single-threaded. This means that when your image is serving files, Seaside is blocked and cannot produce dynamic content at the same time. On most of today's multi-core systems, you get much better performance when serving static files through Apache running in parallel to your Seaside application server. +- External web servers integrate well with the rest of the world. Your web application might need to integrate into an existing site. Often a web site consists of static as well as dynamic content provided by different technologies. The seamless integration of all these technologies is simple with Apache. + + +Let's go and install Apache then. If you are running an older version you might want to consider upgrading, as it makes the integration with Seaside considerably simpler, although it is not strictly necessary. + +``` +$ sudo apt-get install apache2 +``` + + +Ensure the server is running and make it come up automatically when the machine boots: + +``` +$ sudo apache2 -k restart +$ sudo update-rc.d apache2 defaults +``` + + +#### Installing the VM + + +!!todo update + +Depending on the Smalltalk dialect you are using, the installation of +the VM is different. Installing Pharo on a Debian system is simple. +Install Pharo by entering the following command on the terminal: + +``` +$ sudo apt-get install squeak-vm +``` + + +Note that installing and running Squeak does not require you to have the _X Window System_ installed. Just tell the installer not to pull these dependencies in, when you are asked for it. Squeak remains runnable headless without a user-interface, this is what you want to do on most servers anyway. + +Now you should be able to start the VM. Typing the `squeak` command executes a helper script that allows one to install new images and sources in the current directory, and run the VM. The VM itself can be started using the `squeakvm` command. + +``` +$ squeakvm -help +$ squeakvm -vm-display-null imagename.image +``` + + + +You can find additional help on starting the VM and the possible command line parameters in the man pages: + +``` +$ man squeak +``` + + +In the next section we are going to look at how we can run the VM as a daemon. + +#### Running the VM + + +Before we hook up the Pharo side with the web server, we need a reliable way to start and keep the Smalltalk images running as daemon (background process). We have had positive experience using the _daemontools_, a free collection of tools for managing UNIX service written by Daniel J. Bernstein. Contrary to other tools like `inittab`, `ttys`, `init.d`, or `rc.local`, the `daemontools` are reliable and easy to use. Adding a new service means linking a directory with a script that runs your VM into a centralized place. Removing the service means removing the linked directory. + +Type the following command to install daemontools. Please refer to official website ([http://cr.yp.to/daemontools.html](http://cr.yp.to/daemontools.html)) for additional information. + +``` +$ apt-get install daemontools-run +``` + + +On the server create a new folder to carry all the files of your Seaside application. We usually put this folder into a subdirectory of `/srv` and name it according to our application `/srv/appname`, but this is up to you. Copy the deployment image you prepared in into that directory. Next we create a `run` script in the same directory: + +``` +#!/bin/bash # settings USER="www-data" @@ -32,21 +238,135 @@ IMAGE="seaside.image" # start the vm exec \ setuidgid "$USER" \ - "$VM" $VM_PARAMS "$IMAGE" ``` On lines 3 to 7 we define some generic settings. `$USER` is the user of the system that should run your Smalltalk image. If you don't set a user here, the web service will run as root, something you must avoid. Make sure you have the user specified here on your system. `www-data` is the default user for web services on Debian systems. Make sure that this is not a user with root privileges. `$VM` defines the full path to the Squeak VM. If you have a different installation or Smalltalk dialect, you again need to adapt this setting. `$VM_PARAMS` defines the parameters passed to the Squeak VM. If you use a different environment, you need to consult the documentation and adapt these parameters accordingly. The first parameter `-mmap 256m` limits the dynamic heap size of the Squeak VM to 256 MB and makes Squeak VMs run more stably. `-vm-sound-null` disables the sound plugin, something we certainly don't need on the server. `-vm-display-null` makes the VM run headless. This is crucial on our server, as we presumably don't have any windowing server installed. The last four lines of the script actually start the VM with the given parameters. Line 11 changes the user id of the Squeak VM, and line 12 actually runs the Squeak VM. To test the script mark it as executable and run it from the command line. You need to do this as superuser, otherwise you will get an error when trying to change the user id of the VM. ``` $ chmod +x ./run -$ sudo ./run ``` The VM should be running now. You can verify that by using on of the UNIX console tools. In the following example we assume that the image has a web server installed and is listening on port 8080: ``` $ curl http://localhost:8080 + "$VM" $VM_PARAMS "$IMAGE" +``` + + + +On lines 3 to 7 we define some generic settings. `$USER` is the user of the system that should run your Smalltalk image. If you don't set a user here, the web service will run as root, something you must avoid. Make sure you have the user specified here on your system. `www-data` is the default user for web services on Debian systems. Make sure that this is not a user with root privileges. `$VM` defines the full path to the Squeak VM. If you have a different installation or Smalltalk dialect, you again need to adapt this setting. `$VM_PARAMS` defines the parameters passed to the Squeak VM. If you use a different environment, you need to consult the documentation and adapt these parameters accordingly. The first parameter `-mmap 256m` limits the dynamic heap size of the Squeak VM to 256 MB and makes Squeak VMs run more stably. `-vm-sound-null` disables the sound plugin, something we certainly don't need on the server. `-vm-display-null` makes the VM run headless. This is crucial on our server, as we presumably don't have any windowing server installed. + +The last four lines of the script actually start the VM with the given parameters. Line 11 changes the user id of the Squeak VM, and line 12 actually runs the Squeak VM. + +To test the script mark it as executable and run it from the command line. You need to do this as superuser, otherwise you will get an error when trying to change the user id of the VM. + +``` +$ chmod +x ./run +$ sudo ./run +``` + + +The VM should be running now. You can verify that by using on of the UNIX console tools. In the following example we assume that the image has a web server installed and is listening on port 8080: + +``` +$ curl http://localhost:8080 Dispatcher at / -... ``` You might want to change the URL to point to your application. As long as you don't get an error message like `curl: (7) couldn't connect to host` everything is fine. You can install curl using ``` apt-get install curl ``` #### Troubleshooting the VM. You may encounter some common problems at this point. One of these problems is that the VM is unable to find or read the image, change or source files. Make sure that all these files are in the same directory and that their permissions are correctly set so that the user `www-data` can actually read them. Also ensure that the image has been saved with the web server running on the correct port. !!important Pharo displays an error message when the `.changes` or `.sources` file cannot be found or accessed. Unfortunately this message is not visible from the console, but only pops up virtually in the headless window of the VM. The modal dialog prevents the VM from starting up the server and thus the image remains unreachable. To solve the problem make sure that `.changes` and `.sources` files are in the same directory as the image-file and that all files can be read by the user `www-data`. Another possibility \(if you really do not want to distribute the files\) is to disable the missing files warning using: ``` Preferences disable: #warnIfNoSourcesFile ``` A valuable tool to further troubleshoot a running but otherwise not responding VM is `lsof`, an utility that lists opened files and sockets by process. You can install it using `apt-get install lsof`. Use a different terminal to type the following commands: ``` $ ps -A | grep squeakvm +... +``` + + + +You might want to change the URL to point to your application. As long as you don't get an error message like `curl: (7) couldn't connect to host` everything is fine. You can install curl using + +``` +apt-get install curl +``` + + +#### Troubleshooting the VM. + + You may encounter some common problems at this point. One of these problems is that the VM is unable to find or read the image, change or source files. Make sure that all these files are in the same directory and that their permissions are correctly set so that the user `www-data` can actually read them. Also, ensure that the image has been saved with the web server running on the correct port. + +!!important Pharo displays an error message when the `.changes` or `.sources` file cannot be found or accessed. Unfortunately, this message is not visible from the console, but only pops up virtually in the headless window of the VM. The modal dialog prevents the VM from starting up the server and thus the image remains unreachable. To solve the problem make sure that `.changes` and `.sources` files are in the same directory as the image-file and that all files can be read by the user `www-data`. Another possibility (if you really do not want to distribute the files) is to disable the missing files warning using: + +``` +Preferences disable: #warnIfNoSourcesFile +``` + + +A valuable tool to further troubleshoot a running but otherwise not responding VM is `lsof`, an utility that lists opened files and sockets by process. You can install it using `apt-get install lsof`. Use a different terminal to type the following commands: + +``` +$ ps -A | grep squeakvm 22315 ? 00:17:49 squeakvm $ lsof -p 22315 | grep LISTEN squeakvm 22315 www-data 7u IPv4 411140468 TCP *:webcache (LISTEN) -squeakvm 22315 www-data 8u IPv4 409571873 TCP *:5900 (LISTEN) ``` With the first line, we find out the process id of the running Squeak VM. `lsof -p 22315` lists all the open files and sockets of process `22315`. Since we are only interested in the sockets Squeak is listening on we grep for the string `LISTEN`. In this case we see that a web server is correctly listening on port 8080 \(webcache\) and that another service is listening on port 5900. In fact the latter is the RFB server, which we will discuss in . In the original terminal, press Ctrl+C to stop the VM. #### Starting the service. Go to `/service` and link the directory with your run-script in there, to let _daemontools_ automatically start your image. Go to `/etc/service` \(on other distributions this might be `/service`\) and link the directory with your run-script in there, to let _daemontools_ automatically start your image. ``` $ cd /service -$ ln -s /srv/appname . ``` You can do an `svstat` to see if the new service is running correctly: ``` $ svstat * -appname: up (pid 4165) 8 seconds ``` The output of the command tells you that the service `appname` is up and running for 8 seconds with process id 4165. From now on _daemontools_ makes sure that the image is running all the time. The image gets started automatically when the machine boots and -- should it crash -- it is immediately restarted. #### Stopping the service. To stop the image unlink the directory from `/service` and terminate the service. \[\[\[ \$ rm /etc/service/appname \$ cd /srv/appname \$ svc -t . \]\]\] Note that only unlinking the service doesn't stop it from running, it is just taking it away from the pool of services. Also note that terminating the service without first unlinking it from the service directory will cause it to restart immediately. _daemontools_ is very strict on that and will try to keep all services running all the time. As you have seen starting and stopping an image with _daemontools_ is simple and can be easily scripted with a few shell scripts. #### Configuring Apache Now we have all the pieces to run our application. The last remaining thing to do is to get Apache configured correctly. In most UNIX distributions this is done by modifying or adding configuration files to `/etc/apache2`. On older systems the configuration might also be in a directory named `/etc/httpd`. The main configuration file is situated in `apache2.conf`, or `httpd.conf` on older systems. Before we change the configuration of the server and add the Seaside web application, have a look at this file. It is usually instructive to see how the default configuration looks like and there is plenty of documentation in the configuration file itself. On most systems this main configuration file includes other configuration files that specify what modules \(plug-ins\) are loaded and what sites are served through the web server. #### Loading Modules. On Debian the directory `/etc/apache2/mods-available` contains all the available modules that could be loaded. To make them actually available from your configuration you have to link \(`ln`\) the `.load` files to `/etc/apache2/mods-enabled`. We need to do that for the proxy and rewrite modules: ``` $ a2enmod proxy +squeakvm 22315 www-data 8u IPv4 409571873 TCP *:5900 (LISTEN) +``` + + + +With the first line, we find out the process id of the running Squeak VM. `lsof -p 22315` lists all the open files and sockets of process `22315`. Since we are only interested in the sockets Squeak is listening on we grep for the string `LISTEN`. In this case we see that a web server is correctly listening on port 8080 (webcache) and that another service is listening on port 5900. In fact the latter is the RFB server, which we will discuss in . + +In the original terminal, press Ctrl+C to stop the VM. + +#### Starting the service. + +Go to `/service` and link the directory with your run-script in there, to let _daemontools_ automatically start your image. + +Go to `/etc/service` (on other distributions this might be `/service`) and link +the directory with your run-script in there, to let _daemontools_ automatically start your image. + +``` +$ cd /service +$ ln -s /srv/appname . +``` + + +You can do an `svstat` to see if the new service is running correctly: + +``` +$ svstat * +appname: up (pid 4165) 8 seconds +``` + + +The output of the command tells you that the service `appname` is up and running for 8 seconds with process id 4165. From now on _daemontools_ makes sure that the image is running all the time. The image gets started automatically when the machine boots and -- should it crash -- it is immediately restarted. + +#### Stopping the service. + + To stop the image unlink the directory from `/service` and terminate the service. + + ``` +$ rm /etc/service/appname +$ cd /srv/appname +$ svc -t . +``` + +Note that only unlinking the service doesn't stop it from running, it is just taking it away from the pool of services. Also note that terminating the service without first unlinking it from the service directory will cause it to restart immediately. _daemontools_ is very strict on that and will try to keep all services running all the time. + +As you have seen starting and stopping an image with _daemontools_ is simple and can be easily scripted with a few shell scripts. + +#### Configuring Apache + + +Now we have all the pieces to run our application. The last remaining thing to do is to get Apache configured correctly. In most UNIX distributions this is done by modifying or adding configuration files to `/etc/apache2`. On older systems the configuration might also be in a directory named `/etc/httpd`. + +The main configuration file is situated in `apache2.conf`, or `httpd.conf` on older systems. Before we change the configuration of the server and add the Seaside web application, have a look at this file. It is usually instructive to see how the default configuration looks like and there is plenty of documentation in the configuration file itself. On most systems this main configuration file includes other configuration files that specify what modules (plug-ins) are loaded and what sites are served through the web server. + +#### Loading Modules. + +On Debian the directory `/etc/apache2/mods-available` contains all the available modules that could be loaded. To make them actually available from your configuration you have to link (`ln`) the `.load` files to `/etc/apache2/mods-enabled`. We need to do that for the proxy and rewrite modules: + +``` +$ a2enmod proxy $ a2enmod proxy_http -$ a2enmod rewrite ``` If you are running an older version you might need to uncomment some lines in the main configuration file to add these modules. #### Adding a new site. Next we add a new site. The procedure here is very similar to the one of the modules, except that we have to write the configuration file ourselves. `/etc/apache2/sites-available/` contains configuration directives files of different virtual hosts that might be used with Apache 2. `/etc/apache2/sites-enabled/` contains links to the sites in `sites-enabled/` that the administrator wishes to enable. **Step 1.** In `/etc/apache2/sites-available/` create a new configuration file called `appname.conf` as follow. ``` +$ a2enmod rewrite +``` + + +If you are running an older version you might need to uncomment some lines in the main configuration file to add these modules. + +#### Adding a new site. + + Next we add a new site. The procedure here is very similar to the one of the modules, except that we have to write the configuration file ourselves. `/etc/apache2/sites-available/` contains configuration directives files of different virtual hosts that might be used with Apache 2. `/etc/apache2/sites-enabled/` contains links to the sites in `sites-enabled/` that the administrator wishes to enable. + +**Step 1.** In `/etc/apache2/sites-available/` create a new configuration file called `appname.conf` as follow. + +``` + # set server name ProxyPreserveHost On ServerName www.appname.com @@ -55,9 +375,38 @@ $ a2enmod rewrite ``` If you are running an older version you might need to u RewriteEngine On RewriteRule ^/(.*)$ http://localhost:8080/appname/$1 [proxy,last] - ``` The file defines a default virtual host. If you want to have different virtual hosts \(or domain names\) to be served from the same computer replace the `*` in the first line with your domain name. The domain name that should be used to generate absolute URLs is specified with `ServerName`. The setting `ProxyPreserveHost` enables Seaside to figure out the server name automatically, but this setting is not available for versions prior to Apache 2. In this case you have to change the Seaside preference \`hostname' for all your applications manually, see `RewriteEngine` enables the rewrite engine that is used in the line below. The last line actually does all the magic and passes on the request to Seaside. A rewrite rule always consists of 3 parts. The first part matches the URL, in this case all URLs are matched. The second part defines how the URL is transformed. In this case this is the URL that you would use locally when accessing the application. And finally, the last part in square brackets defines the actions to be taken with the transformed URL. In this case we want to proxy the request, this means it should be passed to Squeak using the new URL. We also tell Apache that this is the last rule to be used and no further processing should be done. **Step 2.** Now link the file from `/etc/apache2/sites-enabled/` and restart Apache: ``` $ cd /etc/apache2/sites-enabled + +``` + + +The file defines a default virtual host. If you want to have different virtual hosts (or domain names) to be served from the same computer replace the `*` in the first line with your domain name. The domain name that should be used to generate absolute URLs is specified with `ServerName`. The setting `ProxyPreserveHost` enables Seaside to figure out the server name automatically, but this setting is not available for versions prior to Apache 2. In this case you have to change the Seaside preference \`hostname' for all your applications manually, see + + `RewriteEngine` enables the rewrite engine that is used in the line below. The last line actually does all the magic and passes on the request to Seaside. A rewrite rule always consists of 3 parts. The first part matches the URL, in this case all URLs are matched. The second part defines how the URL is transformed. In this case this is the URL that you would use locally when accessing the application. And finally, the last part in square brackets defines the actions to be taken with the transformed URL. In this case we want to proxy the request, this means it should be passed to Squeak using the new URL. We also tell Apache that this is the last rule to be used and no further processing should be done. + +**Step 2.** Now link the file from `/etc/apache2/sites-enabled/` and restart Apache: + +``` +$ cd /etc/apache2/sites-enabled $ ln -s /etc/apache2/sites-available/appname.conf . -$ sudo apache2ctl restart ``` If the domain name `appname.com` is correctly setup and pointing to your machine, everything should be up and running now. Make sure that you set the \`server base path' in the application configuration to `/`, so that Seaside creates correct URLs. **Troubleshooting the Proxy.** On some systems the above configuration may not suffice to get Seaside working. Check the error\_log and if you get an error messages in the Apache log saying `client denied by server configuration` just remove the file `/etc/mods-enabled/proxy.conf` from the configuration. ### Serving File with Apache Most web applications consist of serving static files at one point or the other. These are all files that don't change over time, as opposed to the XHTML of the web applications. Common static files are style sheets, Javascript code, images, videos, sound or simply document files. As we have seen in Chapter such files can be easily served through the image, however the same drawbacks apply here as those listed in . The simplest way to use an external file server is to overlay a directory tree on the hard disk of the web server over the Seaside application. For example, when someone requests the file [http://www.appname.com/seaside.png](http://www.appname.com/seaside.png) the server serves the file `/srv/appname/web/seaside.png`. With Apache this can be done with a few additional statements in the configuration file: ``` +$ sudo apache2ctl restart +``` + + +If the domain name `appname.com` is correctly setup and pointing to your machine, everything should be up and running now. Make sure that you set the \`server base path' in the application configuration to `/`, so that Seaside creates correct URLs. + +**Troubleshooting the Proxy.** On some systems the above configuration may not suffice to get Seaside working. Check the error\_log and if you get an error messages in the Apache log saying `client denied by server configuration` just remove the file `/etc/mods-enabled/proxy.conf` from the configuration. + + + +### Serving File with Apache + + +Most web applications consist of serving static files at one point or the other. These are all files that don't change over time, as opposed to the XHTML of the web applications. Common static files are style sheets, Javascript code, images, videos, sound or simply document files. As we have seen in Chapter such files can be easily served through the image, however the same drawbacks apply here as those listed in . + +The simplest way to use an external file server is to overlay a directory tree on the hard disk of the web server over the Seaside application. For example, when someone requests the file [http://www.appname.com/seaside.png](http://www.appname.com/seaside.png) the server serves the file `/srv/appname/web/seaside.png`. With Apache this can be done with a few additional statements in the configuration file: + +``` + # set server name ProxyPreserveHost On @@ -75,18 +424,77 @@ $ sudo apache2ctl restart ``` If the domain name `appname.com` is correctly se RewriteCond /srv/appname/web%{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ http://localhost:8080/appname/$1 [proxy,last] - ``` The added part starts line 9 with the comment `#configure static file serving`. Line 9 and following mark a location on the local harddisc to be used as the source of files. So when someone requests the file [http://www.appname.com/seaside.png](http://www.appname.com/seaside.png) Apache will try to serve the file found at `/srv/appname/web/seaside.png`. For security reasons, the default Apache setup forbids serving any files from the local hard disk, even if the filesystem permissions allow the process to access these files. With the lines between `Directory` and `Directory` we specify that Apache can serve all files within `/srv/appname/web`. There are many more configuration options available there, so check out the Apache documentation. The next thing we have to do is add a condition in front of our rewrite rule. As you certainly remember, this rewrite rule passes \(proxies\) all incoming requests to Seaside. Now, we would only like to do this if the requested file does not exist on the file-system. To take the previous example again, if somebody requests [http://www.appname.com/seaside.png](http://www.appname.com/seaside.png) Apache should check if a file named `/srv/appname/web/seaside.png` exists. This is the meaning of the line 16 where `%{REQUEST_FILENAME}` is a variable representing the file looked up, here the variable `REQUEST_FILENAME` is bound to `seaside.png`. Furthermore, the cryptic expression `!-f` means that the following rewrite rule should conditionally be executed _if the file specified does not exist_. In our case, assuming the file `/srv/appname/web/seaside.png` exists, this means that the rewrite rule is skipped and Apache does the default request handling. Which is to serve the static files as specified with the `DocumentRoot` directive. Most other requests, assuming that there are only a few files in `/srv/appname/web`, are passed on to Seaside. A typical layout of the directory `/srv/appname/web` might look like this: | `favicon.ico` | A shortcut icon, which most graphical web browsers automatically make use of. The icon is typically displayed next to the URL and within the list of bookmarks. | | `robots.txt` | A robots exclusion standard, which most search engines request to get information on what parts of the site should be indexed. | | `resources/` | A subdirectory of resources used by the graphical designer of your application. | | `resources/css/` | All the CSS resources of your application. | | `resources/script/` | All the external Javascript files of your application. | ### Load Balancing Multiple Images There are several ways to load balance Seaside images, one common way is to use [mod\_proxy\_balancer](http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html) that comes with Apache. Compared to other solutions it is relatively easy to set up, does not modify the response and thus has no performance impact. It requires to only load one small additional package to load into Seaside. Additionally `mod_proxy_balancer` provides some advanced features like a manager application, pluggable scheduler algorithms and configurable load factors. When load balancing multiple Seaside images care must be taken that all requests that require a particular session are processed by the same image, because unless GemStone is used session do not travel between images. This has to work with and without session cookies. This is referred to as sticky sessions because a session sticks to its unique image. `mod_proxy_balancer` does this by associating each image with an route name. Seaside has to append this route to the session id. `mod_proxy_balancer` reads the route from the request and proxies to the appropriate image. This has the advantage that `mod_proxy_balancer` does not have to keep track of all the sessions and does not have to modify the response. Additionally the mapping is defined statically in the the Apache configuration and is not affected by server restarts. First we need to define our cluster of images. In this example we use two images, the first one on port 8881 with the route name "first" the second on port 8882 with route name "second". We can of course choose other ports and route names as long as they are unique: ``` + +``` + + +The added part starts line 9 with the comment `#configure static file serving`. Line 9 and following mark a location on the local harddisc to be used as the source of files. So when someone requests the file [http://www.appname.com/seaside.png](http://www.appname.com/seaside.png) Apache will try to serve the file found at `/srv/appname/web/seaside.png`. For security reasons, the default Apache setup forbids serving any files from the local hard disk, even if the filesystem permissions allow the process to access these files. With the lines between `Directory` and `Directory` we specify that Apache can serve all files within `/srv/appname/web`. There are many more configuration options available there, so check out the Apache documentation. + +The next thing we have to do is add a condition in front of our rewrite rule. As you certainly remember, this rewrite rule passes (proxies) all incoming requests to Seaside. Now, we would only like to do this if the requested file does not exist on the file-system. To take the previous example again, if somebody requests [http://www.appname.com/seaside.png](http://www.appname.com/seaside.png) Apache should check if a file named `/srv/appname/web/seaside.png` exists. +This is the meaning of the line 16 where `%{REQUEST_FILENAME}` is a variable representing the file looked up, here the variable `REQUEST_FILENAME` is bound to `seaside.png`. Furthermore, the cryptic expression `!-f` means that the following rewrite rule should conditionally be executed _if the file specified does not exist_. In our case, assuming the file `/srv/appname/web/seaside.png` exists, this means that the rewrite rule is skipped and Apache does the default request handling. Which is to serve the static files as specified with the `DocumentRoot` directive. Most other requests, assuming that there are only a few files in `/srv/appname/web`, are passed on to Seaside. + +A typical layout of the directory `/srv/appname/web` might look like this: + + +| `favicon.ico` | A shortcut icon, which most graphical web browsers automatically make use of. The icon is typically displayed next to the URL and within the list of bookmarks. | +| `robots.txt` | A robots exclusion standard, which most search engines request to get information on what parts of the site should be indexed. | +| `resources/` | A subdirectory of resources used by the graphical designer of your application. | +| `resources/css/` | All the CSS resources of your application. | +| `resources/script/` | All the external Javascript files of your application. | + + + +### Load Balancing Multiple Images + + +There are several ways to load balance Seaside images, one common way is to use [mod\_proxy\_balancer](http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html) that comes with Apache. Compared to other solutions it is relatively easy to set up, does not modify the response and thus has no performance impact. It requires to only load one small additional package to load into Seaside. Additionally `mod_proxy_balancer` provides some advanced features like a manager application, pluggable scheduler algorithms and configurable load factors. + +When load balancing multiple Seaside images care must be taken that all requests that require a particular session are processed by the same image, because unless GemStone is used session do not travel between images. This has to work with and without session cookies. This is referred to as sticky sessions because a session sticks to its unique image. `mod_proxy_balancer` does this by associating each image with an route name. Seaside has to append this route to the session id. `mod_proxy_balancer` reads the route from the request and proxies to the appropriate image. This has the advantage that `mod_proxy_balancer` does not have to keep track of all the sessions and does not have to modify the response. Additionally the mapping is defined statically in the the Apache configuration and is not affected by server restarts. + +First we need to define our cluster of images. In this example we use two images, the first one on port 8881 with the route name "first" the second on port 8882 with route name "second". We can of course choose other ports and route names as long as they are unique: + +``` + BalancerMember http://127.0.0.1:8881 route=first BalancerMember http://127.0.0.1:8882 route=second - ``` Next we need to define the actual proxy configuration, which is similar to what we do with a single Seaside image behind an Apache: ``` ProxyPass / balancer://mycluster/ stickysession=_s|_s nofailover=On + +``` + + + +Next we need to define the actual proxy configuration, which is similar to what we do with a single Seaside image behind an Apache: + +``` +ProxyPass / balancer://mycluster/ stickysession=_s|_s nofailover=On =ProxyPassReverse / http://127.0.0.1:8881/ -=ProxyPassReverse / http://127.0.0.1:8882/ ``` Note that we configure `_s` to be the session id for the URL and the cookie. Finally, we can optionally add the balancer manager application: ``` ProxyPass /balancer-manager ! +=ProxyPassReverse / http://127.0.0.1:8882/ +``` + +Note that we configure `_s` to be the session id for the URL and the cookie. + +Finally, we can optionally add the balancer manager application: + +``` +ProxyPass /balancer-manager ! SetHandler balancer-manager - ``` As well as an optional application displaying the server status: ``` ProxyPass /server-status ! + +``` + +As well as an optional application displaying the server status: + +``` +ProxyPass /server-status ! SetHandler server-status - ``` Putting all the parts together this gives an apache configuration like the following: ``` + +``` + + +Putting all the parts together this gives an apache configuration like the following: + +``` + ProxyRequests Off ProxyStatus On @@ -110,7 +518,41 @@ $ sudo apache2ctl restart ``` If the domain name `appname.com` is correctly se SetHandler server-status - ``` The rest can be configured from within Seaside. First we need to load the package `Seaside-Cluster` from [http://www.squeaksource.com/ajp/](http://www.squeaksource.com/ajp/). Then we need to configure each image individually with the correct route name. It is important that this matches the Apache configuration from above. The easiest way to do this is evaluate the expression: ``` WAAdmin makeAllClusteredWith: 'first' ``` in the image on port 8881 and ``` WAAdmin makeAllClusteredWith: 'second' ``` in the image on port 8882. % +balancer-manager.png|width=90%+ ![Apache Load Balancer Manager.](figures/balancer-manager.png width=80&label=fig:balancer-manager) This is it, the cluster is ready to go. If enabled the server manager can be found at [http://localhost/balancer-manager](http://localhost/balancer-manager), see Figure *@fig:balancer-manager@*, and the server status can be queried on [http://localhost/server-status](http://localhost/server-status). Note that before going to production these two admin applications need to be properly protected from unauthorized access. ### Using AJP AJPv13 is a binary protocol between Apache and Seaside. It was originally developed for Java Tomcat but there is nothing Java specific about it. Compared to conventional HTTP it has less overhead and better support for SSL. Starting with version Apache 2.2 the required module [mod\_proxy\_ajp](http://httpd.apache.org/docs/2.2/mod/mod_proxy_ajp.html) is included with the default setup making it much simpler to use. The configuration looks almost the same as the one we saw in Section . The only difference is that you need to replace `proxy_http` with `proxy_ajp`, and that the protocol in the URL of the rewrite rule is `ajp` instead of `http`. The adapted configuration looks like this: ``` + +``` + + +The rest can be configured from within Seaside. First we need to load the package `Seaside-Cluster` from [http://www.squeaksource.com/ajp/](http://www.squeaksource.com/ajp/). Then we need to configure each image individually with the correct route name. It is important that this matches the Apache configuration from above. The easiest way to do this is evaluate the expression: + +``` +WAAdmin makeAllClusteredWith: 'first' +``` + +in the image on port 8881 and + +``` +WAAdmin makeAllClusteredWith: 'second' +``` + +in the image on port 8882. + + +![Apache Load Balancer Manager. %width=80&anchor=fig:balancer-manager](figures/balancer-manager.png ) + +This is it, the cluster is ready to go. If enabled the server manager can be found at [http://localhost/balancer-manager](http://localhost/balancer-manager), see Figure *@fig:balancer-manager@*, and the server status can be queried on [http://localhost/server-status](http://localhost/server-status). Note that before going to production these two admin applications need to be properly protected from unauthorized access. + +### Using AJP + + +AJPv13 is a binary protocol between Apache and Seaside. It was originally developed for Java Tomcat but there is nothing Java specific about it. Compared to conventional HTTP it has less overhead and better support for SSL. + +Starting with version Apache 2.2 the required module [mod_proxy_ajp](http://httpd.apache.org/docs/2.2/mod/mod_proxy_ajp.html) +is included with the default setup making it much simpler to use. The configuration looks almost the same as the one we saw in Section . The only difference is that you need to replace `proxy_http` with `proxy_ajp`, and that the protocol in the URL of the rewrite rule is `ajp` instead of `http`. + +The adapted configuration looks like this: + +``` + # set server name ProxyPreserveHost On @@ -120,16 +562,107 @@ $ sudo apache2ctl restart ``` If the domain name `appname.com` is correctly se RewriteEngine On RewriteRule ^/(.*)$ ajp://localhost:8003/appname/$1 [proxy,last] - ``` On the Smalltalk side you need to load the packages `AJP-Core` and `AJP-Pharo-Core` directly with Monticello from [http://www.squeaksource.com/ajp](http://www.squeaksource.com/ajp). More conveniently you can also use the following Metacello script: ``` Gofer new + +``` + + +On the Smalltalk side you need to load the packages `AJP-Core` and `AJP-Pharo-Core` directly with Monticello from [http://www.squeaksource.com/ajp](http://www.squeaksource.com/ajp). More conveniently you can also use the following Metacello script: + +``` +Gofer new squeaksource: 'MetacelloRepository'; package: 'ConfigurationOfAjp'; load. (Smalltalk globals at: #ConfigurationOfAjp) - project latestVersion load: 'AJP-Core' ``` At that point you need to add and start the `AJPPharoAdaptor` on the correct port from within your image \(8003 in this example\) and your web application is up and running with AJP. ### Maintaining Deployed Images @ref:maintaining If you followed all the instructions up to now, you should have a working Seaside server based on Seaside, Apache and some other tools. Apache is handling the file requests and passing on other requests to your Seaside application server. This setup is straightforward and enough for smaller productive applications. As your web application becomes widely used, you want to regularly provide fixes and new features to your users. Also you might want to investigate and debug the deployed server. To do that, you need a way to get our hands on the running VM. There are several possibilities to do that, we are going to look at those in this section. #### Headful Pharo Instead of running the VM headless as we did previously, it is also possible to run it headful as you do during development. This is common practice on Windows servers, but it is rarely done on Unix. Normally servers doesn’t come with a windowing system for performance and security reasons. Managing a headful image is straightforward, so we will not be discussing this case further. #### VNC A common technique is to run a VNC server within your deployed image. VNC \(Virtual Network Computing\) is a graphical desktop sharing system, which allows one to visually control another computer. The server constantly sends graphical screen updates through the network using a remote frame buffer \(RFB\) protocol, and the client sends back keyboard and mouse events. VNC is platform independent and there are several open-source server and client implementations available. Pharo comes with a VNC client and server implementation, which can optionally be loaded. It is called _Remote Frame Buffer \(RFB\)_. Unfortunately the project is not officially maintained anymore and the latest code is broken in Pharo, however you can get a working version from [http://source.lukas-renggli.ch/unsorted/](http://source.lukas-renggli.ch/unsorted/). !!todo Update Install the RFB package, define a password and start the server. Now you are able to connect to the Pharo screen using any VNC client. Either using the built-in client from a different Pharo image, or more likely using any other native client. Now you are able to connect to the server image from anywhere in the world, and this even works if the image is started headless. This is very useful to be able to directly interact with server images, for example to update code or investigate and fix a problem in the running image. ### Deployment tools Seaside comes with several tools included that help you with the management of deployed applications. The tools included with the Pharo distribution of Seaside includes: | Configuration | [http://localhost:8080/config](http://localhost:8080/config) | | System Status | [http://localhost:8080/status](http://localhost:8080/status) | | Class Browser | [http://localhost:8080/tools/classbrowser](http://localhost:8080/tools/classbrowser) | | Screenshot | [http://localhost:8080/tools/screenshot](http://localhost:8080/tools/screenshot) | | Version Uploader | [http://localhost:8080/tools/versionuploader](http://localhost:8080/tools/versionuploader) | **Configuration.** The Seaside configuration interface is described throughout this book, especially in the previous sections so we are not going to discuss this further. **System Status.** The System status is a tool that provides useful information on the system. It includes information on the image \(see *@fig:system-status@*\), the virtual machine, Seaside, the garbage collector and the running processes. ![System Status Tool.](figures/system-status.png width=80&label=fig:system-status) **Class Browser.** The class browser provides access the source code of the system and allows you to edit any method or class while your application is running. **Screenshot.** The screenshot application provides a view into your image, even if it runs headless. Clicking on the screenshot even allows you to open menus and to interact with the tools within your deployed image. **Version Uploader.** The version uploader is a simple interface to Monticello. It allows you to check what code is loaded into the image and gives you the possibility to update the code on the fly. !!important If you plan to use any of these tools in a deployed image make sure that they are properly secured from unauthorized access. You don't want that any of your users accidentally stumble upon one of them and you don't want to give hackers the possibility to compromise your system. ### Request Handler Another possibility to manage your headless images from the outside is to add a request handler that allows an administrator to access and manipulate the deployed application. !!advanced The technique described here is not limited to administering images, but can also be used for public services and data access using a RESTful API. Many web applications today provide such a functionality to interact with other web and desktop application. To get started we subclass `WARequestHandler` and register it as a new entry point: ``` WARequestHandler subclass: #ManagementHandler - instanceVariableNames: '' - classVariableNames: '' - package: 'ManagementHandler' ``` ``` ManagementHandler class >> initialize - WAAdmin register: self at: 'manager' ``` The key method to override is `#handleFiltered:`. If the URL `manager` is accessed, then the registered handler receives `aRequestContext` passed into this method and has the possibility to produce a response and pass it back to the web server. The most generic way of handling this is to provide a piece of code that can be called to evaluate Pharo code within the image: ``` ManagementHandler >> handleFiltered: aRequestContext + project latestVersion load: 'AJP-Core' +``` + + +At that point you need to add and start the `AJPPharoAdaptor` on the correct port from within your image (8003 in this example) and your web application is up and running with AJP. + + +### Maintaining Deployed Images +@ref:maintaining + +If you followed all the instructions up to now, you should have a working Seaside server based on Seaside, Apache and some other tools. Apache is handling the file requests and passing on other requests to your Seaside application server. This setup is straightforward and enough for smaller productive applications. + +As your web application becomes widely used, you want to regularly provide fixes and new features to your users. Also, you might want to investigate and debug the deployed server. To do that, you need a way to get our hands on the running VM. There are several possibilities to do that, we are going to look at those in this section. + +#### Headful Pharo + + +Instead of running the VM headless as we did previously, it is also possible to run it headful as you do during development. This is common practice on Windows servers, but it is rarely done on Unix. Normally servers doesn’t come with a windowing system for performance and security reasons. Managing a headful image is straightforward, so we will not be discussing this case further. + +#### VNC + + +A common technique is to run a VNC server within your deployed image. VNC (Virtual Network Computing) is a graphical desktop sharing system, which allows one to visually control another computer. The server constantly sends graphical screen updates through the network using a remote frame buffer (RFB) protocol, and the client sends back keyboard and mouse events. VNC is platform-independent and there are several open-source server and client implementations available. + +Pharo comes with a VNC client and server implementation, which can optionally be loaded. It is called _Remote Frame Buffer (RFB)_. Unfortunately, the project is not officially maintained anymore and the latest code is broken in Pharo, however you can get a working version from [http://source.lukas-renggli.ch/unsorted/](http://source.lukas-renggli.ch/unsorted/). + +!!todo Update + +Install the RFB package, define a password and start the server. Now you are able to connect to the Pharo screen using any VNC client. Either using the built-in client from a different Pharo image, or more likely using any other native client. Now you are able to connect to the server image from anywhere in the world, and this even works if the image is started headless. This is very useful to be able to directly interact with server images, for example to update code or investigate and fix a problem in the running image. + + +### Deployment tools + + +Seaside comes with several tools that help you with the management of deployed applications. The tools included with the Pharo distribution of Seaside include: + + +| Configuration | [http://localhost:8080/config](http://localhost:8080/config) | +| System Status | [http://localhost:8080/status](http://localhost:8080/status) | +| Class Browser | [http://localhost:8080/tools/classbrowser](http://localhost:8080/tools/classbrowser) | +| Screenshot | [http://localhost:8080/tools/screenshot](http://localhost:8080/tools/screenshot) | +| Version Uploader | [http://localhost:8080/tools/versionuploader](http://localhost:8080/tools/versionuploader) | + +**Configuration.** The Seaside configuration interface is described throughout this book, especially in the previous sections so we are not going to discuss this further. + +**System Status.** The System status is a tool that provides useful information on the system. It includes information on the image (see *@fig:system-status@*), the virtual machine, Seaside, the garbage collector and the running processes. + + +![System Status Tool.](figures/system-status.png width=80&anchor=fig:system-status) + + + +**Class Browser.** The class browser provides access the source code of the system and allows you to edit any method or class while your application is running. + +**Screenshot.** The screenshot application provides a view into your image, even if it runs headless. Clicking on the screenshot even allows you to open menus and to interact with the tools within your deployed image. + +**Version Uploader.** The version uploader is a simple interface to Monticello. It allows you to check what code is loaded into the image and gives you the possibility to update the code on the fly. + +!!important If you plan to use any of these tools in a deployed image make sure that they are properly secured from unauthorized access. You don't want that any of your users accidentally stumble upon one of them and you don't want to give hackers the possibility to compromise your system. + + + +### Request Handler + + +Another possibility to manage your headless images from the outside is to add a request handler that allows an administrator to access and manipulate the deployed application. + +!!advanced The technique described here is not limited to administering images, but can also be used for public services and data access using a RESTful API. Many web applications today provide such a functionality to interact with other web and desktop application. + +To get started we subclass `WARequestHandler` and register it as a new entry point: + +``` +WARequestHandler << #ManagementHandler + package: 'ManagementHandler' +``` + + +``` +ManagementHandler class >> initialize + WAAdmin register: self at: 'manager' +``` + + +The key method to override is `#handleFiltered:`. If the URL `manager` is accessed, then the registered handler receives `aRequestContext` passed into this method and has the possibility to produce a response and pass it back to the web server. + +The most generic way of handling this is to provide a piece of code that can be called to evaluate Pharo code within the image: + +``` +ManagementHandler >> handleFiltered: aRequestContext | source result | source := aRequestContext request at: 'code' @@ -138,5 +671,25 @@ $ sudo apache2ctl restart ``` If the domain name `appname.com` is correctly se aRequestContext respond: [ :response | response contentType: WAMimeType textPlain; - nextPutAll: result asString ] ``` The first few lines of the code fetch the request parameter with the name code and store it into the temp `source`. Then we call the compiler to evaluate the code and store it into `result`. The last few lines generate a textual response and send it back to the web server. Now you can go to a web server and send commands to your image by navigating to an URL like: [http://localhost:8080/manager?code=SystemVersion current](http://localhost:8080/manager?code=SystemVersion current). This will send the Smalltalk code `SystemVersion current` to the image, evaluate it and send you back the result. Alternatively you might want to write some scripts that allow you to directly contact one or more images from the command line: ``` $ curl 'http://localhost:8080/manager?code=SystemVersion current' -Pharo1.0rc1 of 19 October 2009 update 10492 ``` If you install a request handler like the one presented here in your application make sure to properly protect it from unauthorized access, see Section . ### Summary We show how you can deploy your Seaside applications as well as maintained them over time. \ No newline at end of file + nextPutAll: result asString ] +``` + + +The first few lines of the code fetch the request parameter with the name code and store it into the temp `source`. Then we call the compiler to evaluate the code and store it into `result`. The last few lines generate a textual response and send it back to the web server. + +Now you can go to a web server and send commands to your image by navigating to an URL like: [http://localhost:8080/manager?code=SystemVersion current](http://localhost:8080/manager?code=SystemVersion current). This will send the Smalltalk code `SystemVersion current` to the image, evaluate it, and send you back the result. + +Alternatively, you might want to write some scripts that allow you to directly contact one or more images from the command line: + +``` +$ curl 'http://localhost:8080/manager?code=SystemVersion current' +Pharo1.0rc1 of 19 October 2009 update 10492 +``` + + +If you install a request handler like the one presented here in your application make sure to properly protect it from unauthorized access, see Section XXXX . + +### Summary + + +We show how you can deploy your Seaside applications as well as maintained them over time. \ No newline at end of file diff --git a/Chapters/17-JQuery/jquery.md b/Chapters/17-JQuery/jquery.md index c190f11..2226290 100644 --- a/Chapters/17-JQuery/jquery.md +++ b/Chapters/17-JQuery/jquery.md @@ -1,46 +1,535 @@ -## JQuery [jQuery](http://jquery.com) is one of the most popular open-source JavaScript frameworks today. jQuery was created by John Resig and focuses on simplifying HTML document traversing, event handling, animating, and AJAX interactions for rapid web development. There is a huge collection of [plugins](http://plugins.jquery.com/) available that extend the base framework with new functionality. One of the most popular of these plugins is [jQuery UI](http://jqueryui.com/). It provides additional abstractions over low-level interaction and animation, advanced effects and high-level themeable widgets for building highly interactive web applications. jQuery and jQuery UI are both well integrated into Seaside 3.0. This allows you to access all aspects of the library from Smalltalk by writing Smalltalk code only. The Pharo side of the integration is automatically built from the excellent [jQuery documentation - http://docs.jquery.com](http://docs.jquery.com), so you can be sure that the integration is up-to-date and feature-complete. ![jQuery Demo and Functional Test Suite.](figures/functional-tests.png width=80&label=fig:functional) ### Getting Ready Make sure to have the packages Javascript-Core, JQuery-Core and JQuery-UI-Core loaded. For examples and functional tests also load the test packages Javascript-Tests-Core, JQuery-Tests-Core and JQuery-Tests-UI. To use the libraries in your applications, you will need to load them in the Seaside web configuration application. You will notice that the core JQuery and JQueryUI libraries come in three forms which may be installed interchangeably. The `Development` versions have the full human-readable Javascript, and so are ideal for inspection and debugging during development; the `Deployment` versions are minified and gzipped to about 1/10th of the size of the development libraries, and so are much faster-loading for end users; and the `Google` versions link to copies of the libraries hosted by Google -- as many sites reference these versions, your users may already have them cached, and so these can be the fastest loading versions. | `JQDevelopmentLibrary` | JQuery | Full | | `JQDeploymentLibrary` | JQuery | Compressed | | `JQGoogleLibrary` | JQuery | Google | | `JQUiDevelopmentLibrary` | JQuery UI | Full | | `JQUiDeploymentLibrary` | JQuery UI | Compressed | | `JQUiGoogleLibrary` | JQuery UI | Google | !!advanced For many of the most popular jQuery plugins there are ready-made Smalltalk wrappers in the Project [JQueryWidgetBox](http://www.squeaksource.com/JQueryWidgetBox) on SqueakSource available. ### JQuery Basics jQuery has a simple but powerful model for its interactions. It always follows the same pattern depicted in Figure *@fig:jquery-lifecycle@*. You basically get aJQuery and configure it, navigate its elements and activate it. ![jQuery Lifecycle.](figures/jquery-lifecycle.png width=80&label=fig:jquery-lifecycle) To instantiate a `JQueryClass`, you ask a factory object for a new instance by sending the message `jQuery`. In most cases the factory object is your `WAHtmlCanvas`, but it can also be a `JSScript`. ``` html jQuery ``` While the `JQueryClass` is conceptually a Javascript class, it is implemented as a Pharo instance. `html jQuery` returns an instance of `JQueryClass`. #### Creating Queries To create a `JQueryInstance`, we specify a CSS selector that queries for certain DOM elements on the your web-page. For example, to select all HTML div tags with the CSS class `special` one would write: ``` html jQuery expression: 'div.special' ``` This expression returns a `JQueryInstance` object that represents all HTML tags matching the given CSS query `div.special`. There is also a slightly shorter form that does exactly the same: ``` html jQuery: 'div.special' ``` You find more details on creating queries in Section *@ref:creating-queries@*. #### Refining Queries If you browse the class `JQueryInstance`, you will see that you can add more elements or filter out elements before applying the jQuery action. For example, to select the siblings of the currently selected elements you would write: ``` (html jQuery: 'div.special') siblings ``` You find more details on refining queries in Section *@ref:refining-queries@*. #### Performing Actions Once you have identified the elements, you can specify the actions you wish to perform. These actions can delete, move, transform, animate or change the contents of the element. For example, to remove the elements we selected earlier we write: ``` (html jQuery: 'div.special') siblings; remove ``` There are over 180 actions provided by jQuery; these can be investigated by browsing the `JQueryInstance` Pharo class, and by visiting the jQuery documentation at [http://api.jquery.com/](http://api.jquery.com/). You find more details on performing actions in Section *@ref:performing-actions@*. ### Creating Queries @ref:creating-queries If you've already used jQuery \(or followed the link to the documentation\), you will already be familiar with the `$()` syntax for specifying CSS queries to select DOM elements. `JQueryClass>>expression:` exposes this same interface, but there are also a number of shortcut forms available to you. All the constructor methods return an instance of `JQueryInstance`. #### \$\("div.hint"\) Normally a jQuery instance is setup with a CSS selector. You can either use the long form \(1\) or take the shortcut \(2\). Of course, both forms are absolutely equivalent, in practice you will mostly encounter the shorter second form: ``` html jQuery expression: 'div.hint'. "(1)" -html jQuery: 'div.hint'. "(2)" ``` #### \$\("#foo"\) Often you want to create a query with an element ID. Again we have different possibilities to instantiate that query. \(1\) and \(3\) use a normal CSS selector for element IDs. \(2\) uses the `id:` selector, and \(4\) uses a shortcut using a symbol. Note that the forth form only works for symbols, if you pass a string it will be interpreted as a CSS selector. ``` html jQuery expression: '#foo'. "(1)" +## JQuery + + +[jQuery](http://jquery.com) is one of the most popular open-source JavaScript frameworks today. jQuery was created by John Resig and focuses on simplifying HTML document traversing, event handling, animating, and AJAX interactions for rapid web development. + +There is a huge collection of [plugins](http://plugins.jquery.com/) available that extend the base framework with new functionality. One of the most popular of these plugins is [jQuery UI](http://jqueryui.com/). It provides additional abstractions over low-level interaction and animation, advanced effects and high-level themeable widgets for building highly interactive web applications. + +jQuery and jQuery UI are both well integrated into Seaside 3.0. This allows you to access all aspects of the library from Smalltalk by writing Smalltalk code only. The Pharo side of the integration is automatically built from the excellent [jQuery documentation - http://docs.jquery.com](http://docs.jquery.com), so you can be sure that the integration is up-to-date and feature-complete. + +![jQuery Demo and Functional Test Suite. % width=80&anchor=fig:functional](figures/functional-tests.png) + +### Getting Ready + + +Make sure to have the packages Javascript-Core, JQuery-Core and JQuery-UI-Core loaded. For examples and functional tests also load the test packages Javascript-Tests-Core, JQuery-Tests-Core and JQuery-Tests-UI. + +To use the libraries in your applications, you will need to load them in the Seaside web configuration application. You will notice that the core JQuery and JQueryUI libraries come in three forms which may be installed interchangeably. The `Development` versions have the full human-readable Javascript, and so are ideal for inspection and debugging during development; the `Deployment` versions are minified and gzipped to about 1/10th of the size of the development libraries, and so are much faster-loading for end users; and the `Google` versions link to copies of the libraries hosted by Google -- as many sites reference these versions, your users may already have them cached, and so these can be the fastest loading versions. + + +| `JQDevelopmentLibrary` | JQuery | Full | +| `JQDeploymentLibrary` | JQuery | Compressed | +| `JQGoogleLibrary` | JQuery | Google | +| `JQUiDevelopmentLibrary` | JQuery UI | Full | +| `JQUiDeploymentLibrary` | JQuery UI | Compressed | +| `JQUiGoogleLibrary` | JQuery UI | Google | + +!!advanced For many of the most popular jQuery plugins there are ready-made Smalltalk wrappers in the Project [JQueryWidgetBox](http://www.squeaksource.com/JQueryWidgetBox) on SqueakSource available. + +### JQuery Basics + + +jQuery has a simple but powerful model for its interactions. It always follows the same pattern depicted in Figure *@fig:jquery-lifecycle@*. You basically get aJQuery and configure it, navigate its elements and activate it. + +![jQuery Lifecycle. %width=80&anchor=fig:jquery-lifecycle](figures/jquery-lifecycle.png ) + +To instantiate a `JQueryClass`, you ask a factory object for a new instance by sending the message `jQuery`. In most cases the factory object is your `WAHtmlCanvas`, but it can also be a `JSScript`. + +``` +html jQuery +``` + + +While the `JQueryClass` is conceptually a Javascript class, it is implemented as a Pharo instance. `html jQuery` returns an instance of `JQueryClass`. + +#### Creating Queries + +To create a `JQueryInstance`, we specify a CSS selector that queries for certain DOM elements on the your web-page. For example, to select all HTML div tags with the CSS class `special` one would write: + +``` +html jQuery expression: 'div.special' +``` + + +This expression returns a `JQueryInstance` object that represents all HTML tags matching the given CSS query `div.special`. There is also a slightly shorter form that does exactly the same: + +``` +html jQuery: 'div.special' +``` + + +You find more details on creating queries in Section *@ref:creating-queries@*. + +#### Refining Queries + + +If you browse the class `JQueryInstance`, you will see that you can add more elements or filter out elements before applying the jQuery action. For example, to select the siblings of the currently selected elements you would write: + +``` +(html jQuery: 'div.special') siblings +``` + + +You find more details on refining queries in Section *@ref:refining-queries@*. + +#### Performing Actions + + +Once you have identified the elements, you can specify the actions you wish to perform. These actions can delete, move, transform, animate or change the contents of the element. For example, to remove the elements we selected earlier we write: + +``` +(html jQuery: 'div.special') siblings; remove +``` + + +There are over 180 actions provided by jQuery; these can be investigated by browsing the `JQueryInstance` Pharo class, and by visiting the jQuery documentation at [http://api.jquery.com/](http://api.jquery.com/). + +You find more details on performing actions in Section *@ref:performing-actions@*. + +### Creating Queries + +@ref:creating-queries + +If you've already used jQuery \(or followed the link to the documentation\), you will already be familiar with the `$()` syntax for specifying CSS queries to select DOM elements. `JQueryClass>>expression:` exposes this same interface, but there are also a number of shortcut forms available to you. All the constructor methods return an instance of `JQueryInstance`. + +#### \$\("div.hint"\) + +Normally a jQuery instance is setup with a CSS selector. You can either use the long form \(1\) or take the shortcut \(2\). Of course, both forms are absolutely equivalent, in practice you will mostly encounter the shorter second form: + +``` +html jQuery expression: 'div.hint'. "(1)" +html jQuery: 'div.hint'. "(2)" +``` + + +#### \$\("#foo"\) + + +Often you want to create a query with an element ID. Again we have different possibilities to instantiate that query. \(1\) and \(3\) use a normal CSS selector for element IDs. \(2\) uses the `id:` selector, and \(4\) uses a shortcut using a symbol. Note that the forth form only works for symbols, if you pass a string it will be interpreted as a CSS selector. + +``` +html jQuery expression: '#foo'. "(1)" html jQuery id: 'foo'. "(2)" html jQuery: '#foo'. "(3)" -html jQuery: #foo. "(4)" ``` #### \$\("*"\) The CSS selector to match all elements in the page is `*`. Again you have several equivalent possibilities to achieve the same in jQuery. The first two use a CSS selector, while the last one uses a convenience method: ``` html jQuery expression: '*'. +html jQuery: #foo. "(4)" +``` + + +#### \$\("*"\) + + +The CSS selector to match all elements in the page is `*`. Again you have several equivalent possibilities to achieve the same in jQuery. The first two use a CSS selector, while the last one uses a convenience method: + +``` +html jQuery expression: '*'. html jQuery: '*'. -html jQuery all. ``` #### \$\(this\) If you want to refer to the currently active DOM element from an event handler you can use `new` or `this`. ``` html jQuery this. -html jQuery new. ``` Note that the `new` you call here is not the one implemented in the Smalltalk class `Behavior`, but a custom one implemented on the instance side of `JQueryClass`. Similar to all other constructor methods it returns an instance of `JQueryInstance`. #### \$\("
"\) Furthermore, jQuery provides the possibility to create new HTML code on the fly, that inserted into an existing element. Again we have different equivalent possibilities to do this. The first one uses a raw HTML string, with Seaside we want to avoid this in most cases. The second and third variation uses a block with a new renderer that we can use with the normal Seaside rendering API. ``` html jQuery expression: '
'. +html jQuery all. +``` + + +#### \$\(this\) + + +If you want to refer to the currently active DOM element from an event handler you can use `new` or `this`. + +``` +html jQuery this. +html jQuery new. +``` + + +Note that the `new` you call here is not the one implemented in the Smalltalk class `Behavior`, but a custom one implemented on the instance side of `JQueryClass`. Similar to all other constructor methods it returns an instance of `JQueryInstance`. + +#### \$\("
"\) + + +Furthermore, jQuery provides the possibility to create new HTML code on the fly, that inserted into an existing element. Again we have different equivalent possibilities to do this. The first one uses a raw HTML string, with Seaside we want to avoid this in most cases. The second and third variation uses a block with a new renderer that we can use with the normal Seaside rendering API. + +``` +html jQuery expression: '
'. html jQuery html: [ :r | r div ]. -html jQuery: [ :r | r div ]. ``` #### \$\(function\(\) { alert\('Hello'\); }\) Last but not least there is the case of the `$()` syntax allows you to specify some action that should happen once the page is ready. This is done by attaching ``` html jQuery ready: (html javascript alert: 'Hello'). -html jQuery: (html javascript alert: 'Hello'). ``` ### Refining Queries @ref:refining-queries After you made an initial query you can refine the result with additional operations. All existing operations are described in this section: #### Siblings Get a set of elements containing all of the unique siblings of each of the matched set of elements. ``` aQuery siblings. -aQuery siblings: 'div'. ``` #### Next Siblings Get a set of elements containing the unique next siblings of each of the given set of elements. ``` aQuery next. -aQuery next: 'div'. ``` Or, find all sibling elements after the current element. ``` aQuery nextAll. -aQuery nextAll: 'div'. ``` Or, find all following siblings of each element up to but not including the element matched by the selector. ``` aQuery nextUntil: 'div'. ``` #### Previous Siblings Get a set of elements containing the unique previous siblings of each of the matched set of elements. ``` aQuery previous. -aQuery previous: 'div'. ``` Or, find all sibling elements in front of the current element. ``` aQuery previousAll. -aQuery previousAll: 'div'. ``` Or, find all previous siblings of each element up to but not including the element matched by the selector. ``` aQuery previousUntil: 'div'. ``` #### Children Get a set of elements containing all of the unique immediate children of each of the matched set of elements. ``` aQuery children. -aQuery children: 'div'. ``` Find all the child nodes inside the matched elements \(including text nodes\), or the content document, if the element is an iframe. ``` aQuery contents. ``` Searches for all elements that match the specified expression. ``` aQuery find: 'div'. ``` #### Parents Get a set of elements containing the unique parents of the matched set of elements. ``` aQuery parent. -aQuery parent: 'div'. ``` Or, find all following siblings of each element up to but not including the element matched by the selector. ``` aQuery parents. -aQuery parents: 'div'. ``` Or, find all the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector. ``` aQuery parentsUntil: 'div'. ``` Get a set of elements containing the closest parent element that matches the specified selector, the starting element included. ``` aQuery closest. -aQuery closest: 'div'. ``` ### Performing Actions @ref:performing-actions There is a wide variety of actions that come supported with jQuery. jQuery UI and thousands of other plugins add even more. In this section we present some of the most common actions provided by the core framework. #### Classes The following examples add, remove or toggle the CSS class `important` given as the first argument. These methods are commonly used to change the appearance of one or more HTML elements for example to visualize a state change in the application. ``` aQuery addClass: 'important'. +html jQuery: [ :r | r div ]. +``` + + +#### \$\(function\(\) { alert\('Hello'\); }\) + + +Last but not least there is the case of the `$()` syntax allows you to specify some action that should happen once the page is ready. This is done by attaching + +``` +html jQuery ready: (html javascript alert: 'Hello'). +html jQuery: (html javascript alert: 'Hello'). +``` + + + + + + + + + + + + +### Refining Queries +@ref:refining-queries + +After you made an initial query you can refine the result with additional operations. All existing operations are described in this section: + +#### Siblings + +Get a set of elements containing all of the unique siblings of each of the matched set of elements. + +``` +aQuery siblings. +aQuery siblings: 'div'. +``` + + +#### Next Siblings + +Get a set of elements containing the unique next siblings of each of the given set of elements. + +``` +aQuery next. +aQuery next: 'div'. +``` + + +Or, find all sibling elements after the current element. + +``` +aQuery nextAll. +aQuery nextAll: 'div'. +``` + + +Or, find all following siblings of each element up to but not including the element matched by the selector. + +``` +aQuery nextUntil: 'div'. +``` + + +#### Previous Siblings + + +Get a set of elements containing the unique previous siblings of each of the matched set of elements. + +``` +aQuery previous. +aQuery previous: 'div'. +``` + + +Or, find all sibling elements in front of the current element. + +``` +aQuery previousAll. +aQuery previousAll: 'div'. +``` + + +Or, find all previous siblings of each element up to but not including the element matched by the selector. + +``` +aQuery previousUntil: 'div'. +``` + + +#### Children + + +Get a set of elements containing all of the unique immediate children of each of the matched set of elements. + +``` +aQuery children. +aQuery children: 'div'. +``` + + +Find all the child nodes inside the matched elements \(including text nodes\), or the content document, if the element is an iframe. + +``` +aQuery contents. +``` + + +Searches for all elements that match the specified expression. + +``` +aQuery find: 'div'. +``` + + +#### Parents + + +Get a set of elements containing the unique parents of the matched set of elements. + +``` +aQuery parent. +aQuery parent: 'div'. +``` + + +Or, find all following siblings of each element up to but not including the element matched by the selector. + +``` +aQuery parents. +aQuery parents: 'div'. +``` + + +Or, find all the ancestors of each element in the current set of matched elements, up to but not including the element matched by the selector. + +``` +aQuery parentsUntil: 'div'. +``` + + +Get a set of elements containing the closest parent element that matches the specified selector, the starting element included. + +``` +aQuery closest. +aQuery closest: 'div'. +``` + + +### Performing Actions +@ref:performing-actions + +There is a wide variety of actions that come supported with jQuery. jQuery UI and thousands of other plugins add even more. In this section, we present some of the most common actions provided by the core framework. + +#### Classes + + +The following examples add, remove or toggle the CSS class `important` given as the first argument. These methods are commonly used to change the appearance of one or more HTML elements for example to visualize a state change in the application. + +``` +aQuery addClass: 'important'. aQuery removeClass: 'important'. -aQuery toggleClass: 'important'. ``` Also you can query if a particular class is set: ``` aQuery hasClass: 'important'. ``` #### Styles Similarly you can change the style of one or more HTML elements. By providing a dictionary you can change multiple CSS styles at once: ``` aQuery css: aDictionary. ``` Alternatively you can use a dictionary-like protocol to read and write specific style properties: ``` aQuery cssAt: 'color'. -aQuery cssAt: 'color' put: '#ff0'. ``` Note that in most cases it is preferred to use CSS classes instead of hardcoding your style settings into the application code. #### Attributes While the above methods change the `class` and `style` attribute of one or more DOM elements, there are also accessor methods to change arbitrary HTML attributes. By providing a dictionary of key-value pairs you can change multiple attributes at once: ``` aQuery attributes: aDictionary. ``` Alternatively you can use a dictionary-like protocol to read and write attributes: ``` aQuery attributeAt: 'href'. -aQuery attributeAt: 'href' put: 'http://www.seaside.st/'. ``` #### Replace Contents A common operation on DOM elements is to change their contents, for example to update a view or to display additional information. To set the HTML contents of matched elements you can use the following construct that will replace the contents with `
`: ``` aQuery html: [ :r | r div ]. ``` Alternatively you can set the text contents of each element in the set of matched elements: ``` aQuery text: 'some text'. ``` Last but not least you can set the value. This is especially useful for form fields, that require different ways to set the current contents \(input fields require you to change the attribute value, text areas require you to change the contents\). The following code takes care of the details automatically: ``` aQuery value: 'some value'. ``` #### Insert Contents Alternatively to replacing the contents you can append new contents. `before:` inserts content before each element in the set of matched elements; `prepend:` inserts content to the beginning of each element in the set of matched elements; `append:` inserts content to the end of each element in the set of matched elements; and `after:` inserts content after each element in the set of matched elements. ``` aQuery before: [ :r | r div ]. +aQuery toggleClass: 'important'. +``` + + +Also you can query if a particular class is set: + +``` +aQuery hasClass: 'important'. +``` + + + +#### Styles + + +Similarly you can change the style of one or more HTML elements. By providing a dictionary you can change multiple CSS styles at once: + +``` +aQuery css: aDictionary. +``` + + +Alternatively you can use a dictionary-like protocol to read and write specific style properties: + +``` +aQuery cssAt: 'color'. +aQuery cssAt: 'color' put: '#ff0'. +``` + + +Note that in most cases it is preferred to use CSS classes instead of hardcoding your style settings into the application code. + +#### Attributes + + +While the above methods change the `class` and `style` attribute of one or more DOM elements, there are also accessor methods to change arbitrary HTML attributes. By providing a dictionary of key-value pairs you can change multiple attributes at once: + +``` +aQuery attributes: aDictionary. +``` + + +Alternatively you can use a dictionary-like protocol to read and write attributes: + +``` +aQuery attributeAt: 'href'. +aQuery attributeAt: 'href' put: 'http://www.seaside.st/'. +``` + + +#### Replace Contents + + +A common operation on DOM elements is to change their contents, for example to update a view or to display additional information. To set the HTML contents of matched elements you can use the following construct that will replace the contents with `
`: + +``` +aQuery html: [ :r | r div ]. +``` + + +Alternatively you can set the text contents of each element in the set of matched elements: + +``` +aQuery text: 'some text'. +``` + + +Last but not least you can set the value. This is especially useful for form fields, that require different ways to set the current contents \(input fields require you to change the attribute value, text areas require you to change the contents\). The following code takes care of the details automatically: + +``` +aQuery value: 'some value'. +``` + + +#### Insert Contents + + +Alternatively to replacing the contents you can append new contents. `before:` inserts content before each element in the set of matched elements; `prepend:` inserts content to the beginning of each element in the set of matched elements; `append:` inserts content to the end of each element in the set of matched elements; and `after:` inserts content after each element in the set of matched elements. + + +``` +aQuery before: [ :r | r div ]. aQuery prepend: [ :r | r div ]. aQuery append: [ :r | r div ]. -aQuery after: [ :r | r div ]. ``` Note that, as with `html:`, the argument can be any renderable object: a string, a Seaside component, or a render block as in the given examples. #### Animations Showing or hiding DOM elements is one of the most common operations. While this is typically done by adding or removing a CSS class, jQuery provides a simpler way. The action `show` makes sure that the matching DOM elements are visible. If a duration is given as a first parameter, the elements are faded-in: ``` aQuery show. -aQuery show: 1 second. ``` The same functionality is available to hide one or more DOM elements with `hide`: ``` aQuery hide. -aQuery hide: 1 second. ``` ### Adding JQuery After creating a jQuery object on the Pharo side, it is time to investigate on how to add them to the Seaside application. The standard way of doing so in jQuery is to keep all the Javascript functionality _unobtrusive_ in a separate Javascript file. This is possible with Seaside, but not the suggested way. In Seaside we try to encapsulate views and view-related functionality in components. Furthermore we keep components independent of each other and reusable in different contexts, what does not work well with sharing unobtrusive Javascript code. Additionally, the unobtrusiveness comes into the way when we want to define AJAX interactions. #### Attaching to Element The following scripts show how a query can be attached to an anchor and activated. ``` html anchor +aQuery after: [ :r | r div ]. +``` + + +Note that, as with `html:`, the argument can be any renderable object: a string, a Seaside component, or a render block as in the given examples. + +#### Animations + + +Showing or hiding DOM elements is one of the most common operations. While this is typically done by adding or removing a CSS class, jQuery provides a simpler way. The action `show` makes sure that the matching DOM elements are visible. If a duration is given as a first parameter, the elements are faded-in: + +``` +aQuery show. +aQuery show: 1 second. +``` + + +The same functionality is available to hide one or more DOM elements with `hide`: + +``` +aQuery hide. +aQuery hide: 1 second. +``` + + +### Adding JQuery + + +After creating a jQuery object on the Pharo side, it is time to investigate on how to add them to the Seaside application. + +The standard way of doing so in jQuery is to keep all the Javascript functionality _unobtrusive_ in a separate Javascript file. This is possible with Seaside, but not the suggested way. In Seaside we try to encapsulate views and view-related functionality in components. Furthermore we keep components independent of each other and reusable in different contexts, what does not work well with sharing unobtrusive Javascript code. Additionally, the unobtrusiveness comes into the way when we want to define AJAX interactions. + +#### Attaching to Element + + +The following scripts show how a query can be attached to an anchor and activated. + +``` +html anchor onClick: (html jQuery: 'div') remove; - with: 'Remove DIVs' ``` ``` html anchor + with: 'Remove DIVs' +``` + + + +``` +html anchor onClick: (html jQuery this) remove; - with: 'Remove Myself' ``` #### Execute at Load-Time Javascript proposes a way to execute a query at load-time using `$(document).ready(...)`. With Seaside, you can forget about `$(document).ready(...)`, since Seaside has its own mechanism there. ``` html document addLoadScript: (html jQuery: 'div') remove ``` ### AJAX AJAX is an acronym for _Asynchronous JavaScript and XML_. The fact that it is asynchronous means that additional data is passed to, or requested from the web server in the background, without the user waiting for it to arrive. JavaScript obviously names the programming language that is used to trigger the request. Fortunately the data being transmitted by an AJAX request doesn't have to be in XML. It can be anything that can be sent through the HTTP protocol. The reason for the \`\`XML'' in the name is that in most web browsers the internal implementation of this functionality can be found in an object called `XMLHttpRequest`. JQuery also supports the AJAX. #### Loading ``` aQuery load html: [ :r | r div: Time now ]. ``` #### No Query Once loaded we can get an AJAX object using `ajax` query. ``` html jQuery ajax ``` #### Generators An AJAX object can be configure to emit either html or script using the corresponding messages: ``` anAjax html: [ :r | r div ]. -anAjax script: [ :s | s alert: 'Hello' ]. ``` #### Triggering Callbacks Finally a query can be stored and triggered later. ``` anAjax serialize: aQuery. + with: 'Remove Myself' +``` + + +#### Execute at Load-Time + + +Javascript proposes a way to execute a query at load-time using `$(document).ready(...)`. +With Seaside, you can forget about `$(document).ready(...)`, since Seaside has its own mechanism there. + +``` +html document addLoadScript: (html jQuery: 'div') remove +``` + + +### AJAX + + +AJAX is an acronym for _Asynchronous JavaScript and XML_. The fact that it is asynchronous means that additional data is passed to, or requested from the web server in the background, without the user waiting for it to arrive. JavaScript obviously names the programming language that is used to trigger the request. Fortunately, the data being transmitted by an AJAX request doesn't have to be in XML. It can be anything that can be sent through the HTTP protocol. The reason for the ''XML'' in the name is that in most web browsers the internal implementation of this functionality can be found in an object called `XMLHttpRequest`. JQuery also supports the AJAX. + +#### Loading + + +``` +aQuery load html: [ :r | r div: Time now ]. +``` + + +#### No Query + +Once loaded we can get an AJAX object using `ajax` query. + +``` +html jQuery ajax +``` + + +#### Generators + + +An AJAX object can be configure to emit either html or script using the corresponding messages: +``` +anAjax html: [ :r | r div ]. +anAjax script: [ :s | s alert: 'Hello' ]. +``` + + +#### Triggering Callbacks + + +Finally a query can be stored and triggered later. + +``` +anAjax serialize: aQuery. anAjax trigger: [ :p | ... ] passengers: aQuery. -anAjax callback: [ :v | ... ] value: anObject. ``` ### How tos The following shows a list of possible actions #### Click and Show The following shows how to toggle a given div \(here help\) on click. ``` html anchor +anAjax callback: [ :v | ... ] value: anObject. +``` + + +### How tos + + +The following shows a list of possible actions + +#### Click and Show + + +The following shows how to toggle a given div \(here help\) on click. + +``` +html anchor onClick: (html jQuery: 'div.help') toggle; with: 'About jQuery'. html div class: 'help'; style: 'display: none'; - with: 'jQuery is a fast and ...' ``` #### Open Light box In the following we show how we can open a modal dialog box. ``` | id | + with: 'jQuery is a fast and ...' +``` + + +#### Open Light box + +In the following we show how we can open a modal dialog box. + +``` +| id | html div id: (id := html nextId); script: (html jQuery new dialog @@ -49,7 +538,16 @@ html div with: [ self renderDialogOn: html ] html anchor onClick: (html jQuery id: id) dialog open; - with: 'Open Lightbox' ``` #### Replace a Component In the following we show how to replace a component by a new one. ``` html div + with: 'Open Lightbox' +``` + + +#### Replace a Component + +In the following we show how to replace a component by a new one. + +``` +html div id: (id := html nextId); with: child. @@ -58,7 +556,18 @@ html anchor html: [ :r | child := OtherComponent new; r render: child ]); - with: 'Change Component' ``` #### Updating multiple components The following snippet shows how we can update multiple element of the DOM. Here we have two components date and time. In the script we will render each component on click. ``` html div id: #date. + with: 'Change Component' +``` + + +#### Updating multiple components + +The following snippet shows how we can update multiple elements of the DOM. +Here we have two components date and time. In the script, we will render each component +on click. + +``` +html div id: #date. ... html div id: #time. @@ -68,7 +577,26 @@ html anchor html: [ :r | r render: Date today ]. s << (s jQuery: #time) html: [ :r | r render: Time now ] ]); - with: 'Update' ``` ### Enhanced To Do Application jQuery is an increasingly popular Javascript library. Let’s port the the ToDo application to use jQuery for the Javascript functionality. First, we’ll implement the heading highlight effect with jQuery UI. Then we’ll move on to implementing a couple of interesting effects and eye-candy possible with jQuery. Drag and drop is easy to implement, but we’ll need to do something special to get the "in place" editing to work in jQuery. If you have already worked through enhancing the ToDo application with Prototype and Scriptaculous, then the jQuery version will seem very familiar - we are still working with JavaScript underneath the covers after all. #### Adding an Effect We’ll go ahead and factor the `renderContentOn:` method to add a method to handle rendering the heading and just make modifications to the new method. ``` ToDoListView >> renderContentOn: html + with: 'Update' +``` + + +### Enhanced To Do Application + +jQuery is an increasingly popular Javascript library. Let’s port the ToDo application to use jQuery for the Javascript functionality. + +First, we’ll implement the heading highlight effect with jQuery UI. Then we’ll move on to implementing a couple of interesting effects and eye-candy possible with jQuery. Drag and drop is easy to implement, but we’ll need to do something special to get the "in place" editing to work in jQuery. + +If you have already worked through enhancing the ToDo application with Prototype and Scriptaculous, then the jQuery version will seem very familiar - we are still working with JavaScript underneath the covers after all. + +#### Adding an Effect + + +We’ll go ahead and factor the `renderContentOn:` method to add a method to handle rendering the heading and just make modifications to the new method. + + +``` +ToDoListView >> renderContentOn: html self renderHeadingOn: html. "<-- added." html form: [ html unorderedList @@ -79,10 +607,30 @@ html anchor html submitButton callback: [ self add ]; text: 'Add' ]. - html render: editor ``` The `renderHeadingOn:` method leverages the jQuery UI library to add the highlight effect to the header of our component. ``` ToDoListView >> renderHeadingOn: html + html render: editor +``` + + +The `renderHeadingOn:` method leverages the jQuery UI library to add the highlight effect to the header of our component. + +``` +ToDoListView >> renderHeadingOn: html html heading onClick: html jQuery this effect highlight; - with: self model title. ``` We create a query using `html jQuery this` that selects the heading DOM element. Next we send effect to get a `JQEffect` instance. Then finally we send `JQEffect>>highlight` which highligths the background color. Altering the highlight color is left as an exercise for the reader. Now for something a little more fun - let’s add some help test that appears when you click on the heading; and it won’t just "appear", it will slide open at a rate that we determine. We do this by rendering a new `
` element that contains the help text, and changing the onClick of the header to apply our new cool effect to the new element. We also need some new CSS to help us out with this. ``` ToDoListView >> renderHeadingOn: html + with: self model title. +``` + + +We create a query using `html jQuery this` that selects the heading DOM element. Next we send effect to get a `JQEffect` instance. Then finally we send `JQEffect>>highlight` which highligths the background color. + +Altering the highlight color is left as an exercise for the reader. + +Now for something a little more fun - let’s add some help test that appears when you click on the heading; and it won’t just "appear", it will slide open at a rate that we determine. + +We do this by rendering a new `
` element that contains the help text, and changing the onClick of the header to apply our new cool effect to the new element. We also need some new CSS to help us out with this. + +``` +ToDoListView >> renderHeadingOn: html | helpId | helpId := html nextId. (html heading) @@ -94,7 +642,13 @@ html anchor id: helpId; class: 'help'; style: 'display: none'; - with: 'The ToDo app enhanced with jQuery.' ``` ``` ToDoListView >> style + with: 'The ToDo app enhanced with jQuery.' +``` + + + +``` +ToDoListView >> style ^ ' .help { padding: 1em; @@ -127,4 +681,12 @@ html anchor } li.done { color: #264409; - }' ``` ### Summary This chapter showed how Seaside can interact with Javascript. It opens a large spectrum of possibilities. \ No newline at end of file + }' +``` + + +### Summary + + +This chapter showed how Seaside can interact with Javascript. +It opens a large spectrum of possibilities. diff --git a/Chapters/18-WebSockets/websockets.md b/Chapters/18-WebSockets/websockets.md index b3d44d0..77615b5 100644 --- a/Chapters/18-WebSockets/websockets.md +++ b/Chapters/18-WebSockets/websockets.md @@ -1,9 +1,76 @@ -## WebSockets @cha:websockets DRAFT ### Concepts ### Architecture ### Studying a real case [https://github.com/TelescopeSt/TelescopeCytoscape](https://github.com/TelescopeSt/TelescopeCytoscape) This project allow one to create a model of visualization \(nodes and their contents, layouts, interactions, and update mechanism\) then to render it with the CytoscapeJs visualization framework. The Seaside part is really small. We just use Seaside to render an empty div and initialize a cytoscape visualization inside. We could probably do without Seaside but we did this project for a Seaside application. \(And we use the existing callbacks mechanism\). During the rendering we open a web socket and we generate the initial cytoscape visualization inside the empty div generated by Seaside. Then when the user interact with the visualization, it communicates the interactions via the web socket to the Pharo server and if it should impact the visualization, commands to do so are sent via the websocket to the client. In this project the main part for the websocket management are: - TLCytoscapeComponent : The Seaside component registering the visualization \([https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeComponent.class.st](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeComponent.class.st)\) - TLCytoscapeWebSocketDelegate : The class managing the websocket server side \([https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st)\) - CYSFileLibrary>>cytoscapeTelescopeJs : The javascript managing the socket client side \([https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape-Libraries/CYSFileLibrary.class.st#L539](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape-Libraries/CYSFileLibrary.class.st#L539)\) ### The Component Part ``` TLCytoscapeComponent >> visuDivId [ +## WebSockets +@cha:websockets + +DRAFT + +### Concepts + + +### Architecture + + +### Studying a real case + + +[https://github.com/TelescopeSt/TelescopeCytoscape](https://github.com/TelescopeSt/TelescopeCytoscape) + +This project allows one to create a model of visualization \(nodes and their +contents, layouts, interactions, and update mechanism\) then to render it +with the CytoscapeJs visualization framework. + +The Seaside part is really small. We just use Seaside to render an empty +div and initialize a cytoscape visualization inside. We could probably +do without Seaside but we did this project for a Seaside application. +\(And we use the existing callbacks mechanism\). + +During the rendering,s we open a web socket and we generate the initial +cytoscape visualization inside the empty div generated by Seaside. + +Then when the user interact with the visualization, it communicates the +interactions via the web socket to the Pharo server and if it should +impact the visualization, commands to do so are sent via the websocket +to the client. + +In this project the main part for the websocket management are: + +- TLCytoscapeComponent : The Seaside component registering the + +visualization +\([https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeComponent.class.st](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeComponent.class.st)\) +- TLCytoscapeWebSocketDelegate : The class managing the websocket server + +side +\([https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st)\) +- CYSFileLibrary>>cytoscapeTelescopeJs : The javascript managing the + +socket client side +\([https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape-Libraries/CYSFileLibrary.class.st#L539](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape-Libraries/CYSFileLibrary.class.st#L539)\) + + +### The Component Part + + +``` +TLCytoscapeComponent >> visuDivId [ visuDivId ifNil: [ visuDivId:= self class nextId ]. ^ visuDivId -] ``` ``` TLCytoscapeComponent >> visuDivId: anObject [ +] +``` + + +``` +TLCytoscapeComponent >> visuDivId: anObject [ visuDivId := anObject -] ``` ``` TLCytoscapeComponent >> renderContentOn: html [ +] +``` + + + + + + +``` +TLCytoscapeComponent >> renderContentOn: html [ | visuId div callback | visuId := self visuDivId. div := html div @@ -26,25 +93,59 @@ callbackUrl: {html actionUrl asString. (div storeCallback: callback)} -] ``` ``` TLCytoscapeComponent >> renderOptionalButtonsOn: html [ +] +``` + + +``` +TLCytoscapeComponent >> renderOptionalButtonsOn: html [ html div class: 'fitButton'; with: [ self renderResetButtonOn: html. self exportStrategy renderDownloadButtonForVisu: self visuDivId on: html ] -] ``` ``` TLCytoscapeComponent >> renderResetButtonOn: html [ +] +``` + + +``` +TLCytoscapeComponent >> renderResetButtonOn: html [ html anchor onClick: 'telescope.visuWithId(' , self visuDivId asString , ').fit();'; with: 'Reset' -] ``` ### Websocket server side The class managing the websocket server side \(from [https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st)\) ``` ZnWebSocketDelegate subclass: #TLCytoscapeWebSocketDelegate, +] +``` + + + +### Websocket server side + + +The class managing the websocket server +side \(from [https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st](https://github.com/TelescopeSt/TelescopeCytoscape/blob/development/src/Telescope-Cytoscape/TLCytoscapeWebSocketDelegate.class.st)\) + +``` +ZnWebSocketDelegate subclass: #TLCytoscapeWebSocketDelegate, instVars: 'visualizationByIdDictionary websocketByVisu' - classInstVars: 'singleton development serverPort clientPort' ``` ``` TLCytoscapeWebSocketDelegate class >> registerVisualization: aTLVisualization underId: aDivId withCallBack: aCallBack callbackUrl: callbackUrl [ + classInstVars: 'singleton development serverPort clientPort' +``` + + + + +``` +TLCytoscapeWebSocketDelegate class >> registerVisualization: aTLVisualization underId: aDivId withCallBack: aCallBack callbackUrl: callbackUrl [ self ensureServerIsRunning. self singleton delegate registerVisualization: aTLVisualization underId: aDivId withCallBack: aCallBack callbackUrl: callbackUrl -] ``` ``` initialize [ +] +``` + + +``` +initialize [ super initialize. self visualizationByIdDictionary: Dictionary new. self websocketByVisu: Dictionary new. @@ -56,4 +157,7 @@ do: [ self freeResourcesFor: webSocket ] ] on: PrimitiveFailed do: [ self class restart ] ] - ] ``` \ No newline at end of file + ] +``` + +