diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f8859f --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +deploy/IronRubyNet +*.html.orig +*.pyc +Thumbs.db +*.swp +*.user +*.suo +obj +bin +_ReSharper.iron-websites \ No newline at end of file diff --git a/announcements/index.html b/announcements/index.html new file mode 100644 index 0000000..0f7c9c9 --- /dev/null +++ b/announcements/index.html @@ -0,0 +1,92 @@ + + + + + + IronRuby.net / Announcements + + + + + + + + + +
+
+ + + + + +
+
+

March 13, 2011

+

IronRuby 1.1.3 +is released.

+
+
+

February 7, 2011

+

IronRuby 1.1.2 +is released.

+
+
+

October 21, 2010

+

IronRuby 1.1.1 +is released.

+
+
+

July 16, 2010

+

IronRuby 1.1 +is released.

+
+
+

April 12, 2010

+

IronRuby 1.0 is the first stable release +of the Ruby programming language implementation for the .NET framework.

+
+
+ + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/announcements/index.rst b/announcements/index.rst new file mode 100644 index 0000000..4d98ea3 --- /dev/null +++ b/announcements/index.rst @@ -0,0 +1,34 @@ +============= +Announcements +============= + +.. admonition:: March 13, 2011 + :class: strip space + + `IronRuby 1.1.3 `_ + is released. + +.. admonition:: February 7, 2011 + :class: strip space + + `IronRuby 1.1.2 `_ + is released. + +.. admonition:: October 21, 2010 + :class: strip space + + `IronRuby 1.1.1 `_ + is released. + +.. admonition:: July 16, 2010 + :class: strip space + + `IronRuby 1.1 `_ + is released. + +.. admonition:: April 12, 2010 + :class: strip space + + `IronRuby 1.0 `_ is the first stable release + of the Ruby programming language implementation for the .NET framework. + diff --git a/browser/docs.html b/browser/docs.html new file mode 100644 index 0000000..79704f3 --- /dev/null +++ b/browser/docs.html @@ -0,0 +1,766 @@ + + + + + + IronRuby.net / Ruby in the browser documentation + + + + + + + + + +
+
+ + + + + +
+ +
+

Setup

+

The only setup absolutely required is to reference dlr.js from any HTML +file you wish write Ruby code in:

+
+<script src="http://gestalt.ironruby.net/dlr-latest.js"
+        type="text/javascript"></script>
+
+

If you want to develop without a network connection, or deploy +IronRuby yourself, simply extract the re-distributable package and +reference it:

+
+<script type="text/javascript">
+    window.DLR = {path: 'path/to/gestalt.latest'}
+</script>
+<script src="path/to/gestalt.latest/dlr.js" type="text/javascript">
+</script>
+
+
+

Note

+

using the gestalt.ironruby.net version is preferred, especially in +production. However, please pick a specific version (like dlr-20100305.js, +rather than dlr-latest.js), as this will ensure your application keeps +working between releases; dlr-latest.js will always point to the most +recent version.

+
+

By default just doing this sets up your HTML page for executing Ruby code. +See the dlr.js API documentation for more advanced uses.

+
+
+

Writing Ruby code

+

Ruby code is executed by using HTML script-tags; either inline or as an +external file. This implementation aims to be compliant with the HTML4 +specification for scripting in HTML pages.

+
+

Inline scripts

+

The following attributes are used with a HTML script-tag to embed Ruby:

