├── README
├── static
├── img
│ ├── spinner.gif
│ ├── Screenshot.png
│ └── example_image.jpeg
├── README.rst
├── alto_edit
│ ├── js
│ │ ├── interface.js
│ │ ├── image_client_jp2.js
│ │ ├── seg_edit.js
│ │ ├── alto_view.js
│ │ └── effects.js
│ └── index.html
└── example.xml
├── json_alto
├── README.rst
├── src
│ └── simple_get_response.rb
└── web.rb
├── cropper
├── README.rst
├── web.rb
└── simple_get_response.rb
└── README.rst
/README:
--------------------------------------------------------------------------------
1 | placeholder template
2 |
--------------------------------------------------------------------------------
/static/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KBNLresearch/alto-editor/master/static/img/spinner.gif
--------------------------------------------------------------------------------
/static/img/Screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KBNLresearch/alto-editor/master/static/img/Screenshot.png
--------------------------------------------------------------------------------
/static/img/example_image.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KBNLresearch/alto-editor/master/static/img/example_image.jpeg
--------------------------------------------------------------------------------
/json_alto/README.rst:
--------------------------------------------------------------------------------
1 | ALTO to JSON converter
2 | ---------
3 |
4 | Simple converter for ALTO to JSON
5 |
6 | To run the alto_edit software out of the box you must first:
7 |
8 | 1) Install ruby and the following gems + their dependencies:
9 |
10 | sinatra >= 1.1.2 (sudo gem install sinatra)
11 |
12 | hpricot >= 0.8.2
13 |
14 | json >= 1.4.3
15 |
16 | 2) Run the following command
17 |
18 | $ ruby -rubygems web.rb -p3002
19 |
20 |
21 |
--------------------------------------------------------------------------------
/cropper/README.rst:
--------------------------------------------------------------------------------
1 | Cropper
2 | ---------
3 |
4 | Allows for zooming and cropping images
5 |
6 | To run the alto_edit software out of the box you must first:
7 |
8 | 0) Prereq imagemagick dev lib:
9 |
10 | sudo apt-get install libmagick9-dev
11 |
12 | 1) Install ruby and the following gems:
13 |
14 | sinatra >= 1.1.2 (sudo gem install sinatra)
15 |
16 | rmagick >= 2.13.1
17 |
18 | 2) Run the following command
19 |
20 | $ ruby -rubygems web.rb -p3000
21 |
22 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | Alto Edit
2 | -----------
3 |
4 | Demo: http://altoedit.kbresearch.nl
5 |
6 | Browser based post correction tool for Alto XML files:
7 |
8 | This repos contains three parts:
9 |
10 | cropper --> ruby sinatra based cropper using image magick gem
11 |
12 | json_alto --> ruby sinatra based converter for alto xml files to JSON
13 |
14 | static --> client website, the actual tool
15 |
16 |
17 | Basic setup (for details see the separate subdirs):
18 |
19 | 1. Run the cropper on port 3000
20 |
21 | 2. Run json_alto on port 3002
22 |
23 | 3. On Apache symlink the static dir to /var/www with a name of your choosing
24 |
25 |
26 | .. image:: https://github.com/impactcentre/alto-editor/raw/master/static/img/Screenshot.png
27 |
--------------------------------------------------------------------------------
/static/README.rst:
--------------------------------------------------------------------------------
1 | Alto Edit
2 | ----------
3 |
4 | This website is designed as a demo and is not configurable. To use it generically for your own project some code review is required
5 |
6 | To run this alto editor website, first follow the instructions on starting up the cropper and json_alto services
7 |
8 | Next, to get this demo up and running:
9 |
10 | 1) Install apache
11 |
12 | 2) symlink this folder (static) to your public path (/var/www), using a name of your choosing
13 |
14 | 3) surf to http://localhost/static/alto_edit/
15 |
16 | All remote references are currently hardcoded, so to change them some code review is required:
17 |
18 | All instances of localhost:3000 refer to the cropper service
19 |
20 | All instances of localhost:3002 refer to the json_alto converter
21 |
22 |
23 | There is a same xml file (alto), which is referred to, this is not dynamically retrieved, but this xml file *is* however passed to the json_alto converter service.
24 |
25 | The same goes for the example jpeg file, under the images folder. It is statically retrieved, but it *is* however passed to the cropper service.
26 |
27 | NOTE:
28 | The image_client_jp2.js was written to be used as a client for a dynamic JPEG 2000 service. The cropper is just a dummy for and should not be used with large jpeg files.
29 |
--------------------------------------------------------------------------------
/cropper/web.rb:
--------------------------------------------------------------------------------
1 | # cropper/web.rb: Simple cropper for image magick
2 | # For details see: http://opendatachallenge.kbresearch.nl/
3 | # Copyright (C) 2011 R. van der Ark, Koninklijke Bibliotheek - National Library of the Netherlands
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 | #!/usr/bin/ruby
18 |
19 | require 'sinatra'
20 | require 'RMagick'
21 | require 'simple_get_response'
22 |
23 |
24 |
25 | get '/' do
26 | get = SimpleGetResponse.new(params[:url] || params[:id])
27 | if get.success?
28 | img = Magick::Image.from_blob(get.body).first
29 | img.scale!(params[:s].to_f)
30 | img.crop!(params[:x].to_i, params[:y].to_i, params[:w].to_i, params[:h].to_i)
31 | content_type img.mime_type
32 | return img.to_blob
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/cropper/simple_get_response.rb:
--------------------------------------------------------------------------------
1 | # simple_get_response.rb: Wrapper for net/http
2 | # For details see: http://opendatachallenge.kbresearch.nl/
3 | # Copyright (C) 2011 R. van der Ark, Koninklijke Bibliotheek - National Library of the Netherlands
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | require "net/http"
19 | require "uri"
20 |
21 | class SimpleGetResponse
22 | attr_accessor :response, :http, :url
23 | def initialize(set_url)
24 | self.url = URI.parse(set_url)
25 | self.http = Net::HTTP.new(url.host, url.port)
26 | self.http.read_timeout = 30
27 | self.http.open_timeout = 30
28 | get_response(5)
29 | end
30 |
31 | def get_response(tries = 1)
32 | (0..tries).each do |i|
33 | success = true
34 | begin
35 | self.response = http.start {|http|
36 | http.request_get(url.request_uri) rescue success = false
37 | }
38 | break if success
39 | rescue
40 | puts "Try #{i} on #{self.url.request_uri}: #{$!}"
41 | sleep 1
42 | end
43 | end
44 |
45 | if response.kind_of?(Net::HTTPRedirection)
46 | initialize(redirect_url)
47 | end
48 | end
49 |
50 | def body
51 | return self.response.body
52 | end
53 |
54 | def success?
55 | case self.response
56 | when Net::HTTPSuccess
57 | return true
58 | else
59 | return false
60 | end
61 | end
62 |
63 | def redirect_url
64 | if response['location'].nil?
65 | response.body.match(/]+)\">/i)[1]
66 | else
67 | response['location']
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/json_alto/src/simple_get_response.rb:
--------------------------------------------------------------------------------
1 | # simple_get_response.rb: Wrapper for net/http
2 | # For details see: http://opendatachallenge.kbresearch.nl/
3 | # Copyright (C) 2011 R. van der Ark, Koninklijke Bibliotheek - National Library of the Netherlands
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | require "net/http"
19 | require "uri"
20 |
21 | class SimpleGetResponse
22 | attr_accessor :response, :http, :url
23 | def initialize(set_url)
24 | self.url = URI.parse(set_url)
25 | self.http = Net::HTTP.new(url.host, url.port)
26 | self.http.read_timeout = 30
27 | self.http.open_timeout = 30
28 | get_response(5)
29 | end
30 |
31 | def get_response(tries = 1)
32 | (0..tries).each do |i|
33 | success = true
34 | begin
35 | self.response = http.start {|http|
36 | http.request_get(url.request_uri) rescue success = false
37 | }
38 | break if success
39 | rescue
40 | puts "Try #{i} on #{self.url.request_uri}: #{$!}"
41 | sleep 1
42 | end
43 | end
44 |
45 | if response.kind_of?(Net::HTTPRedirection)
46 | initialize(redirect_url)
47 | end
48 | end
49 |
50 | def body
51 | return self.response.body
52 | end
53 |
54 | def success?
55 | case self.response
56 | when Net::HTTPSuccess
57 | return true
58 | else
59 | return false
60 | end
61 | end
62 |
63 | def redirect_url
64 | if response['location'].nil?
65 | response.body.match(/]+)\">/i)[1]
66 | else
67 | response['location']
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/static/alto_edit/js/interface.js:
--------------------------------------------------------------------------------
1 | var image_client = null;
2 | var viewer = null;
3 |
4 | // Array Remove - By John Resig (MIT Licensed)
5 | Array.prototype.remove = function(from, to) {
6 | var rest = this.slice((to || from) + 1 || this.length);
7 | this.length = from < 0 ? this.length + from : from;
8 | return this.push.apply(this, rest);
9 | };
10 |
11 | document.onkeydown = function(e) {
12 | var code, shift, ctrl = null;
13 | if(!e) code = window.event.keyCode; else code = e.keyCode || e.which;
14 | if(!e) ctrl = window.event.ctrlKey; else ctrl = e.ctrlKey;
15 | if(!e) shift = window.event.shiftKey; else shift = e.shiftKey;
16 |
17 | if(code == 83 && ctrl == 1) {
18 | viewer.saveCurrentUpdate();
19 | if(shift == 1) {
20 | if(confirm("Weet u zeker dat u deze pagina definitief wilt opslaan en verdergaan? Wijzigingen zijn hierna niet meer mogelijk"))
21 | $('update_form').insert(new Element("input", {"name": "finalize", "value": "true", "type": "hidden"}));
22 | else
23 | return false;
24 | }
25 | $('image_container').hide();
26 | $('ocr_container').hide();
27 | $('spinner_div').show();
28 |
29 | $('update_form').submit();
30 | return false;
31 | }
32 | }
33 |
34 | function altoFrom(version) {
35 | json_alto("reinitAltoText", version);
36 | }
37 |
38 |
39 | function confirmChanges() {
40 | viewer.saveCurrentUpdate();
41 | var i = $$('#update_form input').length - 1;
42 | if(i > 0) {
43 | if(confirm("De laatste wijzigingen zijn niet opgeslagen, wilt u eerste de wijzigingen opslaan?")) {
44 | $('image_container').hide(); $('ocr_container').hide(); $('spinner_div').show();
45 | $('update_form').submit();
46 | } else {
47 | $('image_container').hide(); $('ocr_container').hide(); $('spinner_div').show();
48 | return true;
49 | }
50 | } else {
51 | $('image_container').hide(); $('ocr_container').hide(); $('spinner_div').show();
52 | return true;
53 | }
54 | }
55 |
56 | function leadingZero(x) {
57 | var y = "" + x;
58 | var retStr = "";
59 | for(var i = 0; i < 4 - y.length; ++i)
60 | retStr += "0";
61 | return retStr + y;
62 | }
63 |
64 | function viewport() {
65 | if(document.body.clientWidth && document.body.clientHeight)
66 | return {width: document.body.clientWidth, height: document.body.clientHeight};
67 | else
68 | return {width: 950, height: 600};
69 | }
70 |
71 | function scaleWindows() {
72 | var dims = viewport();
73 | [['image_container', 1.9], ['ocr_container', 2.5]].each(function(x) {
74 | $(x[0]).style.width = (dims.width / x[1]) + "px";
75 | $(x[0]).style.height = (dims.height - 170) + "px";
76 | if(image_client && x[0] == 'image_container')
77 | image_client.updateDims();
78 | });
79 | }
80 |
81 | function preventDefault(e) {
82 | if (e.preventDefault) e.preventDefault();
83 | return false;
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/static/alto_edit/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
100 |
101 |
102 |
103 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |

120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/json_alto/web.rb:
--------------------------------------------------------------------------------
1 | # json_alto/web.rb: Simple converter from ALTO XML to JSON
2 | # For details see: http://opendatachallenge.kbresearch.nl/
3 | # Copyright (C) 2011 R. van der Ark, Koninklijke Bibliotheek - National Library of the Netherlands
4 | #
5 | # This program is free software: you can redistribute it and/or modify
6 | # it under the terms of the GNU General Public License as published by
7 | # the Free Software Foundation, either version 3 of the License, or
8 | # (at your option) any later version.
9 | #
10 | # This program is distributed in the hope that it will be useful,
11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 | # GNU General Public License for more details.
14 | #
15 | # You should have received a copy of the GNU General Public License
16 | # along with this program. If not, see .
17 |
18 | require "rubygems"
19 | require "sinatra"
20 | require "hpricot"
21 | require "json"
22 | require "src/simple_get_response"
23 |
24 | def correct_hyphenation(doc)
25 | #(/\-\s*$/, "-")
26 | textlines = (doc/"TextLine")
27 | (textlines/"String").remove_attr("SUBS_CONTENT")
28 | (textlines/"String").remove_attr("SUBS_TYPE")
29 | textlines.each_with_index do |textline, i|
30 | break if i == textlines.length - 1
31 | last_in_line = (textline/"String").last
32 | next_one = (textlines[i+1]/"String").first
33 |
34 | if last_in_line.attributes["CONTENT"] =~ /\-\s*$/
35 | last_in_line.set_attribute("CONTENT", last_in_line.attributes["CONTENT"].sub(/\-\s*$/, ""))
36 | last_in_line.set_attribute("SUBS_CONTENT", last_in_line.attributes["CONTENT"] + next_one.attributes["CONTENT"])
37 | last_in_line.set_attribute("SUBS_TYPE", "HypPart1")
38 | next_one.set_attribute("SUBS_CONTENT", last_in_line.attributes["CONTENT"] + next_one.attributes["CONTENT"])
39 | next_one.set_attribute("SUBS_TYPE", "HypPart2")
40 | if (textline/'HYP').length == 0
41 | est_hyp_width = (last_in_line.attributes["WIDTH"].to_i / (last_in_line.attributes["CONTENT"].length + 1.5)).to_i
42 | hpos = last_in_line.attributes["WIDTH"].to_i + last_in_line.attributes["HPOS"].to_i - est_hyp_width
43 | vpos = last_in_line.attributes["VPOS"].to_i + (last_in_line.attributes["HEIGHT"].to_i / 2).to_i
44 | last_in_line.after(%())
45 | end
46 | end
47 | end
48 | end
49 |
50 | def correct_whitespaces(doc)
51 | (doc/"SP").remove
52 | id_it = 1
53 | (doc/"TextLine").each do |textline|
54 | strings = (textline/'String')
55 | (0..strings.length-2).each do |i|
56 | hpos = strings[i].attributes["HPOS"].to_i + strings[i].attributes["WIDTH"].to_i
57 | width = strings[i+1].attributes["HPOS"].to_i - hpos
58 | vpos = ((strings[i].attributes["VPOS"].to_i + strings[i+1].attributes["VPOS"].to_i).to_f / 2.0).to_i
59 | id = id_it
60 | strings[i].after(%())
61 | id_it += 1
62 | end
63 | end
64 | end
65 |
66 |
67 |
68 | get "/" do
69 | content_type "text/javascript", 'charset' => 'utf-8'
70 | get = SimpleGetResponse.new(params[:url])
71 | timestamp = params[:timestamp] ? params[:timestamp].to_i : Time.now.to_i
72 | if get.success?
73 | doc = Hpricot.XML(get.body)
74 | alto = {
75 | :identifier => (doc/'//fileName').first.innerText,
76 | :page_width => (doc/'//Page').first.attributes["WIDTH"].to_i,
77 | :page_height => (doc/'//Page').first.attributes["HEIGHT"].to_i,
78 | :blocks => (doc/'//TextBlock').map do |tb|
79 | {
80 | :id => tb.attributes["ID"],
81 | :hpos => tb.attributes["HPOS"].to_i,
82 | :vpos => tb.attributes["VPOS"].to_i,
83 | :width => tb.attributes["WIDTH"].to_i,
84 | :height => tb.attributes["HEIGHT"].to_i,
85 | :lines => (tb/'//TextLine').map do |tl|
86 | {
87 | :id => tl.attributes["ID"],
88 | :hpos => tl.attributes["HPOS"].to_i,
89 | :vpos => tl.attributes["VPOS"].to_i,
90 | :width => tl.attributes["WIDTH"].to_i,
91 | :height => tl.attributes["HEIGHT"].to_i,
92 | :strings => (tl/'//String').map do |s|
93 | {
94 | :id => s.attributes["ID"],
95 | :hpos => s.attributes["HPOS"].to_i,
96 | :vpos => s.attributes["VPOS"].to_i,
97 | :height => s.attributes["HEIGHT"].to_i,
98 | :width => s.attributes["WIDTH"].to_i,
99 | :content => s.attributes["CONTENT"],
100 | :wc => s.attributes["WC"],
101 | :updated => false
102 | }
103 | end
104 | }
105 | end
106 | }
107 | end
108 | }
109 |
110 | return "#{params[:callback]}(#{JSON alto});" if params[:callback]
111 | return JSON alto
112 | end
113 | end
114 |
115 | post "/" do
116 | content_type :xml
117 | if params[:url]
118 | get = SimpleGetResponse.new(params[:url])
119 | if get.success?
120 | doc = Hpricot.XML(get.body)
121 | (params[:insert] || []).each do |key, values|
122 | (block_id, line_id, word_id) = key.split(":")
123 | block = (doc/"//TextBlock[@ID=#{block_id}]").first
124 | line = (block/"/TextLine[@ID=#{line_id}]").first
125 | after = (line/"/String[@ID=#{values["after"].gsub(/.+:/, "")}]").first
126 | after.after(%())
127 | end
128 |
129 | (params[:update] || []).each do |key, values|
130 | if values["delete"]
131 | (block_id, line_id, word_id) = key.split(":")
132 | block = (doc/"//TextBlock[@ID=#{block_id}]").first
133 | line = (block/"/TextLine[@ID=#{line_id}]").first
134 | string = (line/"/String[@ID=#{word_id}]").first
135 | deleted_node = (line/"/String[@ID=#{string.attributes["ID"]}]")
136 |
137 | deleted_node.after("")
138 | deleted_node.remove
139 | end
140 | end
141 |
142 |
143 |
144 |
145 |
146 | updates = params[:update]
147 | (updates||[]).each do |key, update|
148 | (block_id, line_id, word_id) = key.split(":")
149 | block = (doc/"//TextBlock[@ID=#{block_id}]").first
150 | line = (block/"/TextLine[@ID=#{line_id}]").first
151 | string = (line/"/String[@ID=#{word_id}]").first
152 | if string && string.has_attribute?("SUBS_CONTENT")
153 | remainder = string.attributes["SUBS_CONTENT"].sub(string.attributes["CONTENT"], "")
154 | string.set_attribute("SUBS_CONTENT", update["content"] + remainder) if string.attributes["SUBS_TYPE"] == "HypPart1"
155 | string.set_attribute("SUBS_CONTENT", remainder + update["content"]) if string.attributes["SUBS_TYPE"] == "HypPart2"
156 | end
157 | string.set_attribute("CONTENT", update["content"]) if string
158 | string.set_attribute("HPOS", update["hpos"]) if string && update["hpos"]
159 | string.set_attribute("WIDTH", update["width"]) if string && update["width"]
160 | end
161 |
162 | # Fourth run: normalize white-spaces
163 | correct_whitespaces(doc)
164 | # Fifth run: correct Hyphenation
165 | correct_hyphenation(doc)
166 |
167 | x = ""
168 | doc.output(x)
169 | return x
170 | end
171 | end
172 | end
173 |
--------------------------------------------------------------------------------
/static/alto_edit/js/image_client_jp2.js:
--------------------------------------------------------------------------------
1 | /*
2 | * image_client_jp2.js: Client for an image service which can zoom and crop via HTTP GET
3 | * For details see: http://opendatachallenge.kbresearch.nl/
4 | * Copyright (C) 2011 R. van der Ark, Koninklijke Bibliotheek - National Library of the Netherlands
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 | var ImageClient = Class.create({
20 | initialize: function(container_id, urn, fullImageDims, words, spinner_src, zc, altUrl, usefullUrl) {
21 | this.imgUrl = (altUrl ? altUrl : "http://imageviewer.kb.nl/ImagingService/imagingService");
22 | this.container = $(container_id);
23 | if(usefullUrl) {
24 | this.settings = {
25 | colour: "89c5e7",
26 | id: urn,
27 | r: 0,
28 | s: 0.1,
29 | x: 0,
30 | y: 0,
31 | w: parseInt(this.container.style.width),
32 | h: parseInt(this.container.style.height),
33 | useresolver: "false"
34 | }
35 | } else {
36 | this.settings = {
37 | colour: "89c5e7",
38 | coords: urn + ":alto",
39 | words: (words ? words : ""),
40 | id: urn + ":image",
41 | r: 0,
42 | s: 0.1,
43 | x: 0,
44 | y: 0,
45 | w: parseInt(this.container.style.width),
46 | h: parseInt(this.container.style.height)
47 | }
48 | }
49 |
50 |
51 | this.spinner = new Element("img", {
52 | "src": (spinner_src ? spinner_src : ""),
53 | "style": "margin-top: " + parseInt(this.container.style.height) / 2 + "; margin-left: " + parseInt(this.container.style.width) / 2
54 | });
55 | this.container.insert(this.spinner);
56 | this.fullImageDims = fullImageDims;
57 | this.settings.s = parseInt(this.container.style.height) / this.fullImageDims.h;
58 | this.zoomCorrection = (zc ? zc : 1.0);
59 | this.locked = false;
60 | this.bufferedImage = null;
61 | this.defineCallbacks();
62 | this.overlays = [];
63 | },
64 |
65 | updateDims: function() {
66 | this.settings.w = parseInt(this.container.style.width);
67 | this.settings.h = parseInt(this.container.style.height)
68 | this.render();
69 | },
70 |
71 | render: function() {
72 | var _this = this;
73 | if(!this.locked) {
74 | this.bufferedImage = new Image();
75 | this.settings.s *= this.zoomCorrection;
76 | this.bufferedImage.src = this.imgUrl + "?" + Object.toQueryString(this.settings);
77 | this.settings.s /= this.zoomCorrection;
78 | this.locked = true;
79 | this.spinner.show();
80 | window.setTimeout(function() { _this.render() }, 5);
81 | } else {
82 | if(this.bufferedImage.complete) {
83 | this.spinner.hide();
84 | this.renderImage();
85 | this.locked = false;
86 | } else {
87 | window.setTimeout(function() { _this.render() }, 5);
88 | }
89 | }
90 | },
91 | renderImage: function() {
92 | this.container.style.backgroundImage = "url(" + this.bufferedImage.src + ")";
93 | this.container.style.backgroundPosition = "0 0";
94 | this.renderOverlays();
95 | },
96 | zoomIn: function() {
97 | this.setZoom(this.settings.s * 1.1);
98 | this.setPosition(this.settings.x, this.settings.y);
99 | this.render();
100 | },
101 | zoomOut: function() {
102 | this.setZoom(this.settings.s * 0.9);
103 | this.setPosition(this.settings.x, this.settings.y);
104 | this.render();
105 | },
106 | zoomBack: function(isVertical) {
107 | if(isVertical)
108 | this.settings.s = parseInt(this.container.style.width) / this.fullImageDims.w;
109 | else
110 | this.settings.s = parseInt(this.container.style.height) / this.fullImageDims.h;
111 | this.setPosition(0,0);
112 | this.render();
113 | },
114 | setPosition: function(x, y) {
115 | this.settings.x = x + parseInt(this.container.style.width) > (this.fullImageDims.w * this.settings.s) ? (this.fullImageDims.w * this.settings.s) - parseInt(this.container.style.width) : x;
116 | this.settings.y = y + parseInt(this.container.style.height) > (this.fullImageDims.h * this.settings.s) ? (this.fullImageDims.h * this.settings.s) - parseInt(this.container.style.height) : y;
117 | this.settings.x = this.settings.x < 0 ? 0 : this.settings.x;
118 | this.settings.y = this.settings.y < 0 ? 0 : this.settings.y;
119 | },
120 | setZoom: function(z) {
121 | this.settings.s = z;
122 | },
123 | getZoom: function() {
124 | return this.settings.s;
125 | },
126 | getTop: function() {
127 | return this.settings.y;
128 | },
129 | getLeft: function() {
130 | return this.settings.x;
131 | },
132 | renderAreaBox: function(elem) {
133 | if(
134 | parseInt(elem.style.top) > this.container.offsetTop + parseInt(this.container.style.height) ||
135 | parseInt(elem.style.top) + parseInt(elem.style.height) < this.container.offsetTop ||
136 | parseInt(elem.style.left) > this.container.offsetLeft + parseInt(this.container.style.width) ||
137 | parseInt(elem.style.left) + parseInt(elem.style.width) < this.container.offsetLeft
138 | )
139 | elem.hide();
140 | else {
141 | var wdiff = (parseInt(elem.style.left) + parseInt(elem.style.width)) - (this.container.offsetLeft + parseInt(this.container.style.width));
142 | var hdiff = (parseInt(elem.style.top) + parseInt(elem.style.height)) - (this.container.offsetTop + parseInt(this.container.style.height));
143 | if(wdiff > 0) elem.style.width = parseInt(elem.style.width) - wdiff;
144 | if(hdiff > 0) elem.style.height = parseInt(elem.style.height) - hdiff;
145 | var tdiff = parseInt(elem.style.top) - this.container.offsetTop;
146 | if(tdiff < 0) {
147 | elem.style.top = this.container.offsetTop;
148 | elem.style.height = parseInt(elem.style.height) + tdiff;
149 | }
150 | var ldiff = parseInt(elem.style.left) - this.container.offsetLeft;
151 | if(ldiff < 0) {
152 | elem.style.left = this.container.offsetLeft;
153 | elem.style.width = parseInt(elem.style.width) + ldiff;
154 | }
155 | elem.show();
156 | }
157 | },
158 | hideOverlays: function() {
159 | this.overlays.each(function(o) { o.div.hide()});
160 | },
161 | renderOverlays: function() {
162 | var _this = this;
163 | this.overlays.each(function(hl) {
164 | hl.div.style.width = hl.w * _this.settings.s;
165 | hl.div.style.height = hl.h * _this.settings.s;
166 | hl.div.style.left = (hl.x * _this.settings.s) + _this.container.offsetLeft - _this.settings.x;
167 | hl.div.style.top = (hl.y * _this.settings.s) + _this.container.offsetTop - _this.settings.y;
168 | _this.renderAreaBox(hl.div);
169 | });
170 | },
171 | addOverlay: function(x, y, w, h, c, mouseover, id, callback) {
172 | var hlDiv = new Element("div", {
173 | "id": id ? id : "",
174 | "style":
175 | "position: absolute; background-color: " + c + "; opacity: 0.2; line-height: 0px; filter: alpha(opacity=20);"
176 | });
177 | hlDiv.insert(" ");
178 | this.container.insert(hlDiv);
179 | hlDiv.hide();
180 | if(callback) hlDiv.observe("click", callback);
181 | if(mouseover) {
182 | if(Prototype.Browser.IE) hlDiv.style.filter = "alpha(opacity=0)";
183 | else hlDiv.style.backgroundColor = "transparent";
184 |
185 | hlDiv.observe("mouseover", function(e) {
186 | if(Prototype.Browser.IE) this.style.filter = "alpha(opacity=20)";
187 | else this.style.backgroundColor = c;
188 | this.style.cursor = "pointer";
189 | });
190 | hlDiv.observe("mouseout", function(e) {
191 | if(Prototype.Browser.IE) this.style.filter = "alpha(opacity=0)";
192 | else this.style.backgroundColor = "transparent";
193 | });
194 | }
195 | var hl = {x: x, y: y, w: w, h: h, div: hlDiv};
196 | this.overlays.push(hl);
197 | this.renderOverlays();
198 | return hl;
199 | },
200 | dropOverlay: function(hl) {
201 | hl.div.remove();
202 | this.overlays.splice(this.overlays.indexOf(hl), 1);
203 | },
204 | defineCallbacks: function() {
205 | this.lastPosition = null;
206 | var _this = this;
207 | this.container.observe("mousedown", function(e) { _this.mouseDown(e); });
208 | this.container.observe("mousemove", function(e) { _this.mouseMove(e); });
209 | this.container.observe("mouseup", function(e) { _this.mouseUp(e); });
210 | var mousewheelevt=(/Firefox/i.test(navigator.userAgent))? "DOMMouseScroll" : "mousewheel";
211 | if(Prototype.Browser.IE || Prototype.Browser.Opera)
212 | document.attachEvent("on"+mousewheelevt, function(e){ _this.mouseScroll(e) });
213 | else
214 | document.addEventListener(mousewheelevt, function(e){ _this.mouseScroll(e) }, false);
215 | },
216 | mouseScroll: function(e) {
217 | if(e.clientX > this.container.offsetLeft && e.clientY > this.container.offsetTop &&
218 | e.clientX < this.container.offsetLeft + parseInt(this.container.style.width) &&
219 | e.clientY < this.container.offsetTop + parseInt(this.container.style.height) && !this.locked) {
220 | var evt = window.event || e;
221 | var delta = evt.detail ? -(evt.detail) : evt.wheelDelta;
222 | if(delta < 0)
223 | this.zoomOut();
224 | else
225 | this.zoomIn();
226 | }
227 | },
228 | mouseDown: function(e) {
229 | if(Prototype.Browser.IE) { e.returnValue = false; } else { e.preventDefault(); }
230 | this.container.style.cursor = "move";
231 | this.lastPosition = {x: e.clientX, y: e.clientY};
232 | },
233 | mouseMove: function(e) {
234 | if(Prototype.Browser.IE) { e.returnValue = false; } else { e.preventDefault(); }
235 | if(this.lastPosition) {
236 | this.hideOverlays();
237 | var movement = {x: this.lastPosition.x - e.clientX, y: this.lastPosition.y - e.clientY};
238 | this.container.style.backgroundPosition = -(movement.x) + " " + -(movement.y);
239 | }
240 | },
241 | mouseUp: function(e) {
242 | if(Prototype.Browser.IE) { e.returnValue = false; } else { e.preventDefault(); }
243 | if(this.lastPosition) {
244 | var movement = {x: this.lastPosition.x - e.clientX, y: this.lastPosition.y - e.clientY};
245 | this.renderOverlays();
246 | if(movement.x != 0 || movement.y != 0) {
247 |
248 | this.setPosition(this.settings.x + movement.x, this.settings.y + movement.y);
249 | this.render();
250 | }
251 | this.container.style.cursor = "default";
252 | this.lastPosition = null;
253 | }
254 | }
255 | });
256 |
257 |
--------------------------------------------------------------------------------
/static/alto_edit/js/seg_edit.js:
--------------------------------------------------------------------------------
1 | var ieKeyCode = 0;
2 |
3 | function delKeyPressedIE(e) {
4 | if(!e)
5 | e = window.event;
6 | ieKeyCode = e.keyCode;
7 | }
8 |
9 |
10 | var SegEdit = Class.create({
11 | initialize: function(altoLine, imageClient, lineDiv, altoViewer) {
12 |
13 | var _this = this
14 | var vp = viewport();
15 | this.altoViewer = altoViewer;
16 | this.deletions = [];
17 | this.resizings = [];
18 | this.insertions = [];
19 | this.contentUpdates = [];
20 | this.origDims = {};
21 | this.resizeMode = "none";
22 | this.selectedRect = null;
23 | this.lastX = 0;
24 | this.lineDiv = lineDiv;
25 | this.splittingSegment = null;
26 | this.wordPart = 0;
27 | this.altoLine = altoLine;
28 |
29 | var max = 0;
30 | lineDiv.childElements().each(function(s) {
31 | if(s.id.match(/appendedword/)) {
32 | var current = parseInt(s.id.replace(/.+_/, ""));
33 | max = current >= max ? (current+1) : max;
34 | }
35 | });
36 | this.appendedCount = max;
37 |
38 | this.shade = new Element("div", {
39 | "style": "position: absolute; top:0;left:0;width:" + vp.width + "px;height:" + vp.height + "px; background-color: black; opacity: 0.6; filter: alpha(opacity=60);"
40 | });
41 | this.container = new Element("div", {
42 | "style": "position: absolute; top: 50; left: 50; width:" + (vp.width-100)+"px;height:"+(vp.height-100)+"px;background-color: white;"
43 | });
44 |
45 | this.delButton = new Element("button", {
46 | "disabled": true,
47 | "style": "margin-left: 50px; margin-top: 50px"
48 | });
49 |
50 | this.delButton.insert("Verwijder segment");
51 | this.delButton.observe("click", function(e) {
52 | _this.deleteSegment();
53 | });
54 |
55 | this.container.insert(this.delButton);
56 |
57 | this.delAllButton = new Element("button");
58 | this.delAllButton.insert("Verwijder alle lege segmenten");
59 | this.delAllButton.observe("click", function(e) {
60 | if(confirm("Weet u zeker dat u alle lege segmenten wilt verwijderen?"))
61 | _this.deleteEmptySegments();
62 | });
63 | this.container.insert(this.delAllButton);
64 |
65 | var imgSettings = {
66 | x: altoLine.hpos,
67 | y: altoLine.vpos,
68 | w: altoLine.width,
69 | h: altoLine.height,
70 | s: imageClient.zoomCorrection,
71 | coords: imageClient.settings.coords,
72 | id: imageClient.settings.id
73 | };
74 | this.img = new Element("div", {
75 | "style": "background-image: url("+ imageClient.imgUrl + "?" + Object.toQueryString(imgSettings) +");" +
76 | "width:" + altoLine.width + "px;" + "height:" + altoLine.height + "px;" +
77 | "margin-top: 100px; margin-left: 50px"
78 | });
79 |
80 | if(parseInt(this.img.style.width) > (parseInt(this.container.style.width) - 200)) {
81 | this.container.style.width = (parseInt(this.img.style.width) + 200) + "px";
82 | this.shade.style.width = (parseInt(this.container.style.width) + 100) + "px";
83 | }
84 |
85 | this.splitterDiv = new Element("div", {
86 | "style": "position: relative; width: 0px; height: " + (parseInt(this.img.style.height) - 4) + "px; border-left: 2px solid black"
87 | });
88 | this.splitterDiv.insert(" ");
89 |
90 | this.img.observe("mousemove", function(e) {
91 | if(_this.selectedRect && _this.resizeMode != "none")
92 | return _this.mousemoveOnSelectedRect(_this.selectedRect, e);
93 | });
94 | this.img.observe("mouseup", function(e) {
95 | if(_this.selectedRect && _this.resizeMode != "none")
96 | _this.resizeMode = "none";
97 | });
98 |
99 | this.word_container = new Element("div", {
100 | "style": "width: " + this.img.style.width + "; height: 100px; margin-top: 15px; margin-left: 50px;"
101 | });
102 |
103 | this.img.insert(" ");
104 | this.shade.insert(" ");
105 | this.container.insert(this.img);
106 | this.container.insert(this.word_container);
107 | $$("body")[0].insert(this.shade);
108 | $$("body")[0].insert(this.container);
109 | this.rects = [];
110 | var _this = this;
111 | altoLine.strings.each(function(word) {
112 | var span = lineDiv.childElements().detect(function(e) { return e.id.replace(/.*:/, "") == word.id});
113 | if(span) {
114 | var color = span.innerHTML == " " || span.innerHTML == " " ? "red" : "green";
115 | _this.appendRect(word, span.innerHTML, color);
116 | }
117 | });
118 | this.saveButton = new Element("button");
119 | this.saveButton.observe("click", function() {
120 | _this.saveToAltoViewer();
121 | _this.terminate();
122 | });
123 | this.cancelButton = new Element("button", {"style": "margin-left: 50px"});
124 | this.cancelButton.observe("click", function() {
125 | _this.terminate();
126 | });
127 | this.saveButton.insert("Ok");
128 | this.cancelButton.insert("Annuleren");
129 | this.container.insert(this.cancelButton);
130 | this.container.insert(this.saveButton);
131 | document.onkeyup = function(e) {
132 | var code = null;
133 | if(!e) code = window.event.keyCode;
134 | else code = e.keyCode || e.which;
135 | if(code == 46) _this.deleteSegment();
136 | }
137 | },
138 |
139 | deleteEmptySegments: function() {
140 | var emptyRects = this.rects.select(function(r) { return r.style.borderColor.match(/red/) });
141 | var _this = this;
142 | emptyRects.each(function(r) {
143 | _this.selectedRect = r;
144 | _this.deleteSegment(true);
145 | });
146 | },
147 |
148 | deleteSegment: function(noConfirm) {
149 | if(this.selectedRect && this.selectedRect.style.borderColor.match(/red/) && (noConfirm || confirm("Weet u zeker dat u dit segement wilt verwijderen?"))) {
150 | this.deletions.push(this.lineDiv.ancestors()[0].id + ":" + this.lineDiv.id + ":" + this.selectedRect.id.replace(":seg", ""));
151 | this.rects.remove(this.rects.indexOf(this.selectedRect));
152 | this.selectedRect.remove();
153 | this.selectedRect = null;
154 | this.delButton.disabled = true;
155 | }
156 | },
157 |
158 | appendRect: function(word, wordContent, color) {
159 | var _this = this;
160 | var rect = new Element("div", {
161 | "style": "background-color: " + color + "; opacity: 0.2; line-height: 0px; filter: alpha(opacity=20);" +
162 | "height: " + (Prototype.Browser.IE ? parseInt(this.img.style.height) : parseInt(this.img.style.height) - 4) + "px; width: " + word.width + "px;" + "position: absolute;" +
163 | "top: " + this.img.offsetTop + "px; left: " + (this.img.offsetLeft + (word.hpos - this.altoLine.hpos)) + "px; " +
164 | "border: 2px solid " + color + ";cursor:pointer",
165 | "id": word.id + ":seg"
166 | });
167 | rect.observe("click", function(e) {
168 | _this.rectOnClick(this, e, false, color == "red");
169 | });
170 | rect.insert(" ");
171 | this.img.insert(rect);
172 | this.rects.push(rect);
173 | this.origDims[rect.id] = {left: parseInt(rect.style.left), width: parseInt(rect.style.width)};
174 |
175 | var word_span = new Element("div", {
176 | "style": "position: absolute; left: " + rect.style.left,
177 | "id": word.id + ":segwrd"
178 | });
179 | word_span.insert(wordContent);
180 | this.word_container.insert(word_span);
181 | rect.observe("mouseover", function(e) {
182 | word_span.style.color = "green";
183 | });
184 | rect.observe("mouseout", function(e) {
185 | word_span.style.color = "black";
186 | })
187 | this.addSplitButtons(word.id, word_span);
188 | },
189 |
190 | addSplitButtons: function(word_id, word_span) {
191 | word_span.innerHTML = word_span.innerHTML.replace(/\s+/g, " ");
192 | var i = 0;
193 | var _this = this;
194 | word_span.childElements().each(function(b) {
195 | b.addClassName("button_" + i);
196 | b.observe("click", function(e) {
197 | _this.splitSegment(word_id + ":seg", this);
198 | });
199 | i++;
200 | });
201 | },
202 |
203 | splitSegment: function(segId, btn) {
204 | this.rectOnClick($(segId), null, true);
205 | this.splittingSegment = segId;
206 | this.wordPart = parseInt(btn.readAttribute("class").replace("button_", ""));
207 | $(this.splittingSegment).insert(this.splitterDiv);
208 | },
209 |
210 | splitSegmentAt: function(hpos) {
211 | var segDiv1 = $(this.splittingSegment);
212 | var adjustedWidth = (hpos - parseInt(segDiv1.style.left)) - (Prototype.Browser.IE ? 0 : 2);
213 | var newWidth = parseInt(segDiv1.style.width) - adjustedWidth - (Prototype.Browser.IE ? 0 : 2);
214 | segDiv1.style.width = adjustedWidth + "px";
215 | var leftContent = "";
216 | var wordSpan = $(this.splittingSegment.replace(":seg", ":segwrd"));
217 | var updatedSegmentId = this.splittingSegment.replace(":seg", "");
218 | wordSpan.childElements().each(function(btn) { btn.stopObserving("click"); btn.remove(); });
219 | var rightContent = wordSpan.innerHTML;
220 | for(var i = 0; i < this.wordPart + 1; ++i) {
221 | leftContent += rightContent.replace(/ .+/, " ");
222 | rightContent = rightContent.replace(/^.+? /, "");
223 | }
224 | rightContent = rightContent.replace(/ /g, " ");
225 | wordSpan.innerHTML = leftContent.strip();
226 | this.addSplitButtons(updatedSegmentId, wordSpan);
227 | this.wordPart = null;
228 | this.splittingSegment = null;
229 | this.splitterDiv.remove();
230 |
231 | this.rectOnClick(segDiv1);
232 | this.appendRect({
233 | "id": "appendedword_" + this.lineDiv.id + "_" + this.appendedCount,
234 | "hpos": hpos + this.altoLine.hpos - parseInt(this.img.style.marginLeft),
235 | "width": newWidth }, rightContent, "green");
236 |
237 | this.saveResizing(segDiv1);
238 | this.insertions.push({
239 | "id": "appendedword_" + this.lineDiv.id + "_" + this.appendedCount,
240 | "hpos": hpos + 2 + this.altoLine.hpos - parseInt(this.img.style.marginLeft),
241 | "width": newWidth,
242 | "vpos": this.altoLine.vpos,
243 | "height": this.altoLine.height,
244 | "content": rightContent
245 | });
246 | this.contentUpdates.push({
247 | "id": updatedSegmentId,
248 | "content": leftContent.strip()
249 | });
250 | this.appendedCount++;
251 | },
252 |
253 | rectOnClick: function(rect, e, suppressOpacityChange, mayDelete) {
254 | if(this.splittingSegment) {
255 | var absX = e.clientX - this.img.cumulativeOffset()[0] + parseInt(this.img.style.marginLeft) + document.body.scrollLeft;
256 | this.splitSegmentAt(absX + (Prototype.Browser.IE ? 3 : 0));
257 | } else {
258 | var _this = this;
259 | this.rects.each(function(r) {
260 | var span = $(r.id.replace(":seg", ":segwrd"))
261 | var color = span.innerHTML == " " || span.innerHTML == " " ? "red" : "green";
262 |
263 | r.style.border = "2px solid " + color;
264 | r.style.opacity = "0.2";
265 | r.style.filter = "alpha(opacity=20)";
266 | r.style.backgroundColor = color;
267 | r.style.cursor = "pointer";
268 | r.stopObserving("mousemove");
269 | r.stopObserving("mouseup");
270 | r.stopObserving("mousedown");
271 | });
272 | rect.style.border = "2px solid " + rect.style.backgroundColor;
273 | if(suppressOpacityChange) {
274 | rect.style.opacity = "0.6";
275 | rect.style.filter = "alpha(opacity=60)";
276 | } else {
277 | rect.style.opacity = "1.0";
278 | rect.style.filter = "alpha(opacity=100)";
279 | rect.style.backgroundColor = "transparent";
280 | }
281 | this.selectedRect = rect;
282 | rect.observe("mousemove", function(e) { return _this.mousemoveOnSelectedRect(rect, e); });
283 | rect.observe("mousedown", function(e) { _this.mousedownOnSelectedRect(rect, e); });
284 | rect.observe("mouseup", function(e) { _this.mouseupOnSelectedRect(rect, e); });
285 | if(mayDelete)
286 | this.delButton.disabled = false;
287 | else
288 | this.delButton.disabled = true;
289 | }
290 | },
291 |
292 | delKeyPressed: function(keyCode) {
293 | if(keyCode == 46)
294 | this.deleteSegment();
295 | },
296 |
297 | mousemoveOnSelectedRect: function(rect, e) {
298 | var margin = 15;
299 | var relX = e.clientX - rect.cumulativeOffset()[0] + document.body.scrollLeft;
300 | var absX = e.clientX - this.img.cumulativeOffset()[0] + parseInt(this.img.style.marginLeft) + document.body.scrollLeft;
301 | var movement = (relX - this.lastX);
302 | if(this.splittingSegment != null) {
303 | this.splitterDiv.style.left = relX + "px";
304 | } else if(this.resizeMode == "none") {
305 | if(relX < margin) {
306 | rect.style.cursor = "col-resize";
307 | } else if(relX > parseInt(rect.style.width) - margin){
308 | rect.style.cursor = "col-resize";
309 | } else {
310 | rect.style.cursor = "pointer";
311 | }
312 | } else if(movement != 0) {
313 | this.img.childElements().each(function(r) { if(r != rect) r.hide()});
314 | if(this.resizeMode == "w-resize") {
315 | var oldX = parseInt(rect.style.left);
316 | rect.style.left = absX + "px";
317 | rect.style.width = (parseInt(rect.style.width) + (oldX - parseInt(rect.style.left))) + "px";
318 | $(rect.id.replace(":seg", ":segwrd")).style.left = rect.style.left;
319 | } else if(this.resizeMode == "e-resize") {
320 | rect.style.width = (parseInt(rect.style.width) + movement) + "px";
321 | var resizingId = this.lineDiv.ancestors()[0].id + ":" + this.lineDiv.id + ":" + rect.id.replace(":seg", "");
322 | }
323 | this.img.childElements().each(function(r) { if(r != rect) r.show()});
324 | }
325 |
326 | this.lastX = relX;
327 | return preventDefault(e);
328 | },
329 |
330 | mousedownOnSelectedRect: function(rect, e) {
331 | var margin = 15;
332 | var relX = e.clientX - rect.cumulativeOffset()[0] + document.body.scrollLeft;
333 | if(relX < margin) {
334 | this.resizeMode = "w-resize";
335 | } else if(relX > parseInt(rect.style.width) - margin){
336 | this.resizeMode = "e-resize";
337 | } else {
338 | this.resizeMode = "pointer";
339 | }
340 | this.lastX = relX;
341 | },
342 |
343 | saveResizing: function(rect) {
344 | var _this = this;
345 | var idPart1 = this.lineDiv.ancestors()[0].id + ":" + this.lineDiv.id + ":";
346 | var resizing = this.resizings.detect(function(r) { return r.id.replace(idPart1, "") + ":seg" == rect.id});
347 | if(resizing) {
348 | resizing.width = parseInt(rect.style.width) - this.origDims[rect.id].width + 2;
349 | resizing.left = parseInt(rect.style.left) - this.origDims[rect.id].left + 2;
350 | } else {
351 | this.resizings.push({
352 | "id": idPart1 + rect.id.replace(":seg", ""),
353 | "width": parseInt(rect.style.width) - this.origDims[rect.id].width + 2,
354 | "left": parseInt(rect.style.left) - this.origDims[rect.id].left + 2
355 | });
356 | }
357 | },
358 |
359 | mouseupOnSelectedRect: function(rect, e) {
360 | if(this.selectedRect && !this.splittingSegment) {
361 | this.saveResizing(this.selectedRect);
362 | }
363 | this.resizeMode = "none";
364 | },
365 |
366 | saveToAltoViewer: function() {
367 | this.altoViewer.updateSegments(this.deletions, this.resizings, this.insertions, this.contentUpdates, this.lineDiv);
368 | },
369 |
370 | terminate: function() {
371 | this.altoViewer.lockLineClick = false;
372 | this.rects.each(function(r) {
373 | r.stopObserving("click");
374 | r.stopObserving("mouseout");
375 | r.stopObserving("mouseover");
376 | r.stopObserving("mousemove");
377 | r.stopObserving("mousedown");
378 | r.stopObserving("mouseup");
379 | r.remove();
380 | });
381 | this.delButton.remove();
382 | this.img.stopObserving("mousemove");
383 | this.img.stopObserving("mouseup");
384 | this.img.remove();
385 | this.word_container.remove();
386 | this.cancelButton.stopObserving("click");
387 | this.saveButton.stopObserving("click");
388 | this.cancelButton.remove();
389 | this.saveButton.remove();
390 | this.container.remove();
391 | this.shade.remove();
392 | document.onkeyup = null;
393 | }
394 | });
395 |
--------------------------------------------------------------------------------
/static/alto_edit/js/alto_view.js:
--------------------------------------------------------------------------------
1 | /*
2 | * alto_view.js: Viewer and editor for ALTO represented as JSON
3 | * For details see: http://opendatachallenge.kbresearch.nl/
4 | * Copyright (C) 2011 R. van der Ark, Koninklijke Bibliotheek - National Library of the Netherlands
5 | *
6 | * This program is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * This program is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | var AltoView = Class.create({
21 | initialize: function(alto, image_client, text_container, words, zc) {
22 | this.alto = alto;
23 | this.words = (words ? words.toLowerCase().split(" ") : []);
24 | this.imageClient = image_client;
25 | this.imageContainer = this.imageClient.container;
26 | this.textContainer = $(text_container);
27 | this.lineHighlight = null;
28 | this.blockAreas = [];
29 | this.updates = [];
30 | this.readorder = [];
31 | this.current_edit = null;
32 | this.lockLineClick = false;
33 | this.broadestLine = 0;
34 | this.renderText();
35 | },
36 |
37 | renderText: function() {
38 | var _this = this;
39 | this.alto.blocks.each(function(textBlock) {
40 | _this.renderTextBlock(textBlock);
41 | _this.blockAreas.push(_this.imageClient.addOverlay(
42 | textBlock.hpos,
43 | textBlock.vpos,
44 | textBlock.width,
45 | textBlock.height,
46 | "red",
47 | true,
48 | textBlock.id + "_box",
49 | function(e) {
50 | _this.textContainer.scrollTop = $(this.id.replace("_box", "")).offsetTop - (Prototype.Browser.IE ? 0 : _this.textContainer.offsetTop);
51 | new Effect.Highlight(this.id.replace("_box", ""), { startcolor: '#ffff99',endcolor: '#ffffff' });
52 | _this.imageClient.setPosition(textBlock.hpos * _this.imageClient.getZoom(), (textBlock.vpos * _this.imageClient.getZoom()));
53 | _this.imageClient.render();
54 | }
55 | ));
56 | });
57 | },
58 |
59 | renderTextBlock: function(textBlock) {
60 | var _this = this;
61 | var textDiv = new Element("div", {"id": textBlock.id});
62 | textBlock.lines.each(function(textLine) {
63 | var lineDiv = new Element("div", {"id": textLine.id, "class": "altoLineDiv"});
64 | lineDiv.observe("mouseover", function(e) {
65 | this.style.backgroundColor = "#dfd";
66 | this.style.cursor = "pointer";
67 | _this.highlightTextLine(textLine);
68 | });
69 | lineDiv.observe("mousemove", function(e) {
70 | $$('.seg_corrector').each(Element.hide);
71 | if(e.clientX > (this.cumulativeOffset()[0] + this.getWidth() - 40))
72 | $(this.id + ":corrector1").show();
73 | });
74 | _this.appendSegLink(lineDiv);
75 | textLine.strings.each(function(word) {
76 | if(!word["delete"]) {
77 | _this.readorder.push(textBlock.id + ":" + textLine.id + ":" + word.id);
78 | var span = new Element("span", {"id": textBlock.id + ":" + textLine.id + ":" + word.id});
79 | span.insert(word.content);
80 | if(word.updated)
81 | span.style.color = "green";
82 |
83 | lineDiv.insert(span);
84 | lineDiv.insert(" ");
85 | span.observe("click", function(e) {
86 | _this.editWord(this);
87 | });
88 | span.observe("mouseover", function(e) {
89 | this.style.backgroundColor = "#ddf";
90 | this.style.cursor = "pointer";
91 | _this.highlightWord(word);
92 | });
93 | span.observe("mouseout", function(e) {
94 | this.style.backgroundColor = "transparent";
95 | });
96 | }
97 | });
98 | lineDiv.observe("click", function(e) {
99 | _this.zoomToLineAt(textLine, textBlock, _this.scrolledLinePos(this));
100 | _this.highlightTextLine(textLine);
101 | if(!this.childElements().detect(function(e) { return e.id.match(/_input$/); }) && !_this.lockLineClick)
102 | _this.editWord(this.childElements()[0]);
103 | });
104 |
105 | lineDiv.observe("mouseout", function(e) {
106 | this.style.backgroundColor = "transparent";
107 | });
108 | _this.broadestLine = _this.broadestLine > textLine.width ? _this.broadestLine : textLine.width;
109 | textDiv.insert(lineDiv);
110 | if(!_this.validateSegments(lineDiv))
111 | _this.appendCorrectSegLink(lineDiv);
112 |
113 |
114 | });
115 | this.textContainer.insert(textDiv);
116 | this.textContainer.insert(new Element("br"));
117 | },
118 |
119 | findLine: function(lineId) {
120 | for(i = 0; i < this.alto.blocks.length; ++i) {
121 | var curLine = this.alto.blocks[i].lines.detect(function(l) { return l.id == lineId; });
122 | if(curLine) return curLine;
123 | };
124 | return null;
125 | },
126 |
127 | findWord: function(wordId) {
128 | wordId = wordId.replace(/.*:/, "");
129 | for(i = 0; i < this.alto.blocks.length; ++i) {
130 | for(j = 0; j < this.alto.blocks[i].lines.length; ++j) {
131 | var curWord = this.alto.blocks[i].lines[j].strings.detect(function(w) { return w.id == wordId; });
132 | if(curWord) return curWord;
133 | }
134 | };
135 | return null;
136 | },
137 |
138 | scrolledLinePos: function(textDiv) {
139 | return textDiv.offsetTop - this.scrollPosition();
140 | },
141 |
142 | scrollPosition: function() {
143 | return this.textContainer.scrollTop + (Prototype.Browser.IE ? 0 : this.textContainer.offsetTop);
144 | },
145 |
146 | zoomToLineAt: function(textLine, textBlock, atPosition) {
147 | var newZoom = 1.0 - ((this.broadestLine + 60) / this.alto.page_width);
148 |
149 | if(newZoom < 0.5)
150 | newZoom = 0.5;
151 | if(newZoom * textLine.width > parseInt(this.imageContainer.style.width) + 60) {
152 | newZoom *= parseInt(this.imageContainer.style.width) / (newZoom * (this.broadestLine +60));
153 | }
154 | this.imageClient.setZoom(newZoom); //1.0 - (textLine.width / this.alto.page_width));
155 | this.imageClient.setPosition((textLine.hpos * this.imageClient.getZoom())-20, (textLine.vpos * this.imageClient.getZoom()) - atPosition);
156 | this.imageClient.render();
157 | },
158 |
159 | highlightTextLine: function(textLine) {
160 | if(this.lineHighlight) this.imageClient.dropOverlay(this.lineHighlight);
161 | this.lineHighlight = this.imageClient.addOverlay(textLine.hpos, textLine.vpos, textLine.width, textLine.height, "blue");
162 | },
163 |
164 | highlightWord: function(wrd, setColor) {
165 | if(this.wordHighlight) this.imageClient.dropOverlay(this.wordHighlight);
166 | this.wordHighlight = this.imageClient.addOverlay(wrd.hpos, wrd.vpos, wrd.width, wrd.height, (setColor ? setColor : "blue"));
167 | },
168 |
169 | appendCorrectSegLink: function(lineDiv) {
170 | var correctLink = new Element("a", {
171 | "style": "color: red; font-weight: bold; margin-right: 40px; text-decoration: underline",
172 | "id": lineDiv.id + ":corrector"
173 | });
174 |
175 | correctLink.insert("!!!");
176 | lineDiv.insert(correctLink);
177 | var _this = this;
178 | correctLink.observe("click", function(e) {
179 | _this.saveCurrentUpdate();
180 | _this.lockLineClick = true;
181 | new SegEdit(_this.findLine(lineDiv.id), _this.imageClient, lineDiv, _this);
182 | });
183 |
184 | },
185 |
186 | appendSegLink: function(lineDiv) {
187 | var correctLink = new Element("a", {
188 | "style": "float: right; color: blue; font-weight: bold; margin-right: 3px; text-decoration: underline",
189 | "class": "seg_corrector",
190 | "id": lineDiv.id + ":corrector1"
191 | });
192 |
193 | correctLink.insert("...");
194 | lineDiv.insert(correctLink);
195 | var _this = this;
196 | correctLink.observe("click", function(e) {
197 | _this.saveCurrentUpdate();
198 | _this.lockLineClick = true;
199 | new SegEdit(_this.findLine(lineDiv.id), _this.imageClient, lineDiv, _this);
200 | });
201 | correctLink.hide();
202 | },
203 |
204 | updateSegments: function(deletions, resizings, insertions, contentUpdates, lineDiv) {
205 | var _this = this;
206 | var altoLine = this.findLine(lineDiv.id);
207 |
208 | insertions.each(function(insert) {
209 | var spans = lineDiv.childElements();
210 | for(var i = 0; i < spans.length; ++i) {
211 | var word = _this.findWord(spans[i].id);
212 | var nextWord = (i == spans.length - 1 ? null : _this.findWord(spans[i+1].id))
213 | if((word && nextWord && word.hpos < insert.hpos && nextWord.hpos > insert.hpos) || (word && !nextWord && word.hpos < insert.hpos)) {
214 | var newSpan = new Element("span", {"id": lineDiv.ancestors()[0].id + ":" + lineDiv.id + ":" + insert.id, "style": "color: green"});
215 | newSpan.insert(insert.content);
216 | spans[i].insert({after: newSpan});
217 | newSpan.insert({before: " "});
218 | altoLine.strings.splice(i,0, insert);
219 | _this.readorder.splice(_this.readorder.indexOf(spans[i].id)+1, 0, newSpan.id);
220 | newSpan.observe("click", function(e) {
221 | _this.editWord(this);
222 | });
223 |
224 | newSpan.observe("mouseover", function(e) {
225 | this.style.backgroundColor = "#ddf";
226 | this.style.cursor = "pointer";
227 | _this.highlightWord(insert);
228 | });
229 |
230 | newSpan.observe("mouseout", function(e) {
231 | this.style.backgroundColor = "transparent";
232 | });
233 |
234 | _this.insertUpdateField("insert", newSpan.id, "after", spans[i].id);
235 | _this.insertUpdateField("insert", newSpan.id, "width", insert.width);
236 | _this.insertUpdateField("insert", newSpan.id, "hpos", insert.hpos);
237 | _this.insertUpdateField("insert", newSpan.id, "vpos", insert.vpos);
238 | _this.insertUpdateField("insert", newSpan.id, "height", insert.height);
239 | _this.insertUpdateField("insert", newSpan.id, "content", insert.content);
240 | break;
241 | }
242 | }
243 | });
244 |
245 | contentUpdates.each(function(cu) {
246 | $(lineDiv.ancestors()[0].id + ":" + lineDiv.id + ":" + cu.id).innerHTML = cu.content;
247 | _this.insertUpdateField("update", lineDiv.ancestors()[0].id + ":" + lineDiv.id + ":" + cu.id, "content", cu.content);
248 | });
249 |
250 | resizings.each(function(resize) {
251 | var word = _this.findWord(resize.id);
252 | if(word) {
253 | word.hpos += resize.left;
254 | word.width += resize.width;
255 | _this.insertUpdateField("update", resize.id, "width", word.width);
256 | _this.insertUpdateField("update", resize.id, "hpos", word.hpos);
257 | }
258 | })
259 |
260 | deletions.each(function(d) {
261 | $(d).remove();
262 | _this.readorder.remove(_this.readorder.indexOf(d));
263 | _this.insertUpdateField("update", d, "delete", "true");
264 | });
265 | if(this.validateSegments(lineDiv) && $(lineDiv.id + ":corrector"))
266 | $(lineDiv.id + ":corrector").remove();
267 | },
268 |
269 | insertUpdateField: function(action, id, key, value) {
270 | var existing = $$("#update_form input").detect(function(inp) {
271 | return inp.getAttribute("name") == action + "[" + id + "][" + key + "]";
272 | });
273 |
274 | if(existing) {
275 | existing.value = value;
276 | } else {
277 | $('update_form').insert(new Element("input", {
278 | "name": action + "[" + id + "][" + key + "]",
279 | "value": value,
280 | "type": "hidden"
281 | }));
282 | }
283 | },
284 |
285 | validateSegments: function(lineDiv) {
286 | var spans = lineDiv.childElements();
287 | for(var x = 0; x < spans.length; x++) {
288 | if(spans[x].innerHTML == " " || spans[x].innerHTML == " " || spans[x].innerHTML.match(/[^\s]+\s+[^\s]+/) ) {
289 | return false;
290 | }
291 | }
292 | return true;
293 | },
294 |
295 | saveCurrentUpdate: function() {
296 | if(this.current_edit) {
297 | var span = $(this.current_edit.input.name);
298 | var lineDiv = this.current_edit.input.ancestors()[0];
299 | this.current_edit.input.stopObserving("keypress");
300 | if(this.current_edit.input.value == "")
301 | this.current_edit.input.value = " ";
302 |
303 | if(this.current_edit.input.value != span.innerHTML) {
304 | this.updates[this.current_edit.input.name] = this.current_edit.input.value;
305 |
306 | this.insertUpdateField("update", this.current_edit.input.name, "content", this.current_edit.input.value);
307 |
308 | span.style.color = "green";
309 | }
310 | span.innerHTML = this.current_edit.input.value;
311 | span.show();
312 |
313 | if(this.validateSegments(lineDiv)) {
314 | if($(lineDiv.id + ":corrector"))
315 | $(lineDiv.id + ":corrector").remove();
316 | } else if(!$(lineDiv.id + ":corrector"))
317 | this.appendCorrectSegLink(lineDiv);
318 |
319 | this.current_edit.input.stopObserving("mouseover");
320 | this.current_edit.input.stopObserving("keydown");
321 | this.current_edit.input.remove();
322 | this.current_edit = null;
323 | }
324 | return true;
325 | },
326 |
327 | editWord: function(elem) {
328 | if(this.saveCurrentUpdate()) {
329 | var input = new Element("input", {"type": "text", "value": elem.innerHTML.replace(" ", ""), "name": elem.id, "style": "width: " + (10*elem.innerHTML.length) + "px; color: " + elem.style.color, "id": elem.id + "_input"});
330 | this.highlightWord(this.findWord(elem.id), "#2A2");
331 | this.current_edit = {"input": input}
332 | elem.hide();
333 | elem.insert({after: input});
334 |
335 | var _this = this;
336 | input.observe("mouseover", function(e) {
337 | _this.highlightWord(_this.findWord(this.name));
338 | });
339 | input.observe("keydown", function(e) {
340 | var k = e.keyCode || e.which;
341 | if(k == 9) {
342 | _this.saveCurrentUpdate();
343 | if(e.shiftKey == 1) _this.editPrevNext(this.name, -1);
344 | else _this.editPrevNext(this.name, 1);
345 | return preventDefault(e);
346 | } else if(k == 13) {
347 | _this.saveCurrentUpdate();
348 | return preventDefault(e);
349 | } else if(k == 38) {
350 | _this.editUpDown(this.ancestors()[0].id, -1);
351 | return preventDefault(e);
352 | } else if(k == 40) {
353 | _this.editUpDown(this.ancestors()[0].id, 1);
354 | return preventDefault(e);
355 | } else if(k == 39 || k == 37) {
356 | var caretPos = null;
357 | if(document.selection) {
358 | var r = document.selection.createRange();
359 | r.moveEnd('character', this.value.length);
360 | caretPos = this.value.lastIndexOf(r.text);
361 | } else
362 | caretPos = this.selectionStart;
363 |
364 | if(caretPos == 0 && k == 37) {
365 | _this.editPrevNext(this.name, -1);
366 | return preventDefault(e);
367 | } else if(caretPos == this.value.length && k == 39) {
368 | _this.editPrevNext(this.name, 1);
369 | return preventDefault(e);
370 | }
371 | }
372 | });
373 | window.setTimeout("$('" + elem.id + "_input').focus(); $('" + elem.id + "_input').select()", 50);
374 | }
375 | },
376 |
377 | editPrevNext: function(word_id, direction) {
378 | var i = 0;
379 | for(i = 0; i < this.readorder.length; ++i)
380 | if(this.readorder[i] == word_id)
381 | break;
382 |
383 | if(this.readorder[i+direction]) {
384 | this.editWord($(this.readorder[i+direction]));
385 | var editDiv = $(this.readorder[i+direction]).ancestors()[0];
386 | if($(word_id).ancestors()[0] != editDiv)
387 | this.zoomToLineAt(this.findLine(editDiv.id), null, this.scrolledLinePos(editDiv));
388 | }
389 | },
390 |
391 | editUpDown: function(div_id, direction) {
392 | var lineDivIds = $$("#" + this.textContainer.id + " div.altoLineDiv").map(function(d) {
393 | return d.id
394 | });
395 |
396 | var curIndex = lineDivIds.indexOf(div_id);
397 | if(lineDivIds[curIndex + direction]) {
398 | var editDiv = $(lineDivIds[curIndex + direction]);
399 | this.editWord(editDiv.childElements()[1]);
400 | this.zoomToLineAt(this.findLine(editDiv.id), null, this.scrolledLinePos(editDiv));
401 | }
402 | }
403 | });
404 |
--------------------------------------------------------------------------------
/static/example.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | pixel
5 |
6 | http://localhost/alto_edit_pub/img/example_image.jpeg
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 2010-01-23T11:51:05
19 | Planman Technologies India Pvt. Ltd.
20 | ABBYY Fine Reader Engine 9.0
21 |
22 | ABBYY FineReader 9.0, Russia
23 | FineReader 9.0
24 | 9.0
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
374 |
375 |
376 |
377 |
378 |
379 |
380 |
381 |
382 |
383 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
400 |
401 |
402 |
403 |
404 |
405 |
406 |
407 |
408 |
409 |
410 |
--------------------------------------------------------------------------------
/static/alto_edit/js/effects.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
2 | // Contributors:
3 | // Justin Palmer (http://encytemedia.com/)
4 | // Mark Pilgrim (http://diveintomark.org/)
5 | // Martin Bialasinki
6 | //
7 | // script.aculo.us is freely distributable under the terms of an MIT-style license.
8 | // For details, see the script.aculo.us web site: http://script.aculo.us/
9 |
10 | // converts rgb() and #xxx to #xxxxxx format,
11 | // returns self (or first argument) if not convertable
12 | String.prototype.parseColor = function() {
13 | var color = '#';
14 | if (this.slice(0,4) == 'rgb(') {
15 | var cols = this.slice(4,this.length-1).split(',');
16 | var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
17 | } else {
18 | if (this.slice(0,1) == '#') {
19 | if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
20 | if (this.length==7) color = this.toLowerCase();
21 | }
22 | }
23 | return (color.length==7 ? color : (arguments[0] || this));
24 | };
25 |
26 | /*--------------------------------------------------------------------------*/
27 |
28 | Element.collectTextNodes = function(element) {
29 | return $A($(element).childNodes).collect( function(node) {
30 | return (node.nodeType==3 ? node.nodeValue :
31 | (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
32 | }).flatten().join('');
33 | };
34 |
35 | Element.collectTextNodesIgnoreClass = function(element, className) {
36 | return $A($(element).childNodes).collect( function(node) {
37 | return (node.nodeType==3 ? node.nodeValue :
38 | ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
39 | Element.collectTextNodesIgnoreClass(node, className) : ''));
40 | }).flatten().join('');
41 | };
42 |
43 | Element.setContentZoom = function(element, percent) {
44 | element = $(element);
45 | element.setStyle({fontSize: (percent/100) + 'em'});
46 | if (Prototype.Browser.WebKit) window.scrollBy(0,0);
47 | return element;
48 | };
49 |
50 | Element.getInlineOpacity = function(element){
51 | return $(element).style.opacity || '';
52 | };
53 |
54 | Element.forceRerendering = function(element) {
55 | try {
56 | element = $(element);
57 | var n = document.createTextNode(' ');
58 | element.appendChild(n);
59 | element.removeChild(n);
60 | } catch(e) { }
61 | };
62 |
63 | /*--------------------------------------------------------------------------*/
64 |
65 | var Effect = {
66 | _elementDoesNotExistError: {
67 | name: 'ElementDoesNotExistError',
68 | message: 'The specified DOM element does not exist, but is required for this effect to operate'
69 | },
70 | Transitions: {
71 | linear: Prototype.K,
72 | sinoidal: function(pos) {
73 | return (-Math.cos(pos*Math.PI)/2) + .5;
74 | },
75 | reverse: function(pos) {
76 | return 1-pos;
77 | },
78 | flicker: function(pos) {
79 | var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
80 | return pos > 1 ? 1 : pos;
81 | },
82 | wobble: function(pos) {
83 | return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
84 | },
85 | pulse: function(pos, pulses) {
86 | return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
87 | },
88 | spring: function(pos) {
89 | return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
90 | },
91 | none: function(pos) {
92 | return 0;
93 | },
94 | full: function(pos) {
95 | return 1;
96 | }
97 | },
98 | DefaultOptions: {
99 | duration: 1.0, // seconds
100 | fps: 100, // 100= assume 66fps max.
101 | sync: false, // true for combining
102 | from: 0.0,
103 | to: 1.0,
104 | delay: 0.0,
105 | queue: 'parallel'
106 | },
107 | tagifyText: function(element) {
108 | var tagifyStyle = 'position:relative';
109 | if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
110 |
111 | element = $(element);
112 | $A(element.childNodes).each( function(child) {
113 | if (child.nodeType==3) {
114 | child.nodeValue.toArray().each( function(character) {
115 | element.insertBefore(
116 | new Element('span', {style: tagifyStyle}).update(
117 | character == ' ' ? String.fromCharCode(160) : character),
118 | child);
119 | });
120 | Element.remove(child);
121 | }
122 | });
123 | },
124 | multiple: function(element, effect) {
125 | var elements;
126 | if (((typeof element == 'object') ||
127 | Object.isFunction(element)) &&
128 | (element.length))
129 | elements = element;
130 | else
131 | elements = $(element).childNodes;
132 |
133 | var options = Object.extend({
134 | speed: 0.1,
135 | delay: 0.0
136 | }, arguments[2] || { });
137 | var masterDelay = options.delay;
138 |
139 | $A(elements).each( function(element, index) {
140 | new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
141 | });
142 | },
143 | PAIRS: {
144 | 'slide': ['SlideDown','SlideUp'],
145 | 'blind': ['BlindDown','BlindUp'],
146 | 'appear': ['Appear','Fade']
147 | },
148 | toggle: function(element, effect) {
149 | element = $(element);
150 | effect = (effect || 'appear').toLowerCase();
151 | var options = Object.extend({
152 | queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
153 | }, arguments[2] || { });
154 | Effect[element.visible() ?
155 | Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
156 | }
157 | };
158 |
159 | Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
160 |
161 | /* ------------- core effects ------------- */
162 |
163 | Effect.ScopedQueue = Class.create(Enumerable, {
164 | initialize: function() {
165 | this.effects = [];
166 | this.interval = null;
167 | },
168 | _each: function(iterator) {
169 | this.effects._each(iterator);
170 | },
171 | add: function(effect) {
172 | var timestamp = new Date().getTime();
173 |
174 | var position = Object.isString(effect.options.queue) ?
175 | effect.options.queue : effect.options.queue.position;
176 |
177 | switch(position) {
178 | case 'front':
179 | // move unstarted effects after this effect
180 | this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
181 | e.startOn += effect.finishOn;
182 | e.finishOn += effect.finishOn;
183 | });
184 | break;
185 | case 'with-last':
186 | timestamp = this.effects.pluck('startOn').max() || timestamp;
187 | break;
188 | case 'end':
189 | // start effect after last queued effect has finished
190 | timestamp = this.effects.pluck('finishOn').max() || timestamp;
191 | break;
192 | }
193 |
194 | effect.startOn += timestamp;
195 | effect.finishOn += timestamp;
196 |
197 | if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
198 | this.effects.push(effect);
199 |
200 | if (!this.interval)
201 | this.interval = setInterval(this.loop.bind(this), 15);
202 | },
203 | remove: function(effect) {
204 | this.effects = this.effects.reject(function(e) { return e==effect });
205 | if (this.effects.length == 0) {
206 | clearInterval(this.interval);
207 | this.interval = null;
208 | }
209 | },
210 | loop: function() {
211 | var timePos = new Date().getTime();
212 | for(var i=0, len=this.effects.length;i= this.startOn) {
279 | if (timePos >= this.finishOn) {
280 | this.render(1.0);
281 | this.cancel();
282 | this.event('beforeFinish');
283 | if (this.finish) this.finish();
284 | this.event('afterFinish');
285 | return;
286 | }
287 | var pos = (timePos - this.startOn) / this.totalTime,
288 | frame = (pos * this.totalFrames).round();
289 | if (frame > this.currentFrame) {
290 | this.render(pos);
291 | this.currentFrame = frame;
292 | }
293 | }
294 | },
295 | cancel: function() {
296 | if (!this.options.sync)
297 | Effect.Queues.get(Object.isString(this.options.queue) ?
298 | 'global' : this.options.queue.scope).remove(this);
299 | this.state = 'finished';
300 | },
301 | event: function(eventName) {
302 | if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
303 | if (this.options[eventName]) this.options[eventName](this);
304 | },
305 | inspect: function() {
306 | var data = $H();
307 | for(property in this)
308 | if (!Object.isFunction(this[property])) data.set(property, this[property]);
309 | return '#';
310 | }
311 | });
312 |
313 | Effect.Parallel = Class.create(Effect.Base, {
314 | initialize: function(effects) {
315 | this.effects = effects || [];
316 | this.start(arguments[1]);
317 | },
318 | update: function(position) {
319 | this.effects.invoke('render', position);
320 | },
321 | finish: function(position) {
322 | this.effects.each( function(effect) {
323 | effect.render(1.0);
324 | effect.cancel();
325 | effect.event('beforeFinish');
326 | if (effect.finish) effect.finish(position);
327 | effect.event('afterFinish');
328 | });
329 | }
330 | });
331 |
332 | Effect.Tween = Class.create(Effect.Base, {
333 | initialize: function(object, from, to) {
334 | object = Object.isString(object) ? $(object) : object;
335 | var args = $A(arguments), method = args.last(),
336 | options = args.length == 5 ? args[3] : null;
337 | this.method = Object.isFunction(method) ? method.bind(object) :
338 | Object.isFunction(object[method]) ? object[method].bind(object) :
339 | function(value) { object[method] = value };
340 | this.start(Object.extend({ from: from, to: to }, options || { }));
341 | },
342 | update: function(position) {
343 | this.method(position);
344 | }
345 | });
346 |
347 | Effect.Event = Class.create(Effect.Base, {
348 | initialize: function() {
349 | this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
350 | },
351 | update: Prototype.emptyFunction
352 | });
353 |
354 | Effect.Opacity = Class.create(Effect.Base, {
355 | initialize: function(element) {
356 | this.element = $(element);
357 | if (!this.element) throw(Effect._elementDoesNotExistError);
358 | // make this work on IE on elements without 'layout'
359 | if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
360 | this.element.setStyle({zoom: 1});
361 | var options = Object.extend({
362 | from: this.element.getOpacity() || 0.0,
363 | to: 1.0
364 | }, arguments[1] || { });
365 | this.start(options);
366 | },
367 | update: function(position) {
368 | this.element.setOpacity(position);
369 | }
370 | });
371 |
372 | Effect.Move = Class.create(Effect.Base, {
373 | initialize: function(element) {
374 | this.element = $(element);
375 | if (!this.element) throw(Effect._elementDoesNotExistError);
376 | var options = Object.extend({
377 | x: 0,
378 | y: 0,
379 | mode: 'relative'
380 | }, arguments[1] || { });
381 | this.start(options);
382 | },
383 | setup: function() {
384 | this.element.makePositioned();
385 | this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
386 | this.originalTop = parseFloat(this.element.getStyle('top') || '0');
387 | if (this.options.mode == 'absolute') {
388 | this.options.x = this.options.x - this.originalLeft;
389 | this.options.y = this.options.y - this.originalTop;
390 | }
391 | },
392 | update: function(position) {
393 | this.element.setStyle({
394 | left: (this.options.x * position + this.originalLeft).round() + 'px',
395 | top: (this.options.y * position + this.originalTop).round() + 'px'
396 | });
397 | }
398 | });
399 |
400 | // for backwards compatibility
401 | Effect.MoveBy = function(element, toTop, toLeft) {
402 | return new Effect.Move(element,
403 | Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
404 | };
405 |
406 | Effect.Scale = Class.create(Effect.Base, {
407 | initialize: function(element, percent) {
408 | this.element = $(element);
409 | if (!this.element) throw(Effect._elementDoesNotExistError);
410 | var options = Object.extend({
411 | scaleX: true,
412 | scaleY: true,
413 | scaleContent: true,
414 | scaleFromCenter: false,
415 | scaleMode: 'box', // 'box' or 'contents' or { } with provided values
416 | scaleFrom: 100.0,
417 | scaleTo: percent
418 | }, arguments[2] || { });
419 | this.start(options);
420 | },
421 | setup: function() {
422 | this.restoreAfterFinish = this.options.restoreAfterFinish || false;
423 | this.elementPositioning = this.element.getStyle('position');
424 |
425 | this.originalStyle = { };
426 | ['top','left','width','height','fontSize'].each( function(k) {
427 | this.originalStyle[k] = this.element.style[k];
428 | }.bind(this));
429 |
430 | this.originalTop = this.element.offsetTop;
431 | this.originalLeft = this.element.offsetLeft;
432 |
433 | var fontSize = this.element.getStyle('font-size') || '100%';
434 | ['em','px','%','pt'].each( function(fontSizeType) {
435 | if (fontSize.indexOf(fontSizeType)>0) {
436 | this.fontSize = parseFloat(fontSize);
437 | this.fontSizeType = fontSizeType;
438 | }
439 | }.bind(this));
440 |
441 | this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
442 |
443 | this.dims = null;
444 | if (this.options.scaleMode=='box')
445 | this.dims = [this.element.offsetHeight, this.element.offsetWidth];
446 | if (/^content/.test(this.options.scaleMode))
447 | this.dims = [this.element.scrollHeight, this.element.scrollWidth];
448 | if (!this.dims)
449 | this.dims = [this.options.scaleMode.originalHeight,
450 | this.options.scaleMode.originalWidth];
451 | },
452 | update: function(position) {
453 | var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
454 | if (this.options.scaleContent && this.fontSize)
455 | this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
456 | this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
457 | },
458 | finish: function(position) {
459 | if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
460 | },
461 | setDimensions: function(height, width) {
462 | var d = { };
463 | if (this.options.scaleX) d.width = width.round() + 'px';
464 | if (this.options.scaleY) d.height = height.round() + 'px';
465 | if (this.options.scaleFromCenter) {
466 | var topd = (height - this.dims[0])/2;
467 | var leftd = (width - this.dims[1])/2;
468 | if (this.elementPositioning == 'absolute') {
469 | if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
470 | if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
471 | } else {
472 | if (this.options.scaleY) d.top = -topd + 'px';
473 | if (this.options.scaleX) d.left = -leftd + 'px';
474 | }
475 | }
476 | this.element.setStyle(d);
477 | }
478 | });
479 |
480 | Effect.Highlight = Class.create(Effect.Base, {
481 | initialize: function(element) {
482 | this.element = $(element);
483 | if (!this.element) throw(Effect._elementDoesNotExistError);
484 | var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
485 | this.start(options);
486 | },
487 | setup: function() {
488 | // Prevent executing on elements not in the layout flow
489 | if (this.element.getStyle('display')=='none') { this.cancel(); return; }
490 | // Disable background image during the effect
491 | this.oldStyle = { };
492 | if (!this.options.keepBackgroundImage) {
493 | this.oldStyle.backgroundImage = this.element.getStyle('background-image');
494 | this.element.setStyle({backgroundImage: 'none'});
495 | }
496 | if (!this.options.endcolor)
497 | this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
498 | if (!this.options.restorecolor)
499 | this.options.restorecolor = this.element.getStyle('background-color');
500 | // init color calculations
501 | this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
502 | this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
503 | },
504 | update: function(position) {
505 | this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
506 | return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
507 | },
508 | finish: function() {
509 | this.element.setStyle(Object.extend(this.oldStyle, {
510 | backgroundColor: this.options.restorecolor
511 | }));
512 | }
513 | });
514 |
515 | Effect.ScrollTo = function(element) {
516 | var options = arguments[1] || { },
517 | scrollOffsets = document.viewport.getScrollOffsets(),
518 | elementOffsets = $(element).cumulativeOffset();
519 |
520 | if (options.offset) elementOffsets[1] += options.offset;
521 |
522 | return new Effect.Tween(null,
523 | scrollOffsets.top,
524 | elementOffsets[1],
525 | options,
526 | function(p){ scrollTo(scrollOffsets.left, p.round()); }
527 | );
528 | };
529 |
530 | /* ------------- combination effects ------------- */
531 |
532 | Effect.Fade = function(element) {
533 | element = $(element);
534 | var oldOpacity = element.getInlineOpacity();
535 | var options = Object.extend({
536 | from: element.getOpacity() || 1.0,
537 | to: 0.0,
538 | afterFinishInternal: function(effect) {
539 | if (effect.options.to!=0) return;
540 | effect.element.hide().setStyle({opacity: oldOpacity});
541 | }
542 | }, arguments[1] || { });
543 | return new Effect.Opacity(element,options);
544 | };
545 |
546 | Effect.Appear = function(element) {
547 | element = $(element);
548 | var options = Object.extend({
549 | from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
550 | to: 1.0,
551 | // force Safari to render floated elements properly
552 | afterFinishInternal: function(effect) {
553 | effect.element.forceRerendering();
554 | },
555 | beforeSetup: function(effect) {
556 | effect.element.setOpacity(effect.options.from).show();
557 | }}, arguments[1] || { });
558 | return new Effect.Opacity(element,options);
559 | };
560 |
561 | Effect.Puff = function(element) {
562 | element = $(element);
563 | var oldStyle = {
564 | opacity: element.getInlineOpacity(),
565 | position: element.getStyle('position'),
566 | top: element.style.top,
567 | left: element.style.left,
568 | width: element.style.width,
569 | height: element.style.height
570 | };
571 | return new Effect.Parallel(
572 | [ new Effect.Scale(element, 200,
573 | { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
574 | new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
575 | Object.extend({ duration: 1.0,
576 | beforeSetupInternal: function(effect) {
577 | Position.absolutize(effect.effects[0].element);
578 | },
579 | afterFinishInternal: function(effect) {
580 | effect.effects[0].element.hide().setStyle(oldStyle); }
581 | }, arguments[1] || { })
582 | );
583 | };
584 |
585 | Effect.BlindUp = function(element) {
586 | element = $(element);
587 | element.makeClipping();
588 | return new Effect.Scale(element, 0,
589 | Object.extend({ scaleContent: false,
590 | scaleX: false,
591 | restoreAfterFinish: true,
592 | afterFinishInternal: function(effect) {
593 | effect.element.hide().undoClipping();
594 | }
595 | }, arguments[1] || { })
596 | );
597 | };
598 |
599 | Effect.BlindDown = function(element) {
600 | element = $(element);
601 | var elementDimensions = element.getDimensions();
602 | return new Effect.Scale(element, 100, Object.extend({
603 | scaleContent: false,
604 | scaleX: false,
605 | scaleFrom: 0,
606 | scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
607 | restoreAfterFinish: true,
608 | afterSetup: function(effect) {
609 | effect.element.makeClipping().setStyle({height: '0px'}).show();
610 | },
611 | afterFinishInternal: function(effect) {
612 | effect.element.undoClipping();
613 | }
614 | }, arguments[1] || { }));
615 | };
616 |
617 | Effect.SwitchOff = function(element) {
618 | element = $(element);
619 | var oldOpacity = element.getInlineOpacity();
620 | return new Effect.Appear(element, Object.extend({
621 | duration: 0.4,
622 | from: 0,
623 | transition: Effect.Transitions.flicker,
624 | afterFinishInternal: function(effect) {
625 | new Effect.Scale(effect.element, 1, {
626 | duration: 0.3, scaleFromCenter: true,
627 | scaleX: false, scaleContent: false, restoreAfterFinish: true,
628 | beforeSetup: function(effect) {
629 | effect.element.makePositioned().makeClipping();
630 | },
631 | afterFinishInternal: function(effect) {
632 | effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
633 | }
634 | });
635 | }
636 | }, arguments[1] || { }));
637 | };
638 |
639 | Effect.DropOut = function(element) {
640 | element = $(element);
641 | var oldStyle = {
642 | top: element.getStyle('top'),
643 | left: element.getStyle('left'),
644 | opacity: element.getInlineOpacity() };
645 | return new Effect.Parallel(
646 | [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
647 | new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
648 | Object.extend(
649 | { duration: 0.5,
650 | beforeSetup: function(effect) {
651 | effect.effects[0].element.makePositioned();
652 | },
653 | afterFinishInternal: function(effect) {
654 | effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
655 | }
656 | }, arguments[1] || { }));
657 | };
658 |
659 | Effect.Shake = function(element) {
660 | element = $(element);
661 | var options = Object.extend({
662 | distance: 20,
663 | duration: 0.5
664 | }, arguments[1] || {});
665 | var distance = parseFloat(options.distance);
666 | var split = parseFloat(options.duration) / 10.0;
667 | var oldStyle = {
668 | top: element.getStyle('top'),
669 | left: element.getStyle('left') };
670 | return new Effect.Move(element,
671 | { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) {
672 | new Effect.Move(effect.element,
673 | { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
674 | new Effect.Move(effect.element,
675 | { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
676 | new Effect.Move(effect.element,
677 | { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
678 | new Effect.Move(effect.element,
679 | { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) {
680 | new Effect.Move(effect.element,
681 | { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
682 | effect.element.undoPositioned().setStyle(oldStyle);
683 | }}); }}); }}); }}); }}); }});
684 | };
685 |
686 | Effect.SlideDown = function(element) {
687 | element = $(element).cleanWhitespace();
688 | // SlideDown need to have the content of the element wrapped in a container element with fixed height!
689 | var oldInnerBottom = element.down().getStyle('bottom');
690 | var elementDimensions = element.getDimensions();
691 | return new Effect.Scale(element, 100, Object.extend({
692 | scaleContent: false,
693 | scaleX: false,
694 | scaleFrom: window.opera ? 0 : 1,
695 | scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
696 | restoreAfterFinish: true,
697 | afterSetup: function(effect) {
698 | effect.element.makePositioned();
699 | effect.element.down().makePositioned();
700 | if (window.opera) effect.element.setStyle({top: ''});
701 | effect.element.makeClipping().setStyle({height: '0px'}).show();
702 | },
703 | afterUpdateInternal: function(effect) {
704 | effect.element.down().setStyle({bottom:
705 | (effect.dims[0] - effect.element.clientHeight) + 'px' });
706 | },
707 | afterFinishInternal: function(effect) {
708 | effect.element.undoClipping().undoPositioned();
709 | effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
710 | }, arguments[1] || { })
711 | );
712 | };
713 |
714 | Effect.SlideUp = function(element) {
715 | element = $(element).cleanWhitespace();
716 | var oldInnerBottom = element.down().getStyle('bottom');
717 | var elementDimensions = element.getDimensions();
718 | return new Effect.Scale(element, window.opera ? 0 : 1,
719 | Object.extend({ scaleContent: false,
720 | scaleX: false,
721 | scaleMode: 'box',
722 | scaleFrom: 100,
723 | scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
724 | restoreAfterFinish: true,
725 | afterSetup: function(effect) {
726 | effect.element.makePositioned();
727 | effect.element.down().makePositioned();
728 | if (window.opera) effect.element.setStyle({top: ''});
729 | effect.element.makeClipping().show();
730 | },
731 | afterUpdateInternal: function(effect) {
732 | effect.element.down().setStyle({bottom:
733 | (effect.dims[0] - effect.element.clientHeight) + 'px' });
734 | },
735 | afterFinishInternal: function(effect) {
736 | effect.element.hide().undoClipping().undoPositioned();
737 | effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
738 | }
739 | }, arguments[1] || { })
740 | );
741 | };
742 |
743 | // Bug in opera makes the TD containing this element expand for a instance after finish
744 | Effect.Squish = function(element) {
745 | return new Effect.Scale(element, window.opera ? 1 : 0, {
746 | restoreAfterFinish: true,
747 | beforeSetup: function(effect) {
748 | effect.element.makeClipping();
749 | },
750 | afterFinishInternal: function(effect) {
751 | effect.element.hide().undoClipping();
752 | }
753 | });
754 | };
755 |
756 | Effect.Grow = function(element) {
757 | element = $(element);
758 | var options = Object.extend({
759 | direction: 'center',
760 | moveTransition: Effect.Transitions.sinoidal,
761 | scaleTransition: Effect.Transitions.sinoidal,
762 | opacityTransition: Effect.Transitions.full
763 | }, arguments[1] || { });
764 | var oldStyle = {
765 | top: element.style.top,
766 | left: element.style.left,
767 | height: element.style.height,
768 | width: element.style.width,
769 | opacity: element.getInlineOpacity() };
770 |
771 | var dims = element.getDimensions();
772 | var initialMoveX, initialMoveY;
773 | var moveX, moveY;
774 |
775 | switch (options.direction) {
776 | case 'top-left':
777 | initialMoveX = initialMoveY = moveX = moveY = 0;
778 | break;
779 | case 'top-right':
780 | initialMoveX = dims.width;
781 | initialMoveY = moveY = 0;
782 | moveX = -dims.width;
783 | break;
784 | case 'bottom-left':
785 | initialMoveX = moveX = 0;
786 | initialMoveY = dims.height;
787 | moveY = -dims.height;
788 | break;
789 | case 'bottom-right':
790 | initialMoveX = dims.width;
791 | initialMoveY = dims.height;
792 | moveX = -dims.width;
793 | moveY = -dims.height;
794 | break;
795 | case 'center':
796 | initialMoveX = dims.width / 2;
797 | initialMoveY = dims.height / 2;
798 | moveX = -dims.width / 2;
799 | moveY = -dims.height / 2;
800 | break;
801 | }
802 |
803 | return new Effect.Move(element, {
804 | x: initialMoveX,
805 | y: initialMoveY,
806 | duration: 0.01,
807 | beforeSetup: function(effect) {
808 | effect.element.hide().makeClipping().makePositioned();
809 | },
810 | afterFinishInternal: function(effect) {
811 | new Effect.Parallel(
812 | [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
813 | new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
814 | new Effect.Scale(effect.element, 100, {
815 | scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
816 | sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
817 | ], Object.extend({
818 | beforeSetup: function(effect) {
819 | effect.effects[0].element.setStyle({height: '0px'}).show();
820 | },
821 | afterFinishInternal: function(effect) {
822 | effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
823 | }
824 | }, options)
825 | );
826 | }
827 | });
828 | };
829 |
830 | Effect.Shrink = function(element) {
831 | element = $(element);
832 | var options = Object.extend({
833 | direction: 'center',
834 | moveTransition: Effect.Transitions.sinoidal,
835 | scaleTransition: Effect.Transitions.sinoidal,
836 | opacityTransition: Effect.Transitions.none
837 | }, arguments[1] || { });
838 | var oldStyle = {
839 | top: element.style.top,
840 | left: element.style.left,
841 | height: element.style.height,
842 | width: element.style.width,
843 | opacity: element.getInlineOpacity() };
844 |
845 | var dims = element.getDimensions();
846 | var moveX, moveY;
847 |
848 | switch (options.direction) {
849 | case 'top-left':
850 | moveX = moveY = 0;
851 | break;
852 | case 'top-right':
853 | moveX = dims.width;
854 | moveY = 0;
855 | break;
856 | case 'bottom-left':
857 | moveX = 0;
858 | moveY = dims.height;
859 | break;
860 | case 'bottom-right':
861 | moveX = dims.width;
862 | moveY = dims.height;
863 | break;
864 | case 'center':
865 | moveX = dims.width / 2;
866 | moveY = dims.height / 2;
867 | break;
868 | }
869 |
870 | return new Effect.Parallel(
871 | [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
872 | new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
873 | new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
874 | ], Object.extend({
875 | beforeStartInternal: function(effect) {
876 | effect.effects[0].element.makePositioned().makeClipping();
877 | },
878 | afterFinishInternal: function(effect) {
879 | effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
880 | }, options)
881 | );
882 | };
883 |
884 | Effect.Pulsate = function(element) {
885 | element = $(element);
886 | var options = arguments[1] || { },
887 | oldOpacity = element.getInlineOpacity(),
888 | transition = options.transition || Effect.Transitions.linear,
889 | reverser = function(pos){
890 | return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
891 | };
892 |
893 | return new Effect.Opacity(element,
894 | Object.extend(Object.extend({ duration: 2.0, from: 0,
895 | afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
896 | }, options), {transition: reverser}));
897 | };
898 |
899 | Effect.Fold = function(element) {
900 | element = $(element);
901 | var oldStyle = {
902 | top: element.style.top,
903 | left: element.style.left,
904 | width: element.style.width,
905 | height: element.style.height };
906 | element.makeClipping();
907 | return new Effect.Scale(element, 5, Object.extend({
908 | scaleContent: false,
909 | scaleX: false,
910 | afterFinishInternal: function(effect) {
911 | new Effect.Scale(element, 1, {
912 | scaleContent: false,
913 | scaleY: false,
914 | afterFinishInternal: function(effect) {
915 | effect.element.hide().undoClipping().setStyle(oldStyle);
916 | } });
917 | }}, arguments[1] || { }));
918 | };
919 |
920 | Effect.Morph = Class.create(Effect.Base, {
921 | initialize: function(element) {
922 | this.element = $(element);
923 | if (!this.element) throw(Effect._elementDoesNotExistError);
924 | var options = Object.extend({
925 | style: { }
926 | }, arguments[1] || { });
927 |
928 | if (!Object.isString(options.style)) this.style = $H(options.style);
929 | else {
930 | if (options.style.include(':'))
931 | this.style = options.style.parseStyle();
932 | else {
933 | this.element.addClassName(options.style);
934 | this.style = $H(this.element.getStyles());
935 | this.element.removeClassName(options.style);
936 | var css = this.element.getStyles();
937 | this.style = this.style.reject(function(style) {
938 | return style.value == css[style.key];
939 | });
940 | options.afterFinishInternal = function(effect) {
941 | effect.element.addClassName(effect.options.style);
942 | effect.transforms.each(function(transform) {
943 | effect.element.style[transform.style] = '';
944 | });
945 | };
946 | }
947 | }
948 | this.start(options);
949 | },
950 |
951 | setup: function(){
952 | function parseColor(color){
953 | if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
954 | color = color.parseColor();
955 | return $R(0,2).map(function(i){
956 | return parseInt( color.slice(i*2+1,i*2+3), 16 );
957 | });
958 | }
959 | this.transforms = this.style.map(function(pair){
960 | var property = pair[0], value = pair[1], unit = null;
961 |
962 | if (value.parseColor('#zzzzzz') != '#zzzzzz') {
963 | value = value.parseColor();
964 | unit = 'color';
965 | } else if (property == 'opacity') {
966 | value = parseFloat(value);
967 | if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
968 | this.element.setStyle({zoom: 1});
969 | } else if (Element.CSS_LENGTH.test(value)) {
970 | var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
971 | value = parseFloat(components[1]);
972 | unit = (components.length == 3) ? components[2] : null;
973 | }
974 |
975 | var originalValue = this.element.getStyle(property);
976 | return {
977 | style: property.camelize(),
978 | originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
979 | targetValue: unit=='color' ? parseColor(value) : value,
980 | unit: unit
981 | };
982 | }.bind(this)).reject(function(transform){
983 | return (
984 | (transform.originalValue == transform.targetValue) ||
985 | (
986 | transform.unit != 'color' &&
987 | (isNaN(transform.originalValue) || isNaN(transform.targetValue))
988 | )
989 | );
990 | });
991 | },
992 | update: function(position) {
993 | var style = { }, transform, i = this.transforms.length;
994 | while(i--)
995 | style[(transform = this.transforms[i]).style] =
996 | transform.unit=='color' ? '#'+
997 | (Math.round(transform.originalValue[0]+
998 | (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
999 | (Math.round(transform.originalValue[1]+
1000 | (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
1001 | (Math.round(transform.originalValue[2]+
1002 | (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
1003 | (transform.originalValue +
1004 | (transform.targetValue - transform.originalValue) * position).toFixed(3) +
1005 | (transform.unit === null ? '' : transform.unit);
1006 | this.element.setStyle(style, true);
1007 | }
1008 | });
1009 |
1010 | Effect.Transform = Class.create({
1011 | initialize: function(tracks){
1012 | this.tracks = [];
1013 | this.options = arguments[1] || { };
1014 | this.addTracks(tracks);
1015 | },
1016 | addTracks: function(tracks){
1017 | tracks.each(function(track){
1018 | track = $H(track);
1019 | var data = track.values().first();
1020 | this.tracks.push($H({
1021 | ids: track.keys().first(),
1022 | effect: Effect.Morph,
1023 | options: { style: data }
1024 | }));
1025 | }.bind(this));
1026 | return this;
1027 | },
1028 | play: function(){
1029 | return new Effect.Parallel(
1030 | this.tracks.map(function(track){
1031 | var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
1032 | var elements = [$(ids) || $$(ids)].flatten();
1033 | return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
1034 | }).flatten(),
1035 | this.options
1036 | );
1037 | }
1038 | });
1039 |
1040 | Element.CSS_PROPERTIES = $w(
1041 | 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
1042 | 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
1043 | 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
1044 | 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
1045 | 'fontSize fontWeight height left letterSpacing lineHeight ' +
1046 | 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
1047 | 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
1048 | 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
1049 | 'right textIndent top width wordSpacing zIndex');
1050 |
1051 | Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
1052 |
1053 | String.__parseStyleElement = document.createElement('div');
1054 | String.prototype.parseStyle = function(){
1055 | var style, styleRules = $H();
1056 | if (Prototype.Browser.WebKit)
1057 | style = new Element('div',{style:this}).style;
1058 | else {
1059 | String.__parseStyleElement.innerHTML = '';
1060 | style = String.__parseStyleElement.childNodes[0].style;
1061 | }
1062 |
1063 | Element.CSS_PROPERTIES.each(function(property){
1064 | if (style[property]) styleRules.set(property, style[property]);
1065 | });
1066 |
1067 | if (Prototype.Browser.IE && this.include('opacity'))
1068 | styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
1069 |
1070 | return styleRules;
1071 | };
1072 |
1073 | if (document.defaultView && document.defaultView.getComputedStyle) {
1074 | Element.getStyles = function(element) {
1075 | var css = document.defaultView.getComputedStyle($(element), null);
1076 | return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
1077 | styles[property] = css[property];
1078 | return styles;
1079 | });
1080 | };
1081 | } else {
1082 | Element.getStyles = function(element) {
1083 | element = $(element);
1084 | var css = element.currentStyle, styles;
1085 | styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
1086 | results[property] = css[property];
1087 | return results;
1088 | });
1089 | if (!styles.opacity) styles.opacity = element.getOpacity();
1090 | return styles;
1091 | };
1092 | }
1093 |
1094 | Effect.Methods = {
1095 | morph: function(element, style) {
1096 | element = $(element);
1097 | new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
1098 | return element;
1099 | },
1100 | visualEffect: function(element, effect, options) {
1101 | element = $(element);
1102 | var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
1103 | new Effect[klass](element, options);
1104 | return element;
1105 | },
1106 | highlight: function(element, options) {
1107 | element = $(element);
1108 | new Effect.Highlight(element, options);
1109 | return element;
1110 | }
1111 | };
1112 |
1113 | $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
1114 | 'pulsate shake puff squish switchOff dropOut').each(
1115 | function(effect) {
1116 | Effect.Methods[effect] = function(element, options){
1117 | element = $(element);
1118 | Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
1119 | return element;
1120 | };
1121 | }
1122 | );
1123 |
1124 | $w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
1125 | function(f) { Effect.Methods[f] = Element[f]; }
1126 | );
1127 |
1128 | Element.addMethods(Effect.Methods);
--------------------------------------------------------------------------------