gallerby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ .yardoc
19
+ doc
20
+ pkg
21
+
22
+ ## PROJECT::SPECIFIC
23
+ .sass-cache
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Waine Kerr
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,61 @@
1
+ gallerby
2
+ ========
3
+
4
+ Think about a dead-simple static gallery generator, with nothing except pictures, thumbnails, previews. A kind of server directory listing for pictures. Enter Gallerby!
5
+
6
+ An example of a gallerby-generated gallery can be found [here]("http://wainekerr.github.com/gallerby").
7
+
8
+ ## Installation
9
+
10
+ Classic!
11
+
12
+ `(sudo) gem install gallerby`
13
+
14
+ ## Usage
15
+
16
+ Before building your gallery, you have to respect this kind of "directory model" :
17
+
18
+ name_of_my_gallery/ <---- This is where you gallery will reside.
19
+ originals/ <---- This is where you have to put your pictures.
20
+ thumbnails/ <---- This is where Gallerby will create thumbnails for you.
21
+ previews/ <---- This is where Gallerby will store "middle-sized" versions of your pictures for Lightbox.
22
+
23
+ When your directory is ready :
24
+
25
+ `gallerby -d directory_of_my_gallery`
26
+
27
+ Wait, and tada!
28
+
29
+ ## Moar Usage
30
+
31
+ If I was an asshole, i'd say you to RTFM. So :
32
+
33
+ $ gallerby -h
34
+ gallerby - frakin' dead simple gallery generator
35
+
36
+ Usage: gallerby -d [directory] [options]
37
+
38
+ Options are:
39
+ -d, --directory DIRECTORY Directory of the Gallery
40
+ -n, --name [NAME] Name (optional, default to directory)
41
+ -s, --summary [SUMMARY] Summary (optional, default empty)
42
+ -p, --per-page [PER_PAGE] Pictures to display per page (default is 20)
43
+ -h, --help RTFM, dude!
44
+
45
+ ## Updating
46
+
47
+ You can re-run `gallerby` to an existing gallery. It'll skip thumbnails & previews creation if files already exists.
48
+
49
+ Currently, no parameters are stored (like custom name, description, pictures per page). It's planned.
50
+
51
+ ## Copyright
52
+
53
+ Copyright (c) 2010 Waine Kerr.
54
+
55
+ Released under the terms of the MIT license, see LICENSE for details.
56
+
57
+ Please note that other software is included in gallerby and may be released under others licenses:
58
+
59
+ * [Prototype]("http://github.com/sstephenson/prototype"), Copyright (c) 2005-2010 Sam Stephenson
60
+ * [script.aculo.us]("http://github.com/madrobby/scriptaculous"), Copyright (c) 2005-2010 Thomas Fuchs
61
+ * [Lightbox]("http://huddletogether.com/projects/lightbox2"), by Lokesh Dhakar
data/Rakefile ADDED
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "gallerby"
8
+ gem.summary = %Q{Think about a webserver directory-listing for pictures.}
9
+ gem.description = %Q{Think about a dead-simple static gallery generator, with nothing except pictures, thumbnails, previews. A kind of server directory listing for pictures. Enter Gallerby!}
10
+ gem.email = "wk@heldscalla.org"
11
+ gem.homepage = "http://github.com/wainekerr/gallerby"
12
+ gem.authors = ["Waine Kerr"]
13
+ gem.executables = %w(gallerby)
14
+ gem.add_dependency "mini_magick", ">= 2.0.0"
15
+ gem.add_dependency "haml", ">= 3.0.17"
16
+ gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
17
+ gem.add_development_dependency "yard", ">= 0"
18
+ end
19
+ Jeweler::GemcutterTasks.new
20
+ rescue LoadError
21
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
+ end
23
+
24
+ require 'rake/testtask'
25
+ Rake::TestTask.new(:test) do |test|
26
+ test.libs << 'lib' << 'test'
27
+ test.pattern = 'test/**/test_*.rb'
28
+ test.verbose = true
29
+ end
30
+
31
+ begin
32
+ require 'rcov/rcovtask'
33
+ Rcov::RcovTask.new do |test|
34
+ test.libs << 'test'
35
+ test.pattern = 'test/**/test_*.rb'
36
+ test.verbose = true
37
+ end
38
+ rescue LoadError
39
+ task :rcov do
40
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
41
+ end
42
+ end
43
+
44
+ task :test => :check_dependencies
45
+
46
+ task :default => :test
47
+
48
+ begin
49
+ require 'yard'
50
+ YARD::Rake::YardocTask.new
51
+ rescue LoadError
52
+ task :yardoc do
53
+ abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
54
+ end
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/bin/gallerby ADDED
@@ -0,0 +1,77 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Runner for Gallerby
4
+
5
+ require 'ostruct'
6
+ require 'optparse'
7
+ require 'optparse/time'
8
+
9
+ lib_dir = File.dirname(__FILE__) + '/../lib/'
10
+ require File.dirname(__FILE__) + '/../lib/gallerby'
11
+
12
+ options = {:per_page=>20}
13
+
14
+ optparse = OptionParser.new do |opts|
15
+ opts.banner = <<BANNER
16
+ gallerby - frakin' dead simple gallery generator
17
+
18
+ Usage: #{File.basename($0)} -d [directory] [options]
19
+
20
+ Options are:
21
+ BANNER
22
+
23
+ opts.on_tail('-h', '--help', 'RTFM, dude!') do
24
+ puts opts
25
+ exit 1
26
+ end
27
+
28
+ opts.on('-d DIRECTORY', '--directory DIRECTORY', "Directory of the Gallery") do |f|
29
+ options[:directory] = f
30
+ end
31
+
32
+ opts.on('-n', '--name [NAME]', 'Name (optional, default to directory)') do |f|
33
+ options[:name] = f || options[:directory]
34
+ end
35
+
36
+ opts.on('-s', '--summary [SUMMARY]', 'Summary (optional, default empty)') do |f|
37
+ options[:summary] = f || ""
38
+ end
39
+
40
+ opts.on('-p', '--per-page [PER_PAGE]', Integer, 'Pictures to display per page (default is 20)') do |f|
41
+ options[:per_page] = f || 20
42
+ end
43
+ end
44
+
45
+ begin # From http://stackoverflow.com/questions/1541294/how-do-you-specify-a-required-switch-not-argument-with-ruby-optionparser/2149183#2149183
46
+ optparse.parse!
47
+ mandatory = [:directory]
48
+ missing = mandatory.select{ |param| options[param].nil? }
49
+ if not missing.empty?
50
+ puts optparse
51
+ exit 1
52
+ end
53
+ rescue OptionParser::InvalidOption, OptionParser::MissingArgument
54
+ puts optparse
55
+ exit 1
56
+ end
57
+
58
+ begin
59
+ g = Gallerby.new(options[:directory], options[:per_page], options[:name], options[:summary])
60
+ g.verify_directory_structure
61
+ g.scan_pictures
62
+ puts "[WARNING ] Found 0 pictures in #{g.originals}. Your gallery will be empty!" if g.pictures.size == 0
63
+ g.map_pages
64
+ puts "[INFO ] Your gallery will have #{g.pictures.size} pictures in #{g.pages.size} pages."
65
+ print "[BUILDING] Thumbnails: " ; STDOUT.flush
66
+ g.build_thumbnails
67
+ print "[BUILDING] Previews: " ; STDOUT.flush
68
+ g.build_previews
69
+ puts "[INFO ] Copying assets and building CSS to #{g.directory}/assets"
70
+ g.copy_and_build_assets
71
+ print "[BUILDING] HTML: " ; STDOUT.flush
72
+ g.build_gallery
73
+ puts "[HOORAY ] Your gallery have been successfully builded. Enjoy \\o/"
74
+ rescue
75
+ puts "[OH NOES ] #{$!}"
76
+ exit 1
77
+ end
data/gallerby.gemspec ADDED
@@ -0,0 +1,82 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{gallerby}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Waine Kerr"]
12
+ s.date = %q{2010-08-22}
13
+ s.default_executable = %q{gallerby}
14
+ s.description = %q{Think about a dead-simple static gallery generator, with nothing except pictures, thumbnails, previews. A kind of server directory listing for pictures. Enter Gallerby!}
15
+ s.email = %q{wk@heldscalla.org}
16
+ s.executables = ["gallerby"]
17
+ s.extra_rdoc_files = [
18
+ "LICENSE",
19
+ "README.md"
20
+ ]
21
+ s.files = [
22
+ ".document",
23
+ ".gitignore",
24
+ "LICENSE",
25
+ "README.md",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/gallerby",
29
+ "gallerby.gemspec",
30
+ "lib/gallerby.rb",
31
+ "lib/shared/assets/css/lightbox.css",
32
+ "lib/shared/assets/img/bullet.gif",
33
+ "lib/shared/assets/img/close.gif",
34
+ "lib/shared/assets/img/closelabel.gif",
35
+ "lib/shared/assets/img/donate-button.gif",
36
+ "lib/shared/assets/img/download-icon.gif",
37
+ "lib/shared/assets/img/loading.gif",
38
+ "lib/shared/assets/img/nextlabel.gif",
39
+ "lib/shared/assets/img/prevlabel.gif",
40
+ "lib/shared/assets/js/builder.js",
41
+ "lib/shared/assets/js/effects.js",
42
+ "lib/shared/assets/js/lightbox.js",
43
+ "lib/shared/assets/js/prototype.js",
44
+ "lib/shared/assets/js/scriptaculous.js",
45
+ "lib/shared/templates/gallerby.haml",
46
+ "lib/shared/templates/gallerby.sass",
47
+ "test/helper.rb",
48
+ "test/test_gallerby.rb"
49
+ ]
50
+ s.homepage = %q{http://github.com/wainekerr/gallerby}
51
+ s.rdoc_options = ["--charset=UTF-8"]
52
+ s.require_paths = ["lib"]
53
+ s.rubygems_version = %q{1.3.6}
54
+ s.summary = %q{Think about a webserver directory-listing for pictures.}
55
+ s.test_files = [
56
+ "test/helper.rb",
57
+ "test/test_gallerby.rb"
58
+ ]
59
+
60
+ if s.respond_to? :specification_version then
61
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
62
+ s.specification_version = 3
63
+
64
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
65
+ s.add_runtime_dependency(%q<mini_magick>, [">= 2.0.0"])
66
+ s.add_runtime_dependency(%q<haml>, [">= 3.0.17"])
67
+ s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
68
+ s.add_development_dependency(%q<yard>, [">= 0"])
69
+ else
70
+ s.add_dependency(%q<mini_magick>, [">= 2.0.0"])
71
+ s.add_dependency(%q<haml>, [">= 3.0.17"])
72
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
73
+ s.add_dependency(%q<yard>, [">= 0"])
74
+ end
75
+ else
76
+ s.add_dependency(%q<mini_magick>, [">= 2.0.0"])
77
+ s.add_dependency(%q<haml>, [">= 3.0.17"])
78
+ s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
79
+ s.add_dependency(%q<yard>, [">= 0"])
80
+ end
81
+ end
82
+
data/lib/gallerby.rb ADDED
@@ -0,0 +1,139 @@
1
+ %w{fileutils mini_magick haml sass}.each {|r| require r }
2
+
3
+ class Gallerby
4
+ PICTURES_MATCHER = "*.{jpg,jpeg,JPG,JPEG,gif,GIF,png,PNG}"
5
+ SHARED_PATH = File.expand_path('../shared', __FILE__)
6
+ HAML_OPTS = {:format => :html5, :escape_html => true}
7
+ SASS_OPTS = {}
8
+ attr_accessor :directory, :name, :pictures, :originals, :pages, :description, :pages_links
9
+
10
+ # Well... initialize the Gallerby.
11
+ #
12
+ # @param [String] directory string of the gallery root directory
13
+ # @param [optional, Integer] per_page number of pictures to display per page
14
+ # @param [optional, String] name custom name for the gallery (default is the directory)
15
+ # @param [optional, String] description description for the gallery (default is nothing)
16
+ # @raise [RuntimeError] if gallery directory don't exists or don't match the required structure.
17
+ def initialize(directory, per_page=20, name=nil, description=nil)
18
+ @directory, @per_page, @name, @description = directory, per_page, name, description
19
+ @thumbs, @previews, @originals = "#{@directory}/thumbnails", "#{@directory}/previews", "#{@directory}/originals"
20
+ raise "Directory #{@directory} don't exists." unless File.exists?(@directory)
21
+ end
22
+
23
+ # Verify if the directory structure match the gallerby requirements. See the README for more information
24
+ #
25
+ # @return [TrueClass, FalseClass]
26
+ def verify_directory_structure
27
+ raise "Thumbnails directory (#{@thumbs}) don't exist." unless File.exists?(@thumbs)
28
+ raise "Previews directory (#{@previews}) don't exist." unless File.exists?(@previews)
29
+ raise "Originals directory (#{@originals}) don't exist." unless File.exists?(@originals)
30
+ end
31
+
32
+ # Scan for pictures in the original directory.
33
+ # @returns [Array] Filenames of the pictures
34
+ def scan_pictures
35
+ @pictures = Dir["#{@originals}/#{PICTURES_MATCHER}"]
36
+ end
37
+
38
+ # Map pages/pictures for pagination purposes
39
+ #
40
+ # @returns [Hash] Pages ID & Pictures associated to the page.
41
+ def map_pages
42
+ @pages = {}
43
+ page_id = 0
44
+ pict_count = 0
45
+ page_pictures = []
46
+
47
+ @pictures.each do |picture|
48
+ if pict_count == @per_page
49
+ @pages[page_id] = page_pictures
50
+ page_pictures = []
51
+ page_id += 1
52
+ pict_count = 0
53
+ end
54
+ page_pictures << picture
55
+ pict_count += 1
56
+ end
57
+
58
+ @pages
59
+ end
60
+
61
+ # Build the thumbnails
62
+ #
63
+ # @see Gallerby#the_rezise_machine
64
+ def build_thumbnails(size="200x200", verbose=true)
65
+ the_resize_machine(@pictures, @thumbs, size, verbose)
66
+ end
67
+
68
+ # Build the previews
69
+ #
70
+ # @see Gallerby#the_rezise_machine
71
+ def build_previews(size="900x780", verbose=true)
72
+ the_resize_machine(@pictures, @previews, size, verbose)
73
+ end
74
+
75
+ # Copy the assets (JS, CSS and images) to the gallery directory, and build the CSS.
76
+ def copy_and_build_assets
77
+ FileUtils.cp_r("#{SHARED_PATH}/assets", @directory)
78
+ sass = Sass::Engine.new(File.read("#{SHARED_PATH}/templates/gallerby.sass"), SASS_OPTS).to_css
79
+ File.open("#{@directory}/assets/css/gallerby.css", 'w') {|f| f.write(sass) }
80
+ end
81
+
82
+ # Build all HTML pages of the gallery.
83
+ #
84
+ # @see Gallerby#build_page
85
+ def build_gallery(verbose=true)
86
+ @pages_links = {}
87
+ @pages.each do |page_id,pictures|
88
+ page_name = page_id==0 ? "index" : "page#{page_id}"
89
+ build_page(page_id, page_name, pictures)
90
+ (print "." ; STDOUT.flush) if verbose
91
+ end
92
+ (print " OK!\n" ; STDOUT.flush) if verbose
93
+ end
94
+
95
+ # Helper to build the navigations links
96
+ def navigation_links(current_page)
97
+ links = "Pages:&nbsp;"
98
+ @pages.keys.sort.each do |pid|
99
+ page_link = pid==0 ? "index.html" : "page#{pid}.html"
100
+ extra_html = pid==current_page ? " class='selected'" : ""
101
+ links << "<a href='#{page_link}' title='Page #{pid+1}'#{extra_html}>#{pid+1}</a>&nbsp;"
102
+ end
103
+ links
104
+ end
105
+
106
+ private
107
+ # Build a page of the gallery.
108
+ #
109
+ # @params [Integer] pid ID of the page
110
+ # @params [String] pname Name of the page (index or pageID)
111
+ # @picts [Array] Array of pictures path
112
+ def build_page(pid, pname, picts)
113
+ page_pictures = []
114
+ picts.each {|pict| page_pictures << pict.gsub("#{@originals}/", '') }
115
+ locals = {:gallery => self, :pid => pid, :pname => pname, :pictures => page_pictures}
116
+ haml = Haml::Engine.new(File.read("#{SHARED_PATH}/templates/gallerby.haml"), HAML_OPTS).render(Object.new, locals)
117
+ File.open("#{@directory}/#{pname}.html", 'w') {|f| f.write(haml) }
118
+ end
119
+
120
+ # Resize an Array of pictures
121
+ #
122
+ # @params [Array] pictures Array of pictures path
123
+ # @params [String] save_path in which directory to save the picture
124
+ # @params [String] size Size to use for rezising.
125
+ # @params [Boolean] verbose Enable verbose or not (useful for `gallerby` binary)
126
+ def the_resize_machine(pictures, save_path, size, verbose)
127
+ pictures.each do |picture|
128
+ read_from = "#{picture}"
129
+ write_to = "#{save_path}#{picture.gsub(@originals, '')}"
130
+ unless File.exists?(write_to)
131
+ magick = MiniMagick::Image.from_file(read_from)
132
+ magick.resize(size)
133
+ magick.write(write_to)
134
+ (print "." ; STDOUT.flush) if verbose
135
+ end
136
+ end
137
+ (print " OK!\n" ; STDOUT.flush) if verbose
138
+ end
139
+ end
@@ -0,0 +1,27 @@
1
+ #lightbox{ position: absolute; left: 0; width: 100%; z-index: 100; text-align: center; line-height: 0;}
2
+ #lightbox img{ width: auto; height: auto;}
3
+ #lightbox a img{ border: none; }
4
+
5
+ #outerImageContainer{ position: relative; background-color: #fff; width: 250px; height: 250px; margin: 0 auto; }
6
+ #imageContainer{ padding: 10px; }
7
+
8
+ #loading{ position: absolute; top: 40%; left: 0%; height: 25%; width: 100%; text-align: center; line-height: 0; }
9
+ #hoverNav{ position: absolute; top: 0; left: 0; height: 100%; width: 100%; z-index: 10; }
10
+ #imageContainer>#hoverNav{ left: 0;}
11
+ #hoverNav a{ outline: none;}
12
+
13
+ #prevLink, #nextLink{ width: 49%; height: 100%; background-image: url(data:image/gif;base64,AAAA); /* Trick IE into showing hover */ display: block; }
14
+ #prevLink { left: 0; float: left;}
15
+ #nextLink { right: 0; float: right;}
16
+ #prevLink:hover, #prevLink:visited:hover { background: url(../img/prevlabel.gif) left 15% no-repeat; }
17
+ #nextLink:hover, #nextLink:visited:hover { background: url(../img/nextlabel.gif) right 15% no-repeat; }
18
+
19
+ #imageDataContainer{ font: 10px Verdana, Helvetica, sans-serif; background-color: #fff; margin: 0 auto; line-height: 1.4em; overflow: auto; width: 100% ; }
20
+
21
+ #imageData{ padding:0 10px; color: #666; }
22
+ #imageData #imageDetails{ width: 70%; float: left; text-align: left; }
23
+ #imageData #caption{ font-weight: bold; }
24
+ #imageData #numberDisplay{ display: block; clear: left; padding-bottom: 1.0em; }
25
+ #imageData #bottomNavClose{ width: 66px; float: right; padding-bottom: 0.7em; outline: none;}
26
+
27
+ #overlay{ position: absolute; top: 0; left: 0; z-index: 90; width: 100%; height: 500px; background-color: #000; }
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,136 @@
1
+ // script.aculo.us builder.js v1.8.1, Thu Jan 03 22:07:12 -0500 2008
2
+
3
+ // Copyright (c) 2005-2007 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
4
+ //
5
+ // script.aculo.us is freely distributable under the terms of an MIT-style license.
6
+ // For details, see the script.aculo.us web site: http://script.aculo.us/
7
+
8
+ var Builder = {
9
+ NODEMAP: {
10
+ AREA: 'map',
11
+ CAPTION: 'table',
12
+ COL: 'table',
13
+ COLGROUP: 'table',
14
+ LEGEND: 'fieldset',
15
+ OPTGROUP: 'select',
16
+ OPTION: 'select',
17
+ PARAM: 'object',
18
+ TBODY: 'table',
19
+ TD: 'table',
20
+ TFOOT: 'table',
21
+ TH: 'table',
22
+ THEAD: 'table',
23
+ TR: 'table'
24
+ },
25
+ // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
26
+ // due to a Firefox bug
27
+ node: function(elementName) {
28
+ elementName = elementName.toUpperCase();
29
+
30
+ // try innerHTML approach
31
+ var parentTag = this.NODEMAP[elementName] || 'div';
32
+ var parentElement = document.createElement(parentTag);
33
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
34
+ parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
35
+ } catch(e) {}
36
+ var element = parentElement.firstChild || null;
37
+
38
+ // see if browser added wrapping tags
39
+ if(element && (element.tagName.toUpperCase() != elementName))
40
+ element = element.getElementsByTagName(elementName)[0];
41
+
42
+ // fallback to createElement approach
43
+ if(!element) element = document.createElement(elementName);
44
+
45
+ // abort if nothing could be created
46
+ if(!element) return;
47
+
48
+ // attributes (or text)
49
+ if(arguments[1])
50
+ if(this._isStringOrNumber(arguments[1]) ||
51
+ (arguments[1] instanceof Array) ||
52
+ arguments[1].tagName) {
53
+ this._children(element, arguments[1]);
54
+ } else {
55
+ var attrs = this._attributes(arguments[1]);
56
+ if(attrs.length) {
57
+ try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
58
+ parentElement.innerHTML = "<" +elementName + " " +
59
+ attrs + "></" + elementName + ">";
60
+ } catch(e) {}
61
+ element = parentElement.firstChild || null;
62
+ // workaround firefox 1.0.X bug
63
+ if(!element) {
64
+ element = document.createElement(elementName);
65
+ for(attr in arguments[1])
66
+ element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
67
+ }
68
+ if(element.tagName.toUpperCase() != elementName)
69
+ element = parentElement.getElementsByTagName(elementName)[0];
70
+ }
71
+ }
72
+
73
+ // text, or array of children
74
+ if(arguments[2])
75
+ this._children(element, arguments[2]);
76
+
77
+ return element;
78
+ },
79
+ _text: function(text) {
80
+ return document.createTextNode(text);
81
+ },
82
+
83
+ ATTR_MAP: {
84
+ 'className': 'class',
85
+ 'htmlFor': 'for'
86
+ },
87
+
88
+ _attributes: function(attributes) {
89
+ var attrs = [];
90
+ for(attribute in attributes)
91
+ attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
92
+ '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
93
+ return attrs.join(" ");
94
+ },
95
+ _children: function(element, children) {
96
+ if(children.tagName) {
97
+ element.appendChild(children);
98
+ return;
99
+ }
100
+ if(typeof children=='object') { // array can hold nodes and text
101
+ children.flatten().each( function(e) {
102
+ if(typeof e=='object')
103
+ element.appendChild(e)
104
+ else
105
+ if(Builder._isStringOrNumber(e))
106
+ element.appendChild(Builder._text(e));
107
+ });
108
+ } else
109
+ if(Builder._isStringOrNumber(children))
110
+ element.appendChild(Builder._text(children));
111
+ },
112
+ _isStringOrNumber: function(param) {
113
+ return(typeof param=='string' || typeof param=='number');
114
+ },
115
+ build: function(html) {
116
+ var element = this.node('div');
117
+ $(element).update(html.strip());
118
+ return element.down();
119
+ },
120
+ dump: function(scope) {
121
+ if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
122
+
123
+ var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
124
+ "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
125
+ "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
126
+ "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
127
+ "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
128
+ "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
129
+
130
+ tags.each( function(tag){
131
+ scope[tag] = function() {
132
+ return Builder.node.apply(Builder, [tag].concat($A(arguments)));
133
+ }
134
+ });
135
+ }
136
+ }