├── .gitignore
├── COPYING
├── README
├── bench
├── README
└── bench.go
├── demo.sh
├── key.go
├── main.go
├── store.go
└── talk
├── balancer.png
├── bumper640x360.png
├── code
├── 0
│ ├── key.go
│ ├── main.go
│ └── store.go
├── 1
│ ├── key.go
│ ├── main.go
│ └── store.go
├── 2
│ ├── key.go
│ ├── main.go
│ └── store.go
└── 3
│ ├── demo.sh
│ ├── key.go
│ ├── main.go
│ └── store.go
├── gopher.png
├── index.html
├── notes.txt
├── slidy.css
├── slidy.js
├── structure.png
└── urlstore.png
/.gitignore:
--------------------------------------------------------------------------------
1 | _go_.6
2 | store.json
3 | /talk/code/?/goto
4 | /bench/bench
5 | /goto
6 |
--------------------------------------------------------------------------------
/COPYING:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
178 |
179 | APPENDIX: How to apply the Apache License to your work.
180 |
181 | To apply the Apache License to your work, attach the following
182 | boilerplate notice, with the fields enclosed by brackets "[]"
183 | replaced with your own identifying information. (Don't include
184 | the brackets!) The text should be enclosed in the appropriate
185 | comment syntax for the file format. We also recommend that a
186 | file or class name and description of purpose be included on the
187 | same "printed page" as the copyright notice for easier
188 | identification within third-party archives.
189 |
190 | Copyright [yyyy] [name of copyright owner]
191 |
192 | Licensed under the Apache License, Version 2.0 (the "License");
193 | you may not use this file except in compliance with the License.
194 | You may obtain a copy of the License at
195 |
196 | http://www.apache.org/licenses/LICENSE-2.0
197 |
198 | Unless required by applicable law or agreed to in writing, software
199 | distributed under the License is distributed on an "AS IS" BASIS,
200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201 | See the License for the specific language governing permissions and
202 | limitations under the License.
203 |
--------------------------------------------------------------------------------
/README:
--------------------------------------------------------------------------------
1 | Goto - A URL shortening service
2 |
3 | This code is the basis of the presentation 'Practical Go Programming',
4 | available in the talk/ directory.
5 |
6 | The accompanying code samples are in talk/code.
7 |
8 | The code in the main directory is the full-featured URL shortener, complete
9 | with bindings to github.com/nf/stat, a statistics-collection library.
10 |
11 | There is a stress tester in bench/, which also depends on stat.
12 |
13 | The demo.sh script launches the stats server (stat must be checked out and
14 | built in ../stat), 4 goto servers (3 slaves and 1 master), and several
15 | iterations of the stress-tester.
16 |
17 | Run it and visit http://localhost:8090/ for a pretty graph.
18 |
--------------------------------------------------------------------------------
/bench/README:
--------------------------------------------------------------------------------
1 | A stress-tester for goto
2 |
3 | To avoid exhausting source TCP ports (they tend to get stuck in TIME_WAIT),
4 | you'll need to set these sysctl values:
5 |
6 | Linux:
7 | sudo sysctl net.ipv4.tcp_tw_recycle=1
8 | sudo sysctl net.ipv4.tcp_tw_reuse=1 (may not be necessary)
9 | OS X:
10 | sudo sysctl -w net.inet.tcp.msl=1000
11 |
12 |
--------------------------------------------------------------------------------
/bench/bench.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "flag"
19 | "fmt"
20 | "github.com/nf/stat"
21 | "io/ioutil"
22 | "log"
23 | "math/rand"
24 | "net/http"
25 | "net/url"
26 | "regexp"
27 | "strings"
28 | "time"
29 | )
30 |
31 | var (
32 | n = flag.Int("n", 10, "magnitude of assault")
33 | host = flag.String("host", "localhost:8080", "target host:port")
34 | statServer = flag.String("stats", "localhost:8090", "stat server host")
35 | hosts []string
36 | hostRe = regexp.MustCompile("http://[a-zA-Z0-9:.]+")
37 | )
38 |
39 | const (
40 | fooUrl = "http://example.net/foobar"
41 | monDelay = 1e9
42 | getDelay = 100e6
43 | getters = 10
44 | postDelay = 100e6
45 | posters = 1
46 | )
47 |
48 | var (
49 | newURL = make(chan string)
50 | randURL = make(chan string)
51 | )
52 |
53 | func keeper() {
54 | var urls []string
55 | urls = append(urls, <-newURL)
56 | for {
57 | r := urls[rand.Intn(len(urls))]
58 | select {
59 | case u := <-newURL:
60 | for _, h := range hosts {
61 | u = hostRe.ReplaceAllString(u, "http://"+h)
62 | urls = append(urls, u)
63 | }
64 | case randURL <- r:
65 | }
66 | }
67 | }
68 |
69 | func post() {
70 | u := fmt.Sprintf("http://%s/add", hosts[rand.Intn(len(hosts))])
71 | r, err := http.PostForm(u, url.Values{"url": {fooUrl}})
72 | if err != nil {
73 | log.Println("post:", err)
74 | return
75 | }
76 | defer r.Body.Close()
77 | b, err := ioutil.ReadAll(r.Body)
78 | if err != nil {
79 | log.Println("post:", err)
80 | return
81 | }
82 | newURL <- string(b)
83 | stat.In <- "put"
84 | }
85 |
86 | func get() {
87 | u := <-randURL
88 | req, err := http.NewRequest("HEAD",u,nil)
89 | r, err := http.DefaultTransport.RoundTrip(req)
90 | if err != nil {
91 | log.Println("get:", err)
92 | return
93 | }
94 | defer r.Body.Close()
95 | b, err := ioutil.ReadAll(r.Body)
96 | if err != nil {
97 | log.Println("get:", err)
98 | return
99 | }
100 | if r.StatusCode != 302 {
101 | log.Println("get: wrong StatusCode:", r.StatusCode)
102 | if r.StatusCode == 500 {
103 | log.Printf("Error: %s\n", b)
104 | }
105 | }
106 | if l := r.Header.Get("Location"); l != fooUrl {
107 | log.Println("get: wrong Location:", l)
108 | }
109 | stat.In <- "get"
110 | }
111 |
112 | func loop(fn func(), delay time.Duration) {
113 | for {
114 | fn()
115 | time.Sleep(delay)
116 | }
117 | }
118 |
119 | func main() {
120 | flag.Parse()
121 | hosts = strings.Split(*host, ",")
122 | rand.Seed(time.Now().UnixNano())
123 | go keeper()
124 | for i := 0; i < getters*(*n); i++ {
125 | go loop(get, getDelay)
126 | }
127 | for i := 0; i < posters*(*n); i++ {
128 | go loop(post, postDelay)
129 | }
130 | stat.Process = "!bench"
131 | stat.Monitor(*statServer)
132 | }
133 |
--------------------------------------------------------------------------------
/demo.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | STATS=127.0.0.1:8090
4 | MASTER=127.0.0.1:8080
5 | N1=1
6 | N2=8
7 | N3=16
8 |
9 | echo "Starting up"
10 | cd ../stat/server
11 | go build -o stats
12 | ./stats &
13 | stats_pid=$!
14 | cd ../../goto
15 | sleep 1
16 | go build -o goto
17 | ./goto -stats=$STATS -host=$MASTER -rpc=true &
18 | master_pid=$!
19 | sleep 1
20 | ./goto -stats=$STATS -host=$MASTER -master=$MASTER -http=:8081 &
21 | slave1_pid=$!
22 | ./goto -stats=$STATS -host=$MASTER -master=$MASTER -http=:8082 &
23 | slave2_pid=$!
24 | ./goto -stats=$STATS -host=$MASTER -master=$MASTER -http=:8083 &
25 | slave3_pid=$!
26 | sleep 1
27 |
28 | echo "Testing the master (n=$N1)"
29 | go build -o bench/bench ./bench
30 | bench/bench -stats=$STATS -host=$MASTER -n=$N1 &
31 | pid=$!
32 | read
33 | kill $pid
34 |
35 | echo "Testing the master (n=$N2)"
36 | bench/bench -stats=$STATS -host=$MASTER -n=$N2 &
37 | pid=$!
38 | read
39 | kill $pid
40 |
41 | echo "Testing 1 slave (n=$N2)"
42 | bench/bench -stats=$STATS -host=127.0.0.1:8081 -n=$N2 &
43 | pid=$!
44 | read
45 | kill $pid
46 |
47 | echo "Testing 2 slaves (n=$N2)"
48 | bench/bench -stats=$STATS -host=127.0.0.1:8081,127.0.0.1:8082 -n=$N2 &
49 | pid=$!
50 | read
51 | kill $pid
52 |
53 | echo "Testing 3 slaves (n=$N2)"
54 | bench/bench -stats=$STATS -host=127.0.0.1:8081,127.0.0.1:8082,127.0.0.1:8083 -n=$N2 &
55 | pid=$!
56 | read
57 | kill $pid
58 |
59 | echo "Testing 3 slaves (n=$N3)"
60 | bench/bench -stats=$STATS -host=127.0.0.1:8081,127.0.0.1:8082,127.0.0.1:8083 -n=$N3 &
61 | pid=$!
62 | read
63 | kill $pid
64 |
65 | echo "Shutting down"
66 | kill $stats_pid
67 | kill $master_pid
68 | kill $slave1_pid
69 | kill $slave2_pid
70 | kill $slave3_pid
71 |
--------------------------------------------------------------------------------
/key.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | var keyChar = []byte("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
18 |
19 | func genKey(n int) string {
20 | if n == 0 {
21 | return string(keyChar[0])
22 | }
23 | l := len(keyChar)
24 | s := make([]byte, 20) // FIXME: will overflow. eventually.
25 | i := len(s)
26 | for n > 0 && i >= 0 {
27 | i--
28 | j := n % l
29 | n = (n - j) / l
30 | s[i] = keyChar[j]
31 | }
32 | return string(s[i:])
33 | }
34 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2011 Google Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package main
16 |
17 | import (
18 | "flag"
19 | "fmt"
20 | "github.com/nf/stat"
21 | "net/http"
22 | "net/rpc"
23 | )
24 |
25 | var (
26 | listenAddr = flag.String("http", ":8080", "http listen address")
27 | dataFile = flag.String("file", "store.json", "data store file name")
28 | hostname = flag.String("host", "localhost:8080", "http host name")
29 | masterAddr = flag.String("master", "", "RPC master address")
30 | rpcEnabled = flag.Bool("rpc", false, "enable RPC server")
31 | statServer = flag.String("stats", "", "stat server address")
32 | )
33 |
34 | var store Store
35 |
36 | func main() {
37 | flag.Parse()
38 | if *masterAddr != "" {
39 | store = NewProxyStore(*masterAddr)
40 | } else {
41 | store = NewURLStore(*dataFile)
42 | }
43 | if *rpcEnabled {
44 | rpc.RegisterName("Store", store)
45 | rpc.HandleHTTP()
46 | }
47 | if *statServer != "" {
48 | stat.Process = *listenAddr
49 | go stat.Monitor(*statServer)
50 | }
51 | http.HandleFunc("/", Redirect)
52 | http.HandleFunc("/add", Add)
53 | http.ListenAndServe(*listenAddr, nil)
54 |
55 | }
56 |
57 | func Redirect(w http.ResponseWriter, r *http.Request) {
58 | key := r.URL.Path[1:]
59 | if key == "favicon.ico" || key == "" {
60 | http.NotFound(w, r)
61 | return
62 | }
63 | var url string
64 | if err := store.Get(&key, &url); err != nil {
65 | http.Error(w, err.Error(), http.StatusInternalServerError)
66 | return
67 | }
68 | http.Redirect(w, r, url, http.StatusFound)
69 | }
70 |
71 | func Add(w http.ResponseWriter, r *http.Request) {
72 | url := r.FormValue("url")
73 | if url == "" {
74 | fmt.Fprint(w, AddForm)
75 | return
76 | }
77 | var key string
78 | if err := store.Put(&url, &key); err != nil {
79 | http.Error(w, err.Error(), http.StatusInternalServerError)
80 | return
81 | }
82 | fmt.Fprintf(w, "http://%s/%s", *hostname, key)
83 | }
84 |
85 | const AddForm = `
86 |
Memory managed and syntactically lightweight; easy to use
30 |
Fast compiled code; comparable to C
31 |
Concurrency support; write simpler code
32 |
Static typing
33 |
Consistent standard library
34 |
Self-documenting (and well-documented)
35 |
Free and Open Source (BSD licensed)
36 |
37 |
38 |
39 |
40 |
This talk
41 |
42 | This talk will cover the complete development of a simple web application.
43 |
44 |
45 | There's a lot to cover, so we'll move pretty fast.
46 |
47 |
48 | If you're new to Go there may be some syntax you don't understand.
49 | The important thing is to get a feel for what the program does,
50 | rather than exactly how it does it.
51 |
148 | An RWMutex has two locks: one for readers, and one for writers.
149 | Many clients can take the read lock simultaneously, but only one client can
150 | take the write lock (to the exclusion of all readers).
151 |
152 |
153 |
154 |
155 |
Setter and Getter methods
156 |
157 | We must now interact with the URLStore through Set
158 | and Get methods.
159 |
160 |
161 | The Get method takes the read lock with mu.RLock, and
162 | returns the URL as a string. If the key is not present in the map, the
163 | zero value for the string type (an empty string) will be returned.
164 |
178 | The Set method takes the write lock and updates the url map.
179 |
180 |
181 | If the key is already present, Set returns a boolean
182 | false value and the map is not updated.
183 | (Later, We will use this behavior to ensure that each URL has a unique key.)
184 |
203 | A defer statement pushes a function call onto a list.
204 | The list of saved calls is executed after the surrounding function returns.
205 | Defer is commonly used to simplify functions that perform various clean-up
206 | actions.
207 |
208 |
209 | For example, this function will print "Hello" and then "World":
210 |
259 | The URLStore struct contains a map field,
260 | which must be initialized with make before it can be used.
261 |
262 | type URLStore struct {
263 | urls map[string]string
264 | mu sync.RWMutex
265 | }
266 |
267 |
268 | Go doesn't have constructors. Instead, the convention is to write a function
269 | named NewXXX that returns an intialized instance of the type.
270 |
296 | if url := s.Get("a"); url != "" {
297 | // redirect to url
298 | } else {
299 | // key not found
300 | }
301 |
302 |
303 |
304 |
305 |
Shortening URLs
306 |
307 | We already have the Get method for retrieving URLs.
308 | Let's create a Put method that takes a URL,
309 | stores the URL under a corresponding key, and returns that key.
310 |
387 | Requests to /add will be served by the Add handler.
388 | All other requests will be served by the Redirect handler.
389 |
390 |
391 |
392 |
HTTP Handlers: Add
393 |
394 | The Add function reads the url
395 | parameter from an HTTP request, Puts it into
396 | the store, and sends the corresponding short URL to the user.
397 |
406 | But what is store?
407 | It's a global variable pointing to an instance of URLStore:
408 |
409 |
410 | var store = NewURLStore()
411 |
412 |
413 | The line above can appear anywhere in the top level of a source file.
414 | It will be evaluated at program initialization, before the main
415 | function is called.
416 |
417 |
418 |
419 |
420 |
HTTP Handlers: Add
421 |
422 | What about the user interface? Let's modify Add to display an
423 | HTML form when no url is supplied:
424 |
447 | The Redirect function finds the key in the HTTP request path,
448 | retrieves the corresponding URL from the store,
449 | and sends an HTTP redirect to the user.
450 | If the URL is not found, a 404 "Not Found" error is sent instead.
451 |
518 | Many types, from both the standard library and other Go code, implement the
519 | Write method described above, and can thus be used anywhere an
520 | io.Writer is expected.
521 |
522 |
523 |
524 |
525 |
Interfaces: an aside
526 |
527 | In fact, we've already used an io.Writer in our
528 | HTTP handlers:
529 |
631 | The new load method will Seek to the beginning of
632 | the file, read and Decode each record,
633 | and store the data using the Set method:
634 |
635 |
636 | func (s *URLStore) load() error {
637 | if _, err := s.file.Seek(0, 0); err != nil {
638 | return err
639 | }
640 | d := json.NewDecoder(s.file)
641 | var err error
642 | for err == nil {
643 | var r record
644 | if err = d.Decode(&r); err == nil {
645 | s.Set(r.Key, r.URL)
646 | }
647 | }
648 | if err == os.EOF {
649 | return nil
650 | }
651 | return err
652 | }
653 |
654 |
655 |
656 |
657 |
Persistent Storage: URLStore
658 |
659 | Took hook it all up, first we add a call to load to the
660 | constructor function:
661 |
Many clients attempt to add URLs simultaneously.
723 |
Even though we may safely update the map concurrently,
724 | the disk writes may happen simultaneously.
725 | Depending on the characteristics of your OS, this may cause corruption.
726 |
Even if the writes do not collide, each client must wait for their
727 | data to be written to disk before their Put will return.
728 |
Therefore, on a heavily I/O-loaded system, clients will wait longer
729 | than necessary for their Add requests to go through.
730 |
731 |
732 | To remedy these issues, we should decouple the Put and
733 | save processes.
734 |
735 |
736 |
737 |
738 |
Goroutines: an aside
739 |
A goroutine is a lightweight thread managed by the Go runtime.
740 |
Goroutines are launched by a go statement. This code executes
741 | both foo and bar concurrently:
742 |
743 | go foo()
744 | bar()
745 |
746 |
747 | The foo function runs in a newly created goroutine, while
748 | bar runs in the main goroutine.
749 |
750 |
751 | Memory is shared between goroutines, like in many popular threading models.
752 |
753 |
754 | Goroutines are cheaper to create than operating system threads.
755 |
756 |
757 |
758 |
759 |
Channels: an aside
760 |
761 | A channel is a conduit, like a unix pipe, through which you can send
762 | typed values. They provide many interesting algorithmic possibilities.
763 |
764 |
765 | Like maps, channels must be initialized with make:
766 |
767 |
768 | ch := make(chan int) // a channel of ints
769 |
770 |
771 | Communication is expressed using the "channel operator", <- :
772 |
773 |
774 | ch <- 7 // send the int 7 to the channel
775 |
776 | i := <-ch // receive an int from the channel
777 |
778 |
779 | Data always moves in the direction of the arrow.
780 |
781 |
782 |
783 |
784 |
Channels: an aside
785 |
786 | Communicating between goroutines:
787 |
788 |
789 | func sum(x, y int, c chan int) {
790 | c <- x + y
791 | }
792 |
793 | func main() {
794 | c := make(chan int)
795 | go sum(24, 18, c)
796 | fmt.Println(<-c)
797 | }
798 |
799 |
800 | Channel send/receive operations typically block until the other side is ready.
801 | Channels can be either buffered or unbuffered.
802 | Sends to a buffered channel will not block until the buffer is full.
803 |
804 |
805 | Buffered channels are initialized by specifying the buffer size as the
806 | second argument to make:
807 |
871 | We need to modify the NewURLStore function to launch the
872 | saveLoop goroutine (and remove the now-unnecessary file opening
873 | code):
874 |
907 | We first create some global variables to hold the flag values:
908 |
909 |
910 | var (
911 | listenAddr = flag.String("http", ":8080", "http listen address")
912 | dataFile = flag.String("file", "store.json", "data store file name")
913 | hostname = flag.String("host", "localhost:8080", "host name and port")
914 | )
915 |
916 |
917 |
918 |
919 |
An aside: Command-line flags
920 |
921 | Then we can add flag.Parse() to the main function,
922 | and instantiate the URLStore after the flags have been parsed
923 | (once we know the value of *dataFile).
924 |
959 | So far we have a program that runs as a single process. But a single process
960 | running on one machine can only serve so many concurrent requests.
961 |
962 |
963 | A URL Shortener typically serves many more Redirects (reads) than it does
964 | Adds (writes).
965 |
966 |
967 | Therefore we can create an arbitrary number of read-only slaves that serve and
968 | cache Get requests, and pass Puts through to the master.
969 |
970 |
971 |
972 |
973 |
Making URLStore an RPC service
974 |
975 | Go's rpc package provides a convenient means of making function
976 | calls over a network connection.
977 |
978 |
979 | Given a value, rpc will expose to the network the value's methods
980 | that meet this function signature:
981 |
996 | And, of course, we need to change the call sites to call these functions
997 | appropriately.
998 |
999 |
1000 |
1001 |
1002 |
Making URLStore an RPC service
1003 |
1004 | To make URLStore an RPC services, we need to alter the
1005 | Get and Put methods to be rpc-friendly.
1006 | The function signatures change, and now return an error value.
1007 |
1008 |
1009 | The Get method can return an explicit error when the provided key
1010 | is not found:
1011 |
1012 |
1013 | func (s *URLStore) Get(key, url *string) error {
1014 | s.mu.RLock()
1015 | defer s.mu.RUnlock()
1016 | if u, ok := s.urls[*key]; ok {
1017 | *url = u
1018 | return nil
1019 | }
1020 | return os.NewError("key not found")
1021 | }
1022 |
1023 |
1024 | Beyond the function signature, the Put method barely changes
1025 | in its actual code (not shown here):
1026 |
1168 | (And we must modify the URLStore so that it doesn't try to write
1169 | to or read from disk if an empty filename is given — no big deal.)
1170 |
1171 |
1172 |
1173 |
1174 |
A caching ProxyStore
1175 |
1176 | The Get method should first check if the key is in the cache.
1177 | If present, Get should return the cached result. If not,
1178 | it should make the RPC call, and update its local cache with the result.
1179 |
1281 | While this program does what we set out to do, there are a few ways it could
1282 | be improved:
1283 |
1284 |
1285 |
Aesthetics: the user interface could be (much) prettier.
1286 | See the Wiki Codelab
1287 | at golang.org for details on using Go's template package.
1288 |
Reliability: the master/slave RPC connections could be more reliable.
1289 | If the client-server connection goes down,
1290 | the client should attempt to re-dial.
1291 | A "dialer" goroutine could manage this.
1292 |
Resource exhaustion: as the size of the URL database grows, memory usage
1293 | might become an issue. This could be resolved by sharding the master
1294 | servers by key.
1295 |
Deletion: to support deletion of shortened URLs, the interactions
1296 | between master and slave would need to be made more complex.
1297 |
1298 |
1299 |
1300 |
1301 |
Go Resources
1302 |
1303 |
http://tour.golang.org/ - an interactive tour of Go.
1304 |
http://golang.org/ - the official Go homepage.
1305 |
1306 |
Lots of documentation (read the language spec!),
1307 |
Tutorials (and codelabs, codewalks),
1308 |
The Go Playground (write, compile, and run Go code from a browser),
1309 |
and more.
1310 |
1311 |
http://blog.golang.org/ - the official Go blog.
1312 |
http://godashboard.appspot.com/package - community-written Go libraries.
1313 |
http://groups.google.com/group/golang-nuts - the Go users mailing list.
1314 |
#go-nuts on irc.freenode.net - realtime Go help.
1315 |
1316 |
1317 |
1318 |
1319 |
Questions?
1320 |
Andrew Gerrand
1321 |
adg@golang.org
1322 |
http://wh3rd.net/practical-go/
1323 |
1324 |
1325 |
1326 |
1327 |
1328 |
1329 |
1330 |
--------------------------------------------------------------------------------
/talk/notes.txt:
--------------------------------------------------------------------------------
1 |
2 | - Let's build a URL shortener.
3 | - Users put a URL in a form, get a shortened URL in return
4 |
5 | - Start with the data; create a data type to store our key->url map
6 | - ASIDE: the map type, make
7 | - type URLMap map[string]string
8 | - add Get and Set methods
9 | - but what about concurrent access? this isn't thread safe
10 | - make it a struct with RWMutex, add lock/unlock to methods
11 | - add constructor functon, NewURLMap
12 |
13 | - We need an interface to Put URLs in exchange for a generated key,
14 | and Get URLs by key.
15 | - type URLStore struct { urls *URLMap }
16 | - NewURLStore function creates and returns a URLStore
17 | - Put method generates the key
18 | - show (but don't explain) keyGen function in key.go
19 | (maybe drop this and just use numeric keys)
20 | - add 'count int' to struct
21 | - this isn't thread safe! two clients could get the same key
22 | - add a mutex to URLStore
23 | - Get method is trivial
24 |
25 | - The web interface:
26 | - main function
27 | - http Handle and Listen
28 | - demo http server that does nothing (404)
29 | - global variable store = NewURLStore()
30 | - Add function
31 | - HTML form constant
32 | - pull url from r.FormValue
33 | - key = store.Put(url)
34 | - output new url
35 | - main: Handle("/add", Add)
36 | - demo visiting /add a couple of times, watch keys increment
37 | (get suggestions of urls to add from crowd)
38 | - Redirect function
39 | - key = r.URL.Path[1:]
40 | - url = store.Get(key)
41 | - if not found, http.NotFound
42 | - http.Redirect(w, r, url, http.StatusFound)
43 | - demo redirects /0, /1, etc
44 | - this is using Go's concurrency features "behind the scenes" - more later
45 |
46 | - Oh no! The process ended and we lost all our data! We need persistent storage.
47 | - ASIDE: interfaces:
48 | - os.Error interface
49 | - Reader and Writer interfaces
50 | - implement WriteTo on URLMap
51 | - ASIDE: json
52 | - create encoder
53 | - take a read lock
54 | - encode to writer
55 | - release lock
56 | - return error
57 | - this is more verbose than it need be:
58 | - ASIDE: defer
59 | - defer releasing the lock, save a line
60 | - implement ReadFrom
61 | - decoder
62 | - take write lock, deferring unlock
63 | - decode from reader
64 | - add filename to URLStore struct and NewURLStore args
65 | - save method on URLStore
66 | - open the file for writing (explain flags)
67 | - handle errors
68 | - defer Close
69 | - WriteTo f
70 | - load method
71 | - open file for reading
72 | - handle errors
73 | - defer Close
74 | - ReadFrom f
75 | - add load to NewURLStore
76 | - add save to Put
77 | - demo our file store now works:
78 | - launch app, add some urls, kill app, reload, urls still there
79 |
80 | - Let's remove some 'magic numbers' from the code:
81 | - command-line flags
82 | - ASIDE: the flag package
83 | - listenAddr, hostname, filename
84 | - replace constants
85 | - flag.Parse in main
86 | - demo working command lines
87 |
88 | - Another issue: When many clients try to add URLs in quick succession and the
89 | URL list is saved to disk after each addition, each client will need to wait
90 | for the list to be saved before their request will go through.
91 | - It would be better to save the list periodically.
92 | - So far, we've used Go's concurrency features "behind the scenes", but
93 | now we want to explicity create another thread that periodically saves the
94 | list.
95 | - ASIDE: goroutines
96 | - create saveLoop
97 | - time.Sleep
98 | - s.save()
99 | - put log.Println("Saving") for explanatory purposes
100 | - add "go s.saveLoop()" to NewURLStore
101 | - remove s.save() from Put
102 | - demo the periodical saving
103 | - but this will rewrite the file regardless of whether it's been updated.
104 | we need to signal the saveLoop goroutine when it should save the file
105 | - ASIDE: channels
106 | - add 'dirty chan bool' to struct
107 | - add '<-s.dirty' to saveLoop
108 | - add make(chan bool, 1) to NewURLStore
109 | - add '_ = s.dirty <- true' to save
110 | - demo periodical saving only after url add
111 |
112 | - So far we have a program that runs as a single process. But a single process
113 | running on one machine can only serve so many simultaenous requests.
114 | - A URL Shortener typically serves many more Redirects (reads) than it does
115 | Adds (writes).
116 | - We can create an arbitrary number of read-only slaves that cache and
117 | serve Get requests, and pass Puts through to the master.
118 | - ASIDE: rpc
119 | - a simple means of communicating between Go programs over a network,
120 | - trivial rpc example
121 | - master:
122 | - Let's make the URLStore into an rpc service by changing the Get and Put
123 | methods slightly.
124 | - Get has slightly different logic, now returns an error if not found
125 | - Put is trivially modified
126 | - The call sites become slightly more complicated:
127 | - Redirect must pre-declare "var url string"
128 | - Add must provide "var key string"
129 | - both must check the error value, and report it to the client
130 | (this will become very useful shortly)
131 | - add 'rpc' flag to enable rpc server
132 | - if rpc = true, register the URLStore with the rpc package in main()
133 | - rpc.RegisterName("Store", store)
134 | (we override the name for reasons we'll see shortly)
135 | - rpc.HandleHTTP() to enable RPC-over-HTTP
136 | - demo the methods registered at /debug/rpc
137 | - slave:
138 | - create ProxyStore struct, with *rpc.Client inside
139 | - NewProxyStore takes addr, connects, and returns *ProxyStore
140 | - Get makes s.client.Call("Store.Get"...)
141 | - Put makes s.client.Call("Store.Put"...)
142 | - we want to be able to use this new store interchangably with the URLStore
143 | this is a job for interfaces!
144 | - create Store interface type
145 | - global variable store becomes "var store Store"
146 | - add 'master' command-line flag
147 | - main() to instantiate ProxyStore or URLStore depending if master is set
148 | - demo working proxy, two processes communicating with each other
149 | - one last thing: caching on the slave side
150 | - add *URLMap to ProxyStore struct
151 | - initialize it in NewProxyStore
152 | - add calls to urls.Get/Set to Get and Put
153 | - demo stress test of 3 slaves, 1 master
154 | - little instrumentation module
155 |
156 |
--------------------------------------------------------------------------------
/talk/slidy.css:
--------------------------------------------------------------------------------
1 | /* slidy.css
2 |
3 | Copyright (c) 2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.
4 | W3C liability, trademark, document use and software licensing
5 | rules apply, see:
6 |
7 | http://www.w3.org/Consortium/Legal/copyright-documents
8 | http://www.w3.org/Consortium/Legal/copyright-software
9 | */
10 | body
11 | {
12 | margin: 0 0 0 0;
13 | padding: 0 0 0 0;
14 | width: 100%;
15 | height: 100%;
16 | color: black;
17 | background-color: white;
18 | font-family: Helvetica, sans-serif;
19 | font-size: 18pt;
20 | }
21 |
22 | .hidden { display: none; visibility: hidden }
23 |
24 | div.toolbar {
25 | position: fixed; z-index: 200;
26 | top: auto; bottom: 0; left: 0; right: 0;
27 | height: 1.2em; text-align: right;
28 | padding-left: 1em;
29 | padding-right: 1em;
30 | font-size: 60%;
31 | color: red; background: rgb(240,240,240);
32 | }
33 |
34 | div.background {
35 | display: none;
36 | }
37 |
38 | div.handout {
39 | margin-left: 20px;
40 | margin-right: 20px;
41 | }
42 |
43 | div.slide.titlepage {
44 | text-align: center;
45 | color: white;
46 | background: black;
47 | }
48 |
49 | div.slide.titlepage h1 {
50 | padding: 5% 0 0;
51 | padding-top: 5%;
52 | margin: 0;
53 | }
54 |
55 | div.slide.titlepage.end h1 {
56 | padding-top: 5%;
57 | }
58 |
59 | div.slide {
60 | z-index: 20;
61 | margin: 0 0 0 0;
62 | padding-top: 20px;
63 | padding-bottom: 0;
64 | padding-left: 40px;
65 | padding-right: 40px;
66 | border-width: 0;
67 | top: 0;
68 | bottom: 0;
69 | left: 0;
70 | right: 0;
71 | line-height: 120%;
72 | background-color: transparent;
73 | }
74 |
75 | /* this rule is hidden from IE 6 and below which don't support + selector */
76 | div.slide + div[class].slide { page-break-before: always;}
77 |
78 | div.slide h1 {
79 | padding-left: 0;
80 | padding-right: 20pt;
81 | padding-top: 10pt;
82 | padding-bottom: 4pt;
83 | margin-top: 0;
84 | margin-left: 0;
85 | margin-right: 60pt;
86 | margin-bottom: 0.5em;
87 | display: block;
88 | font-size: 160%;
89 | line-height: 1.2em;
90 | background: transparent;
91 | }
92 |
93 | div.toc {
94 | position: absolute;
95 | top: auto;
96 | bottom: 4em;
97 | left: 4em;
98 | right: auto;
99 | width: 60%;
100 | max-width: 30em;
101 | height: 30em;
102 | border: solid thin black;
103 | padding: 1em;
104 | background: rgb(240,240,240);
105 | color: black;
106 | z-index: 300;
107 | overflow: auto;
108 | display: block;
109 | visibility: visible;
110 | }
111 |
112 | div.toc-heading {
113 | width: 100%;
114 | border-bottom: solid 1px rgb(180,180,180);
115 | margin-bottom: 1em;
116 | text-align: center;
117 | }
118 |
119 | pre {
120 | line-height: 120%;
121 | padding-top: 0.5em;
122 | padding-bottom: 0.5em;
123 | padding-left: 1em;
124 | padding-right: 1em;
125 | border-style: solid;
126 | border-left-width: thin;
127 | border-top-width: thin;
128 | border-right-width: thin;
129 | border-bottom-width: thin;
130 | border-color: #95ABD0;
131 | background-color: #eee;
132 | }
133 | pre, code {
134 | color: #00428C;
135 | font-weight: bold;
136 | }
137 | pre em {
138 | color: #8C4200;
139 | font-style: normal;
140 | }
141 | pre span {
142 | color: #888;
143 | }
144 |
145 | li pre { margin-left: 0; }
146 |
147 | @media print {
148 | div.slide {
149 | display: block;
150 | visibility: visible;
151 | position: relative;
152 | border-top-style: solid;
153 | border-top-width: thin;
154 | border-top-color: black;
155 | }
156 | div.slide pre { font-size: 60%; padding-left: 0.5em; }
157 | div.handout { display: block; visibility: visible; }
158 | }
159 |
160 | blockquote { font-style: italic }
161 |
162 | img { background-color: transparent }
163 |
164 | p.copyright { font-size: smaller }
165 |
166 | .center { text-align: center }
167 | .footnote { font-size: smaller; margin-left: 2em; }
168 |
169 | a img { border-width: 0; border-style: none }
170 |
171 | a:visited { color: navy }
172 | a:link { color: navy }
173 | a:hover { color: red; text-decoration: underline }
174 | a:active { color: red; text-decoration: underline }
175 |
176 | a {text-decoration: none}
177 | .navbar a:link {color: white}
178 | .navbar a:visited {color: yellow}
179 | .navbar a:active {color: red}
180 | .navbar a:hover {color: red}
181 |
182 | ul { list-style-type: square; }
183 | ul ul { list-style-type: disc; }
184 | ul ul ul { list-style-type: circle; }
185 | ul ul ul ul { list-style-type: disc; }
186 | li { margin-left: 0.5em; margin-top: 0.5em; }
187 |
188 | div dt
189 | {
190 | margin-left: 0;
191 | margin-top: 1em;
192 | margin-bottom: 0.5em;
193 | font-weight: bold;
194 | }
195 | div dd
196 | {
197 | margin-left: 2em;
198 | margin-bottom: 0.5em;
199 | }
200 |
201 |
202 | p,pre,ul,ol,blockquote,h2,h3,h4,h5,h6,dl,table {
203 | margin-left: 1em;
204 | margin-right: 1em;
205 | }
206 |
207 | p.subhead { font-weight: bold; margin-top: 2em; }
208 |
209 | .smaller { font-size: smaller }
210 | .bigger { font-size: 130% }
211 |
212 | td,th { padding: 0.2em }
213 |
214 | ul {
215 | margin: 0.5em 1.5em 0.5em 1.5em;
216 | padding: 0;
217 | }
218 |
219 | ol {
220 | margin: 0.5em 1.5em 0.5em 1.5em;
221 | padding: 0;
222 | }
223 |
224 | ul { list-style-type: square; }
225 | ul ul { list-style-type: disc; }
226 | ul ul ul { list-style-type: circle; }
227 | ul ul ul ul { list-style-type: disc; }
228 |
229 | ul li {
230 | list-style: square;
231 | margin: 0.1em 0em 0.6em 1.0em;
232 | padding: 0 0 0 0;
233 | line-height: 140%;
234 | }
235 |
236 | ol li {
237 | margin: 0.1em 0em 0.6em 1.5em;
238 | padding: 0 0 0 0px;
239 | line-height: 140%;
240 | list-style-type: decimal;
241 | }
242 |
243 | li ul li {
244 | list-style-type: disc;
245 | background: transparent;
246 | padding: 0 0 0 0;
247 | }
248 | li li ul li {
249 | list-style-type: circle;
250 | background: transparent;
251 | padding: 0 0 0 0;
252 | }
253 | li li li ul li {
254 | list-style-type: disc;
255 | background: transparent;
256 | padding: 0 0 0 0;
257 | }
258 |
259 | li ol li {
260 | list-style-type: decimal;
261 | }
262 |
263 |
264 | li li ol li {
265 | list-style-type: decimal;
266 | }
267 |
268 | /*
269 | setting class="outline on ol or ul makes it behave as an
270 | ouline list where blocklevel content in li elements is
271 | hidden by default and can be expanded or collapsed with
272 | mouse click. Set class="expand" on li to override default
273 | */
274 |
275 | ol.outline li:hover { cursor: pointer }
276 | ol.outline li.nofold:hover { cursor: default }
277 |
278 | ul.outline li:hover { cursor: pointer }
279 | ul.outline li.nofold:hover { cursor: default }
280 |
281 | ol.outline { list-style:decimal; }
282 | ol.outline ol { list-style-type:lower-alpha }
283 |
284 | ol.outline li.nofold {
285 | padding: 0 0 0 20px;
286 | background: transparent url(nofold-dim.gif) no-repeat 0px 0.5em;
287 | }
288 | ol.outline li.unfolded {
289 | padding: 0 0 0 20px;
290 | background: transparent url(fold-dim.gif) no-repeat 0px 0.5em;
291 | }
292 | ol.outline li.folded {
293 | padding: 0 0 0 20px;
294 | background: transparent url(unfold-dim.gif) no-repeat 0px 0.5em;
295 | }
296 | ol.outline li.unfolded:hover {
297 | padding: 0 0 0 20px;
298 | background: transparent url(fold.gif) no-repeat 0px 0.5em;
299 | }
300 | ol.outline li.folded:hover {
301 | padding: 0 0 0 20px;
302 | background: transparent url(unfold.gif) no-repeat 0px 0.5em;
303 | }
304 |
305 | ul.outline li.nofold {
306 | padding: 0 0 0 20px;
307 | background: transparent url(nofold-dim.gif) no-repeat 0px 0.5em;
308 | }
309 | ul.outline li.unfolded {
310 | padding: 0 0 0 20px;
311 | background: transparent url(fold-dim.gif) no-repeat 0px 0.5em;
312 | }
313 | ul.outline li.folded {
314 | padding: 0 0 0 20px;
315 | background: transparent url(unfold-dim.gif) no-repeat 0px 0.5em;
316 | }
317 | ul.outline li.unfolded:hover {
318 | padding: 0 0 0 20px;
319 | background: transparent url(fold.gif) no-repeat 0px 0.5em;
320 | }
321 | ul.outline li.folded:hover {
322 | padding: 0 0 0 20px;
323 | background: transparent url(unfold.gif) no-repeat 0px 0.5em;
324 | }
325 |
326 | /* for slides with class "title" in table of contents */
327 | a.titleslide { font-weight: bold; font-style: italic }
328 |
329 | .contrasts em {
330 | color: #642;
331 | }
332 |
333 | .eg {
334 | color: #555;
335 | font-style: italic;
336 | }
337 |
--------------------------------------------------------------------------------
/talk/slidy.js:
--------------------------------------------------------------------------------
1 | /* slidy.js
2 |
3 | Copyright (c) 2005-2009 W3C (MIT, ERCIM, Keio), All Rights Reserved.
4 | W3C liability, trademark, document use and software licensing
5 | rules apply, see:
6 |
7 | http://www.w3.org/Consortium/Legal/copyright-documents
8 | http://www.w3.org/Consortium/Legal/copyright-software
9 | */
10 |
11 | var ns_pos = (typeof window.pageYOffset!='undefined');
12 | var khtml = ((navigator.userAgent).indexOf("KHTML") >= 0 ? true : false);
13 | var opera = ((navigator.userAgent).indexOf("Opera") >= 0 ? true : false);
14 | var ie = (typeof document.all != "undefined" && !opera);
15 | var ie7 = (!ns_pos && navigator.userAgent.indexOf("MSIE 7") != -1);
16 | var ie8 = (!ns_pos && navigator.userAgent.indexOf("MSIE 8") != -1);
17 | var slidy_started = false;
18 |
19 | if (ie && !ie8)
20 | document.write("");
21 |
22 | // IE only event handlers to ensure all slides are printed
23 | // I don't yet know how to emulate these for other browsers
24 | if (typeof beforePrint != 'undefined')
25 | {
26 | window.onbeforeprint = beforePrint;
27 | window.onafterprint = afterPrint;
28 | }
29 |
30 | // to avoid a clash with other scripts or onload attribute on
31 | // we use something smarter than window.onload
32 | //window.onload = startup;
33 |
34 |
35 | if (ie)
36 | setTimeout(ieSlidyInit, 100);
37 | else if (document.addEventListener)
38 | document.addEventListener("DOMContentLoaded", startup, false);
39 |
40 | function ieSlidyInit()
41 | {
42 | if (//document.readyState == "interactive" ||
43 | document.readyState == "complete" ||
44 | document.readyState == "loaded")
45 | {
46 | startup();
47 | }
48 | else
49 | {
50 | setTimeout(ieSlidyInit, 100);
51 | }
52 | }
53 |
54 | setTimeout(hideSlides, 50);
55 |
56 | function hideSlides()
57 | {
58 | if (document.body)
59 | document.body.style.visibility = "hidden";
60 | else
61 | setTimeout(hideSlides, 50);
62 | }
63 |
64 | var slidenum = 0; // integer slide count: 0, 1, 2, ...
65 | var slides; // set to array of slide div's
66 | var slideNumElement; // element containing slide number
67 | var notes; // set to array of handout div's
68 | var backgrounds; // set to array of background div's
69 | var toolbar; // element containing toolbar
70 | var title; // document title
71 | var lastShown = null; // last incrementally shown item
72 | var eos = null; // span element for end of slide indicator
73 | var toc = null; // table of contents
74 | var outline = null; // outline element with the focus
75 | var selectedTextLen; // length of drag selection on document
76 |
77 | var viewAll = 0; // 1 to view all slides + handouts
78 | var wantToolbar = 1; // 0 if toolbar isn't wanted
79 | var mouseClickEnabled = true; // enables left click for next slide
80 | var scrollhack = 0; // IE work around for position: fixed
81 |
82 | var helpAnchor; // used for keyboard focus hack in showToolbar()
83 | var helpPage = "http://www.w3.org/Talks/Tools/Slidy/help.html";
84 | var helpText = "Navigate with mouse click, space bar, Cursor Left/Right, " +
85 | "or Pg Up and Pg Dn. Use S and B to change font size.";
86 |
87 | var sizeIndex = 0;
88 | var sizeAdjustment = 0;
89 | var sizes = new Array("10pt", "12pt", "14pt", "16pt", "18pt", "20pt",
90 | "22pt", "24pt", "26pt", "28pt", "30pt", "32pt");
91 | var okayForIncremental = incrementalElementList();
92 |
93 | // needed for efficient resizing
94 | var lastWidth = 0;
95 | var lastHeight = 0;
96 |
97 | // Needed for cross browser support for relative width/height on
98 | // object elements. The work around is to save width/height attributes
99 | // and then to recompute absolute width/height dimensions on resizing
100 | var objects;
101 |
102 | // updated to language specified by html file
103 | var lang = "en";
104 |
105 | //var localize = {};
106 |
107 | // for each language there is an associative array
108 | var strings_es = {
109 | "slide":"pág.",
110 | "help?":"Ayuda",
111 | "contents?":"Índice",
112 | "table of contents":"tabla de contenidos",
113 | "Table of Contents":"Tabla de Contenidos",
114 | "restart presentation":"Reiniciar presentación",
115 | "restart?":"Inicio"
116 | };
117 |
118 | strings_es[helpText] =
119 | "Utilice el ratón, barra espaciadora, teclas Izda/Dcha, " +
120 | "o Re pág y Av pág. Use S y B para cambiar el tamaño de fuente.";
121 |
122 | var strings_ca = {
123 | "slide":"pàg..",
124 | "help?":"Ajuda",
125 | "contents?":"Índex",
126 | "table of contents":"taula de continguts",
127 | "Table of Contents":"Taula de Continguts",
128 | "restart presentation":"Reiniciar presentació",
129 | "restart?":"Inici"
130 | };
131 |
132 | strings_ca[helpText] =
133 | "Utilitzi el ratolí, barra espaiadora, tecles Esq./Dta. " +
134 | "o Re pàg y Av pàg. Usi S i B per canviar grandària de font.";
135 |
136 | var strings_nl = {
137 | "slide":"pagina",
138 | "help?":"Help?",
139 | "contents?":"Inhoud?",
140 | "table of contents":"inhoudsopgave",
141 | "Table of Contents":"Inhoudsopgave",
142 | "restart presentation":"herstart presentatie",
143 | "restart?":"Herstart?"
144 | };
145 |
146 | strings_nl[helpText] =
147 | "Navigeer d.m.v. het muis, spatiebar, Links/Rechts toetsen, " +
148 | "of PgUp en PgDn. Gebruik S en B om de karaktergrootte te veranderen.";
149 |
150 | var strings_de = {
151 | "slide":"Seite",
152 | "help?":"Hilfe",
153 | "contents?":"Übersicht",
154 | "table of contents":"Inhaltsverzeichnis",
155 | "Table of Contents":"Inhaltsverzeichnis",
156 | "restart presentation":"Präsentation neu starten",
157 | "restart?":"Neustart"
158 | };
159 |
160 | strings_de[helpText] =
161 | "Benutzen Sie die Maus, Leerschlag, die Cursortasten links/rechts oder " +
162 | "Page up/Page Down zum Wechseln der Seiten und S und B für die Schriftgrösse.";
163 |
164 | var strings_pl = {
165 | "slide":"slajd",
166 | "help?":"pomoc?",
167 | "contents?":"spis treści?",
168 | "table of contents":"spis treści",
169 | "Table of Contents":"Spis Treści",
170 | "restart presentation":"Restartuj prezentację",
171 | "restart?":"restart?"
172 | };
173 |
174 | strings_pl[helpText] =
175 | "Zmieniaj slajdy klikając myszą, naciskając spację, strzałki lewo/prawo" +
176 | "lub PgUp / PgDn. Użyj klawiszy S i B, aby zmienić rozmiar czczionki.";
177 |
178 | var strings_fr = {
179 | "slide":"page",
180 | "help?":"Aide",
181 | "contents?":"Index",
182 | "table of contents":"table des matières",
183 | "Table of Contents":"Table des matières",
184 | "restart presentation":"Recommencer l'exposé",
185 | "restart?":"Début"
186 | };
187 |
188 | strings_fr[helpText] =
189 | "Naviguez avec la souris, la barre d'espace, les flèches " +
190 | "gauche/droite ou les touches Pg Up, Pg Dn. Utilisez " +
191 | "les touches S et B pour modifier la taille de la police.";
192 |
193 | var strings_hu = {
194 | "slide":"oldal",
195 | "help?":"segítség",
196 | "contents?":"tartalom",
197 | "table of contents":"tartalomjegyzék",
198 | "Table of Contents":"Tartalomjegyzék",
199 | "restart presentation":"bemutató újraindítása",
200 | "restart?":"újraindítás"
201 | };
202 |
203 | strings_hu[helpText] =
204 | "Az oldalak közti lépkedéshez kattintson az egérrel, vagy " +
205 | "használja a szóköz, a bal, vagy a jobb nyíl, illetve a Page Down, " +
206 | "Page Up billentyűket. Az S és a B billentyűkkel változtathatja " +
207 | "a szöveg méretét.";
208 |
209 | var strings_it = {
210 | "slide":"pag.",
211 | "help?":"Aiuto",
212 | "contents?":"Indice",
213 | "table of contents":"indice",
214 | "Table of Contents":"Indice",
215 | "restart presentation":"Ricominciare la presentazione",
216 | "restart?":"Inizio"
217 | };
218 |
219 | strings_it[helpText] =
220 | "Navigare con mouse, barra spazio, frecce sinistra/destra o " +
221 | "PgUp e PgDn. Usare S e B per cambiare la dimensione dei caratteri.";
222 |
223 | var strings_el = {
224 | "slide":"σελίδα",
225 | "help?":"βοήθεια;",
226 | "contents?":"περιεχόμενα;",
227 | "table of contents":"πίνακας περιεχομένων",
228 | "Table of Contents":"Πίνακας Περιεχομένων",
229 | "restart presentation":"επανεκκίνηση παρουσίασης",
230 | "restart?":"επανεκκίνηση;"
231 | };
232 |
233 | strings_el[helpText] =
234 | "Πλοηγηθείτε με το κλίκ του ποντικιού, το space, τα βέλη αριστερά/δεξιά, " +
235 | "ή Page Up και Page Down. Χρησιμοποιήστε τα πλήκτρα S και B για να αλλάξετε " +
236 | "το μέγεθος της γραμματοσειράς.";
237 |
238 | var strings_ja = {
239 | "slide":"スライド",
240 | "help?":"ヘルプ",
241 | "contents?":"目次",
242 | "table of contents":"目次を表示",
243 | "Table of Contents":"目次",
244 | "restart presentation":"最初から再生",
245 | "restart?":"最初から"
246 | };
247 |
248 | strings_ja[helpText] =
249 | "マウス左クリック ・ スペース ・ 左右キー " +
250 | "または Page Up ・ Page Downで操作, S ・ Bでフォントサイズ変更";
251 |
252 | var strings_zh = {
253 | "slide":"幻灯片",
254 | "help?":"帮助?",
255 | "contents?":"内容?",
256 | "table of contents":"目录",
257 | "Table of Contents":"目录",
258 | "restart presentation":"重新启动展示",
259 | "restart?":"重新启动?"
260 | };
261 |
262 | strings_zh[helpText] =
263 | "用鼠标点击, 空格条, 左右箭头, Pg Up 和 Pg Dn 导航. " +
264 | "用 S, B 改变字体大小.";
265 |
266 | var strings_ru = {
267 | "slide":"слайд",
268 | "help?":"помощь?",
269 | "contents?":"содержание?",
270 | "table of contents":"оглавление",
271 | "Table of Contents":"Оглавление",
272 | "restart presentation":"перезапустить презентацию",
273 | "restart?":"перезапуск?"
274 | };
275 |
276 | strings_ru[helpText] =
277 | "Перемещайтесь кликая мышкой, используя клавишу пробел, стрелки" +
278 | "влево/вправо или Pg Up и Pg Dn. Клавиши S и B меняют размер шрифта.";
279 |
280 |
281 | // each such language array is declared in the localize array
282 | // used indirectly as in help.innerHTML = "help".localize();
283 | var localize = {
284 | "es":strings_es,
285 | "ca":strings_ca,
286 | "nl":strings_nl,
287 | "de":strings_de,
288 | "pl":strings_pl,
289 | "fr":strings_fr,
290 | "hu":strings_hu,
291 | "it":strings_it,
292 | "el":strings_el,
293 | "jp":strings_ja,
294 | "zh":strings_zh,
295 | "ru":strings_ru
296 | };
297 |
298 | /* general initialization */
299 | function startup()
300 | {
301 | if (slidy_started)
302 | {
303 | alert("already started");
304 | return;
305 | }
306 | slidy_started = true;
307 |
308 | // find human language from html element
309 | // for use in localizing strings
310 | lang = document.body.parentNode.getAttribute("lang");
311 |
312 | if (!lang)
313 | lang = document.body.parentNode.getAttribute("xml:lang");
314 |
315 | if (!lang)
316 | lang = "en";
317 |
318 | document.body.style.visibility = "visible";
319 | title = document.title;
320 | toolbar = addToolbar();
321 | wrapImplicitSlides();
322 | slides = collectSlides();
323 | notes = collectNotes();
324 | objects = document.body.getElementsByTagName("object");
325 | backgrounds = collectBackgrounds();
326 | patchAnchors();
327 |
328 | slidenum = findSlideNumber(location.href);
329 | window.offscreenbuffering = true;
330 | sizeAdjustment = findSizeAdjust();
331 | hideImageToolbar(); // suppress IE image toolbar popup
332 | initOutliner(); // activate fold/unfold support
333 |
334 | if (slides.length > 0)
335 | {
336 | var slide = slides[slidenum];
337 | slide.style.position = "absolute";
338 |
339 | if (slidenum > 0)
340 | {
341 | setVisibilityAllIncremental("visible");
342 | lastShown = previousIncrementalItem(null);
343 | setEosStatus(true);
344 | }
345 | else
346 | {
347 | lastShown = null;
348 | setVisibilityAllIncremental("hidden");
349 | setEosStatus(!nextIncrementalItem(lastShown));
350 | }
351 |
352 | setLocation();
353 | }
354 |
355 | toc = tableOfContents();
356 | hideTableOfContents();
357 |
358 | // bind event handlers
359 | document.onclick = mouseButtonClick;
360 | document.onmouseup = mouseButtonUp;
361 | document.onkeydown = keyDown;
362 | window.onresize = resized;
363 | window.onscroll = scrolled;
364 | window.onunload = unloaded;
365 | singleSlideView();
366 |
367 |
368 | setLocation();
369 | resized();
370 |
371 | if (ie7)
372 | setTimeout("ieHack()", 100);
373 |
374 | showToolbar();
375 | setInterval("checkLocation()", 200); // for back button detection
376 | }
377 |
378 | // add localize method to all strings for use
379 | // as in help.innerHTML = "help".localize();
380 | String.prototype.localize = function()
381 | {
382 | if (this == "")
383 | return this;
384 |
385 | // try full language code, e.g. en-US
386 | var s, lookup = localize[lang];
387 |
388 | if (lookup)
389 | {
390 | s = lookup[this];
391 |
392 | if (s)
393 | return s;
394 | }
395 |
396 | // try en if undefined for en-US
397 | var lg = lang.split("-");
398 |
399 | if (lg.length > 1)
400 | {
401 | lookup = localize[lg[0]];
402 |
403 | if (lookup)
404 | {
405 | s = lookup[this];
406 |
407 | if (s)
408 | return s;
409 | }
410 | }
411 |
412 | // otherwise string as is
413 | return this;
414 | }
415 |
416 | // suppress IE's image toolbar pop up
417 | function hideImageToolbar()
418 | {
419 | if (!ns_pos)
420 | {
421 | var images = document.getElementsByTagName("IMG");
422 |
423 | for (var i = 0; i < images.length; ++i)
424 | images[i].setAttribute("galleryimg", "no");
425 | }
426 | }
427 |
428 | // hack to persuade IE to compute correct document height
429 | // as needed for simulating fixed positioning of toolbar
430 | function ieHack()
431 | {
432 | window.resizeBy(0,-1);
433 | window.resizeBy(0, 1);
434 | }
435 |
436 | function unloaded(e)
437 | {
438 | //alert("unloaded");
439 | }
440 |
441 | // Firefox reload SVG bug work around
442 | function reload(e)
443 | {
444 | if (!e)
445 | var e = window.event;
446 |
447 | hideBackgrounds();
448 | setTimeout("document.reload();", 100);
449 |
450 | stopPropagation(e);
451 | e.cancel = true;
452 | e.returnValue = false;
453 |
454 | return false;
455 | }
456 |
457 | // Safari and Konqueror don't yet support getComputedStyle()
458 | // and they always reload page when location.href is updated
459 | function isKHTML()
460 | {
461 | var agent = navigator.userAgent;
462 | return (agent.indexOf("KHTML") >= 0 ? true : false);
463 | }
464 |
465 | function resized()
466 | {
467 | var width = 0;
468 |
469 | if ( typeof( window.innerWidth ) == 'number' )
470 | width = window.innerWidth; // Non IE browser
471 | else if (document.documentElement && document.documentElement.clientWidth)
472 | width = document.documentElement.clientWidth; // IE6
473 | else if (document.body && document.body.clientWidth)
474 | width = document.body.clientWidth; // IE4
475 |
476 | var height = 0;
477 |
478 | if ( typeof( window.innerHeight ) == 'number' )
479 | height = window.innerHeight; // Non IE browser
480 | else if (document.documentElement && document.documentElement.clientHeight)
481 | height = document.documentElement.clientHeight; // IE6
482 | else if (document.body && document.body.clientHeight)
483 | height = document.body.clientHeight; // IE4
484 |
485 | if (height && (width/height > 1.05*1024/768))
486 | {
487 | width = height * 1024.0/768;
488 | }
489 |
490 | // IE fires onresize even when only font size is changed!
491 | // so we do a check to avoid blocking < and > actions
492 | if (width != lastWidth || height != lastHeight)
493 | {
494 | if (width >= 1100)
495 | sizeIndex = 5; // 4
496 | else if (width >= 1000)
497 | sizeIndex = 4; // 3
498 | else if (width >= 800)
499 | sizeIndex = 3; // 2
500 | else if (width >= 600)
501 | sizeIndex = 2; // 1
502 | else if (width)
503 | sizeIndex = 0;
504 |
505 | // add in font size adjustment from meta element e.g.
506 | //
507 | // useful when slides have too much content ;-)
508 |
509 | if (0 <= sizeIndex + sizeAdjustment &&
510 | sizeIndex + sizeAdjustment < sizes.length)
511 | sizeIndex = sizeIndex + sizeAdjustment;
512 |
513 | // enables cross browser use of relative width/height
514 | // on object elements for use with SVG and Flash media
515 | adjustObjectDimensions(width, height);
516 |
517 | document.body.style.fontSize = sizes[sizeIndex];
518 |
519 | lastWidth = width;
520 | lastHeight = height;
521 |
522 | // force reflow to work around Mozilla bug
523 | //if (ns_pos)
524 | {
525 | var slide = slides[slidenum];
526 | hideSlide(slide);
527 | showSlide(slide);
528 | }
529 |
530 | // force correct positioning of toolbar
531 | refreshToolbar(200);
532 | }
533 | }
534 |
535 | function scrolled()
536 | {
537 | if (toolbar && !ns_pos && !ie7)
538 | {
539 | hackoffset = scrollXOffset();
540 | // hide toolbar
541 | toolbar.style.display = "none";
542 |
543 | // make it reappear later
544 | if (scrollhack == 0 && !viewAll)
545 | {
546 | setTimeout(showToolbar, 1000);
547 | scrollhack = 1;
548 | }
549 | }
550 | }
551 |
552 | // used to ensure IE refreshes toolbar in correct position
553 | function refreshToolbar(interval)
554 | {
555 | if (!ns_pos && !ie7)
556 | {
557 | hideToolbar();
558 | setTimeout(showToolbar, interval);
559 | }
560 | }
561 |
562 | // restores toolbar after short delay
563 | function showToolbar()
564 | {
565 | if (wantToolbar)
566 | {
567 | if (!ns_pos)
568 | {
569 | // adjust position to allow for scrolling
570 | var xoffset = scrollXOffset();
571 | toolbar.style.left = xoffset;
572 | toolbar.style.right = xoffset;
573 |
574 | // determine vertical scroll offset
575 | //var yoffset = scrollYOffset();
576 |
577 | // bottom is doc height - window height - scroll offset
578 | //var bottom = documentHeight() - lastHeight - yoffset
579 |
580 | //if (yoffset > 0 || documentHeight() > lastHeight)
581 | // bottom += 16; // allow for height of scrollbar
582 |
583 | toolbar.style.bottom = 0; //bottom;
584 | }
585 |
586 | toolbar.style.display = "block";
587 | toolbar.style.visibility = "visible";
588 | }
589 |
590 | scrollhack = 0;
591 |
592 |
593 | // set the keyboard focus to the help link on the
594 | // toolbar to ensure that document has the focus
595 | // IE doesn't always work with window.focus()
596 | // and this hack has benefit of Enter for help
597 |
598 | try
599 | {
600 | if (!opera)
601 | helpAnchor.focus();
602 | }
603 | catch (e)
604 | {
605 | }
606 | }
607 |
608 | function hideToolbar()
609 | {
610 | toolbar.style.display = "none";
611 | toolbar.style.visibility = "hidden";
612 | window.focus();
613 | }
614 |
615 | // invoked via F key
616 | function toggleToolbar()
617 | {
618 | if (!viewAll)
619 | {
620 | if (toolbar.style.display == "none")
621 | {
622 | toolbar.style.display = "block";
623 | toolbar.style.visibility = "visible";
624 | wantToolbar = 1;
625 | }
626 | else
627 | {
628 | toolbar.style.display = "none";
629 | toolbar.style.visibility = "hidden";
630 | wantToolbar = 0;
631 | }
632 | }
633 | }
634 |
635 | function scrollXOffset()
636 | {
637 | if (window.pageXOffset)
638 | return self.pageXOffset;
639 |
640 | if (document.documentElement &&
641 | document.documentElement.scrollLeft)
642 | return document.documentElement.scrollLeft;
643 |
644 | if (document.body)
645 | return document.body.scrollLeft;
646 |
647 | return 0;
648 | }
649 |
650 |
651 | function scrollYOffset()
652 | {
653 | if (window.pageYOffset)
654 | return self.pageYOffset;
655 |
656 | if (document.documentElement &&
657 | document.documentElement.scrollTop)
658 | return document.documentElement.scrollTop;
659 |
660 | if (document.body)
661 | return document.body.scrollTop;
662 |
663 | return 0;
664 | }
665 |
666 | // looking for a way to determine height of slide content
667 | // the slide itself is set to the height of the window
668 | function optimizeFontSize()
669 | {
670 | var slide = slides[slidenum];
671 |
672 | //var dh = documentHeight(); //getDocHeight(document);
673 | var dh = slide.scrollHeight;
674 | var wh = getWindowHeight();
675 | var u = 100 * dh / wh;
676 |
677 | alert("window utilization = " + u + "% (doc "
678 | + dh + " win " + wh + ")");
679 | }
680 |
681 | function getDocHeight(doc) // from document object
682 | {
683 | if (!doc)
684 | doc = document;
685 |
686 | if (doc && doc.body && doc.body.offsetHeight)
687 | return doc.body.offsetHeight; // ns/gecko syntax
688 |
689 | if (doc && doc.body && doc.body.scrollHeight)
690 | return doc.body.scrollHeight;
691 |
692 | alert("couldn't determine document height");
693 | }
694 |
695 | function getWindowHeight()
696 | {
697 | if ( typeof( window.innerHeight ) == 'number' )
698 | return window.innerHeight; // Non IE browser
699 |
700 | if (document.documentElement && document.documentElement.clientHeight)
701 | return document.documentElement.clientHeight; // IE6
702 |
703 | if (document.body && document.body.clientHeight)
704 | return document.body.clientHeight; // IE4
705 | }
706 |
707 |
708 |
709 | function documentHeight()
710 | {
711 | var sh, oh;
712 |
713 | sh = document.body.scrollHeight;
714 | oh = document.body.offsetHeight;
715 |
716 | if (sh && oh)
717 | {
718 | return (sh > oh ? sh : oh);
719 | }
720 |
721 | // no idea!
722 | return 0;
723 | }
724 |
725 | function smaller()
726 | {
727 | if (sizeIndex > 0)
728 | {
729 | --sizeIndex;
730 | }
731 |
732 | toolbar.style.display = "none";
733 | document.body.style.fontSize = sizes[sizeIndex];
734 | var slide = slides[slidenum];
735 | hideSlide(slide);
736 | showSlide(slide);
737 | setTimeout(showToolbar, 300);
738 | }
739 |
740 | function bigger()
741 | {
742 | if (sizeIndex < sizes.length - 1)
743 | {
744 | ++sizeIndex;
745 | }
746 |
747 | toolbar.style.display = "none";
748 | document.body.style.fontSize = sizes[sizeIndex];
749 | var slide = slides[slidenum];
750 | hideSlide(slide);
751 | showSlide(slide);
752 | setTimeout(showToolbar, 300);
753 | }
754 |
755 | // enables cross browser use of relative width/height
756 | // on object elements for use with SVG and Flash media
757 | // with thanks to Ivan Herman for the suggestion
758 | function adjustObjectDimensions(width, height)
759 | {
760 | for( var i = 0; i < objects.length; i++ )
761 | {
762 | var obj = objects[i];
763 | var mimeType = obj.getAttribute("type");
764 |
765 | if (mimeType == "image/svg+xml" || mimeType == "application/x-shockwave-flash")
766 | {
767 | if ( !obj.initialWidth )
768 | obj.initialWidth = obj.getAttribute("width");
769 |
770 | if ( !obj.initialHeight )
771 | obj.initialHeight = obj.getAttribute("height");
772 |
773 | if ( obj.initialWidth && obj.initialWidth.charAt(obj.initialWidth.length-1) == "%" )
774 | {
775 | var w = parseInt(obj.initialWidth.slice(0, obj.initialWidth.length-1));
776 | var newW = width * (w/100.0);
777 | obj.setAttribute("width",newW);
778 | }
779 |
780 | if ( obj.initialHeight && obj.initialHeight.charAt(obj.initialHeight.length-1) == "%" )
781 | {
782 | var h = parseInt(obj.initialHeight.slice(0, obj.initialHeight.length-1));
783 | var newH = height * (h/100.0);
784 | obj.setAttribute("height", newH);
785 | }
786 | }
787 | }
788 | }
789 |
790 | function cancel(event)
791 | {
792 | if (event)
793 | {
794 | event.cancel = true;
795 | event.returnValue = false;
796 |
797 | if (event.preventDefault)
798 | event.preventDefault();
799 | }
800 |
801 | return false;
802 | }
803 |
804 | // See e.g. http://www.quirksmode.org/js/events/keys.html for keycodes
805 | function keyDown(event)
806 | {
807 | var key;
808 |
809 | if (!event)
810 | var event = window.event;
811 |
812 | // kludge around NS/IE differences
813 | if (window.event)
814 | key = window.event.keyCode;
815 | else if (event.which)
816 | key = event.which;
817 | else
818 | return true; // Yikes! unknown browser
819 |
820 | // ignore event if key value is zero
821 | // as for alt on Opera and Konqueror
822 | if (!key)
823 | return true;
824 |
825 | // check for concurrent control/command/alt key
826 | // but are these only present on mouse events?
827 |
828 | if (event.ctrlKey || event.altKey || event.metaKey)
829 | return true;
830 |
831 | // dismiss table of contents if visible
832 | if (isShownToc() && key != 9 && key != 16 && key != 38 && key != 40)
833 | {
834 | hideTableOfContents();
835 |
836 | if (key == 27 || key == 84 || key == 67)
837 | return cancel(event);
838 | }
839 |
840 | if (key == 34) // Page Down
841 | {
842 | if (viewAll)
843 | return true;
844 |
845 | nextSlide(false);
846 | return cancel(event);
847 | }
848 | else if (key == 33) // Page Up
849 | {
850 | if (viewAll)
851 | return true;
852 |
853 | previousSlide(false);
854 | return cancel(event);
855 | }
856 | else if (key == 32) // space bar
857 | {
858 | nextSlide(true);
859 | return cancel(event);
860 | }
861 | else if (key == 37) // Left arrow
862 | {
863 | previousSlide(!event.shiftKey);
864 | return cancel(event);
865 | }
866 | else if (key == 36) // Home
867 | {
868 | firstSlide();
869 | return cancel(event);
870 | }
871 | else if (key == 35) // End
872 | {
873 | lastSlide();
874 | return cancel(event);
875 | }
876 | else if (key == 39) // Right arrow
877 | {
878 | nextSlide(!event.shiftKey);
879 | return cancel(event);
880 | }
881 | else if (key == 13) // Enter
882 | {
883 | if (outline)
884 | {
885 | if (outline.visible)
886 | fold(outline);
887 | else
888 | unfold(outline);
889 |
890 | return cancel(event);
891 | }
892 | }
893 | else if (key == 188) // < for smaller fonts
894 | {
895 | smaller();
896 | return cancel(event);
897 | }
898 | else if (key == 190) // > for larger fonts
899 | {
900 | bigger();
901 | return cancel(event);
902 | }
903 | else if (key == 189 || key == 109) // - for smaller fonts
904 | {
905 | smaller();
906 | return cancel(event);
907 | }
908 | else if (key == 187 || key == 191 || key == 107) // = + for larger fonts
909 | {
910 | bigger();
911 | return cancel(event);
912 | }
913 | else if (key == 83) // S for smaller fonts
914 | {
915 | smaller();
916 | return cancel(event);
917 | }
918 | else if (key == 66) // B for larger fonts
919 | {
920 | bigger();
921 | return cancel(event);
922 | }
923 | else if (key == 90) // Z for last slide
924 | {
925 | lastSlide();
926 | return cancel(event);
927 | }
928 | else if (key == 70) // F for toggle toolbar
929 | {
930 | toggleToolbar();
931 | return cancel(event);
932 | }
933 | else if (key == 65) // A for toggle view single/all slides
934 | {
935 | toggleView();
936 | return cancel(event);
937 | }
938 | else if (key == 75) // toggle action of left click for next page
939 | {
940 | mouseClickEnabled = !mouseClickEnabled;
941 | alert((mouseClickEnabled ? "enabled" : "disabled") + " mouse click advance");
942 | return cancel(event);
943 | }
944 | else if (key == 84 || key == 67) // T or C for table of contents
945 | {
946 | if (toc)
947 | showTableOfContents();
948 |
949 | return cancel(event);
950 | }
951 | else if (key == 72) // H for help
952 | {
953 | window.location = helpPage;
954 | return cancel(event);
955 | }
956 |
957 | //else if (key == 93) // Windows menu key
958 | //alert("lastShown is " + lastShown);
959 | //else alert("key code is "+ key);
960 |
961 |
962 | return true;
963 | }
964 |
965 | // make note of length of selected text
966 | // as this evaluates to zero in click event
967 | function mouseButtonUp(e)
968 | {
969 | selectedTextLen = getSelectedText().length;
970 | }
971 |
972 | // right mouse button click is reserved for context menus
973 | // it is more reliable to detect rightclick than leftclick
974 | function mouseButtonClick(e)
975 | {
976 | var rightclick = false;
977 | var leftclick = false;
978 | var middleclick = false;
979 | var target;
980 |
981 | if (!e)
982 | var e = window.event;
983 |
984 | if (e.target)
985 | target = e.target;
986 | else if (e.srcElement)
987 | target = e.srcElement;
988 |
989 | // work around Safari bug
990 | if (target.nodeType == 3)
991 | target = target.parentNode;
992 |
993 | if (e.which) // all browsers except IE
994 | {
995 | leftclick = (e.which == 1);
996 | middleclick = (e.which == 2);
997 | rightclick = (e.which == 3);
998 | }
999 | else if (e.button)
1000 | {
1001 | // Konqueror gives 1 for left, 4 for middle
1002 | // IE6 gives 0 for left and not 1 as I expected
1003 |
1004 | if (e.button == 4)
1005 | middleclick = true;
1006 |
1007 | // all browsers agree on 2 for right button
1008 | rightclick = (e.button == 2);
1009 | }
1010 | else leftclick = true;
1011 |
1012 | //alert("selected text length = "+selectedTextLen);
1013 |
1014 | if (selectedTextLen > 0)
1015 | {
1016 | stopPropagation(e);
1017 | e.cancel = true;
1018 | e.returnValue = false;
1019 | return false;
1020 | }
1021 |
1022 | // dismiss table of contents
1023 | hideTableOfContents();
1024 |
1025 | // check if target is something that probably want's clicks
1026 | // e.g. embed, object, input, textarea, select, option
1027 |
1028 | if (mouseClickEnabled && leftclick &&
1029 | target.nodeName != "EMBED" &&
1030 | target.nodeName != "OBJECT" &&
1031 | target.nodeName != "VIDEO" &&
1032 | target.nodeName != "INPUT" &&
1033 | target.nodeName != "TEXTAREA" &&
1034 | target.nodeName != "SELECT" &&
1035 | target.nodeName != "OPTION")
1036 | {
1037 | nextSlide(true);
1038 | stopPropagation(e);
1039 | e.cancel = true;
1040 | e.returnValue = false;
1041 | }
1042 | }
1043 |
1044 | function previousSlide(incremental)
1045 | {
1046 | if (!viewAll)
1047 | {
1048 | var slide;
1049 |
1050 | if ((incremental || slidenum == 0) && lastShown != null)
1051 | {
1052 | lastShown = hidePreviousItem(lastShown);
1053 | setEosStatus(false);
1054 | }
1055 | else if (slidenum > 0)
1056 | {
1057 | slide = slides[slidenum];
1058 | hideSlide(slide);
1059 |
1060 | slidenum = slidenum - 1;
1061 | slide = slides[slidenum];
1062 | setVisibilityAllIncremental("visible");
1063 | lastShown = previousIncrementalItem(null);
1064 | setEosStatus(true);
1065 | showSlide(slide);
1066 | }
1067 |
1068 | setLocation();
1069 |
1070 | if (!ns_pos)
1071 | refreshToolbar(200);
1072 | }
1073 | }
1074 |
1075 | function nextSlide(incremental)
1076 | {
1077 | if (!viewAll)
1078 | {
1079 | var slide, last = lastShown;
1080 |
1081 | if (incremental || slidenum == slides.length - 1)
1082 | lastShown = revealNextItem(lastShown);
1083 |
1084 | if ((!incremental || lastShown == null) && slidenum < slides.length - 1)
1085 | {
1086 | slide = slides[slidenum];
1087 | hideSlide(slide);
1088 |
1089 | slidenum = slidenum + 1;
1090 | slide = slides[slidenum];
1091 | lastShown = null;
1092 | setVisibilityAllIncremental("hidden");
1093 | showSlide(slide);
1094 | }
1095 | else if (!lastShown)
1096 | {
1097 | if (last && incremental)
1098 | lastShown = last;
1099 | }
1100 |
1101 | setLocation();
1102 |
1103 | setEosStatus(!nextIncrementalItem(lastShown));
1104 |
1105 | if (!ns_pos)
1106 | refreshToolbar(200);
1107 | }
1108 | }
1109 |
1110 | // to first slide with nothing revealed
1111 | // i.e. state at start of presentation
1112 | function firstSlide()
1113 | {
1114 | if (!viewAll)
1115 | {
1116 | var slide;
1117 |
1118 | if (slidenum != 0)
1119 | {
1120 | slide = slides[slidenum];
1121 | hideSlide(slide);
1122 |
1123 | slidenum = 0;
1124 | slide = slides[slidenum];
1125 | lastShown = null;
1126 | setVisibilityAllIncremental("hidden");
1127 | showSlide(slide);
1128 | }
1129 |
1130 | setEosStatus(!nextIncrementalItem(lastShown));
1131 | setLocation();
1132 | }
1133 | }
1134 |
1135 |
1136 | // to last slide with everything revealed
1137 | // i.e. state at end of presentation
1138 | function lastSlide()
1139 | {
1140 | if (!viewAll)
1141 | {
1142 | var slide;
1143 |
1144 | lastShown = null; //revealNextItem(lastShown);
1145 |
1146 | if (lastShown == null && slidenum < slides.length - 1)
1147 | {
1148 | slide = slides[slidenum];
1149 | hideSlide(slide);
1150 | slidenum = slides.length - 1;
1151 | slide = slides[slidenum];
1152 | setVisibilityAllIncremental("visible");
1153 | lastShown = previousIncrementalItem(null);
1154 |
1155 | showSlide(slide);
1156 | }
1157 | else
1158 | {
1159 | setVisibilityAllIncremental("visible");
1160 | lastShown = previousIncrementalItem(null);
1161 | }
1162 |
1163 | setEosStatus(true);
1164 | setLocation();
1165 | }
1166 | }
1167 |
1168 | // first slide is 0
1169 | function gotoSlide(num)
1170 | {
1171 | //alert("going to slide " + (num+1));
1172 | var slide = slides[slidenum];
1173 | hideSlide(slide);
1174 | slidenum = num;
1175 | slide = slides[slidenum];
1176 | lastShown = null;
1177 | setVisibilityAllIncremental("hidden");
1178 | setEosStatus(!nextIncrementalItem(lastShown));
1179 | document.title = title + " (" + (slidenum+1) + ")";
1180 | showSlide(slide);
1181 | showSlideNumber();
1182 | }
1183 |
1184 | function setEosStatus(state)
1185 | {
1186 | if (eos)
1187 | eos.style.color = (state ? "rgb(240,240,240)" : "red");
1188 | }
1189 |
1190 | function showSlide(slide)
1191 | {
1192 | syncBackground(slide);
1193 | window.scrollTo(0,0);
1194 | slide.style.visibility = "visible";
1195 | slide.style.display = "block";
1196 | }
1197 |
1198 | function hideSlide(slide)
1199 | {
1200 | slide.style.visibility = "hidden";
1201 | slide.style.display = "none";
1202 | }
1203 |
1204 | function beforePrint()
1205 | {
1206 | showAllSlides();
1207 | hideToolbar();
1208 | }
1209 |
1210 | function afterPrint()
1211 | {
1212 | if (!viewAll)
1213 | {
1214 | singleSlideView();
1215 | showToolbar();
1216 | }
1217 | }
1218 |
1219 | function printSlides()
1220 | {
1221 | beforePrint();
1222 | window.print();
1223 | afterPrint();
1224 | }
1225 |
1226 | function toggleView()
1227 | {
1228 | if (viewAll)
1229 | {
1230 | singleSlideView();
1231 | showToolbar();
1232 | viewAll = 0;
1233 | }
1234 | else
1235 | {
1236 | showAllSlides();
1237 | hideToolbar();
1238 | viewAll = 1;
1239 | }
1240 | }
1241 |
1242 | // prepare for printing
1243 | function showAllSlides()
1244 | {
1245 | var slide;
1246 |
1247 | for (var i = 0; i < slides.length; ++i)
1248 | {
1249 | slide = slides[i];
1250 |
1251 | slide.style.position = "relative";
1252 | slide.style.borderTopStyle = "solid";
1253 | slide.style.borderTopWidth = "thin";
1254 | slide.style.borderTopColor = "black";
1255 |
1256 | try {
1257 | if (i == 0)
1258 | slide.style.pageBreakBefore = "avoid";
1259 | else
1260 | slide.style.pageBreakBefore = "always";
1261 | }
1262 | catch (e)
1263 | {
1264 | //do nothing
1265 | }
1266 |
1267 | setVisibilityAllIncremental("visible");
1268 | showSlide(slide);
1269 | }
1270 |
1271 | var note;
1272 |
1273 | for (var i = 0; i < notes.length; ++i)
1274 | {
1275 | showSlide(notes[i]);
1276 | }
1277 |
1278 | // no easy way to render background under each slide
1279 | // without duplicating the background divs for each slide
1280 | // therefore hide backgrounds to avoid messing up slides
1281 | hideBackgrounds();
1282 | }
1283 |
1284 | // restore after printing
1285 | function singleSlideView()
1286 | {
1287 | var slide;
1288 |
1289 | for (var i = 0; i < slides.length; ++i)
1290 | {
1291 | slide = slides[i];
1292 |
1293 | slide.style.position = "absolute";
1294 |
1295 | if (i == slidenum)
1296 | {
1297 | slide.style.borderStyle = "none";
1298 | showSlide(slide);
1299 | }
1300 | else
1301 | {
1302 | slide.style.borderStyle = "none";
1303 | hideSlide(slide);
1304 | }
1305 | }
1306 |
1307 | setVisibilityAllIncremental("visible");
1308 | lastShown = previousIncrementalItem(null);
1309 |
1310 | var note;
1311 |
1312 | for (var i = 0; i < notes.length; ++i)
1313 | {
1314 | hideSlide(notes[i]);
1315 | }
1316 | }
1317 |
1318 | // the string str is a whitespace separated list of tokens
1319 | // test if str contains a particular token, e.g. "slide"
1320 | function hasToken(str, token)
1321 | {
1322 | if (str)
1323 | {
1324 | // define pattern as regular expression
1325 | var pattern = /\w+/g;
1326 |
1327 | // check for matches
1328 | // place result in array
1329 | var result = str.match(pattern);
1330 |
1331 | // now check if desired token is present
1332 | for (var i = 0; i < result.length; i++)
1333 | {
1334 | if (result[i] == token)
1335 | return true;
1336 | }
1337 | }
1338 |
1339 | return false;
1340 | }
1341 |
1342 | function getClassList(element)
1343 | {
1344 | if (typeof element.className != 'undefined')
1345 | return element.className;
1346 |
1347 | var clsname = (ns_pos||ie8) ? "class" : "className";
1348 | return element.getAttribute(clsname);
1349 | }
1350 |
1351 | function hasClass(element, name)
1352 | {
1353 | var regexp = new RegExp("(^| )" + name + "\W*");
1354 |
1355 | if (typeof element.className != 'undefined')
1356 | return regexp.test(element.className);
1357 |
1358 | var clsname = (ns_pos||ie8) ? "class" : "className";
1359 | return regexp.test(element.getAttribute(clsname));
1360 | }
1361 |
1362 | function removeClass(element, name)
1363 | {
1364 | var regexp = new RegExp("(^| )" + name + "\W*");
1365 | var clsval = "";
1366 |
1367 | if (typeof element.className != 'undefined')
1368 | {
1369 | clsval = element.className;
1370 |
1371 | if (clsval)
1372 | {
1373 | clsval = clsval.replace(regexp, "");
1374 | element.className = clsval;
1375 | }
1376 | }
1377 | else
1378 | {
1379 | var clsname = (ns_pos||ie8) ? "class" : "className";
1380 | clsval = element.getAttribute(clsname);
1381 |
1382 | if (clsval)
1383 | {
1384 | clsval = clsval.replace(regexp, "");
1385 | element.setAttribute(clsname, clsval);
1386 | }
1387 | }
1388 | }
1389 |
1390 | function addClass(element, name)
1391 | {
1392 | if (!hasClass(element, name))
1393 | {
1394 | if (typeof element.className != 'undefined')
1395 | element.className += " " + name;
1396 | else
1397 | {
1398 | var clsname = (ns_pos||ie8) ? "class" : "className";
1399 | var clsval = element.getAttribute(clsname);
1400 | clsval = clsval ? clsval + " " + name : name;
1401 | element.setAttribute(clsname, clsval);
1402 | }
1403 | }
1404 | }
1405 |
1406 | // wysiwyg editors make it hard to use div elements
1407 | // e.g. amaya loses the div when you copy and paste
1408 | // this function wraps div elements around implicit
1409 | // slides which start with an h1 element and continue
1410 | // up to the next heading or div element
1411 | function wrapImplicitSlides()
1412 | {
1413 | var i, heading, node, next, div;
1414 | var headings = document.getElementsByTagName("h1");
1415 |
1416 | if (!headings)
1417 | return;
1418 |
1419 | for (i = 0; i < headings.length; ++i)
1420 | {
1421 | heading = headings[i];
1422 |
1423 | if (heading.parentNode != document.body)
1424 | continue;
1425 |
1426 | node = heading.nextSibling;
1427 |
1428 | div = document.createElement("div");
1429 | addClass(div, "slide");
1430 | document.body.replaceChild(div, heading);
1431 | div.appendChild(heading);
1432 |
1433 | while (node)
1434 | {
1435 | if (node.nodeType == 1 && // an element
1436 | (node.nodeName == "H1" ||
1437 | node.nodeName == "h1" ||
1438 | node.nodeName == "DIV" ||
1439 | node.nodeName == "div"))
1440 | break;
1441 |
1442 | next = node.nextSibling;
1443 | node = document.body.removeChild(node);
1444 | div.appendChild(node);
1445 | node = next;
1446 | }
1447 | }
1448 | }
1449 |
1450 | // return new array of all slides
1451 | function collectSlides()
1452 | {
1453 | var slides = new Array();
1454 | var divs = document.body.getElementsByTagName("div");
1455 |
1456 | for (var i = 0; i < divs.length; ++i)
1457 | {
1458 | div = divs.item(i);
1459 |
1460 | if (hasClass(div, "slide"))
1461 | {
1462 | // add slide to collection
1463 | slides[slides.length] = div;
1464 |
1465 | // hide each slide as it is found
1466 | div.style.display = "none";
1467 | div.style.visibility = "hidden";
1468 |
1469 | // add dummy at end for scrolling hack
1470 | var node1 = document.createElement("br");
1471 | div.appendChild(node1);
1472 | var node2 = document.createElement("br");
1473 | div.appendChild(node2);
1474 | }
1475 | else if (hasClass(div, "background"))
1476 | { // work around for Firefox SVG reload bug
1477 | // which otherwise replaces 1st SVG graphic with 2nd
1478 | div.style.display = "block";
1479 | }
1480 | }
1481 |
1482 | return slides;
1483 | }
1484 |
1485 | // return new array of all
1486 | function collectNotes()
1487 | {
1488 | var notes = new Array();
1489 | var divs = document.body.getElementsByTagName("div");
1490 |
1491 | for (var i = 0; i < divs.length; ++i)
1492 | {
1493 | div = divs.item(i);
1494 |
1495 | if (hasClass(div, "handout"))
1496 | {
1497 | // add slide to collection
1498 | notes[notes.length] = div;
1499 |
1500 | // hide handout notes as they are found
1501 | div.style.display = "none";
1502 | div.style.visibility = "hidden";
1503 | }
1504 | }
1505 |
1506 | return notes;
1507 | }
1508 |
1509 | // return new array of all
1510 | // including named backgrounds e.g. class="background titlepage"
1511 | function collectBackgrounds()
1512 | {
1513 | var backgrounds = new Array();
1514 | var divs = document.body.getElementsByTagName("div");
1515 |
1516 | for (var i = 0; i < divs.length; ++i)
1517 | {
1518 | div = divs.item(i);
1519 |
1520 | if (hasClass(div, "background"))
1521 | {
1522 | // add slide to collection
1523 | backgrounds[backgrounds.length] = div;
1524 |
1525 | // hide named backgrounds as they are found
1526 | // e.g. class="background epilog"
1527 | if (getClassList(div) != "background")
1528 | {
1529 | div.style.display = "none";
1530 | div.style.visibility = "hidden";
1531 | }
1532 | }
1533 | }
1534 |
1535 | return backgrounds;
1536 | }
1537 |
1538 | // show just the backgrounds pertinent to this slide
1539 | function syncBackground(slide)
1540 | {
1541 | var background;
1542 | var bgColor;
1543 |
1544 | if (slide.currentStyle)
1545 | bgColor = slide.currentStyle["backgroundColor"];
1546 | else if (document.defaultView)
1547 | {
1548 | var styles = document.defaultView.getComputedStyle(slide,null);
1549 |
1550 | if (styles)
1551 | bgColor = styles.getPropertyValue("background-color");
1552 | else // broken implementation probably due Safari or Konqueror
1553 | {
1554 | //alert("defective implementation of getComputedStyle()");
1555 | bgColor = "transparent";
1556 | }
1557 | }
1558 | else
1559 | bgColor == "transparent";
1560 |
1561 | if (bgColor == "transparent")
1562 | {
1563 | var slideClass = getClassList(slide);
1564 |
1565 | for (var i = 0; i < backgrounds.length; i++)
1566 | {
1567 | background = backgrounds[i];
1568 |
1569 | var bgClass = getClassList(background);
1570 |
1571 | if (matchingBackground(slideClass, bgClass))
1572 | {
1573 | background.style.display = "block";
1574 | background.style.visibility = "visible";
1575 | }
1576 | else
1577 | {
1578 | background.style.display = "none";
1579 | background.style.visibility = "hidden";
1580 | }
1581 | }
1582 | }
1583 | else // forcibly hide all backgrounds
1584 | hideBackgrounds();
1585 | }
1586 |
1587 | function hideBackgrounds()
1588 | {
1589 | for (var i = 0; i < backgrounds.length; i++)
1590 | {
1591 | background = backgrounds[i];
1592 | background.style.display = "none";
1593 | background.style.visibility = "hidden";
1594 | }
1595 | }
1596 |
1597 | // compare classes for slide and background
1598 | function matchingBackground(slideClass, bgClass)
1599 | {
1600 | if (bgClass == "background")
1601 | return true;
1602 |
1603 | // define pattern as regular expression
1604 | var pattern = /\w+/g;
1605 |
1606 | // check for matches and place result in array
1607 | var result = slideClass.match(pattern);
1608 |
1609 | // now check if desired name is present for background
1610 | for (var i = 0; i < result.length; i++)
1611 | {
1612 | if (hasToken(bgClass, result[i]))
1613 | return true;
1614 | }
1615 |
1616 | return false;
1617 | }
1618 |
1619 | // left to right traversal of root's content
1620 | function nextNode(root, node)
1621 | {
1622 | if (node == null)
1623 | return root.firstChild;
1624 |
1625 | if (node.firstChild)
1626 | return node.firstChild;
1627 |
1628 | if (node.nextSibling)
1629 | return node.nextSibling;
1630 |
1631 | for (;;)
1632 | {
1633 | node = node.parentNode;
1634 |
1635 | if (!node || node == root)
1636 | break;
1637 |
1638 | if (node && node.nextSibling)
1639 | return node.nextSibling;
1640 | }
1641 |
1642 | return null;
1643 | }
1644 |
1645 | // right to left traversal of root's content
1646 | function previousNode(root, node)
1647 | {
1648 | if (node == null)
1649 | {
1650 | node = root.lastChild;
1651 |
1652 | if (node)
1653 | {
1654 | while (node.lastChild)
1655 | node = node.lastChild;
1656 | }
1657 |
1658 | return node;
1659 | }
1660 |
1661 | if (node.previousSibling)
1662 | {
1663 | node = node.previousSibling;
1664 |
1665 | while (node.lastChild)
1666 | node = node.lastChild;
1667 |
1668 | return node;
1669 | }
1670 |
1671 | if (node.parentNode != root)
1672 | return node.parentNode;
1673 |
1674 | return null;
1675 | }
1676 |
1677 | // HTML elements that can be used with class="incremental"
1678 | // note that you can also put the class on containers like
1679 | // up, ol, dl, and div to make their contents appear
1680 | // incrementally. Upper case is used since this is what
1681 | // browsers report for HTML node names (text/html).
1682 | function incrementalElementList()
1683 | {
1684 | var inclist = new Array();
1685 | inclist["P"] = true;
1686 | inclist["PRE"] = true;
1687 | inclist["LI"] = true;
1688 | inclist["BLOCKQUOTE"] = true;
1689 | inclist["DT"] = true;
1690 | inclist["DD"] = true;
1691 | inclist["H2"] = true;
1692 | inclist["H3"] = true;
1693 | inclist["H4"] = true;
1694 | inclist["H5"] = true;
1695 | inclist["H6"] = true;
1696 | inclist["SPAN"] = true;
1697 | inclist["ADDRESS"] = true;
1698 | inclist["TABLE"] = true;
1699 | inclist["TR"] = true;
1700 | inclist["TH"] = true;
1701 | inclist["TD"] = true;
1702 | inclist["IMG"] = true;
1703 | inclist["OBJECT"] = true;
1704 | return inclist;
1705 | }
1706 |
1707 | function nextIncrementalItem(node)
1708 | {
1709 | var slide = slides[slidenum];
1710 |
1711 | for (;;)
1712 | {
1713 | node = nextNode(slide, node);
1714 |
1715 | if (node == null || node.parentNode == null)
1716 | break;
1717 |
1718 | if (node.nodeType == 1) // ELEMENT
1719 | {
1720 | if (node.nodeName == "BR")
1721 | continue;
1722 |
1723 | if (hasClass(node, "incremental")
1724 | && okayForIncremental[node.nodeName])
1725 | return node;
1726 |
1727 | if (hasClass(node.parentNode, "incremental")
1728 | && !hasClass(node, "non-incremental"))
1729 | return node;
1730 | }
1731 | }
1732 |
1733 | return node;
1734 | }
1735 |
1736 | function previousIncrementalItem(node)
1737 | {
1738 | var slide = slides[slidenum];
1739 |
1740 | for (;;)
1741 | {
1742 | node = previousNode(slide, node);
1743 |
1744 | if (node == null || node.parentNode == null)
1745 | break;
1746 |
1747 | if (node.nodeType == 1)
1748 | {
1749 | if (node.nodeName == "BR")
1750 | continue;
1751 |
1752 | if (hasClass(node, "incremental")
1753 | && okayForIncremental[node.nodeName])
1754 | return node;
1755 |
1756 | if (hasClass(node.parentNode, "incremental")
1757 | && !hasClass(node, "non-incremental"))
1758 | return node;
1759 | }
1760 | }
1761 |
1762 | return node;
1763 | }
1764 |
1765 | // set visibility for all elements on current slide with
1766 | // a parent element with attribute class="incremental"
1767 | function setVisibilityAllIncremental(value)
1768 | {
1769 | var node = nextIncrementalItem(null);
1770 |
1771 | while (node)
1772 | {
1773 | node.style.visibility = value;
1774 | node = nextIncrementalItem(node);
1775 | }
1776 | }
1777 |
1778 | // reveal the next hidden item on the slide
1779 | // node is null or the node that was last revealed
1780 | function revealNextItem(node)
1781 | {
1782 | node = nextIncrementalItem(node);
1783 |
1784 | if (node && node.nodeType == 1) // an element
1785 | node.style.visibility = "visible";
1786 |
1787 | return node;
1788 | }
1789 |
1790 |
1791 | // exact inverse of revealNextItem(node)
1792 | function hidePreviousItem(node)
1793 | {
1794 | if (node && node.nodeType == 1) // an element
1795 | node.style.visibility = "hidden";
1796 |
1797 | return previousIncrementalItem(node);
1798 | }
1799 |
1800 |
1801 | /* set click handlers on all anchors */
1802 | function patchAnchors()
1803 | {
1804 | var anchors = document.body.getElementsByTagName("a");
1805 |
1806 | for (var i = 0; i < anchors.length; ++i)
1807 | {
1808 | anchors[i].onclick = clickedAnchor;
1809 | }
1810 | }
1811 |
1812 | function clickedAnchor(e)
1813 | {
1814 | if (!e)
1815 | var e = window.event;
1816 |
1817 | // compare this.href with location.href
1818 | // for link to another slide in this doc
1819 |
1820 | if (pageAddress(this.href) == pageAddress(location.href))
1821 | {
1822 | // yes, so find new slide number
1823 | var newslidenum = findSlideNumber(this.href);
1824 |
1825 | if (newslidenum != slidenum)
1826 | {
1827 | slide = slides[slidenum];
1828 | hideSlide(slide);
1829 | slidenum = newslidenum;
1830 | slide = slides[slidenum];
1831 | showSlide(slide);
1832 | setLocation();
1833 | }
1834 | }
1835 | else if (this.target == null)
1836 | location.href = this.href;
1837 |
1838 | this.blur();
1839 | stopPropagation(e);
1840 | }
1841 |
1842 | function pageAddress(uri)
1843 | {
1844 | var i = uri.indexOf("#");
1845 |
1846 | if (i < 0)
1847 | i = uri.indexOf("%23");
1848 |
1849 | // check if anchor is entire page
1850 |
1851 | if (i < 0)
1852 | return uri; // yes
1853 |
1854 | return uri.substr(0, i);
1855 | }
1856 |
1857 | function showSlideNumber()
1858 | {
1859 | slideNumElement.innerHTML = "slide".localize() + " " +
1860 | (slidenum + 1) + "/" + slides.length;
1861 | }
1862 |
1863 | // every 200mS check if the location has been changed as a
1864 | // result of the user activating the Back button/menu item
1865 | // doesn't work for Opera < 9.5
1866 | function checkLocation()
1867 | {
1868 | var hash = location.hash;
1869 |
1870 | if (slidenum > 0 && (hash == "" || hash == "#"))
1871 | gotoSlide(0);
1872 | else if (hash.length > 2 && hash != "#("+(slidenum+1)+")")
1873 | {
1874 | var num = parseInt(location.hash.substr(2));
1875 |
1876 | if (!isNaN(num))
1877 | gotoSlide(num-1);
1878 | }
1879 | }
1880 |
1881 | // this doesn't push location onto history stack for IE
1882 | // for which a hidden iframe hack is needed: load page into
1883 | // the iframe with script that set's parent's location.hash
1884 | // but that won't work for standalone use unless we can
1885 | // create the page dynamically via a javascript: URL
1886 | function setLocation()
1887 | {
1888 | var uri = pageAddress(location.href);
1889 | var hash = "#(" + (slidenum+1) + ")";
1890 |
1891 | if (slidenum >= 0)
1892 | uri = uri + hash;
1893 |
1894 | if (ie && !ie8)
1895 | pushHash(hash);
1896 |
1897 | if (uri != location.href /*&& !khtml */)
1898 | location.href = uri;
1899 |
1900 | if (khtml)
1901 | hash = "(" + (slidenum+1) + ")";
1902 |
1903 | if (!ie && location.hash != hash && location.hash != "")
1904 | location.hash = hash;
1905 |
1906 | document.title = title + " (" + (slidenum+1) + ")";
1907 | showSlideNumber();
1908 | }
1909 |
1910 | // only used for IE6 and IE7
1911 | function onFrameLoaded(hash)
1912 | {
1913 | location.hash = hash;
1914 | var uri = pageAddress(location.href);
1915 | location.href = uri + hash;
1916 | }
1917 |
1918 | // history hack with thanks to Bertrand Le Roy
1919 | function pushHash(hash)
1920 | {
1921 | if (hash == "") hash = "#(1)";
1922 | window.location.hash = hash;
1923 | var doc = document.getElementById("historyFrame").contentWindow.document;
1924 | doc.open("javascript:''");
1925 | doc.write("hello mum");
1927 | doc.close();
1928 | }
1929 |
1930 | // find current slide based upon location
1931 | // first find target anchor and then look
1932 | // for associated div element enclosing it
1933 | // finally map that to slide number
1934 | function findSlideNumber(uri)
1935 | {
1936 | // first get anchor from page location
1937 |
1938 | var i = uri.indexOf("#");
1939 |
1940 | // check if anchor is entire page
1941 |
1942 | if (i < 0)
1943 | return 0; // yes
1944 |
1945 | var anchor = unescape(uri.substr(i+1));
1946 |
1947 | // now use anchor as XML ID to find target
1948 | var target = document.getElementById(anchor);
1949 |
1950 | if (!target)
1951 | {
1952 | // does anchor look like "(2)" for slide 2 ??
1953 | // where first slide is (1)
1954 | var re = /\((\d)+\)/;
1955 |
1956 | if (anchor.match(re))
1957 | {
1958 | var num = parseInt(anchor.substring(1, anchor.length-1));
1959 |
1960 | if (num > slides.length)
1961 | num = 1;
1962 |
1963 | if (--num < 0)
1964 | num = 0;
1965 |
1966 | return num;
1967 | }
1968 |
1969 | // accept [2] for backwards compatibility
1970 | re = /\[(\d)+\]/;
1971 |
1972 | if (anchor.match(re))
1973 | {
1974 | var num = parseInt(anchor.substring(1, anchor.length-1));
1975 |
1976 | if (num > slides.length)
1977 | num = 1;
1978 |
1979 | if (--num < 0)
1980 | num = 0;
1981 |
1982 | return num;
1983 | }
1984 |
1985 | // oh dear unknown anchor
1986 | return 0;
1987 | }
1988 |
1989 | // search for enclosing slide
1990 |
1991 | while (true)
1992 | {
1993 | // browser coerces html elements to uppercase!
1994 | if (target.nodeName.toLowerCase() == "div" &&
1995 | hasClass(target, "slide"))
1996 | {
1997 | // found the slide element
1998 | break;
1999 | }
2000 |
2001 | // otherwise try parent element if any
2002 |
2003 | target = target.parentNode;
2004 |
2005 | if (!target)
2006 | {
2007 | return 0; // no luck!
2008 | }
2009 | };
2010 |
2011 | for (i = 0; i < slides.length; ++i)
2012 | {
2013 | if (slides[i] == target)
2014 | return i; // success
2015 | }
2016 |
2017 | // oh dear still no luck
2018 | return 0;
2019 | }
2020 |
2021 | // find slide name from first h1 element
2022 | // default to document title + slide number
2023 | function slideName(index)
2024 | {
2025 | var name = null;
2026 | var slide = slides[index];
2027 |
2028 | var heading = findHeading(slide);
2029 |
2030 | if (heading)
2031 | name = extractText(heading);
2032 |
2033 | if (!name)
2034 | name = title + "(" + (index + 1) + ")";
2035 |
2036 | name.replace(/\&/g, "&");
2037 | name.replace(/\/g, ">");
2039 |
2040 | return name;
2041 | }
2042 |
2043 | // find first h1 element in DOM tree
2044 | function findHeading(node)
2045 | { if (!node || node.nodeType != 1)
2046 | return null;
2047 |
2048 | if (node.nodeName == "H1" || node.nodeName == "h1")
2049 | return node;
2050 |
2051 | var child = node.firstChild;
2052 |
2053 | while (child)
2054 | {
2055 | node = findHeading(child);
2056 |
2057 | if (node)
2058 | return node;
2059 |
2060 | child = child.nextSibling;
2061 | }
2062 |
2063 | return null;
2064 | }
2065 |
2066 | // recursively extract text from DOM tree
2067 | function extractText(node)
2068 | {
2069 | if (!node)
2070 | return "";
2071 |
2072 | // text nodes
2073 | if (node.nodeType == 3)
2074 | return node.nodeValue;
2075 |
2076 | // elements
2077 | if (node.nodeType == 1)
2078 | {
2079 | node = node.firstChild;
2080 | var text = "";
2081 |
2082 | while (node)
2083 | {
2084 | text = text + extractText(node);
2085 | node = node.nextSibling;
2086 | }
2087 |
2088 | return text;
2089 | }
2090 |
2091 | return "";
2092 | }
2093 |
2094 |
2095 | // find copyright text from meta element
2096 | function findCopyright()
2097 | {
2098 | var name, content;
2099 | var meta = document.getElementsByTagName("meta");
2100 |
2101 | for (var i = 0; i < meta.length; ++i)
2102 | {
2103 | name = meta[i].getAttribute("name");
2104 | content = meta[i].getAttribute("content");
2105 |
2106 | if (name == "copyright")
2107 | return content;
2108 | }
2109 |
2110 | return null;
2111 | }
2112 |
2113 | function findSizeAdjust()
2114 | {
2115 | var name, content, offset;
2116 | var meta = document.getElementsByTagName("meta");
2117 |
2118 | for (var i = 0; i < meta.length; ++i)
2119 | {
2120 | name = meta[i].getAttribute("name");
2121 | content = meta[i].getAttribute("content");
2122 |
2123 | if (name == "font-size-adjustment")
2124 | return 1 * content;
2125 | }
2126 |
2127 | return 1;
2128 | }
2129 |
2130 | function addToolbar()
2131 | {
2132 | var slideCounter, page;
2133 |
2134 | var toolbar = createElement("div");
2135 | toolbar.setAttribute("class", "toolbar");
2136 |
2137 | if (ns_pos) // a reasonably behaved browser
2138 | {
2139 | var right = document.createElement("div");
2140 | right.setAttribute("style", "float: right; text-align: right");
2141 |
2142 | slideCounter = document.createElement("div")
2143 | slideCounter.innerHTML = "slide".localize() + " n/m";
2144 | right.appendChild(slideCounter);
2145 | toolbar.appendChild(right);
2146 |
2147 | var left = document.createElement("div");
2148 | left.setAttribute("style", "text-align: left");
2149 |
2150 | // global end of slide indicator
2151 | eos = document.createElement("span");
2152 | eos.innerHTML = "* ";
2153 | left.appendChild(eos);
2154 |
2155 | var help = document.createElement("a");
2156 | help.setAttribute("href", helpPage);
2157 | help.setAttribute("title", helpText.localize());
2158 | help.innerHTML = "help?".localize();
2159 | left.appendChild(help);
2160 | helpAnchor = help; // save for focus hack
2161 |
2162 | var gap1 = document.createTextNode(" ");
2163 | left.appendChild(gap1);
2164 |
2165 | var contents = document.createElement("a");
2166 | contents.setAttribute("href", "javascript:toggleTableOfContents()");
2167 | contents.setAttribute("title", "table of contents".localize());
2168 | contents.innerHTML = "contents?".localize();
2169 | left.appendChild(contents);
2170 |
2171 | var gap2 = document.createTextNode(" ");
2172 | left.appendChild(gap2);
2173 |
2174 | var start = document.createElement("a");
2175 | start.setAttribute("href", "javascript:firstSlide()");
2176 | start.setAttribute("title", "restart presentation".localize());
2177 | start.innerHTML = "restart?".localize();
2178 | // start.setAttribute("href", "javascript:printSlides()");
2179 | // start.setAttribute("title", "print all slides".localize());
2180 | // start.innerHTML = "print!".localize();
2181 | left.appendChild(start);
2182 |
2183 | var copyright = findCopyright();
2184 |
2185 | if (copyright)
2186 | {
2187 | var span = document.createElement("span");
2188 | span.innerHTML = copyright;
2189 | span.style.color = "black";
2190 | span.style.marginLeft = "4em";
2191 | left.appendChild(span);
2192 | }
2193 |
2194 | toolbar.appendChild(left);
2195 | }
2196 | else // IE so need to work around its poor CSS support
2197 | {
2198 | toolbar.style.position = (ie7 ? "fixed" : "absolute");
2199 | toolbar.style.zIndex = "200";
2200 | toolbar.style.width = "99.9%";
2201 | toolbar.style.height = "1.2em";
2202 | toolbar.style.top = "auto";
2203 | toolbar.style.bottom = "0";
2204 | toolbar.style.left = "0";
2205 | toolbar.style.right = "0";
2206 | toolbar.style.textAlign = "left";
2207 | toolbar.style.fontSize = "60%";
2208 | toolbar.style.color = "red";
2209 | toolbar.borderWidth = 0;
2210 | toolbar.style.background = "rgb(240,240,240)";
2211 |
2212 | // would like to have help text left aligned
2213 | // and page counter right aligned, floating
2214 | // div's don't work, so instead use nested
2215 | // absolutely positioned div's.
2216 |
2217 | var sp = document.createElement("span");
2218 | sp.innerHTML = " * ";
2219 | toolbar.appendChild(sp);
2220 | eos = sp; // end of slide indicator
2221 |
2222 | var help = document.createElement("a");
2223 | help.setAttribute("href", helpPage);
2224 | help.setAttribute("title", helpText.localize());
2225 | help.innerHTML = "help?".localize();
2226 | toolbar.appendChild(help);
2227 | helpAnchor = help; // save for focus hack
2228 |
2229 | var gap1 = document.createTextNode(" ");
2230 | toolbar.appendChild(gap1);
2231 |
2232 | var contents = document.createElement("a");
2233 | contents.setAttribute("href", "javascript:toggleTableOfContents()");
2234 | contents.setAttribute("title", "table of contents".localize());
2235 | contents.innerHTML = "contents?".localize();
2236 | toolbar.appendChild(contents);
2237 |
2238 | var gap2 = document.createTextNode(" ");
2239 | toolbar.appendChild(gap2);
2240 |
2241 | var start = document.createElement("a");
2242 | start.setAttribute("href", "javascript:firstSlide()");
2243 | start.setAttribute("title", "restart presentation".localize());
2244 | start.innerHTML = "restart?".localize();
2245 | // start.setAttribute("href", "javascript:printSlides()");
2246 | // start.setAttribute("title", "print all slides".localize());
2247 | // start.innerHTML = "print!".localize();
2248 | toolbar.appendChild(start);
2249 |
2250 | var copyright = findCopyright();
2251 |
2252 | if (copyright)
2253 | {
2254 | var span = document.createElement("span");
2255 | span.innerHTML = copyright;
2256 | span.style.color = "black";
2257 | span.style.marginLeft = "2em";
2258 | toolbar.appendChild(span);
2259 | }
2260 |
2261 | slideCounter = document.createElement("div")
2262 | slideCounter.style.position = "absolute";
2263 | slideCounter.style.width = "auto"; //"20%";
2264 | slideCounter.style.height = "1.2em";
2265 | slideCounter.style.top = "auto";
2266 | slideCounter.style.bottom = 0;
2267 | slideCounter.style.right = "0";
2268 | slideCounter.style.textAlign = "right";
2269 | slideCounter.style.color = "red";
2270 | slideCounter.style.background = "rgb(240,240,240)";
2271 |
2272 | slideCounter.innerHTML = "slide".localize() + " n/m";
2273 | toolbar.appendChild(slideCounter);
2274 | }
2275 |
2276 | // ensure that click isn't passed through to the page
2277 | toolbar.onclick = stopPropagation;
2278 | document.body.appendChild(toolbar);
2279 | slideNumElement = slideCounter;
2280 | setEosStatus(false);
2281 |
2282 | return toolbar;
2283 | }
2284 |
2285 | function isShownToc()
2286 | {
2287 | if (toc && toc.style.visible == "visible")
2288 | return true;
2289 |
2290 | return false;
2291 | }
2292 |
2293 | function showTableOfContents()
2294 | {
2295 | if (toc)
2296 | {
2297 | if (toc.style.visibility != "visible")
2298 | {
2299 | toc.style.visibility = "visible";
2300 | toc.style.display = "block";
2301 | toc.focus();
2302 |
2303 | if (ie7 && slidenum == 0)
2304 | setTimeout("ieHack()", 100);
2305 | }
2306 | else
2307 | hideTableOfContents();
2308 | }
2309 | }
2310 |
2311 | function hideTableOfContents()
2312 | {
2313 | if (toc && toc.style.visibility != "hidden")
2314 | {
2315 | toc.style.visibility = "hidden";
2316 | toc.style.display = "none";
2317 |
2318 | try
2319 | {
2320 | if (!opera)
2321 | helpAnchor.focus();
2322 | }
2323 | catch (e)
2324 | {
2325 | }
2326 | }
2327 | }
2328 |
2329 | function toggleTableOfContents()
2330 | {
2331 | if (toc)
2332 | {
2333 | if (toc.style.visible != "visible")
2334 | showTableOfContents();
2335 | else
2336 | hideTableOfContents();
2337 | }
2338 | }
2339 |
2340 | // called on clicking toc entry
2341 | function gotoEntry(e)
2342 | {
2343 | var target;
2344 |
2345 | if (!e)
2346 | var e = window.event;
2347 |
2348 | if (e.target)
2349 | target = e.target;
2350 | else if (e.srcElement)
2351 | target = e.srcElement;
2352 |
2353 | // work around Safari bug
2354 | if (target.nodeType == 3)
2355 | target = target.parentNode;
2356 |
2357 | if (target && target.nodeType == 1)
2358 | {
2359 | var uri = target.getAttribute("href");
2360 |
2361 | if (uri)
2362 | {
2363 | //alert("going to " + uri);
2364 | var slide = slides[slidenum];
2365 | hideSlide(slide);
2366 | slidenum = findSlideNumber(uri);
2367 | slide = slides[slidenum];
2368 | lastShown = null;
2369 | setLocation();
2370 | setVisibilityAllIncremental("hidden");
2371 | setEosStatus(!nextIncrementalItem(lastShown));
2372 | showSlide(slide);
2373 | //target.focus();
2374 |
2375 | try
2376 | {
2377 | if (!opera)
2378 | helpAnchor.focus();
2379 | }
2380 | catch (e)
2381 | {
2382 | }
2383 | }
2384 | }
2385 |
2386 | hideTableOfContents(e);
2387 | if (ie7) ieHack();
2388 | stopPropagation(e);
2389 | return cancel(e);
2390 | }
2391 |
2392 | // called onkeydown for toc entry
2393 | function gotoTocEntry(event)
2394 | {
2395 | var key;
2396 |
2397 | if (!event)
2398 | var event = window.event;
2399 |
2400 | // kludge around NS/IE differences
2401 | if (window.event)
2402 | key = window.event.keyCode;
2403 | else if (event.which)
2404 | key = event.which;
2405 | else
2406 | return true; // Yikes! unknown browser
2407 |
2408 | // ignore event if key value is zero
2409 | // as for alt on Opera and Konqueror
2410 | if (!key)
2411 | return true;
2412 |
2413 | // check for concurrent control/command/alt key
2414 | // but are these only present on mouse events?
2415 |
2416 | if (event.ctrlKey || event.altKey)
2417 | return true;
2418 |
2419 | if (key == 13)
2420 | {
2421 | var uri = this.getAttribute("href");
2422 |
2423 | if (uri)
2424 | {
2425 | //alert("going to " + uri);
2426 | var slide = slides[slidenum];
2427 | hideSlide(slide);
2428 | slidenum = findSlideNumber(uri);
2429 | slide = slides[slidenum];
2430 | lastShown = null;
2431 | setLocation();
2432 | setVisibilityAllIncremental("hidden");
2433 | setEosStatus(!nextIncrementalItem(lastShown));
2434 | showSlide(slide);
2435 | //target.focus();
2436 |
2437 | try
2438 | {
2439 | if (!opera)
2440 | helpAnchor.focus();
2441 | }
2442 | catch (e)
2443 | {
2444 | }
2445 | }
2446 |
2447 | hideTableOfContents();
2448 | if (ie7) ieHack();
2449 | return cancel(event);
2450 | }
2451 |
2452 | if (key == 40 && this.next)
2453 | {
2454 | this.next.focus();
2455 | return cancel(event);
2456 | }
2457 |
2458 | if (key == 38 && this.previous)
2459 | {
2460 | this.previous.focus();
2461 | return cancel(event);
2462 | }
2463 |
2464 | return true;
2465 | }
2466 |
2467 | function isTitleSlide(slide)
2468 | {
2469 | return hasClass(slide, "title");
2470 | }
2471 |
2472 | // create div element with links to each slide
2473 | function tableOfContents()
2474 | {
2475 | var toc = document.createElement("div");
2476 | addClass(toc, "toc");
2477 | //toc.setAttribute("tabindex", "0");
2478 |
2479 | var heading = document.createElement("div");
2480 | addClass(heading, "toc-heading");
2481 | heading.innerHTML = "Table of Contents".localize();
2482 |
2483 | heading.style.textAlign = "center";
2484 | heading.style.width = "100%";
2485 | heading.style.margin = "0";
2486 | heading.style.marginBottom = "1em";
2487 | heading.style.borderBottomStyle = "solid";
2488 | heading.style.borderBottomColor = "rgb(180,180,180)";
2489 | heading.style.borderBottomWidth = "1px";
2490 |
2491 | toc.appendChild(heading);
2492 | var previous = null;
2493 |
2494 | for (var i = 0; i < slides.length; ++i)
2495 | {
2496 | var title = hasClass(slides[i], "title");
2497 | var num = document.createTextNode((i + 1) + ". ");
2498 |
2499 | toc.appendChild(num);
2500 |
2501 | var a = document.createElement("a");
2502 | a.setAttribute("href", "#(" + (i+1) + ")");
2503 |
2504 | if (title)
2505 | addClass(a, "titleslide");
2506 |
2507 | var name = document.createTextNode(slideName(i));
2508 | a.appendChild(name);
2509 | a.onclick = gotoEntry;
2510 | a.onkeydown = gotoTocEntry;
2511 | a.previous = previous;
2512 |
2513 | if (previous)
2514 | previous.next = a;
2515 |
2516 | toc.appendChild(a);
2517 |
2518 | if (i == 0)
2519 | toc.first = a;
2520 |
2521 | if (i < slides.length - 1)
2522 | {
2523 | var br = document.createElement("br");
2524 | toc.appendChild(br);
2525 | }
2526 |
2527 | previous = a;
2528 | }
2529 |
2530 | toc.focus = function () {
2531 | if (this.first)
2532 | this.first.focus();
2533 | }
2534 |
2535 | toc.onmouseup = mouseButtonUp;
2536 |
2537 | toc.onclick = function (e) {
2538 | e||(e=window.event);
2539 |
2540 | if (selectedTextLen <= 0)
2541 | hideTableOfContents();
2542 |
2543 | stopPropagation(e);
2544 |
2545 | if (e.cancel != undefined)
2546 | e.cancel = true;
2547 |
2548 | if (e.returnValue != undefined)
2549 | e.returnValue = false;
2550 |
2551 | return false;
2552 | };
2553 |
2554 | toc.style.position = "absolute";
2555 | toc.style.zIndex = "300";
2556 | toc.style.width = "60%";
2557 | toc.style.maxWidth = "30em";
2558 | toc.style.height = "30em";
2559 | toc.style.overflow = "auto";
2560 | toc.style.top = "auto";
2561 | toc.style.right = "auto";
2562 | toc.style.left = "4em";
2563 | toc.style.bottom = "4em";
2564 | toc.style.padding = "1em";
2565 | toc.style.background = "rgb(240,240,240)";
2566 | toc.style.borderStyle = "solid";
2567 | toc.style.borderWidth = "2px";
2568 | toc.style.fontSize = "60%";
2569 |
2570 | document.body.insertBefore(toc, document.body.firstChild);
2571 | return toc;
2572 | }
2573 |
2574 | function replaceByNonBreakingSpace(str)
2575 | {
2576 | for (var i = 0; i < str.length; ++i)
2577 | str[i] = 160;
2578 | }
2579 |
2580 |
2581 | function initOutliner()
2582 | {
2583 | var items = document.getElementsByTagName("LI");
2584 |
2585 | for (var i = 0; i < items.length; ++i)
2586 | {
2587 | var target = items[i];
2588 |
2589 | if (!hasClass(target.parentNode, "outline"))
2590 | continue;
2591 |
2592 | target.onclick = outlineClick;
2593 |
2594 | if (!ns_pos)
2595 | {
2596 | target.onmouseover = hoverOutline;
2597 | target.onmouseout = unhoverOutline;
2598 | }
2599 |
2600 | if (foldable(target))
2601 | {
2602 | target.foldable = true;
2603 | target.onfocus = function () {outline = this;};
2604 | target.onblur = function () {outline = null;};
2605 |
2606 | if (!target.getAttribute("tabindex"))
2607 | target.setAttribute("tabindex", "0");
2608 |
2609 | if (hasClass(target, "expand"))
2610 | unfold(target);
2611 | else
2612 | fold(target);
2613 | }
2614 | else
2615 | {
2616 | addClass(target, "nofold");
2617 | target.visible = true;
2618 | target.foldable = false;
2619 | }
2620 | }
2621 | }
2622 |
2623 | function foldable(item)
2624 | {
2625 | if (!item || item.nodeType != 1)
2626 | return false;
2627 |
2628 | var node = item.firstChild;
2629 |
2630 | while (node)
2631 | {
2632 | if (node.nodeType == 1 && isBlock(node))
2633 | return true;
2634 |
2635 | node = node.nextSibling;
2636 | }
2637 |
2638 | return false;
2639 | }
2640 |
2641 | function fold(item)
2642 | {
2643 | if (item)
2644 | {
2645 | removeClass(item, "unfolded");
2646 | addClass(item, "folded");
2647 | }
2648 |
2649 | var node = item ? item.firstChild : null;
2650 |
2651 | while (node)
2652 | {
2653 | if (node.nodeType == 1 && isBlock(node)) // element
2654 | {
2655 | // note that getElementStyle won't work for Safari 1.3
2656 | node.display = getElementStyle(node, "display", "display");
2657 | node.style.display = "none";
2658 | node.style.visibility = "hidden";
2659 | }
2660 |
2661 | node = node.nextSibling;
2662 | }
2663 |
2664 | item.visible = false;
2665 | }
2666 |
2667 | function unfold(item)
2668 | {
2669 | if (item)
2670 | {
2671 | addClass(item, "unfolded");
2672 | removeClass(item, "folded");
2673 | }
2674 |
2675 | var node = item ? item.firstChild : null;
2676 |
2677 | while (node)
2678 | {
2679 | if (node.nodeType == 1 && isBlock(node)) // element
2680 | {
2681 | // with fallback for Safari, see above
2682 | node.style.display = (node.display ? node.display : "block");
2683 | node.style.visibility = "visible";
2684 | }
2685 |
2686 | node = node.nextSibling;
2687 | }
2688 |
2689 | item.visible = true;
2690 | }
2691 |
2692 | function outlineClick(e)
2693 | {
2694 | var rightclick = false;
2695 | var target;
2696 |
2697 | if (!e)
2698 | var e = window.event;
2699 |
2700 | if (e.target)
2701 | target = e.target;
2702 | else if (e.srcElement)
2703 | target = e.srcElement;
2704 |
2705 | // work around Safari bug
2706 | if (target.nodeType == 3)
2707 | target = target.parentNode;
2708 |
2709 | while (target && target.visible == undefined)
2710 | target = target.parentNode;
2711 |
2712 | if (!target)
2713 | return true;
2714 |
2715 | if (e.which)
2716 | rightclick = (e.which == 3);
2717 | else if (e.button)
2718 | rightclick = (e.button == 2);
2719 |
2720 | if (!rightclick && target.visible != undefined)
2721 | {
2722 | if (target.foldable)
2723 | {
2724 | if (target.visible)
2725 | fold(target);
2726 | else
2727 | unfold(target);
2728 | }
2729 |
2730 | stopPropagation(e);
2731 | e.cancel = true;
2732 | e.returnValue = false;
2733 | }
2734 |
2735 | return false;
2736 | }
2737 |
2738 | function hoverOutline(e)
2739 | {
2740 | var target;
2741 |
2742 | if (!e)
2743 | var e = window.event;
2744 |
2745 | if (e.target)
2746 | target = e.target;
2747 | else if (e.srcElement)
2748 | target = e.srcElement;
2749 |
2750 | // work around Safari bug
2751 | if (target.nodeType == 3)
2752 | target = target.parentNode;
2753 |
2754 | while (target && target.visible == undefined)
2755 | target = target.parentNode;
2756 |
2757 | if (target && target.foldable)
2758 | target.style.cursor = "pointer";
2759 |
2760 | return true;
2761 | }
2762 |
2763 | function unhoverOutline(e)
2764 | {
2765 | var target;
2766 |
2767 | if (!e)
2768 | var e = window.event;
2769 |
2770 | if (e.target)
2771 | target = e.target;
2772 | else if (e.srcElement)
2773 | target = e.srcElement;
2774 |
2775 | // work around Safari bug
2776 | if (target.nodeType == 3)
2777 | target = target.parentNode;
2778 |
2779 | while (target && target.visible == undefined)
2780 | target = target.parentNode;
2781 |
2782 | if (target)
2783 | target.style.cursor = "default";
2784 |
2785 | return true;
2786 | }
2787 |
2788 |
2789 | function stopPropagation(e)
2790 | {
2791 | if (window.event)
2792 | {
2793 | window.event.cancelBubble = true;
2794 | //window.event.returnValue = false;
2795 | }
2796 | else if (e)
2797 | {
2798 | e.cancelBubble = true;
2799 | e.stopPropagation();
2800 | //e.preventDefault();
2801 | }
2802 | }
2803 |
2804 | /* can't rely on display since we set that to none to hide things */
2805 | function isBlock(elem)
2806 | {
2807 | var tag = elem.nodeName;
2808 |
2809 | return tag == "OL" || tag == "UL" || tag == "P" ||
2810 | tag == "LI" || tag == "TABLE" || tag == "PRE" ||
2811 | tag == "H1" || tag == "H2" || tag == "H3" ||
2812 | tag == "H4" || tag == "H5" || tag == "H6" ||
2813 | tag == "BLOCKQUOTE" || tag == "ADDRESS";
2814 | }
2815 |
2816 | function getElementStyle(elem, IEStyleProp, CSSStyleProp)
2817 | {
2818 | if (elem.currentStyle)
2819 | {
2820 | return elem.currentStyle[IEStyleProp];
2821 | }
2822 | else if (window.getComputedStyle)
2823 | {
2824 | var compStyle = window.getComputedStyle(elem, "");
2825 | return compStyle.getPropertyValue(CSSStyleProp);
2826 | }
2827 | return "";
2828 | }
2829 |
2830 | // works with text/html and text/xhtml+xml with thanks to Simon Willison
2831 | function createElement(element)
2832 | {
2833 | if (typeof document.createElementNS != 'undefined')
2834 | {
2835 | return document.createElementNS('http://www.w3.org/1999/xhtml', element);
2836 | }
2837 |
2838 | if (typeof document.createElement != 'undefined')
2839 | {
2840 | return document.createElement(element);
2841 | }
2842 |
2843 | return false;
2844 | }
2845 |
2846 | // designed to work with both text/html and text/xhtml+xml
2847 | function getElementsByTagName(name)
2848 | {
2849 | if (typeof document.getElementsByTagNameNS != 'undefined')
2850 | {
2851 | return document.getElementsByTagNameNS('http://www.w3.org/1999/xhtml', name);
2852 | }
2853 |
2854 | if (typeof document.getElementsByTagName != 'undefined')
2855 | {
2856 | return document.getElementsByTagName(name);
2857 | }
2858 |
2859 | return null;
2860 | }
2861 |
2862 | /*
2863 | // clean alternative to innerHTML method, but on IE6
2864 | // it doesn't work with named entities like
2865 | // which need to be replaced by numeric entities
2866 | function insertText(element, text)
2867 | {
2868 | try
2869 | {
2870 | element.textContent = text; // DOM3 only
2871 | }
2872 | catch (e)
2873 | {
2874 | if (element.firstChild)
2875 | {
2876 | // remove current children
2877 | while (element.firstChild)
2878 | element.removeChild(element.firstChild);
2879 | }
2880 |
2881 | element.appendChild(document.createTextNode(text));
2882 | }
2883 | }
2884 |
2885 | // as above, but as method of all element nodes
2886 | // doesn't work in IE6 which doesn't allow you to
2887 | // add methods to the HTMLElement prototype
2888 | if (HTMLElement != undefined)
2889 | {
2890 | HTMLElement.prototype.insertText = function(text) {
2891 | var element = this;
2892 |
2893 | try
2894 | {
2895 | element.textContent = text; // DOM3 only
2896 | }
2897 | catch (e)
2898 | {
2899 | if (element.firstChild)
2900 | {
2901 | // remove current children
2902 | while (element.firstChild)
2903 | element.removeChild(element.firstChild);
2904 | }
2905 |
2906 | element.appendChild(document.createTextNode(text));
2907 | }
2908 | };
2909 | }
2910 | */
2911 |
2912 | function getSelectedText()
2913 | {
2914 | try
2915 | {
2916 | if (window.getSelection)
2917 | return window.getSelection().toString();
2918 |
2919 | if (document.getSelection)
2920 | return document.getSelection().toString();
2921 |
2922 | if (document.selection)
2923 | return document.selection.createRange().text;
2924 | }
2925 | catch (e)
2926 | {
2927 | return "";
2928 | }
2929 | return "";
2930 | }
2931 |
--------------------------------------------------------------------------------
/talk/structure.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nf/goto/90baf09463bba69b426d0857028e1e545c111c58/talk/structure.png
--------------------------------------------------------------------------------
/talk/urlstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nf/goto/90baf09463bba69b426d0857028e1e545c111c58/talk/urlstore.png
--------------------------------------------------------------------------------