+
    +
  • type: defines the mime-type the script code should map to; the following +prefixes are allowed: application/, text/, and application/x-. +The actual language name will be passed to the DLR hosting API, so the above +example could have used application/ironruby and it would still work:

    +
    +<script type="application/ruby">
    +  window.alert "Ruby inline script-tag"
    +</script>
    +<script type="text/ruby">
    +  window.alert "Also a Ruby inline script-tag"
    +</script>
    +
    +
  • +
  • (Optional) defer: if this attribute is not present, its value is +false. Otherwise, the value is true (even if it's explicitly set to +defer="false"; this is how all modern browsers behave). If set to true +the code is not run; but it can be used to evaluate later:

    +
    +<script type="application/ruby" defer="true" id="for_later">
    +  puts 2 + 2
    +</script>
    +<script type="application/ruby">
    +  eval document.for_later.innerHTML
    +</script>
    +
    +
  • +
+
+
+

External scripts

+

The following attributes are used with a HTML script-tag to reference Ruby:

+
    +
  • src: specify the path to a Ruby file. If it's a relative URI, it will +be considered relative to the HTML file. The src URI is downloaded and +cached in memory, building a virtual file-system of external script code. +Then this file is executed in its own DLR ScriptScope, which properly +isolates execution between scripts, and most closely matches what Ruby's +import statement does.

    +
    +# foo.rb
    +window.alert "Hello from a ruby file"
    +
    +<!-- foo.html -->
    +<script type="application/ruby" src="foo.rb"></script>
    +
    +
  • +
  • type: specifies the mime-type of the script-tag, which is used to figure +out the language; see Inline Scripts type attribute.

    +

    Technically this is not required, as the extension of the file will be used +to detect the language if type is omitted, but most browsers will then +attempt to run the code with it's built-in JavaScript engine, and most likely +throw a JavaScript syntax exception. So, it's recommended to always using the +type attribute.

    +
  • +
  • (Optional) defer: See Inline Scripts defer attribute for symantics. +If this is true, the src URI is just downloaded and cached, but is not +run. This allows full control over when the script gets run, as another +script can get the first shot at importing it:

    +
    +<script type="application/ruby" src="foo.rb" defer="true"></script>
    +<script type="application/ruby">import foo</script>
    +
    +
  • +
+
+
+

Execution order

+

Script-tags will be executed in the order they are defined, but before the +start script is executed (if one is provided). All inline +code is to be executed in the same scope, basically as if they all one Ruby +file. This allow methods defined in one script-tag to be called from another:

+
+<script type="application/ruby">
+  window.alert "in first script-tag"
+  def foo
+    "In Foo"
+  end
+</script>
+...
+<script type="application/ruby">
+  window.alert "in second script-tag"
+  window.alert foo
+</script>
+
+
+
+

File-system operations

+

Silverlight runs in a sand-box, not allowing programs access to the machine's +file system, as well as forbidding native user-code from being loaded. However, +IronRuby's implementation abstracts file-system operations, allowing it to +provide different behavior when running in Silverlight. External script tags +are used to define the file system entries.

+
+

Ruby script files

+

Each time an external script-tag is downloaded, it is also cached in-memory so +the same file isn't re-downloaded. This download cache is actually presented to +Ruby as a read-only file system, which is how things like require still +work; they are actually asking if the file exists, except all file-system +operations in Silverlight are redirected to the download cache.

+
+
+

Zip files

+

The external script tag's src attribute can be a *.zip file; this is +useful for larger libraries where it may be cumbersome to list all the script +files out as script-tags.

+

The following attributes are used with a HTML script-tag to reference zip files:

+
    +
  • src: URI to a *.zip file.

    +

    The value of the src attribute will be placed on the language's path, and +basically treated as a folder. When a script file is requested from any other +script, the language will try to find it by using its path and checking for +the existence of the file. If the path contains a known zip file name, then +it will continue to look inside the zip file:

    +
    +<script type="application/x-zip-compressed" src="lib.zip"></script>
    +<script type="application/ruby">
    +  $LOAD_PATH << 'lib'
    +  require 'erb'
    +</script>
    +
    +
  • +
  • type: must be set to application/x-zip-compressed

    +
  • +
  • Not implemented yet (Optional) defer: toggles whether the zip file is placed on the path. +Defaults to false which adds it to the path, while true will not add it to the +path. When defer="true" you can always programmatically add it to the path +using Ruby's sys module:

    +
    +<script type="application/x-zip-compressed" src="ruby-stdlib.zip" defer="true">
    +</script>
    +<script type="application/x-ruby">
    +  $LOAD_PATH << "ruby-stdlib"
    +</script>
    +
    +
  • +
+

Note: "added the zip file to the path" is not implemented at the moment, so +it will always behave as defer="true".

+

Since zip files are treated just like a folder, you can put anything inside +the ZIP file; DLLs, XAML files, text files, images, etc, and use them just +like you would if they were part of the file-system:

+
+<script type="application/x-zip-compressed" src="my-archive.zip"></script>
+<script type="application/ruby">
+  require "my-archive.zip/Foo.dll"
+  txt = File.read "my-archive/foo.txt"
+</script>
+
+

When accessing files inside a zip file, just use the zip filename as if it were +a folder name.

+

Note: Today only the zip file's filename (without the .zip extension) is +required to access it (example: open('my-archive/foo.txt')), though that's +a bug in the implementation, not the spec.

+
+
+
+
+

Vector-graphics

+

Silverlight not only provides an execution model for Ruby scripts, but it also +allows for rendering vector graphics in the browser, for animations or rich +user-interfaces. This can be accomplished by using eXtensible Application +Markup Language (XAML), +or directly from Ruby.

+
+

XAML

+

XAML markup can be embedded into a script-tag, either inline or as an external +file:

+
+<!-- inline XAML file -->
+<script type="application/xaml+xml" id="inlineXAML" width="200" height="75">
+  <Canvas Background="Wheat">
+    <TextBlock Canvas.Left="20" FontSize="24" />
+  </Canvas>
+</script>
+
+<!-- external XAML file -->
+<script type="application/xaml+xml" id="externalXAML" src="foo.xaml">
+</script>
+
+

The following attributes are used with a HTML script-tag to embed XAML content:

+
    +
  • width: the width of Silverlight control surface.

    +
  • +
  • height: the height of Silverlight control surface.

    +
  • +
  • type: should be set to application/xaml+xml, though +application/xml+xaml also works.

    +
  • +
  • src: URI to a XAML file. It behaves like external scripts src +attribute with regard to downloading and caching. If it is not set, the XAML +content is expected to be provided in the script-tag's innerText.

    +
  • +
  • id: DOM ID the generated Silverlight control will have; this is needed +to tell Ruby code to run against a specific Silverlight control.

    +
  • +
  • (Optional) defer: By default either the external or inline XAML +causes dlr.js to inject a Silverlight control, and set the RootVisual of +that Silverlight instance to the XAML provided by the script-tag. However, if +this is true, the Silverlight control is still injected into the DOM, but +the XAML content is not set as the RootVisual of that control. If the XAML +content was provided by the src attribute, then the file is still +downloaded and cached. Setting the RootVisual can be done manually, however:

    +
    +<script type="application/xaml+xml" id="xamlContent" defer="true">
    +  <Canvas Background="Wheat">
    +    <TextBlock Canvas.Left="20" FontSize="24" />
    +  </Canvas>
    +</script>
    +
    +<script type="application/ruby" class="xamlContent">
    +  include System::Windows
    +  Application.current.load_root_visual_from_string document.xamlContent.innerHTML
    +</script>
    +
    +

    If you do not want to even have the control added, then you'll have to +disable dlr.js's auto-adding:

    +
    +<script type="text/javascript">
    +  window.DLR = {autoAdd: false}
    +</script>
    +<script type="text/javascript" src="dlr.js"></script>
    +
    +<script type="application/xaml+xml" id="xamlContent" defer="true">
    +  <Canvas Background="Wheat">
    +    <TextBlock Canvas.Left="20" FontSize="24" />
    +  </Canvas>
    +</script>
    +
    +

    Then you can add a control at any time:

    +
    +<script type="text/javascript">
    +  DLR.createObject({width: 200, height: 200});
    +</script>
    +
    +
  • +
+

This is similar to the way that Silverlight 1.0 allowed XAML to be embedded.

+
+
+

From Ruby

+

XAML is simply a markup language for creating objects, so the same thing can +be done directly from Ruby. Given this XAML:

+
+<script type="application/xaml+xml" id="xamlContent">
+  <Canvas Background="Wheat">
+    <TextBlock Canvas.Left="20" FontSize="24" />
+  </Canvas>
+</script>
+
+

The equivalent in Ruby would be:

+
+import System::Windows
+import System::Windows::Media
+import System::Windows::Controls
+c = Canvas.new
+c.background = SolidColorBrush.new Colors.wheat
+t = TextBlock.new
+t.font_size = 24
+c.children.add t
+Canvas.set_left t, 20
+Application.current.root_visual = c
+
+
+
+
+

Unclassified documentation

+

This is just random documentation, which has yet to be incorporated into a place +that makes sense.

+
+

Multiple controls

+

Browsers allow for multiple object-controls to be on a single page, so you +could have multiple Silverlight controls on the same page. This introduces an +unexpected side-effect to having Silverlight run code inside script-tags; +every Silverlight would run run every script-tag. Consider the following:

+
+<div id="message"></div>
+<script src="dlr.js"></script>
+<script type="text/javascript">
+  DLR.createObject({width: '100', height: '100'})
+</script>
+<script type="application/ruby">
+  include System::Windows
+  Application.current.root_visual = UserControl.new
+</script>
+
+

Both Silverlight controls will get their root_visual set, since the Ruby +script-tag is executed twice, once for each Silverlight control. To avoid +this, script-tags must be scoped to a specific Silverlight control. dlr.js +instructs dlr.xap to only run "un-scoped" script-tags on the first control +added to a page, and only run "scoped" script-tags with subsequent added +controls. To "scope" a script-tag, the class attribute contains the same value +as its corresponding Silverlight control's xamlid initParam:

+
+<script type="text/javascript">
+  DLR.createObject({xamlid: 'control1'})
+</script>
+<script type="application/ruby" class="control1">
+  # will only run in the "control1" object
+</script>
+
+

An un-scoped script-tag is simply a script-tag without a class attribute. +These will run in a Silverlight control that does not have the "xamlid" +initParam set; dlr.js does this for only the first control it injects.

+

If you intend to not use Silverlight graphics through script-tags, or only use +them in one control, then you don't need to worry about scoping; scoping only +comes into play when you have multiple controls. If you want to use +Silverlight graphics, you can use this same strategy on script-tags containing +XAML to make sure the proper RootVisual is set.

+

A script-tag having a "*" class attribute will cause it to run in every +script-tag, so the first-example's behavior is still possible.

+
+
+

Changes to existing behavior

+

Though there are no major breaking changes to any existing behavior of +existing applications, there needs to be some changes to existing features to +make this new activation-model work properly.

+

Previously, the "start" initParam (entry-point/start-script to the DLR +Silverlight app) is required if there is no app.* file in the XAP file. If +the "start" initParam is omitted in this condition, an error would have been +raised, complaining about not finding an app.* file.

+

This requirement is now completely relaxed; neither an app.* file or a "start" +initParam is required. If no "start" script or defer=false script-tags exist +on the page; then nothing runs and no error is raised. This is relaxed because +a Silverlight application can be only inline XAML.

+
+<script type="application/ruby">
+  ...
+</script>
+<object ...>
+  <params name="source" value="app.xap" />
+  <params name="initParams" value="" /> <!-- no initParams value needed -->
+</object>
+
+

Though these changes are being introduced to remove the need for Chiron, it is +still a useful tool for generating XAP files on the fly. Chiron now serves +files out of the "externalUrlPrefix" path if it is a relative path, so +extensions can be developed locally and Chiron instantly picks them up. Also, +Chiron's XAP building features will build an appropriate XAP file depending on +whether you're using slvx files or not.

+
+
+

Interacting with markup

+

To make accessing the HTML and XAML easier and more like how JavaScript works, +variables pointing to them are added to the scope in which script-tags are +executed in.

+
+

HTML accessors

+

document maps to System.Windows.Browser.HtmlPage.Document, which is of type +HtmlDocument, and window maps to System.Windows.Browser.HtmlPage.Window, which +is of type HtmlWindow.

+

When a method is called on an HtmlDocument that does not exist, it calls +GetElementById(methodName). The following examples are in Ruby:

+
+document.a_div_id
+# same as ...
+document.GetElementById("a_div_id")
+
+document.doesnotexist # None
+
+

When a method is called on an HtmlElement that does not exist, it should call +GetProperty(methodName). When calling the non-existent method as a setter, +call SetProperty(methodName, value):

+
+document.a_div_id.innerHTML
+# same as ...
+document.a_div_id.GetProperty("innerHTML")
+
+document.a_div_id.innerHTML = "Hi"
+# same as ...
+document.a_div_id.SetProperty("innerHTML", "Hi")
+
+

When an indexer is used on an HtmlElement, it should call +GetAttribute(methodName). When setting the indexer, call +SetAttribute(methodName, value):

+
+document.link_id['href']
+# same as ...
+document.link_id.GetAttribute('href')
+
+document.link_id['href'] = 'http://foo.com'
+# same as ...
+document.a_div_id.SetAttribute('href', 'http://foo.com')
+
+
+
+

XAML accessors

+

me and xaml both map to System.Windows.Application.Current.RootVisual, having a +base-type of FrameworkElement. When a method is called that does not exist on +me or xaml, then FindName(methodName) is called. This allows access to any +XAML elements with an x:Name value to be accessed by the x:Name value as a +method call:

+
+xaml.Message.Text = "New Message"
+
+

To load a XAML file into the root visual, use the LoadRootVisual methods:

+
+xaml.LoadRootVisual(UserControl.new, File.open("foo.xaml"))
+xaml.LoadRootVisualFromString(document.xamlContent.innerHTML)
+
+
+
+

Event handling from code

+

From code, events on both HTML and XAML elements can be hooked via the +language's specific .NET event hookup syntax. Given the following HTML:

+
+<a id="cm">Click Me</a>
+
+

You can hook the onclick event from Ruby:

+
+<script type="application/ruby">
+  document.cm.onclick do |s, e|
+    s.innerHTML = "Clicked!"
+  end
+</script>
+
+

Hooking XAML events also works:

+
+<script type="application/xml+xaml">
+  ...
+  <TextBox x:Name="xcm" Text="Click Me" />
+  ...
+</script>
+
+<script type="application/ruby">
+  include System::Windows
+  root_visual = Application.current.root_visual
+  root_visual.xcm.MouseLeftButtonDown do |s, e|
+    s.text = "Clicked!"
+  end
+</script>
+
+

Event handling from HTML or XAML markup is not supported!

+
+
+
+

Debugging

+
+

Visual Studio Debugger

+

When you have debug mode turned on, it will just work as it used to. Attach +the debugger to the browser, open the script file in Visual Studio, place a +breakpoint, etc. Having the script files in the XAP does not make a difference +for debugging; it's all about the debug-able code being generated and having +the file open in VS.

+
+
+
+
+

Implementation details

+
+

Silverlight XAP file structure

+

With both user scripts and larger libraries outside the main XAP file, the +main XAP only serves as a container for the AppManifest.xaml and any dynamic +language assemblies required by the application. Silverlight 3 introduced +"Transparent Silverlight Extensions", a way to package your own libraries into +a .slvx (Silverlight versioned extension) file (really just zip file) which +applications can depend on by referencing it from their AppManifest.xaml. +Using this feature all the assemblies can be removed from the XAP file, put in +a slvx file, and hosted on an internet location so other applications can +depend on it. Instead of IronRuby and IronRuby releases containing the +assemblies built for Silverlight, they will just contain a dlr.xap file. This +xap file will be shared between all applications; only advanced scenarios will +need to modify the xap file. It will only containing just two files:

+
+

AppManifest.xaml

+

The AppManifest.xaml file just references the Microsoft.Scripting.slvx file, +and points the Silverlight application at the static entry point in +Microsoft.Scripting.Silverlight.dll (included in Microsoft.Scripting.slvx):

+
+<Deployment
+ xmlns="http://schemas.microsoft.com/client/2007/deployment"
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+ RuntimeVersion="3.0.40624.0"
+ EntryPointAssembly="Microsoft.Scripting.Silverlight"
+ EntryPointType="Microsoft.Scripting.Silverlight.DynamicApplication">
+ <Deployment.ExternalParts>
+   <ExtensionPart Source="http://gestalt.ironruby.net/dlr-20100305/Microsoft.Scripting.slvx"/>
+ </Deployment.ExternalParts>
+</Deployment>
+
+
+
+

languages.config

+

The languages.config file lists the configuration information for DLR +languages that can be used in Silverlight. This file can be present in a +DLR-based xap today for defining configuration information for languages other +than Ruby and Ruby, but now this file must be present if an application +depends on the Microsoft.Scripting.slvx file. Included in this information is +the URL for each language's slvx file:

+
+<Languages>
+    <Language names="IronRuby;Ruby;rb"
+              extensions=".rb"
+              languageContext="IronRuby.Runtime.RubyContext"
+              assemblies="IronRuby.dll;IronRuby.Libraries.dll"
+              external="http://gestalt.ironruby.net/dlr-20100305/IronRuby.slvx" />
+</Languages>
+
+

The language node can have the following attributes:

+
    +
  • names: ;-separated list of names the language can use
  • +
  • extensions: ;-separated list of file extensions the language can use
  • +
  • languageContext: language's type that inherits from LanguageContext
  • +
  • assemblies: URIs to assemblies which make up the language
      +
    • Optional: but if external is missing, then this list of assemblies is +assumed to be in the XAP
    • +
    +
  • +
  • external: SLVX file for all language assemblies
  • +
+
+
+

Microsoft.Scripting.slvx

+

Microsoft.Scripting.slvx will contain the following DLLs: +- Microsoft.Scripting.dll +- Microsoft.Dynamic.dll +- Microsoft.Scripting.Core.dll +- Microsoft.Scripting.ExtensionAttribute.dll +- Microsoft.Scripting.Silverlight.dll

+

When an application starts up, Silverlight downloads the +Microsoft.Scripting.slvx file, loads all the assemblies inside it, and then +kicks off the static entry point, +Microsoft.Scripting.Silverlight.DynamicApplication. During its startup logic, +it tries to load language configuration from the languages.config file; if +that fails it looks to already loaded assemblies referenced in the +AppManifest.xaml and loads the configuration info off the assemblies directly. +Because of this, XAP files must have a languages.config to download languages +on-demand. After the language configuration is loaded, the script-tags on the +HTML page are processed; for each language used, the existence of all the +language's assemblies in the XAP file is checked, and if they are not all +found the language's external-package is downloaded, assemblies inside loaded, +and a ScriptEngine created for the language. Both the list of assemblies and +external-package URI are provided by languages.config.

+

If an application cannot depend on the slvx files hosted on the internet, they +can be hosted on any machine. Just change the AppManifest.xaml and +languages.config to point to the new location. If Chiron is still being used +to generate the XAP file, then the externalUrlPrefix in Chiron.exe.config is +the only setting that needs to be changed.

+
+
+
+
+

Non-goals

+

These are clearly non-goals for IronRuby, though some persuasion might move +them up into ideas.

+
+

jQuery-influenced selectors

+

Though the idea of having a jQuery-like selector API for DLR languages is +attractive, it is less feasible since each language will want a different way +to specify the syntax. Also, libraries in those languages may exist (eg. +Ruby's Hpricot), so it'd be best to use those directly. This might be +addressed in a future change, or another library, but is out of scope for this +change.

+
+
+
+

FAQ

+

The "start" script referenced in the Inline Scripts section ... what is it?

+
+

The "start" script is another term for the entry-point script. By default it's +app.*, and * is used to figure out the correct language to instantiate. +However, the user can specify the specific start-script in the initParams:

+
+<param name="initParams" value="start=myapp.rb" />
+
+

See the original dynamic languages in Silverlight specification for more +information TODO add link.

+
+

Can I write offline Silverlight applications with this?

+
+Not with Silverlight 3. Offline Silverlight applications do not allow using +the browser DOM APIs, since they just run the Silverlight control outside the +browser. Therefore, offline Silverlight applications cannot use <script> tag +code. If you'd like to write a Silverlight application that runs both in the +browser and on the desktop, you'll need to keep everything in the XAP file and +use the "start" script as the application's entry-point. Silverlight 4 +supports HTML hosted in an OOB app, so it's possible to directly support this +in the future.
+
+
+

Specs

+ +
+
+

Public APIs

+
    +
  • dlr.js
  • +
  • Microsoft.Scripting.Silverlight.dll
  • +
  • DLR Hosting API
  • +
+
+
+ + +
+
+ + + + + + + + + \ No newline at end of file diff --git a/browser/docs.rst b/browser/docs.rst new file mode 100644 index 0000000..8e940e3 --- /dev/null +++ b/browser/docs.rst @@ -0,0 +1,662 @@ +=================================== +Ruby in the browser documentation +=================================== + +.. contents:: + +----- +Setup +----- +The only setup *absolutely* required is to reference ``dlr.js`` from any HTML +file you wish write Ruby code in:: + + + +If you want to develop without a network connection, or deploy +IronRuby yourself, simply extract the re-distributable package and +reference it:: + + + + +.. note:: using the ``gestalt.ironruby.net`` version is preferred, especially in + production. However, please pick a specific version (like ``dlr-20100305.js``, + rather than ``dlr-latest.js``), as this will ensure your application keeps + working between releases; ``dlr-latest.js`` will always point to the most + recent version. + +By default just doing this sets up your HTML page for executing Ruby code. +See the dlr.js API documentation for more advanced uses. + + +------------------- +Writing Ruby code +------------------- +Ruby code is executed by using HTML script-tags; either inline or as an +external file. This implementation aims to be compliant with the `HTML4 +specification for scripting in HTML pages +`_. + + +Inline scripts +~~~~~~~~~~~~~~ +The following attributes are used with a HTML script-tag to embed Ruby: + +- ``type``: defines the mime-type the script code should map to; the following + prefixes are allowed: ``application/``, ``text/``, and ``application/x-``. + The actual language name will be passed to the DLR hosting API, so the above + example could have used ``application/ironruby`` and it would still work:: + + + + +- (Optional) ``defer``: if this attribute is not present, its value is + ``false``. Otherwise, the value is ``true`` (even if it's explicitly set to + ``defer="false"``; this is how all modern browsers behave). If set to ``true`` + the code is not run; but it can be used to evaluate later:: + + + + + +External scripts +~~~~~~~~~~~~~~~~ +The following attributes are used with a HTML script-tag to reference Ruby: + +- ``src``: specify the path to a Ruby file. If it's a relative URI, it will + be considered relative to the HTML file. The ``src`` URI is downloaded and + cached in memory, building a virtual file-system of external script code. + Then this file is executed in its own DLR ScriptScope, which properly + isolates execution between scripts, and most closely matches what Ruby's + ``import`` statement does. + :: + + # foo.rb + window.alert "Hello from a ruby file" + + + + +- ``type``: specifies the mime-type of the script-tag, which is used to figure + out the language; see Inline Scripts ``type`` attribute. + + Technically this is not required, as the extension of the file will be used + to detect the language if ``type`` is omitted, but most browsers will then + attempt to run the code with it's built-in JavaScript engine, and most likely + throw a JavaScript syntax exception. So, it's recommended to always using the + ``type`` attribute. + +- (Optional) ``defer``: See Inline Scripts ``defer`` attribute for symantics. + If this is true, the ``src`` URI is just downloaded and cached, but is not + run. This allows full control over when the script gets run, as another + script can get the first shot at importing it:: + + + + + +Execution order +~~~~~~~~~~~~~~~ +Script-tags will be executed in the order they are defined, but before the +`start script `_ is executed (if one is provided). All inline +code is to be executed in the same scope, basically as if they all one Ruby +file. This allow methods defined in one script-tag to be called from another:: + + + ... + + + +File-system operations +~~~~~~~~~~~~~~~~~~~~~~ +Silverlight runs in a sand-box, not allowing programs access to the machine's +file system, as well as forbidding native user-code from being loaded. However, +IronRuby's implementation abstracts file-system operations, allowing it to +provide different behavior when running in Silverlight. External script tags +are used to define the file system entries. + + +Ruby script files ++++++++++++++++++++ +Each time an external script-tag is downloaded, it is also cached in-memory so +the same file isn't re-downloaded. This download cache is actually presented to +Ruby as a read-only file system, which is how things like ``require`` still +work; they are actually asking if the file exists, except all file-system +operations in Silverlight are redirected to the download cache. + + +Zip files ++++++++++ +The external script tag's ``src`` attribute can be a ``*.zip`` file; this is +useful for larger libraries where it may be cumbersome to list all the script +files out as script-tags. + +The following attributes are used with a HTML script-tag to reference zip files: + +- ``src``: URI to a ``*.zip`` file. + + The value of the src attribute will be placed on the language's path, and + basically treated as a folder. When a script file is requested from any other + script, the language will try to find it by using its path and checking for + the existence of the file. If the path contains a known zip file name, then + it will continue to look inside the zip file:: + + + + +- ``type``: must be set to ``application/x-zip-compressed`` +- *Not implemented yet* (Optional) ``defer``: toggles whether the zip file is placed on the path. + Defaults to false which adds it to the path, while true will not add it to the + path. When ``defer="true"`` you can always programmatically add it to the path + using Ruby's sys module:: + + + + +Note: "added the zip file to the path" is not implemented at the moment, so +it will always behave as ``defer="true"``. + +Since zip files are treated just like a folder, you can put anything inside +the ZIP file; DLLs, XAML files, text files, images, etc, and use them just +like you would if they were part of the file-system:: + + + + +When accessing files inside a zip file, just use the zip filename as if it were +a folder name. + +Note: Today only the zip file's filename (without the .zip extension) is +required to access it (example: ``open('my-archive/foo.txt')``), though that's +a bug in the implementation, not the spec. + + +--------------- +Vector-graphics +--------------- +Silverlight not only provides an execution model for Ruby scripts, but it also +allows for rendering vector graphics in the browser, for animations or rich +user-interfaces. This can be accomplished by using `eXtensible Application +Markup Language (XAML) `_, +or directly from Ruby. + + +XAML +~~~~ +XAML markup can be embedded into a script-tag, either inline or as an external +file:: + + + + + + + +The following attributes are used with a HTML script-tag to embed XAML content: + +- ``width``: the width of Silverlight control surface. + +- ``height``: the height of Silverlight control surface. + +- ``type``: should be set to ``application/xaml+xml``, though + ``application/xml+xaml`` also works. + +- ``src``: URI to a XAML file. It behaves like external scripts ``src`` + attribute with regard to downloading and caching. If it is not set, the XAML + content is expected to be provided in the script-tag's innerText. + +- ``id``: DOM ID the generated Silverlight control will have; this is needed + to tell Ruby code to run against a specific Silverlight control. + +- (Optional) ``defer``: By default either the external or inline XAML + causes ``dlr.js`` to inject a Silverlight control, and set the RootVisual of + that Silverlight instance to the XAML provided by the script-tag. However, if + this is ``true``, the Silverlight control is still injected into the DOM, but + the XAML content is not set as the RootVisual of that control. If the XAML + content was provided by the ``src`` attribute, then the file is still + downloaded and cached. Setting the RootVisual can be done manually, however:: + + + + + + If you do not want to even have the control added, then you'll have to + disable dlr.js's auto-adding:: + + + + + + + Then you can add a control at any time:: + + + + +This is similar to the way that `Silverlight 1.0 allowed XAML to be embedded +`_. + + +From Ruby +~~~~~~~~~~~ +XAML is simply a markup language for creating objects, so the same thing can +be done directly from Ruby. Given this XAML:: + + + +The equivalent in Ruby would be:: + + import System::Windows + import System::Windows::Media + import System::Windows::Controls + c = Canvas.new + c.background = SolidColorBrush.new Colors.wheat + t = TextBlock.new + t.font_size = 24 + c.children.add t + Canvas.set_left t, 20 + Application.current.root_visual = c + + +-------------------------- +Unclassified documentation +-------------------------- +This is just random documentation, which has yet to be incorporated into a place +that makes sense. + + +Multiple controls +~~~~~~~~~~~~~~~~~ +Browsers allow for multiple object-controls to be on a single page, so you +could have multiple Silverlight controls on the same page. This introduces an +unexpected side-effect to having Silverlight run code inside script-tags; +every Silverlight would run run every script-tag. Consider the following:: + +
+ + + + +Both Silverlight controls will get their `root_visual` set, since the Ruby +script-tag is executed twice, once for each Silverlight control. To avoid +this, script-tags must be scoped to a specific Silverlight control. ``dlr.js`` +instructs ``dlr.xap`` to only run "un-scoped" script-tags on the first control +added to a page, and only run "scoped" script-tags with subsequent added +controls. To "scope" a script-tag, the class attribute contains the same value +as its corresponding Silverlight control's ``xamlid`` initParam:: + + + + +An un-scoped script-tag is simply a script-tag without a class attribute. +These will run in a Silverlight control that does not have the "xamlid" +initParam set; dlr.js does this for only the first control it injects. + +If you intend to not use Silverlight graphics through script-tags, or only use +them in one control, then you don't need to worry about scoping; scoping only +comes into play when you have multiple controls. If you want to use +Silverlight graphics, you can use this same strategy on script-tags containing +XAML to make sure the proper RootVisual is set. + +A script-tag having a "*" class attribute will cause it to run in every +script-tag, so the first-example's behavior is still possible. + + +Changes to existing behavior +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Though there are no major breaking changes to any existing behavior of +existing applications, there needs to be some changes to existing features to +make this new activation-model work properly. + +Previously, the "start" initParam (entry-point/start-script to the DLR +Silverlight app) is required if there is no ``app.*`` file in the XAP file. If +the "start" initParam is omitted in this condition, an error would have been +raised, complaining about not finding an ``app.*`` file. + +This requirement is now completely relaxed; neither an app.* file or a "start" +initParam is required. If no "start" script or defer=false script-tags exist +on the page; then nothing runs and no error is raised. This is relaxed because +a Silverlight application can be only inline XAML. +:: + + + + + + + +Though these changes are being introduced to remove the need for Chiron, it is +still a useful tool for generating XAP files on the fly. Chiron now serves +files out of the "externalUrlPrefix" path if it is a relative path, so +extensions can be developed locally and Chiron instantly picks them up. Also, +Chiron's XAP building features will build an appropriate XAP file depending on +whether you're using slvx files or not. + + +Interacting with markup +~~~~~~~~~~~~~~~~~~~~~~~ +To make accessing the HTML and XAML easier and more like how JavaScript works, +variables pointing to them are added to the scope in which script-tags are +executed in. + +HTML accessors +++++++++++++++ + +`document` maps to `System.Windows.Browser.HtmlPage.Document`, which is of type +`HtmlDocument`, and `window` maps to `System.Windows.Browser.HtmlPage.Window`, which +is of type `HtmlWindow`. + +When a method is called on an `HtmlDocument` that does not exist, it calls +`GetElementById(methodName)`. The following examples are in Ruby:: + + document.a_div_id + # same as ... + document.GetElementById("a_div_id") + + document.doesnotexist # None + +When a method is called on an `HtmlElement` that does not exist, it should call +`GetProperty(methodName)`. When calling the non-existent method as a setter, +call `SetProperty(methodName, value)`:: + + document.a_div_id.innerHTML + # same as ... + document.a_div_id.GetProperty("innerHTML") + + document.a_div_id.innerHTML = "Hi" + # same as ... + document.a_div_id.SetProperty("innerHTML", "Hi") + +When an indexer is used on an `HtmlElement`, it should call +``GetAttribute(methodName)``. When setting the indexer, call +``SetAttribute(methodName, value)``:: + + document.link_id['href'] + # same as ... + document.link_id.GetAttribute('href') + + document.link_id['href'] = 'http://foo.com' + # same as ... + document.a_div_id.SetAttribute('href', 'http://foo.com') + +XAML accessors +++++++++++++++ + +``me`` and ``xaml`` both map to ``System.Windows.Application.Current.RootVisual``, having a +base-type of ``FrameworkElement``. When a method is called that does not exist on +``me`` or ``xaml``, then ``FindName(methodName)`` is called. This allows access to any +XAML elements with an ``x:Name`` value to be accessed by the ``x:Name`` value as a +method call:: + + xaml.Message.Text = "New Message" + + +To load a XAML file into the root visual, use the ``LoadRootVisual`` methods:: + + xaml.LoadRootVisual(UserControl.new, File.open("foo.xaml")) + xaml.LoadRootVisualFromString(document.xamlContent.innerHTML) + + +Event handling from code +++++++++++++++++++++++++ + +From code, events on both HTML and XAML elements can be hooked via the +language's specific .NET event hookup syntax. Given the following HTML:: + + Click Me + +You can hook the ``onclick`` event from Ruby:: + + + +Hooking XAML events also works:: + + + + + +Event handling from HTML or XAML markup is not supported! + + +Debugging +~~~~~~~~~ + +Visual Studio Debugger +++++++++++++++++++++++ + +When you have debug mode turned on, it will just work as it used to. Attach +the debugger to the browser, open the script file in Visual Studio, place a +breakpoint, etc. Having the script files in the XAP does not make a difference +for debugging; it's all about the debug-able code being generated and having +the file open in VS. + + +---------------------- +Implementation details +---------------------- + +Silverlight XAP file structure +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +With both user scripts and larger libraries outside the main XAP file, the +main XAP only serves as a container for the AppManifest.xaml and any dynamic +language assemblies required by the application. Silverlight 3 introduced +"Transparent Silverlight Extensions", a way to package your own libraries into +a .slvx (Silverlight versioned extension) file (really just zip file) which +applications can depend on by referencing it from their AppManifest.xaml. +Using this feature all the assemblies can be removed from the XAP file, put in +a slvx file, and hosted on an internet location so other applications can +depend on it. Instead of IronRuby and IronRuby releases containing the +assemblies built for Silverlight, they will just contain a dlr.xap file. This +xap file will be shared between all applications; only advanced scenarios will +need to modify the xap file. It will only containing just two files: + +AppManifest.xaml +++++++++++++++++ +The AppManifest.xaml file just references the Microsoft.Scripting.slvx file, +and points the Silverlight application at the static entry point in +Microsoft.Scripting.Silverlight.dll (included in Microsoft.Scripting.slvx):: + + + + + + + +languages.config +++++++++++++++++ +The languages.config file lists the configuration information for DLR +languages that can be used in Silverlight. This file can be present in a +DLR-based xap today for defining configuration information for languages other +than Ruby and Ruby, but now this file must be present if an application +depends on the Microsoft.Scripting.slvx file. Included in this information is +the URL for each language's slvx file:: + + + + + +The language node can have the following attributes: + +- ``names``: ``;``-separated list of names the language can use +- ``extensions``: ``;``-separated list of file extensions the language can use +- ``languageContext``: language's type that inherits from ``LanguageContext`` +- ``assemblies``: URIs to assemblies which make up the language + + - Optional: but if external is missing, then this list of assemblies is + assumed to be in the XAP + +- ``external``: SLVX file for all language assemblies + +Microsoft.Scripting.slvx +++++++++++++++++++++++++ +Microsoft.Scripting.slvx will contain the following DLLs: +- Microsoft.Scripting.dll +- Microsoft.Dynamic.dll +- Microsoft.Scripting.Core.dll +- Microsoft.Scripting.ExtensionAttribute.dll +- Microsoft.Scripting.Silverlight.dll + +When an application starts up, Silverlight downloads the +Microsoft.Scripting.slvx file, loads all the assemblies inside it, and then +kicks off the static entry point, +Microsoft.Scripting.Silverlight.DynamicApplication. During its startup logic, +it tries to load language configuration from the languages.config file; if +that fails it looks to already loaded assemblies referenced in the +AppManifest.xaml and loads the configuration info off the assemblies directly. +Because of this, XAP files must have a languages.config to download languages +on-demand. After the language configuration is loaded, the script-tags on the +HTML page are processed; for each language used, the existence of all the +language's assemblies in the XAP file is checked, and if they are not all +found the language's external-package is downloaded, assemblies inside loaded, +and a ScriptEngine created for the language. Both the list of assemblies and +external-package URI are provided by languages.config. + +If an application cannot depend on the slvx files hosted on the internet, they +can be hosted on any machine. Just change the AppManifest.xaml and +languages.config to point to the new location. If Chiron is still being used +to generate the XAP file, then the externalUrlPrefix in Chiron.exe.config is +the only setting that needs to be changed. + + +--------- +Non-goals +--------- +These are clearly non-goals for IronRuby, though some persuasion might move +them up into ideas. + +jQuery-influenced selectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Though the idea of having a jQuery-like selector API for DLR languages is +attractive, it is less feasible since each language will want a different way +to specify the syntax. Also, libraries in those languages may exist (eg. +Ruby's Hpricot), so it'd be best to use those directly. This might be +addressed in a future change, or another library, but is out of scope for this +change. + + +--- +FAQ +--- + +The "start" script referenced in the Inline Scripts section ... what is it? + + The "start" script is another term for the entry-point script. By default it's + ``app.*``, and ``*`` is used to figure out the correct language to instantiate. + However, the user can specify the specific start-script in the initParams:: + + + + See the original dynamic languages in Silverlight specification for more + information TODO add link. + +Can I write offline Silverlight applications with this? + + Not with Silverlight 3. Offline Silverlight applications do not allow using + the browser DOM APIs, since they just run the Silverlight control outside the + browser. Therefore, offline Silverlight applications cannot use + + + + \ No newline at end of file diff --git a/browser/download.rst b/browser/download.rst new file mode 100644 index 0000000..f790cf3 --- /dev/null +++ b/browser/download.rst @@ -0,0 +1,27 @@ +-------- +Download +-------- +**No** IronRuby install is required to develop and deploy Ruby applications for the +browser, though you can always download the pieces hosted online (for working +without a network connection). + +- `Silverlight `_ + +- `IronRuby in Silverlight re-distributable package `_ + + Zip-compressed file containing all the pieces needed to develop and deploy + Ruby applications for the browser. Here are its contents: + + - `dlr.js `_ + + Handles enabling the HTML page to run Ruby code with IronPython in + Silverlight. + + - `dlr.xap `_ + + Actual static Silverlight application, which has logic for executing Ruby. + + - `Microsoft.Scripting.slvx `_ + and `IronRuby.slvx `_ + + IronRuby binaries, including the Dynamic Language Runtime. diff --git a/browser/examples.html b/browser/examples.html new file mode 100644 index 0000000..84daffb --- /dev/null +++ b/browser/examples.html @@ -0,0 +1,87 @@ + + + + + + IronRuby.net / Examples + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/browser/examples.rst b/browser/examples.rst new file mode 100644 index 0000000..7a2c22d --- /dev/null +++ b/browser/examples.rst @@ -0,0 +1,8 @@ +-------- +Examples +-------- + +- `Silverlight.net/dlr examples `_ +- `Gestalt Samples `_ +- `Gestalt Widget Pack `_ + diff --git a/browser/examples/calculator-testing.zip b/browser/examples/calculator-testing.zip new file mode 100644 index 0000000..834cd69 Binary files /dev/null and b/browser/examples/calculator-testing.zip differ diff --git a/browser/examples/dlrconsole.zip b/browser/examples/dlrconsole.zip new file mode 100644 index 0000000..faac41a Binary files /dev/null and b/browser/examples/dlrconsole.zip differ diff --git a/browser/examples/dlrconsole/app.xap b/browser/examples/dlrconsole/app.xap new file mode 100644 index 0000000..98dba3d Binary files /dev/null and b/browser/examples/dlrconsole/app.xap differ diff --git a/browser/examples/dlrconsole/index.html b/browser/examples/dlrconsole/index.html new file mode 100644 index 0000000..c221d34 --- /dev/null +++ b/browser/examples/dlrconsole/index.html @@ -0,0 +1,40 @@ + + + + Interactive DLR Console + + + + + +
+ + + + + + Get Microsoft Silverlight + + + +
+ + + + + diff --git a/browser/examples/helloworld/index.html b/browser/examples/helloworld/index.html new file mode 100644 index 0000000..92b0cca --- /dev/null +++ b/browser/examples/helloworld/index.html @@ -0,0 +1,55 @@ + + + + Hello, World + + + + + +

+<script src="http://gestalt.ironruby.net/dlr-latest.js"
+        type="text/javascript"></script>
+<script type="text/ruby">
+    window.alert "Ruby in the browser!"
+</script>
+<script type="text/python">
+    window.Alert("Python works too!")
+</script>
+ + + + + + + + + + + + + + + + diff --git a/browser/examples/index.html b/browser/examples/index.html new file mode 100644 index 0000000..6da0d5a --- /dev/null +++ b/browser/examples/index.html @@ -0,0 +1,9 @@ + + + +Loading ... + + +Loading ... + + \ No newline at end of file diff --git a/browser/examples/mandelbrot.zip b/browser/examples/mandelbrot.zip new file mode 100644 index 0000000..d077cb5 Binary files /dev/null and b/browser/examples/mandelbrot.zip differ diff --git a/browser/examples/mandelbrot/bin.zip b/browser/examples/mandelbrot/bin.zip new file mode 100644 index 0000000..ce61659 Binary files /dev/null and b/browser/examples/mandelbrot/bin.zip differ diff --git a/browser/examples/mandelbrot/build.bat b/browser/examples/mandelbrot/build.bat new file mode 100644 index 0000000..1470ab4 --- /dev/null +++ b/browser/examples/mandelbrot/build.bat @@ -0,0 +1,19 @@ +@echo off + +set csc=C:\Windows\Microsoft.NET\Framework\v3.5\csc.exe +set slpath=%merlin_root%\Utilities\Silverlight\x86ret + +if exist %~dp0bin.zip del %~dp0bin.zip + +if not exist %~dp0__bin ( + mkdir %~dp0__bin +) else ( + rmdir /Q /S %~dp0__bin +) +%csc% /noconfig /nologo /nostdlib+ /target:library /out:%~dp0__bin\mandelbrotbase.dll /reference:%slpath%\mscorlib.dll,%slpath%\System.dll,%slpath%\System.Windows.dll,%slpath%\System.Core.dll %~dp0mandelbrotbase.cs +%~dp0bin3\chiron.exe /s /d:__bin /x:bin.zip +if exist %~dp0__bin ( + rmdir /Q /S %~dp0__bin +) + +echo mandelbrotbase.cs compiled and put in bin.zip diff --git a/browser/examples/mandelbrot/index.html b/browser/examples/mandelbrot/index.html new file mode 100644 index 0000000..510a4c1 --- /dev/null +++ b/browser/examples/mandelbrot/index.html @@ -0,0 +1,34 @@ + + + + + + + mandelbrot + + + + + + + + + + + diff --git a/browser/examples/mandelbrot/mandelbrot.py b/browser/examples/mandelbrot/mandelbrot.py new file mode 100644 index 0000000..e71f47d --- /dev/null +++ b/browser/examples/mandelbrot/mandelbrot.py @@ -0,0 +1,181 @@ +import sys +sys.path.append("bin") + +# Uses C# for the actual mandelbrot calculation, because it's +# very very number crunchy. However, that doesn't mean IronPython is +# slow, in-fact, on mandelbrot IronPython it one of the fastest +# scripting languages: +# http://mastrodonato.info/index.php/2009/08/comparison-script-languages-for-the-fractal-geometry/ +import clr +clr.AddReferenceToFile("bin/mandelbrotbase.dll") +import mandelbrotbase + +from System import Random, DateTime, Math +from System.Windows import Visibility +from System.Windows.Controls import UserControl, Canvas +from Microsoft.Scripting.Silverlight import DynamicApplication + +class Mandelbrot(UserControl): + + DefaultXS = -2.1 + DefaultYS = -1.3 + DefaultXE = 1.0 + DefaultYE = 1.3 + + def __init__(self): + print '__init__' + self.LoadComponent() + + self.StartPoint = None + self.Randomizer = Random(DateTime.Now.Millisecond) + self.Generator = mandelbrotbase.MandelbrotGenerator(self, int(self.Content.FractalArea.Width), int(self.Content.FractalArea.Height)) + self.Generator.Completed += self.Generator_Completed + + # commands + self.Content.ResetButton.Click += lambda sender, e: self.Reset() + self.Content.ZoomInButton.Click += lambda sender, e: self.ZoomIn() + self.Content.ZoomOutButton.Click += lambda sender, e: self.ZoomOut() + self.Content.PanLeftButton.Click += lambda sender, e: self.Pan(-50, 0) + self.Content.PanRightButton.Click += lambda sender, e: self.Pan(50, 0) + self.Content.PanUpButton.Click += lambda sender, e: self.Pan(0, -50) + self.Content.PanDownButton.Click += lambda sender, e: self.Pan(0, 50) + self.Content.RandomButton.Click += lambda sender, e: self.Randomize() + + self.Content.FractalArea.MouseLeftButtonDown += self.area_MouseLeftButtonDown + self.Content.FractalArea.MouseLeftButtonUp += self.area_MouseLeftButtonUp + self.Content.FractalArea.MouseMove += self.area_MouseMove + + def LoadComponent(self): + print 'LoadComponent' + xaml = DynamicApplication.LoadComponentFromString(open("mandelbrot.xaml").read()) + xaml.Loaded += self.UserControl_Loaded + self.Content = xaml + + def Generator_Completed(self, sender, e): + self.Content.image.ImageSource = e.Image + self.SetEnabled(True) + + def UserControl_Loaded(self, sender, e): + print 'UserControl_Loaded' + self.Reset() + + def ZoomIn(self): + self.Redraw(50, 50, self.Content.FractalArea.Width - 50, self.Content.FractalArea.Height - 50) + + def ZoomOut(self): + self.Redraw(-50, -50, self.Content.FractalArea.Width + 50, self.Content.FractalArea.Height + 50) + + def Pan(self, panX, panY): + self.Redraw(0 + panX, 0 + panY, self.Content.FractalArea.Width + panX, self.Content.FractalArea.Height + panY) + + def Randomize(self): + self.CurrentXS = Mandelbrot.DefaultXS + self.CurrentYS = Mandelbrot.DefaultYS + self.CurrentXE = Mandelbrot.DefaultXE + self.CurrentYE = Mandelbrot.DefaultYE + + xs = self.Randomizer.Next(0, int(self.Content.FractalArea.Width)) + ys = self.Randomizer.Next(0, int(self.Content.FractalArea.Height)) + w = self.Randomizer.Next(3, 100) + h = int(w / (self.Content.FractalArea.Width / self.Content.FractalArea.Height)) + + self.Redraw(xs, ys, xs + w, ys + h) + + def Reset(self): + print 'Reset' + self.CurrentXS = Mandelbrot.DefaultXS + self.CurrentYS = Mandelbrot.DefaultYS + self.CurrentXE = Mandelbrot.DefaultXE + self.CurrentYE = Mandelbrot.DefaultYE + self.Redraw(0, 0, self.Content.FractalArea.Width, self.Content.FractalArea.Height) + + def area_MouseLeftButtonDown(self, sender, e): + self.StartPoint = e.GetPosition(self.Content.FractalArea) + self.Content.selection.Visibility = Visibility.Visible + self.Content.selection.Width = 0 + self.Content.selection.Height = 0 + Canvas.SetLeft(self.Content.selection, self.StartPoint.X) + Canvas.SetTop(self.Content.selection, self.StartPoint.Y) + self.Content.FractalArea.CaptureMouse() + + def area_MouseLeftButtonUp(self, sender, e): + if self.StartPoint is not None: + currentPoint = e.GetPosition(self.Content.FractalArea) + if currentPoint != self.StartPoint: + if currentPoint.X > self.StartPoint.X: + xs = self.StartPoint.X + else: + xs = currentPoint.X + if currentPoint.Y > self.StartPoint.Y: + ys = self.StartPoint.Y + else: + ys = currentPoint.Y + xe = xs + self.Content.selection.Width + ye = ys + self.Content.selection.Height + + self.Redraw(xs, ys, xe, ye) + + self.StartPoint = None + self.Content.selection.Visibility = Visibility.Collapsed + self.Content.FractalArea.ReleaseMouseCapture() + + def area_MouseMove(self, sender, e): + if self.StartPoint is not None: + currentPoint = e.GetPosition(self.Content.FractalArea) + + if currentPoint.X < 0: currentPoint.X = 0 + if currentPoint.Y < 0: currentPoint.Y = 0 + if currentPoint.X > self.Content.FractalArea.Width: currentPoint.X = self.Content.FractalArea.Width + if currentPoint.Y > self.Content.FractalArea.Height: currentPoint.Y = self.Content.FractalArea.Height + + self.Content.selection.Width = Math.Abs(currentPoint.X - self.StartPoint.X) + self.Content.selection.Height = self.Content.selection.Width / (self.Content.FractalArea.Width / self.Content.FractalArea.Height) + + if currentPoint.X > self.StartPoint.X: + canvasLeft = self.StartPoint.X + else: + canvasLeft = currentPoint.X + + if currentPoint.Y > self.StartPoint.Y: + canvasTop = self.StartPoint.Y + else: + canvasTop = currentPoint.Y + + Canvas.SetLeft(self.Content.selection, canvasLeft) + Canvas.SetTop(self.Content.selection, canvasTop) + + def Redraw(self, xs, ys, xe, ye): + print 'Redraw' + self.SetEnabled(False) + + w = self.CurrentXE - self.CurrentXS + h = self.CurrentYE - self.CurrentYS + + xsp = (xs * 100 / self.Content.FractalArea.Width) + cxs = (w / 100 * xsp) + self.CurrentXS + xep = (xe * 100 / self.Content.FractalArea.Width) + cxe = (w / 100 * xep) + self.CurrentXS + ysp = (ys * 100 / self.Content.FractalArea.Height) + cys = (h / 100 * ysp) + self.CurrentYS + yep = (ye * 100 / self.Content.FractalArea.Height) + cye = (h / 100 * yep) + self.CurrentYS + + self.CurrentXS = cxs + self.CurrentXE = cxe + self.CurrentYS = cys + self.CurrentYE = cye + + self.Generator.Generate(self.CurrentXS, self.CurrentYS, self.CurrentXE, self.CurrentYE) + + def SetEnabled(self, enabled): + self.Content.PanDownButton.IsEnabled = enabled + self.Content.PanLeftButton.IsEnabled = enabled + self.Content.PanRightButton.IsEnabled = enabled + self.Content.PanUpButton.IsEnabled = enabled + self.Content.ZoomInButton.IsEnabled = enabled + self.Content.ZoomOutButton.IsEnabled = enabled + self.Content.ResetButton.IsEnabled = enabled + self.Content.RandomButton.IsEnabled = enabled + +from Microsoft.Scripting.Silverlight import DynamicApplication +DynamicApplication.Current.RootVisual = Mandelbrot() \ No newline at end of file diff --git a/browser/examples/mandelbrot/mandelbrot.xaml b/browser/examples/mandelbrot/mandelbrot.xaml new file mode 100644 index 0000000..c7d45a5 --- /dev/null +++ b/browser/examples/mandelbrot/mandelbrot.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + Originally created by A.Boschin : andrea@boschin.it + + + + + + + + + + + + + diff --git a/browser/examples/mandelbrot/mandelbrotbase.cs b/browser/examples/mandelbrot/mandelbrotbase.cs new file mode 100644 index 0000000..aa2f7b2 --- /dev/null +++ b/browser/examples/mandelbrot/mandelbrotbase.cs @@ -0,0 +1,229 @@ +using System; +using System.Net; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Documents; +using System.Windows.Ink; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using System.Collections.Generic; +using System.Windows.Media.Imaging; +using System.ComponentModel; +using System.Threading; + +namespace mandelbrotbase +{ + public class MandelbrotGenerator + { + /// + /// Occurs when [completed]. + /// + public event EventHandler Completed; + /// + /// Gets or sets the palette. + /// + /// The palette. + private Color[] Palette { get; set; } + /// + /// Gets or sets the width. + /// + /// The width. + public int Width { get; set; } + /// + /// Gets or sets the height. + /// + /// The height. + public int Height { get; set; } + /// + /// Gets or sets the UI element. + /// + /// The UI element. + public UIElement UIElement { get; set; } + + /// + /// Initializes a new instance of the class. + /// + public MandelbrotGenerator(UIElement uiElement, int width, int height) + { + this.UIElement = uiElement; + this.Width = width; + this.Height = height; + this.Palette = GeneratePalette(); + } + + /// + /// Draws the specified sx. + /// + /// The sx. + /// The sy. + /// The fx. + /// The fy. + /// + private void Draw(object state) + { + GenerationState size = state as GenerationState; + + if (size == null) + throw new InvalidOperationException(); + + int[] bmap = new int[this.Width * this.Height]; + + // Creates the Bitmap we draw to + // From here on out is just converted from the c++ version. + double x, y, x1, y1, xx, xmin, xmax, ymin, ymax = 0.0; + + int looper, s, z = 0; + double intigralX, intigralY = 0.0; + xmin = size.SX; // Start x value, normally -2.1 + ymin = size.SY; // Start y value, normally -1.3 + xmax = size.FX; // Finish x value, normally 1 + ymax = size.FY; // Finish y value, normally 1.3 + intigralX = (xmax - xmin) / this.Width; // Make it fill the whole window + intigralY = (ymax - ymin) / this.Height; + x = xmin; + + for (s = 0; s < this.Width; s++) + { + y = ymin; + for (z = 0; z < this.Height; z++) + { + x1 = 0; + y1 = 0; + looper = 0; + + while (looper < 100 && ((x1 * x1) + (y1 * y1)) < 4) + { + looper++; + xx = (x1 * x1) - (y1 * y1) + x; + y1 = 2 * x1 * y1 + y; + x1 = xx; + } + + // Get the percent of where the looper stopped + double perc = looper / (100.0); + // Get that part of a 255 scale + int val = ((int)(perc * (this.Palette.Length - 1))); + // Use that number to set the color + + Color px = this.Palette[val]; + bmap[z * this.Width + s] = px.A << 24 | px.R << 16 | px.G << 8 | px.B; + + y += intigralY; + } + + x += intigralX; + } + + this.UIElement.Dispatcher.BeginInvoke( + new Action( + data => + { + WriteableBitmap output = new WriteableBitmap(this.Width, this.Height); + + try + { + for (int i = 0; i < data.Length; i++) + output.Pixels[i] = data[i]; + + this.OnCompleted(new CompletedEventArgs(output)); + } + finally + { + output.Invalidate(); + } + + }), bmap); + } + + /// + /// Generates the palette. + /// + /// + private Color[] GeneratePalette() + { + List colors = new List(); + + for (int c = 0; c < 256; c++) + colors.Add(Color.FromArgb(0xff, (byte)c, 0, 0)); + for (int c = 0; c < 256; c++) + colors.Add(Color.FromArgb(0xff, 0xff, 0, (byte)c)); + for (int c = 255; c >= 0; c--) + colors.Add(Color.FromArgb(0xff, (byte)c, 0, 0xff)); + for (int c = 0; c < 256; c++) + colors.Add(Color.FromArgb(0xff, 0, (byte)c, 0xff)); + for (int c = 255; c >= 0; c--) + colors.Add(Color.FromArgb(0xff, 0, 0xff, (byte)c)); + for (int c = 0; c < 256; c++) + colors.Add(Color.FromArgb(0xff, 0, (byte)c, 0xff)); + for (int c = 255; c >= 0; c--) + colors.Add(Color.FromArgb(0xff, (byte)c, 0, 0xff)); + for (int c = 0; c < 256; c++) + colors.Add(Color.FromArgb(0xff, 0xff, 0, (byte)c)); + for (int c = 0; c < 256; c++) + colors.Add(Color.FromArgb(0xff, (byte)c, 0, 0)); + + var half = + colors.Concat( + colors.Reverse()); + + return half.Concat(half).ToArray(); + } + + /// + /// Raises the event. + /// + /// The instance containing the event data. + private void OnCompleted(CompletedEventArgs e) + { + EventHandler handler = Completed; + + if (handler != null) + handler(this, e); + } + + /// + /// Generates the specified sx. + /// + /// The sx. + /// The sy. + /// The fx. + /// The fy. + public void Generate(double sx, double sy, double fx, double fy) + { + ThreadPool.QueueUserWorkItem( + new WaitCallback(Draw), new GenerationState { SX = sx, SY = sy, FX = fx, FY = fy }); + } + + /// + /// + /// + private class GenerationState + { + public double SX { get; set; } + public double SY { get; set; } + public double FX { get; set; } + public double FY { get; set; } + } + } + + public class CompletedEventArgs : EventArgs + { + /// + /// Gets or sets the image. + /// + /// The image. + public ImageSource Image { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// The image. + public CompletedEventArgs(ImageSource image) + { + this.Image = image; + } + } +} diff --git a/browser/examples/mushroom.zip b/browser/examples/mushroom.zip new file mode 100644 index 0000000..fd06c52 Binary files /dev/null and b/browser/examples/mushroom.zip differ diff --git a/browser/examples/mushroom/index.html b/browser/examples/mushroom/index.html new file mode 100644 index 0000000..5089630 --- /dev/null +++ b/browser/examples/mushroom/index.html @@ -0,0 +1,48 @@ + + + + Speaking Mushroom + + + + +
+ Type your name: + +
+ + + + + + + + + + + diff --git a/browser/examples/mushroom/mushroom.xaml b/browser/examples/mushroom/mushroom.xaml new file mode 100644 index 0000000..c58171b --- /dev/null +++ b/browser/examples/mushroom/mushroom.xaml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Visible + + + + + Visible + + + + + + + + + + + + + + + + Visible + + + + + Visible + + + + + Visible + + + + + Visible + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/browser/examples/mushroom/style.css b/browser/examples/mushroom/style.css new file mode 100644 index 0000000..5dce045 --- /dev/null +++ b/browser/examples/mushroom/style.css @@ -0,0 +1,22 @@ +body { + text-align: center; +} +body, input[type=text], input[type=button] { + background-color: black; + color: white; + font-family: consolas, monotype; + font-size: 24px; +} +input[type=text], input[type=button] { + border: 2px solid gray; + font-size: 24px; + font-family: consolas, monotype; +} +input[type=button] { + background-color: #333; + margin-left: 5px; +} +#silverlightDLRObject1 { + margin-left: auto; + margin-right: auto; +} diff --git a/browser/examples/photoviewer.zip b/browser/examples/photoviewer.zip new file mode 100644 index 0000000..a37960d Binary files /dev/null and b/browser/examples/photoviewer.zip differ diff --git a/browser/examples/photoviewer/app.rb b/browser/examples/photoviewer/app.rb new file mode 100644 index 0000000..ed0ff18 --- /dev/null +++ b/browser/examples/photoviewer/app.rb @@ -0,0 +1,60 @@ +require 'view' +require 'lib/system-json' + +module Photoviewer + class App + Uri = System::Uri + Net = System::Net + Json = System::Json + + def initialize + @view = View.new(self) + @url = "http://api.flickr.com/services/rest" + @options = { + :method => "flickr.photos.search", + :format => "json", + :nojsoncallback => "1", + :api_key => "6dba7971b2abf352b9dcd48a2e5a5921", + :sort => "relevance", + :per_page => "30" + } + document.submit_search.onclick do |s, e| + create_request document.keyword.value + end + end + + def create_request(keyword, page = 1) + @view.loading_start + + @options[:tags] = keyword + @options[:page] = page + @url = make_url @url, @options + + request = Net::WebClient.new + request.download_string_completed do |_,e| + show_images e.result + end + request.download_string_async Uri.new(@url) + end + + def show_images(response) + @flickr = Json.parse response + @view.show_images @flickr, @options[:tags], @options[:page] + @view.loading_finish + end + + private + + def make_url(url, options) + first, separator = true, '?' + options.each do |key, value| + separator = "&" unless first + url += "#{separator}#{key}=#{value}" + first = false + end + url + end + end +end + +$app = Photoviewer::App.new diff --git a/browser/examples/photoviewer/assets/loading.gif b/browser/examples/photoviewer/assets/loading.gif new file mode 100644 index 0000000..2538b9c Binary files /dev/null and b/browser/examples/photoviewer/assets/loading.gif differ diff --git a/browser/examples/photoviewer/assets/screen.css b/browser/examples/photoviewer/assets/screen.css new file mode 100644 index 0000000..d7fbafd --- /dev/null +++ b/browser/examples/photoviewer/assets/screen.css @@ -0,0 +1,76 @@ +body { + font-family: "Trebuchet MS" Verdana sans-serif; + border: 0px; padding: 0px; margin: 0px; +} + +div.clear { + clear: both; +} + +/* main search box */ +.search { + padding: 20px; + margin: 20px; + border: 10px solid gray; + background-color: #ccc; +} + +form#search { + margin: 0px; +} +form#search #images_loading { + width: 18px; + height: 15px; + display: none; +} + + +/* search results */ +#search_results { + display: none; +} + +/* search images */ +#search_images { + padding-top: 10px; +} +#search_images .image, .image a, .image a img { + float: left; + padding: 0px; + margin: 0px; + border: 0px; +} +#search_images .image a:link, +#search_images .image a:visited { + background-color: white; + padding: 5px; + margin: 5px; + background-color: white; + border: 1px solid gray; +} +#search_images .image a:hover { + background-color: #ff9966; +} + +/* search links */ +#search_links { + clear: both; + padding-top: 10px; +} +#search_links a { + border: 1px solid #003344; + margin: 2px; + padding: 4px 5px 1px; + color: #003344; + background-color: white; + text-decoration: none; +} +#search_links a:hover, +#search_links a.active { + color: white; + background-color: #003344; + border: 1px solid white; +} +#search_links a.active { + cursor: default; +} diff --git a/browser/examples/photoviewer/images.erb b/browser/examples/photoviewer/images.erb new file mode 100644 index 0000000..7250d1d --- /dev/null +++ b/browser/examples/photoviewer/images.erb @@ -0,0 +1,16 @@ +<% if @flickr.stat == "ok" && @flickr.photos.total.to_i > 0 %> +
+ <% @flickr.photos.photo.each do |p| %> +
+ #{ p.title }") %>" + rel="lightbox[<%= @tags %>]" + > + + +
+ <% end %> +
+<% else %> + No images found! +<% end %> \ No newline at end of file diff --git a/browser/examples/photoviewer/index.html b/browser/examples/photoviewer/index.html new file mode 100644 index 0000000..2cecc7f --- /dev/null +++ b/browser/examples/photoviewer/index.html @@ -0,0 +1,50 @@ + + + + photoviewer + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + diff --git a/browser/examples/photoviewer/lib.zip b/browser/examples/photoviewer/lib.zip new file mode 100644 index 0000000..b454fee Binary files /dev/null and b/browser/examples/photoviewer/lib.zip differ diff --git a/browser/examples/photoviewer/lib/System.Json.dll b/browser/examples/photoviewer/lib/System.Json.dll new file mode 100644 index 0000000..cdbddd9 Binary files /dev/null and b/browser/examples/photoviewer/lib/System.Json.dll differ diff --git a/browser/examples/photoviewer/lib/erb.rb b/browser/examples/photoviewer/lib/erb.rb new file mode 100644 index 0000000..a1336f8 --- /dev/null +++ b/browser/examples/photoviewer/lib/erb.rb @@ -0,0 +1,799 @@ +# = ERB -- Ruby Templating +# +# Author:: Masatoshi SEKI +# Documentation:: James Edward Gray II and Gavin Sinclair +# +# See ERB for primary documentation and ERB::Util for a couple of utility +# routines. +# +# Copyright (c) 1999-2000,2002,2003 Masatoshi SEKI +# +# You can redistribute it and/or modify it under the same terms as Ruby. + +# +# = ERB -- Ruby Templating +# +# == Introduction +# +# ERB provides an easy to use but powerful templating system for Ruby. Using +# ERB, actual Ruby code can be added to any plain text document for the +# purposes of generating document information details and/or flow control. +# +# A very simple example is this: +# +# require 'erb' +# +# x = 42 +# template = ERB.new <<-EOF +# The value of x is: <%= x %> +# EOF +# puts template.result(binding) +# +# Prints: The value of x is: 42 +# +# More complex examples are given below. +# +# +# == Recognized Tags +# +# ERB recognizes certain tags in the provided template and converts them based +# on the rules below: +# +# <% Ruby code -- inline with output %> +# <%= Ruby expression -- replace with result %> +# <%# comment -- ignored -- useful in testing %> +# % a line of Ruby code -- treated as <% line %> (optional -- see ERB.new) +# %% replaced with % if first thing on a line and % processing is used +# <%% or %%> -- replace with <% or %> respectively +# +# All other text is passed through ERB filtering unchanged. +# +# +# == Options +# +# There are several settings you can change when you use ERB: +# * the nature of the tags that are recognized; +# * the value of $SAFE under which the template is run; +# * the binding used to resolve local variables in the template. +# +# See the ERB.new and ERB#result methods for more detail. +# +# +# == Examples +# +# === Plain Text +# +# ERB is useful for any generic templating situation. Note that in this example, we use the +# convenient "% at start of line" tag, and we quote the template literally with +# %q{...} to avoid trouble with the backslash. +# +# require "erb" +# +# # Create template. +# template = %q{ +# From: James Edward Gray II +# To: <%= to %> +# Subject: Addressing Needs +# +# <%= to[/\w+/] %>: +# +# Just wanted to send a quick note assuring that your needs are being +# addressed. +# +# I want you to know that my team will keep working on the issues, +# especially: +# +# <%# ignore numerous minor requests -- focus on priorities %> +# % priorities.each do |priority| +# * <%= priority %> +# % end +# +# Thanks for your patience. +# +# James Edward Gray II +# }.gsub(/^ /, '') +# +# message = ERB.new(template, 0, "%<>") +# +# # Set up template data. +# to = "Community Spokesman " +# priorities = [ "Run Ruby Quiz", +# "Document Modules", +# "Answer Questions on Ruby Talk" ] +# +# # Produce result. +# email = message.result +# puts email +# +# Generates: +# +# From: James Edward Gray II +# To: Community Spokesman +# Subject: Addressing Needs +# +# Community: +# +# Just wanted to send a quick note assuring that your needs are being addressed. +# +# I want you to know that my team will keep working on the issues, especially: +# +# * Run Ruby Quiz +# * Document Modules +# * Answer Questions on Ruby Talk +# +# Thanks for your patience. +# +# James Edward Gray II +# +# === Ruby in HTML +# +# ERB is often used in .rhtml files (HTML with embedded Ruby). Notice the need in +# this example to provide a special binding when the template is run, so that the instance +# variables in the Product object can be resolved. +# +# require "erb" +# +# # Build template data class. +# class Product +# def initialize( code, name, desc, cost ) +# @code = code +# @name = name +# @desc = desc +# @cost = cost +# +# @features = [ ] +# end +# +# def add_feature( feature ) +# @features << feature +# end +# +# # Support templating of member data. +# def get_binding +# binding +# end +# +# # ... +# end +# +# # Create template. +# template = %{ +# +# Ruby Toys -- <%= @name %> +# +# +#

<%= @name %> (<%= @code %>)

+#

<%= @desc %>

+# +#
    +# <% @features.each do |f| %> +#
  • <%= f %>
  • +# <% end %> +#
+# +#

+# <% if @cost < 10 %> +# Only <%= @cost %>!!! +# <% else %> +# Call for a price, today! +# <% end %> +#

+# +# +# +# }.gsub(/^ /, '') +# +# rhtml = ERB.new(template) +# +# # Set up template data. +# toy = Product.new( "TZ-1002", +# "Rubysapien", +# "Geek's Best Friend! Responds to Ruby commands...", +# 999.95 ) +# toy.add_feature("Listens for verbal commands in the Ruby language!") +# toy.add_feature("Ignores Perl, Java, and all C variants.") +# toy.add_feature("Karate-Chop Action!!!") +# toy.add_feature("Matz signature on left leg.") +# toy.add_feature("Gem studded eyes... Rubies, of course!") +# +# # Produce result. +# rhtml.run(toy.get_binding) +# +# Generates (some blank lines removed): +# +# +# Ruby Toys -- Rubysapien +# +# +#

Rubysapien (TZ-1002)

+#

Geek's Best Friend! Responds to Ruby commands...

+# +#
    +#
  • Listens for verbal commands in the Ruby language!
  • +#
  • Ignores Perl, Java, and all C variants.
  • +#
  • Karate-Chop Action!!!
  • +#
  • Matz signature on left leg.
  • +#
  • Gem studded eyes... Rubies, of course!
  • +#
+# +#

+# Call for a price, today! +#

+# +# +# +# +# +# == Notes +# +# There are a variety of templating solutions available in various Ruby projects: +# * ERB's big brother, eRuby, works the same but is written in C for speed; +# * Amrita (smart at producing HTML/XML); +# * cs/Template (written in C for speed); +# * RDoc, distributed with Ruby, uses its own template engine, which can be reused elsewhere; +# * and others; search the RAA. +# +# Rails, the web application framework, uses ERB to create views. +# +class ERB + Revision = '$Date: 2009-02-24 02:43:45 +0900 (Tue, 24 Feb 2009) $' #' + + # Returns revision information for the erb.rb module. + def self.version + "erb.rb [2.0.4 #{ERB::Revision.split[1]}]" + end +end + +#-- +# ERB::Compiler +class ERB + class Compiler # :nodoc: + class PercentLine # :nodoc: + def initialize(str) + @value = str + end + attr_reader :value + alias :to_s :value + end + + class Scanner # :nodoc: + SplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>)|(\n)/ + + @scanner_map = {} + def self.regist_scanner(klass, trim_mode, percent) + @scanner_map[[trim_mode, percent]] = klass + end + + def self.default_scanner=(klass) + @default_scanner = klass + end + + def self.make_scanner(src, trim_mode, percent) + klass = @scanner_map.fetch([trim_mode, percent], @default_scanner) + klass.new(src, trim_mode, percent) + end + + def initialize(src, trim_mode, percent) + @src = src + @stag = nil + end + attr_accessor :stag + + def scan; end + end + + class TrimScanner < Scanner # :nodoc: + TrimSplitRegexp = /(<%%)|(%%>)|(<%=)|(<%#)|(<%)|(%>\n)|(%>)|(\n)/ + + def initialize(src, trim_mode, percent) + super + @trim_mode = trim_mode + @percent = percent + if @trim_mode == '>' + @scan_line = self.method(:trim_line1) + elsif @trim_mode == '<>' + @scan_line = self.method(:trim_line2) + elsif @trim_mode == '-' + @scan_line = self.method(:explicit_trim_line) + else + @scan_line = self.method(:scan_line) + end + end + attr_accessor :stag + + def scan(&block) + @stag = nil + if @percent + @src.each do |line| + percent_line(line, &block) + end + else + @src.each do |line| + @scan_line.call(line, &block) + end + end + nil + end + + def percent_line(line, &block) + if @stag || line[0] != ?% + return @scan_line.call(line, &block) + end + + line[0] = '' + if line[0] == ?% + @scan_line.call(line, &block) + else + yield(PercentLine.new(line.chomp)) + end + end + + def scan_line(line) + line.split(SplitRegexp).each do |token| + next if token.empty? + yield(token) + end + end + + def trim_line1(line) + line.split(TrimSplitRegexp).each do |token| + next if token.empty? + if token == "%>\n" + yield('%>') + yield(:cr) + break + end + yield(token) + end + end + + def trim_line2(line) + head = nil + line.split(TrimSplitRegexp).each do |token| + next if token.empty? + head = token unless head + if token == "%>\n" + yield('%>') + if is_erb_stag?(head) + yield(:cr) + else + yield("\n") + end + break + end + yield(token) + end + end + + def explicit_trim_line(line) + line.scan(/(.*?)(^[ \t]*<%\-|<%\-|<%%|%%>|<%=|<%#|<%|-%>\n|-%>|%>|\z)/m) do |tokens| + tokens.each do |token| + next if token.empty? + if @stag.nil? && /[ \t]*<%-/ =~ token + yield('<%') + elsif @stag && token == "-%>\n" + yield('%>') + yield(:cr) + elsif @stag && token == '-%>' + yield('%>') + else + yield(token) + end + end + end + end + + ERB_STAG = %w(<%= <%# <%) + def is_erb_stag?(s) + ERB_STAG.member?(s) + end + end + + Scanner.default_scanner = TrimScanner + + class SimpleScanner < Scanner # :nodoc: + def scan + @src.each do |line| + line.split(SplitRegexp).each do |token| + next if token.empty? + yield(token) + end + end + end + end + + Scanner.regist_scanner(SimpleScanner, nil, false) + + begin + require 'strscan' + class SimpleScanner2 < Scanner # :nodoc: + def scan + stag_reg = /(.*?)(<%%|<%=|<%#|<%|\n|\z)/ + etag_reg = /(.*?)(%%>|%>|\n|\z)/ + scanner = StringScanner.new(@src) + while ! scanner.eos? + scanner.scan(@stag ? etag_reg : stag_reg) + text = scanner[1] + elem = scanner[2] + yield(text) unless text.empty? + yield(elem) unless elem.empty? + end + end + end + Scanner.regist_scanner(SimpleScanner2, nil, false) + + class ExplicitScanner < Scanner # :nodoc: + def scan + new_line = true + stag_reg = /(.*?)(<%%|<%=|<%#|<%-|<%|\n|\z)/ + etag_reg = /(.*?)(%%>|-%>|%>|\n|\z)/ + scanner = StringScanner.new(@src) + while ! scanner.eos? + if new_line && @stag.nil? && scanner.scan(/[ \t]*<%-/) + yield('<%') + new_line = false + next + end + scanner.scan(@stag ? etag_reg : stag_reg) + text = scanner[1] + elem = scanner[2] + new_line = (elem == "\n") + yield(text) unless text.empty? + if elem == '-%>' + yield('%>') + if scanner.scan(/(\n|\z)/) + yield(:cr) + new_line = true + end + elsif elem == '<%-' + yield('<%') + else + yield(elem) unless elem.empty? + end + end + end + end + Scanner.regist_scanner(ExplicitScanner, '-', false) + + rescue LoadError + end + + class Buffer # :nodoc: + def initialize(compiler) + @compiler = compiler + @line = [] + @script = "" + @compiler.pre_cmd.each do |x| + push(x) + end + end + attr_reader :script + + def push(cmd) + @line << cmd + end + + def cr + @script << (@line.join('; ')) + @line = [] + @script << "\n" + end + + def close + return unless @line + @compiler.post_cmd.each do |x| + push(x) + end + @script << (@line.join('; ')) + @line = nil + end + end + + def compile(s) + out = Buffer.new(self) + + content = '' + scanner = make_scanner(s) + scanner.scan do |token| + if scanner.stag.nil? + case token + when PercentLine + out.push("#{@put_cmd} #{content.dump}") if content.size > 0 + content = '' + out.push(token.to_s) + out.cr + when :cr + out.cr + when '<%', '<%=', '<%#' + scanner.stag = token + out.push("#{@put_cmd} #{content.dump}") if content.size > 0 + content = '' + when "\n" + content << "\n" + out.push("#{@put_cmd} #{content.dump}") + out.cr + content = '' + when '<%%' + content << '<%' + else + content << token + end + else + case token + when '%>' + case scanner.stag + when '<%' + if content[-1] == ?\n + content.chop! + out.push(content) + out.cr + else + out.push(content) + end + when '<%=' + out.push("#{@insert_cmd}((#{content}).to_s)") + when '<%#' + # out.push("# #{content.dump}") + end + scanner.stag = nil + content = '' + when '%%>' + content << '%>' + else + content << token + end + end + end + out.push("#{@put_cmd} #{content.dump}") if content.size > 0 + out.close + out.script + end + + def prepare_trim_mode(mode) + case mode + when 1 + return [false, '>'] + when 2 + return [false, '<>'] + when 0 + return [false, nil] + when String + perc = mode.include?('%') + if mode.include?('-') + return [perc, '-'] + elsif mode.include?('<>') + return [perc, '<>'] + elsif mode.include?('>') + return [perc, '>'] + else + [perc, nil] + end + else + return [false, nil] + end + end + + def make_scanner(src) + Scanner.make_scanner(src, @trim_mode, @percent) + end + + def initialize(trim_mode) + @percent, @trim_mode = prepare_trim_mode(trim_mode) + @put_cmd = 'print' + @insert_cmd = @put_cmd + @pre_cmd = [] + @post_cmd = [] + end + attr_reader :percent, :trim_mode + attr_accessor :put_cmd, :insert_cmd, :pre_cmd, :post_cmd + end +end + +#-- +# ERB +class ERB + # + # Constructs a new ERB object with the template specified in _str_. + # + # An ERB object works by building a chunk of Ruby code that will output + # the completed template when run. If _safe_level_ is set to a non-nil value, + # ERB code will be run in a separate thread with $SAFE set to the + # provided level. + # + # If _trim_mode_ is passed a String containing one or more of the following + # modifiers, ERB will adjust its code generation as listed: + # + # % enables Ruby code processing for lines beginning with % + # <> omit newline for lines starting with <% and ending in %> + # > omit newline for lines ending in %> + # + # _eoutvar_ can be used to set the name of the variable ERB will build up + # its output in. This is useful when you need to run multiple ERB + # templates through the same binding and/or when you want to control where + # output ends up. Pass the name of the variable to be used inside a String. + # + # === Example + # + # require "erb" + # + # # build data class + # class Listings + # PRODUCT = { :name => "Chicken Fried Steak", + # :desc => "A well messages pattie, breaded and fried.", + # :cost => 9.95 } + # + # attr_reader :product, :price + # + # def initialize( product = "", price = "" ) + # @product = product + # @price = price + # end + # + # def build + # b = binding + # # create and run templates, filling member data variebles + # ERB.new(<<-'END_PRODUCT'.gsub(/^\s+/, ""), 0, "", "@product").result b + # <%= PRODUCT[:name] %> + # <%= PRODUCT[:desc] %> + # END_PRODUCT + # ERB.new(<<-'END_PRICE'.gsub(/^\s+/, ""), 0, "", "@price").result b + # <%= PRODUCT[:name] %> -- <%= PRODUCT[:cost] %> + # <%= PRODUCT[:desc] %> + # END_PRICE + # end + # end + # + # # setup template data + # listings = Listings.new + # listings.build + # + # puts listings.product + "\n" + listings.price + # + # _Generates_ + # + # Chicken Fried Steak + # A well messages pattie, breaded and fried. + # + # Chicken Fried Steak -- 9.95 + # A well messages pattie, breaded and fried. + # + def initialize(str, safe_level=nil, trim_mode=nil, eoutvar='_erbout') + @safe_level = safe_level + compiler = ERB::Compiler.new(trim_mode) + set_eoutvar(compiler, eoutvar) + @src = compiler.compile(str) + @filename = nil + end + + # The Ruby code generated by ERB + attr_reader :src + + # The optional _filename_ argument passed to Kernel#eval when the ERB code + # is run + attr_accessor :filename + + # + # Can be used to set _eoutvar_ as described in ERB#new. It's probably easier + # to just use the constructor though, since calling this method requires the + # setup of an ERB _compiler_ object. + # + def set_eoutvar(compiler, eoutvar = '_erbout') + compiler.put_cmd = "#{eoutvar}.concat" + compiler.insert_cmd = "#{eoutvar}.concat" + + cmd = [] + cmd.push "#{eoutvar} = ''" + + compiler.pre_cmd = cmd + + cmd = [] + cmd.push(eoutvar) + + compiler.post_cmd = cmd + end + + # Generate results and print them. (see ERB#result) + def run(b=TOPLEVEL_BINDING) + print self.result(b) + end + + # + # Executes the generated ERB code to produce a completed template, returning + # the results of that code. (See ERB#new for details on how this process can + # be affected by _safe_level_.) + # + # _b_ accepts a Binding or Proc object which is used to set the context of + # code evaluation. + # + def result(b=TOPLEVEL_BINDING) + if @safe_level + th = Thread.start { + $SAFE = @safe_level + eval(@src, b, (@filename || '(erb)'), 1) + } + return th.value + else + return eval(@src, b, (@filename || '(erb)'), 1) + end + end + + def def_method(mod, methodname, fname='(ERB)') # :nodoc: + mod.module_eval("def #{methodname}\n" + self.src + "\nend\n", fname, 0) + end + + def def_module(methodname='erb') # :nodoc: + mod = Module.new + def_method(mod, methodname) + mod + end + + def def_class(superklass=Object, methodname='result') # :nodoc: + cls = Class.new(superklass) + def_method(cls, methodname) + cls + end +end + +#-- +# ERB::Util +class ERB + # A utility module for conversion routines, often handy in HTML generation. + module Util + public + # + # A utility method for escaping HTML tag characters in _s_. + # + # require "erb" + # include ERB::Util + # + # puts html_escape("is a > 0 & a < 10?") + # + # _Generates_ + # + # is a > 0 & a < 10? + # + def html_escape(s) + s.to_s.gsub(/&/, "&").gsub(/\"/, """).gsub(/>/, ">").gsub(/#hoverNav{ left: 0;} +#hoverNav a{ outline: none;} + +#prevLink, #nextLink{ + width: 49%; + height: 100%; + background: transparent url(../images/blank.gif) no-repeat; /* Trick IE into showing hover */ + display: block; + } +#prevLink { left: 0; float: left;} +#nextLink { right: 0; float: right;} +#prevLink:hover, #prevLink:visited:hover { background: url(../images/prevlabel.gif) left 15% no-repeat; } +#nextLink:hover, #nextLink:visited:hover { background: url(../images/nextlabel.gif) right 15% no-repeat; } + + +#imageDataContainer{ + font: 10px Verdana, Helvetica, sans-serif; + background-color: #fff; + margin: 0 auto; + line-height: 1.4em; + overflow: auto; + width: 100% + } + +#imageData{ padding:0 10px; color: #666; } +#imageData #imageDetails{ width: 70%; float: left; text-align: left; } +#imageData #caption{ font-weight: bold; } +#imageData #numberDisplay{ display: block; clear: left; padding-bottom: 1.0em; } +#imageData #bottomNavClose{ width: 66px; float: right; padding-bottom: 0.7em; } + +#overlay{ + position: absolute; + top: 0; + left: 0; + z-index: 90; + width: 100%; + height: 500px; + background-color: #000; + } \ No newline at end of file diff --git a/browser/examples/photoviewer/lightbox/images/blank.gif b/browser/examples/photoviewer/lightbox/images/blank.gif new file mode 100644 index 0000000..1d11fa9 Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/blank.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/close.gif b/browser/examples/photoviewer/lightbox/images/close.gif new file mode 100644 index 0000000..ca517b6 Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/close.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/closelabel.gif b/browser/examples/photoviewer/lightbox/images/closelabel.gif new file mode 100644 index 0000000..87b4f8b Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/closelabel.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/loading.gif b/browser/examples/photoviewer/lightbox/images/loading.gif new file mode 100644 index 0000000..f864d5f Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/loading.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/next.gif b/browser/examples/photoviewer/lightbox/images/next.gif new file mode 100644 index 0000000..1fe6ca1 Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/next.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/nextlabel.gif b/browser/examples/photoviewer/lightbox/images/nextlabel.gif new file mode 100644 index 0000000..6c40e51 Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/nextlabel.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/prev.gif b/browser/examples/photoviewer/lightbox/images/prev.gif new file mode 100644 index 0000000..aefa804 Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/prev.gif differ diff --git a/browser/examples/photoviewer/lightbox/images/prevlabel.gif b/browser/examples/photoviewer/lightbox/images/prevlabel.gif new file mode 100644 index 0000000..51a31c2 Binary files /dev/null and b/browser/examples/photoviewer/lightbox/images/prevlabel.gif differ diff --git a/browser/examples/photoviewer/lightbox/js/effects.js b/browser/examples/photoviewer/lightbox/js/effects.js new file mode 100644 index 0000000..d3940a8 --- /dev/null +++ b/browser/examples/photoviewer/lightbox/js/effects.js @@ -0,0 +1,903 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// See scriptaculous.js for full license. + +/* ------------- element ext -------------- */ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if(this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if(this.slice(0,1) == '#') { + if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if(this.length==7) color = this.toLowerCase(); + } + } + return(color.length==7 ? color : (arguments[0] || this)); +} + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.setStyle = function(element, style) { + element = $(element); + for(k in style) element.style[k.camelize()] = style[k]; +} + +Element.setContentZoom = function(element, percent) { + Element.setStyle(element, {fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); +} + +Element.getOpacity = function(element){ + var opacity; + if (opacity = Element.getStyle(element, 'opacity')) + return parseFloat(opacity); + if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + Element.setStyle(element, { opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : null }); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + Element.setStyle(element, {opacity: value}); + if(/MSIE/.test(navigator.userAgent)) + Element.setStyle(element, + { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.childrenWithClassName = function(element, className) { + return $A($(element).getElementsByTagName('*')).select( + function(c) { return Element.hasClassName(c, className) }); +} + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1'; + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global') } + }, arguments[2] || {}); + Effect[Element.visible(element) ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = {} + +Effect.Transitions.linear = function(pos) { + return pos; +} +Effect.Transitions.sinoidal = function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; +} +Effect.Transitions.reverse = function(pos) { + return 1-pos; +} +Effect.Transitions.flicker = function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; +} +Effect.Transitions.wobble = function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; +} +Effect.Transitions.pulse = function(pos) { + return (Math.floor(pos*10) % 2 == 0 ? + (pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10))); +} +Effect.Transitions.none = function(pos) { + return 0; +} +Effect.Transitions.full = function(pos) { + return 1; +} + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + this.effects.push(effect); + if(!this.interval) + this.interval = setInterval(this.loop.bind(this), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + Element.setStyle(this.element, {zoom: 1}); + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + Element.setOpacity(this.element, position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + Element.makePositioned(this.element); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + Element.setStyle(this.element, { + left: this.options.x * position + this.originalLeft + 'px', + top: this.options.y * position + this.originalTop + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element) + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = Element.getStyle(this.element,'font-size') || '100%'; + ['em','px','%'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if(this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = width + 'px'; + if(this.options.scaleY) d.height = height + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + Element.setStyle(this.element, d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: Element.getStyle(this.element, 'background-image') }; + Element.setStyle(this.element, {backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = Element.getStyle(this.element, 'background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + 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)); + }, + update: function(position) { + Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + Element.setStyle(this.element, Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { with(Element) { + if(effect.options.to!=0) return; + hide(effect.element); + setStyle(effect.element, {opacity: oldOpacity}); }} + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + var options = Object.extend({ + from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0), + to: 1.0, + beforeSetup: function(effect) { with(Element) { + setOpacity(effect.element, effect.options.from); + show(effect.element); }} + }, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { with(Element) { + setStyle(effect.effects[0].element, {position: 'absolute'}); }}, + afterFinishInternal: function(effect) { with(Element) { + hide(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + Element.makeClipping(element); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); }} + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var oldHeight = Element.getStyle(element, 'height'); + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, + Object.extend({ scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { with(Element) { + makeClipping(effect.element); + setStyle(effect.element, {height: '0px'}); + show(effect.element); + }}, + afterFinishInternal: function(effect) { with(Element) { + undoClipping(effect.element); + setStyle(effect.element, {height: oldHeight}); + }} + }, arguments[1] || {}) + ); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { with(Element) { + [makePositioned,makeClipping].call(effect.element); + }}, + afterFinishInternal: function(effect) { with(Element) { + [hide,undoClipping,undoPositioned].call(effect.element); + setStyle(effect.element, {opacity: oldOpacity}); + }} + }) + } + }); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: Element.getStyle(element, 'top'), + left: Element.getStyle(element, 'left'), + opacity: Element.getInlineOpacity(element) }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { with(Element) { + makePositioned(effect.effects[0].element); }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: Element.getStyle(element, 'top'), + left: Element.getStyle(element, 'left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { with(Element) { + undoPositioned(effect.element); + setStyle(effect.element, oldStyle); + }}}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element); + Element.cleanWhitespace(element); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { with(Element) { + makePositioned(effect.element); + makePositioned(effect.element.firstChild); + if(window.opera) setStyle(effect.element, {top: ''}); + makeClipping(effect.element); + setStyle(effect.element, {height: '0px'}); + show(element); }}, + afterUpdateInternal: function(effect) { with(Element) { + setStyle(effect.element.firstChild, {bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, + afterFinishInternal: function(effect) { with(Element) { + undoClipping(effect.element); + undoPositioned(effect.element.firstChild); + undoPositioned(effect.element); + setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element); + Element.cleanWhitespace(element); + var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { with(Element) { + makePositioned(effect.element); + makePositioned(effect.element.firstChild); + if(window.opera) setStyle(effect.element, {top: ''}); + makeClipping(effect.element); + show(element); }}, + afterUpdateInternal: function(effect) { with(Element) { + setStyle(effect.element.firstChild, {bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); + undoPositioned(effect.element.firstChild); + undoPositioned(effect.element); + setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + }, arguments[1] || {}) + ); +} + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { with(Element) { + makeClipping(effect.element); }}, + afterFinishInternal: function(effect) { with(Element) { + hide(effect.element); + undoClipping(effect.element); }} + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransistion: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: Element.getInlineOpacity(element) }; + + var dims = Element.getDimensions(element); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { with(Element) { + hide(effect.element); + makeClipping(effect.element); + makePositioned(effect.element); + }}, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { with(Element) { + setStyle(effect.effects[0].element, {height: '0px'}); + show(effect.effects[0].element); }}, + afterFinishInternal: function(effect) { with(Element) { + [undoClipping, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransistion: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: Element.getInlineOpacity(element) }; + + var dims = Element.getDimensions(element); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { with(Element) { + [makePositioned, makeClipping].call(effect.effects[0].element) }}, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping, undoPositioned].call(effect.effects[0].element); + setStyle(effect.effects[0].element, oldStyle); }} + }, options) + ); +} + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); + var transition = options.transition || Effect.Transitions.sinoidal; + var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; + reverser.bind(transition); + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { with(Element) { + [hide, undoClipping].call(effect.element); + setStyle(effect.element, oldStyle); + }} }); + }}, arguments[1] || {})); +} diff --git a/browser/examples/photoviewer/lightbox/js/lightbox.js b/browser/examples/photoviewer/lightbox/js/lightbox.js new file mode 100644 index 0000000..576c24d --- /dev/null +++ b/browser/examples/photoviewer/lightbox/js/lightbox.js @@ -0,0 +1,817 @@ +// ----------------------------------------------------------------------------------- +// +// Lightbox v2.03.3 +// by Lokesh Dhakar - http://www.huddletogether.com +// 5/21/06 +// +// For more information on this script, visit: +// http://huddletogether.com/projects/lightbox2/ +// +// Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/ +// +// Credit also due to those who have helped, inspired, and made their code available to the public. +// Including: Scott Upton(uptonic.com), Peter-Paul Koch(quirksmode.com), Thomas Fuchs(mir.aculo.us), and others. +// +// +// ----------------------------------------------------------------------------------- +/* + + Table of Contents + ----------------- + Configuration + Global Variables + + Extending Built-in Objects + - Object.extend(Element) + - Array.prototype.removeDuplicates() + - Array.prototype.empty() + + Lightbox Class Declaration + - initialize() + - updateImageList() + - start() + - changeImage() + - resizeImageContainer() + - showImage() + - updateDetails() + - updateNav() + - enableKeyboardNav() + - disableKeyboardNav() + - keyboardAction() + - preloadNeighborImages() + - end() + + Miscellaneous Functions + - getPageScroll() + - getPageSize() + - getKey() + - listenKey() + - showSelectBoxes() + - hideSelectBoxes() + - showFlash() + - hideFlash() + - pause() + - initLightbox() + + Function Calls + - addLoadEvent(initLightbox) + +*/ +// ----------------------------------------------------------------------------------- + +// +// Configuration +// +var fileLoadingImage = "lightbox/images/loading.gif"; +var fileBottomNavCloseImage = "lightbox/images/closelabel.gif"; + +var overlayOpacity = 0.8; // controls transparency of shadow overlay + +var animate = true; // toggles resizing animations +var resizeSpeed = 7; // controls the speed of the image resizing animations (1=slowest and 10=fastest) + +var borderSize = 10; //if you adjust the padding in the CSS, you will need to update this variable + +// ----------------------------------------------------------------------------------- + +// +// Global Variables +// +var imageArray = new Array; +var activeImage; + +if(animate == true){ + overlayDuration = 0.2; // shadow fade in/out duration + if(resizeSpeed > 10){ resizeSpeed = 10;} + if(resizeSpeed < 1){ resizeSpeed = 1;} + resizeDuration = (11 - resizeSpeed) * 0.15; +} else { + overlayDuration = 0; + resizeDuration = 0; +} + +// ----------------------------------------------------------------------------------- + +// +// Additional methods for Element added by SU, Couloir +// - further additions by Lokesh Dhakar (huddletogether.com) +// +Object.extend(Element, { + getWidth: function(element) { + element = $(element); + return element.offsetWidth; + }, + setWidth: function(element,w) { + element = $(element); + element.style.width = w +"px"; + }, + setHeight: function(element,h) { + element = $(element); + element.style.height = h +"px"; + }, + setTop: function(element,t) { + element = $(element); + element.style.top = t +"px"; + }, + setLeft: function(element,l) { + element = $(element); + element.style.left = l +"px"; + }, + setSrc: function(element,src) { + element = $(element); + element.src = src; + }, + setHref: function(element,href) { + element = $(element); + element.href = href; + }, + setInnerHTML: function(element,content) { + element = $(element); + element.innerHTML = content; + } +}); + +// ----------------------------------------------------------------------------------- + +// +// Extending built-in Array object +// - array.removeDuplicates() +// - array.empty() +// +Array.prototype.removeDuplicates = function () { + for(i = 0; i < this.length; i++){ + for(j = this.length-1; j>i; j--){ + if(this[i][0] == this[j][0]){ + this.splice(j,1); + } + } + } +} + +// ----------------------------------------------------------------------------------- + +Array.prototype.empty = function () { + for(i = 0; i <= this.length; i++){ + this.shift(); + } +} + +// ----------------------------------------------------------------------------------- + +// +// Lightbox Class Declaration +// - initialize() +// - start() +// - changeImage() +// - resizeImageContainer() +// - showImage() +// - updateDetails() +// - updateNav() +// - enableKeyboardNav() +// - disableKeyboardNav() +// - keyboardNavAction() +// - preloadNeighborImages() +// - end() +// +// Structuring of code inspired by Scott Upton (http://www.uptonic.com/) +// +var Lightbox = Class.create(); + +Lightbox.prototype = { + + // initialize() + // Constructor runs on completion of the DOM loading. Calls updateImageList and then + // the function inserts html at the bottom of the page which is used to display the shadow + // overlay and the image container. + // + initialize: function() { + + this.updateImageList(); + + // Code inserts html at the bottom of the page that looks similar to this: + // + //
+ // + + + var objBody = document.getElementsByTagName("body").item(0); + + var objOverlay = document.createElement("div"); + objOverlay.setAttribute('id','overlay'); + objOverlay.style.display = 'none'; + objOverlay.onclick = function() { myLightbox.end(); } + objBody.appendChild(objOverlay); + + var objLightbox = document.createElement("div"); + objLightbox.setAttribute('id','lightbox'); + objLightbox.style.display = 'none'; + objLightbox.onclick = function(e) { // close Lightbox is user clicks shadow overlay + if (!e) var e = window.event; + var clickObj = Event.element(e).id; + if ( clickObj == 'lightbox') { + myLightbox.end(); + } + }; + objBody.appendChild(objLightbox); + + var objOuterImageContainer = document.createElement("div"); + objOuterImageContainer.setAttribute('id','outerImageContainer'); + objLightbox.appendChild(objOuterImageContainer); + + // When Lightbox starts it will resize itself from 250 by 250 to the current image dimension. + // If animations are turned off, it will be hidden as to prevent a flicker of a + // white 250 by 250 box. + if(animate){ + Element.setWidth('outerImageContainer', 250); + Element.setHeight('outerImageContainer', 250); + } else { + Element.setWidth('outerImageContainer', 1); + Element.setHeight('outerImageContainer', 1); + } + + var objImageContainer = document.createElement("div"); + objImageContainer.setAttribute('id','imageContainer'); + objOuterImageContainer.appendChild(objImageContainer); + + var objLightboxImage = document.createElement("img"); + objLightboxImage.setAttribute('id','lightboxImage'); + objImageContainer.appendChild(objLightboxImage); + + var objHoverNav = document.createElement("div"); + objHoverNav.setAttribute('id','hoverNav'); + objImageContainer.appendChild(objHoverNav); + + var objPrevLink = document.createElement("a"); + objPrevLink.setAttribute('id','prevLink'); + objPrevLink.setAttribute('href','#'); + objHoverNav.appendChild(objPrevLink); + + var objNextLink = document.createElement("a"); + objNextLink.setAttribute('id','nextLink'); + objNextLink.setAttribute('href','#'); + objHoverNav.appendChild(objNextLink); + + var objLoading = document.createElement("div"); + objLoading.setAttribute('id','loading'); + objImageContainer.appendChild(objLoading); + + var objLoadingLink = document.createElement("a"); + objLoadingLink.setAttribute('id','loadingLink'); + objLoadingLink.setAttribute('href','#'); + objLoadingLink.onclick = function() { myLightbox.end(); return false; } + objLoading.appendChild(objLoadingLink); + + var objLoadingImage = document.createElement("img"); + objLoadingImage.setAttribute('src', fileLoadingImage); + objLoadingLink.appendChild(objLoadingImage); + + var objImageDataContainer = document.createElement("div"); + objImageDataContainer.setAttribute('id','imageDataContainer'); + objLightbox.appendChild(objImageDataContainer); + + var objImageData = document.createElement("div"); + objImageData.setAttribute('id','imageData'); + objImageDataContainer.appendChild(objImageData); + + var objImageDetails = document.createElement("div"); + objImageDetails.setAttribute('id','imageDetails'); + objImageData.appendChild(objImageDetails); + + var objCaption = document.createElement("span"); + objCaption.setAttribute('id','caption'); + objImageDetails.appendChild(objCaption); + + var objNumberDisplay = document.createElement("span"); + objNumberDisplay.setAttribute('id','numberDisplay'); + objImageDetails.appendChild(objNumberDisplay); + + var objBottomNav = document.createElement("div"); + objBottomNav.setAttribute('id','bottomNav'); + objImageData.appendChild(objBottomNav); + + var objBottomNavCloseLink = document.createElement("a"); + objBottomNavCloseLink.setAttribute('id','bottomNavClose'); + objBottomNavCloseLink.setAttribute('href','#'); + objBottomNavCloseLink.onclick = function() { myLightbox.end(); return false; } + objBottomNav.appendChild(objBottomNavCloseLink); + + var objBottomNavCloseImage = document.createElement("img"); + objBottomNavCloseImage.setAttribute('src', fileBottomNavCloseImage); + objBottomNavCloseLink.appendChild(objBottomNavCloseImage); + }, + + + // + // updateImageList() + // Loops through anchor tags looking for 'lightbox' references and applies onclick + // events to appropriate links. You can rerun after dynamically adding images w/ajax. + // + updateImageList: function() { + if (!document.getElementsByTagName){ return; } + var anchors = document.getElementsByTagName('a'); + var areas = document.getElementsByTagName('area'); + + // loop through all anchor tags + for (var i=0; i 1){ + Element.show('numberDisplay'); + Element.setInnerHTML( 'numberDisplay', "Image " + eval(activeImage + 1) + " of " + imageArray.length); + } + + new Effect.Parallel( + [ new Effect.SlideDown( 'imageDataContainer', { sync: true, duration: resizeDuration, from: 0.0, to: 1.0 }), + new Effect.Appear('imageDataContainer', { sync: true, duration: resizeDuration }) ], + { duration: resizeDuration, afterFinish: function() { + // update overlay size and update nav + var arrayPageSize = getPageSize(); + Element.setHeight('overlay', arrayPageSize[1]); + myLightbox.updateNav(); + } + } + ); + }, + + // + // updateNav() + // Display appropriate previous and next hover navigation. + // + updateNav: function() { + + Element.show('hoverNav'); + + // if not first image in set, display prev image button + if(activeImage != 0){ + Element.show('prevLink'); + document.getElementById('prevLink').onclick = function() { + myLightbox.changeImage(activeImage - 1); return false; + } + } + + // if not last image in set, display next image button + if(activeImage != (imageArray.length - 1)){ + Element.show('nextLink'); + document.getElementById('nextLink').onclick = function() { + myLightbox.changeImage(activeImage + 1); return false; + } + } + + this.enableKeyboardNav(); + }, + + // + // enableKeyboardNav() + // + enableKeyboardNav: function() { + document.onkeydown = this.keyboardAction; + }, + + // + // disableKeyboardNav() + // + disableKeyboardNav: function() { + document.onkeydown = ''; + }, + + // + // keyboardAction() + // + keyboardAction: function(e) { + if (e == null) { // ie + keycode = event.keyCode; + escapeKey = 27; + } else { // mozilla + keycode = e.keyCode; + escapeKey = e.DOM_VK_ESCAPE; + } + + key = String.fromCharCode(keycode).toLowerCase(); + + if((key == 'x') || (key == 'o') || (key == 'c') || (keycode == escapeKey)){ // close lightbox + myLightbox.end(); + } else if((key == 'p') || (keycode == 37)){ // display previous image + if(activeImage != 0){ + myLightbox.disableKeyboardNav(); + myLightbox.changeImage(activeImage - 1); + } + } else if((key == 'n') || (keycode == 39)){ // display next image + if(activeImage != (imageArray.length - 1)){ + myLightbox.disableKeyboardNav(); + myLightbox.changeImage(activeImage + 1); + } + } + + }, + + // + // preloadNeighborImages() + // Preload previous and next images. + // + preloadNeighborImages: function(){ + + if((imageArray.length - 1) > activeImage){ + preloadNextImage = new Image(); + preloadNextImage.src = imageArray[activeImage + 1][0]; + } + if(activeImage > 0){ + preloadPrevImage = new Image(); + preloadPrevImage.src = imageArray[activeImage - 1][0]; + } + + }, + + // + // end() + // + end: function() { + this.disableKeyboardNav(); + Element.hide('lightbox'); + new Effect.Fade('overlay', { duration: overlayDuration}); + showSelectBoxes(); + showFlash(); + } +} + +// ----------------------------------------------------------------------------------- + +// +// getPageScroll() +// Returns array with x,y page scroll values. +// Core code from - quirksmode.com +// +function getPageScroll(){ + + var xScroll, yScroll; + + if (self.pageYOffset) { + yScroll = self.pageYOffset; + xScroll = self.pageXOffset; + } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict + yScroll = document.documentElement.scrollTop; + xScroll = document.documentElement.scrollLeft; + } else if (document.body) {// all other Explorers + yScroll = document.body.scrollTop; + xScroll = document.body.scrollLeft; + } + + arrayPageScroll = new Array(xScroll,yScroll) + return arrayPageScroll; +} + +// ----------------------------------------------------------------------------------- + +// +// getPageSize() +// Returns array with page width, height and window width, height +// Core code from - quirksmode.com +// Edit for Firefox by pHaez +// +function getPageSize(){ + + var xScroll, yScroll; + + if (window.innerHeight && window.scrollMaxY) { + xScroll = window.innerWidth + window.scrollMaxX; + yScroll = window.innerHeight + window.scrollMaxY; + } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac + xScroll = document.body.scrollWidth; + yScroll = document.body.scrollHeight; + } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari + xScroll = document.body.offsetWidth; + yScroll = document.body.offsetHeight; + } + + var windowWidth, windowHeight; + +// console.log(self.innerWidth); +// console.log(document.documentElement.clientWidth); + + if (self.innerHeight) { // all except Explorer + if(document.documentElement.clientWidth){ + windowWidth = document.documentElement.clientWidth; + } else { + windowWidth = self.innerWidth; + } + windowHeight = self.innerHeight; + } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode + windowWidth = document.documentElement.clientWidth; + windowHeight = document.documentElement.clientHeight; + } else if (document.body) { // other Explorers + windowWidth = document.body.clientWidth; + windowHeight = document.body.clientHeight; + } + + // for small pages with total height less then height of the viewport + if(yScroll < windowHeight){ + pageHeight = windowHeight; + } else { + pageHeight = yScroll; + } + +// console.log("xScroll " + xScroll) +// console.log("windowWidth " + windowWidth) + + // for small pages with total width less then width of the viewport + if(xScroll < windowWidth){ + pageWidth = xScroll; + } else { + pageWidth = windowWidth; + } +// console.log("pageWidth " + pageWidth) + + arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight) + return arrayPageSize; +} + +// ----------------------------------------------------------------------------------- + +// +// getKey(key) +// Gets keycode. If 'x' is pressed then it hides the lightbox. +// +function getKey(e){ + if (e == null) { // ie + keycode = event.keyCode; + } else { // mozilla + keycode = e.which; + } + key = String.fromCharCode(keycode).toLowerCase(); + + if(key == 'x'){ + } +} + +// ----------------------------------------------------------------------------------- + +// +// listenKey() +// +function listenKey () { document.onkeypress = getKey; } + +// --------------------------------------------------- + +function showSelectBoxes(){ + var selects = document.getElementsByTagName("select"); + for (i = 0; i != selects.length; i++) { + selects[i].style.visibility = "visible"; + } +} + +// --------------------------------------------------- + +function hideSelectBoxes(){ + var selects = document.getElementsByTagName("select"); + for (i = 0; i != selects.length; i++) { + selects[i].style.visibility = "hidden"; + } +} + +// --------------------------------------------------- + +function showFlash(){ + var flashObjects = document.getElementsByTagName("object"); + for (i = 0; i < flashObjects.length; i++) { + flashObjects[i].style.visibility = "visible"; + } + + var flashEmbeds = document.getElementsByTagName("embed"); + for (i = 0; i < flashEmbeds.length; i++) { + flashEmbeds[i].style.visibility = "visible"; + } +} + +// --------------------------------------------------- + +function hideFlash(){ + var flashObjects = document.getElementsByTagName("object"); + for (i = 0; i < flashObjects.length; i++) { + flashObjects[i].style.visibility = "hidden"; + } + + var flashEmbeds = document.getElementsByTagName("embed"); + for (i = 0; i < flashEmbeds.length; i++) { + flashEmbeds[i].style.visibility = "hidden"; + } + +} + + +// --------------------------------------------------- + +// +// pause(numberMillis) +// Pauses code execution for specified time. Uses busy code, not good. +// Help from Ran Bar-On [ran2103@gmail.com] +// + +function pause(ms){ + var date = new Date(); + curDate = null; + do{var curDate = new Date();} + while( curDate - date < ms); +} +/* +function pause(numberMillis) { + var curently = new Date().getTime() + sender; + while (new Date().getTime(); +} +*/ +// --------------------------------------------------- + + + +function initLightbox() { myLightbox = new Lightbox(); } +//Event.observe(window, 'load', initLightbox, false); \ No newline at end of file diff --git a/browser/examples/photoviewer/lightbox/js/prototype.js b/browser/examples/photoviewer/lightbox/js/prototype.js new file mode 100644 index 0000000..e9ccd3c --- /dev/null +++ b/browser/examples/photoviewer/lightbox/js/prototype.js @@ -0,0 +1,1785 @@ +/* Prototype JavaScript framework, version 1.4.0 + * (c) 2005 Sam Stephenson + * + * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff + * against the source tree, available from the Prototype darcs repository. + * + * Prototype is freely distributable under the terms of an MIT-style license. + * + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.4.0', + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + + emptyFunction: function() {}, + K: function(x) {return x} +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.inspect = function(object) { + try { + if (object == undefined) return 'undefined'; + if (object == null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } +} + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this; + return function(event) { + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0; i < arguments.length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(); + } finally { + this.currentlyExecuting = false; + } + } + } +} + +/*--------------------------------------------------------------------------*/ + +function $() { + var elements = new Array(); + + for (var i = 0; i < arguments.length; i++) { + var element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + + if (arguments.length == 1) + return element; + + elements.push(element); + } + + return elements; +} +Object.extend(String.prototype, { + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(eval); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? div.childNodes[0].nodeValue : ''; + }, + + toQueryParams: function() { + var pairs = this.match(/^\??(.*)$/)[1].split('&'); + return pairs.inject({}, function(params, pairString) { + var pair = pairString.split('='); + params[pair[0]] = pair[1]; + return params; + }); + }, + + toArray: function() { + return this.split(''); + }, + + camelize: function() { + var oStringList = this.split('-'); + if (oStringList.length == 1) return oStringList[0]; + + var camelizedString = this.indexOf('-') == 0 + ? oStringList[0].charAt(0).toUpperCase() + oStringList[0].substring(1) + : oStringList[0]; + + for (var i = 1, len = oStringList.length; i < len; i++) { + var s = oStringList[i]; + camelizedString += s.charAt(0).toUpperCase() + s.substring(1); + } + + return camelizedString; + }, + + inspect: function() { + return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + } +}); + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != undefined || value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0; i < this.length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + shift: function() { + var result = this[0]; + for (var i = 0; i < this.length - 1; i++) + this[i] = this[i + 1]; + this.length--; + return result; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); +var Hash = { + _each: function(iterator) { + for (key in this) { + var value = this[key]; + if (typeof value == 'function') continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject($H(this), function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + return pair.map(encodeURIComponent).join('='); + }).join('&'); + }, + + inspect: function() { + return '#'; + } +} + +function $H(object) { + var hash = Object.extend({}, object || {}); + Object.extend(hash, Enumerable); + Object.extend(hash, Hash); + return hash; +} +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')}, + function() {return new XMLHttpRequest()} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responderToAdd) { + if (!this.include(responderToAdd)) + this.responders.push(responderToAdd); + }, + + unregister: function(responderToRemove) { + this.responders = this.responders.without(responderToRemove); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (responder[callback] && typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + parameters: '' + } + Object.extend(this.options, options || {}); + }, + + responseIsSuccess: function() { + return this.transport.status == undefined + || this.transport.status == 0 + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + responseIsFailure: function() { + return !this.responseIsSuccess(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + var parameters = this.options.parameters || ''; + if (parameters.length > 0) parameters += '&_='; + + try { + this.url = url; + if (this.options.method == 'get' && parameters.length > 0) + this.url += (this.url.match(/\?/) ? '&' : '?') + parameters; + + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.options.method, this.url, + this.options.asynchronous); + + if (this.options.asynchronous) { + this.transport.onreadystatechange = this.onStateChange.bind(this); + setTimeout((function() {this.respondToReadyState(1)}).bind(this), 10); + } + + this.setRequestHeaders(); + + var body = this.options.postBody ? this.options.postBody : parameters; + this.transport.send(this.options.method == 'post' ? body : null); + + } catch (e) { + this.dispatchException(e); + } + }, + + setRequestHeaders: function() { + var requestHeaders = + ['X-Requested-With', 'XMLHttpRequest', + 'X-Prototype-Version', Prototype.Version]; + + if (this.options.method == 'post') { + requestHeaders.push('Content-type', + 'application/x-www-form-urlencoded'); + + /* Force "Connection: close" for Mozilla browsers to work around + * a bug where XMLHttpReqeuest sends an incorrect Content-length + * header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType) + requestHeaders.push('Connection', 'close'); + } + + if (this.options.requestHeaders) + requestHeaders.push.apply(requestHeaders, this.options.requestHeaders); + + for (var i = 0; i < requestHeaders.length; i += 2) + this.transport.setRequestHeader(requestHeaders[i], requestHeaders[i+1]); + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState != 1) + this.respondToReadyState(this.transport.readyState); + }, + + header: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) {} + }, + + evalJSON: function() { + try { + return eval(this.header('X-JSON')); + } catch (e) {} + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + respondToReadyState: function(readyState) { + var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (event == 'Complete') { + try { + (this.options['on' + this.transport.status] + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.header('Content-type') || '').match(/^text\/javascript/i)) + this.evalResponse(); + } + + try { + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + event, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ + if (event == 'Complete') + this.transport.onreadystatechange = Prototype.emptyFunction; + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.containers = { + success: container.success ? $(container.success) : $(container), + failure: container.failure ? $(container.failure) : + (container.success ? null : $(container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, object) { + this.updateContent(); + onComplete(transport, object); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.responseIsSuccess() ? + this.containers.success : this.containers.failure; + var response = this.transport.responseText; + + if (!this.options.evalScripts) + response = response.stripScripts(); + + if (receiver) { + if (this.options.insertion) { + new this.options.insertion(receiver, response); + } else { + Element.update(receiver, response); + } + } + + if (this.responseIsSuccess()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + return $A(children).inject([], function(elements, child) { + if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + elements.push(child); + return elements; + }); +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) { + var Element = new Object(); +} + +Object.extend(Element, { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + Element[Element.visible(element) ? 'hide' : 'show'](element); + } + }, + + hide: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = 'none'; + } + }, + + show: function() { + for (var i = 0; i < arguments.length; i++) { + var element = $(arguments[i]); + element.style.display = ''; + } + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + }, + + update: function(element, html) { + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + }, + + getHeight: function(element) { + element = $(element); + return element.offsetHeight; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).include(className); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).add(className); + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + return Element.classNames(element).remove(className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + for (var i = 0; i < element.childNodes.length; i++) { + var node = element.childNodes[i]; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + Element.remove(node); + } + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + scrollTo: function(element) { + element = $(element); + var x = element.x ? element.x : element.offsetLeft, + y = element.y ? element.y : element.offsetTop; + window.scrollTo(x, y); + }, + + getStyle: function(element, style) { + element = $(element); + var value = element.style[style.camelize()]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css.getPropertyValue(style) : null; + } else if (element.currentStyle) { + value = element.currentStyle[style.camelize()]; + } + } + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (name in style) + element.style[name.camelize()] = style[name]; + }, + + getDimensions: function(element) { + element = $(element); + if (Element.getStyle(element, 'display') != 'none') + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = ''; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = 'none'; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (window.opera) { + element.style.top = 0; + element.style.left = 0; + } + } + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return; + element._overflow = element.style.overflow; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + }, + + undoClipping: function(element) { + element = $(element); + if (element._overflow) return; + element.style.overflow = element._overflow; + element._overflow = undefined; + } +}); + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
'; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set(this.toArray().concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set(this.select(function(className) { + return className != classNameToRemove; + }).join(' ')); + }, + + toString: function() { + return this.toArray().join(' '); + } +} + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Field = { + clear: function() { + for (var i = 0; i < arguments.length; i++) + $(arguments[i]).value = ''; + }, + + focus: function(element) { + $(element).focus(); + }, + + present: function() { + for (var i = 0; i < arguments.length; i++) + if ($(arguments[i]).value == '') return false; + return true; + }, + + select: function(element) { + $(element).select(); + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select) + element.select(); + } +} + +/*--------------------------------------------------------------------------*/ + +var Form = { + serialize: function(form) { + var elements = Form.getElements($(form)); + var queryComponents = new Array(); + + for (var i = 0; i < elements.length; i++) { + var queryComponent = Form.Element.serialize(elements[i]); + if (queryComponent) + queryComponents.push(queryComponent); + } + + return queryComponents.join('&'); + }, + + getElements: function(form) { + form = $(form); + var elements = new Array(); + + for (tagName in Form.Element.Serializers) { + var tagElements = form.getElementsByTagName(tagName); + for (var j = 0; j < tagElements.length; j++) + elements.push(tagElements[j]); + } + return elements; + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) + return inputs; + + var matchingInputs = new Array(); + for (var i = 0; i < inputs.length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || + (name && input.name != name)) + continue; + matchingInputs.push(input); + } + + return matchingInputs; + }, + + disable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.blur(); + element.disabled = 'true'; + } + }, + + enable: function(form) { + var elements = Form.getElements(form); + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element.disabled = ''; + } + }, + + findFirstElement: function(form) { + return Form.getElements(form).find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + Field.activate(Form.findFirstElement(form)); + }, + + reset: function(form) { + $(form).reset(); + } +} + +Form.Element = { + serialize: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) { + var key = encodeURIComponent(parameter[0]); + if (key.length == 0) return; + + if (parameter[1].constructor != Array) + parameter[1] = [parameter[1]]; + + return parameter[1].map(function(value) { + return key + '=' + encodeURIComponent(value); + }).join('&'); + } + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + var parameter = Form.Element.Serializers[method](element); + + if (parameter) + return parameter[1]; + } +} + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'submit': + case 'hidden': + case 'password': + case 'text': + return Form.Element.Serializers.textarea(element); + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + } + return false; + }, + + inputSelector: function(element) { + if (element.checked) + return [element.name, element.value]; + }, + + textarea: function(element) { + return [element.name, element.value]; + }, + + select: function(element) { + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); + } + } + return [element.name, value]; + } +} + +/*--------------------------------------------------------------------------*/ + +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + var elements = Form.getElements(this.element); + for (var i = 0; i < elements.length; i++) + this.registerCallback(elements[i]); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + case 'password': + case 'text': + case 'textarea': + case 'select-one': + case 'select-multiple': + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0; i < Event.observers.length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + this._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + var element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + element.detachEvent('on' + name, observer); + } + } +}); + +/* prevent memory leaks in IE */ +Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (element.offsetParent) return element.offsetParent; + if (element == document.body) return element; + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return element; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + clone: function(source, target) { + source = $(source); + target = $(target); + target.style.position = 'absolute'; + var offsets = this.cumulativeOffset(source); + target.style.top = offsets[1] + 'px'; + target.style.left = offsets[0] + 'px'; + target.style.width = source.offsetWidth + 'px'; + target.style.height = source.offsetHeight + 'px'; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(parent); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if(options.setLeft) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px';; + element.style.left = left + 'px';; + element.style.width = width + 'px';; + element.style.height = height + 'px';; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} \ No newline at end of file diff --git a/browser/examples/photoviewer/lightbox/js/scriptaculous.js b/browser/examples/photoviewer/lightbox/js/scriptaculous.js new file mode 100644 index 0000000..dac1228 --- /dev/null +++ b/browser/examples/photoviewer/lightbox/js/scriptaculous.js @@ -0,0 +1,45 @@ +// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// +// Permission is hereby granted, free of charge, to any person obtaining +// a copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to +// permit persons to whom the Software is furnished to do so, subject to +// the following conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +var Scriptaculous = { + Version: '1.5.1', + require: function(libraryName) { + // inserting via DOM fails in Safari 2.0, so brute force approach + document.write(''); + }, + load: function() { + if((typeof Prototype=='undefined') || + parseFloat(Prototype.Version.split(".")[0] + "." + + Prototype.Version.split(".")[1]) < 1.4) + throw("script.aculo.us requires the Prototype JavaScript framework >= 1.4.0"); + + $A(document.getElementsByTagName("script")).findAll( function(s) { + return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/)) + }).each( function(s) { + var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,''); + var includes = s.src.match(/\?.*load=([a-z,]*)/); + (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider').split(',').each( + function(include) { Scriptaculous.require(path+include+'.js') }); + }); + } +} + +Scriptaculous.load(); \ No newline at end of file diff --git a/browser/examples/photoviewer/pages.erb b/browser/examples/photoviewer/pages.erb new file mode 100644 index 0000000..24dfccf --- /dev/null +++ b/browser/examples/photoviewer/pages.erb @@ -0,0 +1,5 @@ +<% if @flickr.photos.total.to_i > 0 %> + <% flickr_num_pages.times do |i| %> + <%= i + 1 %> + <% end if flickr_num_pages > 1 %> +<% end %> \ No newline at end of file diff --git a/browser/examples/photoviewer/view.rb b/browser/examples/photoviewer/view.rb new file mode 100644 index 0000000..2d274a2 --- /dev/null +++ b/browser/examples/photoviewer/view.rb @@ -0,0 +1,71 @@ +require 'lib/erb' + +module Photoviewer + class View + def initialize(app) + @app = app + end + + def show_images(flickr, tags, current_page) + @flickr, @tags, @current_page = flickr, tags, current_page + + document.search_images.innerHTML = erb :images + document.search_links.innerHTML = erb :pages + + handle_pagination 'search_links' + + init_lightbox + end + + def loading_start + document.images_loading.style[:display] = "inline" + end + + def loading_finish + document.images_loading.style[:display] = "none" + document.search_results.style[:display] = "block" + end + + private + + def flickr_source(p) + "http://farm#{ p.farm.to_i }.static.flickr.com/#{ p.server }/#{ p.photo_id }_#{ p.secret }" + end + + def flickr_page(p) + "http://www.flickr.com/photos/#{ p.owner }/#{ p.photo_id }" + end + + def flickr_num_pages + @flickr.photos.pages > 15 ? 15 : @flickr.photos.pages.to_i + end + + def encode(str) + System::Windows::Browser::HttpUtility.html_encode str + end + + def handle_pagination(div) + $elem = @app.document.get_element_by_id(div) + @app.document.get_element_by_id(div).children.each do |child| + if child.id.to_i == @current_page + child.css_class = "active" + elsif child.parent + child.onclick{|s, args| @app.create_request @tags, child.id.to_i } + end + end + end + + def init_lightbox + if document.overlay && document.lightbox + document.overlay.parent.remove_child document.overlay + document.lightbox.parent.remove_child document.lightbox + end + + window.eval "initLightbox()" + end + + def erb(template, bind = nil) + ERB.new(File.read("#{template}.erb")).result(bind || binding) + end + end +end diff --git a/browser/examples/repl.rb b/browser/examples/repl.rb new file mode 100644 index 0000000..894ddbe --- /dev/null +++ b/browser/examples/repl.rb @@ -0,0 +1,32 @@ +class Microsoft::Scripting::Silverlight::Repl + IronRuby = 'silverlightDlrRepl1' + Minimize = 'silverlightDlrWindowLink' + Container = 'silverlightDlrWindowContainer' + + def hide_all_panels + window.eval "sdlrw.hideAllPanels(document.getElementById(\"#{Minimize}\"))" + end + + def show_panel(id) + window.eval "sdlrw.showPanel(\"#{id}\")" + end + + def show_ironruby + show_panel(IronRuby) + end + + def remove + document.body.remove_child document.silverlightDlrWindowContainer + end +end + +if document.query_string.contains_key 'console' + $repl = Microsoft::Scripting::Silverlight::Repl.show('ruby') + $stdout = $repl.output_buffer + $stderr = $repl.output_buffer + if document.query_string['console'] == 'off' + $repl.hide_all_panels + else + $repl.show_ironruby + end +end diff --git a/browser/examples/webcam.zip b/browser/examples/webcam.zip new file mode 100644 index 0000000..2aef517 Binary files /dev/null and b/browser/examples/webcam.zip differ diff --git a/browser/examples/webcam/app.xaml b/browser/examples/webcam/app.xaml new file mode 100644 index 0000000..0a9bacd --- /dev/null +++ b/browser/examples/webcam/app.xaml @@ -0,0 +1,77 @@ + + + + + diff --git a/browser/examples/webcam/index.html b/browser/examples/webcam/index.html new file mode 100644 index 0000000..1c4cdab --- /dev/null +++ b/browser/examples/webcam/index.html @@ -0,0 +1,36 @@ + + + + + + + webcam-mic + + + + + + + + + + + diff --git a/browser/examples/webcam/webcam-mic.xaml b/browser/examples/webcam/webcam-mic.xaml new file mode 100644 index 0000000..7098b94 --- /dev/null +++ b/browser/examples/webcam/webcam-mic.xaml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +