diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..79ce17422 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +* text=auto + +*.pkgdef text +*.vstemplate text diff --git a/.gitignore b/.gitignore index 986f7ddfb..5489f4321 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,8 @@ bin/ obj/ *.suo *.user +*.cache +*.trx +Scripts/ +packages/ +TestResults/ diff --git a/History.txt b/History.txt new file mode 100644 index 000000000..d93d10faa --- /dev/null +++ b/History.txt @@ -0,0 +1,209 @@ +Changelog +=============================================================================== + +v0.7.6 + - Removed script loading APIs from Script class in mscorlib + (moved them to Loader class in Script.Web) + - Removed script startup functionality from mscorlib.js ... all script + loading is now in ssloader.js (and included into the Runtime nuget + package) + - Removed Script.Alert/Confirm/Prompt from mscorlib and moved them to + Window in Script.Web + - Moved SetTimeout, SetInterval and ClearTimeout, ClearInterval from + Window object to Script object in mscorlib. + - Removed some methods from Type as part of cleanup (AddHandler, + GetProperty, RemoveHandler, SetProperty, IsEnum, IsFlags, IsNamespace, + GetInterfaces, Parse) + - Removed window dependency in mscorlib.js + - Debug class now maps directly to console + - Moved GetField, HasField, DeleteField, HasMethod, InvokeMethod, + CreateInstance, GetScriptType off Type and onto Script while + HasProperty, GetProperty, SetProperty, AddHandler, RemoveHandler are gone + - Optimize generation of static ctors to use a function scope only when + static ctors have a local variable reference. + Should help optimize generation of jQuery scripts, by reducing one extra + function scope. + - Removed Aggregate method from Array, ArrayList and List. Use Reduce instead. + Removed the corresponding custom implementation from mscorlib.js. + - [git #131] Correctly detect member references within array initializers + - [git #161] Add Slice, Splice, Shift, Unshift, Reduce, ReduceRight to + Array, ArrayList and List. + - [git #228] Restore ability to generate public resource classes + (Change Custom Tool property of resx file to + ResxPublicScriptGenerator) + - [git #230] Add String.IsNullOrWhitespace + - [git #234] Knockout API update - added CompareResult status enum + +v0.7.5.0 + - DOM metadata update (readyState property on Document, + plugins on Navigator) + - jQuery API update (on/off methods) + - Knockout API updates for missing APIs + - Addition of support for jQuery validation plugin + - jQueryUI added! + - [git #130] ReadyState property on Document + - [git #132] DOM metadata for plugins + - [git #139] jQuery on/off APIs + - [git #141] Bing maps metadata missing APIs + - [git #145] Knockout additions + - [git #152] Knockout additions + - [git #155] ReadyState property on Document + - [git #156] Changed projects (incl. templates) to target .net framework v2.0 + - [git #166] Fix generic Deffered API on jQuery + - [git #169] Missing jQuery API overload added + - [git #170] Fix bing maps metadata bug + - [git #173] Fix StringBuilder and appending empty string/isEmpty interaction + - [git #178] Issues with string extensions + - [git #180] Added workaround: Better error message for namespace-qualified name gotcha + - [git #183] Various DOM metadata additions (eg. ClassList) + - [git #187] Support for writing 'new Image()' + - [git #189] Fixed jQuery.when metadata + - [git #201] Strongly typed some Knockout API (KnockoutMapping) + - Nuget packages for compiler, and individual import libraries + - VSIX-based approach for getting script# and associated templates + - Breaking changes: + - In a csproj, the TemplateFile property has been changed to ScriptTemplate + - When creating a project there is no prompt to setup a DeploymentPath + ... but for now, msbuild support continues to exist (via manual edit) + ... will potentially be replaced eventually. + - No more support for WebAppParitioning. If you were compiling multiple script + files out of a single c# project, this feature is gone. You can switch your + msbuild scripts to use the ScriptCompilerExecTask, instead of ScriptCompilerTask + and invoke the compiler multiple times for each script file, passing in exactly + the right set of sources for each script file to be generated. + - Removed Script.Windows.dll and gadget project + +v0.7.4.0 + - Fix/update bing maps metadata bug + - Fix observable bug where access to an observed property without an observer + registration would drop existing observers. + - Remove /copyRefs flag from ssc.exe (script references are no longer copied to output folder) + Same applies to ScriptCompilerExec task. The normal ScriptCompilerTask continues to provide + this capability. + - Added Sharpen.dom.js - a minimal set of helper APIs on top of HTML5 DOM APIs. + - Some enum related changes: + ToString/Enum parsing support removed (it didn't work for internal enums anyway) + ToString on named enums (and on strings) skipped as it is redundant + Inline enum values in generated script + Skip generating internal enum types + - [git #25] Importing multi-dimensional arrays + - [git #56] Switch statement when used with more then 6 cases will generate a compile error + - [git #62] Compile error due to assembly name sort order (with generic types being + imported, assembly load order starts to matter) + - [git #66] Support Nullable.GetValueOrDefault which is needed for ?? operator on + nullables. + - [git #67] BREAKING CHANGE: DateTime -> Date. Date is also no longer a value type. + (partially fixes #67, and also makes Date more representative of script semantics + which are not that of a value type) + - [git #84] Raise error for code which calls Type.CreateInstance with the results of a method + call/property access directly. + - [git #92] Don't generate internal consts since their values have been inlined + even in debug builds (for symmetry with release builds) + Also, at the same type skip generating types for internal enums marked + as Named/NumericValues, since their values have been inlined as well. + Also fixes [git #42] indirectly. + - [git #93] Error compiling cross-referenced projects in 0.7.3 + Fix by requiring explicit references, and reporting failure on not doing so + instead of silent failure (this is temporary ... there must be a better + msbuild way of including transitive closure of assemblies, but even + referencing indirect references in the targets file raises an error to + include explicit references). + - [git #94] Raise error for code which calls Type.CreateInstance with the results of a method + call/property access directly. + - [git #96] Handle '+' unary operator + - [git #97] Unable to generate code for projects that don't reference Script.Web.dll. + - [git #103] Fix generation of custom delegates to use "Function" as generated script name + - [git #107] ScriptAlias is not respected on types defined in the current assembly + - [git #114] Window.ApplicationCache.Status.ToString() generates invalid JavaScript + +v0.7.3.0 [8/14/2011] + - [git #71] Fix formatting of GMT dates + - [git #70] Support for const decimal + - [git #69] Fix LocaleFormat typo + - [git #76] Compiler crash in 0.7.2 defining decimal type with non-integer value + - [git #75] Defining a local variable 'ss' hides the Script# ss global + - [git #60] List constructor with params for creating literal array + - [git #74] Fix date formatting to make it more in sync with .net + - [git #63] Fix internal partial class generation + - [git #65] Calling ToString() on a constant breaks minified script + - [git #73] Creating Record objects from another library + - [git #80] The decimal type is missing the Parse method + - [git #84] if( is ) not compiled properly when is a delegate. + - Enable creating derived jQueryEvent objects + - Knockout API updates (ability to create binding handlers for example) + - Propagate ScriptNamespace to generic instance class + - DOM API metadata updates + - Separate out msbuild tasks into ScriptSharp.Build.dll and Visual Studio generators + into ScriptSharp.VisualStudio.dll + +v0.7.2.0 [5/22/2011] + - Revert null equality check to non-falsey check + - Switch String.empty references to in generated script to string literal + - Fix delegate optimization bug involving Type.InvokeMember with first parameter being null + - Remove hack in metadata importer to try import generic types first + (instead design imported apis carefully so type don't have instance of generic type + as return type of a member) + - To fix above bug: + - jQueryXmlHttpRequest replaced with jQueryDataHttpRequest + and jQueryXmlHttpRequest is now non-generic + - Added non-generic versions of various jQuery ajax callbacks + - Improve jQuery deferred APIs + - jQuery 1.6.1 APIs and other missing APIs added + - [git #36] Misc jQuery apis added + - [git #50] Fix GeoLocation API: WatchCurrentPosition -> WatchPosition + - Added AudioElement + - Added Tuple, Callback, various component model interfaces + - FxCop rules for CSS Selector validation, Script.Literal usage and public fields + - [git #45] Apply [PreserveName] semantics in debug builds as well + - [git #52] DocumentFragment related API updates + - [git #54] DateTime metadata update + +v0.7.1.0 [5/2/2011] + - [git #19] Add CssFloat to Style + - [git #22] Fix issue with importing generic delegates - generic types were getting + created before their members were being imported. + - [git #20] If/While/For statements can have an no body statement when compiling in release + mode, and the only statement in source code is a debug-conditional statement. + - [git #27] Optimize generated script for checking equality against null, 0, true/false, empty + string by generating a check that works against false-y values + - [git #28] Fix jQueryAjaxOptions metadata + - [git #29] Add Select() to InputElement + - Added Observable and mscorlib infrastructure for observables, and change notification + - Added ObservableCollection and ObservableCollection along with mscorlib implementation + - Added Script.IsValue (equivalent of !Script.IsNullOrUndefined) + - Added Nullable and T? syntax support; .HasValue -> Script.IsValue, .Value is a no-op at runtime + - Added ElementQuery, ElementListQuery, and ElementReference helper classes for emitting script + expressions when using AddScriptlet + +v0.7.0.2 + - [git #7] Add missing jQueryObject.Html overload + - Add Document.ElementFromPoint + - DOM events API added + - [git #3] Fixed anonymous delegation creation by detecting use of 'this' context for + variable declaration + - [git #9] String literal optimization + - [git #11] Added SCRIPTSHARP to the defines list in the project template + - Remove Script.Windows.js and move the functionality into gadget project template. + - Add ss.init/ss.ready to mscorlib.js as well, in case page is not using ssloader.js. + +v0.7.0.1 + - [git #4] Fixed typo in mscorlib.js + - Fix typo in web.config in nuget package + - Fix bug with jQuery.InArray signature + +v0.7.0.0 [4/13/2011] + - Added basic generic support - ability to consume some generic types such as List, Dictionary, + Action, Func etc. + - Fix bug with NamedValues enum not working with [ScriptName] + - New script loader + - New asp.net mvc integration + - Updates to project templates to match new script loader + - Various metadata updates - eg. Canvas, Image, Document etc. + - Replace ContextualCallback with Action + - Replace CancelEventHandler with EventHandler + - Replace Delegate.Null -> Delegate.Empty + - ScriptCompilerTask msbuild task removes "DEBUG" define for release build even when building + in Debug mode from an msbuild perspective. + - Some namespace changes (eg. System.Globalization) + - Nuget package + - Replace XmlDocumentParser class with parseXml method diff --git a/License.txt b/License.txt new file mode 100644 index 000000000..5f0dac0af --- /dev/null +++ b/License.txt @@ -0,0 +1,180 @@ + Script# Project + Copyright 2012, Script# Project + Copyright 2012, Nikhil Kothari + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + http://www.apache.org/licenses/LICENSE-2.0 + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work. + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "License.txt" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such License.txt file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a License.txt text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the License.txt file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the License.txt text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS diff --git a/Notice.txt b/Notice.txt new file mode 100644 index 000000000..76e1ddbbc --- /dev/null +++ b/Notice.txt @@ -0,0 +1,13 @@ +Script# Project +Copyright 2012, Script# Project +Copyright 2012, Nikhil Kothari + +Script# is licensed under the Apache License v2.0 (see License.txt). +Script# also includes works distributed under the licenses listed below. + +Mono Cecil +-------------- +Website: https://github.com/jbevain/cecil +Copyright: Copyright (c) 2008 - 2011 Jb Evain +License: The MIT License + http://opensource.org/licenses/mit-license.php diff --git a/README b/README deleted file mode 100644 index 11f7fcc75..000000000 --- a/README +++ /dev/null @@ -1,4 +0,0 @@ -Script# Project - -Coming Soon! -More information at http://projects.nikhilk.net/ScriptSharp diff --git a/Readme.md b/Readme.md new file mode 100644 index 000000000..3140c065d --- /dev/null +++ b/Readme.md @@ -0,0 +1,37 @@ +# Script# Project # + +Script# is a development tool that generates JavaScript by compiling C# source code. It is especially interesting for scripting-in-the-large scenarios that is commonplace in the current generation of HTML5 and script-based Web applications. + +The driving principle behind Script# is that JavaScript is the "assembly language of the Web" ... the idea isn't to dislike JavaScript, but rather appreciate it for its flexibility and ubiquity, while bringing the productivity and familiarity of C# when building large scale (large codebase, large team or project over a longer period) application where stronger tools, more maintainable codebases are of utmost importance. You might have heard of this described as "application scale". + +Specifically, Script# lets you leverage the experience of C# (intellisense, build-time error checking, natural language syntax for OOP constructs), the power Visual Studio IDE and standard .NET tools ecosystem (such as msbuild, refactoring, unit testing, static analysis, code visualization, fxcop). Script# brings all this to you without abstracting the runtime environment - you're still authoring plain-old JavaScript and incorporating the best practices and idioms of JavaScript, just with a different set of tools. + +You can even use Script# to work against other existing frameworks and APIs such as jQuery, jQuery plugins and Knockout, and can be extended to work against other existing script. + +Script# requires .NET 4.0 and Visual Studio 2012. You can also use one of the free Express tools instead such as [Visual Studio 2012 Express for Web](http://www.microsoft.com/visualstudio/eng/downloads#d-express-web) or [Visual Studio 2012 Express for Windows Desktop](http://www.microsoft.com/visualstudio/eng/downloads#d-express-windows-desktop). + +## More Information ## +The following set of links are relevant if you're using Script# and want to learn more or ask questions, or stay up-to-date. + +* [Script# Project page](http://scriptsharp.com) +* [Latest released build](http://bit.ly/ssrelease) (packaged as a Visual Studio extension containing Script# project and item templates) +* [Issues, suggestions](https://github.com/nikhilk/scriptsharp/issues) here on GitHub. Also, check this out to participate in future design questions, calls for feedback, etc. +* For general discussion and Q&A, use [StackOverflow](http://stackoverflow.com/questions/tagged/scriptsharp) +* Follow [@scriptsharp](http://twitter.com/scriptsharp) and [@nikhilk](http://twitter.com/nikhilk) on twitter to keep up with updates and announcements. + +The [Script# Wiki](https://github.com/nikhilk/scriptsharp/wiki/Wiki) contains the following bits of information if you're checking out the repository, are interested in creating a private build, or even better, looking to contribute to the project. + +* [Repository Contents](https://github.com/nikhilk/scriptsharp/wiki/Repository) +* [Status](https://github.com/nikhilk/scriptsharp/wiki/Status), including roadmap and some thoughts about areas of contribution +* [Building and testing](https://github.com/nikhilk/scriptsharp/wiki/Building-and-Testing) the sources, and information on using private as well as incremental pre-release builds +* [Coding guidelines](https://github.com/nikhilk/scriptsharp/wiki/Coding-Guidelines) + +### Credits ### + +Script# builds on the excellent Mono.Cecil project. Thanks to Jb Evain. + +Thanks to [all the contributors](https://github.com/nikhilk/scriptsharp/graphs/contributors) who have contributed to Script# over time. Your contributions are welcome, and appreciated. Let me know how you can help - whether its in the form of a feature, a sample, or some evangelizing/spreading the word, or via a suggestion. See the [status page](https://github.com/nikhilk/scriptsharp/wiki/Status) in the wiki for roadmap, plans, and some thoughts about possible contributions. + +### License ### +Copyright (c) 2012, Script# Project. +Script# is licensed under the Apache 2.0 License. diff --git a/bin/ref/ScriptSharp.dll b/bin/ref/ScriptSharp.dll deleted file mode 100644 index f915e76ae..000000000 Binary files a/bin/ref/ScriptSharp.dll and /dev/null differ diff --git a/bin/ref/mscorlib.dll b/bin/ref/mscorlib.dll deleted file mode 100644 index d3d8a2b66..000000000 Binary files a/bin/ref/mscorlib.dll and /dev/null differ diff --git a/bin/ref/ssc.exe b/bin/ref/ssc.exe deleted file mode 100644 index 6b957f367..000000000 Binary files a/bin/ref/ssc.exe and /dev/null differ diff --git a/bin/ref/sspp.exe b/bin/ref/sspp.exe deleted file mode 100644 index 388e36e70..000000000 Binary files a/bin/ref/sspp.exe and /dev/null differ diff --git a/samples/AroundMe/AroundMe.sln b/samples/AroundMe/AroundMe.sln new file mode 100644 index 000000000..c69f9e195 --- /dev/null +++ b/samples/AroundMe/AroundMe.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AroundMe", "AroundMe\AroundMe.csproj", "{C1AFF814-2C3A-4D04-88A4-5CF10D07AA56}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Twitter", "Twitter\Twitter.csproj", "{476312F9-5403-4C3A-848A-0F31213FC72E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MapDemo", "MapDemo\MapDemo.csproj", "{2176DBB5-D8FD-493C-B896-59E7283E7127}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AroundMeTests", "AroundMeTests\AroundMeTests.csproj", "{E01AE039-4B10-4A3B-AB86-EBFC1E2F6B17}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{F9ED6BAF-639F-4F4B-8E04-C6AE7EAE860A}" + ProjectSection(SolutionItems) = preProject + AroundMeTests.testsettings = AroundMeTests.testsettings + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2176DBB5-D8FD-493C-B896-59E7283E7127}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2176DBB5-D8FD-493C-B896-59E7283E7127}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2176DBB5-D8FD-493C-B896-59E7283E7127}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2176DBB5-D8FD-493C-B896-59E7283E7127}.Release|Any CPU.Build.0 = Release|Any CPU + {476312F9-5403-4C3A-848A-0F31213FC72E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {476312F9-5403-4C3A-848A-0F31213FC72E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {476312F9-5403-4C3A-848A-0F31213FC72E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {476312F9-5403-4C3A-848A-0F31213FC72E}.Release|Any CPU.Build.0 = Release|Any CPU + {C1AFF814-2C3A-4D04-88A4-5CF10D07AA56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1AFF814-2C3A-4D04-88A4-5CF10D07AA56}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1AFF814-2C3A-4D04-88A4-5CF10D07AA56}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1AFF814-2C3A-4D04-88A4-5CF10D07AA56}.Release|Any CPU.Build.0 = Release|Any CPU + {E01AE039-4B10-4A3B-AB86-EBFC1E2F6B17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E01AE039-4B10-4A3B-AB86-EBFC1E2F6B17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E01AE039-4B10-4A3B-AB86-EBFC1E2F6B17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E01AE039-4B10-4A3B-AB86-EBFC1E2F6B17}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/AroundMe/AroundMe/AroundMe.csproj b/samples/AroundMe/AroundMe/AroundMe.csproj new file mode 100644 index 000000000..ef7bf43cb --- /dev/null +++ b/samples/AroundMe/AroundMe/AroundMe.csproj @@ -0,0 +1,118 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {C1AFF814-2C3A-4D04-88A4-5CF10D07AA56} + Library + Properties + AroundMe + AroundMe + Properties\Script.template + Properties\FxCop.ruleset + True + True + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\AroundMe.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\AroundMe.xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.BingMaps.0.7.5.0\lib\Script.Microsoft.BingMaps.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + {476312f9-5403-4c3a-848a-0f31213fc72e} + Twitter + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/AroundMe/Content/AroundMe.htm b/samples/AroundMe/AroundMe/Content/AroundMe.htm new file mode 100644 index 000000000..d5846da06 --- /dev/null +++ b/samples/AroundMe/AroundMe/Content/AroundMe.htm @@ -0,0 +1,41 @@ + + + + Photos Around Me + + + + +
+
+
Photos Around Me
+
+ + + + +
+
+
+
+
+ + +
+ + + + + +
+
+
+ + + + + + diff --git a/samples/AroundMe/AroundMe/Content/CloseButton.png b/samples/AroundMe/AroundMe/Content/CloseButton.png new file mode 100644 index 000000000..2589fabdd Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/CloseButton.png differ diff --git a/samples/AroundMe/AroundMe/Content/Dot.png b/samples/AroundMe/AroundMe/Content/Dot.png new file mode 100644 index 000000000..ae533882d Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Dot.png differ diff --git a/samples/AroundMe/AroundMe/Content/Flickr.png b/samples/AroundMe/AroundMe/Content/Flickr.png new file mode 100644 index 000000000..1d0de586b Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Flickr.png differ diff --git a/samples/AroundMe/AroundMe/Content/Heart.png b/samples/AroundMe/AroundMe/Content/Heart.png new file mode 100644 index 000000000..c8172ae17 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Heart.png differ diff --git a/samples/AroundMe/AroundMe/Content/Location.png b/samples/AroundMe/AroundMe/Content/Location.png new file mode 100644 index 000000000..0c473d906 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Location.png differ diff --git a/samples/AroundMe/AroundMe/Content/PlaceHolder.png b/samples/AroundMe/AroundMe/Content/PlaceHolder.png new file mode 100644 index 000000000..c985b8d84 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/PlaceHolder.png differ diff --git a/samples/AroundMe/AroundMe/Content/ProgressBar.gif b/samples/AroundMe/AroundMe/Content/ProgressBar.gif new file mode 100644 index 000000000..a3a539286 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/ProgressBar.gif differ diff --git a/samples/AroundMe/AroundMe/Content/Pushpin.png b/samples/AroundMe/AroundMe/Content/Pushpin.png new file mode 100644 index 000000000..e47fbe671 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Pushpin.png differ diff --git a/samples/AroundMe/AroundMe/Content/Reset.png b/samples/AroundMe/AroundMe/Content/Reset.png new file mode 100644 index 000000000..aa774d270 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Reset.png differ diff --git a/samples/AroundMe/AroundMe/Content/Search.png b/samples/AroundMe/AroundMe/Content/Search.png new file mode 100644 index 000000000..3ba13a452 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Search.png differ diff --git a/samples/AroundMe/AroundMe/Content/Site.css b/samples/AroundMe/AroundMe/Content/Site.css new file mode 100644 index 000000000..f19ffd3b9 --- /dev/null +++ b/samples/AroundMe/AroundMe/Content/Site.css @@ -0,0 +1,184 @@ +html, body { + padding: 0px; margin: 0px; + width: 100%; height: 100%; +} +#mapContainer { + width: 100%; height: 100%; +} +#inputContainer { + background-color: rgba(0,0,0,.85); + color: #fff; + height: 90px; + position: fixed; + top: 20px; + left: 20px; + width: 300px; + padding: 10px; + box-shadow: 0px 0px 8px 4px rgba(0,0,0,.4); + border-radius: 10px; +} +#titleLabel { + font-family: Schoolbell, serif; + font-weight: bold; + font-size: 30px; + margin: 0px; +} +#searchForm { + background-color: white; + border-radius: 2px; + position: relative; + height: 30px; + width: 300px; +} +#favButton { + width: 24px; + height: 24px; + background-repeat: no-repeat; + background-size: cover; + background-image: url(Heart.png); + background-color: rgba(0,0,0,0); + position: absolute; + right: 30px; + top: -32px; + font-size: 0px; + border: none; + cursor: default; +} +#locateMeButton { + width: 24px; + height: 24px; + background-image: url(Location.png); + background-color: rgba(0,0,0,0); + background-size: cover; + position: absolute; + right: 0px; + top: -32px; + font-size: 0px; + border: none; + cursor: default; +} +#searchBox { + font-family: Georgia; + font-size: 18px; + margin-right: 24px; + border: none; + width: 266px; + left: 4px; + position: absolute; + top: 3px; + outline: none; +} +#searchButton { + position: absolute; + right: 4px; + top: 4px; + width: 22px; + height: 22px; + background-color: transparent; + background-image: url(Search.png); + background-repeat: no-repeat; + background-size: cover; + border: none; + font-size: 0px; + cursor: default; +} +#searchButton.reset { + background-image: url(Reset.png); +} +#searchProgress { + position: absolute; + left: -8px; + top: 32px; + width: 316px; + height: 19px; + background-image: url(ProgressBar.gif); + background-size: 316px 19px; + display: none; +} +#searchProgress.active { + display: block; + opacity: 0.5; +} +#photoOverlay { + position: absolute; + width: 100%; + height: 100%; + left: 0px; + top: 0px; + display: none; + background-color: rgba(0,0,0,.85); +} +#photoContainer { + box-shadow: 0px 0px 6px 6px rgba(0,0,0,.5); + background-color: White; + display: table; + padding: 8px; + width: auto; + margin-left: auto; + margin-right: auto; + position: relative; +} +#photo { +} +#photoTitle { + font-family: Georgia; + font-size: 18px; + text-overflow: ellipsis; + display: block; + margin-bottom: 8px; +} +button.photoAction { + width: 32px; + height: 32px; + font-size: 0px; + background-color: transparent; + background-size: cover; + background-repeat: no-repeat; + border: none; +} +#photoSaveButton { + background-image: url(Heart.png); +} +#photoAroundButton { + background-image: url(Location.png); +} +#photoShareButton { + background-image: url(Twitter.png); +} +#photoSourceButton { + background-image: url(Flickr.png); +} +#photoCloseButton { + width: 32px; + height: 32px; + background-image: url(CloseButton.png); + background-color: rgba(255,255,255,0); + position: absolute; + right: -14px; + top: -14px; +} +div.locationPushpin, div.currentPushpin { + font-size: 0pt; +} +div.photoInfobox { + font-size: 0pt; + width: 50px; + height: 50px; + background-size: cover; + background-repeat: no-repeat; + background-color: White; + cursor: default; + -webkit-box-shadow: 0px 0px 10px black; +} +body.map div.photoInfobox, body.map #mapContainer svg { + opacity: 0.15; +} +body.photos #mapContainer img { + opacity: 0.5; +} +body.photos #mapContainer div.currentPushpin img, body.photos #mapContainer div.locationPushpin img { + opacity: 1; +} +#mapContainer svg, div.photoInfobox, #mapContainer img { + -webkit-transition: opacity .25s ease-in-out; +} diff --git a/samples/AroundMe/AroundMe/Content/Twitter.png b/samples/AroundMe/AroundMe/Content/Twitter.png new file mode 100644 index 000000000..96232f0a1 Binary files /dev/null and b/samples/AroundMe/AroundMe/Content/Twitter.png differ diff --git a/samples/AroundMe/AroundMe/Core/Model.cs b/samples/AroundMe/AroundMe/Core/Model.cs new file mode 100644 index 000000000..346caeeed --- /dev/null +++ b/samples/AroundMe/AroundMe/Core/Model.cs @@ -0,0 +1,19 @@ +// Model.cs +// + +using System; +using System.Html; + +namespace AroundMe.Core { + + internal abstract class Model { + + public event EventHandler PropertyChanged; + + protected void RaisePropertyChanged(string propertyName) { + if (PropertyChanged != null) { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } +} diff --git a/samples/AroundMe/AroundMe/Core/PropertyChangedEventArgs.cs b/samples/AroundMe/AroundMe/Core/PropertyChangedEventArgs.cs new file mode 100644 index 000000000..0a708bd64 --- /dev/null +++ b/samples/AroundMe/AroundMe/Core/PropertyChangedEventArgs.cs @@ -0,0 +1,23 @@ +// Model.cs +// + +using System; +using System.Html; + +namespace AroundMe.Core { + + internal sealed class PropertyChangedEventArgs : EventArgs { + + private string _propertyName; + + public PropertyChangedEventArgs(string propertyName) { + _propertyName = propertyName; + } + + public string PropertyName { + get { + return _propertyName; + } + } + } +} diff --git a/samples/AroundMe/AroundMe/Core/Utility.cs b/samples/AroundMe/AroundMe/Core/Utility.cs new file mode 100644 index 000000000..cbadcd571 --- /dev/null +++ b/samples/AroundMe/AroundMe/Core/Utility.cs @@ -0,0 +1,30 @@ +// Utility.cs +// + +using System; +using System.Html; + +namespace AroundMe.Core { + + internal static class Utility { + + public static Element GetElement(string id) { + return Document.GetElementById(id); + } + + public static void SubscribeBlur(string id, ElementEventListener listener) { + Element e = Document.GetElementById(id); + e.AddEventListener("blur", listener, false); + } + + public static void SubscribeClick(string id, ElementEventListener listener) { + Element e = Document.GetElementById(id); + e.AddEventListener("click", listener, false); + } + + public static void SubscribeKey(string id, ElementEventListener listener) { + Element e = Document.GetElementById(id); + e.AddEventListener("keyup", listener, false); + } + } +} diff --git a/samples/AroundMe/AroundMe/DataModel/Photo.cs b/samples/AroundMe/AroundMe/DataModel/Photo.cs new file mode 100644 index 000000000..b9cbcdecd --- /dev/null +++ b/samples/AroundMe/AroundMe/DataModel/Photo.cs @@ -0,0 +1,76 @@ +// Photo.cs +// + +using System; + +namespace AroundMe.DataModel { + + /// + /// Represents a geo-tagged photo along with various bits of metadata. + /// + internal sealed class Photo : Record { + + /// + /// The ID of the photo. + /// + public string id; + + /// + /// The text description of the photo. + /// + public string title; + + /// + /// The URL of the page associated with the photo. + /// + public string url; + + /// + /// The URL of the thumbnail of the photo. + /// + public string thumbnailUrl; + + /// + /// The URL of the image itself. + /// + public string imageUrl; + + /// + /// The width of the photo. + /// + public int width; + + /// + /// The height of the photo. + /// + public int height; + + /// + /// The latitude part of the geo-tag information. + /// + public double latitude; + + /// + /// The longitude part of the geo-tag information. + /// + public double longitude; + + /// + /// The set of tags associated with the photo. + /// + public string tags; + + public Photo(string id, string title, string tags, string url, string imageUrl, int width, int height, string thumbnailUrl, double latitude, double longitude) { + this.id = id; + this.title = title; + this.tags = tags; + this.url = url; + this.imageUrl = imageUrl; + this.width = width; + this.height = height; + this.thumbnailUrl = thumbnailUrl; + this.latitude = latitude; + this.longitude = longitude; + } + } +} diff --git a/samples/AroundMe/AroundMe/Graphs/Graph.cs b/samples/AroundMe/AroundMe/Graphs/Graph.cs new file mode 100644 index 000000000..5043c4e36 --- /dev/null +++ b/samples/AroundMe/AroundMe/Graphs/Graph.cs @@ -0,0 +1,152 @@ +// Graph.cs +// + +using System; +using System.Collections.Generic; + +namespace AroundMe.Graphs { + + internal delegate void GraphNodePairVisitor(GraphNode node1, GraphNode node2); + + internal delegate void GraphNodeVisitor(GraphNode node); + + internal delegate void GraphEdgeVisitor(GraphEdge edge); + + internal class Graph { + + private List _nodes; + private List _edges; + + private GraphLayout _layout; + + private bool _updating; + private bool _updated; + + public Graph() { + _nodes = new List(); + _edges = new List(); + _layout = new GraphLayout(this); + } + + public GraphLayout Layout { + get { + return _layout; + } + } + + public List Nodes { + get { + return _nodes; + } + } + + public List Edges { + get { + return _edges; + } + } + + public event EventHandler Changed; + + public void AddNode(GraphNode node) { + _nodes.Add(node); + if (_updating == false) { + RaiseChangedEvent(); + } + else { + _updated = true; + } + } + + public void AddEdge(GraphEdge edge) { + _edges.Add(edge); + edge.FromNode.AddEdge(edge); + edge.ToNode.AddEdge(edge); + + if (_updating == false) { + RaiseChangedEvent(); + } + else { + _updated = true; + } + } + + public void BeginUpdate() { + _updating = true; + } + + public void EndUpdate() { + _updating = false; + if (_updated) { + _updated = false; + RaiseChangedEvent(); + } + } + + private void RaiseChangedEvent() { + if (Changed != null) { + Changed(this, EventArgs.Empty); + } + } + + public void RemoveNode(GraphNode node) { + foreach (GraphEdge edge in node.Edges) { + RemoveEdge(edge); + } + + _nodes.Remove(node); + if (_updating == false) { + RaiseChangedEvent(); + } + else { + _updated = true; + } + } + + public void RemoveEdge(GraphEdge edge) { + edge.FromNode.RemoveEdge(edge); + edge.ToNode.RemoveEdge(edge); + + _edges.Remove(edge); + if (_updating == false) { + RaiseChangedEvent(); + } + else { + _updated = true; + } + } + + public void Reset() { + _nodes.Clear(); + _edges.Clear(); + if (_updating == false) { + RaiseChangedEvent(); + } + else { + _updated = true; + } + } + + public void VisitEdges(GraphEdgeVisitor visitor) { + foreach (GraphEdge edge in _edges) { + visitor(edge); + } + } + + public void VisitNodes(GraphNodeVisitor visitor) { + foreach (GraphNode node in _nodes) { + visitor(node); + } + } + + public void VisitNodePairs(GraphNodePairVisitor visitor) { + foreach (GraphNode n1 in _nodes) { + foreach (GraphNode n2 in _nodes) { + if (n1 != n2) { + visitor(n1, n2); + } + } + } + } + } +} diff --git a/samples/AroundMe/AroundMe/Graphs/GraphEdge.cs b/samples/AroundMe/AroundMe/Graphs/GraphEdge.cs new file mode 100644 index 000000000..84ea03662 --- /dev/null +++ b/samples/AroundMe/AroundMe/Graphs/GraphEdge.cs @@ -0,0 +1,38 @@ +// GraphEdge.cs +// + +using System; + +namespace AroundMe.Graphs { + + internal class GraphEdge { + + private GraphNode _fromNode; + private GraphNode _toNode; + private int _length; + + public GraphEdge(GraphNode fromNode, GraphNode toNode, int length) { + _fromNode = fromNode; + _toNode = toNode; + _length = length; + } + + public GraphNode FromNode { + get { + return _fromNode; + } + } + + public int Length { + get { + return _length; + } + } + + public GraphNode ToNode { + get { + return _toNode; + } + } + } +} diff --git a/samples/AroundMe/AroundMe/Graphs/GraphLayout.cs b/samples/AroundMe/AroundMe/Graphs/GraphLayout.cs new file mode 100644 index 000000000..58f49fa3b --- /dev/null +++ b/samples/AroundMe/AroundMe/Graphs/GraphLayout.cs @@ -0,0 +1,247 @@ +// GraphLayout.cs +// + +using System; +using System.Diagnostics; + +namespace AroundMe.Graphs { + + internal sealed class GraphLayout { + + internal const double RepulsionFactor = 0.75f; + internal const double DefaultRepulsion = 100.0f; + private const double MotionLimit = 0.01f; + + private Graph _graph; + + private double _damper; + private bool _damping; + private double _rigidity; + private double _newRigidity; + private double _maxMotion; + private double _lastMaxMotion; + private double _intermediateMaxMotion; + + // lastMaxMotion / maxMotion-1 + private double _motionRatio; + + public GraphLayout(Graph graph) { + _graph = graph; + + // A low damper value causes the graph to move slowly; as + // layout proceeds, the damper value decreases. + _damper = 1.0f; + _damping = true; + + // Rigidity has the same effect as the damper, except that it's a constant. + // A low rigidity value causes things to go slowly. + // A value that's too high will cause oscillation. + _rigidity = 0.25f; + _newRigidity = 0.25f; + + // Allow's tracking the fastest moving node to see if the graph is stabilizing + _maxMotion = 0; + _lastMaxMotion = 0; + _motionRatio = 0; + _intermediateMaxMotion = 0; + } + + private void LayoutNode(GraphNode node) { + if (node.moveable) { + double dx = node.dx; + double dy = node.dy; + + // The damper slows things down. It cuts down jiggling at the last moment, and optimizes + // layout. As an experiment, get rid of the damper in these lines, and make a + // long straight line of nodes. It wiggles too much and doesn't straighten out. + dx *= _damper; + dy *= _damper; + + // Slow down, but dont stop. Nodes in motion store momentum. This helps when the force + // on a node is very low, but you still want to get optimal layout. + node.dx = dx / 2; + node.dy = dy / 2; + + // How far did the node actually move? + double distanceMoved = Math.Sqrt(dx * dx + dy * dy); + + // Don't move faster then 30 units at a time. + // Stops severed nodes from flying away? + + node.x = node.x + Math.Max(-30, Math.Min(30, dx)); + node.y = node.y + Math.Max(-30, Math.Min(30, dy)); + + _intermediateMaxMotion = Math.Max(distanceMoved, _intermediateMaxMotion); + } + } + + private void LayoutNodes() { + _lastMaxMotion = _maxMotion; + _intermediateMaxMotion = 0f; + _graph.VisitNodes(new GraphNodeVisitor(LayoutNode)); + _maxMotion = _intermediateMaxMotion; + + if (_maxMotion > 0) { + // Subtract 1 to make a positive value that implies that + // things are moving faster + _motionRatio = _lastMaxMotion / _maxMotion - 1; + } + else { + _motionRatio = 0; + } + + if (_damping) { + if (_motionRatio <= 0.001) { + // Only damp when the graph starts to move faster + // When there is noise, you damp roughly half the time. (Which is a lot) + // If things are slowing down, then you can let them do so on their own, + // without damping. + + if ((_maxMotion < 0.2 || (_maxMotion > 1 && _damper < 0.9)) && + (_damper > 0.01)) { + // If maxMotion < 0.2, damp away + // If by the time the damper has gone down to 0.9, and + // maxMotion is still > 1, then damp away + // We never want the damper to be negative though. + + _damper -= 0.01f; + } + else if (_maxMotion < 0.4 && _damper > 0.003) { + // If we've slowed down significanly, damp more aggresively + _damper -= 0.003f; + } + else if (_damper > 0.0001) { + // If max motion is pretty high, and we just started damping, + // then only damp slightly + _damper -= 0.0001f; + } + } + } + if (_maxMotion < MotionLimit && _damping) { + _damper = 0; + } + } + + public bool PerformLayout() { + if (!(_damper < 0.1 && _damping && _maxMotion < MotionLimit)) { + for (int i = 0; i < 5; i++) { + RelaxEdges(); + RemoveOverlaps(); + LayoutNodes(); + } + + if (_rigidity != _newRigidity) { + _rigidity = _newRigidity; + } + + return true; + } + else { + return false; + } + } + + public void PauseLayout() { + _damping = false; + } + + private void RelaxEdge(GraphEdge edge) { + // Pull nodes closes together by 'relaxing' each edge + + double vx = edge.ToNode.x - edge.FromNode.x; + double vy = edge.ToNode.y - edge.FromNode.y; + double distance = Math.Sqrt(vx * vx + vy * vy); + + // Rigidity makes edges tighter + double dx = vx * _rigidity; + double dy = vy * _rigidity; + + int div = edge.Length * 100; + dx = dx / div; + dy = dy / div; + + // Edges pull directly in proportion to the distance between the nodes. This is good, + // because we want the edges to be stretchy. The edges are ideal rubberbands. They + // They don't become springs when they are too short. That only causes the graph to + // oscillate. + + edge.ToNode.dx = edge.ToNode.dx - dx * distance; + edge.ToNode.dy = edge.ToNode.dy - dy * distance; + edge.FromNode.dx = edge.FromNode.dx + dx * distance; + edge.FromNode.dy = edge.FromNode.dy + dy * distance; + } + + private void RelaxEdges() { + _graph.VisitEdges(new GraphEdgeVisitor(RelaxEdge)); + } + + private void RemoveOverlap(GraphNode node1, GraphNode node2) { + double dx = 0; + double dy = 0; + double vx = node1.x - node2.x; + double vy = node1.y - node2.y; + double distance = vx * vx + vy * vy; + + // TODO: 36 should be a property instead of hardcoded + // This is the radius of a node. + + if (distance < 36 * 36 * 2) { + // If two nodes are right on top of each other, separate them + // apart randomly + dx = Math.Random(); + dy = Math.Random(); + } + else if (distance < 360000) { + // 600 * 600, because we don't want deleted nodes to fly too far away + + // If it was sqrt(len) then a single node surrounded by many others will + // always look like a circle. This might look good at first, but I think + // it makes large graphs look ugly + it contributes to oscillation. A + // linear function does not fall off fast enough, so you get rough edges + // in the 'force field' + + dx = vx / distance; + dy = vy / distance; + } + + double totalRepulsion = DefaultRepulsion * DefaultRepulsion / 100; + double factor = totalRepulsion * _rigidity; + + node1.dx += dx * factor; + node1.dy += dy * factor; + node2.dx -= dx * factor; + node2.dy -= dy * factor; + } + + private void RemoveOverlaps() { + _graph.VisitNodePairs(new GraphNodePairVisitor(RemoveOverlap)); + } + + public void ResetLayout() { + _damping = true; + _damper = 1.0f; + } + + public void ResumeLayout() { + _damping = true; + } + + public void StopLayout() { + _damping = false; + _damper = 1.0f; + } + + public void SlowLayout() { + // Stabilize the graph, but do so gently by setting the damper + // to a low value + + _damping = true; + if (_damper > 0.3f) { + _damper = 0.3f; + } + else { + _damper = 0f; + } + } + } +} diff --git a/samples/AroundMe/AroundMe/Graphs/GraphNode.cs b/samples/AroundMe/AroundMe/Graphs/GraphNode.cs new file mode 100644 index 000000000..f46b4aa59 --- /dev/null +++ b/samples/AroundMe/AroundMe/Graphs/GraphNode.cs @@ -0,0 +1,42 @@ +// GraphNode.cs +// + +using System; +using System.Collections.Generic; + +namespace AroundMe.Graphs { + + internal class GraphNode { + + private List _edges; + + public double x; + public double y; + internal double dx; + internal double dy; + public bool moveable; + + public GraphNode() { + _edges = new List(); + moveable = true; + } + + public List Edges { + get { + return _edges; + } + } + + internal void AddEdge(GraphEdge edge) { + _edges.Add(edge); + } + + internal void ClearEdges() { + _edges.Clear(); + } + + internal void RemoveEdge(GraphEdge edge) { + _edges.Remove(edge); + } + } +} diff --git a/samples/AroundMe/AroundMe/Page.cs b/samples/AroundMe/AroundMe/Page.cs new file mode 100644 index 000000000..47192e0f3 --- /dev/null +++ b/samples/AroundMe/AroundMe/Page.cs @@ -0,0 +1,403 @@ +// Page.cs +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Html; +using System.Html.Media.Graphics; +using System.Html.Services; +using Microsoft.Maps; +using TwitterApi; +using AroundMe.Core; +using AroundMe.DataModel; +using AroundMe.Graphs; +using AroundMe.Services; + +namespace AroundMe { + + internal static class Page { + + private const string MapModeClassName = "map"; + private const string PhotosModeClassName = "photos"; + + private static PageModel _model; + private static Map _map; + private static MapEntityCollection _mapEntities; + private static MapPushpin _currentPushpin; + private static string _tileUrlFormat; + + private static Graph _graph; + private static Dictionary _photoViews; + + private static bool _viewChanging; + private static int _zoomLevel; + private static string _oldMode; + + static Page() { + if (Document.Body.GetAttribute("data-app") == null) { + return; + } + + string flickrKey = (string)Document.Body.GetAttribute("data-flickr-key"); + Debug.Assert(String.IsNullOrEmpty(flickrKey) == false); + + string bingMapsKey = (string)Document.Body.GetAttribute("data-bingmaps-key"); + Debug.Assert(String.IsNullOrEmpty(bingMapsKey) == false); + + _tileUrlFormat = (string)Document.Body.GetAttribute("data-tile-url"); + Debug.Assert(String.IsNullOrEmpty(_tileUrlFormat) == false); + + _model = new PageModel(new FlickrService(flickrKey), new HtmlStorageService()); + _model.PropertyChanged += delegate(object sender, PropertyChangedEventArgs e) { + if (e.PropertyName == "Searching") { + Element progressElement = Utility.GetElement("searchProgress"); + if (_model.Searching) { + progressElement.ClassName = "active"; + } + else { + progressElement.ClassName = ""; + } + } + else if (e.PropertyName == "Photos") { + if (_model.SelectedPhoto != null) { + ShowPhoto(null); + } + + UpdatePhotos(/* newPhotos */ true); + } + }; + + MapOptions mapOptions = new MapOptions(); + mapOptions.Credentials = bingMapsKey; + mapOptions.ShowMapTypeSelector = false; + mapOptions.ShowDashboard = false; + mapOptions.ShowScalebar = false; + mapOptions.ShowCopyright = false; + mapOptions.ShowLogo = false; + mapOptions.MapType = MapType.Custom; + mapOptions.Zoom = 2; + mapOptions.BackgroundColor = new MapColor(255, 255, 255, 255); + _map = new Map(Utility.GetElement("mapContainer"), mapOptions); + + MapTileSourceOptions sourceOptions = new MapTileSourceOptions(); + sourceOptions.UriGenerator = CreateTileUrl; + + MapTileLayerOptions layerOptions = new MapTileLayerOptions(); + layerOptions.Mercator = new MapTileSource(sourceOptions); + _map.Entities.Push(new MapTileLayer(layerOptions)); + + MapEvents.AddHandler(_map, "viewchangestart", delegate(MapEventArgs e) { + _viewChanging = true; + _zoomLevel = _map.GetZoom(); + }); + MapEvents.AddThrottledHandler(_map, "viewchangeend", delegate(MapEventArgs e) { + _viewChanging = false; + + if (_zoomLevel != _map.GetZoom()) { + UpdatePhotos(/* newPhotos */ false); + } + }, 250); + MapEvents.AddHandler(_map, "mousedown", delegate(MapEventArgs e) { + _oldMode = Document.Body.ClassName; + Document.Body.ClassName = MapModeClassName; + }); + MapEvents.AddHandler(_map, "mouseup", delegate(MapEventArgs e) { + Document.Body.ClassName = _oldMode; + }); + + Utility.SubscribeKey("searchBox", delegate(ElementEvent e) { + Window.SetTimeout(delegate() { + Document.GetElementById("searchButton").ClassName = + String.IsNullOrEmpty(Utility.GetElement("searchBox").As().Value) ? "reset" : ""; + }, 0); + }); + Utility.SubscribeBlur("searchBox", delegate(ElementEvent e) { + Window.SetTimeout(delegate() { + Document.GetElementById("searchButton").ClassName = ""; + }, 0); + }); + Utility.SubscribeClick("searchButton", delegate(ElementEvent e) { + Search(Utility.GetElement("searchBox").As().Value); + Document.ActiveElement.Blur(); + }); + Utility.SubscribeClick("locateMeButton", delegate(ElementEvent e) { + ShowLocation(); + }); + Utility.SubscribeClick("favButton", delegate(ElementEvent e) { + ShowFavorites(); + }); + + Utility.SubscribeClick("photoAroundButton", delegate(ElementEvent e) { + SearchSimilar(); + }); + Utility.SubscribeClick("photoCloseButton", delegate(ElementEvent e) { + HidePhoto(); + }); + Utility.SubscribeClick("photoSaveButton", delegate(ElementEvent e) { + FavoritePhoto(); + }); + Utility.SubscribeClick("photoShareButton", delegate(ElementEvent e) { + SharePhoto(); + }); + Utility.SubscribeClick("photoSourceButton", delegate(ElementEvent e) { + ShowPhotoFlickrPage(); + }); + + ShowLocation(); + } + + private static string CreateTileUrl(MapTile tile) { + int index = Math.Floor(Math.Random() * 3 + 1); + string domain = (index == 1) ? "a" : (index == 2) ? "b" : "c"; + + return String.Format(_tileUrlFormat, domain, tile.LevelOfDetail, tile.X, tile.Y); + } + + private static void FavoritePhoto() { + Debug.Assert(_model.SelectedPhoto != null); + _model.AddFavorite(); + Script.Alert("This photo has been saved to your favorites."); + } + + private static void HidePhoto() { + Debug.Assert(_model.SelectedPhoto != null); + ShowPhoto(null); + } + + private static void Search(string text) { + if (String.IsNullOrEmpty(text)) { + _model.ResetSearch(); + return; + } + + MapBounds bounds = _map.GetBounds(); + _model.SearchRegion(text, bounds.GetWest(), bounds.GetSouth(), + bounds.GetEast(), bounds.GetNorth()); + } + + private static void SearchSimilar() { + Debug.Assert(_model.SelectedPhoto != null); + + Photo selectedPhoto = _model.SelectedPhoto; + _model.SearchLocation(selectedPhoto.tags, selectedPhoto.latitude, selectedPhoto.longitude); + ShowPhoto(null); + } + + private static void SharePhoto() { + Debug.Assert(_model.SelectedPhoto != null); + + Twitter.Anywhere(delegate(TwitterObject t) { + Photo photo = _model.SelectedPhoto; + + TweetBoxOptions options = new TweetBoxOptions(); + options.Callback = delegate(string tweet, string htmlTweet) { + Utility.GetElement("photoTweet").InnerHTML = String.Empty; + Script.Alert("This photo has been shared with your followers on Twitter."); + }; + options.Data = new TweetBoxData(); + options.Data.Latitude = photo.latitude; + options.Data.Longitude = photo.longitude; + options.Label = ""; + options.DefaultContent = photo.title + " " + photo.url; + options.Width = photo.width; + + t.Select("#photoTweet").TweetBox(options); + }); + } + + private static void ShowFavorites() { + _model.ShowFavorites(); + + int favoriteCount = _model.Photos.Count; + if (favoriteCount != 0) { + MapLocation[] locations = new MapLocation[favoriteCount]; + + for (int i = 0; i < favoriteCount; i++) { + locations[i] = new MapLocation(_model.Photos[i].latitude, _model.Photos[i].longitude); + } + + MapViewOptions viewOptions = new MapViewOptions(); + viewOptions.Bounds = MapBounds.FromLocations(locations); + viewOptions.Animate = true; + + _map.SetView(viewOptions); + UpdatePhotos(/* newPhotos */ false); + } + } + + private static void ShowLocation() { + Window.Navigator.Geolocation.GetCurrentPosition(delegate(Geolocation location) { + MapViewOptions viewOptions = new MapViewOptions(); + viewOptions.Center = new MapLocation(location.Coordinates.Latitude, + location.Coordinates.Longitude); + viewOptions.Zoom = 10; + viewOptions.Animate = true; + + _map.SetView(viewOptions); + UpdatePhotos(/* newPhotos */ false); + + if (_currentPushpin == null) { + MapPushpinOptions pushpinOptions = new MapPushpinOptions(); + pushpinOptions.Icon = "Pushpin.png"; + pushpinOptions.Anchor = new MapPoint(12, 14); + pushpinOptions.Width = 25; + pushpinOptions.Height = 28; + pushpinOptions.TypeName = "currentPushpin"; + + _currentPushpin = new MapPushpin(viewOptions.Center, pushpinOptions); + _map.Entities.Push(_currentPushpin); + } + else { + _currentPushpin.SetLocation(viewOptions.Center); + } + }); + } + + private static void ShowPhoto(Photo photo) { + Element photoOverlay = Utility.GetElement("photoOverlay"); + Element photoWrapper = Utility.GetElement("photo"); + Element photoTitle = Utility.GetElement("photoTitle"); + Element photoTweet = Utility.GetElement("photoTweet"); + + if (photo != null) { + ImageElement photoImage = Document.CreateElement("img").As(); + photoImage.Width = photo.width; + photoImage.Height = photo.height; + photoImage.Src = photo.imageUrl; + + photoTitle.TextContent = photo.title; + photoTitle.Style.Width = photo.width + "px"; + photoWrapper.AppendChild(photoImage); + photoOverlay.Style.Display = "block"; + + Element photoContainer = Utility.GetElement("photoContainer"); + photoContainer.Style.MarginTop = Math.Max(20, Math.Truncate((Document.Body.OffsetHeight - photoContainer.OffsetHeight) / 2f) - 100) + "px"; + } + else { + photoTitle.TextContent = String.Empty; + photoWrapper.InnerHTML = String.Empty; + photoTweet.InnerHTML = String.Empty; + photoOverlay.Style.Display = "none"; + } + + _model.SelectedPhoto = photo; + } + + private static void ShowPhotoFlickrPage() { + Debug.Assert(_model.SelectedPhoto != null); + Window.Open(_model.SelectedPhoto.url, "_blank"); + } + + private static void UpdateLayout() { + if (_viewChanging) { + return; + } + + bool continueLayout = _graph.Layout.PerformLayout(); + + _model.Photos.ForEach(delegate(Photo photo) { + PhotoView photoView = _photoViews[photo.id]; + + MapLocation pushpinLocation = photoView.pushpin.GetLocation(); + MapLocation calloutLocation = + _map.TryPixelToLocation(new MapPoint(photoView.calloutNode.x, photoView.calloutNode.y), + MapPointReference.Control); + + photoView.callout.SetLocation(calloutLocation); + photoView.connector.SetLocations(new MapLocation[] { pushpinLocation, calloutLocation }); + }); + + if (continueLayout) { + Window.SetTimeout(UpdateLayout, 30); + } + } + + private static void UpdatePhotos(bool newPhotos) { + if (newPhotos) { + if (_mapEntities != null) { + _map.Entities.Remove(_mapEntities); + } + + _mapEntities = new MapEntityCollection(); + _map.Entities.Push(_mapEntities); + + _photoViews = new Dictionary(); + } + + if (_model.Photos.Count == 0) { + Document.Body.ClassName = MapModeClassName; + return; + } + + Document.Body.ClassName = PhotosModeClassName; + + _graph = new Graph(); + _model.Photos.ForEach(delegate(Photo photo) { + MapLocation location = new MapLocation(photo.latitude, photo.longitude); + MapPoint point = _map.TryLocationToPixel(location, MapPointReference.Control); + + PhotoView photoView; + if (newPhotos) { + MapPolylineOptions connectorOptions = new MapPolylineOptions(); + connectorOptions.StrokeColor = new MapColor(255, 0x4E, 0xD3, 0x4E); + connectorOptions.StrokeThickness = 2; + + MapInfoboxOptions calloutOptions = new MapInfoboxOptions(); + calloutOptions.Width = 50; + calloutOptions.Height = 50; + calloutOptions.ShowPointer = false; + calloutOptions.ShowCloseButton = false; + calloutOptions.Offset = new MapPoint(-25, -25); + calloutOptions.HtmlContent = + "
"; + calloutOptions.Visible = true; + + MapPushpinOptions pushpinOptions = new MapPushpinOptions(); + pushpinOptions.Icon = "Dot.png"; + pushpinOptions.Width = 10; + pushpinOptions.Height = 10; + pushpinOptions.Anchor = new MapPoint(5, 5); + pushpinOptions.TypeName = "locationPushpin"; + + photoView = new PhotoView(); + photoView.pushpin = new MapPushpin(location, pushpinOptions); + photoView.connector = new MapPolyline(new MapLocation[] { location, location }, connectorOptions); + photoView.callout = new MapInfobox(location, calloutOptions); + photoView.callout.Data = photo; + _photoViews[photo.id] = photoView; + + _mapEntities.Insert(photoView.connector, 0); + _mapEntities.Insert(photoView.callout, 0); + _mapEntities.Insert(photoView.pushpin, 0); + MapEvents.AddHandler(photoView.callout, "click", delegate(MapEventArgs e) { + ShowPhoto(photo); + }); + } + else { + photoView = _photoViews[photo.id]; + } + + photoView.pushpinNode = new GraphNode(); + photoView.pushpinNode.x = point.X; + photoView.pushpinNode.y = point.Y; + photoView.pushpinNode.moveable = false; + + photoView.calloutNode = new GraphNode(); + photoView.calloutNode.x = point.X; + photoView.calloutNode.y = point.Y; + + GraphEdge connectorEdge = new GraphEdge(photoView.pushpinNode, + photoView.calloutNode, + 10 + Math.Random() * 15); + + _graph.AddNode(photoView.pushpinNode); + _graph.AddNode(photoView.calloutNode); + _graph.AddEdge(connectorEdge); + }); + + Window.SetTimeout(UpdateLayout, 30); + } + } +} diff --git a/samples/AroundMe/AroundMe/PageModel.cs b/samples/AroundMe/AroundMe/PageModel.cs new file mode 100644 index 000000000..26ae519fc --- /dev/null +++ b/samples/AroundMe/AroundMe/PageModel.cs @@ -0,0 +1,130 @@ +// PageModel.cs +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Serialization; +using AroundMe.Core; +using AroundMe.DataModel; +using AroundMe.Services; + +namespace AroundMe { + + internal sealed class PageModel : Model { + + private IFlickrService _flickr; + private IStorageService _storage; + + private bool _searching; + + private List _photos; + private List _favoritePhotos; + private Photo _selectedPhoto; + + public PageModel(IFlickrService flickr, IStorageService storage) { + _flickr = flickr; + _storage = storage; + + _photos = new List(); + } + + public List Photos { + get { + return _photos; + } + private set { + _photos = value; + RaisePropertyChanged("Photos"); + } + } + + public bool Searching { + get { + return _searching; + } + private set { + _searching = value; + RaisePropertyChanged("Searching"); + } + } + + public Photo SelectedPhoto { + get { + return _selectedPhoto; + } + set { + _selectedPhoto = value; + } + } + + public void AddFavorite() { + if (_selectedPhoto == null) { + return; + } + + LoadFavorites(); + + _favoritePhotos.Add(_selectedPhoto); + SaveFavorites(); + } + + private void LoadFavorites() { + if (_favoritePhotos == null) { + try { + string favoriteData = _storage.GetValue("favorites"); + if (String.IsNullOrEmpty(favoriteData) == false) { + _favoritePhotos = Json.ParseData>(favoriteData); + } + } + catch { + Debug.Fail("Failed to load favorites"); + } + } + + if (_favoritePhotos == null) { + _favoritePhotos = new List(); + } + } + + private void SaveFavorites() { + string favoritesData = Json.Stringify(_favoritePhotos); + _storage.SetValue("favorites", favoritesData); + } + + public void ShowFavorites() { + LoadFavorites(); + Photos = _favoritePhotos; + } + + public void ResetSearch() { + Photos = new List(); + } + + public void SearchLocation(string tags, double latitude, double longitude) { + Searching = true; + _flickr.SearchLocation(tags, latitude, longitude, SearchCompleted); + } + + public void SearchRegion(string text, double latitude1, double longitude1, double latitude2, double longitude2) { + Searching = true; + _flickr.SearchRegion(text, latitude1, longitude1, latitude2, longitude2, SearchCompleted); + } + + private void SearchCompleted(IEnumerable photoResults) { + List photos = new List(); + + foreach (PhotoResult photoResult in photoResults) { + Photo photo = new Photo(photoResult.ID, photoResult.Title, photoResult.tags, + photoResult.Url_p, + photoResult.Url_m, Int32.Parse(photoResult.Width_m), Int32.Parse(photoResult.Height_m), + photoResult.Url_sq, + photoResult.Latitude, photoResult.Longitude); + photos.Add(photo); + } + + Searching = false; + Photos = photos; + } + } +} diff --git a/samples/AroundMe/AroundMe/PhotoView.cs b/samples/AroundMe/AroundMe/PhotoView.cs new file mode 100644 index 000000000..0ef0a8680 --- /dev/null +++ b/samples/AroundMe/AroundMe/PhotoView.cs @@ -0,0 +1,20 @@ +// Photo.cs +// + +using System; +using System.Html; +using Microsoft.Maps; +using AroundMe.Graphs; + +namespace AroundMe.DataModel { + + internal sealed class PhotoView : Record { + + public GraphNode pushpinNode; + public GraphNode calloutNode; + + public MapPushpin pushpin; + public MapInfobox callout; + public MapPolyline connector; + } +} diff --git a/samples/AroundMe/AroundMe/Properties/AssemblyInfo.cs b/samples/AroundMe/AroundMe/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0840f80f9 --- /dev/null +++ b/samples/AroundMe/AroundMe/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AroundMe")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("AroundMe")] +[assembly: ScriptNamespace("app")] diff --git a/samples/AroundMe/AroundMe/Properties/FxCop.ruleset b/samples/AroundMe/AroundMe/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/AroundMe/AroundMe/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/AroundMe/Properties/Script.template b/samples/AroundMe/AroundMe/Properties/Script.template new file mode 100644 index 000000000..d0fad56de --- /dev/null +++ b/samples/AroundMe/AroundMe/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function() { +#include[as-is] "%code%" +})(); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/AroundMe/AroundMe/Services/Base58.cs b/samples/AroundMe/AroundMe/Services/Base58.cs new file mode 100644 index 000000000..60ddf6141 --- /dev/null +++ b/samples/AroundMe/AroundMe/Services/Base58.cs @@ -0,0 +1,40 @@ +// Base58.cs +// + +using System; + +namespace AroundMe.Services { + + internal static class Base58 { + + private static string Alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; + + public static string Decode(string id) { + int converted = 0; + int multiplier = 1; + + while (id.Length > 0) { + string currentChar = id.Substr(id.Length - 1); + converted += multiplier * Alphabet.IndexOf(currentChar); + multiplier = multiplier * Alphabet.Length; + + id = id.Substr(0, id.Length - 1); + } + + return converted.ToString(); + } + + public static string Encode(string id) { + int number = Int32.Parse(id); + + string encoded = ""; + while (number > 0) { + int remainder = number % Alphabet.Length; + number = (int)(number / Alphabet.Length); + encoded = Alphabet.CharAt(remainder) + encoded; + } + + return encoded; + } + } +} diff --git a/samples/AroundMe/AroundMe/Services/FlickrService.cs b/samples/AroundMe/AroundMe/Services/FlickrService.cs new file mode 100644 index 000000000..0c57adb1b --- /dev/null +++ b/samples/AroundMe/AroundMe/Services/FlickrService.cs @@ -0,0 +1,95 @@ +// FlickrService.cs +// + +using System; +using System.Collections.Generic; +using System.Html; +using System.Runtime.CompilerServices; + +namespace AroundMe.Services { + + [Imported] + public sealed class SearchResponse { + + [ScriptName("photos")] + public PhotosSearchResponse PhotoResponse; + } + + [Imported] + public sealed class PhotosSearchResponse { + + [ScriptName("photo")] + public List PhotoList; + } + + [Imported] + public sealed class PhotoResult { + + public string ID; + public string Owner; + public string Title; + public string Url_sq; + public string Url_m; + public string Height_m; + public string Width_m; + public double Latitude; + public double Longitude; + public string tags; + + public string Url_p; + } + + internal sealed class FlickrService : IFlickrService { + + private static string RegionSearchUriFormat = "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={0}&tags={1}&tag_mode=all&has_geo=1&sort=interestingness-desc&safe_search=1&extras=o_dims%2Curl_sq%2Curl_m%2Cgeo%2Ctags&bbox={2}%2C{3}%2C{4}%2C{5}&per_page=20&format=json"; + private static string LocationSearchUriFormat = "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={0}&tags={1}&has_geo=true&sort=interestingness-desc&safe_search=1&extras=o_dims%2Curl_sq%2Curl_m%2Cgeo%2Ctags&lat={2}&lon={3}&per_page=20&format=json"; + private string _apiKey; + + public FlickrService(string apiKey) { + _apiKey = apiKey; + } + + public void SearchLocation(string tags, double latitude, double longitude, Action> searchCallback) { + string url = String.Format(LocationSearchUriFormat, + _apiKey, + tags.EncodeUriComponent(), latitude, longitude); + Search(url, searchCallback); + } + + public void SearchRegion(string text, double latitude1, double longitude1, double latitude2, double longitude2, Action> searchCallback) { + string tags = text.Split(' ').Join(",").EncodeUriComponent(); + string url = String.Format(RegionSearchUriFormat, + _apiKey, + tags, latitude1, longitude1, latitude2, longitude2); + + Search(url, searchCallback); + } + + private static void Search(string url, Action> searchCallback) { + Action jsonCallback = + delegate(SearchResponse response) { + List photos = response.PhotoResponse.PhotoList; + + photos.ForEach(delegate(PhotoResult photo) { + string[] tagsArray = photo.tags.Split(' '); + if (tagsArray.Length > 10) { + tagsArray = (string[])tagsArray.Extract(0, 10); + } + + photo.tags = tagsArray.Join(","); + photo.Url_p = "http://flic.kr/p/" + Base58.Encode(photo.ID); + }); + + searchCallback(photos); + }; + + string jsonCallbackName = Delegate.CreateExport(jsonCallback); + string scriptSource = url + "&jsoncallback=" + jsonCallbackName; + + ScriptElement scriptElement = Document.CreateElement("script").As(); + scriptElement.Type = "text/javascript"; + scriptElement.Src = scriptSource; + Document.GetElementsByTagName("head")[0].AppendChild(scriptElement); + } + } +} diff --git a/samples/AroundMe/AroundMe/Services/HtmlStorageService.cs b/samples/AroundMe/AroundMe/Services/HtmlStorageService.cs new file mode 100644 index 000000000..072287647 --- /dev/null +++ b/samples/AroundMe/AroundMe/Services/HtmlStorageService.cs @@ -0,0 +1,19 @@ +// HtmlStorageService.cs +// + +using System; +using System.Html; + +namespace AroundMe.Services { + + internal sealed class HtmlStorageService : IStorageService { + + public string GetValue(string key) { + return (string)Window.LocalStorage[key]; + } + + public void SetValue(string key, string value) { + Window.LocalStorage[key] = value; + } + } +} diff --git a/samples/AroundMe/AroundMe/Services/IFlickrService.cs b/samples/AroundMe/AroundMe/Services/IFlickrService.cs new file mode 100644 index 000000000..af78aa4ff --- /dev/null +++ b/samples/AroundMe/AroundMe/Services/IFlickrService.cs @@ -0,0 +1,15 @@ +// IFlickrService.cs +// + +using System; +using System.Collections.Generic; + +namespace AroundMe.Services { + + interface IFlickrService { + + void SearchLocation(string tags, double latitude, double longitude, Action> searchCallback); + + void SearchRegion(string text, double latitude1, double longitude1, double latitude2, double longitude2, Action> searchCallback); + } +} diff --git a/samples/AroundMe/AroundMe/Services/IStorageService.cs b/samples/AroundMe/AroundMe/Services/IStorageService.cs new file mode 100644 index 000000000..f7d53deb7 --- /dev/null +++ b/samples/AroundMe/AroundMe/Services/IStorageService.cs @@ -0,0 +1,14 @@ +// IStorageService.cs +// + +using System; + +namespace AroundMe.Services { + + internal interface IStorageService { + + string GetValue(string key); + + void SetValue(string key, string value); + } +} diff --git a/samples/AroundMe/AroundMe/Tests/MockFlickrService.cs b/samples/AroundMe/AroundMe/Tests/MockFlickrService.cs new file mode 100644 index 000000000..24a8c2307 --- /dev/null +++ b/samples/AroundMe/AroundMe/Tests/MockFlickrService.cs @@ -0,0 +1,39 @@ +// MockFlickrService.cs +// + +using System; +using System.Collections.Generic; +using AroundMe.Services; + +namespace AroundMe.Tests { + + public class MockFlickrService : IFlickrService { + + private Action> _searchCallback; + private int _searchCount; + + public MockFlickrService() { + _searchCount = 0; + } + + public int SearchCount { + get { + return _searchCount; + } + } + + public void InvokeCallback(IEnumerable results) { + _searchCallback(results); + } + + public void SearchLocation(string tags, double latitude, double longitude, Action> searchCallback) { + _searchCallback = searchCallback; + _searchCount++; + } + + public void SearchRegion(string text, double latitude1, double longitude1, double latitude2, double longitude2, Action> searchCallback) { + _searchCallback = searchCallback; + _searchCount++; + } + } +} diff --git a/samples/AroundMe/AroundMe/Tests/PageModelTests.cs b/samples/AroundMe/AroundMe/Tests/PageModelTests.cs new file mode 100644 index 000000000..ab9035297 --- /dev/null +++ b/samples/AroundMe/AroundMe/Tests/PageModelTests.cs @@ -0,0 +1,37 @@ +// PageModelTests.cs +// + +using System; +using System.Diagnostics.CodeAnalysis; +using System.Html; +using System.Testing; +using AroundMe.Services; + +namespace AroundMe.Tests { + + [SuppressMessage("Microsoft.Performance", "CA1812", + Justification = "Tests don't have internal consumers. They're only compiled into .test.js scripts.")] + internal sealed class PageModelTests : TestClass { + + public void TestSearchLocationTogglesSearchingStatus() { + Assert.ExpectAsserts(2); + + MockFlickrService flickrService = new MockFlickrService(); + PageModel pageModel = new PageModel(flickrService, null); + + pageModel.SearchLocation("xyz", 0, 0); + Assert.IsTrue(pageModel.Searching, "Expected model to report true for Searching."); + + Window.SetTimeout(delegate() { + PhotoResult[] dummyResults = new PhotoResult[0]; + + flickrService.InvokeCallback(dummyResults); + Assert.IsTrue(pageModel.Searching == false, "Expected model to report false for Searching."); + + TestEngine.ResumeOnAsyncCompleted(); + }, 2000); + + TestEngine.WaitForAsyncCompletion(); + } + } +} diff --git a/samples/AroundMe/AroundMe/packages.config b/samples/AroundMe/AroundMe/packages.config new file mode 100644 index 000000000..a7a59cc42 --- /dev/null +++ b/samples/AroundMe/AroundMe/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/AroundMeTests.testsettings b/samples/AroundMe/AroundMeTests.testsettings new file mode 100644 index 000000000..d78647c9b --- /dev/null +++ b/samples/AroundMe/AroundMeTests.testsettings @@ -0,0 +1,25 @@ + + + These are default test settings for a local test run. + + + + + + + + + + +
+
+
+
+ + + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/AroundMeTests/AroundMeTests.csproj b/samples/AroundMe/AroundMeTests/AroundMeTests.csproj new file mode 100644 index 000000000..ea073b83b --- /dev/null +++ b/samples/AroundMe/AroundMeTests/AroundMeTests.csproj @@ -0,0 +1,62 @@ + + + + Debug + AnyCPU + 9.0.0 + 2.0 + {E01AE039-4B10-4A3B-AB86-EBFC1E2F6B17} + Library + Properties + AroundMeTests + AroundMeTests + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\ScriptSharp.Testing.0.7.5.0\lib\ScriptSharp.Testing.dll + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/AroundMeTests/DefaultTests.cs b/samples/AroundMe/AroundMeTests/DefaultTests.cs new file mode 100644 index 000000000..d03768991 --- /dev/null +++ b/samples/AroundMe/AroundMeTests/DefaultTests.cs @@ -0,0 +1,55 @@ +// DefaultTests.cs +// + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ScriptSharp.Testing; + +namespace AroundMeTests { + + [TestClass] + [DeploymentItem("AroundMeTests\\Web", "Web")] + [DeploymentItem("AroundMe\\bin\\Debug\\mscorlib.debug.js", "Web")] + [DeploymentItem("AroundMe\\bin\\Debug\\AroundMe.test.js", "Web")] + public class DefaultTests { + + private static WebTest _webTest; + + public TestContext TestContext { + get; + set; + } + + [ClassInitialize()] + public static void OnInitialize(TestContext testContext) { + // This starts the web server rooted at the specified directory + // on http://localhost: + + string webRoot = Path.Combine(testContext.DeploymentDirectory, "Web"); + int port = 3976; + + _webTest = new WebTest(); + _webTest.StartWebServer(webRoot, port); + } + + [ClassCleanup()] + public static void OnCleanup() { + _webTest.StopWebServer(); + } + + [TestMethod] + public void TestMethod1() { + WebTestPageBuilder pageBuilder = new WebTestPageBuilder("DefaultTests"); + string html = + pageBuilder.AddScripts("mscorlib.debug.js", "AroundMe.test.js") + .ToHtml(); + + Uri pageUri = _webTest.CreateContent("/DefaultTests.htm", html, "text/html"); + + WebTestResult chromeResult = _webTest.RunTest(pageUri, WebBrowser.Chrome); + Assert.IsTrue(chromeResult.Succeeded, "Chrome:\r\n" + chromeResult.Log); + } + } +} diff --git a/samples/AroundMe/AroundMeTests/Properties/AssemblyInfo.cs b/samples/AroundMe/AroundMeTests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..990c6505d --- /dev/null +++ b/samples/AroundMe/AroundMeTests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AroundMeTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("AroundMeTests")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/AroundMe/AroundMeTests/Web/QUnit.css b/samples/AroundMe/AroundMeTests/Web/QUnit.css new file mode 100644 index 000000000..a9b4f5903 --- /dev/null +++ b/samples/AroundMe/AroundMeTests/Web/QUnit.css @@ -0,0 +1,112 @@ +ol#qunit-tests { + font-family:Calibri, Helvetica, Arial; + margin:0; + padding:0; + list-style-position:inside; + font-size: smaller; +} +ol#qunit-tests li{ + padding:0.4em 0.5em 0.4em 2.5em; + border-bottom:1px solid #fff; + font-size:small; + list-style-position:inside; +} +ol#qunit-tests li ol{ + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; + margin-top:0.5em; + margin-left:0; + padding:0.5em; + background-color:#fff; +} +ol#qunit-tests li li{ + border-bottom:none; + margin:0.5em; + background-color:#fff; + list-style-position: inside; + padding:0.4em 0.5em 0.4em 0.5em; +} +ol#qunit-tests li li.pass{ + border-left:26px solid #C6E746; + background-color:#fff; + color:#5E740B; +} +ol#qunit-tests li li.fail{ + border-left:26px solid #EE5757; + background-color:#fff; + color:#710909; +} +ol#qunit-tests li.pass{ + background-color:#D2E0E6; + color:#528CE0; +} +ol#qunit-tests li.fail{ + background-color:#EE5757; + color:#000; +} +ol#qunit-tests li strong { + cursor:pointer; +} +h1#qunit-header{ + background-color:#0d3349; + margin:0; + padding:0.5em 0 0.5em 1em; + color:#fff; + font-family:Calibri, Helvetica, Arial; + border-top-right-radius:15px; + border-top-left-radius:15px; + -moz-border-radius-topright:15px; + -moz-border-radius-topleft:15px; + -webkit-border-top-right-radius:15px; + -webkit-border-top-left-radius:15px; + text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; +} +h2#qunit-banner{ + font-family:Calibri, Helvetica, Arial; + height:5px; + margin:0; + padding:0; +} +h2#qunit-banner.qunit-pass{ + background-color:#C6E746; +} +h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { + background-color:#EE5757; +} +#qunit-testrunner-toolbar { + font-family:Calibri, Helvetica, Arial; + padding:0; + padding:0em 0 0.5em 2em; + font-size: small; +} +h2#qunit-userAgent { + font-family:Calibri, Helvetica, Arial; + background-color:#2b81af; + margin:0; + padding:0; + color:#fff; + font-size: small; + padding:0.5em 0 0.5em 2.5em; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} +p#qunit-testresult{ + font-family:Calibri, Helvetica, Arial; + margin:0; + font-size: small; + color:#2b81af; + border-bottom-right-radius:15px; + border-bottom-left-radius:15px; + -moz-border-radius-bottomright:15px; + -moz-border-radius-bottomleft:15px; + -webkit-border-bottom-right-radius:15px; + -webkit-border-bottom-left-radius:15px; + background-color:#D2E0E6; + padding:0.5em 0.5em 0.5em 2.5em; +} +strong b.fail{ + color:#710909; +} +strong b.pass{ + color:#5E740B; +} diff --git a/samples/AroundMe/AroundMeTests/Web/QUnit.js b/samples/AroundMe/AroundMeTests/Web/QUnit.js new file mode 100644 index 000000000..f2704148e --- /dev/null +++ b/samples/AroundMe/AroundMeTests/Web/QUnit.js @@ -0,0 +1,1043 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // Initialize the configuration options + init: function() { + config = { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + blocking: false, + autorun: false, + assertions: [], + filters: [], + queue: [] + }; + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }); + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = testName, testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = config.currentModule + " module: " + name; + } + + if ( !validTest(name) ) { + return; + } + + synchronize(function() { + QUnit.testStart( testName ); + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.appendChild(document.createTextNode(assertion.message || "(no message)")); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } + + text = text.replace(/(^\s*|\s*$)/g, ""); + + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); + + var li = document.createElement("li"); + li.className = bad ? "fail" : "pass"; + li.appendChild( b ); + li.appendChild( ol ); + tests.appendChild( li ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + push(expected != actual, actual, expected, message); + }, + + deepEqual: function(a, b, message) { + push(QUnit.equiv(a, b), a, b, message); + }, + + notDeepEqual: function(a, b, message) { + push(!QUnit.equiv(a, b), a, b, message); + }, + + strictEqual: function(actual, expected, message) { + push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + push(expected !== actual, actual, expected, message); + }, + + start: function() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function() { + if ( window.jQuery ) { + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; + }, + + // Logging callbacks + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + + QUnit.start(); +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + while ( config.queue.length && !config.blocking ) { + config.queue.shift()(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + + + // Determine what is o. + function hoozit(o) { + if (QUnit.is("String", o)) { + return "string"; + + } else if (QUnit.is("Boolean", o)) { + return "boolean"; + + } else if (QUnit.is("Number", o)) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (QUnit.is( "Array", o)) { + return "array"; + + // consider: typeof new Date() === object + } else if (QUnit.is( "Date", o)) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (QUnit.is( "RegExp", o)) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (QUnit.is( "Function", o)) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i; + var len; + + // b could be an object literal here + if ( ! (hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + for (i = 0; i < len; i++) { + if ( ! innerEquiv(a[i], b[i])) { + return false; + } + } + return true; + }, + + "object": function (b, a) { + var i; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if ( a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + + aProperties.push(i); // collect a's properties + + if ( ! innerEquiv(a[i], b[i])) { + eq = false; + break; + } + } + + callers.pop(); // unstack, we are done + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties.sort(), bProperties.sort()); + } + }; + }(); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 5/15/2008 + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] ); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + + return type == 'function' ? parser.call( this, obj ) : + type == 'string' ? parser : + this.parsers.error; + }, + typeOf:function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if (typeof obj === "undefined") { + type = "undefined"; + } else if (QUnit.is("RegExp", obj)) { + type = "regexp"; + } else if (QUnit.is("Date", obj)) { + type = "date"; + } else if (QUnit.is("Function", obj)) { + type = "function"; + } else if (QUnit.is("Array", obj)) { + type = "array"; + } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { + type = "window"; + } else if (QUnit.is("HTMLDocument", obj)) { + type = "document"; + } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { + type = "nodelist"; + } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { + type = "node"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +})(this); diff --git a/samples/AroundMe/AroundMeTests/Web/QUnitExt.js b/samples/AroundMe/AroundMeTests/Web/QUnitExt.js new file mode 100644 index 000000000..2e268d688 --- /dev/null +++ b/samples/AroundMe/AroundMeTests/Web/QUnitExt.js @@ -0,0 +1,59 @@ +(function() { + var currentModule = null; + var logData = ''; + + function appendLog(logText) { + logData = logData + logText + '\r\n'; + + var logHolder = document.getElementById('qunit-log'); + if (logHolder) { + logHolder.value = logData; + } + } + + QUnit.log = function(result, message) { + if (arguments.length === 1) { + message = arguments[0]; + } + appendLog(' ' + message); + } + + QUnit.done = function(failures, total) { + appendLog('\r\nCompleted; ' + 'failures = ' + failures + '; total = ' + total); + + var logUrl = '/log/' + ((failures === 0) ? 'success' : 'failure'); + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + window.open('', '_self', ''); + window.close(); + } + }; + xhr.open('POST', logUrl, /* async */ false); + xhr.setRequestHeader('Content-Type', 'text/plain'); + xhr.send(logData); + } + + QUnit.testStart = function(name) { + appendLog(' Test Started: ' + name); + } + + QUnit.testDone = function(name, failures, total) { + appendLog(' Test Done: ' + name + '; failures = ' + failures + '; total = ' + total); + appendLog(''); + } + + QUnit.moduleStart = function(name, testEnv) { + currentModule = name; + appendLog('Module Started: ' + name); + } + + QUnit.moduleDone = function(name, failures, total) { + if (name === currentModule) { + appendLog('Module Done: ' + name + '; failures = ' + failures + '; total = ' + total + '\r\n'); + appendLog(''); + appendLog(''); + } + currentModule = null; + } +})(); diff --git a/samples/AroundMe/AroundMeTests/packages.config b/samples/AroundMe/AroundMeTests/packages.config new file mode 100644 index 000000000..753b9e2a5 --- /dev/null +++ b/samples/AroundMe/AroundMeTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/AroundMe/MapDemo/Map.htm b/samples/AroundMe/MapDemo/Map.htm new file mode 100644 index 000000000..bb66498bb --- /dev/null +++ b/samples/AroundMe/MapDemo/Map.htm @@ -0,0 +1,19 @@ + + + + Bing Maps Demo + + +
+
+
+ +
+ + + + + + + + diff --git a/samples/AroundMe/MapDemo/MapApplication.cs b/samples/AroundMe/MapDemo/MapApplication.cs new file mode 100644 index 000000000..278df9f46 --- /dev/null +++ b/samples/AroundMe/MapDemo/MapApplication.cs @@ -0,0 +1,18 @@ +// MapApplication.cs +// + +using System; +using System.Html; + +namespace MapDemo { + + internal static class MapApplication { + + static MapApplication() { + Window.AddEventListener("load", delegate(ElementEvent e) { + MapPage page = new MapPage(); + page.Run(); + }, /* useCapture */ false); + } + } +} diff --git a/samples/AroundMe/MapDemo/MapDemo.csproj b/samples/AroundMe/MapDemo/MapDemo.csproj new file mode 100644 index 000000000..8e7e73c4a --- /dev/null +++ b/samples/AroundMe/MapDemo/MapDemo.csproj @@ -0,0 +1,59 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2176DBB5-D8FD-493C-B896-59E7283E7127} + Library + Properties + MapDemo + MapDemo + Properties\Script.template + Properties\FxCop.ruleset + True + True + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\MapDemo.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\MapDemo.xml + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.BingMaps.0.7.5.0\lib\Script.Microsoft.BingMaps.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/MapDemo/MapPage.cs b/samples/AroundMe/MapDemo/MapPage.cs new file mode 100644 index 000000000..843dac4e9 --- /dev/null +++ b/samples/AroundMe/MapDemo/MapPage.cs @@ -0,0 +1,118 @@ +// MapPage.cs +// + +using System; +using System.Collections; +using System.Html; +using Microsoft.Maps; +using Microsoft.Maps.Location; +using Microsoft.Maps.Traffic; +using Microsoft.Maps.VenueMaps; + +namespace MapDemo { + + internal sealed class MapPage { + + private Map _map; + private MapPushpin _pushpin; + private MapInfobox _infobox; + + public void Run() { + Element rootElement = Document.GetElementById("map"); + + MapOptions mapOptions = new MapOptions(); + mapOptions.Credentials = (string)rootElement.GetAttribute("data-credentials"); + mapOptions.Width = 640; + mapOptions.Height = 480; + mapOptions.ShowCopyright = false; + mapOptions.ShowMapTypeSelector = false; + mapOptions.ShowLogo = false; + mapOptions.ShowScalebar = false; + mapOptions.ShowNavControl = false; + mapOptions.ShowDashboard = false; + mapOptions.Center = new MapLocation(47.610377, -122.2006786); + mapOptions.Zoom = 10; + mapOptions.MapType = MapType.Road; + + MapPushpinOptions pushpinOptions = new MapPushpinOptions(); + _pushpin = new MapPushpin(mapOptions.Center, pushpinOptions); + + MapInfoboxOptions infoboxOptions = new MapInfoboxOptions(); + infoboxOptions.Title = "Bellevue"; + infoboxOptions.Visible = false; + infoboxOptions.Offset = new MapPoint(0, 20); + infoboxOptions.Height = 48; + infoboxOptions.Width = 80; + infoboxOptions.ShowCloseButton = false; + _infobox = new MapInfobox(mapOptions.Center, infoboxOptions); + + _map = new Map(rootElement, mapOptions); + _map.Entities.Push(_pushpin); + _map.Entities.Push(_infobox); + + MapEvents.AddHandler(_pushpin, "click", OnPushpinClick); + MapEvents.AddHandler(_map, "viewchange", OnViewChanged); + + MapModuleOptions trafficOptions = new MapModuleOptions(); + trafficOptions.Callback = delegate() { + TrafficLayer trafficLayer = new TrafficLayer(_map); + trafficLayer.Show(); + }; + Map.LoadModule(MapModule.Traffic, trafficOptions); + + MapModuleOptions venueMapOptions = new MapModuleOptions(); + venueMapOptions.Callback = delegate() { + VenueMapFactory venueMapFactory = new VenueMapFactory(_map); + + VenueMapSearchOptions searchOptions = new VenueMapSearchOptions(); + searchOptions.Map = _map; + searchOptions.Location = mapOptions.Center; + searchOptions.Radius = 1000; + searchOptions.Callback = delegate(Venue[] venues) { + if (venues.Length != 0) { + VenueMapOptions venueOptions = new VenueMapOptions(); + venueOptions.VenueMapID = venues[0].Metadata.ID; + venueOptions.SuccessCallback = delegate(VenueMap venueMap, VenueMapOptions options) { + + venueMap.Show(); + if (Script.Confirm("Zoom to " + venueMap.Name + "?")) { + _map.SetView(venueMap.BestMapView); + } + }; + + venueMapFactory.Create(venueOptions); + } + }; + + venueMapFactory.Search(searchOptions); + }; + Map.LoadModule(MapModule.VenueMaps, venueMapOptions); + + Element locateMeButton = Document.GetElementById("locateMeButton"); + locateMeButton.AddEventListener("click", OnLocateMeClick, false); + } + + private void OnLocateMeClick(ElementEvent e) { + GeoLocationProvider geoLocationProvider = new GeoLocationProvider(_map); + + GeoLocationOptions options = new GeoLocationOptions(); + options.UpdateMapView = true; + + geoLocationProvider.GetCurrentPosition(options); + } + + private void OnPushpinClick(EventArgs e) { + MapInfoboxOptions options = new MapInfoboxOptions(); + options.Visible = true; + + _infobox.SetOptions(options); + } + + private void OnViewChanged(EventArgs e) { + MapInfoboxOptions options = new MapInfoboxOptions(); + options.Visible = false; + + _infobox.SetOptions(options); + } + } +} diff --git a/samples/AroundMe/MapDemo/Properties/AssemblyInfo.cs b/samples/AroundMe/MapDemo/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..cd6b0d34b --- /dev/null +++ b/samples/AroundMe/MapDemo/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("MapDemo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("MapDemo")] diff --git a/samples/AroundMe/MapDemo/Properties/FxCop.ruleset b/samples/AroundMe/MapDemo/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/AroundMe/MapDemo/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/MapDemo/Properties/Script.template b/samples/AroundMe/MapDemo/Properties/Script.template new file mode 100644 index 000000000..d0fad56de --- /dev/null +++ b/samples/AroundMe/MapDemo/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function() { +#include[as-is] "%code%" +})(); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/AroundMe/MapDemo/packages.config b/samples/AroundMe/MapDemo/packages.config new file mode 100644 index 000000000..a7a59cc42 --- /dev/null +++ b/samples/AroundMe/MapDemo/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/Twitter/Properties/AssemblyInfo.cs b/samples/AroundMe/Twitter/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..80fc9acb6 --- /dev/null +++ b/samples/AroundMe/Twitter/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Twitter")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("Twitter")] diff --git a/samples/AroundMe/Twitter/Properties/ScriptInfo.txt b/samples/AroundMe/Twitter/Properties/ScriptInfo.txt new file mode 100644 index 000000000..802afb692 --- /dev/null +++ b/samples/AroundMe/Twitter/Properties/ScriptInfo.txt @@ -0,0 +1,12 @@ +Script Information +=============================================================================== + +This assembly provides access to script APIs. This is only meant for use at +development time, so you can reference and compile your c# code against the +script APIs. You must include the appropriate scripts in your page for +runtime functionality. + +# TODO: You can use this file to describe which version of the script +# your import library is based on, the URLs of associated scripts +# or any particular usage information for including those scripts +# into the application. diff --git a/samples/AroundMe/Twitter/Twitter.cs b/samples/AroundMe/Twitter/Twitter.cs new file mode 100644 index 000000000..291e1b475 --- /dev/null +++ b/samples/AroundMe/Twitter/Twitter.cs @@ -0,0 +1,124 @@ +// Twitter.cs +// + +// https://dev.twitter.com/anywhere/begin + +using System; +using System.Runtime.CompilerServices; + +namespace TwitterApi { + + public delegate void TwitterCallback(TwitterObject twitterObject); + + [Imported] + [IgnoreNamespace] + [ScriptName("twttr")] + public static class Twitter { + + public static void Anywhere(TwitterCallback callback) { + } + } + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class TweetBoxOptions { + + public TweetBoxOptions() { + } + + public TweetBoxOptions(params object[] nameValuePairs) { + } + + [IntrinsicProperty] + public bool Counter { + get; + set; + } + + [IntrinsicProperty] + public TweetBoxData Data { + get; + set; + } + + [IntrinsicProperty] + public string DefaultContent { + get; + set; + } + + [IntrinsicProperty] + public int Height { + get; + set; + } + + [IntrinsicProperty] + public string Label { + get; + set; + } + + [ScriptName("onTweet")] + [IntrinsicProperty] + public TweetCallback Callback { + get; + set; + } + + [IntrinsicProperty] + public int Width { + get; + set; + } + } + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class TweetBoxData { + + public TweetBoxData() { + } + + public TweetBoxData(params object[] nameValuePairs) { + } + + [ScriptName("lat")] + [IntrinsicProperty] + public double Latitude { + get; + set; + } + + [ScriptName("lon")] + [IntrinsicProperty] + public double Longitude { + get; + set; + } + } + + + public delegate void TweetCallback(string text, string htmlText); + + [Imported] + [IgnoreNamespace] + public sealed class TwitterObject { + + private TwitterObject() { + } + + public void TweetBox() { + } + + public void TweetBox(TweetBoxOptions options) { + } + + [ScriptName("")] + public TwitterObject Select(string selector) { + return null; + } + } +} diff --git a/samples/AroundMe/Twitter/Twitter.csproj b/samples/AroundMe/Twitter/Twitter.csproj new file mode 100644 index 000000000..171fb0894 --- /dev/null +++ b/samples/AroundMe/Twitter/Twitter.csproj @@ -0,0 +1,52 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {476312F9-5403-4C3A-848A-0F31213FC72E} + Library + 512 + Properties + TwitterApi + Twitter + true + + + bin\Debug\ + DEBUG + prompt + 4 + 1591, 0661, 0660, 1684 + bin\Debug\Twitter.xml + + + bin\Release\ + + + prompt + 4 + 1591, 0661, 0660, 1684 + bin\Release\Twitter.xml + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/Twitter/packages.config b/samples/AroundMe/Twitter/packages.config new file mode 100644 index 000000000..794c9675d --- /dev/null +++ b/samples/AroundMe/Twitter/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/samples/AroundMe/packages/nuget.exe b/samples/AroundMe/packages/nuget.exe new file mode 100644 index 000000000..e313ff958 Binary files /dev/null and b/samples/AroundMe/packages/nuget.exe differ diff --git a/samples/AroundMe/packages/nuget.targets b/samples/AroundMe/packages/nuget.targets new file mode 100644 index 000000000..51fc72bd2 --- /dev/null +++ b/samples/AroundMe/packages/nuget.targets @@ -0,0 +1,16 @@ + + + + false + $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory))) + $(MSBuildThisFileDirectory)nuget.exe install "$(MSBuildProjectDirectory)\packages.config" -o "$(PackagesDir)" + + RestorePackages; + $(BuildDependsOn); + + + + + + + \ No newline at end of file diff --git a/samples/AroundMe/packages/repositories.config b/samples/AroundMe/packages/repositories.config new file mode 100644 index 000000000..e003ad5ea --- /dev/null +++ b/samples/AroundMe/packages/repositories.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/FishTank/Fish.cs b/samples/FishTank/Fish.cs new file mode 100644 index 000000000..cf2e66b7a --- /dev/null +++ b/samples/FishTank/Fish.cs @@ -0,0 +1,197 @@ +// Fish.cs +// + +using System; +using System.Collections; +using System.Diagnostics; +using System.Html; +using System.Html.Media.Graphics; + +namespace FishTankApp { + + internal sealed class Fish { + + private FishTank _tank; + + private double _angle; + private double _xAngle; + private double _yAngle; + private double _zAngle; + private double _scale; + private double _velocity; + private int _flip; + private int _cellReverse; + + private int _cellIndex; + private int _species; + + private double _x; + private double _y; + private double _z; + private double _zFactor; + + private ImageElement _fishStrip; + private CanvasContext2D _context; + + public Fish(FishTank tank, double x, double y, double z, ImageElement fishStrip, CanvasContext2D context) { + _tank = tank; + + _x = x; + _y = y; + _z = z; + _zFactor = 1; + _species = Math.Floor(Math.Random() * FishTank.FishSpecies); + _cellIndex = Math.Floor(Math.Random() * (FishTank.CellsInFishStrip - 1)); + _cellReverse = -1; + + _angle = 2 * Math.PI * Math.Random(); + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle); + _zAngle = 1 - 2 * Math.Round(Math.Random()); + _scale = 0.1; + _flip = 1; + _velocity = 100; + + // Stop fish from swimming straight up or down + if ((_angle > Math.PI * 4 / 3) && (_angle < Math.PI * 5 / 3) || + (_angle > Math.PI * 1 / 3) && (_angle < Math.PI * 2 / 3)) { + _angle = Math.PI * 1 / 3 * Math.Random(); + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle); + } + + // Face the fish the right way if angle is between 6 o'clock and 12 o'clock + if ((_angle > Math.PI / 2) && (_angle < Math.PI / 2 * 3)) { + _flip = -1; + } + + _fishStrip = fishStrip; + _context = context; + } + + public void Update() { + // Calculate next position of fish + double fps = FishTank.FramesPerSecond; + _velocity = Math.Max(Math.Floor(fps * fps * .5 / 3), 1); + + double nextX = _x + _xAngle * _velocity * FishTank.TimeDelta; + double nextY = _y + _yAngle * _velocity * FishTank.TimeDelta; + double nextZ = _z + _zAngle * .1 * _velocity * FishTank.TimeDelta; + double nextScale = Math.Abs(nextZ) / (FishTank.ZFar - FishTank.ZNear); + + // If fish is going to move off right side of screen + if (nextX + FishTank.FishWidth / 2 * _scale > _tank.Width) { + // If angle is between 3 o'clock and 6 o'clock + if ((_angle >= 0 && _angle < Math.PI / 2)) { + _angle = Math.PI - _angle; + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle) * Math.Random(); + _flip = -_flip; + } + else if (_angle > Math.PI / 2 * 3) { + // If angle is between 12 o'clock and 3 o'clock + _angle = _angle - (_angle - Math.PI / 2 * 3) * 2; + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle) * Math.Random(); + _flip = -_flip; + } + } + + // If fish is going to move off left side of screen + if (nextX - FishTank.FishWidth / 2 * _scale < 0) { + if ((_angle > Math.PI / 2 && _angle < Math.PI)) { + // If angle is between 6 o'clock and 9 o'clock + _angle = Math.PI - _angle; + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle) * Math.Random(); + _flip = -_flip; + } + else if (_angle > Math.PI && _angle < Math.PI / 2 * 3) { + // If angle is between 9 o'clock and 12 o'clock + _angle = _angle + (Math.PI / 2 * 3 - _angle) * 2; + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle) * Math.Random(); + _flip = -_flip; + } + } + + // If fish is going to move off bottom side of screen + if (nextY + FishTank.FishHeight / 2 * _scale > _tank.Height) { + // If angle is between 3 o'clock and 9 o'clock + if ((_angle > 0 && _angle < Math.PI)) { + _angle = Math.PI * 2 - _angle; + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle) * Math.Random(); + } + } + + // If fish is going to move off top side of screen + if (nextY - FishTank.FishHeight / 2 * _scale < 0) { + // If angle is between 9 o'clock and 3 o'clock + if ((_angle > Math.PI && _angle < Math.PI * 2)) { + _angle = _angle - (_angle - Math.PI) * 2; + _xAngle = Math.Cos(_angle); + _yAngle = Math.Sin(_angle); + } + } + + // If fish is going too far (getting too small) + if (nextZ <= FishTank.ZNear && _zAngle < 0) { + _zAngle = -_zAngle; + } + + // If fish is getting to close (getting too large) + if (((_tank.Width / FishTank.FishWidth) * 10) < ((FishTank.FishWidth * FishTank.FishCount) / _tank.Width)) { + _zFactor = .3; + } + else if (((_tank.Width / FishTank.FishWidth) * 2) < ((FishTank.FishWidth * FishTank.FishCount) / _tank.Width)) { + _zFactor = .5; + } + else { + _zFactor = 1; + } + + if (nextZ >= FishTank.ZFar * _zFactor && _zAngle > 0) { + _zAngle = -_zAngle; + } + + if (_scale < .1) { + // Don't let fish get too tiny + _scale = .1; + } + + // Draw the fish + _context.Save(); + + // Move the fish to where it is within the fish tank + _context.Translate(_x, _y); + + // Make the fish bigger or smaller depending on how far away it is. + _context.Scale(_scale, _scale); + + // Make the fish face the way it is swimming. + _context.Transform(_flip, 0, 0, 1, 0, 0); + + _context.DrawImage(_fishStrip, + FishTank.FishWidth * _cellIndex, FishTank.FishHeight * _species, + FishTank.FishWidth, FishTank.FishHeight, + -FishTank.FishWidth / 2, -FishTank.FishHeight / 2, + FishTank.FishWidth, FishTank.FishHeight); + _context.Restore(); + + // Increment to next state + _x = nextX; + _y = nextY; + _z = nextZ; + _scale = nextScale; + + if ((_cellIndex >= FishTank.CellsInFishStrip - 1) || (_cellIndex <= 0)) { + // Go through each cell in the animation + _cellReverse = -_cellReverse; + } + + // Go back down once we hit the end of the animation + _cellIndex = _cellIndex + _cellReverse; + } + } +} diff --git a/samples/FishTank/FishSprites.png b/samples/FishTank/FishSprites.png new file mode 100644 index 000000000..490ddb428 Binary files /dev/null and b/samples/FishTank/FishSprites.png differ diff --git a/samples/FishTank/FishTank.cs b/samples/FishTank/FishTank.cs new file mode 100644 index 000000000..e2af6a170 --- /dev/null +++ b/samples/FishTank/FishTank.cs @@ -0,0 +1,138 @@ +// FishTank.cs +// + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Html; +using System.Html.Media.Graphics; + +namespace FishTankApp { + + internal sealed class FishTank { + + public const int RefreshInterval = 60; + public const int BackgroundImageWidth = 981; + public const int BackgroundImageHeight = 767; + public const int FishWidth = 307; + public const int FishHeight = 313; + public const int FishCount = 20; + public const int FishSpecies = 3; + public const int CellsInFishStrip = 16; + + public const int FramesPerSecond = 60; + public const double TimeDelta = 1.0 / FramesPerSecond; + public const double ZFar = 100; + public const double ZNear = 1; + public const double ZClose = 1; + + + private ImageElement _backgroundImageElement; + private ImageElement _imageStripElement; + + private CanvasElement _tankCanvas; + private CanvasElement _fishesCanvas; + + private CanvasContext2D _tankCanvasContext; + private CanvasContext2D _fishesCanvasContext; + + private List _fishes; + + private int _width; + private int _height; + + private Action _tickHandler; + + public int Height { + get { + return _height; + } + } + + public int Width { + get { + return _width; + } + } + + public void Run() { + _backgroundImageElement = Document.GetElementById("backgroundImage").As(); + _imageStripElement = Document.GetElementById("imageStrip").As(); + _tankCanvas = Document.GetElementById("fishTankCanvas").As(); + _fishesCanvas = Document.GetElementById("fishesCanvas").As(); + + Debug.Assert(_backgroundImageElement != null); + Debug.Assert(_imageStripElement != null); + Debug.Assert(_tankCanvas != null); + Debug.Assert(_fishesCanvas != null); + + _tankCanvasContext = (CanvasContext2D)_tankCanvas.GetContext(Rendering.Render2D); + _fishesCanvasContext = (CanvasContext2D)_fishesCanvas.GetContext(Rendering.Render2D); + + _fishes = new List(); + for (int i = 0; i < FishTank.FishCount; i++) { + double x = Math.Floor(Math.Random() * (_width - FishTank.FishWidth) + FishTank.FishWidth / 2); + double y = Math.Floor(Math.Random() * (_height - FishTank.FishHeight) + FishTank.FishHeight / 2); + double z = Math.Floor(Math.Random() * (FishTank.ZFar - FishTank.ZNear)); + + _fishes.Add(new Fish(this, x, y, z, _imageStripElement, _fishesCanvasContext)); + } + + OnWindowResize(null); + Window.AddEventListener("resize", OnWindowResize, false); + + _tickHandler = OnTick; + QueueUpdate(); + } + + private void Draw() { + if (_width < FishTank.BackgroundImageWidth) { + // Show just a portion of the background image, since the window is + // smaller than the background + _tankCanvasContext.DrawImage(_backgroundImageElement, 0, 0, FishTank.BackgroundImageWidth, _height); + } + else { + // Tile the background image horizontally if the window is wider than + // the background + + int tileCount = (int)(_width / FishTank.BackgroundImageWidth); + double flip = 1; + + for (int i = 0; i <= tileCount; i++) { + _tankCanvasContext.Save(); + _tankCanvasContext.Transform(flip, 0, 0, 1, 0, 0); + _tankCanvasContext.DrawImage(_backgroundImageElement, + (flip - 1) * FishTank.BackgroundImageWidth / 2 + flip * FishTank.BackgroundImageWidth * i, + 0, FishTank.BackgroundImageWidth, _height); + _tankCanvasContext.Restore(); + flip = -flip; + } + } + } + + private void OnTick() { + _fishesCanvasContext.ClearRect(0, 0, _width, _height); + foreach (Fish fish in _fishes) { + fish.Update(); + } + + QueueUpdate(); + } + + private void OnWindowResize(ElementEvent e) { + _width = Document.Body.OffsetWidth; + _height = Document.Body.OffsetHeight; + + _tankCanvas.Width = _width; + _tankCanvas.Height = _height; + _fishesCanvas.Width = _width; + _fishesCanvas.Height = _height; + + Draw(); + } + + private void QueueUpdate() { + Window.SetTimeout(_tickHandler, (int)(1000 / FramesPerSecond)); + } + } +} diff --git a/samples/FishTank/FishTank.csproj b/samples/FishTank/FishTank.csproj new file mode 100644 index 000000000..93e669f10 --- /dev/null +++ b/samples/FishTank/FishTank.csproj @@ -0,0 +1,65 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {A506F058-5DDB-4A1C-8DAE-ECFB59F78C4C} + Library + Properties + FishTank + FishTank + Properties\Script.template + Properties\FxCop.ruleset + True + True + True + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\FishTank.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\FishTank.xml + + + + + Designer + + + + + + + + + + packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + + \ No newline at end of file diff --git a/samples/FishTank/FishTank.htm b/samples/FishTank/FishTank.htm new file mode 100644 index 000000000..9256870a2 --- /dev/null +++ b/samples/FishTank/FishTank.htm @@ -0,0 +1,28 @@ + + + + Fish Tank + + + + + + + + + + + + + diff --git a/samples/FishTank/FishTank.jpg b/samples/FishTank/FishTank.jpg new file mode 100644 index 000000000..aa4af53cc Binary files /dev/null and b/samples/FishTank/FishTank.jpg differ diff --git a/samples/FishTank/FishTank.sln b/samples/FishTank/FishTank.sln new file mode 100644 index 000000000..76ecdfa32 --- /dev/null +++ b/samples/FishTank/FishTank.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FishTank", "FishTank.csproj", "{A506F058-5DDB-4A1C-8DAE-ECFB59F78C4C}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {A506F058-5DDB-4A1C-8DAE-ECFB59F78C4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A506F058-5DDB-4A1C-8DAE-ECFB59F78C4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A506F058-5DDB-4A1C-8DAE-ECFB59F78C4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A506F058-5DDB-4A1C-8DAE-ECFB59F78C4C}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/FishTank/FishTankApplication.cs b/samples/FishTank/FishTankApplication.cs new file mode 100644 index 000000000..28d97accb --- /dev/null +++ b/samples/FishTank/FishTankApplication.cs @@ -0,0 +1,21 @@ +// FishTankApplication.cs +// + +using System; +using System.Collections; +using System.Html; +using System.Runtime.CompilerServices; + +namespace FishTankApp { + + [GlobalMethods] + internal static class FishTankApplication { + + static FishTankApplication() { + Window.AddEventListener("load", delegate(ElementEvent e) { + FishTank fishtank = new FishTank(); + fishtank.Run(); + }, /* useCapture */ false); + } + } +} diff --git a/samples/FishTank/Properties/AssemblyInfo.cs b/samples/FishTank/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..3da5e17cf --- /dev/null +++ b/samples/FishTank/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("FishTank")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("FishTank")] +[assembly: ScriptNamespace("ft")] diff --git a/samples/FishTank/Properties/FxCop.ruleset b/samples/FishTank/Properties/FxCop.ruleset new file mode 100644 index 000000000..eedb32ead --- /dev/null +++ b/samples/FishTank/Properties/FxCop.ruleset @@ -0,0 +1,107 @@ + + + + ..\packages\ScriptSharp.FxCop.0.7.5.0\tools + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/FishTank/Properties/Script.template b/samples/FishTank/Properties/Script.template new file mode 100644 index 000000000..d0fad56de --- /dev/null +++ b/samples/FishTank/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function() { +#include[as-is] "%code%" +})(); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/FishTank/packages.config b/samples/FishTank/packages.config new file mode 100644 index 000000000..7025b00f8 --- /dev/null +++ b/samples/FishTank/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/FishTank/packages/nuget.exe b/samples/FishTank/packages/nuget.exe new file mode 100644 index 000000000..e313ff958 Binary files /dev/null and b/samples/FishTank/packages/nuget.exe differ diff --git a/samples/FishTank/packages/nuget.targets b/samples/FishTank/packages/nuget.targets new file mode 100644 index 000000000..51fc72bd2 --- /dev/null +++ b/samples/FishTank/packages/nuget.targets @@ -0,0 +1,16 @@ + + + + false + $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory))) + $(MSBuildThisFileDirectory)nuget.exe install "$(MSBuildProjectDirectory)\packages.config" -o "$(PackagesDir)" + + RestorePackages; + $(BuildDependsOn); + + + + + + + \ No newline at end of file diff --git a/samples/FishTank/packages/repositories.config b/samples/FishTank/packages/repositories.config new file mode 100644 index 000000000..0dec135fc --- /dev/null +++ b/samples/FishTank/packages/repositories.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoGrid/PhotoGrid.csproj b/samples/Photos/PhotoGrid/PhotoGrid.csproj new file mode 100644 index 000000000..0e4c98d22 --- /dev/null +++ b/samples/Photos/PhotoGrid/PhotoGrid.csproj @@ -0,0 +1,67 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2F542546-7B1D-4C43-AF1F-092AEE556FC2} + Library + Properties + PhotoGrid + PhotoGrid + Properties\Script.template + Properties\FxCop.ruleset + True + True + ..\PhotosWeb\Scripts + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\PhotoGrid.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\PhotoGrid.xml + + + + + + + + + + + ..\packages\ScriptSharp.Lib.jQuery.0.7.5.0\lib\Script.jQuery.dll + + + ..\packages\ScriptSharp.Lib.jQuery.Templating.0.7.5.0\lib\Script.jQuery.Templating.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + {336cc726-e62d-484b-a15e-3e70a676d989} + Photos + + + {a441a116-8f91-4fbc-8420-d21c98283bd3} + Plugins + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoGrid/PhotoGridPage.cs b/samples/Photos/PhotoGrid/PhotoGridPage.cs new file mode 100644 index 000000000..8396ec038 --- /dev/null +++ b/samples/Photos/PhotoGrid/PhotoGridPage.cs @@ -0,0 +1,60 @@ +// PhotoGridPage.cs +// + +using System; +using System.Collections.Generic; +using System.Html; +using System.Runtime.CompilerServices; +using jQueryApi; +using jQueryApi.Templating; +using jQueryApi.Isotope; +using jQueryApi.LightBox; +using Photos; + +namespace PhotoGrid { + + [GlobalMethods] + internal static class PhotoGridPage { + + static PhotoGridPage() { + jQuery.OnDocumentReady(delegate() { + string apiKey = (string)jQuery.FromElement(Document.Body).GetDataValue("flickrKey"); + IPhotoService flickrService = new FlickrPhotoService(); + + jQuery.Select("#searchButton").Click(delegate(jQueryEvent e) { + string tags = jQuery.Select("#tagsTextBox").GetValue(); + + flickrService.SearchPhotos(tags, 20).Done( + delegate(IEnumerable photos) { + jQueryObject thumbnailList = jQuery.Select("#thumbsList"); + thumbnailList.Empty(); + + if (photos == null) { + return; + } + + jQuery.Select("#thumbnailTemplate").Plugin(). + RenderTemplate(photos). + AppendTo(thumbnailList); + + thumbnailList. + Plugin().Isotope(new IsotopeOptions("layoutMode", IsotopeLayout.Masonry)). + Find("a"). + Plugin().LightBox(); + + thumbnailList.Find("li"). + MouseOver(delegate(jQueryEvent e2) { + jQuery.This.CSS("box-shadow", "0 0 15px #888"); + }). + MouseOut(delegate(jQueryEvent e2) { + jQuery.This.CSS("box-shadow", ""); + }); + }); + + jQuery.FromElement(Document.Body).Focus(); + e.PreventDefault(); + }); + }); + } + } +} diff --git a/samples/Photos/PhotoGrid/Properties/AssemblyInfo.cs b/samples/Photos/PhotoGrid/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..9ce779606 --- /dev/null +++ b/samples/Photos/PhotoGrid/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PhotoGrid")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("PhotoGrid")] diff --git a/samples/Photos/PhotoGrid/Properties/FxCop.ruleset b/samples/Photos/PhotoGrid/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/Photos/PhotoGrid/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoGrid/Properties/Script.template b/samples/Photos/PhotoGrid/Properties/Script.template new file mode 100644 index 000000000..8fcd90176 --- /dev/null +++ b/samples/Photos/PhotoGrid/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function($) { +#include[as-is] "%code%" +})(jQuery); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/Photos/PhotoGrid/packages.config b/samples/Photos/PhotoGrid/packages.config new file mode 100644 index 000000000..6f6f76839 --- /dev/null +++ b/samples/Photos/PhotoGrid/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoList/GalleryPlugin.cs b/samples/Photos/PhotoList/GalleryPlugin.cs new file mode 100644 index 000000000..1f27586cb --- /dev/null +++ b/samples/Photos/PhotoList/GalleryPlugin.cs @@ -0,0 +1,92 @@ +// GalleryPlugin.cs +// + +using System; +using System.Collections.Generic; +using System.Html; +using System.Runtime.CompilerServices; +using jQueryApi; +using jQueryApi.Templating; +using Photos.FlickrClient; + +[Mixin("$.fn")] +public static class GalleryPlugin { + + public static jQueryObject Gallery(GalleryPluginOptions customOptions) { + GalleryPluginOptions defaultOptions = + new GalleryPluginOptions("count", 10, + "photoService", new FlickrService()); + GalleryPluginOptions options = + (GalleryPluginOptions)jQuery.Extend(new Dictionary(), defaultOptions, customOptions); + + return jQuery.Current.Each(delegate(int i, Element element) { + jQueryObject thumbnailList = jQuery.Select("#" + options.thumbsListID); + jQueryObject photoPanel = jQuery.Select("#" + options.photoPanelID); + + options.photoService.SearchPhotos(options.tags, options.count, delegate(List photos) { + if ((photos == null) || (photos.Count == 0)) { + return; + } + + thumbnailList.Empty(); + jQuery.Select("#" + options.thumbnailTemplateID).Plugin(). + RenderTemplate(photos). + CSS("opacity", "0.5"). + AppendTo(thumbnailList). + MouseEnter(delegate(jQueryEvent e) { + jQuery.FromElement(e.CurrentTarget).FadeTo(250, 1.0f, delegate() { + jQuery.This.CSS("opacity", "1"); + }); + }) + .MouseLeave(delegate(jQueryEvent e) { + jQuery.FromElement(e.CurrentTarget).FadeTo(250, 0.5f, delegate() { + jQuery.This.CSS("opacity", "0.5"); + }); + }) + .Click(delegate(jQueryEvent e) { + PhotoResult photo = (PhotoResult)jQueryTemplating.GetTemplateInstance(e.CurrentTarget).Data; + + jQueryTemplate photoTemplate = jQueryTemplating.CreateTemplate(jQuery.Select("#" + options.photoTemplateID)); + photoPanel.FadeOut(EffectDuration.Slow, delegate() { + jQuery.This.Empty().CSS("display", ""). + Append(jQueryTemplating.RenderTemplate(photoTemplate, photo)). + FadeIn(EffectDuration.Slow); + }); + }). + Eq(0).Click(); + }); + + return false; + }); + } +} + +[Imported] +[ScriptName("Object")] +public sealed class GalleryPluginOptions { + public string tags; + public int count; + public string thumbsListID; + public string photoPanelID; + public string thumbnailTemplateID; + public string photoTemplateID; + public IFlickrService photoService; + + public GalleryPluginOptions() { + } + + public GalleryPluginOptions(params object[] nameValuePairs) { + } +} + +[Imported] +public sealed class GalleryObject : jQueryObject { + + public jQueryObject Gallery() { + return null; + } + + public jQueryObject Gallery(GalleryPluginOptions options) { + return null; + } +} diff --git a/samples/Photos/PhotoList/ListPage.cs b/samples/Photos/PhotoList/ListPage.cs new file mode 100644 index 000000000..46364bb14 --- /dev/null +++ b/samples/Photos/PhotoList/ListPage.cs @@ -0,0 +1,46 @@ +// ListPage.cs +// + +using System; +using System.Collections; +using jQueryApi; +using jQueryApi.History; + +namespace PhotoList { + + public sealed class ListPage { + + private static string _currentTags; + + public static void Main(Dictionary args) { + jQuery.Window.Bind("hashchange", delegate(jQueryEvent e) { + string tags = (string)jQueryHistory.GetState("tags"); + if (tags != _currentTags) { + ShowPhotos(tags); + } + }); + + jQuery.Select("#searchButton").Bind("click", delegate(jQueryEvent e) { + string tags = jQuery.Select("#tagsTextBox").GetValue(); + ShowPhotos(tags); + }); + } + + private static void ShowPhotos(string tags) { + if (String.IsNullOrEmpty(tags)) { + return; + } + + _currentTags = tags; + GalleryPluginOptions options = + new GalleryPluginOptions("tags", tags, + "thumbsListID", "thumbsList", + "photoPanelID", "photoPanel", + "thumbnailTemplateID", "thumbnailTemplate", + "photoTemplateID", "photoTemplate"); + + jQuery.Select("#gallery").Plugin().Gallery(options); + jQueryHistory.PushState(new Dictionary("tags", tags)); + } + } +} diff --git a/samples/Photos/PhotoList/PhotoList.csproj b/samples/Photos/PhotoList/PhotoList.csproj new file mode 100644 index 000000000..542433705 --- /dev/null +++ b/samples/Photos/PhotoList/PhotoList.csproj @@ -0,0 +1,69 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {D4DF1CC1-C044-4C90-AB09-9744E9ACFCC9} + Library + Properties + PhotoList + PhotoList + Properties\Script.template + Properties\FxCop.ruleset + True + True + ..\PhotosWeb\Scripts + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\PhotoList.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\PhotoList.xml + + + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.jQuery.0.7.5.0\lib\Script.jQuery.dll + + + ..\packages\ScriptSharp.Lib.jQuery.History.0.7.5.0\lib\Script.jQuery.History.dll + + + ..\packages\ScriptSharp.Lib.jQuery.Templating.0.7.5.0\lib\Script.jQuery.Templating.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + {336cc726-e62d-484b-a15e-3e70a676d989} + Flickr + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoList/Properties/AssemblyInfo.cs b/samples/Photos/PhotoList/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..bc8ed6482 --- /dev/null +++ b/samples/Photos/PhotoList/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PhotoList")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("Gallery")] diff --git a/samples/Photos/PhotoList/Properties/FxCop.ruleset b/samples/Photos/PhotoList/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/Photos/PhotoList/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoList/Properties/Script.template b/samples/Photos/PhotoList/Properties/Script.template new file mode 100644 index 000000000..8fcd90176 --- /dev/null +++ b/samples/Photos/PhotoList/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function($) { +#include[as-is] "%code%" +})(jQuery); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/Photos/PhotoList/Tests/GalleryPluginTests.cs b/samples/Photos/PhotoList/Tests/GalleryPluginTests.cs new file mode 100644 index 000000000..0f34bf4c8 --- /dev/null +++ b/samples/Photos/PhotoList/Tests/GalleryPluginTests.cs @@ -0,0 +1,46 @@ +// ListBuilderTests.cs +// + +using System; +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Html; +using System.Testing; +using jQueryApi; +using Photos.FlickrClient; +using Gallery; + +namespace Gallery.Tests { + + [SuppressMessage("Microsoft.Performance", "CA1812", + Justification = "Tests don't have internal consumers. They're only compiled into .test.js scripts.")] + internal sealed class GalleryPluginTests : TestClass { + + private jQueryObject _thumbsList; + + public override void Setup() { + _thumbsList = jQuery.FromHtml("
    "); + _thumbsList.AppendTo(Document.Body); + } + + public override void Cleanup() { + _thumbsList.Remove(); + _thumbsList = null; + } + + public void TestEmptyCollection() { + Assert.ExpectAsserts(1); + + GalleryPluginOptions options = new GalleryPluginOptions("thumbsListID", "thumbsList"); + options.photoService = new MockFlickrService(null); + + TestEngine.WaitForAsyncCompletion(); + + jQuery.FromElement(Document.Body).Plugin().Gallery(options); + + Assert.AreEqual(_thumbsList.GetHtml().Trim().Length, 0, "Expected html to be empty for null photos array."); + + TestEngine.ResumeOnAsyncCompleted(); + } + } +} diff --git a/samples/Photos/PhotoList/Tests/MockFlickrService.cs b/samples/Photos/PhotoList/Tests/MockFlickrService.cs new file mode 100644 index 000000000..d4512f671 --- /dev/null +++ b/samples/Photos/PhotoList/Tests/MockFlickrService.cs @@ -0,0 +1,27 @@ +// MockFlickrService.cs +// + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Html; +using System.Testing; +using Photos.FlickrClient; + +namespace Gallery.Tests { + + public sealed class MockFlickrService : IFlickrService { + + private List _photos; + + public MockFlickrService(List photos) { + _photos = photos; + } + + public void SearchPhotos(string tags, int count, FlickrSearchCallback callback) { + Window.SetTimeout(delegate() { + callback(_photos); + }, 0); + } + } +} diff --git a/samples/Photos/PhotoList/packages.config b/samples/Photos/PhotoList/packages.config new file mode 100644 index 000000000..a887b8310 --- /dev/null +++ b/samples/Photos/PhotoList/packages.config @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoListTests.testsettings b/samples/Photos/PhotoListTests.testsettings new file mode 100644 index 000000000..6047fa016 --- /dev/null +++ b/samples/Photos/PhotoListTests.testsettings @@ -0,0 +1,25 @@ + + + These are default test settings for a local test run. + + + + + + + + + + +
    +
    +
    +
    + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoListTests/GalleryPluginTests.cs b/samples/Photos/PhotoListTests/GalleryPluginTests.cs new file mode 100644 index 000000000..5d3013404 --- /dev/null +++ b/samples/Photos/PhotoListTests/GalleryPluginTests.cs @@ -0,0 +1,51 @@ +// GalleryPluginTests.cs +// + +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using ScriptSharp.Testing; + +namespace PhotoListTests { + + [TestClass] + [DeploymentItem("PhotoListTests\\Web", "Web")] + [DeploymentItem("PhotoList\\bin\\debug\\mscorlib.js", "Web")] + [DeploymentItem("PhotoList\\bin\\debug\\Photos.debug.js", "Web")] + [DeploymentItem("PhotoList\\bin\\debug\\PhotoList.test.js", "Web")] + public class GalleryPluginTests { + + private static WebTest _webTest; + + public TestContext TestContext { + get; + set; + } + + [ClassInitialize()] + public static void OnInitialize(TestContext testContext) { + // This starts the web server rooted at the specified directory + // on http://localhost: + + string webRoot = Path.Combine(testContext.DeploymentDirectory, "Web"); + int port = 3976; + + _webTest = new WebTest(); + _webTest.StartWebServer(webRoot, port); + } + + [ClassCleanup()] + public static void OnCleanup() { + _webTest.StopWebServer(); + } + + [TestMethod] + public void TestGallery() { + Uri testUri = _webTest.GetTestUri("/GalleryPluginTests.htm"); + + WebTestResult chromeResult = _webTest.RunTest(testUri, WebBrowser.Chrome); + Assert.IsTrue(chromeResult.Succeeded, "Chrome:\r\n" + chromeResult.Log); + } + } +} diff --git a/samples/Photos/PhotoListTests/PhotoListTests.csproj b/samples/Photos/PhotoListTests/PhotoListTests.csproj new file mode 100644 index 000000000..6c1972bb6 --- /dev/null +++ b/samples/Photos/PhotoListTests/PhotoListTests.csproj @@ -0,0 +1,61 @@ + + + + Debug + AnyCPU + 9.0.0 + 2.0 + {99344CB4-B164-4635-8795-D44E53357EE8} + Library + Properties + PhotoListTests + PhotoListTests + v4.0 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\ScriptSharp.Testing.0.7.5.0\lib\ScriptSharp.Testing.dll + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoListTests/Properties/AssemblyInfo.cs b/samples/Photos/PhotoListTests/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0de6e67a8 --- /dev/null +++ b/samples/Photos/PhotoListTests/Properties/AssemblyInfo.cs @@ -0,0 +1,20 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PhotoListTests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/Photos/PhotoListTests/Web/GalleryPluginTests.htm b/samples/Photos/PhotoListTests/Web/GalleryPluginTests.htm new file mode 100644 index 000000000..6edce1c10 --- /dev/null +++ b/samples/Photos/PhotoListTests/Web/GalleryPluginTests.htm @@ -0,0 +1,23 @@ + + + + PhotoListTests - GalleryPluginTests + + + + + +
    +

    PhotoListTests - GalleryPluginTests Results

    +

    +

    +
      +
      + +
      + + + + + + diff --git a/samples/Photos/PhotoListTests/Web/QUnit.css b/samples/Photos/PhotoListTests/Web/QUnit.css new file mode 100644 index 000000000..a9b4f5903 --- /dev/null +++ b/samples/Photos/PhotoListTests/Web/QUnit.css @@ -0,0 +1,112 @@ +ol#qunit-tests { + font-family:Calibri, Helvetica, Arial; + margin:0; + padding:0; + list-style-position:inside; + font-size: smaller; +} +ol#qunit-tests li{ + padding:0.4em 0.5em 0.4em 2.5em; + border-bottom:1px solid #fff; + font-size:small; + list-style-position:inside; +} +ol#qunit-tests li ol{ + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; + margin-top:0.5em; + margin-left:0; + padding:0.5em; + background-color:#fff; +} +ol#qunit-tests li li{ + border-bottom:none; + margin:0.5em; + background-color:#fff; + list-style-position: inside; + padding:0.4em 0.5em 0.4em 0.5em; +} +ol#qunit-tests li li.pass{ + border-left:26px solid #C6E746; + background-color:#fff; + color:#5E740B; +} +ol#qunit-tests li li.fail{ + border-left:26px solid #EE5757; + background-color:#fff; + color:#710909; +} +ol#qunit-tests li.pass{ + background-color:#D2E0E6; + color:#528CE0; +} +ol#qunit-tests li.fail{ + background-color:#EE5757; + color:#000; +} +ol#qunit-tests li strong { + cursor:pointer; +} +h1#qunit-header{ + background-color:#0d3349; + margin:0; + padding:0.5em 0 0.5em 1em; + color:#fff; + font-family:Calibri, Helvetica, Arial; + border-top-right-radius:15px; + border-top-left-radius:15px; + -moz-border-radius-topright:15px; + -moz-border-radius-topleft:15px; + -webkit-border-top-right-radius:15px; + -webkit-border-top-left-radius:15px; + text-shadow: rgba(0, 0, 0, 0.5) 4px 4px 1px; +} +h2#qunit-banner{ + font-family:Calibri, Helvetica, Arial; + height:5px; + margin:0; + padding:0; +} +h2#qunit-banner.qunit-pass{ + background-color:#C6E746; +} +h2#qunit-banner.qunit-fail, #qunit-testrunner-toolbar { + background-color:#EE5757; +} +#qunit-testrunner-toolbar { + font-family:Calibri, Helvetica, Arial; + padding:0; + padding:0em 0 0.5em 2em; + font-size: small; +} +h2#qunit-userAgent { + font-family:Calibri, Helvetica, Arial; + background-color:#2b81af; + margin:0; + padding:0; + color:#fff; + font-size: small; + padding:0.5em 0 0.5em 2.5em; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} +p#qunit-testresult{ + font-family:Calibri, Helvetica, Arial; + margin:0; + font-size: small; + color:#2b81af; + border-bottom-right-radius:15px; + border-bottom-left-radius:15px; + -moz-border-radius-bottomright:15px; + -moz-border-radius-bottomleft:15px; + -webkit-border-bottom-right-radius:15px; + -webkit-border-bottom-left-radius:15px; + background-color:#D2E0E6; + padding:0.5em 0.5em 0.5em 2.5em; +} +strong b.fail{ + color:#710909; +} +strong b.pass{ + color:#5E740B; +} diff --git a/samples/Photos/PhotoListTests/Web/QUnit.js b/samples/Photos/PhotoListTests/Web/QUnit.js new file mode 100644 index 000000000..f2704148e --- /dev/null +++ b/samples/Photos/PhotoListTests/Web/QUnit.js @@ -0,0 +1,1043 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2009 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var QUnit = { + + // Initialize the configuration options + init: function() { + config = { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + blocking: false, + autorun: false, + assertions: [], + filters: [], + queue: [] + }; + + var tests = id("qunit-tests"), + banner = id("qunit-banner"), + result = id("qunit-testresult"); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + }, + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + + synchronize(function() { + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + config.currentModule = name; + config.moduleTestEnvironment = testEnvironment; + config.moduleStats = { all: 0, bad: 0 }; + + QUnit.moduleStart( name, testEnvironment ); + }); + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = testName, testEnvironment, testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = config.currentModule + " module: " + name; + } + + if ( !validTest(name) ) { + return; + } + + synchronize(function() { + QUnit.testStart( testName ); + + testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, config.moduleTestEnvironment); + if (testEnvironmentArg) { + extend(testEnvironment,testEnvironmentArg); + } + + // allow utility functions to access the current test environment + QUnit.current_testEnvironment = testEnvironment; + + config.assertions = []; + config.expected = expected; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + testEnvironment.setup.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + name + ": " + e.message ); + } + + if ( async ) { + QUnit.stop(); + } + + try { + callback.call(testEnvironment); + } catch(e) { + fail("Test " + name + " died, exception and test follows", e, callback); + QUnit.ok( false, "Died on test #" + (config.assertions.length + 1) + ": " + e.message ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }); + + synchronize(function() { + try { + checkPollution(); + testEnvironment.teardown.call(testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + name + ": " + e.message ); + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + name + ", exception and reset fn follows", e, reset); + } + + if ( config.expected && config.expected != config.assertions.length ) { + QUnit.ok( false, "Expected " + config.expected + " assertions, but " + config.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += config.assertions.length; + config.moduleStats.all += config.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + ol.style.display = "none"; + + for ( var i = 0; i < config.assertions.length; i++ ) { + var assertion = config.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.appendChild(document.createTextNode(assertion.message || "(no message)")); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + var b = document.createElement("strong"); + b.innerHTML = name + " (" + bad + ", " + good + ", " + config.assertions.length + ")"; + + addEvent(b, "click", function() { + var next = b.nextSibling, display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() === "strong" ) { + var text = "", node = target.firstChild; + + while ( node.nodeType === 3 ) { + text += node.nodeValue; + node = node.nextSibling; + } + + text = text.replace(/(^\s*|\s*$)/g, ""); + + if ( window.location ) { + window.location.href = window.location.href.match(/^(.+?)(\?.*)?$/)[1] + "?" + encodeURIComponent(text); + } + } + }); + + var li = document.createElement("li"); + li.className = bad ? "fail" : "pass"; + li.appendChild( b ); + li.appendChild( ol ); + tests.appendChild( li ); + + if ( bad ) { + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "block"; + id("qunit-filter-pass").disabled = null; + id("qunit-filter-missing").disabled = null; + } + } + + } else { + for ( var i = 0; i < config.assertions.length; i++ ) { + if ( !config.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + QUnit.testDone( testName, bad, config.assertions.length ); + + if ( !window.setTimeout && !config.queue.length ) { + done(); + } + }); + + if ( window.setTimeout && !config.doneTimer ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + } + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + QUnit.log(a, msg); + + config.assertions.push({ + result: !!a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + push(expected != actual, actual, expected, message); + }, + + deepEqual: function(a, b, message) { + push(QUnit.equiv(a, b), a, b, message); + }, + + notDeepEqual: function(a, b, message) { + push(!QUnit.equiv(a, b), a, b, message); + }, + + strictEqual: function(actual, expected, message) { + push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + push(expected !== actual, actual, expected, message); + }, + + start: function() { + // A slight delay, to avoid any current callbacks + if ( window.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 13); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.blocking = true; + + if ( timeout && window.setTimeout ) { + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + */ + reset: function() { + if ( window.jQuery ) { + jQuery("#main").html( config.fixture ); + jQuery.event.global = {}; + jQuery.ajaxSettings = extend({}, config.ajaxSettings); + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return Object.prototype.toString.call( obj ) === "[object "+ type +"]"; + }, + + // Logging callbacks + done: function(failures, total) {}, + log: function(result, message) {}, + testStart: function(name) {}, + testDone: function(name, failures, total) {}, + moduleStart: function(name, testEnvironment) {}, + moduleDone: function(name, failures, total) {} +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + GETParams = location.search.slice(1).split('&'); + + for ( var i = 0; i < GETParams.length; i++ ) { + GETParams[i] = decodeURIComponent( GETParams[i] ); + if ( GETParams[i] === "noglobals" ) { + GETParams.splice( i, 1 ); + i--; + config.noglobals = true; + } else if ( GETParams[i].search('=') > -1 ) { + GETParams.splice( i, 1 ); + i--; + } + } + + // restrict modules/tests by get parameters + config.filters = GETParams; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + toolbar.style.display = "none"; + + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + filter.disabled = true; + addEvent( filter, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("pass") > -1 ) { + li[i].style.display = filter.checked ? "none" : ""; + } + } + }); + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + + var missing = document.createElement("input"); + missing.type = "checkbox"; + missing.id = "qunit-filter-missing"; + missing.disabled = true; + addEvent( missing, "click", function() { + var li = document.getElementsByTagName("li"); + for ( var i = 0; i < li.length; i++ ) { + if ( li[i].className.indexOf("fail") > -1 && li[i].innerHTML.indexOf('missing test - untested code is broken code') > - 1 ) { + li[i].parentNode.parentNode.style.display = missing.checked ? "none" : "block"; + } + } + }); + toolbar.appendChild( missing ); + + label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-missing"); + label.innerHTML = "Hide missing tests (untested code is broken code)"; + toolbar.appendChild( label ); + } + + var main = id('main'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if ( window.jQuery ) { + config.ajaxSettings = window.jQuery.ajaxSettings; + } + + QUnit.start(); +}); + +function done() { + if ( config.doneTimer && window.clearTimeout ) { + window.clearTimeout( config.doneTimer ); + config.doneTimer = null; + } + + if ( config.queue.length ) { + config.doneTimer = window.setTimeout(function(){ + if ( !config.queue.length ) { + done(); + } else { + synchronize( done ); + } + }, 13); + + return; + } + + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( config.currentModule, config.moduleStats.bad, config.moduleStats.all ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + html = ['Tests completed in ', + +new Date - config.started, ' milliseconds.
      ', + '', config.stats.all - config.stats.bad, ' tests of ', config.stats.all, ' passed, ', config.stats.bad,' failed.'].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + var result = id("qunit-testresult"); + + if ( !result ) { + result = document.createElement("p"); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests.nextSibling ); + } + + result.innerHTML = html; + } + + QUnit.done( config.stats.bad, config.stats.all ); +} + +function validTest( name ) { + var i = config.filters.length, + run = false; + + if ( !i ) { + return true; + } + + while ( i-- ) { + var filter = config.filters[i], + not = filter.charAt(0) == '!'; + + if ( not ) { + filter = filter.slice(1); + } + + if ( name.indexOf(filter) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + } + + return run; +} + +function push(result, actual, expected, message) { + message = message || (result ? "okay" : "failed"); + QUnit.ok( result, result ? message + ": " + expected : message + ", expected: " + QUnit.jsDump.parse(expected) + " result: " + QUnit.jsDump.parse(actual) ); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + while ( config.queue.length && !config.blocking ) { + config.queue.shift()(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + a[prop] = b[prop]; + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + + + // Determine what is o. + function hoozit(o) { + if (QUnit.is("String", o)) { + return "string"; + + } else if (QUnit.is("Boolean", o)) { + return "boolean"; + + } else if (QUnit.is("Number", o)) { + + if (isNaN(o)) { + return "nan"; + } else { + return "number"; + } + + } else if (typeof o === "undefined") { + return "undefined"; + + // consider: typeof null === object + } else if (o === null) { + return "null"; + + // consider: typeof [] === object + } else if (QUnit.is( "Array", o)) { + return "array"; + + // consider: typeof new Date() === object + } else if (QUnit.is( "Date", o)) { + return "date"; + + // consider: /./ instanceof Object; + // /./ instanceof RegExp; + // typeof /./ === "function"; // => false in IE and Opera, + // true in FF and Safari + } else if (QUnit.is( "RegExp", o)) { + return "regexp"; + + } else if (typeof o === "object") { + return "object"; + + } else if (QUnit.is( "Function", o)) { + return "function"; + } else { + return undefined; + } + } + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = hoozit(o); + if (prop) { + if (hoozit(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return hoozit(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return hoozit(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i; + var len; + + // b could be an object literal here + if ( ! (hoozit(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + for (i = 0; i < len; i++) { + if ( ! innerEquiv(a[i], b[i])) { + return false; + } + } + return true; + }, + + "object": function (b, a) { + var i; + var eq = true; // unless we can proove it + var aProperties = [], bProperties = []; // collection of strings + + // comparing constructors is more strict than using instanceof + if ( a.constructor !== b.constructor) { + return false; + } + + // stack constructor before traversing properties + callers.push(a.constructor); + + for (i in a) { // be strict: don't ensures hasOwnProperty and go deep + + aProperties.push(i); // collect a's properties + + if ( ! innerEquiv(a[i], b[i])) { + eq = false; + break; + } + } + + callers.pop(); // unstack, we are done + + for (i in b) { + bProperties.push(i); // collect b's properties + } + + // Ensures identical properties name + return eq && innerEquiv(aProperties.sort(), bProperties.sort()); + } + }; + }(); + + innerEquiv = function () { // can take multiple arguments + var args = Array.prototype.slice.apply(arguments); + if (args.length < 2) { + return true; // end transition + } + + return (function (a, b) { + if (a === b) { + return true; // catch the most you can + } else if (a === null || b === null || typeof a === "undefined" || typeof b === "undefined" || hoozit(a) !== hoozit(b)) { + return false; // don't lose time with error prone cases + } else { + return bindCallbacks(a, callbacks, [b, a]); + } + + // apply transition with (1..n) arguments + })(args[0], args[1]) && arguments.callee.apply(this, args.splice(1, args.length -1)); + }; + + return innerEquiv; + +}(); + +/** + * jsDump + * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com + * Licensed under BSD (http://www.opensource.org/licenses/bsd-license.php) + * Date: 5/15/2008 + * @projectDescription Advanced and extensible data dumping for Javascript. + * @version 1.0.0 + * @author Ariel Flesler + * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html} + */ +QUnit.jsDump = (function() { + function quote( str ) { + return '"' + str.toString().replace(/"/g, '\\"') + '"'; + }; + function literal( o ) { + return o + ''; + }; + function join( pre, arr, post ) { + var s = jsDump.separator(), + base = jsDump.indent(), + inner = jsDump.indent(1); + if ( arr.join ) + arr = arr.join( ',' + s + inner ); + if ( !arr ) + return pre + post; + return [ pre, inner + arr, base + post ].join(s); + }; + function array( arr ) { + var i = arr.length, ret = Array(i); + this.up(); + while ( i-- ) + ret[i] = this.parse( arr[i] ); + this.down(); + return join( '[', ret, ']' ); + }; + + var reName = /^function (\w+)/; + + var jsDump = { + parse:function( obj, type ) { //type is used mostly internally, you can fix a (custom)type in advance + var parser = this.parsers[ type || this.typeOf(obj) ]; + type = typeof parser; + + return type == 'function' ? parser.call( this, obj ) : + type == 'string' ? parser : + this.parsers.error; + }, + typeOf:function( obj ) { + var type; + if ( obj === null ) { + type = "null"; + } else if (typeof obj === "undefined") { + type = "undefined"; + } else if (QUnit.is("RegExp", obj)) { + type = "regexp"; + } else if (QUnit.is("Date", obj)) { + type = "date"; + } else if (QUnit.is("Function", obj)) { + type = "function"; + } else if (QUnit.is("Array", obj)) { + type = "array"; + } else if (QUnit.is("Window", obj) || QUnit.is("global", obj)) { + type = "window"; + } else if (QUnit.is("HTMLDocument", obj)) { + type = "document"; + } else if (QUnit.is("HTMLCollection", obj) || QUnit.is("NodeList", obj)) { + type = "nodelist"; + } else if (/^\[object HTML/.test(Object.prototype.toString.call( obj ))) { + type = "node"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
      ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + undefined:'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, this.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, this.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + this.up(); + for ( var key in map ) + ret.push( this.parse(key,'key') + ': ' + this.parse(map[key]) ); + this.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = this.HTML ? '<' : '<', + close = this.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in this.DOMAttrs ) { + var val = node[this.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + this.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:true,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +})(this); diff --git a/samples/Photos/PhotoListTests/Web/QUnitExt.js b/samples/Photos/PhotoListTests/Web/QUnitExt.js new file mode 100644 index 000000000..2e268d688 --- /dev/null +++ b/samples/Photos/PhotoListTests/Web/QUnitExt.js @@ -0,0 +1,59 @@ +(function() { + var currentModule = null; + var logData = ''; + + function appendLog(logText) { + logData = logData + logText + '\r\n'; + + var logHolder = document.getElementById('qunit-log'); + if (logHolder) { + logHolder.value = logData; + } + } + + QUnit.log = function(result, message) { + if (arguments.length === 1) { + message = arguments[0]; + } + appendLog(' ' + message); + } + + QUnit.done = function(failures, total) { + appendLog('\r\nCompleted; ' + 'failures = ' + failures + '; total = ' + total); + + var logUrl = '/log/' + ((failures === 0) ? 'success' : 'failure'); + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = function() { + if (xhr.readyState == 4) { + window.open('', '_self', ''); + window.close(); + } + }; + xhr.open('POST', logUrl, /* async */ false); + xhr.setRequestHeader('Content-Type', 'text/plain'); + xhr.send(logData); + } + + QUnit.testStart = function(name) { + appendLog(' Test Started: ' + name); + } + + QUnit.testDone = function(name, failures, total) { + appendLog(' Test Done: ' + name + '; failures = ' + failures + '; total = ' + total); + appendLog(''); + } + + QUnit.moduleStart = function(name, testEnv) { + currentModule = name; + appendLog('Module Started: ' + name); + } + + QUnit.moduleDone = function(name, failures, total) { + if (name === currentModule) { + appendLog('Module Done: ' + name + '; failures = ' + failures + '; total = ' + total + '\r\n'); + appendLog(''); + appendLog(''); + } + currentModule = null; + } +})(); diff --git a/samples/Photos/PhotoListTests/packages.config b/samples/Photos/PhotoListTests/packages.config new file mode 100644 index 000000000..753b9e2a5 --- /dev/null +++ b/samples/Photos/PhotoListTests/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoTiles/PhotoTiles.csproj b/samples/Photos/PhotoTiles/PhotoTiles.csproj new file mode 100644 index 000000000..7a54746e9 --- /dev/null +++ b/samples/Photos/PhotoTiles/PhotoTiles.csproj @@ -0,0 +1,67 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2F542547-7B1D-4C43-AF1F-092AEE556FC2} + Library + Properties + PhotoTiles + PhotoTiles + Properties\Script.template + Properties\FxCop.ruleset + True + True + ..\PhotosWeb\Scripts + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\PhotoTiles.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\PhotoTiles.xml + + + + + + + + + + + ..\packages\ScriptSharp.Lib.jQuery.0.7.5.0\lib\Script.jQuery.dll + + + ..\packages\ScriptSharp.Lib.jQuery.Templating.0.7.5.0\lib\Script.jQuery.Templating.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + {336cc726-e62d-484b-a15e-3e70a676d989} + Photos + + + {a441a116-8f91-4fbc-8420-d21c98283bd3} + Plugins + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoTiles/PhotoTilesPage.cs b/samples/Photos/PhotoTiles/PhotoTilesPage.cs new file mode 100644 index 000000000..5f2d491e0 --- /dev/null +++ b/samples/Photos/PhotoTiles/PhotoTilesPage.cs @@ -0,0 +1,81 @@ +// PhotoTilesPage.cs +// + +using System; +using System.Collections.Generic; +using System.Html; +using System.Runtime.CompilerServices; +using jQueryApi; +using jQueryApi.Templating; +using jQueryApi.Gridster; +using jQueryApi.LightBox; +using Photos; + +namespace PhotoGrid { + + [GlobalMethods] + internal static class PhotoTilesPage { + + static PhotoTilesPage() { + jQuery.OnDocumentReady(delegate() { + string apiKey = (string)jQuery.FromElement(Document.Body).GetDataValue("flickrKey"); + IPhotoService flickrService = new FlickrPhotoService(); + + jQuery.Select("#searchButton").Click(delegate(jQueryEvent e) { + string tags = jQuery.Select("#tagsTextBox").GetValue(); + + flickrService.SearchPhotos(tags, 20).Done( + delegate(IEnumerable photos) { + jQueryObject thumbnailList = jQuery.Select("#thumbsList"); + thumbnailList.Empty(); + + if (photos == null) { + return; + } + + int photoIndex = 0; + foreach (Photo photo in photos) { + if (photoIndex % 6 == 0) { + jQuery.Select("#bigThumbnailTemplate").Plugin() + .RenderTemplate(photo) + .AppendTo(thumbnailList); + } + else if ((photoIndex % 3 == 0) || (photo.imageWidth == photo.imageHeight)) { + jQuery.Select("#sqThumbnailTemplate").Plugin() + .RenderTemplate(photo) + .AppendTo(thumbnailList); + } + else if (photo.thumbnailWidth > photo.thumbnailHeight) { + jQuery.Select("#horzThumbnailTemplate").Plugin() + .RenderTemplate(photo) + .AppendTo(thumbnailList); + } + else { + jQuery.Select("#vertThumbnailTemplate").Plugin() + .RenderTemplate(photo) + .AppendTo(thumbnailList); + } + + photoIndex++; + } + + GridsterOptions gridOptions = new GridsterOptions(); + gridOptions.Margins = new int[] { 10, 10 }; + gridOptions.BaseDimensions = new int[] { 140, 140 }; + + thumbnailList.Plugin().Gridster(gridOptions) + .Find("a") + .Plugin().LightBox(); + + GridsterObject gridster = + (GridsterObject)thumbnailList.GetDataValue("gridster"); + gridster.DisableDragging(); + }); + + jQuery.FromElement(Document.Body).Focus(); + e.PreventDefault(); + }); + }); + } + } +} diff --git a/samples/Photos/PhotoTiles/Properties/AssemblyInfo.cs b/samples/Photos/PhotoTiles/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..9ce779606 --- /dev/null +++ b/samples/Photos/PhotoTiles/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PhotoGrid")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("PhotoGrid")] diff --git a/samples/Photos/PhotoTiles/Properties/FxCop.ruleset b/samples/Photos/PhotoTiles/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/Photos/PhotoTiles/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotoTiles/Properties/Script.template b/samples/Photos/PhotoTiles/Properties/Script.template new file mode 100644 index 000000000..8fcd90176 --- /dev/null +++ b/samples/Photos/PhotoTiles/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function($) { +#include[as-is] "%code%" +})(jQuery); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/Photos/PhotoTiles/packages.config b/samples/Photos/PhotoTiles/packages.config new file mode 100644 index 000000000..6f6f76839 --- /dev/null +++ b/samples/Photos/PhotoTiles/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/Photos/Photos.sln b/samples/Photos/Photos.sln new file mode 100644 index 000000000..98a205941 --- /dev/null +++ b/samples/Photos/Photos.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Photos", "Photos\Photos.csproj", "{336CC726-E62D-484B-A15E-3E70A676D989}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Plugins", "Plugins\Plugins.csproj", "{A441A116-8F91-4FBC-8420-D21C98283BD3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoGrid", "PhotoGrid\PhotoGrid.csproj", "{2F542546-7B1D-4C43-AF1F-092AEE556FC2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoList", "PhotoList\PhotoList.csproj", "{D4DF1CC1-C044-4C90-AB09-9744E9ACFCC9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotosWeb", "PhotosWeb\PhotosWeb.csproj", "{D1A2D374-10B7-45A6-A699-AFDE6AD62150}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoListTests", "PhotoListTests\PhotoListTests.csproj", "{99344CB4-B164-4635-8795-D44E53357EE8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{E27F9C93-1A3C-453A-B259-9ED71B63D332}" + ProjectSection(SolutionItems) = preProject + PhotoListTests.testsettings = PhotoListTests.testsettings + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PhotoTiles", "PhotoTiles\PhotoTiles.csproj", "{2F542547-7B1D-4C43-AF1F-092AEE556FC2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {336CC726-E62D-484B-A15E-3E70A676D989}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {336CC726-E62D-484B-A15E-3E70A676D989}.Debug|Any CPU.Build.0 = Debug|Any CPU + {336CC726-E62D-484B-A15E-3E70A676D989}.Release|Any CPU.ActiveCfg = Release|Any CPU + {336CC726-E62D-484B-A15E-3E70A676D989}.Release|Any CPU.Build.0 = Release|Any CPU + {A441A116-8F91-4FBC-8420-D21C98283BD3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A441A116-8F91-4FBC-8420-D21C98283BD3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A441A116-8F91-4FBC-8420-D21C98283BD3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A441A116-8F91-4FBC-8420-D21C98283BD3}.Release|Any CPU.Build.0 = Release|Any CPU + {2F542546-7B1D-4C43-AF1F-092AEE556FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F542546-7B1D-4C43-AF1F-092AEE556FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F542546-7B1D-4C43-AF1F-092AEE556FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F542546-7B1D-4C43-AF1F-092AEE556FC2}.Release|Any CPU.Build.0 = Release|Any CPU + {D4DF1CC1-C044-4C90-AB09-9744E9ACFCC9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D4DF1CC1-C044-4C90-AB09-9744E9ACFCC9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D4DF1CC1-C044-4C90-AB09-9744E9ACFCC9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D4DF1CC1-C044-4C90-AB09-9744E9ACFCC9}.Release|Any CPU.Build.0 = Release|Any CPU + {D1A2D374-10B7-45A6-A699-AFDE6AD62150}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1A2D374-10B7-45A6-A699-AFDE6AD62150}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1A2D374-10B7-45A6-A699-AFDE6AD62150}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1A2D374-10B7-45A6-A699-AFDE6AD62150}.Release|Any CPU.Build.0 = Release|Any CPU + {99344CB4-B164-4635-8795-D44E53357EE8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {99344CB4-B164-4635-8795-D44E53357EE8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {99344CB4-B164-4635-8795-D44E53357EE8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {99344CB4-B164-4635-8795-D44E53357EE8}.Release|Any CPU.Build.0 = Release|Any CPU + {2F542547-7B1D-4C43-AF1F-092AEE556FC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F542547-7B1D-4C43-AF1F-092AEE556FC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F542547-7B1D-4C43-AF1F-092AEE556FC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F542547-7B1D-4C43-AF1F-092AEE556FC2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/Photos/Photos/FlickrClient/Flickr.cs b/samples/Photos/Photos/FlickrClient/Flickr.cs new file mode 100644 index 000000000..80d7bc7ba --- /dev/null +++ b/samples/Photos/Photos/FlickrClient/Flickr.cs @@ -0,0 +1,38 @@ +// Flickr.cs +// + +using System; +using System.Collections.Generic; +using System.Html; +using System.Runtime.CompilerServices; + +namespace Photos.FlickrClient { + + public interface IFlickrService { + + void SearchPhotos(string tags, int count, FlickrSearchCallback callback); + } + + public sealed class FlickrService : IFlickrService { + + private const string FlickrSearchURLFormat = + "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=be9b6f66bd7a1c0c0f1465a1b7e8a764&tags={0}&per_page={1}&sort=interestingness-desc&safe_search=1&content_type=1&in_gallery=true&extras=o_dims%2Curl_sq%2Curl_m&format=json&jsoncallback={2}"; + + public void SearchPhotos(string tags, int count, FlickrSearchCallback callback) { + FlickrCallback requestCallback = delegate(PhotoSearchResponse response) { + callback(response.Photos.Results); + }; + string callbackName = Delegate.CreateExport(requestCallback); + + string url = String.Format(FlickrSearchURLFormat, tags.EncodeUriComponent(), count, callbackName); + ScriptElement script = (ScriptElement)Document.CreateElement("script"); + script.Type = "text/javascript"; + script.Src = url; + Document.GetElementsByTagName("head")[0].AppendChild(script); + } + } + + internal delegate void FlickrCallback(PhotoSearchResponse response); + + public delegate void FlickrSearchCallback(List photos); +} diff --git a/samples/Photos/Photos/FlickrClient/Json.cs b/samples/Photos/Photos/FlickrClient/Json.cs new file mode 100644 index 000000000..0d4e5736a --- /dev/null +++ b/samples/Photos/Photos/FlickrClient/Json.cs @@ -0,0 +1,39 @@ +// Json.cs +// + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using jQueryApi; + +namespace Photos.FlickrClient { + + [Imported] + public sealed class PhotoSearchResponse { + + [ScriptName("photos")] + public PhotoCollection Photos; + } + + [Imported] + public sealed class PhotoCollection { + + [ScriptName("photo")] + public List Results; + } + + [Imported] + public sealed class PhotoResult { + + public string ID; + public string Owner; + public string Title; + public string Url_sq; + public string Url_m; + public string Url_t; + public string Height_m; + public string Width_m; + public string Height_t; + public string Width_t; + } +} diff --git a/samples/Photos/Photos/FlickrPhotoService.cs b/samples/Photos/Photos/FlickrPhotoService.cs new file mode 100644 index 000000000..5a929a3dd --- /dev/null +++ b/samples/Photos/Photos/FlickrPhotoService.cs @@ -0,0 +1,60 @@ +// FlickrPhotoService.cs +// + +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using jQueryApi; +using Photos.FlickrClient; + +namespace Photos { + + public sealed class FlickrPhotoService : IPhotoService { + + private static string SearchUriFormat = "http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key={0}&tags={1}&sort=interestingness-desc&safe_search=1&extras=o_dims%2Curl_sq%2Curl_m%2Curl_t&per_page={2}&format=json"; + private static string Base58Alphabet = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"; + private string _apiKey; + + public FlickrPhotoService() { + _apiKey = "be9b6f66bd7a1c0c0f1465a1b7e8a764"; + } + + private static string Base58Encode(string id) { + int number = Int32.Parse(id); + + string encoded = ""; + while (number > 0) { + int remainder = number % Base58Alphabet.Length; + number = (int)(number / Base58Alphabet.Length); + encoded = Base58Alphabet.CharAt(remainder) + encoded; + } + + return encoded; + } + + public IDeferred> SearchPhotos(string tags, int count) { + jQueryAjaxOptions ajaxOptions = + new jQueryAjaxOptions("url", String.Format(SearchUriFormat, _apiKey, tags.EncodeUriComponent(), count), + "jsonp", "jsoncallback", + "dataType", "jsonp"); + return jQuery.AjaxRequest(ajaxOptions).Pipe>( + delegate(PhotoSearchResponse response) { + List photos = + response.Photos.Results.Map(delegate(PhotoResult photoResult) { + return new Photo(photoResult.ID, + photoResult.Title, + "http://flic.kr/p/" + Base58Encode(photoResult.ID), + photoResult.Url_m, + photoResult.Url_t, + Int32.Parse(photoResult.Width_m), + Int32.Parse(photoResult.Height_m), + Int32.Parse(photoResult.Width_t), + Int32.Parse(photoResult.Height_t), + photoResult.Url_sq); + }); + + return photos; + }); + } + } +} diff --git a/samples/Photos/Photos/IPhotoService.cs b/samples/Photos/Photos/IPhotoService.cs new file mode 100644 index 000000000..860a14682 --- /dev/null +++ b/samples/Photos/Photos/IPhotoService.cs @@ -0,0 +1,14 @@ +// IPhotoService.cs +// + +using System; +using System.Collections.Generic; +using jQueryApi; + +namespace Photos { + + public interface IPhotoService { + + IDeferred> SearchPhotos(string tags, int count); + } +} diff --git a/samples/Photos/Photos/Photo.cs b/samples/Photos/Photos/Photo.cs new file mode 100644 index 000000000..18875f0ed --- /dev/null +++ b/samples/Photos/Photos/Photo.cs @@ -0,0 +1,41 @@ +// Photo.cs +// + +using System; + +namespace Photos { + + /// + /// Represents a geo-tagged photo along with various bits of metadata. + /// + public sealed class Photo : Record { + + public string id; + + public string title; + + public string url; + public string thumbnailUrl; + public string imageUrl; + public string squareImageUrl; + + public int imageWidth; + public int imageHeight; + + public int thumbnailWidth; + public int thumbnailHeight; + + public Photo(string id, string title, string url, string imageUrl, string thumbnailUrl, int imageWidth, int imageHeight, int thumbnailWidth, int thumbnailHeight, string squareImageUrl) { + this.id = id; + this.title = title; + this.url = url; + this.imageUrl = imageUrl; + this.imageWidth = imageWidth; + this.imageHeight = imageHeight; + this.thumbnailWidth = thumbnailWidth; + this.thumbnailHeight = thumbnailHeight; + this.thumbnailUrl = thumbnailUrl; + this.squareImageUrl = squareImageUrl; + } + } +} diff --git a/samples/Photos/Photos/Photos.csproj b/samples/Photos/Photos/Photos.csproj new file mode 100644 index 000000000..b67740215 --- /dev/null +++ b/samples/Photos/Photos/Photos.csproj @@ -0,0 +1,58 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {336CC726-E62D-484B-A15E-3E70A676D989} + Library + Properties + Photos + Photos + Properties\Script.template + Properties\FxCop.ruleset + True + True + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Debug\Photos.xml + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + bin\Release\Photos.xml + + + + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.jQuery.0.7.5.0\lib\Script.jQuery.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + + + \ No newline at end of file diff --git a/samples/Photos/Photos/Properties/AssemblyInfo.cs b/samples/Photos/Photos/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..b22746978 --- /dev/null +++ b/samples/Photos/Photos/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Flickr")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("Flickr")] diff --git a/samples/Photos/Photos/Properties/FxCop.ruleset b/samples/Photos/Photos/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/Photos/Photos/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/Photos/Properties/Script.template b/samples/Photos/Photos/Properties/Script.template new file mode 100644 index 000000000..d0fad56de --- /dev/null +++ b/samples/Photos/Photos/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function() { +#include[as-is] "%code%" +})(); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/Photos/Photos/packages.config b/samples/Photos/Photos/packages.config new file mode 100644 index 000000000..8b5e6e6e7 --- /dev/null +++ b/samples/Photos/Photos/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotosWeb/Content/Images/lightbox-blank.gif b/samples/Photos/PhotosWeb/Content/Images/lightbox-blank.gif new file mode 100644 index 000000000..1d11fa9ad Binary files /dev/null and b/samples/Photos/PhotosWeb/Content/Images/lightbox-blank.gif differ diff --git a/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-close.gif b/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-close.gif new file mode 100644 index 000000000..33bcf517a Binary files /dev/null and b/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-close.gif differ diff --git a/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-next.gif b/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-next.gif new file mode 100644 index 000000000..a0d4fcf84 Binary files /dev/null and b/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-next.gif differ diff --git a/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-prev.gif b/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-prev.gif new file mode 100644 index 000000000..040ee5992 Binary files /dev/null and b/samples/Photos/PhotosWeb/Content/Images/lightbox-btn-prev.gif differ diff --git a/samples/Photos/PhotosWeb/Content/Images/lightbox-ico-loading.gif b/samples/Photos/PhotosWeb/Content/Images/lightbox-ico-loading.gif new file mode 100644 index 000000000..4f1429c06 Binary files /dev/null and b/samples/Photos/PhotosWeb/Content/Images/lightbox-ico-loading.gif differ diff --git a/samples/Photos/PhotosWeb/Content/jquery.gridster.css b/samples/Photos/PhotosWeb/Content/jquery.gridster.css new file mode 100644 index 000000000..3f3fad042 --- /dev/null +++ b/samples/Photos/PhotosWeb/Content/jquery.gridster.css @@ -0,0 +1,64 @@ +/*! gridster.js - v0.1.0 - 2012-08-20 +* http://gridster.net/ +* Copyright (c) 2012 ducksboard; Licensed MIT */ + +.gridster { + position:relative; +} + +.gridster > * { + margin: 0 auto; + -webkit-transition: height .4s; + -moz-transition: height .4s; + -o-transition: height .4s; + -ms-transition: height .4s; + transition: height .4s; +} + +.gridster .gs_w{ + z-index: 2; + position: absolute; +} + +.ready .gs_w:not(.preview-holder) { + -webkit-transition: opacity .3s, left .3s, top .3s; + -moz-transition: opacity .3s, left .3s, top .3s; + -o-transition: opacity .3s, left .3s, top .3s; + transition: opacity .3s, left .3s, top .3s; +} + +.ready .gs_w:not(.preview-holder) { + -webkit-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -moz-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + -o-transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; + transition: opacity .3s, left .3s, top .3s, width .3s, height .3s; +} + +.gridster .preview-holder { + z-index: 1; + position: absolute; + background-color: #fff; + border-color: #fff; + opacity: 0.3; +} + +.gridster .player-revert { + z-index: 10!important; + -webkit-transition: left .3s, top .3s!important; + -moz-transition: left .3s, top .3s!important; + -o-transition: left .3s, top .3s!important; + transition: left .3s, top .3s!important; +} + +.gridster .dragging { + z-index: 10!important; + -webkit-transition: all 0s !important; + -moz-transition: all 0s !important; + -o-transition: all 0s !important; + transition: all 0s !important; +} + +/* Uncomment this if you set helper : "clone" in draggable options */ +/*.gridster .player { + opacity:0; +}*/ diff --git a/samples/Photos/PhotosWeb/Content/jquery.lightbox-0.5.css b/samples/Photos/PhotosWeb/Content/jquery.lightbox-0.5.css new file mode 100644 index 000000000..62618a8f3 --- /dev/null +++ b/samples/Photos/PhotosWeb/Content/jquery.lightbox-0.5.css @@ -0,0 +1,101 @@ +/** + * jQuery lightBox plugin + * This jQuery plugin was inspired and based on Lightbox 2 by Lokesh Dhakar (http://www.huddletogether.com/projects/lightbox2/) + * and adapted to me for use like a plugin from jQuery. + * @name jquery-lightbox-0.5.css + * @author Leandro Vieira Pinho - http://leandrovieira.com + * @version 0.5 + * @date April 11, 2008 + * @category jQuery plugin + * @copyright (c) 2008 Leandro Vieira Pinho (leandrovieira.com) + * @license CCAttribution-ShareAlike 2.5 Brazil - http://creativecommons.org/licenses/by-sa/2.5/br/deed.en_US + * @example Visit http://leandrovieira.com/projects/jquery/lightbox/ for more informations about this jQuery plugin + */ +#jquery-overlay { + position: absolute; + top: 0; + left: 0; + z-index: 90; + width: 100%; + height: 500px; +} +#jquery-lightbox { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 100; + text-align: center; + line-height: 0; +} +#jquery-lightbox a img { border: none; } +#lightbox-container-image-box { + position: relative; + background-color: #fff; + width: 250px; + height: 250px; + margin: 0 auto; +} +#lightbox-container-image { padding: 10px; } +#lightbox-loading { + position: absolute; + top: 40%; + left: 0%; + height: 25%; + width: 100%; + text-align: center; + line-height: 0; +} +#lightbox-nav { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + z-index: 10; +} +#lightbox-container-image-box > #lightbox-nav { left: 0; } +#lightbox-nav a { outline: none;} +#lightbox-nav-btnPrev, #lightbox-nav-btnNext { + width: 49%; + height: 100%; + zoom: 1; + display: block; +} +#lightbox-nav-btnPrev { + left: 0; + float: left; +} +#lightbox-nav-btnNext { + right: 0; + float: right; +} +#lightbox-container-image-data-box { + font: 10px Verdana, Helvetica, sans-serif; + background-color: #fff; + margin: 0 auto; + line-height: 1.4em; + overflow: auto; + width: 100%; + padding: 0 10px 0; +} +#lightbox-container-image-data { + padding: 0 10px; + color: #666; +} +#lightbox-container-image-data #lightbox-image-details { + width: 70%; + float: left; + text-align: left; +} +#lightbox-image-details-caption { font-weight: bold; } +#lightbox-image-details-currentNumber { + display: block; + clear: left; + padding-bottom: 1.0em; +} +#lightbox-secNav-btnClose { + width: 66px; + float: right; + padding-bottom: 0.7em; +} \ No newline at end of file diff --git a/samples/Photos/PhotosWeb/PhotoGrid.htm b/samples/Photos/PhotosWeb/PhotoGrid.htm new file mode 100644 index 000000000..67c45f7e6 --- /dev/null +++ b/samples/Photos/PhotosWeb/PhotoGrid.htm @@ -0,0 +1,39 @@ + + + + Photo Grid + + + + +
      + +
      +
      +
        + + + + + + + + + + diff --git a/samples/Photos/PhotosWeb/PhotoList.htm b/samples/Photos/PhotosWeb/PhotoList.htm new file mode 100644 index 000000000..3182cb667 --- /dev/null +++ b/samples/Photos/PhotosWeb/PhotoList.htm @@ -0,0 +1,48 @@ + + + + Photo List + + + +
        +
        + + +
        + + + + + + + + + + + + + diff --git a/samples/Photos/PhotosWeb/PhotoTiles.htm b/samples/Photos/PhotosWeb/PhotoTiles.htm new file mode 100644 index 000000000..b92ff11a6 --- /dev/null +++ b/samples/Photos/PhotosWeb/PhotoTiles.htm @@ -0,0 +1,61 @@ + + + + Photo Tiles + + + + + +
        + +
        +
        +
        +
          +
          + + + + + + + + + + + + + diff --git a/samples/Photos/PhotosWeb/PhotosWeb.csproj b/samples/Photos/PhotosWeb/PhotosWeb.csproj new file mode 100644 index 000000000..32c3a0488 --- /dev/null +++ b/samples/Photos/PhotosWeb/PhotosWeb.csproj @@ -0,0 +1,112 @@ + + + + + Debug + AnyCPU + + + 2.0 + {D1A2D374-10B7-45A6-A699-AFDE6AD62150} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + PhotosWeb + PhotosWeb + v4.0 + true + + + + + + true + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + + True + True + 0 + / + http://localhost:19223/ + False + False + + + False + + + + + + \ No newline at end of file diff --git a/samples/Photos/PhotosWeb/Properties/AssemblyInfo.cs b/samples/Photos/PhotosWeb/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..61372cb21 --- /dev/null +++ b/samples/Photos/PhotosWeb/Properties/AssemblyInfo.cs @@ -0,0 +1,17 @@ +// AssemblyInfo.cs +// + +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +[assembly: AssemblyTitle("PhotosWeb")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/Photos/PhotosWeb/Scripts/jquery.gridster.js b/samples/Photos/PhotosWeb/Scripts/jquery.gridster.js new file mode 100644 index 000000000..9193da624 --- /dev/null +++ b/samples/Photos/PhotosWeb/Scripts/jquery.gridster.js @@ -0,0 +1,3148 @@ +/*! gridster.js - v0.1.0 - 2012-08-20 +* http://gridster.net/ +* Copyright (c) 2012 ducksboard; Licensed MIT */ + +;(function($, window, document, undefined){ + /** + * Creates objects with coordinates (x1, y1, x2, y2, cx, cy, width, height) + * to simulate DOM elements on the screen. + * Coords is used by Gridster to create a faux grid with any DOM element can + * collide. + * + * @class Coords + * @param {HTMLElement|Object} obj The jQuery HTMLElement or a object with: left, + * top, width and height properties. + * @return {Object} Coords instance. + * @constructor + */ + function Coords(obj) { + if (obj[0] && $.isPlainObject(obj[0])) { + this.data = obj[0]; + }else { + this.el = obj; + } + + this.isCoords = true; + this.coords = {}; + this.init(); + return this; + } + + + var fn = Coords.prototype; + + + fn.init = function(){ + this.set(); + this.original_coords = this.get(); + }; + + + fn.set = function(update, not_update_offsets) { + var el = this.el; + + if (el && !update) { + this.data = el.offset(); + this.data.width = el.width(); + this.data.height = el.height(); + } + + if (el && update && !not_update_offsets) { + var offset = el.offset(); + this.data.top = offset.top; + this.data.left = offset.left; + } + + var d = this.data; + + this.coords.x1 = d.left; + this.coords.y1 = d.top; + this.coords.x2 = d.left + d.width; + this.coords.y2 = d.top + d.height; + this.coords.cx = d.left + (d.width / 2); + this.coords.cy = d.top + (d.height / 2); + this.coords.width = d.width; + this.coords.height = d.height; + this.coords.el = el || false ; + + return this; + }; + + + fn.update = function(data){ + if (!data && !this.el) { + return this; + } + + if (data) { + var new_data = $.extend({}, this.data, data); + this.data = new_data; + return this.set(true, true); + } + + this.set(true); + return this; + }; + + + fn.get = function(){ + return this.coords; + }; + + + //jQuery adapter + $.fn.coords = function() { + if (this.data('coords') ) { + return this.data('coords'); + } + + var ins = new Coords(this, arguments[0]); + this.data('coords', ins); + return ins; + }; + +}(jQuery, window, document)); + +;(function($, window, document, undefined){ + + var defaults = { + colliders_context: document.body + // ,on_overlap: function(collider_data){}, + // on_overlap_start : function(collider_data){}, + // on_overlap_stop : function(collider_data){} + }; + + + /** + * Detects collisions between a DOM element against other DOM elements or + * Coords objects. + * + * @class Collision + * @uses Coords + * @param {HTMLElement} el The jQuery wrapped HTMLElement. + * @param {HTMLElement|Array} colliders Can be a jQuery collection + * of HTMLElements or an Array of Coords instances. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {Function} [options.on_overlap_start] Executes a function the first + * time each `collider ` is overlapped. + * @param {Function} [options.on_overlap_stop] Executes a function when a + * `collider` is no longer collided. + * @param {Function} [options.on_overlap] Executes a function when the + * mouse is moved during the collision. + * @return {Object} Collision instance. + * @constructor + */ + function Collision(el, colliders, options) { + this.options = $.extend(defaults, options); + this.$element = el; + this.last_colliders = []; + this.last_colliders_coords = []; + if (typeof colliders === 'string' || colliders instanceof jQuery) { + this.$colliders = $(colliders, + this.options.colliders_context).not(this.$element); + }else{ + this.colliders = $(colliders); + } + + this.init(); + } + + + var fn = Collision.prototype; + + + fn.init = function() { + this.find_collisions(); + }; + + + fn.overlaps = function(a, b) { + var x = false; + var y = false; + + if ((b.x1 >= a.x1 && b.x1 <= a.x2) || + (b.x2 >= a.x1 && b.x2 <= a.x2) || + (a.x1 >= b.x1 && a.x2 <= b.x2) + ) { x = true; } + + if ((b.y1 >= a.y1 && b.y1 <= a.y2) || + (b.y2 >= a.y1 && b.y2 <= a.y2) || + (a.y1 >= b.y1 && a.y2 <= b.y2) + ) { y = true; } + + return (x && y); + }; + + + fn.detect_overlapping_region = function(a, b){ + var regionX = ''; + var regionY = ''; + + if (a.y1 > b.cy && a.y1 < b.y2) { regionX = 'N'; } + if (a.y2 > b.y1 && a.y2 < b.cy) { regionX = 'S'; } + if (a.x1 > b.cx && a.x1 < b.x2) { regionY = 'W'; } + if (a.x2 > b.x1 && a.x2 < b.cx) { regionY = 'E'; } + + return (regionX + regionY) || 'C'; + }; + + + fn.calculate_overlapped_area_coords = function(a, b){ + var x1 = Math.max(a.x1, b.x1); + var y1 = Math.max(a.y1, b.y1); + var x2 = Math.min(a.x2, b.x2); + var y2 = Math.min(a.y2, b.y2); + + return $({ + left: x1, + top: y1, + width : (x2 - x1), + height: (y2 - y1) + }).coords().get(); + }; + + + fn.calculate_overlapped_area = function(coords){ + return (coords.width * coords.height); + }; + + + fn.manage_colliders_start_stop = function(new_colliders_coords, start_callback, stop_callback){ + var last = this.last_colliders_coords; + + for (var i = 0, il = last.length; i < il; i++) { + if ($.inArray(last[i], new_colliders_coords) === -1) { + start_callback.call(this, last[i]); + } + } + + for (var j = 0, jl = new_colliders_coords.length; j < jl; j++) { + if ($.inArray(new_colliders_coords[j], last) === -1) { + stop_callback.call(this, new_colliders_coords[j]); + } + + } + }; + + + fn.find_collisions = function(player_data_coords){ + var self = this; + var colliders_coords = []; + var colliders_data = []; + var $colliders = (this.colliders || this.$colliders); + var count = $colliders.length; + var player_coords = self.$element.coords() + .update(player_data_coords || false).get(); + + while(count--){ + var $collider = self.$colliders ? + $($colliders[count]) : $colliders[count]; + var $collider_coords_ins = ($collider.isCoords) ? + $collider : $collider.coords(); + var collider_coords = $collider_coords_ins.get(); + var overlaps = self.overlaps(player_coords, collider_coords); + + if (!overlaps) { + continue; + } + + var region = self.detect_overlapping_region( + player_coords, collider_coords); + + //todo: make this an option + if (region === 'C'){ + var area_coords = self.calculate_overlapped_area_coords( + player_coords, collider_coords); + var area = self.calculate_overlapped_area(area_coords); + var collider_data = { + area: area, + area_coords : area_coords, + region: region, + coords: collider_coords, + player_coords: player_coords, + el: $collider + }; + + if (self.options.on_overlap) { + self.options.on_overlap.call(this, collider_data); + } + colliders_coords.push($collider_coords_ins); + colliders_data.push(collider_data); + } + } + + if (self.options.on_overlap_stop || self.options.on_overlap_start) { + this.manage_colliders_start_stop(colliders_coords, + self.options.on_overlap_stop, self.options.on_overlap_start); + } + + this.last_colliders_coords = colliders_coords; + + return colliders_data; + }; + + + fn.get_closest_colliders = function(player_data_coords){ + var colliders = this.find_collisions(player_data_coords); + var min_area = 100; + colliders.sort(function(a, b){ + if (a.area <= min_area) { + return 1; + } + + /* if colliders are being overlapped by the "C" (center) region, + * we have to set a lower index in the array to which they are placed + * above in the grid. */ + if (a.region === 'C' && b.region === 'C') { + if (a.coords.y1 < b.coords.y1 || a.coords.x1 < b.coords.x1) { + return - 1; + }else{ + return 1; + } + } + + if (a.area < b.area){ + return 1; + } + + return 1; + }); + return colliders; + }; + + + //jQuery adapter + $.fn.collision = function(collider, options) { + return new Collision( this, collider, options ); + }; + + +}(jQuery, window, document)); + +;(function(window, undefined) { + /* Debounce and throttle functions taken from underscore.js */ + window.debounce = function(func, wait, immediate) { + var timeout; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) func.apply(context, args); + }; + if (immediate && !timeout) func.apply(context, args); + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; + }; + + + window.throttle = function(func, wait) { + var context, args, timeout, throttling, more, result; + var whenDone = debounce( + function(){ more = throttling = false; }, wait); + return function() { + context = this; args = arguments; + var later = function() { + timeout = null; + if (more) func.apply(context, args); + whenDone(); + }; + if (!timeout) timeout = setTimeout(later, wait); + if (throttling) { + more = true; + } else { + result = func.apply(context, args); + } + whenDone(); + throttling = true; + return result; + }; + }; + +})(window); + +;(function($, window, document, undefined){ + + var defaults = { + items: '.gs_w', + distance: 1, + limit: true, + offset_left: 0, + autoscroll: true + // ,drag: function(e){}, + // start : function(e, ui){}, + // stop : function(e){} + }; + + var $window = $(window); + var isTouch = !!('ontouchstart' in window); + var pointer_events = { + start: isTouch ? 'touchstart' : 'mousedown.draggable', + move: isTouch ? 'touchmove' : 'mousemove.draggable', + end: isTouch ? 'touchend' : 'mouseup.draggable' + }; + + /** + * Basic drag implementation for DOM elements inside a container. + * Provide start/stop/drag callbacks. + * + * @class Draggable + * @param {HTMLElement} el The HTMLelement that contains all the widgets + * to be dragged. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.items] Define who will + * be the draggable items. Can be a CSS Selector String or a + * collection of HTMLElements. + * @param {Number} [options.distance] Distance in pixels after mousedown + * the mouse must move before dragging should start. + * @param {Boolean} [options.limit] Constrains dragging to the width of + * the container + * @param {offset_left} [options.offset_left] Offset added to the item + * that is being dragged. + * @param {Number} [options.drag] Executes a callback when the mouse is + * moved during the dragging. + * @param {Number} [options.start] Executes a callback when the drag + * starts. + * @param {Number} [options.stop] Executes a callback when the drag stops. + * @return {Object} Returns `el`. + * @constructor + */ + function Draggable(el, options) { + this.options = $.extend({}, defaults, options); + this.$body = $(document.body); + this.$container = $(el); + this.$dragitems = $(this.options.items, this.$container); + this.is_dragging = false; + this.player_min_left = 0 + this.options.offset_left; + this.init(); + } + + var fn = Draggable.prototype; + + fn.init = function() { + this.calculate_positions(); + this.$container.css('position', 'relative'); + this.enable(); + + $(window).bind('resize', + throttle($.proxy(this.calculate_positions, this), 200)); + }; + + + fn.get_actual_pos = function($el) { + var pos = $el.position(); + return pos; + }; + + + fn.get_mouse_pos = function(e) { + if (isTouch) { + var oe = e.originalEvent; + e = oe.touches.length ? oe.touches[0] : oe.changedTouches[0]; + }; + + return { + left: e.clientX, + top: e.clientY + }; + }; + + + fn.get_offset = function(e) { + e.preventDefault(); + var mouse_actual_pos = this.get_mouse_pos(e); + var diff_x = Math.round( + mouse_actual_pos.left - this.mouse_init_pos.left); + var diff_y = Math.round(mouse_actual_pos.top - this.mouse_init_pos.top); + + var left = Math.round(this.el_init_offset.left + diff_x - this.baseX); + var top = Math.round( + this.el_init_offset.top + diff_y - this.baseY + this.scrollOffset); + + if (this.options.limit) { + if (left > this.player_max_left) { + left = this.player_max_left; + }else if(left < this.player_min_left) { + left = this.player_min_left; + } + } + + return { + left: left, + top: top, + mouse_left: mouse_actual_pos.left, + mouse_top: mouse_actual_pos.top + }; + }; + + + fn.manage_scroll = function(offset) { + /* scroll document */ + var nextScrollTop; + var scrollTop = $window.scrollTop(); + var min_window_y = scrollTop; + var max_window_y = min_window_y + this.window_height; + + var mouse_down_zone = max_window_y - 50; + var mouse_up_zone = min_window_y + 50; + + var abs_mouse_left = offset.mouse_left; + var abs_mouse_top = min_window_y + offset.mouse_top; + + var max_player_y = (this.doc_height - this.window_height + + this.player_height); + + if (abs_mouse_top >= mouse_down_zone) { + nextScrollTop = scrollTop + 30; + if (nextScrollTop < max_player_y) { + $window.scrollTop(nextScrollTop); + this.scrollOffset = this.scrollOffset + 30; + } + }; + + if (abs_mouse_top <= mouse_up_zone) { + nextScrollTop = scrollTop - 30; + if (nextScrollTop > 0) { + $window.scrollTop(nextScrollTop); + this.scrollOffset = this.scrollOffset - 30; + } + }; + } + + + fn.calculate_positions = function(e) { + this.window_height = $window.height(); + } + + + fn.drag_handler = function(e) { + var node = e.target.nodeName; + + if (e.which !== 1 && !isTouch) { + return; + } + + if (node === 'INPUT' || node === 'TEXTAREA' || node === 'SELECT' || + node === 'BUTTON') { + return; + }; + + var self = this; + var first = true; + this.$player = $(e.currentTarget); + + this.el_init_pos = this.get_actual_pos(this.$player); + this.mouse_init_pos = this.get_mouse_pos(e); + this.offsetY = this.mouse_init_pos.top - this.el_init_pos.top; + + this.$body.on(pointer_events.move, function(mme){ + var mouse_actual_pos = self.get_mouse_pos(mme); + var diff_x = Math.abs( + mouse_actual_pos.left - self.mouse_init_pos.left); + var diff_y = Math.abs( + mouse_actual_pos.top - self.mouse_init_pos.top); + if (!(diff_x > self.options.distance || + diff_y > self.options.distance) + ) { + return false; + } + + if (first) { + first = false; + self.on_dragstart.call(self, mme); + return false; + } + + if (self.is_dragging == true) { + self.on_dragmove.call(self, mme); + } + + return false; + }); + + return false; + }; + + + fn.on_dragstart = function(e) { + e.preventDefault(); + this.drag_start = true; + this.is_dragging = true; + var offset = this.$container.offset(); + this.baseX = Math.round(offset.left); + this.baseY = Math.round(offset.top); + this.doc_height = $(document).height(); + + if (this.options.helper === 'clone') { + this.$helper = this.$player.clone() + .appendTo(this.$container).addClass('helper'); + this.helper = true; + }else{ + this.helper = false; + } + this.scrollOffset = 0; + this.el_init_offset = this.$player.offset(); + this.player_width = this.$player.width(); + this.player_height = this.$player.height(); + this.player_max_left = (this.$container.width() - this.player_width + + this.options.offset_left); + + if (this.options.start) { + this.options.start.call(this.$player, e, { + helper: this.helper ? this.$helper : this.$player + }); + } + return false; + }; + + + fn.on_dragmove = function(e) { + var offset = this.get_offset(e); + + this.options.autoscroll && this.manage_scroll(offset); + + (this.helper ? this.$helper : this.$player).css({ + 'position': 'absolute', + 'left' : offset.left, + 'top' : offset.top + }); + + var ui = { + 'position': { + 'left': offset.left, + 'top': offset.top + } + }; + + if (this.options.drag) { + this.options.drag.call(this.$player, e, ui); + } + return false; + }; + + + fn.on_dragstop = function(e) { + var offset = this.get_offset(e); + this.drag_start = false; + + var ui = { + 'position': { + 'left': offset.left, + 'top': offset.top + } + }; + + if (this.options.stop) { + this.options.stop.call(this.$player, e, ui); + } + + if (this.helper) { + this.$helper.remove(); + } + + return false; + }; + + fn.on_select_start = function(e) { + return false; + } + + + fn.enable = function(){ + this.$container.on('selectstart', this.on_select_start); + + this.$container.on(pointer_events.start, this.options.items, $.proxy( + this.drag_handler, this)); + + this.$body.on(pointer_events.end, $.proxy(function(e) { + this.is_dragging = false; + this.$body.off(pointer_events.move); + if (this.drag_start) { + this.on_dragstop(e); + } + }, this)); + }; + + + fn.disable = function(){ + this.$container.off(pointer_events.start); + this.$body.off(pointer_events.end); + this.$container.off('selectstart', this.on_select_start); + }; + + + fn.destroy = function(){ + this.disable(); + $.removeData(this.$container, 'drag'); + }; + + + //jQuery adapter + $.fn.drag = function ( options ) { + return this.each(function () { + if (!$.data(this, 'drag')) { + $.data(this, 'drag', new Draggable( this, options )); + } + }); + }; + + +}(jQuery, window, document)); + +;(function($, window, document, undefined) { + + var defaults = { + widget_selector: '> li', + widget_margins: [10, 10], + widget_base_dimensions: [400, 225], + extra_rows: 0, + extra_cols: 0, + min_cols: 1, + min_rows: 15, + max_size_x: 6, + autogenerate_stylesheet: true, + avoid_overlapped_widgets: true, + serialize_params: function($w, wgd) { + return { + col: wgd.col, + row: wgd.row, + size_x: wgd.size_x, + size_y: wgd.size_y + }; + }, + collision: {}, + draggable: { + distance: 4 + } + }; + + + /** + * @class Gridster + * @uses Draggable + * @uses Collision + * @param {HTMLElement} el The HTMLelement that contains all the widgets. + * @param {Object} [options] An Object with all options you want to + * overwrite: + * @param {HTMLElement|String} [options.widget_selector] Define who will + * be the draggable widgets. Can be a CSS Selector String or a + * collection of HTMLElements + * @param {Array} [options.widget_margins] Margin between widgets. + * The first index for the horizontal margin (left, right) and + * the second for the vertical margin (top, bottom). + * @param {Array} [options.widget_base_dimensions] Base widget dimensions + * in pixels. The first index for the width and the second for the + * height. + * @param {Number} [options.extra_cols] Add more columns in addition to + * those that have been calculated. + * @param {Number} [options.extra_rows] Add more rows in addition to + * those that have been calculated. + * @param {Number} [options.min_cols] The minimum required columns. + * @param {Number} [options.min_rows] The minimum required rows. + * @param {Number} [options.max_size_x] The maximum number of columns + * that a widget can span. + * @param {Boolean} [options.autogenerate_stylesheet] If true, all the + * CSS required to position all widgets in their respective columns + * and rows will be generated automatically and injected to the + * `` of the document. You can set this to false, and write + * your own CSS targeting rows and cols via data-attributes like so: + * `[data-col="1"] { left: 10px; }` + * @param {Boolean} [options.avoid_overlapped_widgets] Avoid that widgets loaded + * from the DOM can be overlapped. It is helpful if the positions were + * bad stored in the database or if there was any conflict. + * @param {Function} [options.serialize_params] Return the data you want + * for each widget in the serialization. Two arguments are passed: + * `$w`: the jQuery wrapped HTMLElement, and `wgd`: the grid + * coords object (`col`, `row`, `size_x`, `size_y`). + * @param {Object} [options.collision] An Object with all options for + * Collision class you want to overwrite. See Collision docs for + * more info. + * @param {Object} [options.draggable] An Object with all options for + * Draggable class you want to overwrite. See Draggable docs for more + * info. + * + * @constructor + */ + function Gridster(el, options) { + this.options = $.extend(true, defaults, options); + this.$el = $(el); + this.$wrapper = this.$el.parent(); + this.$widgets = $(this.options.widget_selector, this.$el).addClass('gs_w'); + this.widgets = []; + this.$changed = $([]); + this.wrapper_width = this.$wrapper.width(); + this.min_widget_width = (this.options.widget_margins[0] * 2) + + this.options.widget_base_dimensions[0]; + this.min_widget_height = (this.options.widget_margins[1] * 2) + + this.options.widget_base_dimensions[1]; + this.init(); + } + + Gridster.generated_stylesheets = []; + + var fn = Gridster.prototype; + + fn.init = function() { + this.generate_grid_and_stylesheet(); + this.get_widgets_from_DOM(); + this.set_dom_grid_height(); + this.$wrapper.addClass('ready'); + this.draggable(); + + $(window).bind( + 'resize', throttle($.proxy(this.recalculate_faux_grid, this), 200)); + }; + + + /** + * Disables dragging. + * + * @method disable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.disable = function() { + this.$wrapper.find('.player-revert').removeClass('player-revert'); + this.drag_api.disable(); + return this; + }; + + + /** + * Enables dragging. + * + * @method enable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.enable = function() { + this.drag_api.enable(); + return this; + }; + + + /** + * Add a new widget to the grid. + * + * @method add_widget + * @param {String|HTMLElement} html The string representing the HTML of the widget + * or the HTMLElement. + * @param {Number} [size_x] The nº of rows the widget occupies horizontally. + * @param {Number} [size_y] The nº of columns the widget occupies vertically. + * @param {Number} [col] The column the widget should start in. + * @param {Number} [row] The row the widget should start in. + * @return {HTMLElement} Returns the jQuery wrapped HTMLElement representing. + * the widget that was just created. + */ + fn.add_widget = function(html, size_x, size_y, col, row) { + var pos; + size_x || (size_x = 1); + size_y || (size_y = 1); + + if (!col & !row) { + pos = this.next_position(size_x, size_y); + }else{ + pos = { + col: col, + row: row + }; + + this.empty_cells(col, row, size_x, size_y); + } + + var $w = $(html).attr({ + 'data-col': pos.col, + 'data-row': pos.row, + 'data-sizex' : size_x, + 'data-sizey' : size_y + }).addClass('gs_w').appendTo(this.$el).hide(); + + this.$widgets = this.$widgets.add($w); + + this.register_widget($w); + + this.add_faux_rows(pos.size_y); + + this.set_dom_grid_height(); + + return $w.fadeIn(); + }; + + + + /** + * Change the size of a widget. + * + * @method resize_widget + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement + * representing the widget. + * @param {Number} size_x The number of columns that will occupy the widget. + * @param {Number} size_y The number of rows that will occupy the widget. + * @return {HTMLElement} Returns $widget. + */ + fn.resize_widget = function($widget, size_x, size_y) { + var wgd = $widget.coords().grid; + size_x || (size_x = wgd.size_x); + size_y || (size_y = wgd.size_y); + + if (size_x > this.cols) { + size_x = this.cols; + }; + + var old_cells_occupied = this.get_cells_occupied(wgd); + var old_size_x = wgd.size_x; + var old_size_y = wgd.size_y; + var old_col = wgd.col; + var new_col = old_col; + var wider = size_x > old_size_x; + var taller = size_y > old_size_y; + + if (old_col + size_x - 1 > this.cols) { + var diff = old_col + (size_x - 1) - this.cols; + var c = old_col - diff; + new_col = Math.max(1, c); + }; + + var new_grid_data = { + col: new_col, + row: wgd.row, + size_x: size_x, + size_y: size_y + }; + + var new_cells_occupied = this.get_cells_occupied(new_grid_data); + + var empty_cols = []; + $.each(old_cells_occupied.cols, function(i, col) { + if ($.inArray(col, new_cells_occupied.cols) === -1) { + empty_cols.push(col); + }; + }); + + var occupied_cols = []; + $.each(new_cells_occupied.cols, function(i, col) { + if ($.inArray(col, old_cells_occupied.cols) === -1) { + occupied_cols.push(col); + } + }); + + var empty_rows = []; + $.each(old_cells_occupied.rows, function(i, row) { + if ($.inArray(row, new_cells_occupied.rows) === -1) { + empty_rows.push(row); + }; + }); + + var occupied_rows = []; + $.each(new_cells_occupied.rows, function(i, row) { + if ($.inArray(row, old_cells_occupied.rows) === -1) { + occupied_rows.push(row); + }; + }); + + this.remove_from_gridmap(wgd); + + if (occupied_cols.length) { + var cols_to_empty = [ + new_col, wgd.row, size_x, Math.min(old_size_y, size_y), $widget + ]; + this.empty_cells.apply(this, cols_to_empty); + }; + + if (occupied_rows.length) { + var rows_to_empty = [new_col, wgd.row, size_x, size_y, $widget]; + this.empty_cells.apply(this, rows_to_empty); + }; + + wgd.col = new_col; + wgd.size_x = size_x; + wgd.size_y = size_y; + this.add_to_gridmap(new_grid_data, $widget); + + //update coords instance attributes + $widget.data('coords').update({ + width: (size_x * this.options.widget_base_dimensions[0] + + ((size_x - 1) * this.options.widget_margins[0]) * 2), + height: (size_y * this.options.widget_base_dimensions[1] + + ((size_y - 1) * this.options.widget_margins[1]) * 2) + }); + + if (size_y > old_size_y) { + this.add_faux_rows(size_y - old_size_y); + }; + + $widget.attr({ + 'data-col': new_col, + 'data-sizex': size_x, + 'data-sizey': size_y + }); + + if (empty_cols.length) { + var cols_to_remove_holes = [ + empty_cols[0], wgd.row, + empty_cols.length, + Math.min(old_size_y, size_y), + $widget + ]; + + this.remove_empty_cells.apply(this, cols_to_remove_holes); + } + + if (empty_rows.length) { + var rows_to_remove_holes = [ + new_col, wgd.row, size_x, size_y, $widget + ]; + this.remove_empty_cells.apply(this, rows_to_remove_holes); + } + + return $widget; + }; + + /** + * Move down widgets in cells represented by the arguments col, row, size_x, + * size_y + * + * @method empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} $exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells = function(col, row, size_x, size_y, $exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row - size_y, + size_x: size_x, + size_y: size_y + }); + + $nexts.not($exclude).each($.proxy(function(i, w) { + var wgd = $(w).coords().grid; + if (!(wgd.row <= (row + size_y - 1))) { return; }; + var diff = (row + size_y) - wgd.row; + this.move_widget_down($(w), diff); + }, this)); + + this.set_dom_grid_height(); + + return this; + }; + + + /** + * Move up widgets below cells represented by the arguments col, row, size_x, + * size_y. + * + * @method remove_empty_cells + * @param {Number} col The column where the group of cells begin. + * @param {Number} row The row where the group of cells begin. + * @param {Number} size_x The number of columns that the group of cells + * occupy. + * @param {Number} size_y The number of rows that the group of cells + * occupy. + * @param {HTMLElement} $exclude Exclude widgets from being moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_empty_cells = function(col, row, size_x, size_y, exclude) { + var $nexts = this.widgets_below({ + col: col, + row: row, + size_x: size_x, + size_y: size_y + }); + + $nexts.not(exclude).each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), size_y ); + }, this)); + + this.set_dom_grid_height(); + + return this; + } + + + /** + * Get the most left column below to add a new widget. + * + * @method next_position + * @param {Number} size_x The nº of rows the widget occupies horizontally. + * @param {Number} size_y The nº of columns the widget occupies vertically. + * @return {Object} Returns a grid coords object representing the future + * widget coords. + */ + fn.next_position = function(size_x, size_y) { + size_x || (size_x = 1); + size_y || (size_y = 1); + var ga = this.gridmap; + var cols_l = ga.length; + var valid_pos = []; + + for (var c = 1; c < cols_l; c++) { + var rows_l = ga[c].length; + for (var r = 1; r <= rows_l; r++) { + var can_move_to = this.can_move_to({ + size_x: size_x, + size_y: size_y + }, c, r); + + if (can_move_to) { + valid_pos.push({ + col: c, + row: r, + size_y: size_y, + size_x: size_x + }); + } + } + } + + if (valid_pos.length) { + return this.sort_by_row_and_col_asc(valid_pos)[0]; + } + return false; + }; + + + /** + * Remove a widget from the grid. + * + * @method remove_widget + * @param {HTMLElement} el The jQuery wrapped HTMLElement you want to remove. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_widget = function(el, callback) { + var $el = el instanceof jQuery ? el : $(el); + var wgd = $el.coords().grid; + + this.cells_occupied_by_placeholder = {}; + this.$widgets = this.$widgets.not($el); + + var $nexts = this.widgets_below($el); + + this.remove_from_gridmap(wgd); + + $el.fadeOut($.proxy(function() { + $el.remove(); + + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget), wgd.size_y ); + }, this)); + + this.set_dom_grid_height(); + + if (callback) { + callback.call(this, el); + } + }, this)); + }; + + + /** + * Returns a serialized array of the widgets in the grid. + * + * @method serialize + * @param {HTMLElement} [$widgets] The collection of jQuery wrapped + * HTMLElements you want to serialize. If no argument is passed all widgets + * will be serialized. + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize = function($widgets) { + $widgets || ($widgets = this.$widgets); + var result = []; + $widgets.each($.proxy(function(i, widget) { + result.push(this.options.serialize_params( + $(widget), $(widget).coords().grid ) ); + }, this)); + + return result; + }; + + + /** + * Returns a serialized array of the widgets that have changed their + * position. + * + * @method serialize_changed + * @return {Array} Returns an Array of Objects with the data specified in + * the serialize_params option. + */ + fn.serialize_changed = function() { + return this.serialize(this.$changed); + }; + + + /** + * Creates the grid coords object representing the widget a add it to the + * mapped array of positions. + * + * @method register_widget + * @return {Array} Returns the instance of the Gridster class. + */ + fn.register_widget = function($el) { + + var wgd = { + 'col': parseInt($el.attr('data-col'), 10), + 'row': parseInt($el.attr('data-row'), 10), + 'size_x': parseInt($el.attr('data-sizex'), 10), + 'size_y': parseInt($el.attr('data-sizey'), 10), + 'el': $el + }; + + if (this.options.avoid_overlapped_widgets && + !this.can_move_to( + {size_x: wgd.size_x, size_y: wgd.size_y}, wgd.col, wgd.row) + ) { + wgd = this.next_position(wgd.size_x, wgd.size_y); + wgd.el = $el; + $el.attr({ + 'data-col': wgd.col, + 'data-row': wgd.row, + 'data-sizex': wgd.size_x, + 'data-sizey': wgd.size_y + }); + } + + // attach Coord object to player data-coord attribute + $el.data('coords', $el.coords()); + + // Extend Coord object with grid position info + $el.data('coords').grid = wgd; + + this.add_to_gridmap(wgd, $el); + this.widgets.push($el); + return this; + }; + + + /** + * Update in the mapped array of positions the value of cells represented by + * the grid coords object passed in the `grid_data` param. + * + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value Pass `false` or the jQuery wrapped + * HTMLElement, depends if you want to delete an existing position or add + * a new one. + * @method update_widget_position + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.update_widget_position = function(grid_data, value) { + this.for_each_cell_occupied(grid_data, function(col, row) { + if (!this.gridmap[col]) { return this; } + this.gridmap[col][row] = value; + }); + return this; + }; + + + /** + * Remove a widget from the mapped array of positions. + * + * @method remove_from_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.remove_from_gridmap = function(grid_data) { + return this.update_widget_position(grid_data, false); + }; + + + /** + * Add a widget to the mapped array of positions. + * + * @method add_to_gridmap + * @param {Object} grid_data The grid coords object representing the cells + * to update in the mapped array. + * @param {HTMLElement|Boolean} value The value to set in the specified + * position . + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.add_to_gridmap = function(grid_data, value) { + this.update_widget_position(grid_data, value || grid_data.el); + + if (grid_data.el) { + var $widgets = this.widgets_below(grid_data.el); + $widgets.each($.proxy(function(i, widget) { + this.move_widget_up( $(widget)); + }, this)); + } + }; + + + /** + * Make widgets draggable. + * + * @uses Draggable + * @method draggable + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.draggable = function() { + var self = this; + var draggable_options = $.extend(true, {}, this.options.draggable, { + offset_left: this.options.widget_margins[0], + start: function(event, ui) { + self.$widgets.filter('.player-revert') + .removeClass('player-revert'); + + self.$player = $(this); + self.$helper = self.options.draggable.helper === 'clone' ? + $(ui.helper) : self.$player; + self.helper = !self.$helper.is(self.$player); + + self.on_start_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstart'); + }, + stop: function(event, ui) { + self.on_stop_drag.call(self, event, ui); + self.$el.trigger('gridster:dragstop'); + }, + drag: throttle(function(event, ui) { + self.on_drag.call(self, event, ui); + self.$el.trigger('gridster:drag'); + }, 60) + }); + + this.drag_api = this.$el.drag(draggable_options).data('drag'); + return this; + }; + + + /** + * This function is executed when the player begins to be dragged. + * + * @method on_start_drag + * @param {Event} The original browser event + * @param {Object} A prepared ui object. + */ + fn.on_start_drag = function(event, ui) { + + this.$helper.add(this.$player).add(this.$wrapper).addClass('dragging'); + + this.$player.addClass('player'); + this.player_grid_data = this.$player.coords().grid; + this.placeholder_grid_data = $.extend({}, this.player_grid_data); + + //set new grid height along the dragging period + this.$el.css('height', this.$el.height() + + (this.player_grid_data.size_y * this.min_widget_height)); + + var colliders = this.faux_grid; + var coords = this.$player.data('coords').coords; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.last_cols = []; + this.last_rows = []; + + + // see jquery.collision.js + this.collision_api = this.$helper.collision( + colliders, this.options.collision); + + this.$preview_holder = $('
        • ', { + 'class': 'preview-holder', + 'data-row': this.$player.attr('data-row'), + 'data-col': this.$player.attr('data-col'), + css: { + width: coords.width, + height: coords.height + } + }).appendTo(this.$el); + + if (this.options.draggable.start) { + this.options.draggable.start.call(this, event, ui); + } + }; + + + /** + * This function is executed when the player is being dragged. + * + * @method on_drag + * @param {Event} The original browser event + * @param {Object} A prepared ui object. + */ + fn.on_drag = function(event, ui) { + //break if dragstop has been fired + if (this.$player === null) { + return false; + }; + + var abs_offset = { + left: ui.position.left + this.baseX, + top: ui.position.top + this.baseY + }; + + this.colliders_data = this.collision_api.get_closest_colliders( + abs_offset); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + if (this.helper && this.$player) { + this.$player.css({ + 'left': ui.position.left, + 'top': ui.position.top + }); + } + + if (this.options.draggable.drag) { + this.options.draggable.drag.call(this, event, ui); + } + }; + + /** + * This function is executed when the player stops being dragged. + * + * @method on_stop_drag + * @param {Event} The original browser event + * @param {Object} A prepared ui object. + */ + fn.on_stop_drag = function(event, ui) { + this.$helper.add(this.$player).add(this.$wrapper) + .removeClass('dragging'); + + ui.position.left = ui.position.left + this.baseX; + ui.position.top = ui.position.top + this.baseY; + this.colliders_data = this.collision_api.get_closest_colliders(ui.position); + + this.on_overlapped_column_change( + this.on_start_overlapping_column, + this.on_stop_overlapping_column + ); + + this.on_overlapped_row_change( + this.on_start_overlapping_row, + this.on_stop_overlapping_row + ); + + this.$player.addClass('player-revert').removeClass('player') + .attr({ + 'data-col': this.placeholder_grid_data.col, + 'data-row': this.placeholder_grid_data.row + }).css({ + 'left': '', + 'top': '' + }); + + this.$changed = this.$changed.add(this.$player); + + this.cells_occupied_by_player = this.get_cells_occupied( + this.placeholder_grid_data); + this.set_cells_player_occupies( + this.placeholder_grid_data.col, this.placeholder_grid_data.row); + + this.$player.coords().grid.row = this.placeholder_grid_data.row; + this.$player.coords().grid.col = this.placeholder_grid_data.col; + + if (this.options.draggable.stop) { + this.options.draggable.stop.call(this, event, ui); + } + + this.$preview_holder.remove(); + + this.$player = null; + this.$helper = null; + this.placeholder_grid_data = {}; + this.player_grid_data = {}; + this.cells_occupied_by_placeholder = {}; + this.cells_occupied_by_player = {}; + + this.set_dom_grid_height(); + }; + + + /** + * Executes the callbacks passed as arguments when a column begins to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new column + * begins to be overlapped. The column is passed as first argument. + * @param {Function} stop_callback Function executed when a column stops + * being overlapped. The column is passed as first argument. + * @method on_overlapped_column_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_column_change = function(start_callback, stop_callback) { + if (!this.colliders_data.length) { + return; + } + var cols = this.get_targeted_columns( + this.colliders_data[0].el.data.col); + + var last_n_cols = this.last_cols.length; + var n_cols = cols.length; + var i; + + for (i = 0; i < n_cols; i++) { + if ($.inArray(cols[i], this.last_cols) === -1) { + (start_callback || $.noop).call(this, cols[i]); + } + } + + for (i = 0; i< last_n_cols; i++) { + if ($.inArray(this.last_cols[i], cols) === -1) { + (stop_callback || $.noop).call(this, this.last_cols[i]); + } + } + + this.last_cols = cols; + + return this; + }; + + + /** + * Executes the callbacks passed as arguments when a row starts to be + * overlapped or stops being overlapped. + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} stop_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method on_overlapped_row_change + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.on_overlapped_row_change = function(start_callback, end_callback) { + if (!this.colliders_data.length) { + return; + } + var rows = this.get_targeted_rows(this.colliders_data[0].el.data.row); + var last_n_rows = this.last_rows.length; + var n_rows = rows.length; + var i; + + for (i = 0; i < n_rows; i++) { + if ($.inArray(rows[i], this.last_rows) === -1) { + (start_callback || $.noop).call(this, rows[i]); + } + } + + for (i = 0; i < last_n_rows; i++) { + if ($.inArray(this.last_rows[i], rows) === -1) { + (end_callback || $.noop).call(this, this.last_rows[i]); + } + } + + this.last_rows = rows; + }; + + + /** + * Sets the current position of the player + * + * @param {Function} start_callback Function executed when a new row begins + * to be overlapped. The row is passed as first argument. + * @param {Function} stop_callback Function executed when a row stops being + * overlapped. The row is passed as first argument. + * @method set_player + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_player = function(col, row, no_player) { + var self = this; + if (!no_player) { + this.empty_cells_player_occupies(); + }; + var cell = !no_player ? self.colliders_data[0].el.data : {col: col}; + var to_col = cell.col; + var to_row = row || cell.row; + + this.player_grid_data = { + col: to_col, + row: to_row, + size_y : this.player_grid_data.size_y, + size_x : this.player_grid_data.size_x + }; + + this.cells_occupied_by_player = this.get_cells_occupied( + this.player_grid_data); + + var $overlapped_widgets = this.get_widgets_overlapped( + this.player_grid_data); + + var constraints = this.widgets_constraints($overlapped_widgets); + + this.manage_movements(constraints.can_go_up, to_col, to_row); + this.manage_movements(constraints.can_not_go_up, to_col, to_row); + + /* if there is not widgets overlapping in the new player position, + * update the new placeholder position. */ + if (!$overlapped_widgets.length) { + var pp = this.can_go_player_up(this.player_grid_data); + if (pp !== false) { + to_row = pp; + } + this.set_placeholder(to_col, to_row); + } + + return { + col: to_col, + row: to_row + }; + }; + + + /** + * See which of the widgets in the $widgets param collection can go to + * a upper row and which not. + * + * @method widgets_contraints + * @param {HTMLElements} $widgets A jQuery wrapped collection of + * HTMLElements. + * @return {Array} Returns a literal Object with two keys: `can_go_up` & + * `can_not_go_up`. Each contains a set of HTMLElements. + */ + fn.widgets_constraints = function($widgets) { + var $widgets_can_go_up = $([]); + var $widgets_can_not_go_up; + var wgd_can_go_up = []; + var wgd_can_not_go_up = []; + + $widgets.each($.proxy(function(i, w) { + var $w = $(w); + var wgd = $w.coords().grid; + if (this.can_go_widget_up(wgd)) { + $widgets_can_go_up = $widgets_can_go_up.add($w); + wgd_can_go_up.push(wgd); + }else{ + wgd_can_not_go_up.push(wgd); + } + }, this)); + + $widgets_can_not_go_up = $widgets.not($widgets_can_go_up); + + return { + can_go_up: this.sort_by_row_asc(wgd_can_go_up), + can_not_go_up: this.sort_by_row_desc(wgd_can_not_go_up) + }; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in ascending way. + * + * @method sort_by_row_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (!a.row) { + a = $(a).coords().grid; + b = $(b).coords().grid; + }; + + if (a.row > b.row) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) placing first the empty cells upper left. + * + * @method sort_by_row_and_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_and_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row > b.row || a.row == b.row && a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects by column (representing the grid + * coords of each widget) in ascending way. + * + * @method sort_by_col_asc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_col_asc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.col > b.col) { + return 1; + } + return -1; + }); + + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method sort_by_row_desc + * @param {Array} widgets Array of grid coords objects + * @return {Array} Returns the array sorted. + */ + fn.sort_by_row_desc = function(widgets) { + widgets = widgets.sort(function(a, b) { + if (a.row + a.size_y < b.row + b.size_y) { + return 1; + } + return -1; + }); + return widgets; + }; + + + /** + * Sorts an Array of grid coords objects (representing the grid coords of + * each widget) in descending way. + * + * @method manage_movements + * @param {HTMLElements} $widgets A jQuery collection of HTMLElements + * representing the widgets you want to move. + * @param {Number} to_col The column to which we want to move the widgets. + * @param {Number} to_row The row to which we want to move the widgets. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.manage_movements = function($widgets, to_col, to_row) { + $.each($widgets, $.proxy(function(i, w) { + var wgd = w; + var $w = wgd.el; + + var can_go_widget_up = this.can_go_widget_up(wgd); + + if (can_go_widget_up) { + //target CAN go up + //so move widget up + this.move_widget_to($w, can_go_widget_up); + this.set_placeholder(to_col, can_go_widget_up + wgd.size_y); + + } else { + //target can't go up + var can_go_player_up = this.can_go_player_up( + this.player_grid_data); + + if (!can_go_player_up) { + // target can't go up + // player cant't go up + // so we need to move widget down to a position that dont + // overlaps player + var y = (to_row + this.player_grid_data.size_y) - wgd.row; + + this.move_widget_down($w, y); + this.set_placeholder(to_col, to_row); + } + } + }, this)); + + return this; + }; + + /** + * Determines if there is a widget in the row and col given. Or if the + * HTMLElement passed as first argument is the player. + * + * @method is_player + * @param {Number|HTMLElement} col_or_el A jQuery wrapped collection of + * HTMLElements. + * @param {Number} [row] The column to which we want to move the widgets. + * @return {Boolean} Returns true or false. + */ + fn.is_player = function(col_or_el, row) { + if (row && !this.gridmap[col_or_el]) { return false; } + var $w = row ? this.gridmap[col_or_el][row] : col_or_el; + return $w && ($w.is(this.$player) || $w.is(this.$helper)); + }; + + + /** + * Determines if the widget that is being dragged is currently over the row + * and col given. + * + * @method is_player_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_player_in = function(col, row) { + var c = this.cells_occupied_by_player || {}; + return $.inArray(col, c.cols) >= 0 && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the row and col given. + * + * @method is_placeholder_in + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in = function(col, row) { + var c = this.cells_occupied_by_placeholder || {}; + return this.is_placeholder_in_col(col) && $.inArray(row, c.rows) >= 0; + }; + + + /** + * Determines if the placeholder is currently over the column given. + * + * @method is_placeholder_in_col + * @param {Number} col The column to check. + * @return {Boolean} Returns true or false. + */ + fn.is_placeholder_in_col = function(col) { + var c = this.cells_occupied_by_placeholder || []; + return $.inArray(col, c.cols) >= 0; + }; + + + /** + * Determines if the cell represented by col and row params is empty. + * + * @method is_empty + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_empty = function(col, row) { + if (typeof this.gridmap[col] !== 'undefined' && + typeof this.gridmap[col][row] !== 'undefined' && + this.gridmap[col][row] === false + ) { + return true; + } + return false; + }; + + + /** + * Determines if the cell represented by col and row params is occupied. + * + * @method is_occupied + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_occupied = function(col, row) { + if (!this.gridmap[col]) { + return false; + } + + if (this.gridmap[col][row]) { + return true; + } + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row params. + * + * @method is_widget + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean|HTMLElement} Returns false if there is no widget, + * else returns the jQuery HTMLElement + */ + fn.is_widget = function(col, row) { + var cell = this.gridmap[col]; + if (!cell) { + return false; + } + + cell = cell[row]; + + if (cell) { + return cell; + } + + return false; + }; + + + /** + * Determines if there is a widget in the cell represented by col/row + * params and if this is under the widget that is being dragged. + * + * @method is_widget_under_player + * @param {Number} col The column to check. + * @param {Number} row The row to check. + * @return {Boolean} Returns true or false. + */ + fn.is_widget_under_player = function(col, row) { + if (this.is_widget(col, row)) { + return this.is_player_in(col, row); + } + return false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_under_player + * @return {HTMLElement} Returns a jQuery collection of HTMLElements + */ + fn.get_widgets_under_player = function() { + var cells = this.cells_occupied_by_player || {cols: [], rows: []}; + var $widgets = $([]); + + $.each(cells.cols, $.proxy(function(i, col) { + $.each(cells.rows, $.proxy(function(i, row) { + if(this.is_widget(col, row)) { + $widgets = $widgets.add(this.gridmap[col][row]); + } + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * Put placeholder at the row and column specified. + * + * @method set_placeholder + * @param {Number} col The column to which we want to move the + * placeholder. + * @param {Number} row The row to which we want to move the + * placeholder. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_placeholder = function(col, row) { + var phgd = $.extend({}, this.placeholder_grid_data); + var $nexts = this.widgets_below({ + col: phgd.col, + row: phgd.row, + size_y: phgd.size_y, + size_x: phgd.size_x + }); + + //Prevents widgets go out of the grid + var right_col = (col + phgd.size_x - 1); + if (right_col > this.cols) { + col = col - (right_col - col); + } + + var moved_down = this.placeholder_grid_data.row < row; + var changed_column = this.placeholder_grid_data.col !== col; + + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + + this.cells_occupied_by_placeholder = this.get_cells_occupied( + this.placeholder_grid_data); + + this.$preview_holder.attr({ + 'data-row' : row, + 'data-col' : col + }); + + if (moved_down || changed_column) { + $nexts.each($.proxy(function(i, widget) { + this.move_widget_up( + $(widget), this.placeholder_grid_data.col - col + phgd.size_y); + }, this)); + } + + }; + + + /** + * Determines whether the player can move to a position above. + * + * @method can_go_player_up + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @return {Number|Boolean} If the player can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_player_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + var $widgets_under_player = this.get_widgets_under_player(); + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + var r = p_bottom_row + 1; + upper_rows[tcol] = []; + + while (--r > 0) { + if (this.is_empty(tcol, r) || this.is_player(tcol, r) || + this.is_widget(tcol, r) && + grid_col[r].is($widgets_under_player) + ) { + upper_rows[tcol].push(r); + min_row = r < min_row ? r : min_row; + }else{ + break; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(); + }); + + if (!result) { return false; } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Determines whether a widget can move to a position above. + * + * @method can_go_widget_up + * @param {Object} widget_grid_data The actual grid coords object of the + * widget we want to check. + * @return {Number|Boolean} If the widget can be moved to an upper row + * returns the row number, else returns false. + */ + fn.can_go_widget_up = function(widget_grid_data) { + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var result = true; + var upper_rows = []; + var min_row = 10000; + + if (widget_grid_data.col < this.player_grid_data.col && + (widget_grid_data.col + widget_grid_data.size_y - 1) > + (this.player_grid_data.col + this.player_grid_data.size_y - 1) + ) { + return false; + }; + + /* generate an array with columns as index and array with upper rows + * empty as value */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = this.gridmap[tcol]; + upper_rows[tcol] = []; + + var r = p_bottom_row + 1; + + while (--r > 0) { + if (this.is_widget(tcol, r) && !this.is_player_in(tcol, r)) { + if (!grid_col[r].is(widget_grid_data.el)) { + break; + }; + } + + if (!this.is_player(tcol, r) && + !this.is_placeholder_in(tcol, r) && + !this.is_player_in(tcol, r)) { + upper_rows[tcol].push(r); + }; + + if (r < min_row) { + min_row = r; + } + } + + if (upper_rows[tcol].length === 0) { + result = false; + return true; //break + } + + upper_rows[tcol].sort(); + }); + + if (!result) { return false; } + + return this.get_valid_rows(widget_grid_data, upper_rows, min_row); + }; + + + /** + * Search a valid row for the widget represented by `widget_grid_data' in + * the `upper_rows` array. Iteration starts from row specified in `min_row`. + * + * @method get_valid_rows + * @param {Object} widget_grid_data The actual grid coords object of the + * player. + * @param {Array} upper_rows An array with columns as index and arrays + * of valid rows as values. + * @param {Number} min_row The upper row from which the iteration will start. + * @return {Number|Boolean} Returns the upper row valid from the `upper_rows` + * for the widget in question. + */ + fn.get_valid_rows = function(widget_grid_data, upper_rows, min_row) { + var p_top_row = widget_grid_data.row; + var p_bottom_row = widget_grid_data.row + widget_grid_data.size_y - 1; + var size_y = widget_grid_data.size_y; + var r = min_row - 1; + var valid_rows = []; + + while (++r <= p_bottom_row ) { + var common = true; + $.each(upper_rows, function(col, rows) { + if ($.isArray(rows) && $.inArray(r, rows) === -1) { + common = false; + } + }); + + if (common === true) { + valid_rows.push(r); + if (valid_rows.length === size_y) { + break; + } + } + } + + var new_row = false; + if (size_y === 1) { + if (valid_rows[0] !== p_top_row) { + new_row = valid_rows[0] || false; + } + }else{ + if (valid_rows[0] !== p_top_row) { + new_row = this.get_consecutive_numbers_index( + valid_rows, size_y); + } + } + + return new_row; + }; + + + fn.get_consecutive_numbers_index = function(arr, size_y) { + var max = arr.length; + var result = []; + var first = true; + var prev = -1; // or null? + + for (var i=0; i < max; i++) { + if (first || arr[i] === prev + 1) { + result.push(i); + if (result.length === size_y) { + break; + } + first = false; + }else{ + result = []; + first = true; + } + + prev = arr[i]; + } + + return result.length >= size_y ? arr[result[0]] : false; + }; + + + /** + * Get widgets overlapping with the player. + * + * @method get_widgets_overlapped + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.get_widgets_overlapped = function() { + var $w; + var $widgets = $([]); + var used = []; + var rows_from_bottom = this.cells_occupied_by_player.rows.slice(0); + rows_from_bottom.reverse(); + + $.each(this.cells_occupied_by_player.cols, $.proxy(function(i, col) { + $.each(rows_from_bottom, $.proxy(function(i, row) { + // if there is a widget in the player position + if (!this.gridmap[col]) { return true; } //next iteration + var $w = this.gridmap[col][row]; + if (this.is_occupied(col, row) && !this.is_player($w) && + $.inArray($w, used) === -1 + ) { + $widgets = $widgets.add($w); + used.push($w); + } + + }, this)); + }, this)); + + return $widgets; + }; + + + /** + * This callback is executed when the player begins to collide with a column. + * + * @method on_start_overlapping_column + * @param {Number} col The collided column. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_column = function(col) { + this.set_player(col, false); + }; + + + /** + * A callback executed when the player begins to collide with a row. + * + * @method on_start_overlapping_row + * @param {Number} col The collided row. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_start_overlapping_row = function(row) { + this.set_player(false, row); + }; + + + /** + * A callback executed when the the player ends to collide with a column. + * + * @method on_stop_overlapping_column + * @param {Number} col The collided row. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_column = function(col) { + this.set_player(col, false); + + var self = this; + this.for_each_widget_below(col, this.cells_occupied_by_player.rows[0], + function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + }; + + + /** + * This callback is executed when the player ends to collide with a row. + * + * @method on_stop_overlapping_row + * @param {Number} row The collided row. + * @return {HTMLElements} Returns a jQuery collection of HTMLElements. + */ + fn.on_stop_overlapping_row = function(row) { + this.set_player(false, row); + + var self = this; + var cols = this.cells_occupied_by_player.cols; + for (var c = 0, cl = cols.length; c < cl; c++) { + this.for_each_widget_below(cols[c], row, function(tcol, trow) { + self.move_widget_up(this, self.player_grid_data.size_y); + }); + } + }; + + + /** + * Move a widget to a specific row. The cell or cells must be empty. + * If the widget has widgets below, all of these widgets will be moved also + * if they can. + * + * @method move_widget_to + * @param {HTMLElement} $widget The jQuery wrapped HTMLElement of the + * widget is going to be moved. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_to = function($widget, row) { + var self = this; + var widget_grid_data = $widget.coords().grid; + var diff = row - widget_grid_data.row; + var $next_widgets = this.widgets_below($widget); + + var can_move_to_new_cell = this.can_move_to( + widget_grid_data, widget_grid_data.col, row, $widget); + + if (can_move_to_new_cell === false) { + return false; + } + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', row); + this.$changed = this.$changed.add($widget); + + + $next_widgets.each(function(i, widget) { + var $w = $(widget); + var wgd = $w.coords().grid; + var can_go_up = self.can_go_widget_up(wgd); + if (can_go_up && can_go_up !== wgd.row) { + self.move_widget_to($w, can_go_up); + } + }); + + return this; + }; + + + /** + * Move up the specified widget and all below it. + * + * @method move_widget_up + * @param {HTMLElement} $widget The widget you want to move. + * @param {Number} [y_units] The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_up = function($widget, y_units) { + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + var can_go_up = true; + y_units || (y_units = 1); + + if (!this.can_go_up($widget)) { return false; } //break; + + this.for_each_column_occupied(el_grid_data, function(col) { + // can_go_up + if ($.inArray($widget, moved) === -1) { + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row - y_units; + next_row = this.can_go_up_to_row( + widget_grid_data, col, next_row); + + if (!next_row) { + return true; + } + + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + widget_grid_data.row = next_row; + this.add_to_gridmap(widget_grid_data); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + + $next_widgets.each($.proxy(function(i, widget) { + this.move_widget_up($(widget), y_units); + }, this)); + } + }); + + }; + + + /** + * Move down the specified widget and all below it. + * + * @method move_widget_down + * @param {HTMLElement} $widget The jQuery object representing the widget + * you want to move. + * @param {Number} The number of cells that the widget has to move. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.move_widget_down = function($widget, y_units) { + var el_grid_data = $widget.coords().grid; + var actual_row = el_grid_data.row; + var moved = []; + var y_diff = y_units; + + if (!$widget) { return false; } + + if ($.inArray($widget, moved) === -1) { + + var widget_grid_data = $widget.coords().grid; + var next_row = actual_row + y_units; + var $next_widgets = this.widgets_below($widget); + + this.remove_from_gridmap(widget_grid_data); + + $next_widgets.each($.proxy(function(i, widget) { + var $w = $(widget); + var wd = $w.coords().grid; + var tmp_y = this.displacement_diff( + wd, widget_grid_data, y_diff); + + if (tmp_y > 0) { + this.move_widget_down($w, tmp_y); + } + }, this)); + + widget_grid_data.row = next_row; + this.update_widget_position(widget_grid_data, $widget); + $widget.attr('data-row', widget_grid_data.row); + this.$changed = this.$changed.add($widget); + + moved.push($widget); + } + }; + + + /** + * Check if the widget can move to the specified row, else returns the + * upper row possible. + * + * @method can_go_up_to_row + * @param {Number} widget_grid_data The current grid coords object of the + * widget. + * @param {Number} col The target column. + * @param {Number} row The target row. + * @return {Boolean|Number} Returns the row number if the widget can move + * to the target position, else returns false. + */ + fn.can_go_up_to_row = function(widget_grid_data, col, row) { + var ga = this.gridmap; + var result = true; + var urc = []; // upper_rows_in_columns + var actual_row = widget_grid_data.row; + var r; + + /* generate an array with columns as index and array with + * upper rows empty in the column */ + this.for_each_column_occupied(widget_grid_data, function(tcol) { + var grid_col = ga[tcol]; + urc[tcol] = []; + + r = actual_row; + while (r--) { + if (this.is_empty(tcol, r) && + !this.is_placeholder_in(tcol, r) + ) { + urc[tcol].push(r); + }else{ + break; + } + } + + if (!urc[tcol].length) { + result = false; + return true; + } + + }); + + if (!result) { return false; } + + /* get common rows starting from upper position in all the columns + * that widget occupies */ + r = row; + for (r = 1; r < actual_row; r++) { + var common = true; + + for (var uc = 0, ucl = urc.length; uc < ucl; uc++) { + if (urc[uc] && $.inArray(r, urc[uc]) === -1) { + common = false; + } + } + + if (common === true) { + result = r; + break; + } + } + + return result; + }; + + + fn.displacement_diff = function(widget_grid_data, parent_bgd, y_units) { + var actual_row = widget_grid_data.row; + var diffs = []; + var parent_max_y = parent_bgd.row + parent_bgd.size_y; + + this.for_each_column_occupied(widget_grid_data, function(col) { + var temp_y_units = 0; + + for (var r = parent_max_y; r < actual_row; r++) { + if (this.is_empty(col, r)) { + temp_y_units = temp_y_units + 1; + } + } + + diffs.push(temp_y_units); + }); + + var max_diff = Math.max.apply(Math, diffs); + y_units = (y_units - max_diff); + + return y_units > 0 ? y_units : 0; + }; + + + /** + * Get widgets below a widget. + * + * @method widgets_below + * @param {HTMLElement} $el The jQuery wrapped HTMLElement. + * @return {HTMLElements} A jQuery collection of HTMLElements. + */ + fn.widgets_below = function($el) { + var el_grid_data = $.isPlainObject($el) ? $el : $el.coords().grid; + var self = this; + var ga = this.gridmap; + var next_row = el_grid_data.row + el_grid_data.size_y - 1; + var $nexts = $([]); + + this.for_each_column_occupied(el_grid_data, function(col) { + self.for_each_widget_below(col, next_row, function(tcol, trow) { + if (!self.is_player(this) && $.inArray(this, $nexts) === -1) { + $nexts = $nexts.add(this); + return true; // break + } + }); + }); + + return this.sort_by_row_asc($nexts); + }; + + + /** + * Update the array of mapped positions with the new player position. + * + * @method set_cells_player_occupies + * @param {Number} col The new player col. + * @param {Number} col The new player row. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.set_cells_player_occupies = function(col, row) { + this.remove_from_gridmap(this.placeholder_grid_data); + this.placeholder_grid_data.col = col; + this.placeholder_grid_data.row = row; + this.add_to_gridmap(this.placeholder_grid_data, this.$player); + return this; + }; + + + /** + * Remove from the array of mapped positions the reference to the player. + * + * @method empty_cells_player_occupies + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.empty_cells_player_occupies = function() { + this.remove_from_gridmap(this.placeholder_grid_data); + return this; + }; + + + fn.can_go_up = function($el) { + var el_grid_data = $el.coords().grid; + var initial_row = el_grid_data.row; + var prev_row = initial_row - 1; + var ga = this.gridmap; + var upper_rows_by_column = []; + + var result = true; + if (initial_row === 1) { return false; } + + this.for_each_column_occupied(el_grid_data, function(col) { + var $w = this.is_widget(col, prev_row); + + if (this.is_occupied(col, prev_row) || + this.is_player(col, prev_row) || + this.is_placeholder_in(col, prev_row) || + this.is_player_in(col, prev_row) + ) { + result = false; + return true; //break + } + }); + + return result; + }; + + + + /** + * Check if it's possible to move a widget to a specific col/row. It takes + * into account the dimensions (`size_y` and `size_x` attrs. of the grid + * coords object) the widget occupies. + * + * @method can_move_to + * @param {Object} widget_grid_data The grid coords object that represents + * the widget. + * @param {Object} col The col to check. + * @param {Object} row The row to check. + * @return {Boolean} Returns true if all cells are empty, else return false. + */ + fn.can_move_to = function(widget_grid_data, col, row) { + var ga = this.gridmap; + var $w = widget_grid_data.el; + var future_wd = { + size_y: widget_grid_data.size_y, + size_x: widget_grid_data.size_x, + col: col, + row: row + }; + var result = true; + + //Prevents widgets go out of the grid + var right_col = col + widget_grid_data.size_x - 1; + if (right_col > this.cols) { + return false; + }; + + this.for_each_cell_occupied(future_wd, function(tcol, trow) { + var $tw = this.is_widget(tcol, trow); + if ($tw && (!widget_grid_data.el || $tw.is($w))) { + result = false; + } + }); + + return result; + }; + + + /** + * Given the leftmost column returns all columns that are overlapping + * with the player. + * + * @method get_targeted_columns + * @param {Number} [from_col] The leftmost column. + * @return {Array} Returns an array with column numbers. + */ + fn.get_targeted_columns = function(from_col) { + var max = (from_col || this.player_grid_data.col) + + (this.player_grid_data.size_x - 1); + var cols = []; + for (var col = from_col; col <= max; col++) { + cols.push(col); + } + return cols; + }; + + + /** + * Given the upper row returns all rows that are overlapping with the player. + * + * @method get_targeted_rows + * @param {Number} [from_row] The upper row. + * @return {Array} Returns an array with row numbers. + */ + fn.get_targeted_rows = function(from_row) { + var max = (from_row || this.player_grid_data.row) + + (this.player_grid_data.size_y - 1); + var rows = []; + for (var row = from_row; row <= max; row++) { + rows.push(row); + } + return rows; + }; + + /** + * Get all columns and rows that a widget occupies. + * + * @method get_cells_occupied + * @param {Object} el_grid_data The grid coords object of the widget. + * @return {Object} Returns an object like `{ cols: [], rows: []}`. + */ + fn.get_cells_occupied = function(el_grid_data) { + var cells = { cols: [], rows: []}; + var i; + if (arguments[1] instanceof jQuery) { + el_grid_data = arguments[1].coords().grid; + } + + for (i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + cells.cols.push(col); + } + + for (i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + cells.rows.push(row); + } + + return cells; + }; + + + /** + * Iterate over the cells occupied by a widget executing a function for + * each one. + * + * @method for_each_cell_occupied + * @param {Object} el_grid_data The grid coords object that represents the + * widget. + * @param {Function} callback The function to execute on each column + * iteration. Column and row are passed as arguments. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_cell_occupied = function(grid_data, callback) { + this.for_each_column_occupied(grid_data, function(col) { + this.for_each_row_occupied(grid_data, function(row) { + callback.call(this, col, row); + }); + }); + return this; + }; + + + /** + * Iterate over the columns occupied by a widget executing a function for + * each one. + * + * @method for_each_column_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The column number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_column_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_x; i++) { + var col = el_grid_data.col + i; + callback.call(this, col, el_grid_data); + } + }; + + + /** + * Iterate over the rows occupied by a widget executing a function for + * each one. + * + * @method for_each_row_occupied + * @param {Object} el_grid_data The grid coords object that represents + * the widget. + * @param {Function} callback The function to execute on each column + * iteration. The row number is passed as first argument. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_row_occupied = function(el_grid_data, callback) { + for (var i = 0; i < el_grid_data.size_y; i++) { + var row = el_grid_data.row + i; + callback.call(this, row, el_grid_data); + } + }; + + + + fn._traversing_widgets = function(type, direction, col, row, callback) { + var ga = this.gridmap; + if (!ga[col]) { return; } + + var cr, max; + var action = type + '/' + direction; + if (arguments[2] instanceof jQuery) { + var el_grid_data = arguments[2].coords().grid; + col = el_grid_data.col; + row = el_grid_data.row; + callback = arguments[3]; + } + var matched = []; + var trow = row; + + + var methods = { + 'for_each/above': function() { + while (trow--) { + if (trow > 0 && this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { break; } + } + } + }, + 'for_each/below': function() { + for (trow = row + 1, max = ga[col].length; trow < max; trow++) { + if (this.is_widget(col, trow) && + $.inArray(ga[col][trow], matched) === -1 + ) { + cr = callback.call(ga[col][trow], col, trow); + matched.push(ga[col][trow]); + if (cr) { break; } + } + } + } + }; + + if (methods[action]) { + methods[action].call(this); + } + }; + + + /** + * Iterate over each widget above the column and row specified. + * + * @method for_each_widget_above + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery + * wrapped HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_above = function(col, row, callback) { + this._traversing_widgets('for_each', 'above', col, row, callback); + return this; + }; + + + /** + * Iterate over each widget below the column and row specified. + * + * @method for_each_widget_below + * @param {Number} col The column to start iterating. + * @param {Number} row The row to start iterating. + * @param {Function} callback The function to execute on each widget + * iteration. The value of `this` inside the function is the jQuery wrapped + * HTMLElement. + * @return {Class} Returns the instance of the Gridster Class. + */ + fn.for_each_widget_below = function(col, row, callback) { + this._traversing_widgets('for_each', 'below', col, row, callback); + return this; + }; + + + /** + * Returns the highest occupied cell in the grid. + * + * @method get_highest_occupied_cell + * @return {Object} Returns an object with `col` and `row` numbers. + */ + fn.get_highest_occupied_cell = function() { + var r; + var gm = this.gridmap; + var rows = []; + var row_in_col = []; + for (var c = gm.length - 1; c >= 1; c--) { + for (r = gm[c].length - 1; r >= 1; r--) { + if (this.is_widget(c, r)) { + rows.push(r); + row_in_col[r] = c; + break; + } + } + } + + var highest_row = Math.max.apply(Math, rows); + + this.highest_occupied_cell = { + col: row_in_col[highest_row], + row: highest_row + }; + + return this.highest_occupied_cell; + }; + + + fn.get_widgets_from = function(col, row) { + var ga = this.gridmap; + var $widgets = $(); + + if (col) { + $widgets = $widgets.add( + this.$widgets.filter(function() { + var tcol = $(this).attr('data-col'); + return (tcol === col || tcol > col); + }) + ); + } + + if (row) { + $widgets = $widgets.add( + this.$widgets.filter(function() { + var trow = $(this).attr('data-row'); + return (trow === row || trow > row); + }) + ); + } + + return $widgets; + } + + + /** + * Set the current height of the parent grid. + * + * @method set_dom_grid_height + * @return {Object} Returns the instance of the Gridster class. + */ + fn.set_dom_grid_height = function() { + var r = this.get_highest_occupied_cell().row; + this.$el.css('height', r * this.min_widget_height); + return this; + }; + + + /** + * It generates the neccessary styles to position the widgets. + * + * @method generate_stylesheet + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_stylesheet = function(opts) { + var styles = ''; + var max_size_x = this.options.max_size_x; + var max_rows = 0; + var max_cols = 0; + var i; + var rules; + + opts || (opts = {}); + opts.cols || (opts.cols = this.cols); + opts.rows || (opts.rows = this.rows); + opts.namespace || (opts.namespace = ''); + opts.widget_base_dimensions || + (opts.widget_base_dimensions = this.options.widget_base_dimensions); + opts.widget_margins || + (opts.widget_margins = this.options.widget_margins); + opts.min_widget_width = (opts.widget_margins[0] * 2) + + opts.widget_base_dimensions[0]; + opts.min_widget_height = (opts.widget_margins[1] * 2) + + opts.widget_base_dimensions[1]; + + // don't duplicate stylesheets for the same configuration + var serialized_opts = $.param(opts); + if ($.inArray(serialized_opts, Gridster.generated_stylesheets) >= 0) { + return false; + } + + Gridster.generated_stylesheets.push(serialized_opts); + + /* generate CSS styles for cols */ + for (i = opts.cols; i >= 0; i--) { + styles += (opts.namespace + ' [data-col="'+ (i + 1) + '"] { left:' + + ((i * opts.widget_base_dimensions[0]) + + (i * opts.widget_margins[0]) + + ((i + 1) * opts.widget_margins[0])) + 'px;} '); + } + + /* generate CSS styles for rows */ + for (i = opts.rows; i >= 0; i--) { + styles += (opts.namespace + ' [data-row="' + (i + 1) + '"] { top:' + + ((i * opts.widget_base_dimensions[1]) + + (i * opts.widget_margins[1]) + + ((i + 1) * opts.widget_margins[1]) ) + 'px;} '); + } + + for (var y = 1; y <= opts.rows; y++) { + styles += (opts.namespace + ' [data-sizey="' + y + '"] { height:' + + (y * opts.widget_base_dimensions[1] + + (y - 1) * (opts.widget_margins[1] * 2)) + 'px;}'); + } + + for (var x = 1; x <= max_size_x; x++) { + styles += (opts.namespace + ' [data-sizex="' + x + '"] { width:' + + (x * opts.widget_base_dimensions[0] + + (x - 1) * (opts.widget_margins[0] * 2)) + 'px;}'); + } + + return this.add_style_tag(styles); + }; + + + /** + * Injects the given CSS as string to the head of the document. + * + * @method add_style_tag + * @param {String} css The styles to apply. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_style_tag = function(css) { + var d = document; + var tag = d.createElement('style'); + + d.getElementsByTagName('head')[0].appendChild(tag); + tag.setAttribute('type', 'text/css'); + + if (tag.styleSheet) { + tag.styleSheet.cssText = css; + }else{ + tag.appendChild(document.createTextNode(css)); + } + return this; + }; + + + /** + * Generates a faux grid to collide with it when a widget is dragged and + * detect row or column that we want to go. + * + * @method generate_faux_grid + * @param {Number} rows Number of columns. + * @param {Number} cols Number of rows. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_faux_grid = function(rows, cols) { + this.faux_grid = []; + this.gridmap = []; + var col; + var row; + for (col = cols; col > 0; col--) { + this.gridmap[col] = []; + for (row = rows; row > 0; row--) { + this.add_faux_cell(row, col); + } + } + return this; + }; + + + /** + * Add cell to the faux grid. + * + * @method add_faux_cell + * @param {Number} row The row for the new faux cell. + * @param {Number} col The col for the new faux cell. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_cell = function(row, col) { + var coords = $({ + left: this.baseX + ((col - 1) * this.min_widget_width), + top: this.baseY + (row -1) * this.min_widget_height, + width: this.min_widget_width, + height: this.min_widget_height, + col: col, + row: row, + original_col: col, + original_row: row + }).coords(); + + this.gridmap[col][row] = false; + this.faux_grid.push(coords); + + return this; + }; + + + /** + * Add rows to the faux grid. + * + * @method add_faux_rows + * @param {Number} rows The number of rows you want to add to the faux grid. + * @return {Object} Returns the instance of the Gridster class. + */ + fn.add_faux_rows = function(rows) { + var actual_rows = this.rows; + var max_rows = actual_rows + (rows || 1); + + for (var r = max_rows; r > actual_rows; r--) { + for (var c = this.cols; c >= 1; c--) { + this.add_faux_cell(r, c); + }; + }; + + this.rows = max_rows; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this; + }; + + + /** + * Recalculates the offsets for the faux grid. You need to use it when + * the browser is resized. + * + * @method recalculate_faux_grid + * @return {Object} Returns the instance of the Gridster class. + */ + fn.recalculate_faux_grid = function() { + var aw = this.$wrapper.width(); + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + $.each(this.faux_grid, $.proxy(function(i, coords) { + this.faux_grid[i] = coords.update({ + left: this.baseX + (coords.data.col -1) * this.min_widget_width, + top: this.baseY + (coords.data.row -1) * this.min_widget_height + }); + + }, this)); + + return this; + }; + + + /** + * Get all widgets in the DOM and register them. + * + * @method get_widgets_from_DOM + * @return {Object} Returns the instance of the Gridster class. + */ + fn.get_widgets_from_DOM = function() { + this.$widgets.each($.proxy(function(i, widget) { + this.register_widget($(widget)); + }, this)); + return this; + }; + + + /** + * Calculate columns and rows to be set based on the configuration + * parameters, grid dimensions, etc ... + * + * @method generate_grid_and_stylesheet + * @return {Object} Returns the instance of the Gridster class. + */ + fn.generate_grid_and_stylesheet = function() { + var aw = this.$wrapper.width(); + var ah = this.$wrapper.height(); + + var cols = Math.floor(aw / this.min_widget_width) + + this.options.extra_cols; + + var actual_cols = this.$widgets.map(function() { + return $(this).attr('data-col'); + }); + actual_cols = Array.prototype.slice.call(actual_cols, 0); + //needed to pass tests with phantomjs + actual_cols.length || (actual_cols = [0]); + + var min_cols = Math.max.apply(Math, actual_cols); + + // get all rows that could be occupied by the current widgets + var max_rows = this.options.extra_rows; + this.$widgets.each(function(i, w){ + max_rows += (+$(w).attr('data-sizey')); + }); + + this.cols = Math.max(min_cols, cols, this.options.min_cols); + this.rows = Math.max(max_rows, this.options.min_rows); + + this.baseX = ($(window).width() - aw) / 2; + this.baseY = this.$wrapper.offset().top; + + if (this.options.autogenerate_stylesheet) { + this.generate_stylesheet(); + } + + return this.generate_faux_grid(this.rows, this.cols); + }; + + + //jQuery adapter + $.fn.gridster = function(options) { + return this.each(function() { + if (!$(this).data('gridster')) { + $(this).data('gridster', new Gridster( this, options )); + } + }); + }; + + +}(jQuery, window, document)); diff --git a/samples/Photos/PhotosWeb/packages.config b/samples/Photos/PhotosWeb/packages.config new file mode 100644 index 000000000..faa19e174 --- /dev/null +++ b/samples/Photos/PhotosWeb/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/Photos/Plugins/Gridster.cs b/samples/Photos/Plugins/Gridster.cs new file mode 100644 index 000000000..55a6c2403 --- /dev/null +++ b/samples/Photos/Plugins/Gridster.cs @@ -0,0 +1,203 @@ +// Isotope.cs +// + +using System; +using System.Html; +using System.Runtime.CompilerServices; +using jQueryApi; + +namespace jQueryApi.Gridster { + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class GridsterCollision { + } + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class GridsterDraggable { + } + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class GridsterCoordinates { + + [ScriptName("col")] + [IntrinsicProperty] + public int Column { + get; + set; + } + + [IntrinsicProperty] + public int Row { + get; + set; + } + + [ScriptName("size_x")] + [IntrinsicProperty] + public int SizeX { + get; + set; + } + + [ScriptName("size_y")] + [IntrinsicProperty] + public int SizeY { + get; + set; + } + } + + [Imported] + [IgnoreNamespace] + public delegate object GridsterSerializationCallback(jQueryObject widget, GridsterCoordinates coords); + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class GridsterOptions { + + public GridsterOptions() { + } + + public GridsterOptions(params object[] nameValuePairs) { + } + + [ScriptName("autogenerate_stylesheet")] + [IntrinsicProperty] + public bool AutoGenerateStyleSheet { + get; + set; + } + + [ScriptName("avoid_overlapped_widgets")] + [IntrinsicProperty] + public bool AvoidOverlapping { + get; + set; + } + + [ScriptName("widget_base_dimensions")] + [IntrinsicProperty] + public int[] BaseDimensions { + get; + set; + } + + [IntrinsicProperty] + public GridsterCollision Collision { + get; + set; + } + + [IntrinsicProperty] + public GridsterDraggable Draggable { + get; + set; + } + + [ScriptName("extra_cols")] + [IntrinsicProperty] + public int ExtraColumns { + get; + set; + } + + [ScriptName("extra_rows")] + [IntrinsicProperty] + public int ExtraRows { + get; + set; + } + + [ScriptName("widget_margins")] + [IntrinsicProperty] + public int[] Margins { + get; + set; + } + + [ScriptName("max_size_x")] + [IntrinsicProperty] + public int MaximumColumnSpan { + get; + set; + } + + [ScriptName("max_size_y")] + [IntrinsicProperty] + public int MaximumRowSpan { + get; + set; + } + + [ScriptName("min_cols")] + [IntrinsicProperty] + public int MinimumColumns { + get; + set; + } + + [ScriptName("min_rows")] + [IntrinsicProperty] + public int MinimumRows { + get; + set; + } + + [ScriptName("widget_selector")] + [IntrinsicProperty] + public string Selector { + get; + set; + } + + [ScriptName("serialize_params")] + [IntrinsicProperty] + public GridsterSerializationCallback SerializationCallback { + get; + set; + } + } + + [Imported] + [IgnoreNamespace] + public sealed class GridsterObject { + + private GridsterObject() { + } + + // TODO: Define methods... + + [ScriptName("disable")] + public GridsterObject DisableDragging() { + return null; + } + + [ScriptName("enable")] + public GridsterObject EnableDragging() { + return null; + } + } + + [Imported] + [IgnoreNamespace] + public sealed class jQueryGridsterObject : jQueryObject { + + private jQueryGridsterObject() { + } + + public jQueryObject Gridster() { + return null; + } + + public jQueryObject Gridster(GridsterOptions options) { + return null; + } + } +} diff --git a/samples/Photos/Plugins/Isotope.cs b/samples/Photos/Plugins/Isotope.cs new file mode 100644 index 000000000..56279a742 --- /dev/null +++ b/samples/Photos/Plugins/Isotope.cs @@ -0,0 +1,48 @@ +// Isotope.cs +// + +using System; +using System.Html; +using System.Runtime.CompilerServices; +using jQueryApi; + +namespace jQueryApi.Isotope { + + [Imported] + [IgnoreNamespace] + [NamedValues] + public enum IsotopeLayout { + + Masonry + } + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + public sealed class IsotopeOptions : Record { + + public IsotopeLayout LayoutMode; + + public IsotopeOptions() { + } + + public IsotopeOptions(params object[] nameValuePairs) { + } + } + + [Imported] + [IgnoreNamespace] + public sealed class jQueryIsotopeObject : jQueryObject { + + private jQueryIsotopeObject() { + } + + public jQueryObject Isotope() { + return null; + } + + public jQueryObject Isotope(IsotopeOptions options) { + return null; + } + } +} diff --git a/samples/Photos/Plugins/LightBox.cs b/samples/Photos/Plugins/LightBox.cs new file mode 100644 index 000000000..b980f8cf6 --- /dev/null +++ b/samples/Photos/Plugins/LightBox.cs @@ -0,0 +1,22 @@ +// Isotope.cs +// + +using System; +using System.Html; +using System.Runtime.CompilerServices; +using jQueryApi; + +namespace jQueryApi.LightBox { + + [Imported] + [IgnoreNamespace] + public sealed class jQueryLightBoxObject : jQueryObject { + + private jQueryLightBoxObject() { + } + + public jQueryObject LightBox() { + return null; + } + } +} diff --git a/samples/Photos/Plugins/Plugins.csproj b/samples/Photos/Plugins/Plugins.csproj new file mode 100644 index 000000000..6118bf360 --- /dev/null +++ b/samples/Photos/Plugins/Plugins.csproj @@ -0,0 +1,57 @@ + + + + Debug + AnyCPU + 9.0.30729 + 2.0 + {A441A116-8F91-4FBC-8420-D21C98283BD3} + Library + 512 + Properties + Plugins + Plugins + true + + + bin\Debug\ + DEBUG + prompt + 4 + 1591, 0661, 0660, 1684 + bin\Debug\Plugins.xml + + + bin\Release\ + + + prompt + 4 + 1591, 0661, 0660, 1684 + bin\Release\Plugins.xml + + + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.jQuery.0.7.5.0\lib\Script.jQuery.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.5.0\lib\Script.Web.dll + + + + + + + + + \ No newline at end of file diff --git a/samples/Photos/Plugins/Properties/AssemblyInfo.cs b/samples/Photos/Plugins/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..56aa0d82a --- /dev/null +++ b/samples/Photos/Plugins/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Plugins")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("Plugins")] diff --git a/samples/Photos/Plugins/Properties/ScriptInfo.txt b/samples/Photos/Plugins/Properties/ScriptInfo.txt new file mode 100644 index 000000000..802afb692 --- /dev/null +++ b/samples/Photos/Plugins/Properties/ScriptInfo.txt @@ -0,0 +1,12 @@ +Script Information +=============================================================================== + +This assembly provides access to script APIs. This is only meant for use at +development time, so you can reference and compile your c# code against the +script APIs. You must include the appropriate scripts in your page for +runtime functionality. + +# TODO: You can use this file to describe which version of the script +# your import library is based on, the URLs of associated scripts +# or any particular usage information for including those scripts +# into the application. diff --git a/samples/Photos/Plugins/packages.config b/samples/Photos/Plugins/packages.config new file mode 100644 index 000000000..8b5e6e6e7 --- /dev/null +++ b/samples/Photos/Plugins/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/Photos/packages/nuget.exe b/samples/Photos/packages/nuget.exe new file mode 100644 index 000000000..e313ff958 Binary files /dev/null and b/samples/Photos/packages/nuget.exe differ diff --git a/samples/Photos/packages/nuget.targets b/samples/Photos/packages/nuget.targets new file mode 100644 index 000000000..51fc72bd2 --- /dev/null +++ b/samples/Photos/packages/nuget.targets @@ -0,0 +1,16 @@ + + + + false + $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory))) + $(MSBuildThisFileDirectory)nuget.exe install "$(MSBuildProjectDirectory)\packages.config" -o "$(PackagesDir)" + + RestorePackages; + $(BuildDependsOn); + + + + + + + \ No newline at end of file diff --git a/samples/Photos/packages/repositories.config b/samples/Photos/packages/repositories.config new file mode 100644 index 000000000..eaf386955 --- /dev/null +++ b/samples/Photos/packages/repositories.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/samples/Todo/Todo.sln b/samples/Todo/Todo.sln new file mode 100644 index 000000000..57b26a6aa --- /dev/null +++ b/samples/Todo/Todo.sln @@ -0,0 +1,29 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Todo", "Todo\Todo.csproj", "{27D4B0A9-E192-40A9-BFE2-034B9B93AA40}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoWeb", "TodoWeb\TodoWeb.csproj", "{6B543006-1D4E-42A9-8679-4F43CA91DD4F}" + ProjectSection(ProjectDependencies) = postProject + {27D4B0A9-E192-40A9-BFE2-034B9B93AA40} = {27D4B0A9-E192-40A9-BFE2-034B9B93AA40} + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {27D4B0A9-E192-40A9-BFE2-034B9B93AA40}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {27D4B0A9-E192-40A9-BFE2-034B9B93AA40}.Debug|Any CPU.Build.0 = Debug|Any CPU + {27D4B0A9-E192-40A9-BFE2-034B9B93AA40}.Release|Any CPU.ActiveCfg = Release|Any CPU + {27D4B0A9-E192-40A9-BFE2-034B9B93AA40}.Release|Any CPU.Build.0 = Release|Any CPU + {6B543006-1D4E-42A9-8679-4F43CA91DD4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6B543006-1D4E-42A9-8679-4F43CA91DD4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6B543006-1D4E-42A9-8679-4F43CA91DD4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6B543006-1D4E-42A9-8679-4F43CA91DD4F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/samples/Todo/Todo/Properties/AssemblyInfo.cs b/samples/Todo/Todo/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..83a6fbc41 --- /dev/null +++ b/samples/Todo/Todo/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Todo")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + +[assembly: ScriptAssembly("Todo")] diff --git a/samples/Todo/Todo/Properties/FxCop.ruleset b/samples/Todo/Todo/Properties/FxCop.ruleset new file mode 100644 index 000000000..47c160f61 --- /dev/null +++ b/samples/Todo/Todo/Properties/FxCop.ruleset @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/Todo/Todo/Properties/Script.template b/samples/Todo/Todo/Properties/Script.template new file mode 100644 index 000000000..8fcd90176 --- /dev/null +++ b/samples/Todo/Todo/Properties/Script.template @@ -0,0 +1,8 @@ +//! #= ScriptFile ## +// + +(function($) { +#include[as-is] "%code%" +})(jQuery); + +//! This script was generated using Script# v#= CompilerVersion ## diff --git a/samples/Todo/Todo/Todo.csproj b/samples/Todo/Todo/Todo.csproj new file mode 100644 index 000000000..a9a17a5a6 --- /dev/null +++ b/samples/Todo/Todo/Todo.csproj @@ -0,0 +1,61 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {27D4B0A9-E192-40A9-BFE2-034B9B93AA40} + Library + Properties + Todo + Todo + Properties\Script.template + Properties\FxCop.ruleset + True + True + ..\TodoWeb\Scripts + true + + + bin\Debug\ + DEBUG;CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + + + + + bin\Release\ + CODE_ANALYSIS;SCRIPTSHARP + prompt + 4 + 0028, 1591, 1684 + + + + + + + + + + + + + + + ..\packages\ScriptSharp.Lib.jQuery.0.7.6.0\lib\Script.jQuery.dll + + + ..\packages\ScriptSharp.Lib.jQuery.Templating.0.7.6.0\lib\Script.jQuery.Templating.dll + + + ..\packages\ScriptSharp.Lib.HTML.0.7.6.0\lib\Script.Web.dll + + + + + + \ No newline at end of file diff --git a/samples/Todo/Todo/TodoApplication.cs b/samples/Todo/Todo/TodoApplication.cs new file mode 100644 index 000000000..3f0ac4277 --- /dev/null +++ b/samples/Todo/Todo/TodoApplication.cs @@ -0,0 +1,121 @@ +// TodoApplication.cs +// + +using System; +using System.Html; +using jQueryApi; +using jQueryApi.Templating; + +namespace Todo { + + internal sealed class TodoApplication { + + private TodoList _todoList; + + private jQueryTemplateObject _itemTemplate; + + private jQueryObject _newItem; + private jQueryObject _list; + private jQueryObject _activeCount; + private jQueryObject _clearButton; + + static TodoApplication() { + jQuery.OnDocumentReady(delegate() { + new TodoApplication(); + }); + } + + public TodoApplication() { + _todoList = new TodoList(); + + _itemTemplate = jQuery.Select("#itemTemplate").Plugin(); + + _newItem = jQuery.Select("#new-todo"); + _list = jQuery.Select("#todo-list"); + _activeCount = jQuery.Select("#todo-count"); + _clearButton = jQuery.Select("#clear-completed"); + + _newItem.On("keyup", OnNewItemKeyUp); + _list.On("change", ".toggle", OnItemToggle); + _list.On("click", ".destroy", OnItemDelete); + _list.On("dblclick", "label", OnItemEditBegin); + _list.On("keypress", "input", OnItemEdit); + _list.On("blur", "input", OnItemEditComplete); + _clearButton.On("click", OnClearCompletedClick); + + UpdateRendering(); + } + + private string GetItemID(Element e) { + return jQuery.FromElement(e).Closest("li").GetDataValue("id").ToString(); + } + + private void OnClearCompletedClick(jQueryEvent e) { + _todoList.DeleteItems(); + UpdateRendering(); + } + + private void OnItemDelete(jQueryEvent e) { + string id = GetItemID(e.Target); + _todoList.DeleteItem(id); + + UpdateRendering(); + } + + private void OnItemEdit(jQueryEvent e) { + if (e.Which == 13) { + e.Target.Blur(); + } + } + + private void OnItemEditBegin(jQueryEvent e) { + jQuery.FromElement(e.Target).Closest("li") + .AddClass("editing") + .Find(".edit") + .Focus(); + } + + private void OnItemEditComplete(jQueryEvent e) { + string id = GetItemID(e.Target); + TodoItem item = _todoList.GetItem(id); + + string text = jQuery.FromElement(e.Target).GetValue().Trim(); + if (String.IsNullOrEmpty(text)) { + _todoList.DeleteItem(item.ID); + } + else { + _todoList.UpdateItem(item, text, /* completed */ false); + } + + UpdateRendering(); + } + + private void OnItemToggle(jQueryEvent e) { + string id = GetItemID(e.Target); + TodoItem item = _todoList.GetItem(id); + + if (item != null) { + _todoList.UpdateItem(item, item.Title, !item.Completed); + UpdateRendering(); + } + } + + private void OnNewItemKeyUp(jQueryEvent e) { + string text = _newItem.GetValue().Trim(); + if ((text.Length != 0) && (e.Which == 13)) { + _todoList.CreateItem(text); + _newItem.Value(String.Empty); + + UpdateRendering(); + } + } + + private void UpdateRendering() { + _list.Empty(); + _itemTemplate.RenderTemplate(_todoList.Items).AppendTo(_list); + + _clearButton.SwitchClass("hidden", _todoList.CompletedCount == 0); + _activeCount.Text(_todoList.ActiveCount + " item(s) left"); + } + } +} diff --git a/samples/Todo/Todo/TodoItem.cs b/samples/Todo/Todo/TodoItem.cs new file mode 100644 index 000000000..c4e282eaa --- /dev/null +++ b/samples/Todo/Todo/TodoItem.cs @@ -0,0 +1,21 @@ +// TodoItem.cs +// + +using System; +using System.Runtime.CompilerServices; + +namespace Todo { + + [Imported] + [IgnoreNamespace] + [ScriptName("Object")] + internal sealed class TodoItem { + + public bool Completed; + + [ScriptName("id")] + public string ID; + + public string Title; + } +} diff --git a/samples/Todo/Todo/TodoList.cs b/samples/Todo/Todo/TodoList.cs new file mode 100644 index 000000000..a23b5e36b --- /dev/null +++ b/samples/Todo/Todo/TodoList.cs @@ -0,0 +1,109 @@ +// TodoList.cs +// + +using System; +using System.Collections.Generic; +using System.Html; +using System.Serialization; + +namespace Todo { + + internal sealed class TodoList { + + private const string StorageKey = "todoItems"; + + private List _items; + + public TodoList() { + LoadItems(); + } + + public int ActiveCount { + get { + return _items.Reduce(delegate(int previousValue, TodoItem item) { + return previousValue + (item.Completed ? 0 : 1); + }, 0); + } + } + + public int CompletedCount { + get { + return _items.Reduce(delegate(int previousValue, TodoItem item) { + return previousValue + (item.Completed ? 1 : 0); + }, 0); + } + } + + public IEnumerable Items { + get { + return _items; + } + } + + public TodoItem CreateItem(string text) { + string id = Date.Now.GetTime().ToString(); + + TodoItem item = new TodoItem(); + item.ID = id; + item.Title = text; + item.Completed = false; + + _items.Add(item); + SaveItems(); + + return item; + } + + public void DeleteItem(string id) { + foreach (TodoItem item in _items) { + if (item.ID == id) { + _items.Remove(item); + SaveItems(); + + return; + } + } + } + + public void DeleteItems() { + for (int i = _items.Count - 1; i >= 0; i--) { + if (_items[i].Completed) { + _items.RemoveAt(i); + } + } + + SaveItems(); + } + + public TodoItem GetItem(string id) { + foreach (TodoItem item in _items) { + if (item.ID == id) { + return item; + } + } + return null; + } + + private void LoadItems() { + string json = (string)Window.LocalStorage.GetItem(StorageKey); + + if (String.IsNullOrEmpty(json) == false) { + _items = Json.ParseData>(json); + } + else { + _items = new List(); + } + } + + private void SaveItems() { + string json = Json.Stringify(_items); + Window.LocalStorage.SetItem(StorageKey, json); + } + + public void UpdateItem(TodoItem item, string text, bool completed) { + item.Title = text; + item.Completed = completed; + SaveItems(); + } + } +} diff --git a/samples/Todo/Todo/packages.config b/samples/Todo/Todo/packages.config new file mode 100644 index 000000000..7a72c8bd3 --- /dev/null +++ b/samples/Todo/Todo/packages.config @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/samples/Todo/TodoWeb/Assets/Background.png b/samples/Todo/TodoWeb/Assets/Background.png new file mode 100644 index 000000000..b2a760082 Binary files /dev/null and b/samples/Todo/TodoWeb/Assets/Background.png differ diff --git a/samples/Todo/TodoWeb/Assets/Todo.css b/samples/Todo/TodoWeb/Assets/Todo.css new file mode 100644 index 000000000..39d0c5009 --- /dev/null +++ b/samples/Todo/TodoWeb/Assets/Todo.css @@ -0,0 +1,309 @@ +html, body { + margin: 0; + padding: 0; + width: 100%; height: 100%; +} + +button { + margin: 0; + padding: 0; + border: 0; + background: none; + font-size: 100%; + vertical-align: baseline; + font-family: inherit; + color: inherit; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; +} + +body { + font: 14px 'Helvetica Neue', 'Segoe UI', Helvetica, sans-serif; + line-height: 1.4em; + background-color: #eaeaea; + background-image: url(Background.png); + color: #4d4d4d; + width: 550px; + margin: 0 auto; + -webkit-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#todoapp { + background: #fff; + background: rgba(255, 255, 255, 0.9); + margin: 130px 0 40px 0; + border: 1px solid #ccc; + position: relative; + border-top-left-radius: 2px; + border-top-right-radius: 2px; + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.2), + 0 25px 50px 0 rgba(0, 0, 0, 0.15); +} + +#todoapp:before { + content: ''; + border-left: 1px solid #f5d6d6; + border-right: 1px solid #f5d6d6; + width: 2px; + position: absolute; + top: 0; + left: 40px; + height: 100%; +} + +#todoapp input::-webkit-input-placeholder { + font-style: italic; +} + +#todoapp h1 { + position: absolute; + top: -120px; + width: 100%; + font-size: 70px; + font-weight: bold; + text-align: center; + color: #b3b3b3; + color: rgba(255, 255, 255, 0.3); + text-shadow: -1px -1px rgba(0, 0, 0, 0.2); + -webkit-text-rendering: optimizeLegibility; + -ms-text-rendering: optimizeLegibility; + text-rendering: optimizeLegibility; +} + +#header { + padding-top: 15px; + border-radius: inherit; +} + +#header:before { + content: ''; + position: absolute; + top: 0; + right: 0; + left: 0; + height: 15px; + z-index: 2; + border-bottom: 1px solid #6c615c; + background: #8d7d77; + background: -webkit-gradient(linear, left top, left bottom, from(rgba(132, 110, 100, 0.8)),to(rgba(101, 84, 76, 0.8))); + background: -webkit-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: -ms-linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + background: linear-gradient(top, rgba(132, 110, 100, 0.8), rgba(101, 84, 76, 0.8)); + border-top-left-radius: 1px; + border-top-right-radius: 1px; +} + +#new-todo, .edit { + position: relative; + margin: 0; + width: 100%; + font-size: 24px; + font-family: inherit; + line-height: 1.4em; + outline: none; + color: inherit; + padding: 5px; + border: solid 1px rgba(0, 0, 0, 0.2); + box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2); + -webkit-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; + -webkit-font-smoothing: antialiased; + -ms-font-smoothing: antialiased; + font-smoothing: antialiased; +} + +#new-todo { + padding: 16px 16px 16px 60px; + border: none; + background: rgba(0, 0, 0, 0.02); + z-index: 2; + box-shadow: none; +} + +#main { + position: relative; + z-index: 2; + border-top: 1px dotted #adadad; +} + +#todo-list { + margin: 0; + padding: 0; + list-style: none; +} + +#todo-list li { + position: relative; + font-size: 24px; + border-bottom: 1px dotted #ccc; +} + +#todo-list li:last-child { + border-bottom: none; +} + +#todo-list li.editing { + border-bottom: none; + padding: 0; +} + +#todo-list li.editing .edit { + display: block; + width: 505px; + padding: 13px 17px 12px 17px; + margin: 0 0 0 43px; +} + +#todo-list li.editing .view { + display: none; +} + +#todo-list li .toggle { + text-align: center; + width: 40px; + height: 40px; + position: absolute; + top: 0; + bottom: 0; + margin: auto 0; + border: none; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; +} + +#todo-list li .toggle:after { + font-size: 18px; + content: '✔'; + line-height: 43px; /* 40 + a couple of pixels visual adjustment */ + font-size: 20px; + color: #d9d9d9; + text-shadow: 0 -1px 0 #bfbfbf; +} + +#todo-list li .toggle:checked:after { + color: #85ada7; + text-shadow: 0 1px 0 #669991; + bottom: 1px; + position: relative; +} + +#todo-list li label { + word-break: break-word; + padding: 15px; + margin-left: 45px; + display: block; + line-height: 1.2; + -webkit-transition: color 0.4s; + -ms-transition: color 0.4s; + transition: color 0.4s; +} + +#todo-list li.completed label { + color: #a9a9a9; + text-decoration: line-through; +} + +#todo-list li .destroy { + display: none; + position: absolute; + top: 0; + right: 10px; + bottom: 0; + width: 40px; + height: 40px; + margin: auto 0; + font-size: 22px; + color: #a88a8a; + -webkit-transition: all 0.2s; + -ms-transition: all 0.2s; + transition: all 0.2s; +} + +#todo-list li .destroy:hover { + text-shadow: 0 0 1px #000, 0 0 10px rgba(199, 107, 107, 0.8); + -webkit-transform: scale(1.3); + -ms-transform: scale(1.3); + transform: scale(1.3); +} + +#todo-list li .destroy:after { + content: '✖'; +} + +#todo-list li:hover .destroy { + display: block; +} + +#todo-list li .edit { + display: none; +} + +#todo-list li.editing:last-child { + margin-bottom: -1px; +} + +#footer { + font-size: 12px; + color: #777; + padding: 0 15px; + position: absolute; + right: 0; + bottom: -31px; + left: 0; + height: 20px; + z-index: 1; + text-align: center; +} + +#footer:before { + content: ''; + position: absolute; + right: 0; + bottom: 31px; + left: 0; + height: 50px; + z-index: -1; + box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3), + 0 6px 0 -3px rgba(255, 255, 255, 0.8), + 0 7px 1px -3px rgba(0, 0, 0, 0.3), + 0 43px 0 -6px rgba(255, 255, 255, 0.8), + 0 44px 2px -6px rgba(0, 0, 0, 0.2); +} + +#todo-count { + float: left; + text-align: left; +} + +#clear-completed { + float: right; + position: relative; + line-height: 20px; + text-decoration: none; + background: rgba(0, 0, 0, 0.1); + font-size: 11px; + padding: 0 10px; + border-radius: 3px; + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.2); +} + +#clear-completed:hover { + background: rgba(0, 0, 0, 0.15); + box-shadow: 0 -1px 0 0 rgba(0, 0, 0, 0.3); +} + +#info { + margin: 65px auto 0; + color: #a6a6a6; + font-size: 12px; + text-align: center; +} + +.hidden{ + display:none; +} diff --git a/samples/Todo/TodoWeb/Default.htm b/samples/Todo/TodoWeb/Default.htm new file mode 100644 index 000000000..e6326e0b8 --- /dev/null +++ b/samples/Todo/TodoWeb/Default.htm @@ -0,0 +1,41 @@ + + + + Todo + + + +
          + +
          +
            +
            +
            + + +
            +
            +
            +

            Script# TODO List Sample (based on TodoMVC)

            +
            + + + + + + + + + diff --git a/samples/Todo/TodoWeb/Properties/AssemblyInfo.cs b/samples/Todo/TodoWeb/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..05d594856 --- /dev/null +++ b/samples/Todo/TodoWeb/Properties/AssemblyInfo.cs @@ -0,0 +1,16 @@ +// AssemblyInfo.cs +// + +using System; +using System.Reflection; + +[assembly: AssemblyTitle("TodoWeb")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Script# Samples")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/samples/Todo/TodoWeb/Scripts/jquery.min.js b/samples/Todo/TodoWeb/Scripts/jquery.min.js new file mode 100644 index 000000000..16ad06c5a --- /dev/null +++ b/samples/Todo/TodoWeb/Scripts/jquery.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.7.2 jquery.com | jquery.org/license */ +(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cu(a){if(!cj[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){ck||(ck=c.createElement("iframe"),ck.frameBorder=ck.width=ck.height=0),b.appendChild(ck);if(!cl||!ck.createElement)cl=(ck.contentWindow||ck.contentDocument).document,cl.write((f.support.boxModel?"":"")+""),cl.close();d=cl.createElement(a),cl.body.appendChild(d),e=f.css(d,"display"),b.removeChild(ck)}cj[a]=e}return cj[a]}function ct(a,b){var c={};f.each(cp.concat.apply([],cp.slice(0,b)),function(){c[this]=a});return c}function cs(){cq=b}function cr(){setTimeout(cs,0);return cq=f.now()}function ci(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ch(){try{return new a.XMLHttpRequest}catch(b){}}function cb(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g0){if(c!=="border")for(;e=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?+d:j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){if(typeof c!="string"||!c)return null;var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c
            a",d=p.getElementsByTagName("*"),e=p.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=p.getElementsByTagName("input")[0],b={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:p.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,pixelMargin:!0},f.boxModel=b.boxModel=c.compatMode==="CSS1Compat",i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete p.test}catch(r){b.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",function(){b.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),i.setAttribute("name","t"),p.appendChild(i),j=c.createDocumentFragment(),j.appendChild(p.lastChild),b.checkClone=j.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,j.removeChild(i),j.appendChild(p);if(p.attachEvent)for(n in{submit:1,change:1,focusin:1})m="on"+n,o=m in p,o||(p.setAttribute(m,"return;"),o=typeof p[m]=="function"),b[n+"Bubbles"]=o;j.removeChild(p),j=g=h=p=i=null,f(function(){var d,e,g,h,i,j,l,m,n,q,r,s,t,u=c.getElementsByTagName("body")[0];!u||(m=1,t="padding:0;margin:0;border:",r="position:absolute;top:0;left:0;width:1px;height:1px;",s=t+"0;visibility:hidden;",n="style='"+r+t+"5px solid #000;",q="
            "+""+"
            ",d=c.createElement("div"),d.style.cssText=s+"width:0;height:0;position:static;top:0;margin-top:"+m+"px",u.insertBefore(d,u.firstChild),p=c.createElement("div"),d.appendChild(p),p.innerHTML="
            t
            ",k=p.getElementsByTagName("td"),o=k[0].offsetHeight===0,k[0].style.display="",k[1].style.display="none",b.reliableHiddenOffsets=o&&k[0].offsetHeight===0,a.getComputedStyle&&(p.innerHTML="",l=c.createElement("div"),l.style.width="0",l.style.marginRight="0",p.style.width="2px",p.appendChild(l),b.reliableMarginRight=(parseInt((a.getComputedStyle(l,null)||{marginRight:0}).marginRight,10)||0)===0),typeof p.style.zoom!="undefined"&&(p.innerHTML="",p.style.width=p.style.padding="1px",p.style.border=0,p.style.overflow="hidden",p.style.display="inline",p.style.zoom=1,b.inlineBlockNeedsLayout=p.offsetWidth===3,p.style.display="block",p.style.overflow="visible",p.innerHTML="
            ",b.shrinkWrapBlocks=p.offsetWidth!==3),p.style.cssText=r+s,p.innerHTML=q,e=p.firstChild,g=e.firstChild,i=e.nextSibling.firstChild.firstChild,j={doesNotAddBorder:g.offsetTop!==5,doesAddBorderForTableAndCells:i.offsetTop===5},g.style.position="fixed",g.style.top="20px",j.fixedPosition=g.offsetTop===20||g.offsetTop===15,g.style.position=g.style.top="",e.style.overflow="hidden",e.style.position="relative",j.subtractsBorderForOverflowNotVisible=g.offsetTop===-5,j.doesNotIncludeMarginInBodyOffset=u.offsetTop!==m,a.getComputedStyle&&(p.style.marginTop="1%",b.pixelMargin=(a.getComputedStyle(p,null)||{marginTop:0}).marginTop!=="1%"),typeof d.style.zoom!="undefined"&&(d.style.zoom=1),u.removeChild(d),l=p=d=null,f.extend(b,j))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e1,null,!1)},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){var d=2;typeof a!="string"&&(c=a,a="fx",d--);if(arguments.length1)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,f.prop,a,b,arguments.length>1)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.type]||f.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.type]||f.valHooks[g.nodeName.toLowerCase()];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h,i=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;i=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/(?:^|\s)hover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function( +a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler,g=p.selector),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;le&&j.push({elem:this,matches:d.slice(e)});for(k=0;k0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return bc[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));o.match.globalPOS=p;var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="

            ";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="
            ";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h0)for(h=g;h=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/]","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*",""],legend:[1,"
            ","
            "],thead:[1,"","
            "],tr:[2,"","
            "],td:[3,"","
            "],col:[2,"","
            "],area:[1,"",""],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div
            ","
            "]),f.fn.extend({text:function(a){return f.access(this,function(a){return a===b?f.text(this):this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f +.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){return f.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1>");try{for(;d1&&l0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||f.isXMLDoc(a)||!bc.test("<"+a.nodeName+">")?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g,h,i,j=[];b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);for(var k=0,l;(l=a[k])!=null;k++){typeof l=="number"&&(l+="");if(!l)continue;if(typeof l=="string")if(!_.test(l))l=b.createTextNode(l);else{l=l.replace(Y,"<$1>");var m=(Z.exec(l)||["",""])[1].toLowerCase(),n=bg[m]||bg._default,o=n[0],p=b.createElement("div"),q=bh.childNodes,r;b===c?bh.appendChild(p):U(b).appendChild(p),p.innerHTML=n[1]+l+n[2];while(o--)p=p.lastChild;if(!f.support.tbody){var s=$.test(l),t=m==="table"&&!s?p.firstChild&&p.firstChild.childNodes:n[1]===""&&!s?p.childNodes:[];for(i=t.length-1;i>=0;--i)f.nodeName(t[i],"tbody")&&!t[i].childNodes.length&&t[i].parentNode.removeChild(t[i])}!f.support.leadingWhitespace&&X.test(l)&&p.insertBefore(b.createTextNode(X.exec(l)[0]),p.firstChild),l=p.childNodes,p&&(p.parentNode.removeChild(p),q.length>0&&(r=q[q.length-1],r&&r.parentNode&&r.parentNode.removeChild(r)))}var u;if(!f.support.appendChecked)if(l[0]&&typeof (u=l.length)=="number")for(i=0;i1)},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=by(a,"opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bu.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(by)return by(a,c)},swap:function(a,b,c){var d={},e,f;for(f in b)d[f]=a.style[f],a.style[f]=b[f];e=c.call(a);for(f in b)a.style[f]=d[f];return e}}),f.curCSS=f.css,c.defaultView&&c.defaultView.getComputedStyle&&(bz=function(a,b){var c,d,e,g,h=a.style;b=b.replace(br,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b))),!f.support.pixelMargin&&e&&bv.test(b)&&bt.test(c)&&(g=h.width,h.width=c,c=e.width,h.width=g);return c}),c.documentElement.currentStyle&&(bA=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f==null&&g&&(e=g[b])&&(f=e),bt.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),by=bz||bA,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth!==0?bB(a,b,d):f.swap(a,bw,function(){return bB(a,b,d)})},set:function(a,b){return bs.test(b)?b+"px":b}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return bq.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bp,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bp.test(g)?g.replace(bp,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){return f.swap(a,{display:"inline-block"},function(){return b?by(a,"margin-right"):a.style.marginRight})}})}),f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)}),f.each({margin:"",padding:"",border:"Width"},function(a,b){f.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bx[d]+b]=e[d]||e[d-2]||e[0];return f}}});var bC=/%20/g,bD=/\[\]$/,bE=/\r?\n/g,bF=/#.*$/,bG=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bH=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bI=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bJ=/^(?:GET|HEAD)$/,bK=/^\/\//,bL=/\?/,bM=/)<[^<]*)*<\/script>/gi,bN=/^(?:select|textarea)/i,bO=/\s+/,bP=/([?&])_=[^&]*/,bQ=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bR=f.fn.load,bS={},bT={},bU,bV,bW=["*/"]+["*"];try{bU=e.href}catch(bX){bU=c.createElement("a"),bU.href="",bU=bU.href}bV=bQ.exec(bU.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bR)return bR.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("
            ").append(c.replace(bM,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bN.test(this.nodeName)||bH.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bE,"\r\n")}}):{name:b.name,value:c.replace(bE,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b$(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b$(a,b);return a},ajaxSettings:{url:bU,isLocal:bI.test(bV[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bY(bS),ajaxTransport:bY(bT),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?ca(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cb(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bG.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bF,"").replace(bK,bV[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bO),d.crossDomain==null&&(r=bQ.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bV[1]&&r[2]==bV[2]&&(r[3]||(r[1]==="http:"?80:443))==(bV[3]||(bV[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),bZ(bS,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bJ.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bL.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bP,"$1_="+x);d.url=y+(y===d.url?(bL.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bW+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=bZ(bT,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)b_(g,a[g],c,e);return d.join("&").replace(bC,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cc=f.now(),cd=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cc++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=typeof b.data=="string"&&/^application\/x\-www\-form\-urlencoded/.test(b.contentType);if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(cd.test(b.url)||e&&cd.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(cd,l),b.url===j&&(e&&(k=k.replace(cd,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var ce=a.ActiveXObject?function(){for(var a in cg)cg[a](0,1)}:!1,cf=0,cg;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ch()||ci()}:ch,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,ce&&delete cg[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n);try{m.text=h.responseText}catch(a){}try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cf,ce&&(cg||(cg={},f(a).unload(ce)),cg[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var cj={},ck,cl,cm=/^(?:toggle|show|hide)$/,cn=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,co,cp=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cq;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(ct("show",3),a,b,c);for(var g=0,h=this.length;g=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);f.fn[a]=function(e){return f.access(this,function(a,e,g){var h=cy(a);if(g===b)return h?c in h?h[c]:f.support.boxModel&&h.document.documentElement[e]||h.document.body[e]:a[e];h?h.scrollTo(d?f(h).scrollLeft():g,d?g:f(h).scrollTop()):a[e]=g},a,e,arguments.length,null)}}),f.each({Height:"height",Width:"width"},function(a,c){var d="client"+a,e="scroll"+a,g="offset"+a;f.fn["inner"+a]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,c,"padding")):this[c]():null},f.fn["outer"+a]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,c,a?"margin":"border")):this[c]():null},f.fn[c]=function(a){return f.access(this,function(a,c,h){var i,j,k,l;if(f.isWindow(a)){i=a.document,j=i.documentElement[d];return f.support.boxModel&&j||i.body&&i.body[d]||j}if(a.nodeType===9){i=a.documentElement;if(i[d]>=i[e])return i[d];return Math.max(a.body[e],i[e],a.body[g],i[g])}if(h===b){k=f.css(a,c),l=parseFloat(k);return f.isNumeric(l)?l:k}f(a).css(c,h)},c,a,arguments.length,null)}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window); \ No newline at end of file diff --git a/samples/Todo/TodoWeb/Scripts/jquery.tmpl.js b/samples/Todo/TodoWeb/Scripts/jquery.tmpl.js new file mode 100644 index 000000000..1c5b2d62f --- /dev/null +++ b/samples/Todo/TodoWeb/Scripts/jquery.tmpl.js @@ -0,0 +1,486 @@ +/* + * jQuery Templating Plugin + * Copyright 2010, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + */ +(function( jQuery, undefined ){ + var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /, + newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = []; + + function newTmplItem( options, parentItem, fn, data ) { + // Returns a template item data structure for a new rendered instance of a template (a 'template item'). + // The content field is a hierarchical array of strings and nested items (to be + // removed and replaced by nodes field of dom elements, once inserted in DOM). + var newItem = { + data: data || (parentItem ? parentItem.data : {}), + _wrap: parentItem ? parentItem._wrap : null, + tmpl: null, + parent: parentItem || null, + nodes: [], + calls: tiCalls, + nest: tiNest, + wrap: tiWrap, + html: tiHtml, + update: tiUpdate + }; + if ( options ) { + jQuery.extend( newItem, options, { nodes: [], parent: parentItem } ); + } + if ( fn ) { + // Build the hierarchical content to be used during insertion into DOM + newItem.tmpl = fn; + newItem._ctnt = newItem._ctnt || newItem.tmpl( jQuery, newItem ); + newItem.key = ++itemKey; + // Keep track of new template item, until it is stored as jQuery Data on DOM element + (stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem; + } + return newItem; + } + + // Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core). + jQuery.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" + }, function( name, original ) { + jQuery.fn[ name ] = function( selector ) { + var ret = [], insert = jQuery( selector ), elems, i, l, tmplItems, + parent = this.length === 1 && this[0].parentNode; + + appendToTmplItems = newTmplItems || {}; + if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { + insert[ original ]( this[0] ); + ret = this; + } else { + for ( i = 0, l = insert.length; i < l; i++ ) { + cloneIndex = i; + elems = (i > 0 ? this.clone(true) : this).get(); + jQuery.fn[ original ].apply( jQuery(insert[i]), elems ); + ret = ret.concat( elems ); + } + cloneIndex = 0; + ret = this.pushStack( ret, name, insert.selector ); + } + tmplItems = appendToTmplItems; + appendToTmplItems = null; + jQuery.tmpl.complete( tmplItems ); + return ret; + }; + }); + + jQuery.fn.extend({ + // Use first wrapped element as template markup. + // Return wrapped set of template items, obtained by rendering template against data. + tmpl: function( data, options, parentItem ) { + return jQuery.tmpl( this[0], data, options, parentItem ); + }, + + // Find which rendered template item the first wrapped DOM element belongs to + tmplItem: function() { + return jQuery.tmplItem( this[0] ); + }, + + // Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template. + template: function( name ) { + return jQuery.template( name, this[0] ); + }, + + domManip: function( args, table, callback, options ) { + // This appears to be a bug in the appendTo, etc. implementation + // it should be doing .call() instead of .apply(). See #6227 + if ( args[0] && args[0].nodeType ) { + var dmArgs = jQuery.makeArray( arguments ), argsLength = args.length, i = 0, tmplItem; + while ( i < argsLength && !(tmplItem = jQuery.data( args[i++], "tmplItem" ))) {} + if ( argsLength > 1 ) { + dmArgs[0] = [jQuery.makeArray( args )]; + } + if ( tmplItem && cloneIndex ) { + dmArgs[2] = function( fragClone ) { + // Handler called by oldManip when rendered template has been inserted into DOM. + jQuery.tmpl.afterManip( this, fragClone, callback ); + }; + } + oldManip.apply( this, dmArgs ); + } else { + oldManip.apply( this, arguments ); + } + cloneIndex = 0; + if ( !appendToTmplItems ) { + jQuery.tmpl.complete( newTmplItems ); + } + return this; + } + }); + + jQuery.extend({ + // Return wrapped set of template items, obtained by rendering template against data. + tmpl: function( tmpl, data, options, parentItem ) { + var ret, topLevel = !parentItem; + if ( topLevel ) { + // This is a top-level tmpl call (not from a nested template using {{tmpl}}) + parentItem = topTmplItem; + tmpl = jQuery.template[tmpl] || jQuery.template( null, tmpl ); + wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level + } else if ( !tmpl ) { + // The template item is already associated with DOM - this is a refresh. + // Re-evaluate rendered template for the parentItem + tmpl = parentItem.tmpl; + newTmplItems[parentItem.key] = parentItem; + parentItem.nodes = []; + if ( parentItem.wrapped ) { + updateWrapped( parentItem, parentItem.wrapped ); + } + // Rebuild, without creating a new template item + return jQuery( build( parentItem, null, parentItem.tmpl( jQuery, parentItem ) )); + } + if ( !tmpl ) { + return []; // Could throw... + } + if ( typeof data === "function" ) { + data = data.call( parentItem || {} ); + } + if ( options && options.wrapped ) { + updateWrapped( options, options.wrapped ); + } + ret = jQuery.isArray( data ) ? + jQuery.map( data, function( dataItem ) { + return dataItem ? newTmplItem( options, parentItem, tmpl, dataItem ) : null; + }) : + [ newTmplItem( options, parentItem, tmpl, data ) ]; + return topLevel ? jQuery( build( parentItem, null, ret ) ) : ret; + }, + + // Return rendered template item for an element. + tmplItem: function( elem ) { + var tmplItem; + if ( elem instanceof jQuery ) { + elem = elem[0]; + } + while ( elem && elem.nodeType === 1 && !(tmplItem = jQuery.data( elem, "tmplItem" )) && (elem = elem.parentNode) ) {} + return tmplItem || topTmplItem; + }, + + // Set: + // Use $.template( name, tmpl ) to cache a named template, + // where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc. + // Use $( "selector" ).template( name ) to provide access by name to a script block template declaration. + + // Get: + // Use $.template( name ) to access a cached template. + // Also $( selectorToScriptBlock ).template(), or $.template( null, templateString ) + // will return the compiled template, without adding a name reference. + // If templateString includes at least one HTML tag, $.template( templateString ) is equivalent + // to $.template( null, templateString ) + template: function( name, tmpl ) { + if (tmpl) { + // Compile template and associate with name + if ( typeof tmpl === "string" ) { + // This is an HTML string being passed directly in. + tmpl = buildTmplFn( tmpl ) + } else if ( tmpl instanceof jQuery ) { + tmpl = tmpl[0] || {}; + } + if ( tmpl.nodeType ) { + // If this is a template block, use cached copy, or generate tmpl function and cache. + tmpl = jQuery.data( tmpl, "tmpl" ) || jQuery.data( tmpl, "tmpl", buildTmplFn( tmpl.innerHTML )); + } + return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl; + } + // Return named compiled template + return name ? (typeof name !== "string" ? jQuery.template( null, name ): + (jQuery.template[name] || + // If not in map, treat as a selector. (If integrated with core, use quickExpr.exec) + jQuery.template( null, htmlExpr.test( name ) ? name : jQuery( name )))) : null; + }, + + encode: function( text ) { + // Do HTML encoding replacing < > & and ' and " by corresponding entities. + return ("" + text).split("<").join("<").split(">").join(">").split('"').join(""").split("'").join("'"); + } + }); + + jQuery.extend( jQuery.tmpl, { + tag: { + "tmpl": { + _default: { $2: "null" }, + open: "if($notnull_1){_=_.concat($item.nest($1,$2));}" + // tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions) + // This means that {{tmpl foo}} treats foo as a template (which IS a function). + // Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}. + }, + "wrap": { + _default: { $2: "null" }, + open: "$item.calls(_,$1,$2);_=[];", + close: "call=$item.calls();_=call._.concat($item.wrap(call,_));" + }, + "each": { + _default: { $2: "$index, $value" }, + open: "if($notnull_1){$.each($1a,function($2){with(this){", + close: "}});}" + }, + "if": { + open: "if(($notnull_1) && $1a){", + close: "}" + }, + "else": { + _default: { $1: "true" }, + open: "}else if(($notnull_1) && $1a){" + }, + "html": { + // Unecoded expression evaluation. + open: "if($notnull_1){_.push($1a);}" + }, + "=": { + // Encoded expression evaluation. Abbreviated form is ${}. + _default: { $1: "$data" }, + open: "if($notnull_1){_.push($.encode($1a));}" + }, + "!": { + // Comment tag. Skipped by parser + open: "" + } + }, + + // This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events + complete: function( items ) { + newTmplItems = {}; + }, + + // Call this from code which overrides domManip, or equivalent + // Manage cloning/storing template items etc. + afterManip: function afterManip( elem, fragClone, callback ) { + // Provides cloned fragment ready for fixup prior to and after insertion into DOM + var content = fragClone.nodeType === 11 ? + jQuery.makeArray(fragClone.childNodes) : + fragClone.nodeType === 1 ? [fragClone] : []; + + // Return fragment to original caller (e.g. append) for DOM insertion + callback.call( elem, fragClone ); + + // Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data. + storeTmplItems( content ); + cloneIndex++; + } + }); + + //========================== Private helper functions, used by code above ========================== + + function build( tmplItem, nested, content ) { + // Convert hierarchical content into flat string array + // and finally return array of fragments ready for DOM insertion + var frag, ret = content ? jQuery.map( content, function( item ) { + return (typeof item === "string") ? + // Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM. + (tmplItem.key ? item.replace( /(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2" ) : item) : + // This is a child template item. Build nested template. + build( item, tmplItem, item._ctnt ); + }) : + // If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}. + tmplItem; + if ( nested ) { + return ret; + } + + // top-level template + ret = ret.join(""); + + // Support templates which have initial or final text nodes, or consist only of text + // Also support HTML entities within the HTML markup. + ret.replace( /^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function( all, before, middle, after) { + frag = jQuery( middle ).get(); + + storeTmplItems( frag ); + if ( before ) { + frag = unencode( before ).concat(frag); + } + if ( after ) { + frag = frag.concat(unencode( after )); + } + }); + return frag ? frag : unencode( ret ); + } + + function unencode( text ) { + // Use createElement, since createTextNode will not render HTML entities correctly + var el = document.createElement( "div" ); + el.innerHTML = text; + return jQuery.makeArray(el.childNodes); + } + + // Generate a reusable function that will serve to render a template against data + function buildTmplFn( markup ) { + return new Function("jQuery","$item", + "var $=jQuery,call,_=[],$data=$item.data;" + + + // Introduce the data as local variables using with(){} + "with($data){_.push('" + + + // Convert the template into pure JavaScript + jQuery.trim(markup) + .replace( /([\\'])/g, "\\$1" ) + .replace( /[\r\t\n]/g, " " ) + .replace( /\$\{([^\}]*)\}/g, "{{= $1}}" ) + .replace( /\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g, + function( all, slash, type, fnargs, target, parens, args ) { + var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect; + if ( !tag ) { + throw "Template command not found: " + type; + } + def = tag._default || []; + if ( parens && !/\w$/.test(target)) { + target += parens; + parens = ""; + } + if ( target ) { + target = unescape( target ); + args = args ? ("," + unescape( args ) + ")") : (parens ? ")" : ""); + // Support for target being things like a.toLowerCase(); + // In that case don't call with template item as 'this' pointer. Just evaluate... + expr = parens ? (target.indexOf(".") > -1 ? target + parens : ("(" + target + ").call($item" + args)) : target; + exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))"; + } else { + exprAutoFnDetect = expr = def.$1 || "null"; + } + fnargs = unescape( fnargs ); + return "');" + + tag[ slash ? "close" : "open" ] + .split( "$notnull_1" ).join( target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true" ) + .split( "$1a" ).join( exprAutoFnDetect ) + .split( "$1" ).join( expr ) + .split( "$2" ).join( fnargs ? + fnargs.replace( /\s*([^\(]+)\s*(\((.*?)\))?/g, function( all, name, parens, params ) { + params = params ? ("," + params + ")") : (parens ? ")" : ""); + return params ? ("(" + name + ").call($item" + params) : all; + }) + : (def.$2||"") + ) + + "_.push('"; + }) + + "');}return _;" + ); + } + function updateWrapped( options, wrapped ) { + // Build the wrapped content. + options._wrap = build( options, true, + // Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string. + jQuery.isArray( wrapped ) ? wrapped : [htmlExpr.test( wrapped ) ? wrapped : jQuery( wrapped ).html()] + ).join(""); + } + + function unescape( args ) { + return args ? args.replace( /\\'/g, "'").replace(/\\\\/g, "\\" ) : null; + } + function outerHtml( elem ) { + var div = document.createElement("div"); + div.appendChild( elem.cloneNode(true) ); + return div.innerHTML; + } + + // Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance. + function storeTmplItems( content ) { + var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m; + for ( i = 0, l = content.length; i < l; i++ ) { + if ( (elem = content[i]).nodeType !== 1 ) { + continue; + } + elems = elem.getElementsByTagName("*"); + for ( m = elems.length - 1; m >= 0; m-- ) { + processItemKey( elems[m] ); + } + processItemKey( elem ); + } + function processItemKey( el ) { + var pntKey, pntNode = el, pntItem, tmplItem, key; + // Ensure that each rendered template inserted into the DOM has its own template item, + if ( (key = el.getAttribute( tmplItmAtt ))) { + while ( pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute( tmplItmAtt ))) { } + if ( pntKey !== key ) { + // The next ancestor with a _tmplitem expando is on a different key than this one. + // So this is a top-level element within this template item + // Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment. + pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute( tmplItmAtt ) || 0)) : 0; + if ( !(tmplItem = newTmplItems[key]) ) { + // The item is for wrapped content, and was copied from the temporary parent wrappedItem. + tmplItem = wrappedItems[key]; + tmplItem = newTmplItem( tmplItem, newTmplItems[pntNode]||wrappedItems[pntNode], null, true ); + tmplItem.key = ++itemKey; + newTmplItems[itemKey] = tmplItem; + } + if ( cloneIndex ) { + cloneTmplItem( key ); + } + } + el.removeAttribute( tmplItmAtt ); + } else if ( cloneIndex && (tmplItem = jQuery.data( el, "tmplItem" )) ) { + // This was a rendered element, cloned during append or appendTo etc. + // TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem. + cloneTmplItem( tmplItem.key ); + newTmplItems[tmplItem.key] = tmplItem; + pntNode = jQuery.data( el.parentNode, "tmplItem" ); + pntNode = pntNode ? pntNode.key : 0; + } + if ( tmplItem ) { + pntItem = tmplItem; + // Find the template item of the parent element. + // (Using !=, not !==, since pntItem.key is number, and pntNode may be a string) + while ( pntItem && pntItem.key != pntNode ) { + // Add this element as a top-level node for this rendered template item, as well as for any + // ancestor items between this item and the item of its parent element + pntItem.nodes.push( el ); + pntItem = pntItem.parent; + } + // Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering... + delete tmplItem._ctnt; + delete tmplItem._wrap; + // Store template item as jQuery data on the element + jQuery.data( el, "tmplItem", tmplItem ); + } + function cloneTmplItem( key ) { + key = key + keySuffix; + tmplItem = newClonedItems[key] = + (newClonedItems[key] || newTmplItem( tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent, null, true )); + } + } + } + + //---- Helper functions for template item ---- + + function tiCalls( content, tmpl, data, options ) { + if ( !content ) { + return stack.pop(); + } + stack.push({ _: content, tmpl: tmpl, item:this, data: data, options: options }); + } + + function tiNest( tmpl, data, options ) { + // nested template, using {{tmpl}} tag + return jQuery.tmpl( jQuery.template( tmpl ), data, options, this ); + } + + function tiWrap( call, wrapped ) { + // nested template, using {{wrap}} tag + var options = call.options || {}; + options.wrapped = wrapped; + // Apply the template, which may incorporate wrapped content, + return jQuery.tmpl( jQuery.template( call.tmpl ), call.data, options, call.item ); + } + + function tiHtml( filter, textOnly ) { + var wrapped = this._wrap; + return jQuery.map( + jQuery( jQuery.isArray( wrapped ) ? wrapped.join("") : wrapped ).filter( filter || "*" ), + function(e) { + return textOnly ? + e.innerText || e.textContent : + e.outerHTML || outerHtml(e); + }); + } + + function tiUpdate() { + var coll = this.nodes; + jQuery.tmpl( null, null, null, this).insertBefore( coll[0] ); + jQuery( coll ).remove(); + } +})( jQuery ); diff --git a/samples/Todo/TodoWeb/TodoWeb.csproj b/samples/Todo/TodoWeb/TodoWeb.csproj new file mode 100644 index 000000000..70d996aad --- /dev/null +++ b/samples/Todo/TodoWeb/TodoWeb.csproj @@ -0,0 +1,90 @@ + + + + + Debug + AnyCPU + + + 2.0 + {6B543006-1D4E-42A9-8679-4F43CA91DD4F} + {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + TodoWeb + TodoWeb + v4.5 + true + + + + + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + + + + + + True + True + 0 + / + http://localhost:53850/ + False + False + + + False + + + + + + \ No newline at end of file diff --git a/samples/Todo/TodoWeb/favicon.ico b/samples/Todo/TodoWeb/favicon.ico new file mode 100644 index 000000000..96d3eb672 Binary files /dev/null and b/samples/Todo/TodoWeb/favicon.ico differ diff --git a/samples/Todo/packages/nuget.exe b/samples/Todo/packages/nuget.exe new file mode 100644 index 000000000..e313ff958 Binary files /dev/null and b/samples/Todo/packages/nuget.exe differ diff --git a/samples/Todo/packages/nuget.targets b/samples/Todo/packages/nuget.targets new file mode 100644 index 000000000..51fc72bd2 --- /dev/null +++ b/samples/Todo/packages/nuget.targets @@ -0,0 +1,16 @@ + + + + false + $([System.IO.Directory]::GetParent($(MSBuildThisFileDirectory))) + $(MSBuildThisFileDirectory)nuget.exe install "$(MSBuildProjectDirectory)\packages.config" -o "$(PackagesDir)" + + RestorePackages; + $(BuildDependsOn); + + + + + + + \ No newline at end of file diff --git a/samples/Todo/packages/repositories.config b/samples/Todo/packages/repositories.config new file mode 100644 index 000000000..9f3521892 --- /dev/null +++ b/samples/Todo/packages/repositories.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/Core/App/App.csproj b/src/Core/App/App.csproj new file mode 100644 index 000000000..86b0342e8 --- /dev/null +++ b/src/Core/App/App.csproj @@ -0,0 +1,70 @@ + + + + Debug + AnyCPU + 9.0.21022 + 2.0 + {1772A38C-7204-42DC-B81D-6C74D17A4F66} + Exe + Properties + ScriptSharp + ssc + true + ..\..\ScriptSharp.snk + false + v4.0 + 512 + + + + true + full + false + ..\..\..\bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + ..\..\..\bin\Release\ + TRACE + prompt + 4 + true + + + + + + + {9f14036f-673f-418e-b817-7e2289d7f3f6} + Compiler + + + + + FileInputStreamSource.cs + + + FileOutputStreamSource.cs + + + + Properties\ScriptSharp.cs + + + + + + + \ No newline at end of file diff --git a/src/Core/App/Application.cs b/src/Core/App/Application.cs new file mode 100644 index 000000000..b614c6b2d --- /dev/null +++ b/src/Core/App/Application.cs @@ -0,0 +1,242 @@ +// Application.cs +// Script#/Core/Compiler +// This source code is subject to terms and conditions of the Apache License, Version 2.0. +// + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.IO; + +namespace ScriptSharp { + + internal static class Application { + + private static readonly string AboutTextFormat = @" +Script# Compiler v{0} (C# to JavaScript compiler) +Written by Nikhil Kothari. +More information at http://projects.nikhilk.net/ScriptSharp. +"; + + private static readonly string UsageText = @" +Usage: + ssc [/nologo] [/?] + [/debug] + [/tests] + [/minimize] + [/D:] + [/template: