commit 2c734345e7904979c564cb82ee77c8691be4ba97 Author: James South Date: Mon May 28 16:23:27 2012 +0100 First Commit Former-commit-id: 267292b5e198d1edec106c8b46222cdbcb9d8777 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..a664be3a8 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,49 @@ +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain + +*.jpg binary +*.png binary +*.gif binary + +*.cs text=auto diff=csharp +*.vb text=auto +*.c text=auto +*.cpp text=auto +*.cxx text=auto +*.h text=auto +*.hxx text=auto +*.py text=auto +*.rb text=auto +*.java text=auto +*.html text=auto +*.htm text=auto +*.css text=auto +*.scss text=auto +*.sass text=auto +*.less text=auto +*.js text=auto +*.lisp text=auto +*.clj text=auto +*.sql text=auto +*.php text=auto +*.lua text=auto +*.m text=auto +*.asm text=auto +*.erl text=auto +*.fs text=auto +*.fsx text=auto +*.hs text=auto + +*.csproj text=auto merge=union +*.vbproj text=auto merge=union +*.fsproj text=auto merge=union +*.dbproj text=auto merge=union +*.sln text=auto eol=crlf merge=union diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..aa8811c67 --- /dev/null +++ b/.gitignore @@ -0,0 +1,165 @@ + +################# +## Eclipse +################# + +*.pydevproject +.project +.metadata +bin/** +tmp/** +tmp/**/* +*.tmp +*.bak +*.swp +*~.nib +local.properties +.classpath +.settings/ +.loadpath + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# PDT-specific +.buildpath + + +################# +## Visual Studio +################# + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results +**/[Dd]ebug/ +**/[Rr]elease/ +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.vspscc +.builds +**/*.dotCover + +## TODO: If you have NuGet Package Restore enabled, uncomment this +#**/packages/ + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf + +# Visual Studio profiler +*.psess +*.vsp + +# ReSharper is a .NET coding add-in +_ReSharper* + +# Installshield output folder +[Ee]xpress + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish + +# Others +[Bb]in +[Oo]bj +sql +TestResults +*.Cache +ClientBin +stylecop.* +~$* +*.dbmdl +Generated_Code #added for RIA/Silverlight projects + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML + + + +############ +## Windows +############ + +# Windows image file caches +Thumbs.db + +# Folder config file +Desktop.ini + + +############# +## Python +############# + +*.py[co] + +# Packages +*.egg +*.egg-info +dist +build +eggs +parts +bin +var +sdist +develop-eggs +.installed.cfg + +# Installer logs +pip-log.txt + +# Unit test / coverage reports +.coverage +.tox + +#Translations +*.mo + +#Mr Developer +.mr.developer.cfg + +# Mac crap +.DS_Store diff --git a/README.md b/README.md new file mode 100644 index 000000000..2c3ade838 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +ImageProcessor +=============== + +ImageProcessor is a library for on the fly processing of image files using Asp.Net + +The library architecture is highly extensible and allows for easy extension. + +Core plugins at present include: + + - Resize + - Crop + - Quality (The quality to set the output for jpeg files) + - Filter (Image filters including sepia, greyscale, blackwhite, lomograph) + - Vignette + - Format (Sets the output format) + - Alpha (Sets opacity) + +The library consists of two binaries: ImageProcessor.dll and ImageProcessor.Web.dll. + +ImageProcessor.dll contains all the core functionality that allows for image manipulation via the `ImageFactory` class. This has a fluent API which allows you to easily chain methods to deliver the desired output. + +e.g. + + // Read a file and resize it. + var photoBytes = File.ReadAllBytes(file); + var before = DateTime.Now; + var quality = 90; + var format = ImageFormat.Jpeg; + var thumbnailSize = 150; + + byte[] resized; + + using (var inStream = new MemoryStream(photoBytes)) + { + using (var outStream = new MemoryStream()) + { + using (ImageFactory imageFactory = new ImageFactory()) + { + // Load, resize and save an image. + imageFactory.Load(inStream).Format(format).Quality(quality).Resize(thumbnailSize, 0).Save(outStream); + } + + resized = outStream.ToArray(); + } + } + +ImageProcessor.Web.dll contains a HttpModule which captures internal and external requests automagically processing them based on values captured through querystring parameters. + +Using the HttpModule requires no code writing at all. Just reference the binaries and add the relevant sections to the web.config + +Image requests suffixed with QueryString parameters will then be processed and cached to the server allowing for easy and efficient parsing of following requests. + +e.g. + + your resized image + +Will resize your image to 200px wide whilst keeping the correct aspect ratio. + + diff --git a/src/ImageProcessor.Web/Caching/DiskCache.cs b/src/ImageProcessor.Web/Caching/DiskCache.cs new file mode 100644 index 000000000..4ce6021fb --- /dev/null +++ b/src/ImageProcessor.Web/Caching/DiskCache.cs @@ -0,0 +1,213 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Caching +{ + #region Using + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Threading; + using System.Threading.Tasks; + using System.Web; + using System.Web.Hosting; + using ImageProcessor.Helpers.Extensions; + using ImageProcessor.Web.Config; + using ImageProcessor.Web.Helpers; + #endregion + + /// + /// Encapsulates methods to handle disk caching of images. + /// + internal sealed class DiskCache + { + #region Fields + /// + /// The maximum number or time a new file should be cached before checking the + /// cache controller and running any clearing mechanisms. + /// + /// + /// NTFS file systems can handle up to 8000 files in one directory. The Cache controller will clear out any + /// time we hit 6000 so if we tell the handler to run at every 1000 times an image is added to the cache we + /// should have a 1000 file buffer. + /// + internal const int MaxRunsBeforeCacheClear = 1000; + + /// + /// The maximum number of days to cache files on the system for. + /// + internal static readonly int MaxFileCachedDuration = ImageProcessorConfig.Instance.MaxCacheDays; + + /// + /// The object to lock against. + /// + private static readonly object SyncRoot = new object(); + + /// + /// The default paths for Cached folders on the server. + /// + private static readonly string CachePath = ImageProcessorConfig.Instance.VirtualCachePath; + + /// + /// The maximum number of files allowed in the directory. + /// + /// + /// NTFS Folder can handle up to 8000 files in a directory. + /// This buffer will help us to ensure that we rarely hit anywhere near that limit. + /// + private const int MaxFilesCount = 6000; + #endregion + + #region Methods + /// + /// Gets the full transformed cached path for the image. + /// + /// The original image path. + /// The original image name. + /// The full cached path for the image. + internal static string GetCachePath(string imagePath, string imageName) + { + string virtualCachePath = CachePath; + string absoluteCachePath = HostingEnvironment.MapPath(virtualCachePath); + string cachedPath = string.Empty; + + if (absoluteCachePath != null) + { + DirectoryInfo di = new DirectoryInfo(absoluteCachePath); + + if (!di.Exists) + { + // Create the directory. + Directory.CreateDirectory(absoluteCachePath); + } + + string cachedFileName = string.Format("{0}{1}", imagePath.ToMD5Fingerprint(), imageName.Substring(imageName.LastIndexOf(".", StringComparison.Ordinal))); + cachedPath = Path.Combine(absoluteCachePath, cachedFileName); + } + + return cachedPath; + } + + /// + /// Converts an absolute file path + /// + /// The absolute path to convert. + /// The from the current context. + /// The virtual path to the file. + internal static string GetVirtualPath(string absolutePath, HttpRequest request) + { + string applicationPath = request.PhysicalApplicationPath; + string virtualDir = request.ApplicationPath; + virtualDir = virtualDir == "/" ? virtualDir : (virtualDir + "/"); + if (applicationPath != null) + { + return absolutePath.Replace(applicationPath, virtualDir).Replace(@"\", "/"); + } + + throw new InvalidOperationException("We can only map an absolute back to a relative path if the application path is available."); + } + + /// + /// Purges any files from the filesystem cache in a background thread. + /// + internal static void PurgeCachedFolders() + { + ThreadStart threadStart = PurgeFolders; + + Thread thread = new Thread(threadStart) + { + IsBackground = true + }; + + thread.Start(); + } + + /// + /// Returns a value indicating whether the original file has been updated. + /// + /// The original image path. + /// The cached image path. + /// + /// True if the the original file has been updated; otherwise, false. + /// + internal static bool IsUpdatedFile(string imagePath, string cachedImagePath) + { + if (File.Exists(imagePath) && File.Exists(cachedImagePath)) + { + FileInfo imageFileInfo = new FileInfo(imagePath); + FileInfo cachedImageFileInfo = new FileInfo(cachedImagePath); + + return !new FileCompareLastwritetime().Equals(imageFileInfo, cachedImageFileInfo); + } + + return true; + } + + /// + /// Sets the LastWriteTime of the cached file to match the original file. + /// + /// The original image path. + /// The cached image path. + internal static void SetCachedLastWriteTime(string imagePath, string cachedImagePath) + { + if (File.Exists(imagePath) && File.Exists(cachedImagePath)) + { + lock (SyncRoot) + { + DateTime dateTime = File.GetLastWriteTime(imagePath); + File.SetLastWriteTime(cachedImagePath, dateTime); + } + } + } + + /// + /// Purges any files from the filesystem cache in the given folders. + /// + private static void PurgeFolders() + { + string folder = HostingEnvironment.MapPath(CachePath); + + if (folder != null) + { + DirectoryInfo directoryInfo = new DirectoryInfo(folder); + + if (directoryInfo.Exists) + { + // Get all the files in the cache ordered by LastAccessTime - oldest first. + List fileInfos = directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories) + .OrderBy(x => x.LastAccessTime).ToList(); + + int counter = fileInfos.Count; + + Parallel.ForEach( + fileInfos, + fileInfo => + { + lock (SyncRoot) + { + try + { + // Delete the file if we are nearing our limit buffer. + if (counter >= MaxFilesCount || fileInfo.LastAccessTime < DateTime.Now.AddDays(-MaxFileCachedDuration)) + { + fileInfo.Delete(); + counter -= 1; + } + } + catch + { + // TODO: Sort out the try/catch. + throw; + } + } + }); + } + } + } + #endregion + } +} diff --git a/src/ImageProcessor.Web/Config/ImageCacheSection.cs b/src/ImageProcessor.Web/Config/ImageCacheSection.cs new file mode 100644 index 000000000..51a5831f4 --- /dev/null +++ b/src/ImageProcessor.Web/Config/ImageCacheSection.cs @@ -0,0 +1,76 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Config +{ + #region Using + using System.Configuration; + using ImageProcessor.Helpers.Extensions; + #endregion + + /// + /// Represents an imagecache section within a configuration file. + /// + public class ImageCacheSection : ConfigurationSection + { + /// + /// Gets or sets the virtual path of the cache folder. + /// + /// The name of the cache folder. + [ConfigurationProperty("virtualPath", DefaultValue = "~/cache", IsRequired = true)] + [StringValidator(MinLength = 3, MaxLength = 200)] + public string VirtualPath + { + get + { + string virtualPath = (string)this["virtualPath"]; + + return virtualPath.IsValidVirtualPathName() ? virtualPath : "~/cache"; + } + + set + { + this["virtualPath"] = value; + } + } + + /// + /// Gets or sets the maximum number of days to store an image in the cache. + /// + /// The maximum number of days to store an image in the cache. + /// Defaults to 7 if not set. Maximum of 28. + [ConfigurationProperty("maxDays", DefaultValue = "7", IsRequired = false)] + [IntegerValidator(ExcludeRange = false, MaxValue = 28, MinValue = 0)] + public int MaxDays + { + get + { + return (int)this["maxDays"]; + } + + set + { + this["maxDays"] = value; + } + } + + /// + /// Retrieves the cache configuration section from the current application configuration. + /// + /// The cache configuration section from the current application configuration. + public static ImageCacheSection GetConfiguration() + { + ImageCacheSection imageCacheSection = ConfigurationManager.GetSection("imageProcessor/cache") as ImageCacheSection; + + if (imageCacheSection != null) + { + return imageCacheSection; + } + + return new ImageCacheSection(); + } + } +} diff --git a/src/ImageProcessor.Web/Config/ImageProcessingSection.cs b/src/ImageProcessor.Web/Config/ImageProcessingSection.cs new file mode 100644 index 000000000..9751e3ddf --- /dev/null +++ b/src/ImageProcessor.Web/Config/ImageProcessingSection.cs @@ -0,0 +1,312 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Config +{ + #region Using + using System.Configuration; + using System.Linq; + #endregion + + /// + /// Represents an imageprocessing section within a configuration file. + /// Nested syntax adapted from http://tneustaedter.blogspot.co.uk/2011/09/how-to-create-one-or-more-nested.html + /// + public class ImageProcessingSection : ConfigurationSection + { + #region Properties + /// + /// Gets the . + /// + /// + /// The . + /// + [ConfigurationProperty("plugins", IsRequired = true)] + public PluginElementCollection Plugins + { + get + { + return this["plugins"] as PluginElementCollection; + } + } + #endregion + + #region Methods + /// + /// Retrieves the processing configuration section from the current application configuration. + /// + /// The processing configuration section from the current application configuration. + public static ImageProcessingSection GetConfiguration() + { + ImageProcessingSection imageProcessingSection = + ConfigurationManager.GetSection("imageProcessor/processing") as ImageProcessingSection; + + if (imageProcessingSection != null) + { + return imageProcessingSection; + } + + return new ImageProcessingSection(); + } + #endregion + + /// + /// Represents a PluginElement configuration element within the configuration. + /// + public class PluginElement : ConfigurationElement + { + /// + /// Gets or sets the name of the plugin file. + /// + /// The name of the plugin. + [ConfigurationProperty("name", DefaultValue = "", IsRequired = true)] + public string Name + { + get { return (string)this["name"]; } + + set { this["name"] = value; } + } + + /// + /// Gets the . + /// + /// + /// The . + /// + [ConfigurationProperty("settings", IsRequired = true)] + public SettingElementCollection Settings + { + get + { + return this["settings"] as SettingElementCollection; + } + } + } + + /// + /// Represents a PluginElementCollection collection configuration element within the configuration. + /// + public class PluginElementCollection : ConfigurationElementCollection + { + /// + /// Gets the type of the . + /// + /// + /// The of this collection. + /// + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + /// + /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. + /// + /// + /// The name of the collection; otherwise, an empty string. The default is an empty string. + /// + protected override string ElementName + { + get { return "plugin"; } + } + + /// + /// Gets or sets the + /// at the specified index within the collection. + /// + /// The index at which to get the specified object. + /// + /// The the + /// at the specified index within the collection. + /// + public PluginElement this[int index] + { + get + { + return (PluginElement)BaseGet(index); + } + + set + { + if (BaseGet(index) != null) + { + BaseRemoveAt(index); + } + + BaseAdd(index, value); + } + } + + /// + /// Creates a new PluginConfig configuration element. + /// + /// + /// A new PluginConfig configuration element. + /// + protected override ConfigurationElement CreateNewElement() + { + return new PluginElement(); + } + + /// + /// Gets the element key for a specified PluginElement configuration element. + /// + /// + /// The ConfigurationElement + /// to return the key for. + /// + /// The element key for a specified PluginElement configuration element. + protected override object GetElementKey(ConfigurationElement element) + { + return ((PluginElement)element).Name; + } + } + + /// + /// Represents a SettingElement configuration element within the configuration. + /// + public class SettingElement : ConfigurationElement + { + /// + /// Gets or sets the key of the plugin setting. + /// + /// The key of the plugin setting. + [ConfigurationProperty("key", IsRequired = true, IsKey = true)] + public string Key + { + get + { + return this["key"] as string; + } + + set + { + this["key"] = value; + } + } + + /// + /// Gets or sets the value of the plugin setting. + /// + /// The value of the plugin setting. + [ConfigurationProperty("value", IsRequired = true)] + public string Value + { + get + { + return (string)this["value"]; + } + + set + { + this["value"] = value; + } + } + } + + /// + /// Represents a SettingElementCollection collection configuration element within the configuration. + /// + public class SettingElementCollection : ConfigurationElementCollection + { + /// + /// Gets the type of the . + /// + /// + /// The of this collection. + /// + public override ConfigurationElementCollectionType CollectionType + { + get { return ConfigurationElementCollectionType.BasicMap; } + } + + /// + /// Gets the name used to identify this collection of elements in the configuration file when overridden in a derived class. + /// + /// + /// The name of the collection; otherwise, an empty string. The default is an empty string. + /// + protected override string ElementName + { + get { return "setting"; } + } + + /// + /// Gets or sets the + /// at the specified index within the collection. + /// + /// The index at which to get the specified object. + /// + /// The the + /// at the specified index within the collection. + /// + public SettingElement this[int index] + { + get + { + return (SettingElement)BaseGet(index); + } + + set + { + if (BaseGet(index) != null) + { + BaseRemoveAt(index); + } + + BaseAdd(index, value); + } + } + + /// + /// Returns the setting element with the specified key. + /// + /// knkn knk + /// jn jnj + public new SettingElement this[string key] + { + get { return (SettingElement)BaseGet(key); } + } + + /// + /// Returns a value indicating whether the settings collection contains the + /// given object. + /// + /// The key to identify the setting. + /// True if the collection contains the key; otherwise false. + public bool ContainsKey(string key) + { + object[] keys = BaseGetAllKeys(); + + return keys.Any(obj => (string)obj == key); + } + + /// + /// Gets the element key for a specified PluginElement configuration element. + /// + /// + /// The ConfigurationElement + /// to return the key for. + /// + /// The element key for a specified PluginElement configuration element. + protected override object GetElementKey(ConfigurationElement element) + { + return ((SettingElement)element).Key; + } + + /// + /// Creates a new SettingElement configuration element. + /// + /// + /// A new SettingElement configuration element. + /// + protected override ConfigurationElement CreateNewElement() + { + return new SettingElement(); + } + } + } +} diff --git a/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs b/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs new file mode 100644 index 000000000..a47ae8de5 --- /dev/null +++ b/src/ImageProcessor.Web/Config/ImageProcessorConfig.cs @@ -0,0 +1,255 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Config +{ + #region Using + using System; + using System.Collections.Generic; + using System.Linq; + using ImageProcessor.Processors; + #endregion + + /// + /// Encapsulates methods to allow the retrieval of imageprocessor settings. + /// http://csharpindepth.com/Articles/General/Singleton.aspx + /// + public class ImageProcessorConfig + { + #region Fields + /// + /// A new instance Initializes a new instance of the class. + /// intitialized lazily. + /// + private static readonly Lazy Lazy = + new Lazy(() => new ImageProcessorConfig()); + + /// + /// A collection of the elements + /// for available plugins. + /// + private static readonly Dictionary> PluginSettings = + new Dictionary>(); + + /// + /// The processing configuration section from the current application configuration. + /// + private static ImageProcessingSection imageProcessingSection; + + /// + /// The cache configuration section from the current application configuration. + /// + private static ImageCacheSection imageCacheSection; + + /// + /// The security configuration section from the current application configuration. + /// + private static ImageSecuritySection imageSecuritySection; + #endregion + + #region Constructors + /// + /// Prevents a default instance of the class from being created. + /// + private ImageProcessorConfig() + { + this.LoadGraphicsProcessors(); + } + #endregion + + #region Properties + /// + /// Gets the current instance of the class. + /// + public static ImageProcessorConfig Instance + { + get + { + return Lazy.Value; + } + } + + /// + /// Gets the list of available GraphicsProcessors. + /// + public List GraphicsProcessors { get; private set; } + + #region Caching + /// + /// Gets the maximum number of days to store images in the cache. + /// + public int MaxCacheDays + { + get + { + return GetImageCacheSection().MaxDays; + } + } + + /// + /// Gets or the virtual path of the cache folder. + /// + /// The virtual path of the cache folder. + public string VirtualCachePath + { + get + { + return GetImageCacheSection().VirtualPath; + } + } + #endregion + + #region Security + /// + /// Gets a list of whitelisted urls that images can be downloaded from. + /// + public Uri[] RemoteFileWhiteList + { + get + { + return GetImageSecuritySection().WhiteList.Cast().Select(x => x.Url).ToArray(); + } + } + + /// + /// Gets a value indicating whether the current application is allowed to download remote files. + /// + public bool AllowRemoteDownloads + { + get + { + return GetImageSecuritySection().AllowRemoteDownloads; + } + } + + /// + /// Gets the maximum length to wait in milliseconds before throwing an error requesting a remote file. + /// + public int Timeout + { + get + { + return GetImageSecuritySection().Timeout; + } + } + + /// + /// Gets the maximum allowable size in bytes of e remote file to process. + /// + public int MaxBytes + { + get + { + return GetImageSecuritySection().MaxBytes; + } + } + + /// + /// Gets the remote prefix for external files for the application. + /// + public string RemotePrefix + { + get + { + return GetImageSecuritySection().RemotePrefix; + } + } + #endregion + #endregion + + #region Methods + /// + /// Returns the for the given plugin. + /// + /// + /// The name of the plugin to get the settings for. + /// + /// + /// The for the given plugin. + /// + public Dictionary GetPluginSettings(string name) + { + if (!PluginSettings.ContainsKey(name)) + { + var pluginElement = + GetImageProcessingSection().Plugins + .Cast() + .FirstOrDefault(x => x.Name == name); + + Dictionary settings; + + if (pluginElement != null) + { + settings = pluginElement.Settings + .Cast() + .ToDictionary(setting => setting.Key, setting => setting.Value); + } + else + { + settings = new Dictionary(); + } + + PluginSettings.Add(name, settings); + return settings; + } + + return PluginSettings[name]; + } + + /// + /// Retrieves the processing configuration section from the current application configuration. + /// + /// The processing configuration section from the current application configuration. + private static ImageProcessingSection GetImageProcessingSection() + { + return imageProcessingSection ?? (imageProcessingSection = ImageProcessingSection.GetConfiguration()); + } + + /// + /// Retrieves the caching configuration section from the current application configuration. + /// + /// The caching configuration section from the current application configuration. + private static ImageCacheSection GetImageCacheSection() + { + return imageCacheSection ?? (imageCacheSection = ImageCacheSection.GetConfiguration()); + } + + /// + /// Retrieves the security configuration section from the current application configuration. + /// + /// The security configuration section from the current application configuration. + private static ImageSecuritySection GetImageSecuritySection() + { + return imageSecuritySection ?? (imageSecuritySection = ImageSecuritySection.GetConfiguration()); + } + + /// + /// Gets the list of available GraphicsProcessors. + /// + private void LoadGraphicsProcessors() + { + if (this.GraphicsProcessors == null) + { + // Build a list of native IGraphicsProcessor instances. + Type type = typeof(IGraphicsProcessor); + IEnumerable types = + AppDomain.CurrentDomain.GetAssemblies().SelectMany(s => s.GetTypes()).Where( + p => type.IsAssignableFrom(p) && p.IsClass && !p.IsAbstract).ToList(); + + // Create them and add. + this.GraphicsProcessors = + types.Select(x => (Activator.CreateInstance(x) as IGraphicsProcessor)).ToList(); + + // Add the available settings. + foreach (IGraphicsProcessor processor in this.GraphicsProcessors) + { + processor.Settings = this.GetPluginSettings(processor.Name); + } + } + } + #endregion + } +} diff --git a/src/ImageProcessor.Web/Config/ImageSecuritySection.cs b/src/ImageProcessor.Web/Config/ImageSecuritySection.cs new file mode 100644 index 000000000..0e17cc7a5 --- /dev/null +++ b/src/ImageProcessor.Web/Config/ImageSecuritySection.cs @@ -0,0 +1,182 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Config +{ + #region Using + using System; + using System.Configuration; + #endregion + + /// + /// Represents an imagecache section within a configuration file. + /// + public class ImageSecuritySection : ConfigurationSection + { + #region Properties + /// + /// Gets or sets a value indicating whether the current application is allowed download remote files. + /// + /// if the current application is allowed download remote files; otherwise, . + [ConfigurationProperty("allowRemoteDownloads", DefaultValue = false, IsRequired = true)] + public bool AllowRemoteDownloads + { + get { return (bool)this["allowRemoteDownloads"]; } + set { this["allowRemoteDownloads"] = value; } + } + + /// + /// Gets or sets the maximum allowed remote file timeout in milliseconds for the application. + /// + /// The maximum number of days to store an image in the cache. + /// Defaults to 30000 (30 seconds) if not set. + [ConfigurationProperty("timeout", DefaultValue = "300000", IsRequired = true)] + public int Timeout + { + get + { + return (int)this["timeout"]; + } + + set + { + this["timeout"] = value; + } + } + + /// + /// Gets or sets the maximum allowed remote file size in bytes for the application. + /// + /// The maximum number of days to store an image in the cache. + /// Defaults to 524288 (512kb) if not set. + [ConfigurationProperty("maxBytes", DefaultValue = "524288", IsRequired = true)] + public int MaxBytes + { + get + { + return (int)this["maxBytes"]; + } + + set + { + this["maxBytes"] = value; + } + } + + /// + /// Gets or sets the prefix for remote files for the application. + /// + /// The prefix for remote files for the application. + [ConfigurationProperty("remotePrefix", DefaultValue = "", IsRequired = true)] + public string RemotePrefix + { + get { return (string)this["remotePrefix"]; } + + set { this["remotePrefix"] = value; } + } + + /// + /// Gets the + /// + /// The + [ConfigurationProperty("whiteList", IsRequired = true)] + public WhiteListElementCollection WhiteList + { + get + { + object o = this["whiteList"]; + return o as WhiteListElementCollection; + } + } + #endregion + + #region Methods + /// + /// Retrieves the security configuration section from the current application configuration. + /// + /// The cache configuration section from the current application configuration. + public static ImageSecuritySection GetConfiguration() + { + ImageSecuritySection imageSecuritySection = ConfigurationManager.GetSection("imageProcessor/security") as ImageSecuritySection; + + if (imageSecuritySection != null) + { + return imageSecuritySection; + } + + return new ImageSecuritySection(); + } + #endregion + + /// + /// Represents a whitelist collection configuration element within the configuration. + /// + public class WhiteListElementCollection : ConfigurationElementCollection + { + /// + /// Gets or sets the whitelist item at the given index. + /// + /// The index of the whitelist item to get. + /// The whitelist item at the given index. + public SafeURL this[int index] + { + get + { + return this.BaseGet(index) as SafeURL; + } + + set + { + if (this.BaseGet(index) != null) + { + this.BaseRemoveAt(index); + } + + this.BaseAdd(index, value); + } + } + + /// + /// Creates a new SafeURL configuration element. + /// + /// + /// A new SafeURL configuration element. + /// + protected override ConfigurationElement CreateNewElement() + { + return new SafeURL(); + } + + /// + /// Gets the element key for a specified whitelist configuration element. + /// + /// The ConfigurationElement to return the key for. + /// The element key for a specified whitelist configuration element. + protected override object GetElementKey(ConfigurationElement element) + { + return ((SafeURL)element).Url; + } + } + + /// + /// Represents a whitelist configuration element within the configuration. + /// + public class SafeURL : ConfigurationElement + { + /// + /// Gets or sets the url of the whitelisted file. + /// + /// The url of the whitelisted file. + [ConfigurationProperty("url", DefaultValue = "", IsRequired = true)] + public Uri Url + { + get { return (Uri)this["url"]; } + + set { this["url"] = value; } + } + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs b/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs new file mode 100644 index 000000000..3fc315b5e --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/FileCompareLastwritetime.cs @@ -0,0 +1,58 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + #region Using + using System; + using System.Collections.Generic; + #endregion + + /// + /// Encapsulates methods to support the comparison of objects for equality. + /// + public class FileCompareLastwritetime : IEqualityComparer + { + /// + /// Converts the value of the current object to its equivalent + /// nearest minute representation. + /// + /// An instance of . + /// + /// A value of the current object to its equivalent + /// nearest minute representation. + /// + public static DateTime ToMinute(DateTime value) + { + return new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, 0, value.Kind); + } + + /// + /// Determines whether the specified instances of object are equal. + /// + /// + /// The first object to compare. + /// + /// + /// The second object to compare. + /// + /// true if the specified objects are equal; otherwise, false. + public bool Equals(System.IO.FileInfo f1, System.IO.FileInfo f2) + { + return ToMinute(f1.LastWriteTime) == ToMinute(f2.LastWriteTime); + } + + /// + /// Returns a hash code for the specified . + /// + /// The FileInfo to return the hashcode for. + /// A hash code for the specified . + public int GetHashCode(System.IO.FileInfo fi) + { + return ToMinute(fi.LastWriteTime).GetHashCode(); + } + } +} diff --git a/src/ImageProcessor.Web/Helpers/RemoteFile.cs b/src/ImageProcessor.Web/Helpers/RemoteFile.cs new file mode 100644 index 000000000..af637b7ee --- /dev/null +++ b/src/ImageProcessor.Web/Helpers/RemoteFile.cs @@ -0,0 +1,346 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.Helpers +{ + #region Using + using System; + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Net; + using System.Security; + using System.Text; + using ImageProcessor.Web.Config; + #endregion + + /// + /// Encapsulates methods used to download files from a website address. + /// + /// + /// + /// The purpose of this class is so there's one core way of downloading remote files with urls that are from + /// outside users. There's various areas in application where an attacker could supply an external url to the server + /// and tie up resources. + /// + /// For example, the ImageProcessingModule accepts off-server addresses as a path. An attacker could, for instance, pass the url + /// to a file that's a few gigs in size, causing the server to get out-of-memory exceptions or some other errors. An attacker + /// could also use this same method to use one application instance to hammer another site by, again, passing an off-server + /// address of the victims site to the ImageProcessingModule. + /// This class will not throw an exception if the Uri supplied points to a resource local to the running application instance. + /// + /// There shouldn't be any security issues there, as the internal WebRequest instance is still calling it remotely. + /// Any local files that shouldn't be accessed by this won't be allowed by the remote call. + /// + /// Adapted from BlogEngine.Net + /// + internal sealed class RemoteFile + { + #region Fields + /// + /// The white-list of urls from which to download remote files. + /// + private static readonly Uri[] RemoteFileWhiteList = ImageProcessorConfig.Instance.RemoteFileWhiteList; + + /// + /// The length of time, in milliseconds, that a remote file download attempt can last before timing out. + /// + private static readonly int TimeoutMilliseconds = ImageProcessorConfig.Instance.Timeout; + + /// + /// The maximum size, in bytes, that a remote file download attempt can download. + /// + private static readonly int MaxBytes = ImageProcessorConfig.Instance.MaxBytes; + + /// + /// Whether to allow remote downloads. + /// + private static readonly bool AllowRemoteDownloads = ImageProcessorConfig.Instance.AllowRemoteDownloads; + + /// + /// Whether this RemoteFile instance is ignoring remote download rules set in the current application + /// instance. + /// + private readonly bool ignoreRemoteDownloadSettings; + + /// + /// The Uri of the remote file being downloaded. + /// + private readonly Uri url; + + /// + /// The maximum allowable download size in bytes. + /// + private readonly int maxDownloadSize; + + /// + /// The length of time, in milliseconds, that a remote file download attempt can last before timing out. + /// + private int timeoutLength; + + /// + /// The WebResponse object used internally for this RemoteFile instance. + /// + private WebRequest webRequest; + #endregion + + #region Constructors + /// + /// Initializes a new instance of the RemoteFile class. + /// + /// The url of the file to be downloaded. + /// + /// If set to , then RemoteFile should ignore the current the applications instance's remote download settings; otherwise,. + /// + internal RemoteFile(Uri filePath, bool ignoreRemoteDownloadSettings) + { + Contract.Requires(filePath != null); + + this.url = filePath; + this.ignoreRemoteDownloadSettings = ignoreRemoteDownloadSettings; + this.timeoutLength = TimeoutMilliseconds; + this.maxDownloadSize = MaxBytes; + } + #endregion + + #region Properties + /// + /// Gets a value indicating whether this RemoteFile instance is ignoring remote download rules set in the + /// current application instance. + /// + /// This should only be set to true if the supplied url is a verified resource. Use at your own risk. + /// + /// + /// + /// if this RemoteFile instance is ignoring remote download rules set in the current + /// application instance; otherwise, . + /// + public bool IgnoreRemoteDownloadSettings + { + get + { + return this.ignoreRemoteDownloadSettings; + } + } + + /// + /// Gets the Uri of the remote file being downloaded. + /// + public Uri Uri + { + get + { + return this.url; + } + } + + /// + /// Gets or sets the length of time, in milliseconds, that a remote file download attempt can + /// last before timing out. + /// + /// + /// This value can only be set if the instance is supposed to ignore the remote download settings set + /// in the current application instance. + /// + /// + /// Set this value to 0 if there should be no timeout. + /// + /// + /// + public int TimeoutLength + { + get + { + return this.IgnoreRemoteDownloadSettings ? this.timeoutLength : TimeoutMilliseconds; + } + + set + { + if (!this.IgnoreRemoteDownloadSettings) + { + throw new SecurityException("Timeout length can not be adjusted on remote files that are abiding by remote download rules"); + } + + if (value < 0) + { + throw new ArgumentOutOfRangeException("TimeoutLength"); + } + + this.timeoutLength = value; + } + } + + /// + /// Gets or sets the maximum download size, in bytes, that a remote file download attempt can be. + /// + /// + /// This value can only be set if the instance is supposed to ignore the remote download settings set + /// in the current application instance. + /// + /// + /// Set this value to 0 if there should be no timeout. + /// + /// + /// + public int MaxDownloadSize + { + get + { + return this.IgnoreRemoteDownloadSettings ? this.maxDownloadSize : MaxBytes; + } + + set + { + if (!this.IgnoreRemoteDownloadSettings) + { + throw new SecurityException("Max Download Size can not be adjusted on remote files that are abiding by remote download rules"); + } + + if (value < 0) + { + throw new ArgumentOutOfRangeException("MaxDownloadSize"); + } + + this.timeoutLength = value; + } + } + #endregion + + #region Methods + #region Public + /// + /// Returns the WebResponse used to download this file. + /// + /// + /// This method is meant for outside users who need specific access to the WebResponse this class + /// generates. They're responsible for disposing of it. + /// + /// + /// + /// The WebResponse used to download this file. + public WebResponse GetWebResponse() + { + WebResponse response = this.GetWebRequest().GetResponse(); + + long contentLength = response.ContentLength; + + // WebResponse.ContentLength doesn't always know the value, it returns -1 in this case. + if (contentLength == -1) + { + // Response headers may still have the Content-Length inside of it. + string headerContentLength = response.Headers["Content-Length"]; + + if (!string.IsNullOrWhiteSpace(headerContentLength)) + { + contentLength = long.Parse(headerContentLength, CultureInfo.InvariantCulture); + } + } + + // We don't need to check the url here since any external urls are available only from the web.config. + if ((this.MaxDownloadSize > 0) && (contentLength > this.MaxDownloadSize)) + { + response.Close(); + throw new SecurityException("An attempt to download a remote file has been halted because the file is larger than allowed."); + } + + return response; + } + + /// + /// Returns the remote file as a String. + /// + /// This returns the resulting stream as a string as passed through a StreamReader. + /// + /// + /// The remote file as a String. + public string GetFileAsString() + { + using (WebResponse response = this.GetWebResponse()) + { + Stream responseStream = response.GetResponseStream(); + + if (responseStream != null) + { + // Pipe the stream to a stream reader with the required encoding format. + using (StreamReader reader = new StreamReader(responseStream, Encoding.UTF8)) + { + return reader.ReadToEnd(); + } + } + + return string.Empty; + } + } + #endregion + + #region Private + /// + /// Performs a check to see whether the application is able to download remote files. + /// + private void CheckCanDownload() + { + if (!this.IgnoreRemoteDownloadSettings && !AllowRemoteDownloads) + { + throw new SecurityException("application is not configured to allow remote file downloads."); + } + } + + /// + /// Creates the WebRequest object used internally for this RemoteFile instance. + /// + /// + /// + /// The WebRequest should not be passed outside of this instance, as it will allow tampering. Anyone + /// that needs more fine control over the downloading process should probably be using the WebRequest + /// class on its own. + /// + /// + private WebRequest GetWebRequest() + { + // Check downloads are allowed. + this.CheckCanDownload(); + + // Check the url is from a whitelisted location. + this.CheckSafeUrlLocation(); + + if (this.webRequest == null) + { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(this.Uri); + request.Headers["Accept-Encoding"] = "gzip"; + request.Headers["Accept-Language"] = "en-us"; + request.Credentials = CredentialCache.DefaultNetworkCredentials; + request.AutomaticDecompression = DecompressionMethods.GZip; + + if (this.TimeoutLength > 0) + { + request.Timeout = this.TimeoutLength; + } + + this.webRequest = request; + } + + return this.webRequest; + } + + /// + /// Returns a value indicating whether the current url is in a list of safe download locations. + /// + private void CheckSafeUrlLocation() + { + bool validUrl = RemoteFileWhiteList.Any(item => item.Host.ToUpperInvariant().Equals(this.url.Host.ToUpperInvariant())); + + if (!validUrl) + { + throw new SecurityException("application is not configured to allow remote file downloads from this domain."); + } + } + #endregion + #endregion + } +} + + diff --git a/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs new file mode 100644 index 000000000..335df1cb5 --- /dev/null +++ b/src/ImageProcessor.Web/HttpModules/ImageProcessingModule.cs @@ -0,0 +1,252 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web.HttpModules +{ + #region Using + using System; + using System.IO; + using System.Net; + using System.Web; + using System.Web.Hosting; + using ImageProcessor.Helpers.Extensions; + using ImageProcessor.Imaging; + using ImageProcessor.Web.Caching; + using ImageProcessor.Web.Config; + using ImageProcessor.Web.Helpers; + #endregion + + /// + /// TODO: Update summary. + /// + public class ImageProcessingModule : IHttpModule + { + #region Fields + /// + /// The value to prefix any remote image requests with to ensure they get captured. + /// + private static readonly string RemotePrefix = ImageProcessorConfig.Instance.RemotePrefix; + + /// + /// The key for storing the response type of the current image. + /// + private const string CachedResponseTypeKey = "CACHED_IMAGE_RESPONSE_TYPE"; + + /// + /// Whether this is the first run of the handler. + /// + private static bool isFirstRun = true; + + /// + /// A counter for keeping track of how many images have been added to the cache. + /// + private static int cachedImageCounter; + #endregion + + #region IHttpModule Members + /// + /// Initializes a module and prepares it to handle requests. + /// + /// An that provides access to the methods, properties, and events common to all application objects within an ASP.NET application + public void Init(HttpApplication context) + { + context.BeginRequest += this.ContextBeginRequest; + context.PreSendRequestHeaders += this.ContextPreSendRequestHeaders; + } + + /// + /// Disposes of the resources (other than memory) used by the module that implements . + /// + public void Dispose() + { + // Nothing to dispose. + } + #endregion + + /// + /// Occurs as the first event in the HTTP pipeline chain of execution when ASP.NET responds to a request. + /// + /// The source of the event. + /// An EventArgs that contains the event data. + private void ContextBeginRequest(object sender, EventArgs e) + { + HttpContext context = ((HttpApplication)sender).Context; + + // Is this a remote file. + bool isRemote = context.Request.Path.Equals(RemotePrefix, StringComparison.OrdinalIgnoreCase); + string path; + string queryString = string.Empty; + + if (isRemote) + { + // We need to split the querystring to get the actual values we want. + string[] paths = HttpUtility.UrlDecode(context.Request.QueryString.ToString()).Split('?'); + + path = paths[0]; + + if (paths.Length > 1) + { + queryString = paths[1]; + } + } + else + { + path = HostingEnvironment.MapPath(context.Request.Path); + queryString = context.Request.QueryString.ToString(); + } + + if (ImageUtils.IsValidImageExtension(path) && !string.IsNullOrWhiteSpace(queryString)) + { + string fullPath = string.Format("{0}?{1}", path, queryString); + string imageName = Path.GetFileName(path); + string cachedPath = DiskCache.GetCachePath(fullPath, imageName); + + if (path != null && this.FileExists(path, isRemote)) + { + bool exists = File.Exists(cachedPath); + bool updated = DiskCache.IsUpdatedFile(path, cachedPath); + + if ((exists == false) || (!isRemote && updated)) + { + // Check to see if this is the first run and if so run the cache controller. + if (isFirstRun) + { + // Trim the cache. + DiskCache.PurgeCachedFolders(); + + // Disable the controller. + isFirstRun = false; + } + + // ImageFactory.Instance.Load(fullPath).AutoProcess().Save(cachedPath); + using (ImageFactory imageFactory = new ImageFactory()) + { + if (isRemote) + { + Uri uri = new Uri(path); + RemoteFile remoteFile = new RemoteFile(uri, false); + + using (MemoryStream memoryStream = new MemoryStream()) + { + using (Stream responseStream = remoteFile.GetWebResponse().GetResponseStream()) + { + if (responseStream != null) + { + responseStream.CopyTo(memoryStream); + + imageFactory.Load(memoryStream) + .AddQueryString(queryString) + .Format(ImageUtils.GetImageFormat(imageName)) + .AutoProcess().Save(cachedPath); + } + } + } + } + else + { + imageFactory.Load(fullPath).AutoProcess().Save(cachedPath); + } + } + + // Add 1 to the counter + cachedImageCounter += 1; + + // Ensure that the LastWriteTime property of the source and cached file match. + DiskCache.SetCachedLastWriteTime(path, cachedPath); + + // If the number of cached imaged hits the maximum allowed for this session then we clear + // the cache again and reset the counter + if (cachedImageCounter >= DiskCache.MaxRunsBeforeCacheClear) + { + DiskCache.PurgeCachedFolders(); + cachedImageCounter = 0; + } + } + + context.Items[CachedResponseTypeKey] = ImageUtils.GetResponseType(imageName).ToDescription(); + + // The cached file is valid so just rewrite the path. + context.RewritePath(DiskCache.GetVirtualPath(cachedPath, context.Request), false); + } + } + } + + /// + /// Occurs just before ASP.NET send Httpheaders to the client. + /// + /// The source of the event. + /// An EventArgs that contains the event data. + private void ContextPreSendRequestHeaders(object sender, EventArgs e) + { + HttpContext context = ((HttpApplication)sender).Context; + + object responseTypeObject = context.Items[CachedResponseTypeKey]; + + if (responseTypeObject != null) + { + string responseType = (string)responseTypeObject; + + this.SetHeaders(context, responseType); + + context.Items[CachedResponseTypeKey] = null; + } + } + + #region Private + /// + /// returns a value indicating whether a file exists. + /// + /// The path to the file to check. + /// Whether the file is remote. + /// True if the file exists, otherwise false. + /// If the file is remote the method will always return true. + private bool FileExists(string path, bool remote) + { + return remote || File.Exists(path); + } + + /// + /// This will make the browser and server keep the output + /// in its cache and thereby improve performance. + /// See http://en.wikipedia.org/wiki/HTTP_ETag + /// + /// + /// the HttpContext object that provides + /// references to the intrinsic server objects + /// + /// The HTTP MIME type to to send. + private void SetHeaders(HttpContext context, string responseType) + { + HttpResponse response = context.Response; + + response.ContentType = responseType; + + HttpCachePolicy cache = response.Cache; + + cache.VaryByHeaders["Accept-Encoding"] = true; + + int maxDays = DiskCache.MaxFileCachedDuration; + + cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(maxDays)); + cache.SetMaxAge(new TimeSpan(maxDays, 0, 0, 0)); + cache.SetRevalidation(HttpCacheRevalidation.AllCaches); + + string incomingEtag = context.Request.Headers["If-None-Match"]; + + cache.SetCacheability(HttpCacheability.Public); + + if (incomingEtag == null) + { + return; + } + + response.Clear(); + response.StatusCode = (int)HttpStatusCode.NotModified; + response.SuppressContent = true; + } + #endregion + } +} diff --git a/src/ImageProcessor.Web/ImageFactoryExtensions.cs b/src/ImageProcessor.Web/ImageFactoryExtensions.cs new file mode 100644 index 000000000..cf2eb7d49 --- /dev/null +++ b/src/ImageProcessor.Web/ImageFactoryExtensions.cs @@ -0,0 +1,50 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Web +{ + #region Using + using System.Collections.Generic; + using System.Linq; + using ImageProcessor.Processors; + using ImageProcessor.Web.Config; + #endregion + + /// + /// Extends the ImageFactory class to provide a fluent api. + /// + public static class ImageFactoryExtensions + { + /// + /// Auto processes image files based on any querystring parameters added to the image path. + /// + /// + /// The current instance of the class + /// that this method extends. + /// + /// + /// The current instance of the class. + /// + public static ImageFactory AutoProcess(this ImageFactory factory) + { + if (factory.ShouldProcess) + { + // Get a list of all graphics processors that have parsed and matched the querystring. + List list = + ImageProcessorConfig.Instance.GraphicsProcessors.Where(x => x.MatchRegexIndex(factory.QueryString) != int.MaxValue).OrderBy( + y => y.SortOrder).ToList(); + + // Loop through and process the image. + foreach (IGraphicsProcessor graphicsProcessor in list) + { + factory.Image = graphicsProcessor.ProcessImage(factory); + } + } + + return factory; + } + } +} diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.csproj b/src/ImageProcessor.Web/ImageProcessor.Web.csproj new file mode 100644 index 000000000..6f5589cf3 --- /dev/null +++ b/src/ImageProcessor.Web/ImageProcessor.Web.csproj @@ -0,0 +1,80 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43} + Library + Properties + ImageProcessor.Web + ImageProcessor.Web + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + true + bin\All\ + DEBUG;TRACE + full + AnyCPU + prompt + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E} + ImageProcessor + + + + + + \ No newline at end of file diff --git a/src/ImageProcessor.Web/ImageProcessor.Web.vsdoc b/src/ImageProcessor.Web/ImageProcessor.Web.vsdoc new file mode 100644 index 000000000..26a18eb4c --- /dev/null +++ b/src/ImageProcessor.Web/ImageProcessor.Web.vsdoc @@ -0,0 +1,96 @@ + + + + default + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + yes + ImageProcessor.Web Reference + imageprocessorweb_reference + vsdocman_escaped_]_]_> + + + + placeholder + no + + d85a6fde0cd5454f9dc418dda28f5422 + + + + + + +]]> + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ImageProcessor.Web/Properties/AssemblyInfo.cs b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0f5e66028 --- /dev/null +++ b/src/ImageProcessor.Web/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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("ImageProcessor.Web")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ImageProcessor.Web")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cef6713b-4088-488a-ad2c-6f94aff082d5")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/ImageProcessor/Helpers/Extensions/EnumExtensions.cs b/src/ImageProcessor/Helpers/Extensions/EnumExtensions.cs new file mode 100644 index 000000000..f9a771c70 --- /dev/null +++ b/src/ImageProcessor/Helpers/Extensions/EnumExtensions.cs @@ -0,0 +1,40 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Helpers.Extensions +{ + #region Using + using System; + using System.ComponentModel; + using System.Diagnostics.Contracts; + #endregion + + /// + /// Encapsulates a series of time saving extension methods to Enums. + /// + public static class EnumExtensions + { + #region Methods + /// + /// Extends the Enum type to return the description attribute for the given type. + /// Useful for when the type to match in the data source contains spaces. + /// + /// The given Enum that this method extends. + /// A string containing the Enum's description attribute. + public static string ToDescription(this Enum expression) + { + Contract.Requires(expression != null); + + DescriptionAttribute[] descriptionAttribute = + (DescriptionAttribute[]) + expression.GetType().GetField(expression.ToString()) + .GetCustomAttributes(typeof(DescriptionAttribute), false); + + return descriptionAttribute.Length > 0 ? descriptionAttribute[0].Description : expression.ToString(); + } + #endregion + } +} diff --git a/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs b/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs new file mode 100644 index 000000000..32976d937 --- /dev/null +++ b/src/ImageProcessor/Helpers/Extensions/StringExtensions.cs @@ -0,0 +1,116 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Helpers.Extensions +{ + #region Using + using System.Diagnostics.Contracts; + using System.Globalization; + using System.IO; + using System.Linq; + using System.Security.Cryptography; + using System.Text; + using System.Text.RegularExpressions; + #endregion + + /// + /// Encapsulates a series of time saving extension methods to Strings. + /// + public static class StringExtensions + { + #region Cryptography + /// + /// Creates an MD5 fingerprint of the String. + /// + /// The String instance that this method extends. + /// An MD5 fingerprint of the String. + public static string ToMD5Fingerprint(this string expression) + { + Contract.Requires(!string.IsNullOrWhiteSpace(expression)); + + byte[] bytes = Encoding.Unicode.GetBytes(expression.ToCharArray()); + + using (MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider()) + { + byte[] hash = md5.ComputeHash(bytes); + + // Concatenate the hash bytes into one long String. + return hash.Aggregate( + new StringBuilder(32), + (sb, b) => sb.Append(b.ToString("X2", CultureInfo.InvariantCulture))) + .ToString(); + } + } + #endregion + + #region Numbers + /// + /// Creates an array of integers scraped from the String. + /// + /// The String instance that this method extends. + /// An array of integers scraped from the String. + public static int[] ToIntegerArray(this string expression) + { + Contract.Requires(!string.IsNullOrWhiteSpace(expression)); + + Regex regex = new Regex(@"\d+", RegexOptions.Compiled); + + MatchCollection matchCollection = regex.Matches(expression); + + // Get the collections. + int count = matchCollection.Count; + int[] matches = new int[count]; + + // Loop and parse the int values. + for (int i = 0; i < count; i++) + { + matches[i] = int.Parse(matchCollection[i].Value); + } + + return matches; + } + #endregion + + #region Files and Paths + /// + /// Checks the string to see whether the value is a valid virtual path name. + /// + /// The String instance that this method extends. + /// True if the given string is a valid virtual path name + public static bool IsValidVirtualPathName(this string expression) + { + Contract.Requires(!string.IsNullOrWhiteSpace(expression)); + + // Check the start of the string. + if (expression.StartsWith("~/")) + { + // Trim the first two characters and test the path. + expression = expression.Substring(2); + return expression.IsValidPathName(); + } + + return false; + } + + /// + /// Checks the string to see whether the value is a valid path name. + /// http://stackoverflow.com/questions/62771/how-check-if-given-string-is-legal-allowed-file-name-under-windows/ + /// + /// The String instance that this method extends. + /// True if the given string is a valid path name + public static bool IsValidPathName(this string expression) + { + Contract.Requires(!string.IsNullOrWhiteSpace(expression)); + + // Create a regex of invalid characters and test it. + string invalidPathNameChars = new string(Path.GetInvalidFileNameChars()); + Regex regFixPathName = new Regex("[" + Regex.Escape(invalidPathNameChars) + "]"); + + return !regFixPathName.IsMatch(expression); + } + #endregion + } +} diff --git a/src/ImageProcessor/ImageFactory.cs b/src/ImageProcessor/ImageFactory.cs new file mode 100644 index 000000000..718bd7e15 --- /dev/null +++ b/src/ImageProcessor/ImageFactory.cs @@ -0,0 +1,498 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor +{ + #region Using + using System; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Imaging; + using System.IO; + using System.Linq; + using ImageProcessor.Imaging; + using ImageProcessor.Processors; + + #endregion + + /// + /// Encapsulates methods for processing image files. + /// http://csharpindepth.com/Articles/General/Singleton.aspx + /// + public class ImageFactory : IDisposable + { + #region Fields + /// + /// The default quality for jpeg files. + /// + private const int DefaultJpegQuality = 90; + + /// + /// A value indicating whether this instance of the given entity has been disposed. + /// + /// if this instance has been disposed; otherwise, . + /// + /// If the entity is disposed, it must not be disposed a second + /// time. The isDisposed field is set the first time the entity + /// is disposed. If the isDisposed field is true, then the Dispose() + /// method will not dispose again. This help not to prolong the entity's + /// life in the Garbage Collector. + /// + private bool isDisposed; + #endregion + + #region Destructors + /// + /// Finalizes an instance of the ImageFactory class. + /// + /// + /// Use C# destructor syntax for finalization code. + /// This destructor will run only if the Dispose method + /// does not get called. + /// It gives your base class the opportunity to finalize. + /// Do not provide destructors in types derived from this class. + /// + ~ImageFactory() + { + // Do not re-create Dispose clean-up code here. + // Calling Dispose(false) is optimal in terms of + // readability and maintainability. + this.Dispose(false); + } + + #endregion + + #region Properties + /// + /// Gets or sets the local image for manipulation. + /// + public Image Image { get; set; } + + /// + /// Gets the path to the local image for manipulation. + /// + public string ImagePath { get; private set; } + + /// + /// Gets the querystring params for web image manipulation. + /// + public string QueryString { get; private set; } + + /// + /// Gets a value indicating whether the image factory should process the file. + /// + public bool ShouldProcess { get; private set; } + + /// + /// Gets or sets the quality of output for jpeg images as a percentile. + /// + internal int JpegQuality { get; set; } + + /// + /// Gets or sets the file format of the image. + /// + internal ImageFormat ImageFormat { get; set; } + #endregion + + #region Methods + + /// + /// Loads the image to process. Always call this method first. + /// + /// + /// The containing the image information. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Load(MemoryStream memoryStream) + { + // Set our image as the memorystream value. + this.Image = Image.FromStream(memoryStream); + + // Store the stream in the image Tag property so we can dispose of it later. + this.Image.Tag = memoryStream; + + // Set the other properties. + this.JpegQuality = DefaultJpegQuality; + this.ImageFormat = ImageFormat.Jpeg; + this.ShouldProcess = true; + + return this; + } + + /// + /// Loads the image to process. Always call this method first. + /// + /// The absolute path to the image to load. + /// + /// The current instance of the class. + /// + public ImageFactory Load(string imagePath) + { + string[] paths = imagePath.Split('?'); + string path = paths[0]; + string query = string.Empty; + + if (paths.Length > 1) + { + query = paths[1]; + } + + string imageName = Path.GetFileName(path); + + if (File.Exists(path)) + { + this.ImagePath = path; + this.QueryString = query; + + // Open a filstream to prevent the need for lock. + using (FileStream fileStream = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + MemoryStream memoryStream = new MemoryStream(); + + // Copy the stream. + fileStream.CopyTo(memoryStream); + + // Set the position to 0 afterwards. + fileStream.Position = memoryStream.Position = 0; + + // Set our image as the memorystream value. + this.Image = Image.FromStream(memoryStream); + + // Store the stream in the image Tag property so we can dispose of it later. + this.Image.Tag = memoryStream; + + // Set the other properties. + this.JpegQuality = DefaultJpegQuality; + this.ImageFormat = ImageUtils.GetImageFormat(imageName); + this.ShouldProcess = true; + } + } + + return this; + } + + #region Manipulation + + /// + /// Adds a querystring to the image factory to allow autoprocessing of remote files. + /// + /// The querystring parameter to process. + /// + /// The current instance of the class. + /// + public ImageFactory AddQueryString(string query) + { + if (this.ShouldProcess) + { + this.QueryString = query; + } + + return this; + } + + /// + /// Changes the opacity of the current image. + /// + /// The percentage by which to alter the images opacity. + /// + /// The current instance of the class. + /// + public ImageFactory Alpha(int percentage) + { + if (this.ShouldProcess) + { + var alpha = new Alpha { DynamicParameter = percentage }; + + this.Image = alpha.ProcessImage(this); + } + + return this; + } + + /// + /// Crops an image to the given coordinates. + /// + /// + /// The containing the coordinates to crop the image to. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Crop(Rectangle rectangle) + { + if (this.ShouldProcess) + { + var crop = new Crop { DynamicParameter = rectangle }; + + this.Image = crop.ProcessImage(this); + } + + return this; + } + + /// + /// Applies a filter to an image. + /// + /// + /// The name of the filter to add to the image. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Filter(string filterName) + { + if (this.ShouldProcess) + { + var filter = new Filter { DynamicParameter = filterName }; + + this.Image = filter.ProcessImage(this); + } + + return this; + } + + /// + /// Sets the output format of the image to the matching . + /// + /// The . to set the image to. + /// + /// The current instance of the class. + /// + public ImageFactory Format(ImageFormat imageFormat) + { + if (this.ShouldProcess) + { + this.ImageFormat = imageFormat; + } + + return this; + } + + /// + /// Applies a filter to an image. + /// + /// A value between 1 and 100 to set the quality to. + /// + /// The current instance of the class. + /// + public ImageFactory Quality(int percentage) + { + if (this.ShouldProcess) + { + this.JpegQuality = percentage; + } + + return this; + } + + /// + /// Resizes an image to the given dimensions. + /// + /// The width to set the image to. + /// The height to set the image to. + /// + /// The current instance of the class. + /// + public ImageFactory Resize(int width, int height) + { + if (this.ShouldProcess) + { + var resizeSettings = new Dictionary { { "MaxWidth", width.ToString("G") }, { "MaxHeight", height.ToString("G") } }; + + var resize = new Resize { DynamicParameter = new Size(width, height), Settings = resizeSettings }; + + this.Image = resize.ProcessImage(this); + } + + return this; + } + + /// + /// Adds a vignette image effect to the current image. + /// + /// + /// The current instance of the class. + /// + public ImageFactory Vignette() + { + if (this.ShouldProcess) + { + var vignette = new Vignette(); + + this.Image = vignette.ProcessImage(this); + } + + return this; + } + #endregion + + /// + /// Saves the current image to the specified file path. + /// + /// The path to save the image to. + public void Save(string filePath) + { + if (this.ShouldProcess) + { + // Fix the colour palette of gif images. + this.FixGifs(); + + if (this.ImageFormat == ImageFormat.Jpeg) + { + // Jpegs can be saved with different settings to include a quality setting for the JPEG compression. + // This improves output compression and quality. + using (EncoderParameters encoderParameters = ImageUtils.GetEncodingParameters(this.JpegQuality)) + { + ImageCodecInfo imageCodecInfo = + ImageCodecInfo.GetImageEncoders().FirstOrDefault( + ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)); + + this.Image.Save(filePath, imageCodecInfo, encoderParameters); + } + } + else + { + this.Image.Save(filePath, this.ImageFormat); + } + } + } + + /// + /// Saves the current image to the specified output stream. + /// + /// + /// The to save the image information to. + /// + public void Save(MemoryStream memoryStream) + { + if (this.ShouldProcess) + { + // Fix the colour palette of gif images. + this.FixGifs(); + + if (this.ImageFormat == ImageFormat.Jpeg) + { + // Jpegs can be saved with different settings to include a quality setting for the JPEG compression. + // This improves output compression and quality. + using (EncoderParameters encoderParameters = ImageUtils.GetEncodingParameters(this.JpegQuality)) + { + ImageCodecInfo imageCodecInfo = + ImageCodecInfo.GetImageEncoders().FirstOrDefault( + ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)); + + if (imageCodecInfo != null) + { + this.Image.Save(memoryStream, imageCodecInfo, encoderParameters); + } + } + } + else + { + this.Image.Save(memoryStream, this.ImageFormat); + } + } + } + + #region IDisposable Members + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + public void Dispose() + { + this.Dispose(true); + + // This object will be cleaned up by the Dispose method. + // Therefore, you should call GC.SupressFinalize to + // take this object off the finalization queue + // and prevent finalization code for this object + // from executing a second time. + GC.SuppressFinalize(this); + } + + /// + /// Disposes the object and frees resources for the Garbage Collector. + /// + /// If true, the object gets disposed. + protected virtual void Dispose(bool disposing) + { + if (this.isDisposed) + { + return; + } + + if (disposing) + { + // Dispose of any managed resources here. + if (this.Image != null) + { + // Dispose of the memorystream from Load and the image. + if (this.Image.Tag != null) + { + ((IDisposable)this.Image.Tag).Dispose(); + this.Image.Tag = null; + } + + this.Image.Dispose(); + this.Image = null; + } + } + + // Call the appropriate methods to clean up + // unmanaged resources here. + // Note disposing is done. + this.isDisposed = true; + } + #endregion + + /// + /// Saves the current image to the specified file path and resets any internal parameters. + /// + /// The path to save the image to. + private void SaveFile(string filePath) + { + // Fix the colour palette of gif images. + this.FixGifs(); + + if (this.ImageFormat == ImageFormat.Jpeg) + { + // Jpegs can be saved with different settings to include a quality setting for the JPEG compression. + // This improves output compression and quality. + using (EncoderParameters encoderParameters = ImageUtils.GetEncodingParameters(this.JpegQuality)) + { + ImageCodecInfo imageCodecInfo = ImageCodecInfo.GetImageEncoders() + .FirstOrDefault(ici => ici.MimeType.Equals("image/jpeg", StringComparison.OrdinalIgnoreCase)); + + if (imageCodecInfo != null) + { + this.Image.Save(filePath, imageCodecInfo, encoderParameters); + } + } + } + else + { + this.Image.Save(filePath, this.ImageFormat); + } + } + + /// + /// Uses the + /// to fix the colour palette of gif images. + /// + private void FixGifs() + { + // Fix the colour palette of gif images. + // TODO: Why does the palette not get fixed when resized to the same dimensions. + if (this.ImageFormat == ImageFormat.Gif) + { + OctreeQuantizer quantizer = new OctreeQuantizer(255, 8); + this.Image = quantizer.Quantize(this.Image); + } + } + #endregion + } +} diff --git a/src/ImageProcessor/ImageProcessor.csproj b/src/ImageProcessor/ImageProcessor.csproj new file mode 100644 index 000000000..22ffe9fa3 --- /dev/null +++ b/src/ImageProcessor/ImageProcessor.csproj @@ -0,0 +1,86 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E} + Library + Properties + ImageProcessor + ImageProcessor + v4.0 + 512 + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + bin\Debug\ImageProcessor.XML + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + bin\Release\ImageProcessor.XML + + + true + bin\All\ + DEBUG;TRACE + bin\Debug\ImageProcessor.XML + full + AnyCPU + prompt + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ImageProcessor/ImageProcessor.sln b/src/ImageProcessor/ImageProcessor.sln new file mode 100644 index 000000000..bdf26b14e --- /dev/null +++ b/src/ImageProcessor/ImageProcessor.sln @@ -0,0 +1,39 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor", "ImageProcessor.csproj", "{3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "..\Test\Test\Test.csproj", "{30327C08-7574-4D7E-AC95-6A58753C6855}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web", "..\ImageProcessor.Web\ImageProcessor.Web.csproj", "{4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + All|Any CPU = All|Any CPU + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}.All|Any CPU.ActiveCfg = All|Any CPU + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}.All|Any CPU.Build.0 = All|Any CPU + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E}.Release|Any CPU.Build.0 = Release|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.All|Any CPU.ActiveCfg = All|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.All|Any CPU.Build.0 = All|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Release|Any CPU.Build.0 = Release|Any CPU + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}.All|Any CPU.ActiveCfg = All|Any CPU + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}.All|Any CPU.Build.0 = All|Any CPU + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/ImageProcessor/ImageProcessor.sln.vsdoc b/src/ImageProcessor/ImageProcessor.sln.vsdoc new file mode 100644 index 000000000..9ffff5e7f --- /dev/null +++ b/src/ImageProcessor/ImageProcessor.sln.vsdoc @@ -0,0 +1,7 @@ + + + + default + + + diff --git a/src/ImageProcessor/ImageProcessor.vsdoc b/src/ImageProcessor/ImageProcessor.vsdoc new file mode 100644 index 000000000..75458c7ac --- /dev/null +++ b/src/ImageProcessor/ImageProcessor.vsdoc @@ -0,0 +1,103 @@ + + + + default + + +]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + normal + yes + ImageProcessor Reference + imageprocessor_reference + vsdocman_escaped_]_]_> + + + + placeholder + no + + e3cba20ec62e422b8bb39e6be7ca2142 + + + + + + +]]> + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/ImageProcessor/Imaging/ImageUtils.cs b/src/ImageProcessor/Imaging/ImageUtils.cs new file mode 100644 index 000000000..e5525e44c --- /dev/null +++ b/src/ImageProcessor/Imaging/ImageUtils.cs @@ -0,0 +1,174 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + #region Using + using System; + using System.Drawing.Imaging; + using System.IO; + using System.Linq; + using System.Threading.Tasks; + #endregion + + /// + /// Encapsulates useful image utility methods. + /// + public static class ImageUtils + { + /// + /// Returns the correct response type based on the given file extension. + /// + /// The string containing the filename to check against. + /// The correct response type based on the given file extension. + public static ResponseType GetResponseType(string fileName) + { + string extension = Path.GetExtension(fileName); + if (extension != null) + { + string ext = extension.ToUpperInvariant(); + + switch (ext) + { + case ".PNG": + return ResponseType.Png; + case ".BMP": + return ResponseType.Bmp; + case ".GIF": + return ResponseType.Gif; + default: + // Should be a jpeg. + return ResponseType.Jpeg; + } + } + + // TODO: Should we call this on bad request? + return ResponseType.Jpeg; + } + + /// + /// Returns the correct image format based on the given file extension. + /// + /// The string containing the filename to check against. + /// The correct image format based on the given filename. + public static ImageFormat GetImageFormat(string fileName) + { + string extension = Path.GetExtension(fileName); + if (extension != null) + { + string ext = extension.ToUpperInvariant(); + + switch (ext) + { + case ".PNG": + return ImageFormat.Png; + case ".BMP": + return ImageFormat.Bmp; + case ".GIF": + return ImageFormat.Gif; + default: + // Should be a jpeg. + return ImageFormat.Jpeg; + } + } + // TODO: SHow custom exception?? + return null; + } + + /// + /// Returns the correct image format based on the given response type. + /// + /// + /// The to check against. + /// + /// The correct image format based on the given response type. + public static ImageFormat GetImageFormat(ResponseType responseType) + { + switch (responseType) + { + case ResponseType.Png: + return ImageFormat.Png; + case ResponseType.Bmp: + return ImageFormat.Bmp; + case ResponseType.Gif: + return ImageFormat.Gif; + default: + // Should be a jpeg. + return ImageFormat.Jpeg; + } + } + + /// + /// Returns the first ImageCodeInfo instance with the specified mime type. + /// + /// + /// A string that contains the codec's Multipurpose Internet Mail Extensions (MIME) type. + /// + /// + /// The first ImageCodeInfo instance with the specified mime type. + /// + public static ImageCodecInfo GetImageCodeInfo(string mimeType) + { + ImageCodecInfo[] info = ImageCodecInfo.GetImageEncoders(); + return info.FirstOrDefault(ici => ici.MimeType.Equals(mimeType, StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Returns an instance of EncodingParameters for jpeg comression. + /// + /// The quality to return the image at. + /// The encodingParameters for jpeg comression. + public static EncoderParameters GetEncodingParameters(int quality) + { + EncoderParameters encoderParameters = null; + try + { + // Create a series of encoder parameters. + encoderParameters = new EncoderParameters(1); + + // Set the quality. + encoderParameters.Param[0] = new EncoderParameter(Encoder.Quality, quality); + } + catch + { + if (encoderParameters != null) + { + encoderParameters.Dispose(); + } + } + + return encoderParameters; + } + + /// + /// Checks a given string to check whether the value contains a valid image extension. + /// + /// The string containing the filename to check. + /// True the value contains a valid image extension, otherwise false. + public static bool IsValidImageExtension(string fileName) + { + bool isValid = false; + + if (!string.IsNullOrWhiteSpace(fileName)) + { + string[] fileExtensions = { ".BMP", ".JPG", ".PNG", ".GIF", ".JPEG" }; + + Parallel.ForEach( + fileExtensions, + (extension, loop) => + { + if (fileName.ToUpperInvariant().EndsWith(extension)) + { + isValid = true; + loop.Stop(); + } + }); + } + + return isValid; + } + } +} diff --git a/src/ImageProcessor/Imaging/OctreeQuantizer.cs b/src/ImageProcessor/Imaging/OctreeQuantizer.cs new file mode 100644 index 000000000..d9c9928a2 --- /dev/null +++ b/src/ImageProcessor/Imaging/OctreeQuantizer.cs @@ -0,0 +1,512 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + #region Using + using System; + using System.Collections; + using System.Drawing; + using System.Drawing.Imaging; + #endregion + + /// + /// Encapsulates methods to calculate the colour palette if an image using an octree pattern. + /// + internal class OctreeQuantizer : Quantizer + { + #region Fields + /// + /// Stores the tree. + /// + private readonly Octree octree; + + /// + /// The maximum allowed color depth. + /// + private readonly int maxColors; + #endregion + + /// + /// Initializes a new instance of the OctreeQuantizer class. + /// + /// + /// The Octree quantizer is a two pass algorithm. The initial pass sets up the octree, + /// the second pass quantizes a colour based on the nodes in the tree + /// + /// The maximum number of colours to return, maximum 255. + /// The number of significant bits minimum 1, maximum 8. + public OctreeQuantizer(int maxColors, int maxColorBits) + : base(false) + { + if (maxColors > 255) + { + throw new ArgumentOutOfRangeException("maxColors", maxColors, "The number of colours should be less than 256"); + } + + if ((maxColorBits < 1) | (maxColorBits > 8)) + { + throw new ArgumentOutOfRangeException("maxColorBits", maxColorBits, "This should be between 1 and 8"); + } + + // Construct the octree + this.octree = new Octree(maxColorBits); + this.maxColors = maxColors; + } + + /// + /// Process the pixel in the first pass of the algorithm. + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected override void InitialQuantizePixel(Color32 pixel) + { + // Add the colour to the octree + this.octree.AddColor(pixel); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm. + /// + /// The pixel to quantize + /// The quantized value. + protected override byte QuantizePixel(Color32 pixel) + { + // The colour at [this.maxColors] is set to transparent + byte paletteIndex; + + // Get the palette index if this non-transparent + if (pixel.Alpha > 0) + { + paletteIndex = (byte)this.octree.GetPaletteIndex(pixel); + } + else + { + paletteIndex = (byte)this.maxColors; + } + + return paletteIndex; + } + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overwritten + /// The new colour palette + protected override ColorPalette GetPalette(ColorPalette original) + { + // First off convert the octree to this.maxColors colours + ArrayList palette = this.octree.Palletize(this.maxColors - 1); + + // Then convert the palette based on those colours + for (int index = 0; index < palette.Count; index++) + { + original.Entries[index] = (Color)palette[index]; + } + + // Add the transparent colour + original.Entries[this.maxColors] = Color.FromArgb(0, 0, 0, 0); + + return original; + } + + /// + /// Describes a tree data structure in which each internal node has exactly eight children. + /// + private class Octree + { + /// + /// Initializes a new instance of the Octree class. + /// + /// The maximum number of significant bits in the image + public Octree(int maxColorBits) + { + this._maxColorBits = maxColorBits; + this._leafCount = 0; + this._reducibleNodes = new OctreeNode[9]; + this._root = new OctreeNode(0, this._maxColorBits, this); + this._previousColor = 0; + this._previousNode = null; + } + + /// + /// Add a given colour value to the octree + /// + /// + /// The color value to add. + /// + public void AddColor(Color32 pixel) + { + // Check if this request is for the same colour as the last + if (this._previousColor == pixel.ARGB) + { + // If so, check if I have a previous node setup. This will only occur if the first colour in the image + // happens to be black, with an alpha component of zero. + if (null == this._previousNode) + { + this._previousColor = pixel.ARGB; + this._root.AddColor(pixel, this._maxColorBits, 0, this); + } + else + { + // Just update the previous node + this._previousNode.Increment(pixel); + } + } + else + { + this._previousColor = pixel.ARGB; + this._root.AddColor(pixel, this._maxColorBits, 0, this); + } + } + + /// + /// Reduce the depth of the tree + /// + public void Reduce() + { + // Find the deepest level containing at least one reducible node + int index = this._maxColorBits - 1; + while ((index > 0) && (this._reducibleNodes[index] == null)) + { + index--; + } + + // Reduce the node most recently added to the list at level 'index' + OctreeNode node = this._reducibleNodes[index]; + this._reducibleNodes[index] = node.NextReducible; + + // Decrement the leaf count after reducing the node + this._leafCount -= node.Reduce(); + + // And just in case I've reduced the last color to be added, and the next color to + // be added is the same, invalidate the previousNode... + this._previousNode = null; + } + + /// + /// Get or sets the number of leaves in the tree + /// + public int Leaves + { + get { return this._leafCount; } + set { this._leafCount = value; } + } + + /// + /// Return the array of reducible nodes + /// + protected OctreeNode[] ReducibleNodes + { + get { return this._reducibleNodes; } + } + + /// + /// Keep track of the previous node that was quantized + /// + /// The node last quantized + protected void TrackPrevious(OctreeNode node) + { + this._previousNode = node; + } + + /// + /// Convert the nodes in the octree to a palette with a maximum of colorCount colours + /// + /// The maximum number of colours + /// An array list with the palletized colours + public ArrayList Palletize(int colorCount) + { + while (this.Leaves > colorCount) + { + this.Reduce(); + } + + // Now palletize the nodes + ArrayList palette = new ArrayList(this.Leaves); + int paletteIndex = 0; + this._root.ConstructPalette(palette, ref paletteIndex); + + // And return the palette + return palette; + } + + /// + /// Get the palette index for the passed colour + /// + /// + /// + public int GetPaletteIndex(Color32 pixel) + { + return this._root.GetPaletteIndex(pixel, 0); + } + + /// + /// Mask used when getting the appropriate pixels for a given node + /// + private static int[] mask = new int[8] { 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01 }; + + /// + /// The root of the octree + /// + private OctreeNode _root; + + /// + /// Number of leaves in the tree + /// + private int _leafCount; + + /// + /// Array of reducible nodes + /// + private OctreeNode[] _reducibleNodes; + + /// + /// Maximum number of significant bits in the image + /// + private int _maxColorBits; + + /// + /// Store the last node quantized + /// + private OctreeNode _previousNode; + + /// + /// Cache the previous color quantized + /// + private int _previousColor; + + /// + /// Class which encapsulates each node in the tree + /// + protected class OctreeNode + { + /// + /// Construct the node + /// + /// The level in the tree = 0 - 7 + /// The number of significant color bits in the image + /// The tree to which this node belongs + public OctreeNode(int level, int colorBits, Octree octree) + { + // Construct the new node + this._leaf = (level == colorBits); + + this._red = _green = _blue = 0; + this._pixelCount = 0; + + // If a leaf, increment the leaf count + if (this._leaf) + { + octree.Leaves++; + this._nextReducible = null; + this._children = null; + } + else + { + // Otherwise add this to the reducible nodes + this._nextReducible = octree.ReducibleNodes[level]; + octree.ReducibleNodes[level] = this; + this._children = new OctreeNode[8]; + } + } + + /// + /// Add a color into the tree + /// + /// The color + /// The number of significant color bits + /// The level in the tree + /// The tree to which this node belongs + public void AddColor(Color32 pixel, int colorBits, int level, Octree octree) + { + // Update the color information if this is a leaf + if (this._leaf) + { + Increment(pixel); + // Setup the previous node + octree.TrackPrevious(this); + } + else + { + // Go to the next level down in the tree + int shift = 7 - level; + int index = ((pixel.Red & mask[level]) >> (shift - 2)) | + ((pixel.Green & mask[level]) >> (shift - 1)) | + ((pixel.Blue & mask[level]) >> (shift)); + + OctreeNode child = this._children[index]; + + if (null == child) + { + // Create a new child node & store in the array + child = new OctreeNode(level + 1, colorBits, octree); + this._children[index] = child; + } + + // Add the color to the child node + child.AddColor(pixel, colorBits, level + 1, octree); + } + + } + + /// + /// Get or Sets the next reducible node + /// + public OctreeNode NextReducible + { + get { return _nextReducible; } + set { _nextReducible = value; } + } + + /// + /// Return the child nodes + /// + public OctreeNode[] Children + { + get { return _children; } + } + + /// + /// Reduce this node by removing all of its children + /// + /// The number of leaves removed + public int Reduce() + { + this._red = this._green = this._blue = 0; + int children = 0; + + // Loop through all children and add their information to this node + for (int index = 0; index < 8; index++) + { + if (null != this._children[index]) + { + this._red += this._children[index]._red; + this._green += this._children[index]._green; + this._blue += this._children[index]._blue; + this._pixelCount += this._children[index]._pixelCount; + ++children; + this._children[index] = null; + } + } + + // Now change this to a leaf node + this._leaf = true; + + // Return the number of nodes to decrement the leaf count by + return children - 1; + } + + /// + /// Traverse the tree, building up the color palette + /// + /// The palette + /// The current palette index + public void ConstructPalette(ArrayList palette, ref int paletteIndex) + { + if (_leaf) + { + // Consume the next palette index + _paletteIndex = paletteIndex++; + + // And set the color of the palette entry + palette.Add(Color.FromArgb(_red / _pixelCount, _green / _pixelCount, _blue / _pixelCount)); + } + else + { + // Loop through children looking for leaves + for (int index = 0; index < 8; index++) + { + if (null != _children[index]) + _children[index].ConstructPalette(palette, ref paletteIndex); + } + } + } + + /// + /// Return the palette index for the passed color + /// + public int GetPaletteIndex(Color32 pixel, int level) + { + int paletteIndex = _paletteIndex; + + if (!_leaf) + { + int shift = 7 - level; + int index = ((pixel.Red & mask[level]) >> (shift - 2)) | + ((pixel.Green & mask[level]) >> (shift - 1)) | + ((pixel.Blue & mask[level]) >> (shift)); + + if (null != _children[index]) + { + paletteIndex = _children[index].GetPaletteIndex(pixel, level + 1); + } + else + { + throw new Exception("Didn't expect this!"); + } + } + + return paletteIndex; + } + + /// + /// Increment the pixel count and add to the color information + /// + public void Increment(Color32 pixel) + { + this._pixelCount++; + this._red += pixel.Red; + this._green += pixel.Green; + this._blue += pixel.Blue; + } + + /// + /// Flag indicating that this is a leaf node + /// + private bool _leaf; + + /// + /// Number of pixels in this node + /// + private int _pixelCount; + + /// + /// Red component + /// + private int _red; + + /// + /// Green Component + /// + private int _green; + + /// + /// Blue component + /// + private int _blue; + + /// + /// Pointers to any child nodes + /// + private OctreeNode[] _children; + + /// + /// Pointer to next reducible node + /// + private OctreeNode _nextReducible; + + /// + /// The index of this node in the palette + /// + private int _paletteIndex; + } + } + } +} diff --git a/src/ImageProcessor/Imaging/PaletteQuantizer.cs b/src/ImageProcessor/Imaging/PaletteQuantizer.cs new file mode 100644 index 000000000..b9d929fd7 --- /dev/null +++ b/src/ImageProcessor/Imaging/PaletteQuantizer.cs @@ -0,0 +1,138 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Imaging; + using System.Linq; + using System.Text; + + /// + /// Encapsulates methods to calculate the colour palette if an image. + /// http://codebetter.com/brendantompkins/2007/06/14/gif-image-color-quantizer-now-with-safe-goodness/ + /// + internal class PaletteQuantizer : Quantizer + { + /// + /// Initializes a new instance of the class. + /// + /// + /// The color palette to quantize to + /// + /// + /// Palette quantization only requires a single quantization step + /// + public PaletteQuantizer(ArrayList palette) + : base(true) + { + _colorMap = new Hashtable(); + + _colors = new Color[palette.Count]; + palette.CopyTo(_colors); + } + + /// + /// Override this to process the pixel in the second pass of the algorithm + /// + /// The pixel to quantize + /// The quantized value + protected override byte QuantizePixel(Color32 pixel) + { + byte colorIndex = 0; + int colorHash = pixel.ARGB; + + // Check if the color is in the lookup table + if (_colorMap.ContainsKey(colorHash)) + { + colorIndex = (byte)_colorMap[colorHash]; + } + else + { + // Not found - loop through the palette and find the nearest match. + // Firstly check the alpha value - if 0, lookup the transparent color + if (0 == pixel.Alpha) + { + // Transparent. Lookup the first color with an alpha value of 0 + for (int index = 0; index < _colors.Length; index++) + { + if (0 == _colors[index].A) + { + colorIndex = (byte)index; + break; + } + } + } + else + { + // Not transparent... + int leastDistance = int.MaxValue; + int red = pixel.Red; + int green = pixel.Green; + int blue = pixel.Blue; + + // Loop through the entire palette, looking for the closest color match + for (int index = 0; index < _colors.Length; index++) + { + Color paletteColor = _colors[index]; + + int redDistance = paletteColor.R - red; + int greenDistance = paletteColor.G - green; + int blueDistance = paletteColor.B - blue; + + int distance = (redDistance * redDistance) + + (greenDistance * greenDistance) + + (blueDistance * blueDistance); + + if (distance < leastDistance) + { + colorIndex = (byte)index; + leastDistance = distance; + + // And if it's an exact match, exit the loop + if (0 == distance) + { + break; + } + } + } + } + + // Now I have the color, pop it into the hashtable for next time + _colorMap.Add(colorHash, colorIndex); + } + + return colorIndex; + } + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overrwritten + /// The new color palette + protected override ColorPalette GetPalette(ColorPalette palette) + { + for (int index = 0; index < _colors.Length; index++) + { + palette.Entries[index] = _colors[index]; + } + return palette; + } + + /// + /// Lookup table for colors + /// + private Hashtable _colorMap; + + /// + /// List of all colors in the palette + /// + protected Color[] _colors; + } +} diff --git a/src/ImageProcessor/Imaging/Quantizer.cs b/src/ImageProcessor/Imaging/Quantizer.cs new file mode 100644 index 000000000..97fda4b90 --- /dev/null +++ b/src/ImageProcessor/Imaging/Quantizer.cs @@ -0,0 +1,314 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + #region Using + using System; + using System.Drawing; + using System.Drawing.Imaging; + using System.Runtime.InteropServices; + #endregion + + /// + /// Encapsulates methods to calulate the colour pallete of an image. + /// + internal abstract class Quantizer + { + #region Fields + /// + /// The flag used to indicate whether a single pass or two passes are needed for quantization. + /// + private readonly bool singlePass; + + /// + /// The size in bytes of the 32 bpp Colour structure. + /// + private readonly int pixelSize; + #endregion + + /// + /// Initializes a new instance of the Quantizer class. + /// + /// + /// If set to , then the quantizer will loop through the source pixels once; + /// otherwise, . + /// + protected Quantizer(bool singlePass) + { + this.singlePass = singlePass; + this.pixelSize = Marshal.SizeOf(typeof(Color32)); + } + + /// + /// Quantizes the given Image and returns the resulting output + /// Bitmap. + /// + /// The image to quantize + /// + /// A quantized Bitmap version of the Image + /// + public Bitmap Quantize(Image source) + { + // Get the size of the source image + int height = source.Height; + int width = source.Width; + + // And construct a rectangle from these dimensions + Rectangle bounds = new Rectangle(0, 0, width, height); + + // First off take a 32bpp copy of the image + using (Bitmap copy = new Bitmap(width, height, PixelFormat.Format32bppArgb)) + { + Bitmap output = null; + + // Define a pointer to the bitmap data + BitmapData sourceData = null; + try + { + // And construct an 8bpp version + output = new Bitmap(width, height, PixelFormat.Format8bppIndexed); + + // Now lock the bitmap into memory + using (Graphics graphics = Graphics.FromImage(copy)) + { + graphics.PageUnit = GraphicsUnit.Pixel; + + // Draw the source image onto the copy bitmap, + // which will effect a widening as appropriate. + graphics.DrawImage(source, bounds); + } + + // Get the source image bits and lock into memory + sourceData = copy.LockBits(bounds, ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb); + + // Call the FirstPass function if not a single pass algorithm. + // For something like an octree quantizer, this will run through + // all image pixels, build a data structure, and create a palette. + if (!this.singlePass) + { + this.FirstPass(sourceData, width, height); + } + + // Then set the colour palette on the output bitmap. I'm passing in the current palette + // as there's no way to construct a new, empty palette. + output.Palette = this.GetPalette(output.Palette); + + // Then call the second pass which actually does the conversion + this.SecondPass(sourceData, output, width, height, bounds); + } + catch + { + if (output != null) + { + output.Dispose(); + } + } + finally + { + // Ensure that the bits are unlocked + copy.UnlockBits(sourceData); + } + + // Last but not least, return the output bitmap + return output; + } + } + + /// + /// Execute the first pass through the pixels in the image + /// + /// The source data + /// The width in pixels of the image + /// The height in pixels of the image + protected virtual void FirstPass(BitmapData sourceData, int width, int height) + { + // Define the source data pointers. The source row is a byte to + // keep addition of the stride value easier (as this is in bytes) + IntPtr sourceRow = sourceData.Scan0; + + // Loop through each row + for (int row = 0; row < height; row++) + { + // Set the source pixel to the first pixel in this row + IntPtr sourcePixel = sourceRow; + + // And loop through each column + for (int col = 0; col < width; col++) + { + this.InitialQuantizePixel(new Color32(sourcePixel)); + sourcePixel = (IntPtr)((int)sourcePixel + this.pixelSize); + } + + // Now I have the pixel, call the FirstPassQuantize function. + // Add the stride to the source row + sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride); + } + } + + /// + /// Execute a second pass through the bitmap + /// + /// The source bitmap, locked into memory + /// The output bitmap + /// The width in pixels of the image + /// The height in pixels of the image + /// The bounding rectangle + protected virtual void SecondPass(BitmapData sourceData, Bitmap output, int width, int height, Rectangle bounds) + { + BitmapData outputData = null; + + try + { + // Lock the output bitmap into memory + outputData = output.LockBits(bounds, ImageLockMode.WriteOnly, PixelFormat.Format8bppIndexed); + + // Define the source data pointers. The source row is a byte to + // keep addition of the stride value easier (as this is in bytes) + IntPtr sourceRow = sourceData.Scan0; + IntPtr sourcePixel = sourceRow; + IntPtr previousPixel = sourcePixel; + + // Now define the destination data pointers + IntPtr destinationRow = outputData.Scan0; + IntPtr destinationPixel = destinationRow; + + // And convert the first pixel, so that I have values going into the loop. + byte pixelValue = this.QuantizePixel(new Color32(sourcePixel)); + + // Assign the value of the first pixel + Marshal.WriteByte(destinationPixel, pixelValue); + + // Loop through each row + for (int row = 0; row < height; row++) + { + // Set the source pixel to the first pixel in this row + sourcePixel = sourceRow; + + // And set the destination pixel pointer to the first pixel in the row + destinationPixel = destinationRow; + + // Loop through each pixel on this scan line + for (int col = 0; col < width; col++) + { + // Check if this is the same as the last pixel. If so use that value + // rather than calculating it again. This is an inexpensive optimisation. + if (Marshal.ReadByte(previousPixel) != Marshal.ReadByte(sourcePixel)) + { + // Quantize the pixel + pixelValue = this.QuantizePixel(new Color32(sourcePixel)); + + // And setup the previous pointer + previousPixel = sourcePixel; + } + + // And set the pixel in the output + Marshal.WriteByte(destinationPixel, pixelValue); + + sourcePixel = (IntPtr)((long)sourcePixel + this.pixelSize); + destinationPixel = (IntPtr)((long)destinationPixel + 1); + } + + // Add the stride to the source row + sourceRow = (IntPtr)((long)sourceRow + sourceData.Stride); + + // And to the destination row + destinationRow = (IntPtr)((long)destinationRow + outputData.Stride); + } + } + finally + { + // Ensure that I unlock the output bits + output.UnlockBits(outputData); + } + } + + /// + /// Override this to process the pixel in the first pass of the algorithm + /// + /// The pixel to quantize + /// + /// This function need only be overridden if your quantize algorithm needs two passes, + /// such as an Octree quantizer. + /// + protected virtual void InitialQuantizePixel(Color32 pixel) + { + } + + /// + /// Override this to process the pixel in the second pass of the algorithm. + /// + /// The pixel to quantize + /// The quantized value. + protected abstract byte QuantizePixel(Color32 pixel); + + /// + /// Retrieve the palette for the quantized image + /// + /// Any old palette, this is overwritten + /// The new colour palette + protected abstract ColorPalette GetPalette(ColorPalette original); + + /// + /// Structure that defines a 32 bpp colour + /// + /// + /// This structure is used to read data from a 32 bits per pixel image + /// in memory, and is ordered in this manner as this is the way that + /// the data is laid out in memory + /// + [StructLayout(LayoutKind.Explicit)] + public struct Color32 + { + /// + /// Holds the blue component of the colour + /// + [FieldOffset(0)] + public byte Blue; + + /// + /// Holds the green component of the colour + /// + [FieldOffset(1)] + public byte Green; + + /// + /// Holds the red component of the colour + /// + [FieldOffset(2)] + public byte Red; + + /// + /// Holds the alpha component of the colour + /// + [FieldOffset(3)] + public byte Alpha; + + /// + /// Permits the color32 to be treated as an int32 + /// + [FieldOffset(0)] + public int ARGB; + + /// + /// Initializes a new instance of the Color32 structure. + /// + /// The pointer to the pixel. + public Color32(IntPtr sourcePixel) + { + this = (Color32)Marshal.PtrToStructure(sourcePixel, typeof(Color32)); + } + + /// + /// Gets the colour for this Color32 object + /// + public Color Color + { + get { return Color.FromArgb(this.Alpha, this.Red, this.Green, this.Blue); } + } + } + } +} diff --git a/src/ImageProcessor/Imaging/ResponseType.cs b/src/ImageProcessor/Imaging/ResponseType.cs new file mode 100644 index 000000000..7ae3748b5 --- /dev/null +++ b/src/ImageProcessor/Imaging/ResponseType.cs @@ -0,0 +1,62 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Imaging +{ + #region Using + using System.ComponentModel; + #endregion + + /// + /// Globally available enumeration which specifies the correct HTTP MIME type of + /// the output stream for different response types. + /// + /// http://en.wikipedia.org/wiki/Internet_media_type"/ + /// + /// + public enum ResponseType + { + #region Image + /// + /// The correct HTTP MIME type of the output stream for bmp images. + /// + [DescriptionAttribute("image/bmp")] + Bmp, + + /// + /// The correct HTTP MIME type of the output stream for gif images. + /// + [DescriptionAttribute("image/gif")] + Gif, + + /// + /// The correct HTTP MIME type of the output stream for jpeg images. + /// + [DescriptionAttribute("image/jpeg")] + Jpeg, + + /// + /// The correct HTTP MIME type of the output stream for png images. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Png", Justification = "File extension name")] + [DescriptionAttribute("image/png")] + Png, + + /// + /// The correct HTTP MIME type of the output stream for svg images. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Svg", Justification = "File extension name")] + [DescriptionAttribute("image/svg+xml")] + Svg, + + /// + /// The correct HTTP MIME type of the output stream for tiff images. + /// + [DescriptionAttribute("image/tiff")] + Tiff, + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Alpha.cs b/src/ImageProcessor/Processors/Alpha.cs new file mode 100644 index 000000000..87d2783b4 --- /dev/null +++ b/src/ImageProcessor/Processors/Alpha.cs @@ -0,0 +1,175 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Imaging; + using System.Text.RegularExpressions; + using ImageProcessor.Helpers.Extensions; + #endregion + + /// + /// Encapsulates methods to change the alpha component of the image to effect its transparency. + /// + public class Alpha : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// http://stackoverflow.com/a/6400969/427899 + /// + private static readonly Regex QueryRegex = new Regex(@"alpha=\d{1,2}(?!\d)|alpha=100", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Alpha"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Changes the alpha component of the image to effect its transparency."; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + int percentage = match.Value.ToIntegerArray()[0]; + + this.DynamicParameter = percentage; + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Image image = factory.Image; + + try + { + int alphaPercent = this.DynamicParameter; + + newImage = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb) { Tag = image.Tag }; + + ColorMatrix colorMatrix = new ColorMatrix(); + colorMatrix.Matrix00 = colorMatrix.Matrix11 = colorMatrix.Matrix22 = colorMatrix.Matrix44 = 1; + colorMatrix.Matrix33 = (float)alphaPercent / 100; + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + using (ImageAttributes imageAttributes = new ImageAttributes()) + { + imageAttributes.SetColorMatrix(colorMatrix); + + graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, imageAttributes); + + image.Dispose(); + image = newImage; + } + } + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Crop.cs b/src/ImageProcessor/Processors/Crop.cs new file mode 100644 index 000000000..fb5810989 --- /dev/null +++ b/src/ImageProcessor/Processors/Crop.cs @@ -0,0 +1,210 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Imaging; + using System.Text.RegularExpressions; + using ImageProcessor.Helpers.Extensions; + #endregion + + /// + /// Crops an image to the given directions. + /// + public class Crop : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// http://stackoverflow.com/a/6400969/427899 + /// + private static readonly Regex QueryRegex = new Regex(@"crop=\d+-\d+-\d+-\d+", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Crop"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Crops an image to the given directions."; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + int[] coordinates = match.Value.ToIntegerArray(); + + int x = coordinates[0]; + int y = coordinates[1]; + int width = coordinates[2]; + int height = coordinates[3]; + + Rectangle rectangle = new Rectangle(x, y, width, height); + this.DynamicParameter = rectangle; + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Image image = factory.Image; + try + { + Rectangle rectangle = this.DynamicParameter; + + int sourceWidth = image.Width; + int sourceHeight = image.Height; + + if (rectangle.X < sourceWidth && rectangle.Y < sourceHeight) + { + if (rectangle.Width > (sourceWidth - rectangle.X)) + { + rectangle.Width = sourceWidth - rectangle.X; + } + + if (rectangle.Height > (sourceHeight - rectangle.Y)) + { + rectangle.Height = sourceHeight - rectangle.Y; + } + + newImage = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb) { Tag = image.Tag }; + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + + // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image + // as the algorithm appears to be pulling averaging detail from surrounding pixels beyond the edge + // of the image. Using the ImageAttributes class to specify that the pixels beyond are simply mirror + // images of the pixels within solves this problem. + using (ImageAttributes wrapMode = new ImageAttributes()) + { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage( + image, + new Rectangle(0, 0, rectangle.Width, rectangle.Height), + rectangle.X, + rectangle.Y, + rectangle.Width, + rectangle.Height, + GraphicsUnit.Pixel, + wrapMode); + } + + // Reassign the image. + image.Dispose(); + image = newImage; + } + } + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Filter.cs b/src/ImageProcessor/Processors/Filter.cs new file mode 100644 index 000000000..b3c0d2756 --- /dev/null +++ b/src/ImageProcessor/Processors/Filter.cs @@ -0,0 +1,292 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Imaging; + using System.Text.RegularExpressions; + #endregion + + /// + /// Encapsulates methods with which to add filters to an image. + /// + public class Filter : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"filter=(lomograph|polaroid|blackwhite|sepia|greyscale)", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Filter"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Encapsulates methods with which to add filters to an image. e.g polaroid, lomograph"; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + this.DynamicParameter = match.Value.Split('=')[1]; + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Image image = factory.Image; + try + { + newImage = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb) { Tag = image.Tag }; + + ColorMatrix colorMatrix = null; + + switch ((string)this.DynamicParameter) + { + case "polaroid": + colorMatrix = ColorMatrixes.Poloroid; + break; + case "lomograph": + colorMatrix = ColorMatrixes.Lomograph; + break; + case "sepia": + colorMatrix = ColorMatrixes.Sepia; + break; + case "blackwhite": + colorMatrix = ColorMatrixes.BlackWhite; + break; + case "greyscale": + colorMatrix = ColorMatrixes.GreyScale; + break; + } + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + using (ImageAttributes attributes = new ImageAttributes()) + { + if (colorMatrix != null) + { + attributes.SetColorMatrix(colorMatrix); + } + + Rectangle rectangle = new Rectangle(0, 0, image.Width, image.Height); + + graphics.DrawImage(image, rectangle, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, attributes); + } + } + + // Reassign the image. + image.Dispose(); + image = newImage; + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + #endregion + + /// + /// A list of available color matrices to apply to an image. + /// + private static class ColorMatrixes + { + /// + /// Gets Sepia. + /// + internal static ColorMatrix Sepia + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { .393f, .349f, .272f, 0, 0 }, + new float[] { .769f, .686f, .534f, 0, 0 }, + new float[] { .189f, .168f, .131f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + } + } + + /// + /// Gets BlackWhite. + /// + internal static ColorMatrix BlackWhite + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { 1.5f, 1.5f, 1.5f, 0, 0 }, + new float[] { 1.5f, 1.5f, 1.5f, 0, 0 }, + new float[] { 1.5f, 1.5f, 1.5f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { -1, -1, -1, 0, 1 } + }); + } + } + + /// + /// Gets Poloroid. + /// + internal static ColorMatrix Poloroid + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { 1.438f, -0.062f, -0.062f, 0, 0 }, + new float[] { -0.122f, 1.378f, -0.122f, 0, 0 }, + new float[] { -0.016f, -0.016f, 1.483f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { -0.03f, 0.05f, -0.02f, 0, 1 } + }); + } + } + + /// + /// Gets Lomograph. + /// + internal static ColorMatrix Lomograph + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { 1.25f, 0, 0, 0, 0 }, + new float[] { 0, 1.25f, 0, 0, 0 }, + new float[] { 0, 0, 0.94f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + } + } + + /// + /// Gets GreyScale. + /// + internal static ColorMatrix GreyScale + { + get + { + return new ColorMatrix( + new float[][] + { + new float[] { .33f, .33f, .33f, 0, 0 }, + new float[] { .59f, .59f, .59f, 0, 0 }, + new float[] { .11f, .11f, .11f, 0, 0 }, + new float[] { 0, 0, 0, 1, 0 }, + new float[] { 0, 0, 0, 0, 1 } + }); + } + } + } + } +} diff --git a/src/ImageProcessor/Processors/Format.cs b/src/ImageProcessor/Processors/Format.cs new file mode 100644 index 000000000..f0725d86f --- /dev/null +++ b/src/ImageProcessor/Processors/Format.cs @@ -0,0 +1,159 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Imaging; + using System.Text.RegularExpressions; + #endregion + + /// + /// Sets the output of the image to a specific format. + /// + public class Format : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"format=(jpeg|png|bmp|gif)", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Format"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Sets the output of the image to a specific format."; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + this.DynamicParameter = match.Value.Split('=')[1]; + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + string format = this.DynamicParameter; + ImageFormat imageFormat; + switch (format) + { + case "png": + imageFormat = ImageFormat.Png; + break; + case "bmp": + imageFormat = ImageFormat.Bmp; + break; + case "gif": + imageFormat = ImageFormat.Gif; + break; + default: + // Should be a jpeg. + imageFormat = ImageFormat.Jpeg; + break; + } + + // Set the internal property. + factory.ImageFormat = imageFormat; + + return factory.Image; + } + #endregion + } +} diff --git a/src/ImageProcessor/Processors/IGraphicsProcessor.cs b/src/ImageProcessor/Processors/IGraphicsProcessor.cs new file mode 100644 index 000000000..672bea4ff --- /dev/null +++ b/src/ImageProcessor/Processors/IGraphicsProcessor.cs @@ -0,0 +1,80 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + + using System.Collections; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Drawing; + using System.Text.RegularExpressions; + #endregion + + /// + /// Defines properties and methods for ImageProcessor Plugins. + /// + public interface IGraphicsProcessor + { + #region MetaData + /// + /// Gets the name. + /// + string Name { get; } + + /// + /// Gets the description. + /// + string Description { get; } + #endregion + + /// + /// Gets the regular expression to search strings for. + /// + Regex RegexPattern { get; } + + /// + /// Gets DynamicParameter. + /// + dynamic DynamicParameter { get; } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + int SortOrder { get; } + + /// + /// Gets or sets any additional settings required by the processor. + /// + Dictionary Settings { get; set; } + + #region Methods + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + int MatchRegexIndex(string queryString); + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + Image ProcessImage(ImageFactory factory); + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Png8.cs b/src/ImageProcessor/Processors/Png8.cs new file mode 100644 index 000000000..36b44f0b7 --- /dev/null +++ b/src/ImageProcessor/Processors/Png8.cs @@ -0,0 +1,147 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Drawing; + using System.Linq; + using System.Text; + using System.Text.RegularExpressions; + using ImageProcessor.Imaging; + + /// + /// TODO: Update summary. + /// + public class Png8 : IGraphicsProcessor + { + #region Implementation of IGraphicsProcessor + + /// + /// Gets the name. + /// + public string Name + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets DynamicParameter. + /// + public dynamic DynamicParameter + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get + { + throw new NotImplementedException(); + } + set + { + throw new NotImplementedException(); + } + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + throw new NotImplementedException(); + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Image image = factory.Image; + try + { + newImage = new Bitmap(image) { Tag = image.Tag }; + using (Graphics graphics = Graphics.FromImage(newImage)) + { + ArrayList pallete = new ArrayList(); + PaletteQuantizer paletteQuantizer = new PaletteQuantizer(new ArrayList(newImage.Palette.Entries)); + newImage = paletteQuantizer.Quantize(newImage); + } + + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Quality.cs b/src/ImageProcessor/Processors/Quality.cs new file mode 100644 index 000000000..2e4dfb66f --- /dev/null +++ b/src/ImageProcessor/Processors/Quality.cs @@ -0,0 +1,142 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System.Collections.Generic; + using System.Drawing; + using System.Text.RegularExpressions; + using ImageProcessor.Helpers.Extensions; + #endregion + + /// + /// TODO: Update summary. + /// + public class Quality : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"quality=\d{1,2}(?!\d)|quality=100", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Quality"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Sets the the quality output for jpeg images."; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + int percentage = match.Value.ToIntegerArray()[0]; + + this.DynamicParameter = percentage; + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + // Set the internal property. + factory.JpegQuality = this.DynamicParameter; + + return factory.Image; + } + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Resize.cs b/src/ImageProcessor/Processors/Resize.cs new file mode 100644 index 000000000..8fe518796 --- /dev/null +++ b/src/ImageProcessor/Processors/Resize.cs @@ -0,0 +1,235 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System; + using System.Collections; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Drawing.Imaging; + using System.Text.RegularExpressions; + using ImageProcessor.Helpers.Extensions; + #endregion + + /// + /// Resizes an image to the given dimensions. + /// + public class Resize : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"(width|height)=\d+", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Resize"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Resizes an image to the given dimensions."; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + Size size = new Size(); + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + } + + if (match.Value.Contains("width")) + { + size.Width = match.Value.ToIntegerArray()[0]; + } + else + { + size.Height = match.Value.ToIntegerArray()[0]; + } + + index += 1; + } + } + + this.DynamicParameter = size; + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Image image = factory.Image; + + try + { + int width = this.DynamicParameter.Width ?? 0; + int height = this.DynamicParameter.Height ?? 0; + int sourceWidth = image.Width; + int sourceHeight = image.Height; + int defaultMaxWidth; + int defaultMaxHeight; + int.TryParse(this.Settings["MaxWidth"], out defaultMaxWidth); + int.TryParse(this.Settings["MaxHeight"], out defaultMaxHeight); + int maxWidth = defaultMaxWidth > 0 ? defaultMaxWidth : int.MaxValue; + int maxHeight = defaultMaxHeight > 0 ? defaultMaxHeight : int.MaxValue; + + // If height or width is not passed we assume that the standard ratio is to be kept. + if (height == 0) + { + // Bit of simple fractional maths here. + float percentWidth = Math.Abs(width / (float)sourceWidth); + height = (int)Math.Floor(sourceHeight * percentWidth); + } + + if (width == 0) + { + float percentHeight = Math.Abs(height / (float)sourceHeight); + width = (int)Math.Floor(sourceWidth * percentHeight); + } + + if (width > 0 && height > 0 && width <= maxWidth && height <= maxHeight) + { + newImage = new Bitmap(width, height, PixelFormat.Format32bppPArgb) { Tag = image.Tag }; + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + // We want to use two different blending algorithms for enlargement/shrinking. + // Bicubic is better enlarging for whilst Bilinear is better for shrinking. + // http://www.codinghorror.com/blog/2007/07/better-image-resizing.html + if (image.Width < width && image.Height < height) + { + // We are making it larger. + graphics.SmoothingMode = SmoothingMode.AntiAlias; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; + graphics.CompositingQuality = CompositingQuality.HighQuality; + } + else + { + // We are making it smaller. + graphics.SmoothingMode = SmoothingMode.None; + + // Contrary to everything I have read bicubic is producing the best results. + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.PixelOffsetMode = PixelOffsetMode.None; + graphics.CompositingQuality = CompositingQuality.HighSpeed; + } + + // An unwanted border appears when using InterpolationMode.HighQualityBicubic to resize the image + // as the algorithm appears to be pulling averaging detail from surFlooring pixels beyond the edge + // of the image. Using the ImageAttributes class to specify that the pixels beyond are simply mirror + // images of the pixels within solves this problem. + using (ImageAttributes wrapMode = new ImageAttributes()) + { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + Rectangle destRect = new Rectangle(0, 0, width, height); + graphics.DrawImage(image, destRect, 0, 0, sourceWidth, sourceHeight, GraphicsUnit.Pixel, wrapMode); + } + + // Reassign the image. + image.Dispose(); + image = newImage; + } + } + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + + #endregion + } +} diff --git a/src/ImageProcessor/Processors/Vignette.cs b/src/ImageProcessor/Processors/Vignette.cs new file mode 100644 index 000000000..fa1f5d880 --- /dev/null +++ b/src/ImageProcessor/Processors/Vignette.cs @@ -0,0 +1,200 @@ +// ----------------------------------------------------------------------- +// +// TODO: Update copyright text. +// +// ----------------------------------------------------------------------- + +namespace ImageProcessor.Processors +{ + #region Using + using System; + using System.Collections.Generic; + using System.Drawing; + using System.Drawing.Drawing2D; + using System.Text.RegularExpressions; + #endregion + + /// + /// Encapsulates methods with which to add a vignette image effect to an image. + /// + public class Vignette : IGraphicsProcessor + { + /// + /// The regular expression to search strings for. + /// + private static readonly Regex QueryRegex = new Regex(@"vignette=true", RegexOptions.Compiled); + + #region IGraphicsProcessor Members + /// + /// Gets the name. + /// + public string Name + { + get + { + return "Vignette"; + } + } + + /// + /// Gets the description. + /// + public string Description + { + get + { + return "Adds a vignette image effect to the image."; + } + } + + /// + /// Gets the regular expression to search strings for. + /// + public Regex RegexPattern + { + get + { + return QueryRegex; + } + } + + /// + /// Gets or sets DynamicParameter. + /// + public dynamic DynamicParameter + { + get; + set; + } + + /// + /// Gets the order in which this processor is to be used in a chain. + /// + public int SortOrder + { + get; + private set; + } + + /// + /// Gets or sets any additional settings required by the processor. + /// + public Dictionary Settings + { + get; + set; + } + + /// + /// The position in the original string where the first character of the captured substring was found. + /// + /// + /// The query string to search. + /// + /// + /// The zero-based starting position in the original string where the captured substring was found. + /// + public int MatchRegexIndex(string queryString) + { + int index = 0; + + // Set the sort order to max to allow filtering. + this.SortOrder = int.MaxValue; + + foreach (Match match in this.RegexPattern.Matches(queryString)) + { + if (match.Success) + { + if (index == 0) + { + // Set the index on the first instance only. + this.SortOrder = match.Index; + bool doVignette; + this.DynamicParameter = bool.TryParse(match.Value.Split('=')[1], out doVignette); + } + + index += 1; + } + } + + return this.SortOrder; + } + + /// + /// Processes the image. + /// + /// + /// The the current instance of the class containing + /// the image to process. + /// + /// + /// The processed image from the current instance of the class. + /// + public Image ProcessImage(ImageFactory factory) + { + Bitmap newImage = null; + Image image = factory.Image; + + try + { + newImage = new Bitmap(image) { Tag = image.Tag }; + + using (Graphics graphics = Graphics.FromImage(newImage)) + { + Rectangle bounds = new Rectangle(0, 0, newImage.Width, newImage.Height); + Rectangle ellipsebounds = bounds; + + // Increase the rectangle size by the difference between the rectangle dimensions and sqrt(2)/2 * the rectangle dimensions. + // Why sqrt(2)/2? Because the point (sqrt(2)/2, sqrt(2)/2) is the 45 degree angle point on a unit circle. Scaling by the width + // and height gives the distance needed to inflate the rect to make sure it's fully covered. + ellipsebounds.Offset(-ellipsebounds.X, -ellipsebounds.Y); + int x = ellipsebounds.Width - (int)Math.Floor(.70712 * ellipsebounds.Width); + int y = ellipsebounds.Height - (int)Math.Floor(.70712 * ellipsebounds.Height); + ellipsebounds.Inflate(x, y); + + using (GraphicsPath path = new GraphicsPath()) + { + path.AddEllipse(ellipsebounds); + using (PathGradientBrush brush = new PathGradientBrush(path)) + { + // Fill a rectangle with an elliptical gradient brush that goes from white to black. + // Change the colour from white to pure transparent black and from black to pure opaque black. + // This has the effect of painting the far corners black and shade less on the way in to the centre. + brush.WrapMode = WrapMode.Tile; + brush.CenterColor = Color.FromArgb(0, 0, 0, 0); + brush.SurroundColors = new Color[] { Color.FromArgb(255, 0, 0, 0) }; + + Blend blend = new Blend + { + Positions = new float[] { 0.0f, 0.2f, 0.4f, 0.6f, 0.8f, 1.0F }, + Factors = new float[] { 0.0f, 0.5f, 1f, 1f, 1.0f, 1.0f } + }; + + brush.Blend = blend; + + Region oldClip = graphics.Clip; + graphics.Clip = new Region(bounds); + graphics.FillRectangle(brush, ellipsebounds); + graphics.Clip = oldClip; + } + + // Reassign the image. + image.Dispose(); + image = newImage; + } + } + } + catch + { + if (newImage != null) + { + newImage.Dispose(); + } + } + + return image; + } + #endregion + } +} + diff --git a/src/ImageProcessor/Properties/AssemblyInfo.cs b/src/ImageProcessor/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..20aa364de --- /dev/null +++ b/src/ImageProcessor/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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("ImageProcessor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ImageProcessor")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("bdaae9bd-0dc8-4b06-8722-e2e0c9a74301")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Test/Test.sln b/src/Test/Test.sln new file mode 100644 index 000000000..7893f8741 --- /dev/null +++ b/src/Test/Test.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{30327C08-7574-4D7E-AC95-6A58753C6855}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {30327C08-7574-4D7E-AC95-6A58753C6855}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30327C08-7574-4D7E-AC95-6A58753C6855}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/Test/Test/Content/Site.css b/src/Test/Test/Content/Site.css new file mode 100644 index 000000000..0c669c7de --- /dev/null +++ b/src/Test/Test/Content/Site.css @@ -0,0 +1,324 @@ +/*---------------------------------------------------------- +The base color for this template is #5c87b2. If you'd like +to use a different color start by replacing all instances of +#5c87b2 with your new color. +----------------------------------------------------------*/ +body { + background-color: #5c87b2; + font-size: .85em; + font-family: "Trebuchet MS", Verdana, Helvetica, Sans-Serif; + margin: 0; + padding: 0; + color: #696969; +} + +a:link { + color: #034af3; + text-decoration: underline; +} + +a:visited { + color: #505abc; +} + +a:hover { + color: #1d60ff; + text-decoration: none; +} + +a:active { + color: #12eb87; +} + +p, ul { + margin-bottom: 20px; + line-height: 1.6em; +} + +header, +footer, +nav, +section { + display: block; +} + +/* HEADINGS +----------------------------------------------------------*/ +h1, h2, h3, h4, h5, h6 { + font-size: 1.5em; + color: #000; +} + +h1 { + font-size: 2em; + padding-bottom: 0; + margin-bottom: 0; +} + +h2 { + padding: 0 0 10px 0; +} + +h3 { + font-size: 1.2em; +} + +h4 { + font-size: 1.1em; +} + +h5, h6 { + font-size: 1em; +} + +/* PRIMARY LAYOUT ELEMENTS +----------------------------------------------------------*/ + +/* you can specify a greater or lesser percentage for the +page width. Or, you can specify an exact pixel width. */ +.page { + width: 90%; + margin-left: auto; + margin-right: auto; +} + +header, #header { + position: relative; + margin-bottom: 0px; + color: #000; + padding: 0; +} + +header h1, #header h1 { + font-weight: bold; + padding: 5px 0; + margin: 0; + color: #fff; + border: none; + line-height: 2em; + font-size: 32px !important; + text-shadow: 1px 1px 2px #111; +} + +#main { + padding: 30px 30px 15px 30px; + background-color: #fff; + border-radius: 4px 0 0 0; + -webkit-border-radius: 4px 0 0 0; + -moz-border-radius: 4px 0 0 0; +} + +footer, +#footer { + background-color: #fff; + color: #999; + padding: 10px 0; + text-align: center; + line-height: normal; + margin: 0 0 30px 0; + font-size: .9em; + border-radius: 0 0 4px 4px; + -webkit-border-radius: 0 0 4px 4px; + -moz-border-radius: 0 0 4px 4px; +} + +/* TAB MENU +----------------------------------------------------------*/ +ul#menu { + border-bottom: 1px #5C87B2 solid; + padding: 0 0 2px; + position: relative; + margin: 0; + text-align: right; +} + +ul#menu li { + display: inline; + list-style: none; +} + +ul#menu li#greeting { + padding: 10px 20px; + font-weight: bold; + text-decoration: none; + line-height: 2.8em; + color: #fff; +} + +ul#menu li a { + padding: 10px 20px; + font-weight: bold; + text-decoration: none; + line-height: 2.8em; + background-color: #e8eef4; + color: #034af3; + border-radius: 4px 4px 0 0; + -webkit-border-radius: 4px 4px 0 0; + -moz-border-radius: 4px 4px 0 0; +} + +ul#menu li a:hover { + background-color: #fff; + text-decoration: none; +} + +ul#menu li a:active { + background-color: #a6e2a6; + text-decoration: none; +} + +ul#menu li.selected a { + background-color: #fff; + color: #000; +} + +/* FORM LAYOUT ELEMENTS +----------------------------------------------------------*/ + +fieldset { + border: 1px solid #ddd; + padding: 0 1.4em 1.4em 1.4em; + margin: 0 0 1.5em 0; +} + +legend { + font-size: 1.2em; + font-weight: bold; +} + +textarea { + min-height: 75px; +} + +input[type="text"], +input[type="password"] { + border: 1px solid #ccc; + padding: 2px; + font-size: 1.2em; + color: #444; + width: 200px; +} + +select { + border: 1px solid #ccc; + padding: 2px; + font-size: 1.2em; + color: #444; +} + +input[type="submit"] { + font-size: 1.2em; + padding: 5px; +} + +/* TABLE +----------------------------------------------------------*/ + +table { + border: solid 1px #e8eef4; + border-collapse: collapse; +} + +table td { + padding: 5px; + border: solid 1px #e8eef4; +} + +table th { + padding: 6px 5px; + text-align: left; + background-color: #e8eef4; + border: solid 1px #e8eef4; +} + +/* MISC +----------------------------------------------------------*/ +.clear { + clear: both; +} + +.error { + color: Red; +} + +nav, +#menucontainer { + margin-top: 40px; +} + +div#title { + display: block; + float: left; + text-align: left; +} + +#logindisplay { + font-size: 1.1em; + display: block; + text-align: right; + margin: 10px; + color: White; +} + +#logindisplay a:link { + color: white; + text-decoration: underline; +} + +#logindisplay a:visited { + color: white; + text-decoration: underline; +} + +#logindisplay a:hover { + color: white; + text-decoration: none; +} + +/* Styles for validation helpers +-----------------------------------------------------------*/ +.field-validation-error { + color: #ff0000; +} + +.field-validation-valid { + display: none; +} + +.input-validation-error { + border: 1px solid #ff0000; + background-color: #ffeeee; +} + +.validation-summary-errors { + font-weight: bold; + color: #ff0000; +} + +.validation-summary-valid { + display: none; +} + +/* Styles for editor and display helpers +----------------------------------------------------------*/ +.display-label, +.editor-label { + margin: 1em 0 0 0; +} + +.display-field, +.editor-field { + margin: 0.5em 0 0 0; +} + +.text-box { + width: 30em; +} + +.text-box.multi-line { + height: 6.5em; +} + +.tri-state { + width: 6em; +} diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png b/src/Test/Test/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png new file mode 100644 index 000000000..5b5dab2ab Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_flat_0_aaaaaa_40x100.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png b/src/Test/Test/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png new file mode 100644 index 000000000..ac8b229af Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_flat_75_ffffff_40x100.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png b/src/Test/Test/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png new file mode 100644 index 000000000..ad3d6346e Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_glass_55_fbf9ee_1x400.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png b/src/Test/Test/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png new file mode 100644 index 000000000..42ccba269 Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_glass_65_ffffff_1x400.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png b/src/Test/Test/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png new file mode 100644 index 000000000..5a46b47cb Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_glass_75_dadada_1x400.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png b/src/Test/Test/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png new file mode 100644 index 000000000..86c2baa65 Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_glass_75_e6e6e6_1x400.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png b/src/Test/Test/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png new file mode 100644 index 000000000..4443fdc1a Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_glass_95_fef1ec_1x400.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png b/src/Test/Test/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png new file mode 100644 index 000000000..7c9fa6c6e Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-bg_highlight-soft_75_cccccc_1x100.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-icons_222222_256x240.png b/src/Test/Test/Content/themes/base/images/ui-icons_222222_256x240.png new file mode 100644 index 000000000..ee039dc09 Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-icons_222222_256x240.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-icons_2e83ff_256x240.png b/src/Test/Test/Content/themes/base/images/ui-icons_2e83ff_256x240.png new file mode 100644 index 000000000..45e8928e5 Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-icons_2e83ff_256x240.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-icons_454545_256x240.png b/src/Test/Test/Content/themes/base/images/ui-icons_454545_256x240.png new file mode 100644 index 000000000..7ec70d11b Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-icons_454545_256x240.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-icons_888888_256x240.png b/src/Test/Test/Content/themes/base/images/ui-icons_888888_256x240.png new file mode 100644 index 000000000..5ba708c39 Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-icons_888888_256x240.png differ diff --git a/src/Test/Test/Content/themes/base/images/ui-icons_cd0a0a_256x240.png b/src/Test/Test/Content/themes/base/images/ui-icons_cd0a0a_256x240.png new file mode 100644 index 000000000..7930a5580 Binary files /dev/null and b/src/Test/Test/Content/themes/base/images/ui-icons_cd0a0a_256x240.png differ diff --git a/src/Test/Test/Content/themes/base/jquery.ui.accordion.css b/src/Test/Test/Content/themes/base/jquery.ui.accordion.css new file mode 100644 index 000000000..4a67cbf8e --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.accordion.css @@ -0,0 +1,24 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Accordion 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Accordion#theming + */ +/* IE/Win - Fix animation bug - #4615 */ +.ui-accordion { width: 100%; } +.ui-accordion .ui-accordion-header { cursor: pointer; position: relative; margin-top: 1px; zoom: 1; } +.ui-accordion .ui-accordion-li-fix { display: inline; } +.ui-accordion .ui-accordion-header-active { border-bottom: 0 !important; } +.ui-accordion .ui-accordion-header a { display: block; font-size: 1em; padding: .5em .5em .5em .7em; } +.ui-accordion-icons .ui-accordion-header a { padding-left: 2.2em; } +.ui-accordion .ui-accordion-header .ui-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; } +.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; margin-top: -2px; position: relative; top: 1px; margin-bottom: 2px; overflow: auto; display: none; zoom: 1; } +.ui-accordion .ui-accordion-content-active { display: block; } diff --git a/src/Test/Test/Content/themes/base/jquery.ui.all.css b/src/Test/Test/Content/themes/base/jquery.ui.all.css new file mode 100644 index 000000000..2b2c103e8 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.all.css @@ -0,0 +1,16 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI CSS Framework 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Theming + */ +@import "jquery.ui.base.css"; +@import "jquery.ui.theme.css"; diff --git a/src/Test/Test/Content/themes/base/jquery.ui.autocomplete.css b/src/Test/Test/Content/themes/base/jquery.ui.autocomplete.css new file mode 100644 index 000000000..aac4e204d --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.autocomplete.css @@ -0,0 +1,62 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Autocomplete 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * http://docs.jquery.com/UI/Autocomplete#theming + */ +.ui-autocomplete { position: absolute; cursor: default; } + +/* workarounds */ +* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */ + +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Menu 1.8.11 + * + * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Menu#theming + */ +.ui-menu { + list-style:none; + padding: 2px; + margin: 0; + display:block; + float: left; +} +.ui-menu .ui-menu { + margin-top: -3px; +} +.ui-menu .ui-menu-item { + margin:0; + padding: 0; + zoom: 1; + float: left; + clear: left; + width: 100%; +} +.ui-menu .ui-menu-item a { + text-decoration:none; + display:block; + padding:.2em .4em; + line-height:1.5; + zoom:1; +} +.ui-menu .ui-menu-item a.ui-state-hover, +.ui-menu .ui-menu-item a.ui-state-active { + font-weight: normal; + margin: -1px; +} diff --git a/src/Test/Test/Content/themes/base/jquery.ui.base.css b/src/Test/Test/Content/themes/base/jquery.ui.base.css new file mode 100644 index 000000000..f52ee39b9 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.base.css @@ -0,0 +1,11 @@ +@import url("jquery.ui.core.css"); +@import url("jquery.ui.resizable.css"); +@import url("jquery.ui.selectable.css"); +@import url("jquery.ui.accordion.css"); +@import url("jquery.ui.autocomplete.css"); +@import url("jquery.ui.button.css"); +@import url("jquery.ui.dialog.css"); +@import url("jquery.ui.slider.css"); +@import url("jquery.ui.tabs.css"); +@import url("jquery.ui.datepicker.css"); +@import url("jquery.ui.progressbar.css"); \ No newline at end of file diff --git a/src/Test/Test/Content/themes/base/jquery.ui.button.css b/src/Test/Test/Content/themes/base/jquery.ui.button.css new file mode 100644 index 000000000..af6c985b8 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.button.css @@ -0,0 +1,43 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Button 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Button#theming + */ +.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; text-decoration: none !important; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */ +.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */ +button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */ +.ui-button-icons-only { width: 3.4em; } +button.ui-button-icons-only { width: 3.7em; } + +/*button text element */ +.ui-button .ui-button-text { display: block; line-height: 1.4; } +.ui-button-text-only .ui-button-text { padding: .4em 1em; } +.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; } +.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; } +.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; } +.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; } +/* no icon support for input elements, provide padding by default */ +input.ui-button { padding: .4em 1em; } + +/*button icon element(s) */ +.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; } +.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; } +.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; } +.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } +.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; } + +/*button sets*/ +.ui-buttonset { margin-right: 7px; } +.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; } + +/* workarounds */ +button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */ diff --git a/src/Test/Test/Content/themes/base/jquery.ui.core.css b/src/Test/Test/Content/themes/base/jquery.ui.core.css new file mode 100644 index 000000000..55fb8b0d9 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.core.css @@ -0,0 +1,46 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI CSS Framework 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Theming/API + */ + +/* Layout helpers +----------------------------------*/ +.ui-helper-hidden { display: none; } +.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); } +.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; } +.ui-helper-clearfix:after { content: "."; display: block; height: 0; clear: both; visibility: hidden; } +.ui-helper-clearfix { display: inline-block; } +/* required comment for clearfix to work in Opera \*/ +* html .ui-helper-clearfix { height:1%; } +.ui-helper-clearfix { display:block; } +/* end clearfix */ +.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); } + + +/* Interaction Cues +----------------------------------*/ +.ui-state-disabled { cursor: default !important; } + + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; } + + +/* Misc visuals +----------------------------------*/ + +/* Overlays */ +.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; } diff --git a/src/Test/Test/Content/themes/base/jquery.ui.datepicker.css b/src/Test/Test/Content/themes/base/jquery.ui.datepicker.css new file mode 100644 index 000000000..7126923cc --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.datepicker.css @@ -0,0 +1,73 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Datepicker 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Datepicker#theming + */ +.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; } +.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; } +.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; } +.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; } +.ui-datepicker .ui-datepicker-prev { left:2px; } +.ui-datepicker .ui-datepicker-next { right:2px; } +.ui-datepicker .ui-datepicker-prev-hover { left:1px; } +.ui-datepicker .ui-datepicker-next-hover { right:1px; } +.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; } +.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; } +.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; } +.ui-datepicker select.ui-datepicker-month-year {width: 100%;} +.ui-datepicker select.ui-datepicker-month, +.ui-datepicker select.ui-datepicker-year { width: 49%;} +.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; } +.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; } +.ui-datepicker td { border: 0; padding: 1px; } +.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; } +.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; } +.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; } +.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; } + +/* with multiple calendars */ +.ui-datepicker.ui-datepicker-multi { width:auto; } +.ui-datepicker-multi .ui-datepicker-group { float:left; } +.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; } +.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; } +.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; } +.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; } +.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; } +.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; } +.ui-datepicker-row-break { clear:both; width:100%; } + +/* RTL support */ +.ui-datepicker-rtl { direction: rtl; } +.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; } +.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; } +.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; } +.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; } +.ui-datepicker-rtl .ui-datepicker-group { float:right; } +.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; } +.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; } + +/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */ +.ui-datepicker-cover { + display: none; /*sorry for IE5*/ + display/**/: block; /*sorry for IE5*/ + position: absolute; /*must have*/ + z-index: -1; /*must have*/ + filter: mask(); /*must have*/ + top: -4px; /*must have*/ + left: -4px; /*must have*/ + width: 200px; /*must have*/ + height: 200px; /*must have*/ +} \ No newline at end of file diff --git a/src/Test/Test/Content/themes/base/jquery.ui.dialog.css b/src/Test/Test/Content/themes/base/jquery.ui.dialog.css new file mode 100644 index 000000000..311dd32e5 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.dialog.css @@ -0,0 +1,26 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Dialog 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Dialog#theming + */ +.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; } +.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; } +.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; } +.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; } +.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; } +.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; } +.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; } +.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; } +.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; } +.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; } +.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; } +.ui-draggable .ui-dialog-titlebar { cursor: move; } diff --git a/src/Test/Test/Content/themes/base/jquery.ui.progressbar.css b/src/Test/Test/Content/themes/base/jquery.ui.progressbar.css new file mode 100644 index 000000000..6e8718e03 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.progressbar.css @@ -0,0 +1,16 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Progressbar 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Progressbar#theming + */ +.ui-progressbar { height:2em; text-align: left; } +.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; } \ No newline at end of file diff --git a/src/Test/Test/Content/themes/base/jquery.ui.resizable.css b/src/Test/Test/Content/themes/base/jquery.ui.resizable.css new file mode 100644 index 000000000..bf037be18 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.resizable.css @@ -0,0 +1,25 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Resizable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about)] + * + * http://docs.jquery.com/UI/Resizable#theming + */ +.ui-resizable { position: relative;} +.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;} +.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; } +.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; } +.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; } +.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; } +.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; } +.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; } +.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; } +.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; } +.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;} \ No newline at end of file diff --git a/src/Test/Test/Content/themes/base/jquery.ui.selectable.css b/src/Test/Test/Content/themes/base/jquery.ui.selectable.css new file mode 100644 index 000000000..011416b6f --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.selectable.css @@ -0,0 +1,15 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Selectable 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Selectable#theming + */ +.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; } diff --git a/src/Test/Test/Content/themes/base/jquery.ui.slider.css b/src/Test/Test/Content/themes/base/jquery.ui.slider.css new file mode 100644 index 000000000..3bbfb932d --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.slider.css @@ -0,0 +1,29 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Slider 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Slider#theming + */ +.ui-slider { position: relative; text-align: left; } +.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; } +.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; } + +.ui-slider-horizontal { height: .8em; } +.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; } +.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; } +.ui-slider-horizontal .ui-slider-range-min { left: 0; } +.ui-slider-horizontal .ui-slider-range-max { right: 0; } + +.ui-slider-vertical { width: .8em; height: 100px; } +.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; } +.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; } +.ui-slider-vertical .ui-slider-range-min { bottom: 0; } +.ui-slider-vertical .ui-slider-range-max { top: 0; } \ No newline at end of file diff --git a/src/Test/Test/Content/themes/base/jquery.ui.tabs.css b/src/Test/Test/Content/themes/base/jquery.ui.tabs.css new file mode 100644 index 000000000..aa5cd8a54 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.tabs.css @@ -0,0 +1,23 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI Tabs 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Tabs#theming + */ +.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */ +.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; } +.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 1px; margin: 0 .2em 1px 0; border-bottom: 0 !important; padding: 0; white-space: nowrap; } +.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected { margin-bottom: 0; padding-bottom: 1px; } +.ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; } +.ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */ +.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; } +.ui-tabs .ui-tabs-hide { display: none !important; } diff --git a/src/Test/Test/Content/themes/base/jquery.ui.theme.css b/src/Test/Test/Content/themes/base/jquery.ui.theme.css new file mode 100644 index 000000000..0d5b73546 --- /dev/null +++ b/src/Test/Test/Content/themes/base/jquery.ui.theme.css @@ -0,0 +1,257 @@ +/* + * Note: While Microsoft is not the author of this file, Microsoft is + * offering you a license subject to the terms of the Microsoft Software + * License Terms for Microsoft ASP.NET Model View Controller 3. + * Microsoft reserves all other rights. The notices below are provided + * for informational purposes only and are not the license terms under + * which Microsoft distributed this file. + * + * jQuery UI CSS Framework 1.8.11 + * + * Copyright 2011, AUTHORS.txt (http://jqueryui.com/about) + * + * http://docs.jquery.com/UI/Theming/API + * + * To view and modify this theme, visit http://jqueryui.com/themeroller/ + */ + + +/* Component containers +----------------------------------*/ +.ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; } +.ui-widget .ui-widget { font-size: 1em; } +.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; } +.ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(images/ui-bg_flat_75_ffffff_40x100.png)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; } +.ui-widget-content a { color: #222222/*{fcContent}*/; } +.ui-widget-header { border: 1px solid #aaaaaa/*{borderColorHeader}*/; background: #cccccc/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #222222/*{fcHeader}*/; font-weight: bold; } +.ui-widget-header a { color: #222222/*{fcHeader}*/; } + +/* Interaction states +----------------------------------*/ +.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; } +.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; } +.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; } +.ui-state-hover a, .ui-state-hover a:hover { color: #212121/*{fcHover}*/; text-decoration: none; } +.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; } +.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; text-decoration: none; } +.ui-widget :active { outline: none; } + +/* Interaction Cues +----------------------------------*/ +.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; } +.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636/*{fcHighlight}*/; } +.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; } +.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a/*{fcError}*/; } +.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a/*{fcError}*/; } +.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; } +.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; } +.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; } + +/* Icons +----------------------------------*/ + +/* states and images */ +.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } +.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; } +.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/; } +.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; } +.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/; } +.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; } +.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; } +.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; } + +/* positioning */ +.ui-icon-carat-1-n { background-position: 0 0; } +.ui-icon-carat-1-ne { background-position: -16px 0; } +.ui-icon-carat-1-e { background-position: -32px 0; } +.ui-icon-carat-1-se { background-position: -48px 0; } +.ui-icon-carat-1-s { background-position: -64px 0; } +.ui-icon-carat-1-sw { background-position: -80px 0; } +.ui-icon-carat-1-w { background-position: -96px 0; } +.ui-icon-carat-1-nw { background-position: -112px 0; } +.ui-icon-carat-2-n-s { background-position: -128px 0; } +.ui-icon-carat-2-e-w { background-position: -144px 0; } +.ui-icon-triangle-1-n { background-position: 0 -16px; } +.ui-icon-triangle-1-ne { background-position: -16px -16px; } +.ui-icon-triangle-1-e { background-position: -32px -16px; } +.ui-icon-triangle-1-se { background-position: -48px -16px; } +.ui-icon-triangle-1-s { background-position: -64px -16px; } +.ui-icon-triangle-1-sw { background-position: -80px -16px; } +.ui-icon-triangle-1-w { background-position: -96px -16px; } +.ui-icon-triangle-1-nw { background-position: -112px -16px; } +.ui-icon-triangle-2-n-s { background-position: -128px -16px; } +.ui-icon-triangle-2-e-w { background-position: -144px -16px; } +.ui-icon-arrow-1-n { background-position: 0 -32px; } +.ui-icon-arrow-1-ne { background-position: -16px -32px; } +.ui-icon-arrow-1-e { background-position: -32px -32px; } +.ui-icon-arrow-1-se { background-position: -48px -32px; } +.ui-icon-arrow-1-s { background-position: -64px -32px; } +.ui-icon-arrow-1-sw { background-position: -80px -32px; } +.ui-icon-arrow-1-w { background-position: -96px -32px; } +.ui-icon-arrow-1-nw { background-position: -112px -32px; } +.ui-icon-arrow-2-n-s { background-position: -128px -32px; } +.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; } +.ui-icon-arrow-2-e-w { background-position: -160px -32px; } +.ui-icon-arrow-2-se-nw { background-position: -176px -32px; } +.ui-icon-arrowstop-1-n { background-position: -192px -32px; } +.ui-icon-arrowstop-1-e { background-position: -208px -32px; } +.ui-icon-arrowstop-1-s { background-position: -224px -32px; } +.ui-icon-arrowstop-1-w { background-position: -240px -32px; } +.ui-icon-arrowthick-1-n { background-position: 0 -48px; } +.ui-icon-arrowthick-1-ne { background-position: -16px -48px; } +.ui-icon-arrowthick-1-e { background-position: -32px -48px; } +.ui-icon-arrowthick-1-se { background-position: -48px -48px; } +.ui-icon-arrowthick-1-s { background-position: -64px -48px; } +.ui-icon-arrowthick-1-sw { background-position: -80px -48px; } +.ui-icon-arrowthick-1-w { background-position: -96px -48px; } +.ui-icon-arrowthick-1-nw { background-position: -112px -48px; } +.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; } +.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; } +.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; } +.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; } +.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; } +.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; } +.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; } +.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; } +.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; } +.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; } +.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; } +.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; } +.ui-icon-arrowreturn-1-w { background-position: -64px -64px; } +.ui-icon-arrowreturn-1-n { background-position: -80px -64px; } +.ui-icon-arrowreturn-1-e { background-position: -96px -64px; } +.ui-icon-arrowreturn-1-s { background-position: -112px -64px; } +.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; } +.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; } +.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; } +.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; } +.ui-icon-arrow-4 { background-position: 0 -80px; } +.ui-icon-arrow-4-diag { background-position: -16px -80px; } +.ui-icon-extlink { background-position: -32px -80px; } +.ui-icon-newwin { background-position: -48px -80px; } +.ui-icon-refresh { background-position: -64px -80px; } +.ui-icon-shuffle { background-position: -80px -80px; } +.ui-icon-transfer-e-w { background-position: -96px -80px; } +.ui-icon-transferthick-e-w { background-position: -112px -80px; } +.ui-icon-folder-collapsed { background-position: 0 -96px; } +.ui-icon-folder-open { background-position: -16px -96px; } +.ui-icon-document { background-position: -32px -96px; } +.ui-icon-document-b { background-position: -48px -96px; } +.ui-icon-note { background-position: -64px -96px; } +.ui-icon-mail-closed { background-position: -80px -96px; } +.ui-icon-mail-open { background-position: -96px -96px; } +.ui-icon-suitcase { background-position: -112px -96px; } +.ui-icon-comment { background-position: -128px -96px; } +.ui-icon-person { background-position: -144px -96px; } +.ui-icon-print { background-position: -160px -96px; } +.ui-icon-trash { background-position: -176px -96px; } +.ui-icon-locked { background-position: -192px -96px; } +.ui-icon-unlocked { background-position: -208px -96px; } +.ui-icon-bookmark { background-position: -224px -96px; } +.ui-icon-tag { background-position: -240px -96px; } +.ui-icon-home { background-position: 0 -112px; } +.ui-icon-flag { background-position: -16px -112px; } +.ui-icon-calendar { background-position: -32px -112px; } +.ui-icon-cart { background-position: -48px -112px; } +.ui-icon-pencil { background-position: -64px -112px; } +.ui-icon-clock { background-position: -80px -112px; } +.ui-icon-disk { background-position: -96px -112px; } +.ui-icon-calculator { background-position: -112px -112px; } +.ui-icon-zoomin { background-position: -128px -112px; } +.ui-icon-zoomout { background-position: -144px -112px; } +.ui-icon-search { background-position: -160px -112px; } +.ui-icon-wrench { background-position: -176px -112px; } +.ui-icon-gear { background-position: -192px -112px; } +.ui-icon-heart { background-position: -208px -112px; } +.ui-icon-star { background-position: -224px -112px; } +.ui-icon-link { background-position: -240px -112px; } +.ui-icon-cancel { background-position: 0 -128px; } +.ui-icon-plus { background-position: -16px -128px; } +.ui-icon-plusthick { background-position: -32px -128px; } +.ui-icon-minus { background-position: -48px -128px; } +.ui-icon-minusthick { background-position: -64px -128px; } +.ui-icon-close { background-position: -80px -128px; } +.ui-icon-closethick { background-position: -96px -128px; } +.ui-icon-key { background-position: -112px -128px; } +.ui-icon-lightbulb { background-position: -128px -128px; } +.ui-icon-scissors { background-position: -144px -128px; } +.ui-icon-clipboard { background-position: -160px -128px; } +.ui-icon-copy { background-position: -176px -128px; } +.ui-icon-contact { background-position: -192px -128px; } +.ui-icon-image { background-position: -208px -128px; } +.ui-icon-video { background-position: -224px -128px; } +.ui-icon-script { background-position: -240px -128px; } +.ui-icon-alert { background-position: 0 -144px; } +.ui-icon-info { background-position: -16px -144px; } +.ui-icon-notice { background-position: -32px -144px; } +.ui-icon-help { background-position: -48px -144px; } +.ui-icon-check { background-position: -64px -144px; } +.ui-icon-bullet { background-position: -80px -144px; } +.ui-icon-radio-off { background-position: -96px -144px; } +.ui-icon-radio-on { background-position: -112px -144px; } +.ui-icon-pin-w { background-position: -128px -144px; } +.ui-icon-pin-s { background-position: -144px -144px; } +.ui-icon-play { background-position: 0 -160px; } +.ui-icon-pause { background-position: -16px -160px; } +.ui-icon-seek-next { background-position: -32px -160px; } +.ui-icon-seek-prev { background-position: -48px -160px; } +.ui-icon-seek-end { background-position: -64px -160px; } +.ui-icon-seek-start { background-position: -80px -160px; } +/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */ +.ui-icon-seek-first { background-position: -80px -160px; } +.ui-icon-stop { background-position: -96px -160px; } +.ui-icon-eject { background-position: -112px -160px; } +.ui-icon-volume-off { background-position: -128px -160px; } +.ui-icon-volume-on { background-position: -144px -160px; } +.ui-icon-power { background-position: 0 -176px; } +.ui-icon-signal-diag { background-position: -16px -176px; } +.ui-icon-signal { background-position: -32px -176px; } +.ui-icon-battery-0 { background-position: -48px -176px; } +.ui-icon-battery-1 { background-position: -64px -176px; } +.ui-icon-battery-2 { background-position: -80px -176px; } +.ui-icon-battery-3 { background-position: -96px -176px; } +.ui-icon-circle-plus { background-position: 0 -192px; } +.ui-icon-circle-minus { background-position: -16px -192px; } +.ui-icon-circle-close { background-position: -32px -192px; } +.ui-icon-circle-triangle-e { background-position: -48px -192px; } +.ui-icon-circle-triangle-s { background-position: -64px -192px; } +.ui-icon-circle-triangle-w { background-position: -80px -192px; } +.ui-icon-circle-triangle-n { background-position: -96px -192px; } +.ui-icon-circle-arrow-e { background-position: -112px -192px; } +.ui-icon-circle-arrow-s { background-position: -128px -192px; } +.ui-icon-circle-arrow-w { background-position: -144px -192px; } +.ui-icon-circle-arrow-n { background-position: -160px -192px; } +.ui-icon-circle-zoomin { background-position: -176px -192px; } +.ui-icon-circle-zoomout { background-position: -192px -192px; } +.ui-icon-circle-check { background-position: -208px -192px; } +.ui-icon-circlesmall-plus { background-position: 0 -208px; } +.ui-icon-circlesmall-minus { background-position: -16px -208px; } +.ui-icon-circlesmall-close { background-position: -32px -208px; } +.ui-icon-squaresmall-plus { background-position: -48px -208px; } +.ui-icon-squaresmall-minus { background-position: -64px -208px; } +.ui-icon-squaresmall-close { background-position: -80px -208px; } +.ui-icon-grip-dotted-vertical { background-position: 0 -224px; } +.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; } +.ui-icon-grip-solid-vertical { background-position: -32px -224px; } +.ui-icon-grip-solid-horizontal { background-position: -48px -224px; } +.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; } +.ui-icon-grip-diagonal-se { background-position: -80px -224px; } + + +/* Misc visuals +----------------------------------*/ + +/* Corner radius */ +.ui-corner-tl { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; } +.ui-corner-tr { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-bl { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; } +.ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-top { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-bottom { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-right { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; } +.ui-corner-left { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; } +.ui-corner-all { -moz-border-radius: 4px/*{cornerRadius}*/; -webkit-border-radius: 4px/*{cornerRadius}*/; border-radius: 4px/*{cornerRadius}*/; } + +/* Overlays */ +.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; } +.ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRadiusShadow}*/; border-radius: 8px/*{cornerRadiusShadow}*/; } \ No newline at end of file diff --git a/src/Test/Test/Controllers/AccountController.cs b/src/Test/Test/Controllers/AccountController.cs new file mode 100644 index 000000000..3ec272166 --- /dev/null +++ b/src/Test/Test/Controllers/AccountController.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; +using System.Web.Security; +using Test.Models; + +namespace Test.Controllers +{ + public class AccountController : Controller + { + + // + // GET: /Account/LogOn + + public ActionResult LogOn() + { + return View(); + } + + // + // POST: /Account/LogOn + + [HttpPost] + public ActionResult LogOn(LogOnModel model, string returnUrl) + { + if (ModelState.IsValid) + { + if (Membership.ValidateUser(model.UserName, model.Password)) + { + FormsAuthentication.SetAuthCookie(model.UserName, model.RememberMe); + if (Url.IsLocalUrl(returnUrl) && returnUrl.Length > 1 && returnUrl.StartsWith("/") + && !returnUrl.StartsWith("//") && !returnUrl.StartsWith("/\\")) + { + return Redirect(returnUrl); + } + else + { + return RedirectToAction("Index", "Home"); + } + } + else + { + ModelState.AddModelError("", "The user name or password provided is incorrect."); + } + } + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/LogOff + + public ActionResult LogOff() + { + FormsAuthentication.SignOut(); + + return RedirectToAction("Index", "Home"); + } + + // + // GET: /Account/Register + + public ActionResult Register() + { + return View(); + } + + // + // POST: /Account/Register + + [HttpPost] + public ActionResult Register(RegisterModel model) + { + if (ModelState.IsValid) + { + // Attempt to register the user + MembershipCreateStatus createStatus; + Membership.CreateUser(model.UserName, model.Password, model.Email, null, null, true, null, out createStatus); + + if (createStatus == MembershipCreateStatus.Success) + { + FormsAuthentication.SetAuthCookie(model.UserName, false /* createPersistentCookie */); + return RedirectToAction("Index", "Home"); + } + else + { + ModelState.AddModelError("", ErrorCodeToString(createStatus)); + } + } + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/ChangePassword + + [Authorize] + public ActionResult ChangePassword() + { + return View(); + } + + // + // POST: /Account/ChangePassword + + [Authorize] + [HttpPost] + public ActionResult ChangePassword(ChangePasswordModel model) + { + if (ModelState.IsValid) + { + + // ChangePassword will throw an exception rather + // than return false in certain failure scenarios. + bool changePasswordSucceeded; + try + { + MembershipUser currentUser = Membership.GetUser(User.Identity.Name, true /* userIsOnline */); + changePasswordSucceeded = currentUser.ChangePassword(model.OldPassword, model.NewPassword); + } + catch (Exception) + { + changePasswordSucceeded = false; + } + + if (changePasswordSucceeded) + { + return RedirectToAction("ChangePasswordSuccess"); + } + else + { + ModelState.AddModelError("", "The current password is incorrect or the new password is invalid."); + } + } + + // If we got this far, something failed, redisplay form + return View(model); + } + + // + // GET: /Account/ChangePasswordSuccess + + public ActionResult ChangePasswordSuccess() + { + return View(); + } + + #region Status Codes + private static string ErrorCodeToString(MembershipCreateStatus createStatus) + { + // See http://go.microsoft.com/fwlink/?LinkID=177550 for + // a full list of status codes. + switch (createStatus) + { + case MembershipCreateStatus.DuplicateUserName: + return "User name already exists. Please enter a different user name."; + + case MembershipCreateStatus.DuplicateEmail: + return "A user name for that e-mail address already exists. Please enter a different e-mail address."; + + case MembershipCreateStatus.InvalidPassword: + return "The password provided is invalid. Please enter a valid password value."; + + case MembershipCreateStatus.InvalidEmail: + return "The e-mail address provided is invalid. Please check the value and try again."; + + case MembershipCreateStatus.InvalidAnswer: + return "The password retrieval answer provided is invalid. Please check the value and try again."; + + case MembershipCreateStatus.InvalidQuestion: + return "The password retrieval question provided is invalid. Please check the value and try again."; + + case MembershipCreateStatus.InvalidUserName: + return "The user name provided is invalid. Please check the value and try again."; + + case MembershipCreateStatus.ProviderError: + return "The authentication provider returned an error. Please verify your entry and try again. If the problem persists, please contact your system administrator."; + + case MembershipCreateStatus.UserRejected: + return "The user creation request has been canceled. Please verify your entry and try again. If the problem persists, please contact your system administrator."; + + default: + return "An unknown error occurred. Please verify your entry and try again. If the problem persists, please contact your system administrator."; + } + } + #endregion + } +} diff --git a/src/Test/Test/Controllers/HomeController.cs b/src/Test/Test/Controllers/HomeController.cs new file mode 100644 index 000000000..d41ab863e --- /dev/null +++ b/src/Test/Test/Controllers/HomeController.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; + +namespace Test.Controllers +{ + using System.IO; + using System.Threading.Tasks; + using System.Web.Hosting; + + public class HomeController : Controller + { + public ActionResult Index() + { + ViewBag.Message = "Welcome to ASP.NET MVC!"; + + return View(); + } + + public ActionResult About() + { + List images = new List(); + + const string Path = "/images/"; + string folder = HostingEnvironment.MapPath(Path); + if (folder != null) + { + DirectoryInfo directoryInfo = new DirectoryInfo(folder); + + if (directoryInfo.Exists) + { + // Get all the files in the cache ordered by LastAccessTime - oldest first. + List fileInfos = directoryInfo.EnumerateFiles("*", SearchOption.AllDirectories).OrderBy(x => x.LastAccessTime).ToList(); + + int counter = fileInfos.Count; + + Parallel.ForEach( + fileInfos, + fileInfo => images.Add(Path + fileInfo.Name)); + } + } + + return View(images); + } + } +} diff --git a/src/Test/Test/Global.asax b/src/Test/Test/Global.asax new file mode 100644 index 000000000..5798265d5 --- /dev/null +++ b/src/Test/Test/Global.asax @@ -0,0 +1 @@ +<%@ Application Codebehind="Global.asax.cs" Inherits="Test.MvcApplication" Language="C#" %> diff --git a/src/Test/Test/Global.asax.cs b/src/Test/Test/Global.asax.cs new file mode 100644 index 000000000..1d151b372 --- /dev/null +++ b/src/Test/Test/Global.asax.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Web; +using System.Web.Mvc; +using System.Web.Routing; + +namespace Test +{ + // Note: For instructions on enabling IIS6 or IIS7 classic mode, + // visit http://go.microsoft.com/?LinkId=9394801 + + public class MvcApplication : System.Web.HttpApplication + { + public static void RegisterGlobalFilters(GlobalFilterCollection filters) + { + filters.Add(new HandleErrorAttribute()); + } + + public static void RegisterRoutes(RouteCollection routes) + { + routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); + + routes.MapRoute( + "Default", // Route name + "{controller}/{action}/{id}", // URL with parameters + new { controller = "Home", action = "Index", id = UrlParameter.Optional } // Parameter defaults + ); + + } + + protected void Application_Start() + { + AreaRegistration.RegisterAllAreas(); + + RegisterGlobalFilters(GlobalFilters.Filters); + RegisterRoutes(RouteTable.Routes); + } + } +} \ No newline at end of file diff --git a/src/Test/Test/Images/1182076_e8c402e938_z.jpg b/src/Test/Test/Images/1182076_e8c402e938_z.jpg new file mode 100644 index 000000000..bd2c9b902 Binary files /dev/null and b/src/Test/Test/Images/1182076_e8c402e938_z.jpg differ diff --git a/src/Test/Test/Images/6287131503_9b1f82e4f0_b.jpg.REMOVED.git-id b/src/Test/Test/Images/6287131503_9b1f82e4f0_b.jpg.REMOVED.git-id new file mode 100644 index 000000000..0db1445a2 --- /dev/null +++ b/src/Test/Test/Images/6287131503_9b1f82e4f0_b.jpg.REMOVED.git-id @@ -0,0 +1 @@ +4d040d9aa3519b3d2303419d1f03eebebf88e956 \ No newline at end of file diff --git a/src/Test/Test/Images/Chrysanthemum.jpg.REMOVED.git-id b/src/Test/Test/Images/Chrysanthemum.jpg.REMOVED.git-id new file mode 100644 index 000000000..d067665c9 --- /dev/null +++ b/src/Test/Test/Images/Chrysanthemum.jpg.REMOVED.git-id @@ -0,0 +1 @@ +757c2a628dd03b1cbe4b3ef07c153897a702b57a \ No newline at end of file diff --git a/src/Test/Test/Images/Chrysanthemum.png.REMOVED.git-id b/src/Test/Test/Images/Chrysanthemum.png.REMOVED.git-id new file mode 100644 index 000000000..71c0303c0 --- /dev/null +++ b/src/Test/Test/Images/Chrysanthemum.png.REMOVED.git-id @@ -0,0 +1 @@ +acbb9eee53e909814665053e0713335921c7d205 \ No newline at end of file diff --git a/src/Test/Test/Images/Desert.jpg.REMOVED.git-id b/src/Test/Test/Images/Desert.jpg.REMOVED.git-id new file mode 100644 index 000000000..228aac3ab --- /dev/null +++ b/src/Test/Test/Images/Desert.jpg.REMOVED.git-id @@ -0,0 +1 @@ +0b88c91336ff8073f34d21ccd683a01f0e0995da \ No newline at end of file diff --git a/src/Test/Test/Images/Hydrangeas.jpg.REMOVED.git-id b/src/Test/Test/Images/Hydrangeas.jpg.REMOVED.git-id new file mode 100644 index 000000000..90b0b53a0 --- /dev/null +++ b/src/Test/Test/Images/Hydrangeas.jpg.REMOVED.git-id @@ -0,0 +1 @@ +a587c9656db0f4773fbada4807b9377df2216d77 \ No newline at end of file diff --git a/src/Test/Test/Images/Jellyfish.jpg.REMOVED.git-id b/src/Test/Test/Images/Jellyfish.jpg.REMOVED.git-id new file mode 100644 index 000000000..54d26d8dd --- /dev/null +++ b/src/Test/Test/Images/Jellyfish.jpg.REMOVED.git-id @@ -0,0 +1 @@ +fa4fd4110616804c956a8fe4c388700ec4408eef \ No newline at end of file diff --git a/src/Test/Test/Images/Koala.jpg.REMOVED.git-id b/src/Test/Test/Images/Koala.jpg.REMOVED.git-id new file mode 100644 index 000000000..e2ff22b20 --- /dev/null +++ b/src/Test/Test/Images/Koala.jpg.REMOVED.git-id @@ -0,0 +1 @@ +78704a099bad91c76ecb96417137464b0fa96b28 \ No newline at end of file diff --git a/src/Test/Test/Images/Lighthouse.jpg.REMOVED.git-id b/src/Test/Test/Images/Lighthouse.jpg.REMOVED.git-id new file mode 100644 index 000000000..66c1485a3 --- /dev/null +++ b/src/Test/Test/Images/Lighthouse.jpg.REMOVED.git-id @@ -0,0 +1 @@ +494be09b8b43cfce7550ba6d54ff3cf799e4c315 \ No newline at end of file diff --git a/src/Test/Test/Images/MSwanson - Wide Large - Rock 02.jpg.REMOVED.git-id b/src/Test/Test/Images/MSwanson - Wide Large - Rock 02.jpg.REMOVED.git-id new file mode 100644 index 000000000..41c6c25df --- /dev/null +++ b/src/Test/Test/Images/MSwanson - Wide Large - Rock 02.jpg.REMOVED.git-id @@ -0,0 +1 @@ +33b6912af301bf216ee81d82b2c3ce6c49e03021 \ No newline at end of file diff --git a/src/Test/Test/Images/Penguins.jpg.REMOVED.git-id b/src/Test/Test/Images/Penguins.jpg.REMOVED.git-id new file mode 100644 index 000000000..ad4371113 --- /dev/null +++ b/src/Test/Test/Images/Penguins.jpg.REMOVED.git-id @@ -0,0 +1 @@ +030ab8a685bebb796c24cc710edd9e69859164f6 \ No newline at end of file diff --git a/src/Test/Test/Images/Tulips.jpg.REMOVED.git-id b/src/Test/Test/Images/Tulips.jpg.REMOVED.git-id new file mode 100644 index 000000000..84b9aff85 --- /dev/null +++ b/src/Test/Test/Images/Tulips.jpg.REMOVED.git-id @@ -0,0 +1 @@ +54c51eb6a86f31a42433b8167470fb18dad32c7d \ No newline at end of file diff --git a/src/Test/Test/Images/fid11246.jpg.REMOVED.git-id b/src/Test/Test/Images/fid11246.jpg.REMOVED.git-id new file mode 100644 index 000000000..4098eb2bc --- /dev/null +++ b/src/Test/Test/Images/fid11246.jpg.REMOVED.git-id @@ -0,0 +1 @@ +30b51f2b174d67995deb595343e3cef4483d64e4 \ No newline at end of file diff --git a/src/Test/Test/Images/fid9141.jpg.REMOVED.git-id b/src/Test/Test/Images/fid9141.jpg.REMOVED.git-id new file mode 100644 index 000000000..f51c8806e --- /dev/null +++ b/src/Test/Test/Images/fid9141.jpg.REMOVED.git-id @@ -0,0 +1 @@ +f945a806925fa3a763bfd4ec421134c217c46494 \ No newline at end of file diff --git a/src/Test/Test/Images/war_horse_quad.jpg.REMOVED.git-id b/src/Test/Test/Images/war_horse_quad.jpg.REMOVED.git-id new file mode 100644 index 000000000..4118c18e4 --- /dev/null +++ b/src/Test/Test/Images/war_horse_quad.jpg.REMOVED.git-id @@ -0,0 +1 @@ +c274f9d59c7f206c1cdf0cf97fd0cc13fc443191 \ No newline at end of file diff --git a/src/Test/Test/Models/AccountModels.cs b/src/Test/Test/Models/AccountModels.cs new file mode 100644 index 000000000..d78c76472 --- /dev/null +++ b/src/Test/Test/Models/AccountModels.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Globalization; +using System.Web.Mvc; +using System.Web.Security; + +namespace Test.Models +{ + + public class ChangePasswordModel + { + [Required] + [DataType(DataType.Password)] + [Display(Name = "Current password")] + public string OldPassword { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "New password")] + public string NewPassword { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm new password")] + [Compare("NewPassword", ErrorMessage = "The new password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } + + public class LogOnModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + + [Required] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [Display(Name = "Remember me?")] + public bool RememberMe { get; set; } + } + + public class RegisterModel + { + [Required] + [Display(Name = "User name")] + public string UserName { get; set; } + + [Required] + [DataType(DataType.EmailAddress)] + [Display(Name = "Email address")] + public string Email { get; set; } + + [Required] + [StringLength(100, ErrorMessage = "The {0} must be at least {2} characters long.", MinimumLength = 6)] + [DataType(DataType.Password)] + [Display(Name = "Password")] + public string Password { get; set; } + + [DataType(DataType.Password)] + [Display(Name = "Confirm password")] + [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")] + public string ConfirmPassword { get; set; } + } +} diff --git a/src/Test/Test/Properties/AssemblyInfo.cs b/src/Test/Test/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..499251b9f --- /dev/null +++ b/src/Test/Test/Properties/AssemblyInfo.cs @@ -0,0 +1,35 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 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("Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Test")] +[assembly: AssemblyCopyright("Copyright © 2012")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("34f4c859-67cc-40d2-97ae-27e8c7157052")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/src/Test/Test/Scripts/MicrosoftAjax.debug.js.REMOVED.git-id b/src/Test/Test/Scripts/MicrosoftAjax.debug.js.REMOVED.git-id new file mode 100644 index 000000000..6d7cfd091 --- /dev/null +++ b/src/Test/Test/Scripts/MicrosoftAjax.debug.js.REMOVED.git-id @@ -0,0 +1 @@ +a5f7942ef2b6b06e3c1aac2110fe7e5a1d88bf51 \ No newline at end of file diff --git a/src/Test/Test/Scripts/MicrosoftAjax.js.REMOVED.git-id b/src/Test/Test/Scripts/MicrosoftAjax.js.REMOVED.git-id new file mode 100644 index 000000000..21e699e8d --- /dev/null +++ b/src/Test/Test/Scripts/MicrosoftAjax.js.REMOVED.git-id @@ -0,0 +1 @@ +52e6626a072ad18c35926f81b94995a0c5eeee31 \ No newline at end of file diff --git a/src/Test/Test/Scripts/MicrosoftMvcAjax.debug.js b/src/Test/Test/Scripts/MicrosoftMvcAjax.debug.js new file mode 100644 index 000000000..eb68ba7e7 --- /dev/null +++ b/src/Test/Test/Scripts/MicrosoftMvcAjax.debug.js @@ -0,0 +1,408 @@ +//!---------------------------------------------------------- +//! Copyright (C) Microsoft Corporation. All rights reserved. +//!---------------------------------------------------------- +//! MicrosoftMvcAjax.js + +Type.registerNamespace('Sys.Mvc'); + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.AjaxOptions + +Sys.Mvc.$create_AjaxOptions = function Sys_Mvc_AjaxOptions() { return {}; } + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.InsertionMode + +Sys.Mvc.InsertionMode = function() { + /// + /// + /// + /// + /// + /// +}; +Sys.Mvc.InsertionMode.prototype = { + replace: 0, + insertBefore: 1, + insertAfter: 2 +} +Sys.Mvc.InsertionMode.registerEnum('Sys.Mvc.InsertionMode', false); + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.AjaxContext + +Sys.Mvc.AjaxContext = function Sys_Mvc_AjaxContext(request, updateTarget, loadingElement, insertionMode) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + this._request = request; + this._updateTarget = updateTarget; + this._loadingElement = loadingElement; + this._insertionMode = insertionMode; +} +Sys.Mvc.AjaxContext.prototype = { + _insertionMode: 0, + _loadingElement: null, + _response: null, + _request: null, + _updateTarget: null, + + get_data: function Sys_Mvc_AjaxContext$get_data() { + /// + if (this._response) { + return this._response.get_responseData(); + } + else { + return null; + } + }, + + get_insertionMode: function Sys_Mvc_AjaxContext$get_insertionMode() { + /// + return this._insertionMode; + }, + + get_loadingElement: function Sys_Mvc_AjaxContext$get_loadingElement() { + /// + return this._loadingElement; + }, + + get_object: function Sys_Mvc_AjaxContext$get_object() { + /// + var executor = this.get_response(); + return (executor) ? executor.get_object() : null; + }, + + get_response: function Sys_Mvc_AjaxContext$get_response() { + /// + return this._response; + }, + set_response: function Sys_Mvc_AjaxContext$set_response(value) { + /// + this._response = value; + return value; + }, + + get_request: function Sys_Mvc_AjaxContext$get_request() { + /// + return this._request; + }, + + get_updateTarget: function Sys_Mvc_AjaxContext$get_updateTarget() { + /// + return this._updateTarget; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.AsyncHyperlink + +Sys.Mvc.AsyncHyperlink = function Sys_Mvc_AsyncHyperlink() { +} +Sys.Mvc.AsyncHyperlink.handleClick = function Sys_Mvc_AsyncHyperlink$handleClick(anchor, evt, ajaxOptions) { + /// + /// + /// + /// + /// + /// + evt.preventDefault(); + Sys.Mvc.MvcHelpers._asyncRequest(anchor.href, 'post', '', anchor, ajaxOptions); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.MvcHelpers + +Sys.Mvc.MvcHelpers = function Sys_Mvc_MvcHelpers() { +} +Sys.Mvc.MvcHelpers._serializeSubmitButton = function Sys_Mvc_MvcHelpers$_serializeSubmitButton(element, offsetX, offsetY) { + /// + /// + /// + /// + /// + /// + /// + if (element.disabled) { + return null; + } + var name = element.name; + if (name) { + var tagName = element.tagName.toUpperCase(); + var encodedName = encodeURIComponent(name); + var inputElement = element; + if (tagName === 'INPUT') { + var type = inputElement.type; + if (type === 'submit') { + return encodedName + '=' + encodeURIComponent(inputElement.value); + } + else if (type === 'image') { + return encodedName + '.x=' + offsetX + '&' + encodedName + '.y=' + offsetY; + } + } + else if ((tagName === 'BUTTON') && (name.length) && (inputElement.type === 'submit')) { + return encodedName + '=' + encodeURIComponent(inputElement.value); + } + } + return null; +} +Sys.Mvc.MvcHelpers._serializeForm = function Sys_Mvc_MvcHelpers$_serializeForm(form) { + /// + /// + /// + var formElements = form.elements; + var formBody = new Sys.StringBuilder(); + var count = formElements.length; + for (var i = 0; i < count; i++) { + var element = formElements[i]; + var name = element.name; + if (!name || !name.length) { + continue; + } + var tagName = element.tagName.toUpperCase(); + if (tagName === 'INPUT') { + var inputElement = element; + var type = inputElement.type; + if ((type === 'text') || (type === 'password') || (type === 'hidden') || (((type === 'checkbox') || (type === 'radio')) && element.checked)) { + formBody.append(encodeURIComponent(name)); + formBody.append('='); + formBody.append(encodeURIComponent(inputElement.value)); + formBody.append('&'); + } + } + else if (tagName === 'SELECT') { + var selectElement = element; + var optionCount = selectElement.options.length; + for (var j = 0; j < optionCount; j++) { + var optionElement = selectElement.options[j]; + if (optionElement.selected) { + formBody.append(encodeURIComponent(name)); + formBody.append('='); + formBody.append(encodeURIComponent(optionElement.value)); + formBody.append('&'); + } + } + } + else if (tagName === 'TEXTAREA') { + formBody.append(encodeURIComponent(name)); + formBody.append('='); + formBody.append(encodeURIComponent((element.value))); + formBody.append('&'); + } + } + var additionalInput = form._additionalInput; + if (additionalInput) { + formBody.append(additionalInput); + formBody.append('&'); + } + return formBody.toString(); +} +Sys.Mvc.MvcHelpers._asyncRequest = function Sys_Mvc_MvcHelpers$_asyncRequest(url, verb, body, triggerElement, ajaxOptions) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + if (ajaxOptions.confirm) { + if (!confirm(ajaxOptions.confirm)) { + return; + } + } + if (ajaxOptions.url) { + url = ajaxOptions.url; + } + if (ajaxOptions.httpMethod) { + verb = ajaxOptions.httpMethod; + } + if (body.length > 0 && !body.endsWith('&')) { + body += '&'; + } + body += 'X-Requested-With=XMLHttpRequest'; + var upperCaseVerb = verb.toUpperCase(); + var isGetOrPost = (upperCaseVerb === 'GET' || upperCaseVerb === 'POST'); + if (!isGetOrPost) { + body += '&'; + body += 'X-HTTP-Method-Override=' + upperCaseVerb; + } + var requestBody = ''; + if (upperCaseVerb === 'GET' || upperCaseVerb === 'DELETE') { + if (url.indexOf('?') > -1) { + if (!url.endsWith('&')) { + url += '&'; + } + url += body; + } + else { + url += '?'; + url += body; + } + } + else { + requestBody = body; + } + var request = new Sys.Net.WebRequest(); + request.set_url(url); + if (isGetOrPost) { + request.set_httpVerb(verb); + } + else { + request.set_httpVerb('POST'); + request.get_headers()['X-HTTP-Method-Override'] = upperCaseVerb; + } + request.set_body(requestBody); + if (verb.toUpperCase() === 'PUT') { + request.get_headers()['Content-Type'] = 'application/x-www-form-urlencoded;'; + } + request.get_headers()['X-Requested-With'] = 'XMLHttpRequest'; + var updateElement = null; + if (ajaxOptions.updateTargetId) { + updateElement = $get(ajaxOptions.updateTargetId); + } + var loadingElement = null; + if (ajaxOptions.loadingElementId) { + loadingElement = $get(ajaxOptions.loadingElementId); + } + var ajaxContext = new Sys.Mvc.AjaxContext(request, updateElement, loadingElement, ajaxOptions.insertionMode); + var continueRequest = true; + if (ajaxOptions.onBegin) { + continueRequest = ajaxOptions.onBegin(ajaxContext) !== false; + } + if (loadingElement) { + Sys.UI.DomElement.setVisible(ajaxContext.get_loadingElement(), true); + } + if (continueRequest) { + request.add_completed(Function.createDelegate(null, function(executor) { + Sys.Mvc.MvcHelpers._onComplete(request, ajaxOptions, ajaxContext); + })); + request.invoke(); + } +} +Sys.Mvc.MvcHelpers._onComplete = function Sys_Mvc_MvcHelpers$_onComplete(request, ajaxOptions, ajaxContext) { + /// + /// + /// + /// + /// + /// + ajaxContext.set_response(request.get_executor()); + if (ajaxOptions.onComplete && ajaxOptions.onComplete(ajaxContext) === false) { + return; + } + var statusCode = ajaxContext.get_response().get_statusCode(); + if ((statusCode >= 200 && statusCode < 300) || statusCode === 304 || statusCode === 1223) { + if (statusCode !== 204 && statusCode !== 304 && statusCode !== 1223) { + var contentType = ajaxContext.get_response().getResponseHeader('Content-Type'); + if ((contentType) && (contentType.indexOf('application/x-javascript') !== -1)) { + eval(ajaxContext.get_data()); + } + else { + Sys.Mvc.MvcHelpers.updateDomElement(ajaxContext.get_updateTarget(), ajaxContext.get_insertionMode(), ajaxContext.get_data()); + } + } + if (ajaxOptions.onSuccess) { + ajaxOptions.onSuccess(ajaxContext); + } + } + else { + if (ajaxOptions.onFailure) { + ajaxOptions.onFailure(ajaxContext); + } + } + if (ajaxContext.get_loadingElement()) { + Sys.UI.DomElement.setVisible(ajaxContext.get_loadingElement(), false); + } +} +Sys.Mvc.MvcHelpers.updateDomElement = function Sys_Mvc_MvcHelpers$updateDomElement(target, insertionMode, content) { + /// + /// + /// + /// + /// + /// + if (target) { + switch (insertionMode) { + case Sys.Mvc.InsertionMode.replace: + target.innerHTML = content; + break; + case Sys.Mvc.InsertionMode.insertBefore: + if (content && content.length > 0) { + target.innerHTML = content + target.innerHTML.trimStart(); + } + break; + case Sys.Mvc.InsertionMode.insertAfter: + if (content && content.length > 0) { + target.innerHTML = target.innerHTML.trimEnd() + content; + } + break; + } + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.AsyncForm + +Sys.Mvc.AsyncForm = function Sys_Mvc_AsyncForm() { +} +Sys.Mvc.AsyncForm.handleClick = function Sys_Mvc_AsyncForm$handleClick(form, evt) { + /// + /// + /// + /// + var additionalInput = Sys.Mvc.MvcHelpers._serializeSubmitButton(evt.target, evt.offsetX, evt.offsetY); + form._additionalInput = additionalInput; +} +Sys.Mvc.AsyncForm.handleSubmit = function Sys_Mvc_AsyncForm$handleSubmit(form, evt, ajaxOptions) { + /// + /// + /// + /// + /// + /// + evt.preventDefault(); + var validationCallbacks = form.validationCallbacks; + if (validationCallbacks) { + for (var i = 0; i < validationCallbacks.length; i++) { + var callback = validationCallbacks[i]; + if (!callback()) { + return; + } + } + } + var body = Sys.Mvc.MvcHelpers._serializeForm(form); + Sys.Mvc.MvcHelpers._asyncRequest(form.action, form.method || 'post', body, form, ajaxOptions); +} + + +Sys.Mvc.AjaxContext.registerClass('Sys.Mvc.AjaxContext'); +Sys.Mvc.AsyncHyperlink.registerClass('Sys.Mvc.AsyncHyperlink'); +Sys.Mvc.MvcHelpers.registerClass('Sys.Mvc.MvcHelpers'); +Sys.Mvc.AsyncForm.registerClass('Sys.Mvc.AsyncForm'); + +// ---- Do not remove this footer ---- +// Generated using Script# v0.5.0.0 (http://projects.nikhilk.net) +// ----------------------------------- diff --git a/src/Test/Test/Scripts/MicrosoftMvcAjax.js b/src/Test/Test/Scripts/MicrosoftMvcAjax.js new file mode 100644 index 000000000..c5a6165e7 --- /dev/null +++ b/src/Test/Test/Scripts/MicrosoftMvcAjax.js @@ -0,0 +1,25 @@ +//---------------------------------------------------------- +// Copyright (C) Microsoft Corporation. All rights reserved. +//---------------------------------------------------------- +// MicrosoftMvcAjax.js + +Type.registerNamespace('Sys.Mvc');Sys.Mvc.$create_AjaxOptions=function(){return {};} +Sys.Mvc.InsertionMode=function(){};Sys.Mvc.InsertionMode.prototype = {replace:0,insertBefore:1,insertAfter:2} +Sys.Mvc.InsertionMode.registerEnum('Sys.Mvc.InsertionMode',false);Sys.Mvc.AjaxContext=function(request,updateTarget,loadingElement,insertionMode){this.$3=request;this.$4=updateTarget;this.$1=loadingElement;this.$0=insertionMode;} +Sys.Mvc.AjaxContext.prototype={$0:0,$1:null,$2:null,$3:null,$4:null,get_data:function(){if(this.$2){return this.$2.get_responseData();}else{return null;}},get_insertionMode:function(){return this.$0;},get_loadingElement:function(){return this.$1;},get_object:function(){var $0=this.get_response();return ($0)?$0.get_object():null;},get_response:function(){return this.$2;},set_response:function(value){this.$2=value;return value;},get_request:function(){return this.$3;},get_updateTarget:function(){return this.$4;}} +Sys.Mvc.AsyncHyperlink=function(){} +Sys.Mvc.AsyncHyperlink.handleClick=function(anchor,evt,ajaxOptions){evt.preventDefault();Sys.Mvc.MvcHelpers.$2(anchor.href,'post','',anchor,ajaxOptions);} +Sys.Mvc.MvcHelpers=function(){} +Sys.Mvc.MvcHelpers.$0=function($p0,$p1,$p2){if($p0.disabled){return null;}var $0=$p0.name;if($0){var $1=$p0.tagName.toUpperCase();var $2=encodeURIComponent($0);var $3=$p0;if($1==='INPUT'){var $4=$3.type;if($4==='submit'){return $2+'='+encodeURIComponent($3.value);}else if($4==='image'){return $2+'.x='+$p1+'&'+$2+'.y='+$p2;}}else if(($1==='BUTTON')&&($0.length)&&($3.type==='submit')){return $2+'='+encodeURIComponent($3.value);}}return null;} +Sys.Mvc.MvcHelpers.$1=function($p0){var $0=$p0.elements;var $1=new Sys.StringBuilder();var $2=$0.length;for(var $4=0;$4<$2;$4++){var $5=$0[$4];var $6=$5.name;if(!$6||!$6.length){continue;}var $7=$5.tagName.toUpperCase();if($7==='INPUT'){var $8=$5;var $9=$8.type;if(($9==='text')||($9==='password')||($9==='hidden')||((($9==='checkbox')||($9==='radio'))&&$5.checked)){$1.append(encodeURIComponent($6));$1.append('=');$1.append(encodeURIComponent($8.value));$1.append('&');}}else if($7==='SELECT'){var $A=$5;var $B=$A.options.length;for(var $C=0;$C<$B;$C++){var $D=$A.options[$C];if($D.selected){$1.append(encodeURIComponent($6));$1.append('=');$1.append(encodeURIComponent($D.value));$1.append('&');}}}else if($7==='TEXTAREA'){$1.append(encodeURIComponent($6));$1.append('=');$1.append(encodeURIComponent(($5.value)));$1.append('&');}}var $3=$p0._additionalInput;if($3){$1.append($3);$1.append('&');}return $1.toString();} +Sys.Mvc.MvcHelpers.$2=function($p0,$p1,$p2,$p3,$p4){if($p4.confirm){if(!confirm($p4.confirm)){return;}}if($p4.url){$p0=$p4.url;}if($p4.httpMethod){$p1=$p4.httpMethod;}if($p2.length>0&&!$p2.endsWith('&')){$p2+='&';}$p2+='X-Requested-With=XMLHttpRequest';var $0=$p1.toUpperCase();var $1=($0==='GET'||$0==='POST');if(!$1){$p2+='&';$p2+='X-HTTP-Method-Override='+$0;}var $2='';if($0==='GET'||$0==='DELETE'){if($p0.indexOf('?')>-1){if(!$p0.endsWith('&')){$p0+='&';}$p0+=$p2;}else{$p0+='?';$p0+=$p2;}}else{$2=$p2;}var $3=new Sys.Net.WebRequest();$3.set_url($p0);if($1){$3.set_httpVerb($p1);}else{$3.set_httpVerb('POST');$3.get_headers()['X-HTTP-Method-Override']=$0;}$3.set_body($2);if($p1.toUpperCase()==='PUT'){$3.get_headers()['Content-Type']='application/x-www-form-urlencoded;';}$3.get_headers()['X-Requested-With']='XMLHttpRequest';var $4=null;if($p4.updateTargetId){$4=$get($p4.updateTargetId);}var $5=null;if($p4.loadingElementId){$5=$get($p4.loadingElementId);}var $6=new Sys.Mvc.AjaxContext($3,$4,$5,$p4.insertionMode);var $7=true;if($p4.onBegin){$7=$p4.onBegin($6)!==false;}if($5){Sys.UI.DomElement.setVisible($6.get_loadingElement(),true);}if($7){$3.add_completed(Function.createDelegate(null,function($p1_0){ +Sys.Mvc.MvcHelpers.$3($3,$p4,$6);}));$3.invoke();}} +Sys.Mvc.MvcHelpers.$3=function($p0,$p1,$p2){$p2.set_response($p0.get_executor());if($p1.onComplete&&$p1.onComplete($p2)===false){return;}var $0=$p2.get_response().get_statusCode();if(($0>=200&&$0<300)||$0===304||$0===1223){if($0!==204&&$0!==304&&$0!==1223){var $1=$p2.get_response().getResponseHeader('Content-Type');if(($1)&&($1.indexOf('application/x-javascript')!==-1)){eval($p2.get_data());}else{Sys.Mvc.MvcHelpers.updateDomElement($p2.get_updateTarget(),$p2.get_insertionMode(),$p2.get_data());}}if($p1.onSuccess){$p1.onSuccess($p2);}}else{if($p1.onFailure){$p1.onFailure($p2);}}if($p2.get_loadingElement()){Sys.UI.DomElement.setVisible($p2.get_loadingElement(),false);}} +Sys.Mvc.MvcHelpers.updateDomElement=function(target,insertionMode,content){if(target){switch(insertionMode){case 0:target.innerHTML=content;break;case 1:if(content&&content.length>0){target.innerHTML=content+target.innerHTML.trimStart();}break;case 2:if(content&&content.length>0){target.innerHTML=target.innerHTML.trimEnd()+content;}break;}}} +Sys.Mvc.AsyncForm=function(){} +Sys.Mvc.AsyncForm.handleClick=function(form,evt){var $0=Sys.Mvc.MvcHelpers.$0(evt.target,evt.offsetX,evt.offsetY);form._additionalInput = $0;} +Sys.Mvc.AsyncForm.handleSubmit=function(form,evt,ajaxOptions){evt.preventDefault();var $0=form.validationCallbacks;if($0){for(var $2=0;$2<$0.length;$2++){var $3=$0[$2];if(!$3()){return;}}}var $1=Sys.Mvc.MvcHelpers.$1(form);Sys.Mvc.MvcHelpers.$2(form.action,form.method||'post',$1,form,ajaxOptions);} +Sys.Mvc.AjaxContext.registerClass('Sys.Mvc.AjaxContext');Sys.Mvc.AsyncHyperlink.registerClass('Sys.Mvc.AsyncHyperlink');Sys.Mvc.MvcHelpers.registerClass('Sys.Mvc.MvcHelpers');Sys.Mvc.AsyncForm.registerClass('Sys.Mvc.AsyncForm'); +// ---- Do not remove this footer ---- +// Generated using Script# v0.5.0.0 (http://projects.nikhilk.net) +// ----------------------------------- diff --git a/src/Test/Test/Scripts/MicrosoftMvcValidation.debug.js b/src/Test/Test/Scripts/MicrosoftMvcValidation.debug.js new file mode 100644 index 000000000..346ba4818 --- /dev/null +++ b/src/Test/Test/Scripts/MicrosoftMvcValidation.debug.js @@ -0,0 +1,883 @@ +//!---------------------------------------------------------- +//! Copyright (C) Microsoft Corporation. All rights reserved. +//!---------------------------------------------------------- +//! MicrosoftMvcValidation.js + + +Type.registerNamespace('Sys.Mvc'); + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.Validation + +Sys.Mvc.$create_Validation = function Sys_Mvc_Validation() { return {}; } + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.JsonValidationField + +Sys.Mvc.$create_JsonValidationField = function Sys_Mvc_JsonValidationField() { return {}; } + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.JsonValidationOptions + +Sys.Mvc.$create_JsonValidationOptions = function Sys_Mvc_JsonValidationOptions() { return {}; } + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.JsonValidationRule + +Sys.Mvc.$create_JsonValidationRule = function Sys_Mvc_JsonValidationRule() { return {}; } + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.ValidationContext + +Sys.Mvc.$create_ValidationContext = function Sys_Mvc_ValidationContext() { return {}; } + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.NumberValidator + +Sys.Mvc.NumberValidator = function Sys_Mvc_NumberValidator() { +} +Sys.Mvc.NumberValidator.create = function Sys_Mvc_NumberValidator$create(rule) { + /// + /// + /// + return Function.createDelegate(new Sys.Mvc.NumberValidator(), new Sys.Mvc.NumberValidator().validate); +} +Sys.Mvc.NumberValidator.prototype = { + + validate: function Sys_Mvc_NumberValidator$validate(value, context) { + /// + /// + /// + /// + /// + if (Sys.Mvc._validationUtil.stringIsNullOrEmpty(value)) { + return true; + } + var n = Number.parseLocale(value); + return (!isNaN(n)); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.FormContext + +Sys.Mvc.FormContext = function Sys_Mvc_FormContext(formElement, validationSummaryElement) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + this._errors = []; + this.fields = new Array(0); + this._formElement = formElement; + this._validationSummaryElement = validationSummaryElement; + formElement[Sys.Mvc.FormContext._formValidationTag] = this; + if (validationSummaryElement) { + var ulElements = validationSummaryElement.getElementsByTagName('ul'); + if (ulElements.length > 0) { + this._validationSummaryULElement = ulElements[0]; + } + } + this._onClickHandler = Function.createDelegate(this, this._form_OnClick); + this._onSubmitHandler = Function.createDelegate(this, this._form_OnSubmit); +} +Sys.Mvc.FormContext._Application_Load = function Sys_Mvc_FormContext$_Application_Load() { + var allFormOptions = window.mvcClientValidationMetadata; + if (allFormOptions) { + while (allFormOptions.length > 0) { + var thisFormOptions = allFormOptions.pop(); + Sys.Mvc.FormContext._parseJsonOptions(thisFormOptions); + } + } +} +Sys.Mvc.FormContext._getFormElementsWithName = function Sys_Mvc_FormContext$_getFormElementsWithName(formElement, name) { + /// + /// + /// + /// + /// + var allElementsWithNameInForm = []; + var allElementsWithName = document.getElementsByName(name); + for (var i = 0; i < allElementsWithName.length; i++) { + var thisElement = allElementsWithName[i]; + if (Sys.Mvc.FormContext._isElementInHierarchy(formElement, thisElement)) { + Array.add(allElementsWithNameInForm, thisElement); + } + } + return allElementsWithNameInForm; +} +Sys.Mvc.FormContext.getValidationForForm = function Sys_Mvc_FormContext$getValidationForForm(formElement) { + /// + /// + /// + return formElement[Sys.Mvc.FormContext._formValidationTag]; +} +Sys.Mvc.FormContext._isElementInHierarchy = function Sys_Mvc_FormContext$_isElementInHierarchy(parent, child) { + /// + /// + /// + /// + /// + while (child) { + if (parent === child) { + return true; + } + child = child.parentNode; + } + return false; +} +Sys.Mvc.FormContext._parseJsonOptions = function Sys_Mvc_FormContext$_parseJsonOptions(options) { + /// + /// + /// + var formElement = $get(options.FormId); + var validationSummaryElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(options.ValidationSummaryId)) ? $get(options.ValidationSummaryId) : null; + var formContext = new Sys.Mvc.FormContext(formElement, validationSummaryElement); + formContext.enableDynamicValidation(); + formContext.replaceValidationSummary = options.ReplaceValidationSummary; + for (var i = 0; i < options.Fields.length; i++) { + var field = options.Fields[i]; + var fieldElements = Sys.Mvc.FormContext._getFormElementsWithName(formElement, field.FieldName); + var validationMessageElement = (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(field.ValidationMessageId)) ? $get(field.ValidationMessageId) : null; + var fieldContext = new Sys.Mvc.FieldContext(formContext); + Array.addRange(fieldContext.elements, fieldElements); + fieldContext.validationMessageElement = validationMessageElement; + fieldContext.replaceValidationMessageContents = field.ReplaceValidationMessageContents; + for (var j = 0; j < field.ValidationRules.length; j++) { + var rule = field.ValidationRules[j]; + var validator = Sys.Mvc.ValidatorRegistry.getValidator(rule); + if (validator) { + var validation = Sys.Mvc.$create_Validation(); + validation.fieldErrorMessage = rule.ErrorMessage; + validation.validator = validator; + Array.add(fieldContext.validations, validation); + } + } + fieldContext.enableDynamicValidation(); + Array.add(formContext.fields, fieldContext); + } + var registeredValidatorCallbacks = formElement.validationCallbacks; + if (!registeredValidatorCallbacks) { + registeredValidatorCallbacks = []; + formElement.validationCallbacks = registeredValidatorCallbacks; + } + registeredValidatorCallbacks.push(Function.createDelegate(null, function() { + return Sys.Mvc._validationUtil.arrayIsNullOrEmpty(formContext.validate('submit')); + })); + return formContext; +} +Sys.Mvc.FormContext.prototype = { + _onClickHandler: null, + _onSubmitHandler: null, + _submitButtonClicked: null, + _validationSummaryElement: null, + _validationSummaryULElement: null, + _formElement: null, + replaceValidationSummary: false, + + addError: function Sys_Mvc_FormContext$addError(message) { + /// + /// + this.addErrors([ message ]); + }, + + addErrors: function Sys_Mvc_FormContext$addErrors(messages) { + /// + /// + if (!Sys.Mvc._validationUtil.arrayIsNullOrEmpty(messages)) { + Array.addRange(this._errors, messages); + this._onErrorCountChanged(); + } + }, + + clearErrors: function Sys_Mvc_FormContext$clearErrors() { + Array.clear(this._errors); + this._onErrorCountChanged(); + }, + + _displayError: function Sys_Mvc_FormContext$_displayError() { + if (this._validationSummaryElement) { + if (this._validationSummaryULElement) { + Sys.Mvc._validationUtil.removeAllChildren(this._validationSummaryULElement); + for (var i = 0; i < this._errors.length; i++) { + var liElement = document.createElement('li'); + Sys.Mvc._validationUtil.setInnerText(liElement, this._errors[i]); + this._validationSummaryULElement.appendChild(liElement); + } + } + Sys.UI.DomElement.removeCssClass(this._validationSummaryElement, Sys.Mvc.FormContext._validationSummaryValidCss); + Sys.UI.DomElement.addCssClass(this._validationSummaryElement, Sys.Mvc.FormContext._validationSummaryErrorCss); + } + }, + + _displaySuccess: function Sys_Mvc_FormContext$_displaySuccess() { + var validationSummaryElement = this._validationSummaryElement; + if (validationSummaryElement) { + var validationSummaryULElement = this._validationSummaryULElement; + if (validationSummaryULElement) { + validationSummaryULElement.innerHTML = ''; + } + Sys.UI.DomElement.removeCssClass(validationSummaryElement, Sys.Mvc.FormContext._validationSummaryErrorCss); + Sys.UI.DomElement.addCssClass(validationSummaryElement, Sys.Mvc.FormContext._validationSummaryValidCss); + } + }, + + enableDynamicValidation: function Sys_Mvc_FormContext$enableDynamicValidation() { + Sys.UI.DomEvent.addHandler(this._formElement, 'click', this._onClickHandler); + Sys.UI.DomEvent.addHandler(this._formElement, 'submit', this._onSubmitHandler); + }, + + _findSubmitButton: function Sys_Mvc_FormContext$_findSubmitButton(element) { + /// + /// + /// + if (element.disabled) { + return null; + } + var tagName = element.tagName.toUpperCase(); + var inputElement = element; + if (tagName === 'INPUT') { + var type = inputElement.type; + if (type === 'submit' || type === 'image') { + return inputElement; + } + } + else if ((tagName === 'BUTTON') && (inputElement.type === 'submit')) { + return inputElement; + } + return null; + }, + + _form_OnClick: function Sys_Mvc_FormContext$_form_OnClick(e) { + /// + /// + this._submitButtonClicked = this._findSubmitButton(e.target); + }, + + _form_OnSubmit: function Sys_Mvc_FormContext$_form_OnSubmit(e) { + /// + /// + var form = e.target; + var submitButton = this._submitButtonClicked; + if (submitButton && submitButton.disableValidation) { + return; + } + var errorMessages = this.validate('submit'); + if (!Sys.Mvc._validationUtil.arrayIsNullOrEmpty(errorMessages)) { + e.preventDefault(); + } + }, + + _onErrorCountChanged: function Sys_Mvc_FormContext$_onErrorCountChanged() { + if (!this._errors.length) { + this._displaySuccess(); + } + else { + this._displayError(); + } + }, + + validate: function Sys_Mvc_FormContext$validate(eventName) { + /// + /// + /// + var fields = this.fields; + var errors = []; + for (var i = 0; i < fields.length; i++) { + var field = fields[i]; + if (!field.elements[0].disabled) { + var thisErrors = field.validate(eventName); + if (thisErrors) { + Array.addRange(errors, thisErrors); + } + } + } + if (this.replaceValidationSummary) { + this.clearErrors(); + this.addErrors(errors); + } + return errors; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.FieldContext + +Sys.Mvc.FieldContext = function Sys_Mvc_FieldContext(formContext) { + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + this._errors = []; + this.elements = new Array(0); + this.validations = new Array(0); + this.formContext = formContext; + this._onBlurHandler = Function.createDelegate(this, this._element_OnBlur); + this._onChangeHandler = Function.createDelegate(this, this._element_OnChange); + this._onInputHandler = Function.createDelegate(this, this._element_OnInput); + this._onPropertyChangeHandler = Function.createDelegate(this, this._element_OnPropertyChange); +} +Sys.Mvc.FieldContext.prototype = { + _onBlurHandler: null, + _onChangeHandler: null, + _onInputHandler: null, + _onPropertyChangeHandler: null, + defaultErrorMessage: null, + formContext: null, + replaceValidationMessageContents: false, + validationMessageElement: null, + + addError: function Sys_Mvc_FieldContext$addError(message) { + /// + /// + this.addErrors([ message ]); + }, + + addErrors: function Sys_Mvc_FieldContext$addErrors(messages) { + /// + /// + if (!Sys.Mvc._validationUtil.arrayIsNullOrEmpty(messages)) { + Array.addRange(this._errors, messages); + this._onErrorCountChanged(); + } + }, + + clearErrors: function Sys_Mvc_FieldContext$clearErrors() { + Array.clear(this._errors); + this._onErrorCountChanged(); + }, + + _displayError: function Sys_Mvc_FieldContext$_displayError() { + var validationMessageElement = this.validationMessageElement; + if (validationMessageElement) { + if (this.replaceValidationMessageContents) { + Sys.Mvc._validationUtil.setInnerText(validationMessageElement, this._errors[0]); + } + Sys.UI.DomElement.removeCssClass(validationMessageElement, Sys.Mvc.FieldContext._validationMessageValidCss); + Sys.UI.DomElement.addCssClass(validationMessageElement, Sys.Mvc.FieldContext._validationMessageErrorCss); + } + var elements = this.elements; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + Sys.UI.DomElement.removeCssClass(element, Sys.Mvc.FieldContext._inputElementValidCss); + Sys.UI.DomElement.addCssClass(element, Sys.Mvc.FieldContext._inputElementErrorCss); + } + }, + + _displaySuccess: function Sys_Mvc_FieldContext$_displaySuccess() { + var validationMessageElement = this.validationMessageElement; + if (validationMessageElement) { + if (this.replaceValidationMessageContents) { + Sys.Mvc._validationUtil.setInnerText(validationMessageElement, ''); + } + Sys.UI.DomElement.removeCssClass(validationMessageElement, Sys.Mvc.FieldContext._validationMessageErrorCss); + Sys.UI.DomElement.addCssClass(validationMessageElement, Sys.Mvc.FieldContext._validationMessageValidCss); + } + var elements = this.elements; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + Sys.UI.DomElement.removeCssClass(element, Sys.Mvc.FieldContext._inputElementErrorCss); + Sys.UI.DomElement.addCssClass(element, Sys.Mvc.FieldContext._inputElementValidCss); + } + }, + + _element_OnBlur: function Sys_Mvc_FieldContext$_element_OnBlur(e) { + /// + /// + if (e.target[Sys.Mvc.FieldContext._hasTextChangedTag] || e.target[Sys.Mvc.FieldContext._hasValidationFiredTag]) { + this.validate('blur'); + } + }, + + _element_OnChange: function Sys_Mvc_FieldContext$_element_OnChange(e) { + /// + /// + e.target[Sys.Mvc.FieldContext._hasTextChangedTag] = true; + }, + + _element_OnInput: function Sys_Mvc_FieldContext$_element_OnInput(e) { + /// + /// + e.target[Sys.Mvc.FieldContext._hasTextChangedTag] = true; + if (e.target[Sys.Mvc.FieldContext._hasValidationFiredTag]) { + this.validate('input'); + } + }, + + _element_OnPropertyChange: function Sys_Mvc_FieldContext$_element_OnPropertyChange(e) { + /// + /// + if (e.rawEvent.propertyName === 'value') { + e.target[Sys.Mvc.FieldContext._hasTextChangedTag] = true; + if (e.target[Sys.Mvc.FieldContext._hasValidationFiredTag]) { + this.validate('input'); + } + } + }, + + enableDynamicValidation: function Sys_Mvc_FieldContext$enableDynamicValidation() { + var elements = this.elements; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (Sys.Mvc._validationUtil.elementSupportsEvent(element, 'onpropertychange')) { + var compatMode = document.documentMode; + if (compatMode && compatMode >= 8) { + Sys.UI.DomEvent.addHandler(element, 'propertychange', this._onPropertyChangeHandler); + } + } + else { + Sys.UI.DomEvent.addHandler(element, 'input', this._onInputHandler); + } + Sys.UI.DomEvent.addHandler(element, 'change', this._onChangeHandler); + Sys.UI.DomEvent.addHandler(element, 'blur', this._onBlurHandler); + } + }, + + _getErrorString: function Sys_Mvc_FieldContext$_getErrorString(validatorReturnValue, fieldErrorMessage) { + /// + /// + /// + /// + /// + var fallbackErrorMessage = fieldErrorMessage || this.defaultErrorMessage; + if (Boolean.isInstanceOfType(validatorReturnValue)) { + return (validatorReturnValue) ? null : fallbackErrorMessage; + } + if (String.isInstanceOfType(validatorReturnValue)) { + return ((validatorReturnValue).length) ? validatorReturnValue : fallbackErrorMessage; + } + return null; + }, + + _getStringValue: function Sys_Mvc_FieldContext$_getStringValue() { + /// + var elements = this.elements; + return (elements.length > 0) ? elements[0].value : null; + }, + + _markValidationFired: function Sys_Mvc_FieldContext$_markValidationFired() { + var elements = this.elements; + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + element[Sys.Mvc.FieldContext._hasValidationFiredTag] = true; + } + }, + + _onErrorCountChanged: function Sys_Mvc_FieldContext$_onErrorCountChanged() { + if (!this._errors.length) { + this._displaySuccess(); + } + else { + this._displayError(); + } + }, + + validate: function Sys_Mvc_FieldContext$validate(eventName) { + /// + /// + /// + var validations = this.validations; + var errors = []; + var value = this._getStringValue(); + for (var i = 0; i < validations.length; i++) { + var validation = validations[i]; + var context = Sys.Mvc.$create_ValidationContext(); + context.eventName = eventName; + context.fieldContext = this; + context.validation = validation; + var retVal = validation.validator(value, context); + var errorMessage = this._getErrorString(retVal, validation.fieldErrorMessage); + if (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(errorMessage)) { + Array.add(errors, errorMessage); + } + } + this._markValidationFired(); + this.clearErrors(); + this.addErrors(errors); + return errors; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.RangeValidator + +Sys.Mvc.RangeValidator = function Sys_Mvc_RangeValidator(minimum, maximum) { + /// + /// + /// + /// + /// + /// + /// + /// + this._minimum = minimum; + this._maximum = maximum; +} +Sys.Mvc.RangeValidator.create = function Sys_Mvc_RangeValidator$create(rule) { + /// + /// + /// + var min = rule.ValidationParameters['min']; + var max = rule.ValidationParameters['max']; + return Function.createDelegate(new Sys.Mvc.RangeValidator(min, max), new Sys.Mvc.RangeValidator(min, max).validate); +} +Sys.Mvc.RangeValidator.prototype = { + _minimum: null, + _maximum: null, + + validate: function Sys_Mvc_RangeValidator$validate(value, context) { + /// + /// + /// + /// + /// + if (Sys.Mvc._validationUtil.stringIsNullOrEmpty(value)) { + return true; + } + var n = Number.parseLocale(value); + return (!isNaN(n) && this._minimum <= n && n <= this._maximum); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.RegularExpressionValidator + +Sys.Mvc.RegularExpressionValidator = function Sys_Mvc_RegularExpressionValidator(pattern) { + /// + /// + /// + /// + this._pattern = pattern; +} +Sys.Mvc.RegularExpressionValidator.create = function Sys_Mvc_RegularExpressionValidator$create(rule) { + /// + /// + /// + var pattern = rule.ValidationParameters['pattern']; + return Function.createDelegate(new Sys.Mvc.RegularExpressionValidator(pattern), new Sys.Mvc.RegularExpressionValidator(pattern).validate); +} +Sys.Mvc.RegularExpressionValidator.prototype = { + _pattern: null, + + validate: function Sys_Mvc_RegularExpressionValidator$validate(value, context) { + /// + /// + /// + /// + /// + if (Sys.Mvc._validationUtil.stringIsNullOrEmpty(value)) { + return true; + } + var regExp = new RegExp(this._pattern); + var matches = regExp.exec(value); + return (!Sys.Mvc._validationUtil.arrayIsNullOrEmpty(matches) && matches[0].length === value.length); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.RequiredValidator + +Sys.Mvc.RequiredValidator = function Sys_Mvc_RequiredValidator() { +} +Sys.Mvc.RequiredValidator.create = function Sys_Mvc_RequiredValidator$create(rule) { + /// + /// + /// + return Function.createDelegate(new Sys.Mvc.RequiredValidator(), new Sys.Mvc.RequiredValidator().validate); +} +Sys.Mvc.RequiredValidator._isRadioInputElement = function Sys_Mvc_RequiredValidator$_isRadioInputElement(element) { + /// + /// + /// + if (element.tagName.toUpperCase() === 'INPUT') { + var inputType = (element.type).toUpperCase(); + if (inputType === 'RADIO') { + return true; + } + } + return false; +} +Sys.Mvc.RequiredValidator._isSelectInputElement = function Sys_Mvc_RequiredValidator$_isSelectInputElement(element) { + /// + /// + /// + if (element.tagName.toUpperCase() === 'SELECT') { + return true; + } + return false; +} +Sys.Mvc.RequiredValidator._isTextualInputElement = function Sys_Mvc_RequiredValidator$_isTextualInputElement(element) { + /// + /// + /// + if (element.tagName.toUpperCase() === 'INPUT') { + var inputType = (element.type).toUpperCase(); + switch (inputType) { + case 'TEXT': + case 'PASSWORD': + case 'FILE': + return true; + } + } + if (element.tagName.toUpperCase() === 'TEXTAREA') { + return true; + } + return false; +} +Sys.Mvc.RequiredValidator._validateRadioInput = function Sys_Mvc_RequiredValidator$_validateRadioInput(elements) { + /// + /// + /// + for (var i = 0; i < elements.length; i++) { + var element = elements[i]; + if (element.checked) { + return true; + } + } + return false; +} +Sys.Mvc.RequiredValidator._validateSelectInput = function Sys_Mvc_RequiredValidator$_validateSelectInput(optionElements) { + /// + /// + /// + for (var i = 0; i < optionElements.length; i++) { + var element = optionElements[i]; + if (element.selected) { + if (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(element.value)) { + return true; + } + } + } + return false; +} +Sys.Mvc.RequiredValidator._validateTextualInput = function Sys_Mvc_RequiredValidator$_validateTextualInput(element) { + /// + /// + /// + return (!Sys.Mvc._validationUtil.stringIsNullOrEmpty(element.value)); +} +Sys.Mvc.RequiredValidator.prototype = { + + validate: function Sys_Mvc_RequiredValidator$validate(value, context) { + /// + /// + /// + /// + /// + var elements = context.fieldContext.elements; + if (!elements.length) { + return true; + } + var sampleElement = elements[0]; + if (Sys.Mvc.RequiredValidator._isTextualInputElement(sampleElement)) { + return Sys.Mvc.RequiredValidator._validateTextualInput(sampleElement); + } + if (Sys.Mvc.RequiredValidator._isRadioInputElement(sampleElement)) { + return Sys.Mvc.RequiredValidator._validateRadioInput(elements); + } + if (Sys.Mvc.RequiredValidator._isSelectInputElement(sampleElement)) { + return Sys.Mvc.RequiredValidator._validateSelectInput((sampleElement).options); + } + return true; + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.StringLengthValidator + +Sys.Mvc.StringLengthValidator = function Sys_Mvc_StringLengthValidator(minLength, maxLength) { + /// + /// + /// + /// + /// + /// + /// + /// + this._minLength = minLength; + this._maxLength = maxLength; +} +Sys.Mvc.StringLengthValidator.create = function Sys_Mvc_StringLengthValidator$create(rule) { + /// + /// + /// + var minLength = (rule.ValidationParameters['min'] || 0); + var maxLength = (rule.ValidationParameters['max'] || Number.MAX_VALUE); + return Function.createDelegate(new Sys.Mvc.StringLengthValidator(minLength, maxLength), new Sys.Mvc.StringLengthValidator(minLength, maxLength).validate); +} +Sys.Mvc.StringLengthValidator.prototype = { + _maxLength: 0, + _minLength: 0, + + validate: function Sys_Mvc_StringLengthValidator$validate(value, context) { + /// + /// + /// + /// + /// + if (Sys.Mvc._validationUtil.stringIsNullOrEmpty(value)) { + return true; + } + return (this._minLength <= value.length && value.length <= this._maxLength); + } +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc._validationUtil + +Sys.Mvc._validationUtil = function Sys_Mvc__validationUtil() { +} +Sys.Mvc._validationUtil.arrayIsNullOrEmpty = function Sys_Mvc__validationUtil$arrayIsNullOrEmpty(array) { + /// + /// + /// + return (!array || !array.length); +} +Sys.Mvc._validationUtil.stringIsNullOrEmpty = function Sys_Mvc__validationUtil$stringIsNullOrEmpty(value) { + /// + /// + /// + return (!value || !value.length); +} +Sys.Mvc._validationUtil.elementSupportsEvent = function Sys_Mvc__validationUtil$elementSupportsEvent(element, eventAttributeName) { + /// + /// + /// + /// + /// + return (eventAttributeName in element); +} +Sys.Mvc._validationUtil.removeAllChildren = function Sys_Mvc__validationUtil$removeAllChildren(element) { + /// + /// + while (element.firstChild) { + element.removeChild(element.firstChild); + } +} +Sys.Mvc._validationUtil.setInnerText = function Sys_Mvc__validationUtil$setInnerText(element, innerText) { + /// + /// + /// + /// + var textNode = document.createTextNode(innerText); + Sys.Mvc._validationUtil.removeAllChildren(element); + element.appendChild(textNode); +} + + +//////////////////////////////////////////////////////////////////////////////// +// Sys.Mvc.ValidatorRegistry + +Sys.Mvc.ValidatorRegistry = function Sys_Mvc_ValidatorRegistry() { + /// + /// +} +Sys.Mvc.ValidatorRegistry.getValidator = function Sys_Mvc_ValidatorRegistry$getValidator(rule) { + /// + /// + /// + var creator = Sys.Mvc.ValidatorRegistry.validators[rule.ValidationType]; + return (creator) ? creator(rule) : null; +} +Sys.Mvc.ValidatorRegistry._getDefaultValidators = function Sys_Mvc_ValidatorRegistry$_getDefaultValidators() { + /// + return { required: Function.createDelegate(null, Sys.Mvc.RequiredValidator.create), length: Function.createDelegate(null, Sys.Mvc.StringLengthValidator.create), regex: Function.createDelegate(null, Sys.Mvc.RegularExpressionValidator.create), range: Function.createDelegate(null, Sys.Mvc.RangeValidator.create), number: Function.createDelegate(null, Sys.Mvc.NumberValidator.create) }; +} + + +Sys.Mvc.NumberValidator.registerClass('Sys.Mvc.NumberValidator'); +Sys.Mvc.FormContext.registerClass('Sys.Mvc.FormContext'); +Sys.Mvc.FieldContext.registerClass('Sys.Mvc.FieldContext'); +Sys.Mvc.RangeValidator.registerClass('Sys.Mvc.RangeValidator'); +Sys.Mvc.RegularExpressionValidator.registerClass('Sys.Mvc.RegularExpressionValidator'); +Sys.Mvc.RequiredValidator.registerClass('Sys.Mvc.RequiredValidator'); +Sys.Mvc.StringLengthValidator.registerClass('Sys.Mvc.StringLengthValidator'); +Sys.Mvc._validationUtil.registerClass('Sys.Mvc._validationUtil'); +Sys.Mvc.ValidatorRegistry.registerClass('Sys.Mvc.ValidatorRegistry'); +Sys.Mvc.FormContext._validationSummaryErrorCss = 'validation-summary-errors'; +Sys.Mvc.FormContext._validationSummaryValidCss = 'validation-summary-valid'; +Sys.Mvc.FormContext._formValidationTag = '__MVC_FormValidation'; +Sys.Mvc.FieldContext._hasTextChangedTag = '__MVC_HasTextChanged'; +Sys.Mvc.FieldContext._hasValidationFiredTag = '__MVC_HasValidationFired'; +Sys.Mvc.FieldContext._inputElementErrorCss = 'input-validation-error'; +Sys.Mvc.FieldContext._inputElementValidCss = 'input-validation-valid'; +Sys.Mvc.FieldContext._validationMessageErrorCss = 'field-validation-error'; +Sys.Mvc.FieldContext._validationMessageValidCss = 'field-validation-valid'; +Sys.Mvc.ValidatorRegistry.validators = Sys.Mvc.ValidatorRegistry._getDefaultValidators(); + +// ---- Do not remove this footer ---- +// Generated using Script# v0.5.0.0 (http://projects.nikhilk.net) +// ----------------------------------- + +// register validation +Sys.Application.add_load(function() { + Sys.Application.remove_load(arguments.callee); + Sys.Mvc.FormContext._Application_Load(); +}); diff --git a/src/Test/Test/Scripts/MicrosoftMvcValidation.js b/src/Test/Test/Scripts/MicrosoftMvcValidation.js new file mode 100644 index 000000000..9483492f1 --- /dev/null +++ b/src/Test/Test/Scripts/MicrosoftMvcValidation.js @@ -0,0 +1,55 @@ +//---------------------------------------------------------- +// Copyright (C) Microsoft Corporation. All rights reserved. +//---------------------------------------------------------- +// MicrosoftMvcValidation.js + +Type.registerNamespace('Sys.Mvc');Sys.Mvc.$create_Validation=function(){return {};} +Sys.Mvc.$create_JsonValidationField=function(){return {};} +Sys.Mvc.$create_JsonValidationOptions=function(){return {};} +Sys.Mvc.$create_JsonValidationRule=function(){return {};} +Sys.Mvc.$create_ValidationContext=function(){return {};} +Sys.Mvc.NumberValidator=function(){} +Sys.Mvc.NumberValidator.create=function(rule){return Function.createDelegate(new Sys.Mvc.NumberValidator(),new Sys.Mvc.NumberValidator().validate);} +Sys.Mvc.NumberValidator.prototype={validate:function(value,context){if(Sys.Mvc._ValidationUtil.$1(value)){return true;}var $0=Number.parseLocale(value);return (!isNaN($0));}} +Sys.Mvc.FormContext=function(formElement,validationSummaryElement){this.$5=[];this.fields=new Array(0);this.$9=formElement;this.$7=validationSummaryElement;formElement['__MVC_FormValidation'] = this;if(validationSummaryElement){var $0=validationSummaryElement.getElementsByTagName('ul');if($0.length>0){this.$8=$0[0];}}this.$3=Function.createDelegate(this,this.$D);this.$4=Function.createDelegate(this,this.$E);} +Sys.Mvc.FormContext._Application_Load=function(){var $0=window.mvcClientValidationMetadata;if($0){while($0.length>0){var $1=$0.pop();Sys.Mvc.FormContext.$12($1);}}} +Sys.Mvc.FormContext.$F=function($p0,$p1){var $0=[];var $1=document.getElementsByName($p1);for(var $2=0;$2<$1.length;$2++){var $3=$1[$2];if(Sys.Mvc.FormContext.$10($p0,$3)){Array.add($0,$3);}}return $0;} +Sys.Mvc.FormContext.getValidationForForm=function(formElement){return formElement['__MVC_FormValidation'];} +Sys.Mvc.FormContext.$10=function($p0,$p1){while($p1){if($p0===$p1){return true;}$p1=$p1.parentNode;}return false;} +Sys.Mvc.FormContext.$12=function($p0){var $0=$get($p0.FormId);var $1=(!Sys.Mvc._ValidationUtil.$1($p0.ValidationSummaryId))?$get($p0.ValidationSummaryId):null;var $2=new Sys.Mvc.FormContext($0,$1);$2.enableDynamicValidation();$2.replaceValidationSummary=$p0.ReplaceValidationSummary;for(var $4=0;$4<$p0.Fields.length;$4++){var $5=$p0.Fields[$4];var $6=Sys.Mvc.FormContext.$F($0,$5.FieldName);var $7=(!Sys.Mvc._ValidationUtil.$1($5.ValidationMessageId))?$get($5.ValidationMessageId):null;var $8=new Sys.Mvc.FieldContext($2);Array.addRange($8.elements,$6);$8.validationMessageElement=$7;$8.replaceValidationMessageContents=$5.ReplaceValidationMessageContents;for(var $9=0;$9<$5.ValidationRules.length;$9++){var $A=$5.ValidationRules[$9];var $B=Sys.Mvc.ValidatorRegistry.getValidator($A);if($B){var $C=Sys.Mvc.$create_Validation();$C.fieldErrorMessage=$A.ErrorMessage;$C.validator=$B;Array.add($8.validations,$C);}}$8.enableDynamicValidation();Array.add($2.fields,$8);}var $3=$0.validationCallbacks;if(!$3){$3=[];$0.validationCallbacks = $3;}$3.push(Function.createDelegate(null,function(){ +return Sys.Mvc._ValidationUtil.$0($2.validate('submit'));}));return $2;} +Sys.Mvc.FormContext.prototype={$3:null,$4:null,$6:null,$7:null,$8:null,$9:null,replaceValidationSummary:false,addError:function(message){this.addErrors([message]);},addErrors:function(messages){if(!Sys.Mvc._ValidationUtil.$0(messages)){Array.addRange(this.$5,messages);this.$11();}},clearErrors:function(){Array.clear(this.$5);this.$11();},$A:function(){if(this.$7){if(this.$8){Sys.Mvc._ValidationUtil.$3(this.$8);for(var $0=0;$0=8){Sys.UI.DomEvent.addHandler($2,'propertychange',this.$9);}}else{Sys.UI.DomEvent.addHandler($2,'input',this.$8);}Sys.UI.DomEvent.addHandler($2,'change',this.$7);Sys.UI.DomEvent.addHandler($2,'blur',this.$6);}},$11:function($p0,$p1){var $0=$p1||this.defaultErrorMessage;if(Boolean.isInstanceOfType($p0)){return ($p0)?null:$0;}if(String.isInstanceOfType($p0)){return (($p0).length)?$p0:$0;}return null;},$12:function(){var $0=this.elements;return ($0.length>0)?$0[0].value:null;},$13:function(){var $0=this.elements;for(var $1=0;$1<$0.length;$1++){var $2=$0[$1];$2['__MVC_HasValidationFired'] = true;}},$14:function(){if(!this.$A.length){this.$C();}else{this.$B();}},validate:function(eventName){var $0=this.validations;var $1=[];var $2=this.$12();for(var $3=0;$3<$0.length;$3++){var $4=$0[$3];var $5=Sys.Mvc.$create_ValidationContext();$5.eventName=eventName;$5.fieldContext=this;$5.validation=$4;var $6=$4.validator($2,$5);var $7=this.$11($6,$4.fieldErrorMessage);if(!Sys.Mvc._ValidationUtil.$1($7)){Array.add($1,$7);}}this.$13();this.clearErrors();this.addErrors($1);return $1;}} +Sys.Mvc.RangeValidator=function(minimum,maximum){this.$0=minimum;this.$1=maximum;} +Sys.Mvc.RangeValidator.create=function(rule){var $0=rule.ValidationParameters['min'];var $1=rule.ValidationParameters['max'];return Function.createDelegate(new Sys.Mvc.RangeValidator($0,$1),new Sys.Mvc.RangeValidator($0,$1).validate);} +Sys.Mvc.RangeValidator.prototype={$0:null,$1:null,validate:function(value,context){if(Sys.Mvc._ValidationUtil.$1(value)){return true;}var $0=Number.parseLocale(value);return (!isNaN($0)&&this.$0<=$0&&$0<=this.$1);}} +Sys.Mvc.RegularExpressionValidator=function(pattern){this.$0=pattern;} +Sys.Mvc.RegularExpressionValidator.create=function(rule){var $0=rule.ValidationParameters['pattern'];return Function.createDelegate(new Sys.Mvc.RegularExpressionValidator($0),new Sys.Mvc.RegularExpressionValidator($0).validate);} +Sys.Mvc.RegularExpressionValidator.prototype={$0:null,validate:function(value,context){if(Sys.Mvc._ValidationUtil.$1(value)){return true;}var $0=new RegExp(this.$0);var $1=$0.exec(value);return (!Sys.Mvc._ValidationUtil.$0($1)&&$1[0].length===value.length);}} +Sys.Mvc.RequiredValidator=function(){} +Sys.Mvc.RequiredValidator.create=function(rule){return Function.createDelegate(new Sys.Mvc.RequiredValidator(),new Sys.Mvc.RequiredValidator().validate);} +Sys.Mvc.RequiredValidator.$0=function($p0){if($p0.tagName.toUpperCase()==='INPUT'){var $0=($p0.type).toUpperCase();if($0==='RADIO'){return true;}}return false;} +Sys.Mvc.RequiredValidator.$1=function($p0){if($p0.tagName.toUpperCase()==='SELECT'){return true;}return false;} +Sys.Mvc.RequiredValidator.$2=function($p0){if($p0.tagName.toUpperCase()==='INPUT'){var $0=($p0.type).toUpperCase();switch($0){case 'TEXT':case 'PASSWORD':case 'FILE':return true;}}if($p0.tagName.toUpperCase()==='TEXTAREA'){return true;}return false;} +Sys.Mvc.RequiredValidator.$3=function($p0){for(var $0=0;$0<$p0.length;$0++){var $1=$p0[$0];if($1.checked){return true;}}return false;} +Sys.Mvc.RequiredValidator.$4=function($p0){for(var $0=0;$0<$p0.length;$0++){var $1=$p0[$0];if($1.selected){if(!Sys.Mvc._ValidationUtil.$1($1.value)){return true;}}}return false;} +Sys.Mvc.RequiredValidator.$5=function($p0){return (!Sys.Mvc._ValidationUtil.$1($p0.value));} +Sys.Mvc.RequiredValidator.prototype={validate:function(value,context){var $0=context.fieldContext.elements;if(!$0.length){return true;}var $1=$0[0];if(Sys.Mvc.RequiredValidator.$2($1)){return Sys.Mvc.RequiredValidator.$5($1);}if(Sys.Mvc.RequiredValidator.$0($1)){return Sys.Mvc.RequiredValidator.$3($0);}if(Sys.Mvc.RequiredValidator.$1($1)){return Sys.Mvc.RequiredValidator.$4(($1).options);}return true;}} +Sys.Mvc.StringLengthValidator=function(minLength,maxLength){this.$1=minLength;this.$0=maxLength;} +Sys.Mvc.StringLengthValidator.create=function(rule){var $0=(rule.ValidationParameters['min']||0);var $1=(rule.ValidationParameters['max']||Number.MAX_VALUE);return Function.createDelegate(new Sys.Mvc.StringLengthValidator($0,$1),new Sys.Mvc.StringLengthValidator($0,$1).validate);} +Sys.Mvc.StringLengthValidator.prototype={$0:0,$1:0,validate:function(value,context){if(Sys.Mvc._ValidationUtil.$1(value)){return true;}return (this.$1<=value.length&&value.length<=this.$0);}} +Sys.Mvc._ValidationUtil=function(){} +Sys.Mvc._ValidationUtil.$0=function($p0){return (!$p0||!$p0.length);} +Sys.Mvc._ValidationUtil.$1=function($p0){return (!$p0||!$p0.length);} +Sys.Mvc._ValidationUtil.$2=function($p0,$p1){return ($p1 in $p0);} +Sys.Mvc._ValidationUtil.$3=function($p0){while($p0.firstChild){$p0.removeChild($p0.firstChild);}} +Sys.Mvc._ValidationUtil.$4=function($p0,$p1){var $0=document.createTextNode($p1);Sys.Mvc._ValidationUtil.$3($p0);$p0.appendChild($0);} +Sys.Mvc.ValidatorRegistry=function(){} +Sys.Mvc.ValidatorRegistry.getValidator=function(rule){var $0=Sys.Mvc.ValidatorRegistry.validators[rule.ValidationType];return ($0)?$0(rule):null;} +Sys.Mvc.ValidatorRegistry.$0=function(){return {required:Function.createDelegate(null,Sys.Mvc.RequiredValidator.create),length:Function.createDelegate(null,Sys.Mvc.StringLengthValidator.create),regex:Function.createDelegate(null,Sys.Mvc.RegularExpressionValidator.create),range:Function.createDelegate(null,Sys.Mvc.RangeValidator.create),number:Function.createDelegate(null,Sys.Mvc.NumberValidator.create)};} +Sys.Mvc.NumberValidator.registerClass('Sys.Mvc.NumberValidator');Sys.Mvc.FormContext.registerClass('Sys.Mvc.FormContext');Sys.Mvc.FieldContext.registerClass('Sys.Mvc.FieldContext');Sys.Mvc.RangeValidator.registerClass('Sys.Mvc.RangeValidator');Sys.Mvc.RegularExpressionValidator.registerClass('Sys.Mvc.RegularExpressionValidator');Sys.Mvc.RequiredValidator.registerClass('Sys.Mvc.RequiredValidator');Sys.Mvc.StringLengthValidator.registerClass('Sys.Mvc.StringLengthValidator');Sys.Mvc._ValidationUtil.registerClass('Sys.Mvc._ValidationUtil');Sys.Mvc.ValidatorRegistry.registerClass('Sys.Mvc.ValidatorRegistry');Sys.Mvc.ValidatorRegistry.validators=Sys.Mvc.ValidatorRegistry.$0(); +// ---- Do not remove this footer ---- +// Generated using Script# v0.5.0.0 (http://projects.nikhilk.net) +// ----------------------------------- +Sys.Application.add_load(function(){Sys.Application.remove_load(arguments.callee);Sys.Mvc.FormContext._Application_Load();}); \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery-1.5.1-vsdoc.js.REMOVED.git-id b/src/Test/Test/Scripts/jquery-1.5.1-vsdoc.js.REMOVED.git-id new file mode 100644 index 000000000..afb367d66 --- /dev/null +++ b/src/Test/Test/Scripts/jquery-1.5.1-vsdoc.js.REMOVED.git-id @@ -0,0 +1 @@ +8f56f29ea15515370d52a560396067bb28b52005 \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery-1.5.1.js.REMOVED.git-id b/src/Test/Test/Scripts/jquery-1.5.1.js.REMOVED.git-id new file mode 100644 index 000000000..25bdfee81 --- /dev/null +++ b/src/Test/Test/Scripts/jquery-1.5.1.js.REMOVED.git-id @@ -0,0 +1 @@ +5948d8cc4932a48bc126343cf1d5ea2ac5f0b3c0 \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery-1.5.1.min.js.REMOVED.git-id b/src/Test/Test/Scripts/jquery-1.5.1.min.js.REMOVED.git-id new file mode 100644 index 000000000..1013447da --- /dev/null +++ b/src/Test/Test/Scripts/jquery-1.5.1.min.js.REMOVED.git-id @@ -0,0 +1 @@ +eec584bc589cf1ab1fe50781c1780f89c90aa31b \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery-ui-1.8.11.js.REMOVED.git-id b/src/Test/Test/Scripts/jquery-ui-1.8.11.js.REMOVED.git-id new file mode 100644 index 000000000..281a68518 --- /dev/null +++ b/src/Test/Test/Scripts/jquery-ui-1.8.11.js.REMOVED.git-id @@ -0,0 +1 @@ +79285779228262f9c021d1ee5a455c6f7f4c1113 \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery-ui-1.8.11.min.js.REMOVED.git-id b/src/Test/Test/Scripts/jquery-ui-1.8.11.min.js.REMOVED.git-id new file mode 100644 index 000000000..ec266dd80 --- /dev/null +++ b/src/Test/Test/Scripts/jquery-ui-1.8.11.min.js.REMOVED.git-id @@ -0,0 +1 @@ +89ff61bcba0fbfbe359c0d3734942d260b702a4b \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery.unobtrusive-ajax.js b/src/Test/Test/Scripts/jquery.unobtrusive-ajax.js new file mode 100644 index 000000000..95cdfd331 --- /dev/null +++ b/src/Test/Test/Scripts/jquery.unobtrusive-ajax.js @@ -0,0 +1,165 @@ +/// + +/*! +** Unobtrusive Ajax support library for jQuery +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ + +/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ +/*global window: false, jQuery: false */ + +(function ($) { + var data_click = "unobtrusiveAjaxClick", + data_validation = "unobtrusiveValidation"; + + function getFunction(code, argNames) { + var fn = window, parts = (code || "").split("."); + while (fn && parts.length) { + fn = fn[parts.shift()]; + } + if (typeof (fn) === "function") { + return fn; + } + argNames.push(code); + return Function.constructor.apply(null, argNames); + } + + function isMethodProxySafe(method) { + return method === "GET" || method === "POST"; + } + + function asyncOnBeforeSend(xhr, method) { + if (!isMethodProxySafe(method)) { + xhr.setRequestHeader("X-HTTP-Method-Override", method); + } + } + + function asyncOnSuccess(element, data, contentType) { + var mode; + + if (contentType.indexOf("application/x-javascript") !== -1) { // jQuery already executes JavaScript for us + return; + } + + mode = (element.getAttribute("data-ajax-mode") || "").toUpperCase(); + $(element.getAttribute("data-ajax-update")).each(function (i, update) { + var top; + + switch (mode) { + case "BEFORE": + top = update.firstChild; + $("
").html(data).contents().each(function () { + update.insertBefore(this, top); + }); + break; + case "AFTER": + $("
").html(data).contents().each(function () { + update.appendChild(this); + }); + break; + default: + $(update).html(data); + break; + } + }); + } + + function asyncRequest(element, options) { + var confirm, loading, method, duration; + + confirm = element.getAttribute("data-ajax-confirm"); + if (confirm && !window.confirm(confirm)) { + return; + } + + loading = $(element.getAttribute("data-ajax-loading")); + duration = element.getAttribute("data-ajax-loading-duration") || 0; + + $.extend(options, { + type: element.getAttribute("data-ajax-method") || undefined, + url: element.getAttribute("data-ajax-url") || undefined, + beforeSend: function (xhr) { + var result; + asyncOnBeforeSend(xhr, method); + result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(this, arguments); + if (result !== false) { + loading.show(duration); + } + return result; + }, + complete: function () { + loading.hide(duration); + getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(this, arguments); + }, + success: function (data, status, xhr) { + asyncOnSuccess(element, data, xhr.getResponseHeader("Content-Type") || "text/html"); + getFunction(element.getAttribute("data-ajax-success"), ["data", "status", "xhr"]).apply(this, arguments); + }, + error: getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]) + }); + + options.data.push({ name: "X-Requested-With", value: "XMLHttpRequest" }); + + method = options.type.toUpperCase(); + if (!isMethodProxySafe(method)) { + options.type = "POST"; + options.data.push({ name: "X-HTTP-Method-Override", value: method }); + } + + $.ajax(options); + } + + function validate(form) { + var validationInfo = $(form).data(data_validation); + return !validationInfo || !validationInfo.validate || validationInfo.validate(); + } + + $("a[data-ajax=true]").live("click", function (evt) { + evt.preventDefault(); + asyncRequest(this, { + url: this.href, + type: "GET", + data: [] + }); + }); + + $("form[data-ajax=true] input[type=image]").live("click", function (evt) { + var name = evt.target.name, + $target = $(evt.target), + form = $target.parents("form")[0], + offset = $target.offset(); + + $(form).data(data_click, [ + { name: name + ".x", value: Math.round(evt.pageX - offset.left) }, + { name: name + ".y", value: Math.round(evt.pageY - offset.top) } + ]); + + setTimeout(function () { + $(form).removeData(data_click); + }, 0); + }); + + $("form[data-ajax=true] :submit").live("click", function (evt) { + var name = evt.target.name, + form = $(evt.target).parents("form")[0]; + + $(form).data(data_click, name ? [{ name: name, value: evt.target.value }] : []); + + setTimeout(function () { + $(form).removeData(data_click); + }, 0); + }); + + $("form[data-ajax=true]").live("submit", function (evt) { + var clickInfo = $(this).data(data_click) || []; + evt.preventDefault(); + if (!validate(this)) { + return; + } + asyncRequest(this, { + url: this.action, + type: this.method || "GET", + data: clickInfo.concat($(this).serializeArray()) + }); + }); +}(jQuery)); \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery.unobtrusive-ajax.min.js b/src/Test/Test/Scripts/jquery.unobtrusive-ajax.min.js new file mode 100644 index 000000000..3542991c1 --- /dev/null +++ b/src/Test/Test/Scripts/jquery.unobtrusive-ajax.min.js @@ -0,0 +1,5 @@ +/* +** Unobtrusive Ajax support library for jQuery +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ +(function(a){var b="unobtrusiveAjaxClick",g="unobtrusiveValidation";function c(d,b){var a=window,c=(d||"").split(".");while(a&&c.length)a=a[c.shift()];if(typeof a==="function")return a;b.push(d);return Function.constructor.apply(null,b)}function d(a){return a==="GET"||a==="POST"}function f(b,a){!d(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function h(c,b,e){var d;if(e.indexOf("application/x-javascript")!==-1)return;d=(c.getAttribute("data-ajax-mode")||"").toUpperCase();a(c.getAttribute("data-ajax-update")).each(function(f,c){var e;switch(d){case"BEFORE":e=c.firstChild;a("
").html(b).contents().each(function(){c.insertBefore(this,e)});break;case"AFTER":a("
").html(b).contents().each(function(){c.appendChild(this)});break;default:a(c).html(b)}})}function e(b,e){var j,k,g,i;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));i=b.getAttribute("data-ajax-loading-duration")||0;a.extend(e,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,beforeSend:function(d){var a;f(d,g);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(this,arguments);a!==false&&k.show(i);return a},complete:function(){k.hide(i);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(this,arguments)},success:function(a,e,d){h(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(this,arguments)},error:c(b.getAttribute("data-ajax-failure"),["xhr","status","error"])});e.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});g=e.type.toUpperCase();if(!d(g)){e.type="POST";e.data.push({name:"X-HTTP-Method-Override",value:g})}a.ajax(e)}function i(c){var b=a(c).data(g);return!b||!b.validate||b.validate()}a("a[data-ajax=true]").live("click",function(a){a.preventDefault();e(this,{url:this.href,type:"GET",data:[]})});a("form[data-ajax=true] input[type=image]").live("click",function(c){var g=c.target.name,d=a(c.target),f=d.parents("form")[0],e=d.offset();a(f).data(b,[{name:g+".x",value:Math.round(c.pageX-e.left)},{name:g+".y",value:Math.round(c.pageY-e.top)}]);setTimeout(function(){a(f).removeData(b)},0)});a("form[data-ajax=true] :submit").live("click",function(c){var e=c.target.name,d=a(c.target).parents("form")[0];a(d).data(b,e?[{name:e,value:c.target.value}]:[]);setTimeout(function(){a(d).removeData(b)},0)});a("form[data-ajax=true]").live("submit",function(d){var c=a(this).data(b)||[];d.preventDefault();if(!i(this))return;e(this,{url:this.action,type:this.method||"GET",data:c.concat(a(this).serializeArray())})})})(jQuery); \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery.validate-vsdoc.js b/src/Test/Test/Scripts/jquery.validate-vsdoc.js new file mode 100644 index 000000000..30774ac11 --- /dev/null +++ b/src/Test/Test/Scripts/jquery.validate-vsdoc.js @@ -0,0 +1,1299 @@ +/* +* This file has been commented to support Visual Studio Intellisense. +* You should not use this file at runtime inside the browser--it is only +* intended to be used only for design-time IntelliSense. Please use the +* standard jQuery library for all production use. +* +* Comment version: 1.8 +*/ + +/* +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* jQuery validation plugin 1.8.0 +* +* http://bassistance.de/jquery-plugins/jquery-plugin-validation/ +* http://docs.jquery.com/Plugins/Validation +* +* Copyright (c) 2006 - 2011 Jörn Zaefferer +* +*/ + +(function($) { + +$.extend($.fn, { + // http://docs.jquery.com/Plugins/Validation/validate + validate: function( options ) { + /// + /// Validates the selected form. This method sets up event handlers for submit, focus, + /// keyup, blur and click to trigger validation of the entire form or individual + /// elements. Each one can be disabled, see the onxxx options (onsubmit, onfocusout, + /// onkeyup, onclick). focusInvalid focuses elements when submitting a invalid form. + /// + /// + /// A set of key/value pairs that configure the validate. All options are optional. + /// + /// + + // if nothing is selected, return nothing; can't chain anyway + if (!this.length) { + options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" ); + return; + } + + // check if a validator for this form was already created + var validator = $.data(this[0], 'validator'); + if ( validator ) { + return validator; + } + + validator = new $.validator( options, this[0] ); + $.data(this[0], 'validator', validator); + + if ( validator.settings.onsubmit ) { + + // allow suppresing validation by adding a cancel class to the submit button + this.find("input, button").filter(".cancel").click(function() { + validator.cancelSubmit = true; + }); + + // when a submitHandler is used, capture the submitting button + if (validator.settings.submitHandler) { + this.find("input, button").filter(":submit").click(function() { + validator.submitButton = this; + }); + } + + // validate the form on submit + this.submit( function( event ) { + if ( validator.settings.debug ) + // prevent form submit to be able to see console output + event.preventDefault(); + + function handle() { + if ( validator.settings.submitHandler ) { + if (validator.submitButton) { + // insert a hidden input as a replacement for the missing submit button + var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); + } + validator.settings.submitHandler.call( validator, validator.currentForm ); + if (validator.submitButton) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://docs.jquery.com/Plugins/Validation/valid + valid: function() { + /// + /// Checks if the selected form is valid or if all selected elements are valid. + /// validate() needs to be called on the form before checking it using this method. + /// + /// + + if ( $(this[0]).is('form')) { + return this.validate().form(); + } else { + var valid = true; + var validator = $(this[0].form).validate(); + this.each(function() { + valid &= validator.element(this); + }); + return valid; + } + }, + // attributes: space seperated list of attributes to retrieve and remove + removeAttrs: function(attributes) { + /// + /// Remove the specified attributes from the first matched element and return them. + /// + /// + /// A space-seperated list of attribute names to remove. + /// + /// + + var result = {}, + $element = this; + $.each(attributes.split(/\s/), function(index, value) { + result[value] = $element.attr(value); + $element.removeAttr(value); + }); + return result; + }, + // http://docs.jquery.com/Plugins/Validation/rules + rules: function(command, argument) { + /// + /// Return the validations rules for the first selected element. + /// + /// + /// Can be either "add" or "remove". + /// + /// + /// A list of rules to add or remove. + /// + /// + + var element = this[0]; + + if (command) { + var settings = $.data(element.form, 'validator').settings; + var staticRules = settings.rules; + var existingRules = $.validator.staticRules(element); + switch(command) { + case "add": + $.extend(existingRules, $.validator.normalizeRule(argument)); + staticRules[element.name] = existingRules; + if (argument.messages) + settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); + break; + case "remove": + if (!argument) { + delete staticRules[element.name]; + return existingRules; + } + var filtered = {}; + $.each(argument.split(/\s/), function(index, method) { + filtered[method] = existingRules[method]; + delete existingRules[method]; + }); + return filtered; + } + } + + var data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.metadataRules(element), + $.validator.classRules(element), + $.validator.attributeRules(element), + $.validator.staticRules(element) + ), element); + + // make sure required is at front + if (data.required) { + var param = data.required; + delete data.required; + data = $.extend({required: param}, data); + } + + return data; + } +}); + +// Custom selectors +$.extend($.expr[":"], { + // http://docs.jquery.com/Plugins/Validation/blank + blank: function(a) {return !$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/filled + filled: function(a) {return !!$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/unchecked + unchecked: function(a) {return !a.checked;} +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +$.validator.format = function(source, params) { + /// + /// Replaces {n} placeholders with arguments. + /// One or more arguments can be passed, in addition to the string template itself, to insert + /// into the string. + /// + /// + /// The string to format. + /// + /// + /// The first argument to insert, or an array of Strings to insert + /// + /// + + if ( arguments.length == 1 ) + return function() { + var args = $.makeArray(arguments); + args.unshift(source); + return $.validator.format.apply( this, args ); + }; + if ( arguments.length > 2 && params.constructor != Array ) { + params = $.makeArray(arguments).slice(1); + } + if ( params.constructor != Array ) { + params = [ params ]; + } + $.each(params, function(i, n) { + source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); + }); + return source; +}; + +$.extend($.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: [], + ignoreTitle: false, + onfocusin: function(element) { + this.lastActive = element; + + // hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { + this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + this.addWrapper(this.errorsFor(element)).hide(); + } + }, + onfocusout: function(element) { + if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { + this.element(element); + } + }, + onkeyup: function(element) { + if ( element.name in this.submitted || element == this.lastElement ) { + this.element(element); + } + }, + onclick: function(element) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) + this.element(element); + // or option elements, check parent select in that case + else if (element.parentNode.name in this.submitted) + this.element(element.parentNode); + }, + highlight: function( element, errorClass, validClass ) { + $(element).addClass(errorClass).removeClass(validClass); + }, + unhighlight: function( element, errorClass, validClass ) { + $(element).removeClass(errorClass).addClass(validClass); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults + setDefaults: function(settings) { + /// + /// Modify default settings for validation. + /// Accepts everything that Plugins/Validation/validate accepts. + /// + /// + /// Options to set as default. + /// + /// + + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + accept: "Please enter a value with a valid extension.", + maxlength: $.validator.format("Please enter no more than {0} characters."), + minlength: $.validator.format("Please enter at least {0} characters."), + rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), + range: $.validator.format("Please enter a value between {0} and {1}."), + max: $.validator.format("Please enter a value less than or equal to {0}."), + min: $.validator.format("Please enter a value greater than or equal to {0}.") + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $(this.settings.errorLabelContainer); + this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); + this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = (this.groups = {}); + $.each(this.settings.groups, function(key, value) { + $.each(value.split(/\s/), function(index, name) { + groups[name] = key; + }); + }); + var rules = this.settings.rules; + $.each(rules, function(key, value) { + rules[key] = $.validator.normalizeRule(value); + }); + + function delegate(event) { + var validator = $.data(this[0].form, "validator"), + eventType = "on" + event.type.replace(/^validate/, ""); + validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] ); + } + $(this.currentForm) + .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate) + .validateDelegate(":radio, :checkbox, select, option", "click", delegate); + + if (this.settings.invalidHandler) + $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/form + form: function() { + /// + /// Validates the form, returns true if it is valid, false otherwise. + /// This behaves as a normal submit event, but returns the result. + /// + /// + + this.checkForm(); + $.extend(this.submitted, this.errorMap); + this.invalid = $.extend({}, this.errorMap); + if (!this.valid()) + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { + this.check( elements[i] ); + } + return this.valid(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/element + element: function( element ) { + /// + /// Validates a single element, returns true if it is valid, false otherwise. + /// This behaves as validation on blur or keyup, but returns the result. + /// + /// + /// An element to validate, must be inside the validated form. + /// + /// + + element = this.clean( element ); + this.lastElement = element; + this.prepareElement( element ); + this.currentElements = $(element); + var result = this.check( element ); + if ( result ) { + delete this.invalid[element.name]; + } else { + this.invalid[element.name] = true; + } + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/showErrors + showErrors: function(errors) { + /// + /// Show the specified messages. + /// Keys have to refer to the names of elements, values are displayed for those elements, using the configured error placement. + /// + /// + /// One or more key/value pairs of input names and messages. + /// + /// + + if(errors) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[name], + element: this.findByName(name)[0] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function(element) { + return !(element.name in errors); + }); + } + this.settings.showErrors + ? this.settings.showErrors.call( this, this.errorMap, this.errorList ) + : this.defaultShowErrors(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/resetForm + resetForm: function() { + /// + /// Resets the controlled form. + /// Resets input fields to their original value (requires form plugin), removes classes + /// indicating invalid elements and hides error messages. + /// + /// + + if ( $.fn.resetForm ) + $( this.currentForm ).resetForm(); + this.submitted = {}; + this.prepareForm(); + this.hideErrors(); + this.elements().removeClass( this.settings.errorClass ); + }, + + numberOfInvalids: function() { + /// + /// Returns the number of invalid fields. + /// This depends on the internal validator state. It covers all fields only after + /// validating the complete form (on submit or via $("form").valid()). After validating + /// a single element, only that element is counted. Most useful in combination with the + /// invalidHandler-option. + /// + /// + + return this.objectLength(this.invalid); + }, + + objectLength: function( obj ) { + var count = 0; + for ( var i in obj ) + count++; + return count; + }, + + hideErrors: function() { + this.addWrapper( this.toHide ).hide(); + }, + + valid: function() { + return this.size() == 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if( this.settings.focusInvalid ) { + try { + $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) + .filter(":visible") + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger("focusin"); + } catch(e) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep(this.errorList, function(n) { + return n.element.name == lastActive.name; + }).length == 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved + return $([]).add(this.currentForm.elements) + .filter(":input") + .not(":submit, :reset, :image, [disabled]") + .not( this.settings.ignore ) + .filter(function() { + !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this); + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) + return false; + + rulesCache[this.name] = true; + return true; + }); + }, + + clean: function( selector ) { + return $( selector )[0]; + }, + + errors: function() { + return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext ); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $([]); + this.toHide = $([]); + this.currentElements = $([]); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor(element); + }, + + check: function( element ) { + element = this.clean( element ); + + // if radio/checkbox, validate first element in group instead + if (this.checkable(element)) { + element = this.findByName(element.name).not(this.settings.ignore)[0]; + } + + var rules = $(element).rules(); + var dependencyMismatch = false; + for (var method in rules) { + var rule = { method: method, parameters: rules[method] }; + try { + var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result == "dependency-mismatch" ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result == "pending" ) { + this.toHide = this.toHide.not( this.errorsFor(element) ); + return; + } + + if( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch(e) { + this.settings.debug && window.console && console.log("exception occured when checking element " + element.id + + ", check the '" + rule.method + "' method", e); + throw e; + } + } + if (dependencyMismatch) + return; + if ( this.objectLength(rules) ) + this.successList.push(element); + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's "messages" metadata + customMetaMessage: function(element, method) { + if (!$.metadata) + return; + + var meta = this.settings.meta + ? $(element).metadata()[this.settings.meta] + : $(element).metadata(); + + return meta && meta.messages && meta.messages[method]; + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[name]; + return m && (m.constructor == String + ? m + : m[method]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for(var i = 0; i < arguments.length; i++) { + if (arguments[i] !== undefined) + return arguments[i]; + } + return undefined; + }, + + defaultMessage: function( element, method) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customMetaMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[method], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message == "function" ) { + message = message.call(this, rule.parameters, element); + } else if (theregex.test(message)) { + message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters); + } + this.errorList.push({ + message: message, + element: element + }); + + this.errorMap[element.name] = message; + this.submitted[element.name] = message; + }, + + addWrapper: function(toToggle) { + if ( this.settings.wrapper ) + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + return toToggle; + }, + + defaultShowErrors: function() { + for ( var i = 0; this.errorList[i]; i++ ) { + var error = this.errorList[i]; + this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + this.showLabel( error.element, error.message ); + } + if( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if (this.settings.success) { + for ( var i = 0; this.successList[i]; i++ ) { + this.showLabel( this.successList[i] ); + } + } + if (this.settings.unhighlight) { + for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) { + this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not(this.invalidElements()); + }, + + invalidElements: function() { + return $(this.errorList).map(function() { + return this.element; + }); + }, + + showLabel: function(element, message) { + var label = this.errorsFor( element ); + if ( label.length ) { + // refresh error/success class + label.removeClass().addClass( this.settings.errorClass ); + + // check if we have a generated label, replace the message then + label.attr("generated") && label.html(message); + } else { + // create label + label = $("<" + this.settings.errorElement + "/>") + .attr({"for": this.idOrName(element), generated: true}) + .addClass(this.settings.errorClass) + .html(message || ""); + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); + } + if ( !this.labelContainer.append(label).length ) + this.settings.errorPlacement + ? this.settings.errorPlacement(label, $(element) ) + : label.insertAfter(element); + } + if ( !message && this.settings.success ) { + label.text(""); + typeof this.settings.success == "string" + ? label.addClass( this.settings.success ) + : this.settings.success( label ); + } + this.toShow = this.toShow.add(label); + }, + + errorsFor: function(element) { + var name = this.idOrName(element); + return this.errors().filter(function() { + return $(this).attr('for') == name; + }); + }, + + idOrName: function(element) { + return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); + }, + + checkable: function( element ) { + return /radio|checkbox/i.test(element.type); + }, + + findByName: function( name ) { + // select by name and filter by form for performance over form.find("[name=...]") + var form = this.currentForm; + return $(document.getElementsByName(name)).map(function(index, element) { + return element.form == form && element.name == name && element || null; + }); + }, + + getLength: function(value, element) { + switch( element.nodeName.toLowerCase() ) { + case 'select': + return $("option:selected", element).length; + case 'input': + if( this.checkable( element) ) + return this.findByName(element.name).filter(':checked').length; + } + return value.length; + }, + + depend: function(param, element) { + return this.dependTypes[typeof param] + ? this.dependTypes[typeof param](param, element) + : true; + }, + + dependTypes: { + "boolean": function(param, element) { + return param; + }, + "string": function(param, element) { + return !!$(param, element.form).length; + }, + "function": function(param, element) { + return param(element); + } + }, + + optional: function(element) { + return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch"; + }, + + startRequest: function(element) { + if (!this.pending[element.name]) { + this.pendingRequest++; + this.pending[element.name] = true; + } + }, + + stopRequest: function(element, valid) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if (this.pendingRequest < 0) + this.pendingRequest = 0; + delete this.pending[element.name]; + if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) { + $(this.currentForm).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.formSubmitted = false; + } + }, + + previousValue: function(element) { + return $.data(element, "previousValue") || $.data(element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: {required: true}, + email: {email: true}, + url: {url: true}, + date: {date: true}, + dateISO: {dateISO: true}, + dateDE: {dateDE: true}, + number: {number: true}, + numberDE: {numberDE: true}, + digits: {digits: true}, + creditcard: {creditcard: true} + }, + + addClassRules: function(className, rules) { + /// + /// Add a compound class method - useful to refactor common combinations of rules into a single + /// class. + /// + /// + /// The name of the class rule to add + /// + /// + /// The compound rules + /// + /// + + className.constructor == String ? + this.classRuleSettings[className] = rules : + $.extend(this.classRuleSettings, className); + }, + + classRules: function(element) { + var rules = {}; + var classes = $(element).attr('class'); + classes && $.each(classes.split(' '), function() { + if (this in $.validator.classRuleSettings) { + $.extend(rules, $.validator.classRuleSettings[this]); + } + }); + return rules; + }, + + attributeRules: function(element) { + var rules = {}; + var $element = $(element); + + for (var method in $.validator.methods) { + var value = $element.attr(method); + if (value) { + rules[method] = value; + } + } + + // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs + if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) { + delete rules.maxlength; + } + + return rules; + }, + + metadataRules: function(element) { + if (!$.metadata) return {}; + + var meta = $.data(element.form, 'validator').settings.meta; + return meta ? + $(element).metadata()[meta] : + $(element).metadata(); + }, + + staticRules: function(element) { + var rules = {}; + var validator = $.data(element.form, 'validator'); + if (validator.settings.rules) { + rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; + } + return rules; + }, + + normalizeRules: function(rules, element) { + // handle dependency check + $.each(rules, function(prop, val) { + // ignore rule when param is explicitly false, eg. required:false + if (val === false) { + delete rules[prop]; + return; + } + if (val.param || val.depends) { + var keepRule = true; + switch (typeof val.depends) { + case "string": + keepRule = !!$(val.depends, element.form).length; + break; + case "function": + keepRule = val.depends.call(element, element); + break; + } + if (keepRule) { + rules[prop] = val.param !== undefined ? val.param : true; + } else { + delete rules[prop]; + } + } + }); + + // evaluate parameters + $.each(rules, function(rule, parameter) { + rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; + }); + + // clean number parameters + $.each(['minlength', 'maxlength', 'min', 'max'], function() { + if (rules[this]) { + rules[this] = Number(rules[this]); + } + }); + $.each(['rangelength', 'range'], function() { + if (rules[this]) { + rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; + } + }); + + if ($.validator.autoCreateRanges) { + // auto-create ranges + if (rules.min && rules.max) { + rules.range = [rules.min, rules.max]; + delete rules.min; + delete rules.max; + } + if (rules.minlength && rules.maxlength) { + rules.rangelength = [rules.minlength, rules.maxlength]; + delete rules.minlength; + delete rules.maxlength; + } + } + + // To support custom messages in metadata ignore rule methods titled "messages" + if (rules.messages) { + delete rules.messages; + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function(data) { + if( typeof data == "string" ) { + var transformed = {}; + $.each(data.split(/\s/), function() { + transformed[this] = true; + }); + data = transformed; + } + return data; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/addMethod + addMethod: function(name, method, message) { + /// + /// Add a custom validation method. It must consist of a name (must be a legal javascript + /// identifier), a javascript based function and a default string message. + /// + /// + /// The name of the method, used to identify and referencing it, must be a valid javascript + /// identifier + /// + /// + /// The actual method implementation, returning true if an element is valid + /// + /// + /// (Optional) The default message to display for this method. Can be a function created by + /// jQuery.validator.format(value). When undefined, an already existing message is used + /// (handy for localization), otherwise the field-specific messages have to be defined. + /// + /// + + $.validator.methods[name] = method; + $.validator.messages[name] = message != undefined ? message : $.validator.messages[name]; + if (method.length < 3) { + $.validator.addClassRules(name, $.validator.normalizeRule(name)); + } + }, + + methods: { + + // http://docs.jquery.com/Plugins/Validation/Methods/required + required: function(value, element, param) { + // check if dependency is met + if ( !this.depend(param, element) ) + return "dependency-mismatch"; + switch( element.nodeName.toLowerCase() ) { + case 'select': + // could be an array for select-multiple or a string, both are fine this way + var val = $(element).val(); + return val && val.length > 0; + case 'input': + if ( this.checkable(element) ) + return this.getLength(value, element) > 0; + default: + return $.trim(value).length > 0; + } + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/remote + remote: function(value, element, param) { + if ( this.optional(element) ) + return "dependency-mismatch"; + + var previous = this.previousValue(element); + if (!this.settings.messages[element.name] ) + this.settings.messages[element.name] = {}; + previous.originalMessage = this.settings.messages[element.name].remote; + this.settings.messages[element.name].remote = previous.message; + + param = typeof param == "string" && {url:param} || param; + + if ( this.pending[element.name] ) { + return "pending"; + } + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + var validator = this; + this.startRequest(element); + var data = {}; + data[element.name] = value; + $.ajax($.extend(true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + success: function(response) { + validator.settings.messages[element.name].remote = previous.originalMessage; + var valid = response === true; + if ( valid ) { + var submitted = validator.formSubmitted; + validator.prepareElement(element); + validator.formSubmitted = submitted; + validator.successList.push(element); + validator.showErrors(); + } else { + var errors = {}; + var message = response || validator.defaultMessage(element, "remote"); + errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; + validator.showErrors(errors); + } + previous.valid = valid; + validator.stopRequest(element, valid); + } + }, param)); + return "pending"; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/minlength + minlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/maxlength + maxlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/rangelength + rangelength: function(value, element, param) { + var length = this.getLength($.trim(value), element); + return this.optional(element) || ( length >= param[0] && length <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/min + min: function( value, element, param ) { + return this.optional(element) || value >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/max + max: function( value, element, param ) { + return this.optional(element) || value <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/range + range: function( value, element, param ) { + return this.optional(element) || ( value >= param[0] && value <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/email + email: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ + return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/url + url: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/date + date: function(value, element) { + return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/dateISO + dateISO: function(value, element) { + return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/number + number: function(value, element) { + return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/digits + digits: function(value, element) { + return this.optional(element) || /^\d+$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/creditcard + // based on http://en.wikipedia.org/wiki/Luhn + creditcard: function(value, element) { + if ( this.optional(element) ) + return "dependency-mismatch"; + // accept only digits and dashes + if (/[^0-9-]+/.test(value)) + return false; + var nCheck = 0, + nDigit = 0, + bEven = false; + + value = value.replace(/\D/g, ""); + + for (var n = value.length - 1; n >= 0; n--) { + var cDigit = value.charAt(n); + var nDigit = parseInt(cDigit, 10); + if (bEven) { + if ((nDigit *= 2) > 9) + nDigit -= 9; + } + nCheck += nDigit; + bEven = !bEven; + } + + return (nCheck % 10) == 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/accept + accept: function(value, element, param) { + param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; + return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/equalTo + equalTo: function(value, element, param) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { + $(element).valid(); + }); + return value == target.val(); + } + + } + +}); + +// deprecated, use $.validator.format instead +$.format = $.validator.format; + +})(jQuery); + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() +;(function($) { + var pendingRequests = {}; + // Use a prefilter if available (1.5+) + if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function(settings, _, xhr) { + var port = settings.port; + if (settings.mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } pendingRequests[port] = xhr; + } + }); + } else { + // Proxy ajax + var ajax = $.ajax; + $.ajax = function(settings) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if (mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + + return (pendingRequests[port] = ajax.apply(this, arguments)); + } + return ajax.apply(this, arguments); + }; + } +})(jQuery); + +// provides cross-browser focusin and focusout events +// IE has native support, in other browsers, use event caputuring (neither bubbles) + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target +;(function($) { + // only implement if not provided by jQuery core (since 1.4) + // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs + if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) { + $.each({ + focus: 'focusin', + blur: 'focusout' + }, function( original, fix ){ + $.event.special[fix] = { + setup:function() { + this.addEventListener( original, handler, true ); + }, + teardown:function() { + this.removeEventListener( original, handler, true ); + }, + handler: function(e) { + arguments[0] = $.event.fix(e); + arguments[0].type = fix; + return $.event.handle.apply(this, arguments); + } + }; + function handler(e) { + e = $.event.fix(e); + e.type = fix; + return $.event.handle.call(this, e); + } + }); + }; + $.extend($.fn, { + validateDelegate: function(delegate, type, handler) { + return this.bind(type, function(event) { + var target = $(event.target); + if (target.is(delegate)) { + return handler.apply(target, arguments); + } + }); + } + }); +})(jQuery); diff --git a/src/Test/Test/Scripts/jquery.validate.js b/src/Test/Test/Scripts/jquery.validate.js new file mode 100644 index 000000000..4ba1ad970 --- /dev/null +++ b/src/Test/Test/Scripts/jquery.validate.js @@ -0,0 +1,1162 @@ +/** +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* jQuery Validation Plugin 1.8.0 +* +* http://bassistance.de/jquery-plugins/jquery-plugin-validation/ +* http://docs.jquery.com/Plugins/Validation +* +* Copyright (c) 2006 - 2011 Jörn Zaefferer +*/ + +(function($) { + +$.extend($.fn, { + // http://docs.jquery.com/Plugins/Validation/validate + validate: function( options ) { + + // if nothing is selected, return nothing; can't chain anyway + if (!this.length) { + options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" ); + return; + } + + // check if a validator for this form was already created + var validator = $.data(this[0], 'validator'); + if ( validator ) { + return validator; + } + + validator = new $.validator( options, this[0] ); + $.data(this[0], 'validator', validator); + + if ( validator.settings.onsubmit ) { + + // allow suppresing validation by adding a cancel class to the submit button + this.find("input, button").filter(".cancel").click(function() { + validator.cancelSubmit = true; + }); + + // when a submitHandler is used, capture the submitting button + if (validator.settings.submitHandler) { + this.find("input, button").filter(":submit").click(function() { + validator.submitButton = this; + }); + } + + // validate the form on submit + this.submit( function( event ) { + if ( validator.settings.debug ) + // prevent form submit to be able to see console output + event.preventDefault(); + + function handle() { + if ( validator.settings.submitHandler ) { + if (validator.submitButton) { + // insert a hidden input as a replacement for the missing submit button + var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); + } + validator.settings.submitHandler.call( validator, validator.currentForm ); + if (validator.submitButton) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://docs.jquery.com/Plugins/Validation/valid + valid: function() { + if ( $(this[0]).is('form')) { + return this.validate().form(); + } else { + var valid = true; + var validator = $(this[0].form).validate(); + this.each(function() { + valid &= validator.element(this); + }); + return valid; + } + }, + // attributes: space seperated list of attributes to retrieve and remove + removeAttrs: function(attributes) { + var result = {}, + $element = this; + $.each(attributes.split(/\s/), function(index, value) { + result[value] = $element.attr(value); + $element.removeAttr(value); + }); + return result; + }, + // http://docs.jquery.com/Plugins/Validation/rules + rules: function(command, argument) { + var element = this[0]; + + if (command) { + var settings = $.data(element.form, 'validator').settings; + var staticRules = settings.rules; + var existingRules = $.validator.staticRules(element); + switch(command) { + case "add": + $.extend(existingRules, $.validator.normalizeRule(argument)); + staticRules[element.name] = existingRules; + if (argument.messages) + settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); + break; + case "remove": + if (!argument) { + delete staticRules[element.name]; + return existingRules; + } + var filtered = {}; + $.each(argument.split(/\s/), function(index, method) { + filtered[method] = existingRules[method]; + delete existingRules[method]; + }); + return filtered; + } + } + + var data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.metadataRules(element), + $.validator.classRules(element), + $.validator.attributeRules(element), + $.validator.staticRules(element) + ), element); + + // make sure required is at front + if (data.required) { + var param = data.required; + delete data.required; + data = $.extend({required: param}, data); + } + + return data; + } +}); + +// Custom selectors +$.extend($.expr[":"], { + // http://docs.jquery.com/Plugins/Validation/blank + blank: function(a) {return !$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/filled + filled: function(a) {return !!$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/unchecked + unchecked: function(a) {return !a.checked;} +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +$.validator.format = function(source, params) { + if ( arguments.length == 1 ) + return function() { + var args = $.makeArray(arguments); + args.unshift(source); + return $.validator.format.apply( this, args ); + }; + if ( arguments.length > 2 && params.constructor != Array ) { + params = $.makeArray(arguments).slice(1); + } + if ( params.constructor != Array ) { + params = [ params ]; + } + $.each(params, function(i, n) { + source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); + }); + return source; +}; + +$.extend($.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: [], + ignoreTitle: false, + onfocusin: function(element) { + this.lastActive = element; + + // hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { + this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + this.addWrapper(this.errorsFor(element)).hide(); + } + }, + onfocusout: function(element) { + if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { + this.element(element); + } + }, + onkeyup: function(element) { + if ( element.name in this.submitted || element == this.lastElement ) { + this.element(element); + } + }, + onclick: function(element) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) + this.element(element); + // or option elements, check parent select in that case + else if (element.parentNode.name in this.submitted) + this.element(element.parentNode); + }, + highlight: function( element, errorClass, validClass ) { + $(element).addClass(errorClass).removeClass(validClass); + }, + unhighlight: function( element, errorClass, validClass ) { + $(element).removeClass(errorClass).addClass(validClass); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults + setDefaults: function(settings) { + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + accept: "Please enter a value with a valid extension.", + maxlength: $.validator.format("Please enter no more than {0} characters."), + minlength: $.validator.format("Please enter at least {0} characters."), + rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), + range: $.validator.format("Please enter a value between {0} and {1}."), + max: $.validator.format("Please enter a value less than or equal to {0}."), + min: $.validator.format("Please enter a value greater than or equal to {0}.") + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $(this.settings.errorLabelContainer); + this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); + this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = (this.groups = {}); + $.each(this.settings.groups, function(key, value) { + $.each(value.split(/\s/), function(index, name) { + groups[name] = key; + }); + }); + var rules = this.settings.rules; + $.each(rules, function(key, value) { + rules[key] = $.validator.normalizeRule(value); + }); + + function delegate(event) { + var validator = $.data(this[0].form, "validator"), + eventType = "on" + event.type.replace(/^validate/, ""); + validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] ); + } + $(this.currentForm) + .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate) + .validateDelegate(":radio, :checkbox, select, option", "click", delegate); + + if (this.settings.invalidHandler) + $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/form + form: function() { + this.checkForm(); + $.extend(this.submitted, this.errorMap); + this.invalid = $.extend({}, this.errorMap); + if (!this.valid()) + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { + this.check( elements[i] ); + } + return this.valid(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/element + element: function( element ) { + element = this.clean( element ); + this.lastElement = element; + this.prepareElement( element ); + this.currentElements = $(element); + var result = this.check( element ); + if ( result ) { + delete this.invalid[element.name]; + } else { + this.invalid[element.name] = true; + } + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/showErrors + showErrors: function(errors) { + if(errors) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[name], + element: this.findByName(name)[0] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function(element) { + return !(element.name in errors); + }); + } + this.settings.showErrors + ? this.settings.showErrors.call( this, this.errorMap, this.errorList ) + : this.defaultShowErrors(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/resetForm + resetForm: function() { + if ( $.fn.resetForm ) + $( this.currentForm ).resetForm(); + this.submitted = {}; + this.prepareForm(); + this.hideErrors(); + this.elements().removeClass( this.settings.errorClass ); + }, + + numberOfInvalids: function() { + return this.objectLength(this.invalid); + }, + + objectLength: function( obj ) { + var count = 0; + for ( var i in obj ) + count++; + return count; + }, + + hideErrors: function() { + this.addWrapper( this.toHide ).hide(); + }, + + valid: function() { + return this.size() == 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if( this.settings.focusInvalid ) { + try { + $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) + .filter(":visible") + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger("focusin"); + } catch(e) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep(this.errorList, function(n) { + return n.element.name == lastActive.name; + }).length == 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved + return $([]).add(this.currentForm.elements) + .filter(":input") + .not(":submit, :reset, :image, [disabled]") + .not( this.settings.ignore ) + .filter(function() { + !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this); + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) + return false; + + rulesCache[this.name] = true; + return true; + }); + }, + + clean: function( selector ) { + return $( selector )[0]; + }, + + errors: function() { + return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext ); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $([]); + this.toHide = $([]); + this.currentElements = $([]); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor(element); + }, + + check: function( element ) { + element = this.clean( element ); + + // if radio/checkbox, validate first element in group instead + if (this.checkable(element)) { + element = this.findByName( element.name ).not(this.settings.ignore)[0]; + } + + var rules = $(element).rules(); + var dependencyMismatch = false; + for (var method in rules ) { + var rule = { method: method, parameters: rules[method] }; + try { + var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result == "dependency-mismatch" ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result == "pending" ) { + this.toHide = this.toHide.not( this.errorsFor(element) ); + return; + } + + if( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch(e) { + this.settings.debug && window.console && console.log("exception occured when checking element " + element.id + + ", check the '" + rule.method + "' method", e); + throw e; + } + } + if (dependencyMismatch) + return; + if ( this.objectLength(rules) ) + this.successList.push(element); + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's "messages" metadata + customMetaMessage: function(element, method) { + if (!$.metadata) + return; + + var meta = this.settings.meta + ? $(element).metadata()[this.settings.meta] + : $(element).metadata(); + + return meta && meta.messages && meta.messages[method]; + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[name]; + return m && (m.constructor == String + ? m + : m[method]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for(var i = 0; i < arguments.length; i++) { + if (arguments[i] !== undefined) + return arguments[i]; + } + return undefined; + }, + + defaultMessage: function( element, method) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customMetaMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[method], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message == "function" ) { + message = message.call(this, rule.parameters, element); + } else if (theregex.test(message)) { + message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters); + } + this.errorList.push({ + message: message, + element: element + }); + + this.errorMap[element.name] = message; + this.submitted[element.name] = message; + }, + + addWrapper: function(toToggle) { + if ( this.settings.wrapper ) + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + return toToggle; + }, + + defaultShowErrors: function() { + for ( var i = 0; this.errorList[i]; i++ ) { + var error = this.errorList[i]; + this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + this.showLabel( error.element, error.message ); + } + if( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if (this.settings.success) { + for ( var i = 0; this.successList[i]; i++ ) { + this.showLabel( this.successList[i] ); + } + } + if (this.settings.unhighlight) { + for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) { + this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not(this.invalidElements()); + }, + + invalidElements: function() { + return $(this.errorList).map(function() { + return this.element; + }); + }, + + showLabel: function(element, message) { + var label = this.errorsFor( element ); + if ( label.length ) { + // refresh error/success class + label.removeClass().addClass( this.settings.errorClass ); + + // check if we have a generated label, replace the message then + label.attr("generated") && label.html(message); + } else { + // create label + label = $("<" + this.settings.errorElement + "/>") + .attr({"for": this.idOrName(element), generated: true}) + .addClass(this.settings.errorClass) + .html(message || ""); + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); + } + if ( !this.labelContainer.append(label).length ) + this.settings.errorPlacement + ? this.settings.errorPlacement(label, $(element) ) + : label.insertAfter(element); + } + if ( !message && this.settings.success ) { + label.text(""); + typeof this.settings.success == "string" + ? label.addClass( this.settings.success ) + : this.settings.success( label ); + } + this.toShow = this.toShow.add(label); + }, + + errorsFor: function(element) { + var name = this.idOrName(element); + return this.errors().filter(function() { + return $(this).attr('for') == name; + }); + }, + + idOrName: function(element) { + return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); + }, + + checkable: function( element ) { + return /radio|checkbox/i.test(element.type); + }, + + findByName: function( name ) { + // select by name and filter by form for performance over form.find("[name=...]") + var form = this.currentForm; + return $(document.getElementsByName(name)).map(function(index, element) { + return element.form == form && element.name == name && element || null; + }); + }, + + getLength: function(value, element) { + switch( element.nodeName.toLowerCase() ) { + case 'select': + return $("option:selected", element).length; + case 'input': + if( this.checkable( element) ) + return this.findByName(element.name).filter(':checked').length; + } + return value.length; + }, + + depend: function(param, element) { + return this.dependTypes[typeof param] + ? this.dependTypes[typeof param](param, element) + : true; + }, + + dependTypes: { + "boolean": function(param, element) { + return param; + }, + "string": function(param, element) { + return !!$(param, element.form).length; + }, + "function": function(param, element) { + return param(element); + } + }, + + optional: function(element) { + return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch"; + }, + + startRequest: function(element) { + if (!this.pending[element.name]) { + this.pendingRequest++; + this.pending[element.name] = true; + } + }, + + stopRequest: function(element, valid) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if (this.pendingRequest < 0) + this.pendingRequest = 0; + delete this.pending[element.name]; + if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) { + $(this.currentForm).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.formSubmitted = false; + } + }, + + previousValue: function(element) { + return $.data(element, "previousValue") || $.data(element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: {required: true}, + email: {email: true}, + url: {url: true}, + date: {date: true}, + dateISO: {dateISO: true}, + dateDE: {dateDE: true}, + number: {number: true}, + numberDE: {numberDE: true}, + digits: {digits: true}, + creditcard: {creditcard: true} + }, + + addClassRules: function(className, rules) { + className.constructor == String ? + this.classRuleSettings[className] = rules : + $.extend(this.classRuleSettings, className); + }, + + classRules: function(element) { + var rules = {}; + var classes = $(element).attr('class'); + classes && $.each(classes.split(' '), function() { + if (this in $.validator.classRuleSettings) { + $.extend(rules, $.validator.classRuleSettings[this]); + } + }); + return rules; + }, + + attributeRules: function(element) { + var rules = {}; + var $element = $(element); + + for (var method in $.validator.methods) { + var value = $element.attr(method); + if (value) { + rules[method] = value; + } + } + + // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs + if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) { + delete rules.maxlength; + } + + return rules; + }, + + metadataRules: function(element) { + if (!$.metadata) return {}; + + var meta = $.data(element.form, 'validator').settings.meta; + return meta ? + $(element).metadata()[meta] : + $(element).metadata(); + }, + + staticRules: function(element) { + var rules = {}; + var validator = $.data(element.form, 'validator'); + if (validator.settings.rules) { + rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; + } + return rules; + }, + + normalizeRules: function(rules, element) { + // handle dependency check + $.each(rules, function(prop, val) { + // ignore rule when param is explicitly false, eg. required:false + if (val === false) { + delete rules[prop]; + return; + } + if (val.param || val.depends) { + var keepRule = true; + switch (typeof val.depends) { + case "string": + keepRule = !!$(val.depends, element.form).length; + break; + case "function": + keepRule = val.depends.call(element, element); + break; + } + if (keepRule) { + rules[prop] = val.param !== undefined ? val.param : true; + } else { + delete rules[prop]; + } + } + }); + + // evaluate parameters + $.each(rules, function(rule, parameter) { + rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; + }); + + // clean number parameters + $.each(['minlength', 'maxlength', 'min', 'max'], function() { + if (rules[this]) { + rules[this] = Number(rules[this]); + } + }); + $.each(['rangelength', 'range'], function() { + if (rules[this]) { + rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; + } + }); + + if ($.validator.autoCreateRanges) { + // auto-create ranges + if (rules.min && rules.max) { + rules.range = [rules.min, rules.max]; + delete rules.min; + delete rules.max; + } + if (rules.minlength && rules.maxlength) { + rules.rangelength = [rules.minlength, rules.maxlength]; + delete rules.minlength; + delete rules.maxlength; + } + } + + // To support custom messages in metadata ignore rule methods titled "messages" + if (rules.messages) { + delete rules.messages; + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function(data) { + if( typeof data == "string" ) { + var transformed = {}; + $.each(data.split(/\s/), function() { + transformed[this] = true; + }); + data = transformed; + } + return data; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/addMethod + addMethod: function(name, method, message) { + $.validator.methods[name] = method; + $.validator.messages[name] = message != undefined ? message : $.validator.messages[name]; + if (method.length < 3) { + $.validator.addClassRules(name, $.validator.normalizeRule(name)); + } + }, + + methods: { + + // http://docs.jquery.com/Plugins/Validation/Methods/required + required: function(value, element, param) { + // check if dependency is met + if ( !this.depend(param, element) ) + return "dependency-mismatch"; + switch( element.nodeName.toLowerCase() ) { + case 'select': + // could be an array for select-multiple or a string, both are fine this way + var val = $(element).val(); + return val && val.length > 0; + case 'input': + if ( this.checkable(element) ) + return this.getLength(value, element) > 0; + default: + return $.trim(value).length > 0; + } + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/remote + remote: function(value, element, param) { + if ( this.optional(element) ) + return "dependency-mismatch"; + + var previous = this.previousValue(element); + if (!this.settings.messages[element.name] ) + this.settings.messages[element.name] = {}; + previous.originalMessage = this.settings.messages[element.name].remote; + this.settings.messages[element.name].remote = previous.message; + + param = typeof param == "string" && {url:param} || param; + + if ( this.pending[element.name] ) { + return "pending"; + } + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + var validator = this; + this.startRequest(element); + var data = {}; + data[element.name] = value; + $.ajax($.extend(true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + success: function(response) { + validator.settings.messages[element.name].remote = previous.originalMessage; + var valid = response === true; + if ( valid ) { + var submitted = validator.formSubmitted; + validator.prepareElement(element); + validator.formSubmitted = submitted; + validator.successList.push(element); + validator.showErrors(); + } else { + var errors = {}; + var message = response || validator.defaultMessage( element, "remote" ); + errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; + validator.showErrors(errors); + } + previous.valid = valid; + validator.stopRequest(element, valid); + } + }, param)); + return "pending"; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/minlength + minlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/maxlength + maxlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/rangelength + rangelength: function(value, element, param) { + var length = this.getLength($.trim(value), element); + return this.optional(element) || ( length >= param[0] && length <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/min + min: function( value, element, param ) { + return this.optional(element) || value >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/max + max: function( value, element, param ) { + return this.optional(element) || value <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/range + range: function( value, element, param ) { + return this.optional(element) || ( value >= param[0] && value <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/email + email: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ + return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/url + url: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/date + date: function(value, element) { + return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/dateISO + dateISO: function(value, element) { + return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/number + number: function(value, element) { + return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/digits + digits: function(value, element) { + return this.optional(element) || /^\d+$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/creditcard + // based on http://en.wikipedia.org/wiki/Luhn + creditcard: function(value, element) { + if ( this.optional(element) ) + return "dependency-mismatch"; + // accept only digits and dashes + if (/[^0-9-]+/.test(value)) + return false; + var nCheck = 0, + nDigit = 0, + bEven = false; + + value = value.replace(/\D/g, ""); + + for (var n = value.length - 1; n >= 0; n--) { + var cDigit = value.charAt(n); + var nDigit = parseInt(cDigit, 10); + if (bEven) { + if ((nDigit *= 2) > 9) + nDigit -= 9; + } + nCheck += nDigit; + bEven = !bEven; + } + + return (nCheck % 10) == 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/accept + accept: function(value, element, param) { + param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; + return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/equalTo + equalTo: function(value, element, param) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { + $(element).valid(); + }); + return value == target.val(); + } + + } + +}); + +// deprecated, use $.validator.format instead +$.format = $.validator.format; + +})(jQuery); + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() +;(function($) { + var pendingRequests = {}; + // Use a prefilter if available (1.5+) + if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function(settings, _, xhr) { + var port = settings.port; + if (settings.mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = xhr; + } + }); + } else { + // Proxy ajax + var ajax = $.ajax; + $.ajax = function(settings) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if (mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + return (pendingRequests[port] = ajax.apply(this, arguments)); + } + return ajax.apply(this, arguments); + }; + } +})(jQuery); + +// provides cross-browser focusin and focusout events +// IE has native support, in other browsers, use event caputuring (neither bubbles) + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target +;(function($) { + // only implement if not provided by jQuery core (since 1.4) + // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs + if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) { + $.each({ + focus: 'focusin', + blur: 'focusout' + }, function( original, fix ){ + $.event.special[fix] = { + setup:function() { + this.addEventListener( original, handler, true ); + }, + teardown:function() { + this.removeEventListener( original, handler, true ); + }, + handler: function(e) { + arguments[0] = $.event.fix(e); + arguments[0].type = fix; + return $.event.handle.apply(this, arguments); + } + }; + function handler(e) { + e = $.event.fix(e); + e.type = fix; + return $.event.handle.call(this, e); + } + }); + }; + $.extend($.fn, { + validateDelegate: function(delegate, type, handler) { + return this.bind(type, function(event) { + var target = $(event.target); + if (target.is(delegate)) { + return handler.apply(target, arguments); + } + }); + } + }); +})(jQuery); diff --git a/src/Test/Test/Scripts/jquery.validate.min.js b/src/Test/Test/Scripts/jquery.validate.min.js new file mode 100644 index 000000000..b07c2ab2d --- /dev/null +++ b/src/Test/Test/Scripts/jquery.validate.min.js @@ -0,0 +1,53 @@ +/** +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* jQuery Validation Plugin 1.8.0 +* +* http://bassistance.de/jquery-plugins/jquery-plugin-validation/ +* http://docs.jquery.com/Plugins/Validation +* +* Copyright (c) 2006 - 2011 Jörn Zaefferer +*/ +(function(c){c.extend(c.fn,{validate:function(a){if(this.length){var b=c.data(this[0],"validator");if(b)return b;b=new c.validator(a,this[0]);c.data(this[0],"validator",b);if(b.settings.onsubmit){this.find("input, button").filter(".cancel").click(function(){b.cancelSubmit=true});b.settings.submitHandler&&this.find("input, button").filter(":submit").click(function(){b.submitButton=this});this.submit(function(d){function e(){if(b.settings.submitHandler){if(b.submitButton)var f=c("").attr("name", +b.submitButton.name).val(b.submitButton.value).appendTo(b.currentForm);b.settings.submitHandler.call(b,b.currentForm);b.submitButton&&f.remove();return false}return true}b.settings.debug&&d.preventDefault();if(b.cancelSubmit){b.cancelSubmit=false;return e()}if(b.form()){if(b.pendingRequest){b.formSubmitted=true;return false}return e()}else{b.focusInvalid();return false}})}return b}else a&&a.debug&&window.console&&console.warn("nothing selected, can't validate, returning nothing")},valid:function(){if(c(this[0]).is("form"))return this.validate().form(); +else{var a=true,b=c(this[0].form).validate();this.each(function(){a&=b.element(this)});return a}},removeAttrs:function(a){var b={},d=this;c.each(a.split(/\s/),function(e,f){b[f]=d.attr(f);d.removeAttr(f)});return b},rules:function(a,b){var d=this[0];if(a){var e=c.data(d.form,"validator").settings,f=e.rules,g=c.validator.staticRules(d);switch(a){case "add":c.extend(g,c.validator.normalizeRule(b));f[d.name]=g;if(b.messages)e.messages[d.name]=c.extend(e.messages[d.name],b.messages);break;case "remove":if(!b){delete f[d.name]; +return g}var h={};c.each(b.split(/\s/),function(j,i){h[i]=g[i];delete g[i]});return h}}d=c.validator.normalizeRules(c.extend({},c.validator.metadataRules(d),c.validator.classRules(d),c.validator.attributeRules(d),c.validator.staticRules(d)),d);if(d.required){e=d.required;delete d.required;d=c.extend({required:e},d)}return d}});c.extend(c.expr[":"],{blank:function(a){return!c.trim(""+a.value)},filled:function(a){return!!c.trim(""+a.value)},unchecked:function(a){return!a.checked}});c.validator=function(a, +b){this.settings=c.extend(true,{},c.validator.defaults,a);this.currentForm=b;this.init()};c.validator.format=function(a,b){if(arguments.length==1)return function(){var d=c.makeArray(arguments);d.unshift(a);return c.validator.format.apply(this,d)};if(arguments.length>2&&b.constructor!=Array)b=c.makeArray(arguments).slice(1);if(b.constructor!=Array)b=[b];c.each(b,function(d,e){a=a.replace(RegExp("\\{"+d+"\\}","g"),e)});return a};c.extend(c.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error", +validClass:"valid",errorElement:"label",focusInvalid:true,errorContainer:c([]),errorLabelContainer:c([]),onsubmit:true,ignore:[],ignoreTitle:false,onfocusin:function(a){this.lastActive=a;if(this.settings.focusCleanup&&!this.blockFocusCleanup){this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass);this.addWrapper(this.errorsFor(a)).hide()}},onfocusout:function(a){if(!this.checkable(a)&&(a.name in this.submitted||!this.optional(a)))this.element(a)}, +onkeyup:function(a){if(a.name in this.submitted||a==this.lastElement)this.element(a)},onclick:function(a){if(a.name in this.submitted)this.element(a);else a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(a,b,d){c(a).addClass(b).removeClass(d)},unhighlight:function(a,b,d){c(a).removeClass(b).addClass(d)}},setDefaults:function(a){c.extend(c.validator.defaults,a)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.", +url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",accept:"Please enter a value with a valid extension.",maxlength:c.validator.format("Please enter no more than {0} characters."),minlength:c.validator.format("Please enter at least {0} characters."),rangelength:c.validator.format("Please enter a value between {0} and {1} characters long."), +range:c.validator.format("Please enter a value between {0} and {1}."),max:c.validator.format("Please enter a value less than or equal to {0}."),min:c.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:false,prototype:{init:function(){function a(e){var f=c.data(this[0].form,"validator");e="on"+e.type.replace(/^validate/,"");f.settings[e]&&f.settings[e].call(f,this[0])}this.labelContainer=c(this.settings.errorLabelContainer);this.errorContext=this.labelContainer.length&& +this.labelContainer||c(this.currentForm);this.containers=c(this.settings.errorContainer).add(this.settings.errorLabelContainer);this.submitted={};this.valueCache={};this.pendingRequest=0;this.pending={};this.invalid={};this.reset();var b=this.groups={};c.each(this.settings.groups,function(e,f){c.each(f.split(/\s/),function(g,h){b[h]=e})});var d=this.settings.rules;c.each(d,function(e,f){d[e]=c.validator.normalizeRule(f)});c(this.currentForm).validateDelegate(":text, :password, :file, select, textarea", +"focusin focusout keyup",a).validateDelegate(":radio, :checkbox, select, option","click",a);this.settings.invalidHandler&&c(this.currentForm).bind("invalid-form.validate",this.settings.invalidHandler)},form:function(){this.checkForm();c.extend(this.submitted,this.errorMap);this.invalid=c.extend({},this.errorMap);this.valid()||c(this.currentForm).triggerHandler("invalid-form",[this]);this.showErrors();return this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]); +return this.valid()},element:function(a){this.lastElement=a=this.clean(a);this.prepareElement(a);this.currentElements=c(a);var b=this.check(a);if(b)delete this.invalid[a.name];else this.invalid[a.name]=true;if(!this.numberOfInvalids())this.toHide=this.toHide.add(this.containers);this.showErrors();return b},showErrors:function(a){if(a){c.extend(this.errorMap,a);this.errorList=[];for(var b in a)this.errorList.push({message:a[b],element:this.findByName(b)[0]});this.successList=c.grep(this.successList, +function(d){return!(d.name in a)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){c.fn.resetForm&&c(this.currentForm).resetForm();this.submitted={};this.prepareForm();this.hideErrors();this.elements().removeClass(this.settings.errorClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b=0,d;for(d in a)b++;return b},hideErrors:function(){this.addWrapper(this.toHide).hide()}, +valid:function(){return this.size()==0},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{c(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(a){}},findLastActive:function(){var a=this.lastActive;return a&&c.grep(this.errorList,function(b){return b.element.name==a.name}).length==1&&a},elements:function(){var a=this,b={};return c([]).add(this.currentForm.elements).filter(":input").not(":submit, :reset, :image, [disabled]").not(this.settings.ignore).filter(function(){!this.name&& +a.settings.debug&&window.console&&console.error("%o has no name assigned",this);if(this.name in b||!a.objectLength(c(this).rules()))return false;return b[this.name]=true})},clean:function(a){return c(a)[0]},errors:function(){return c(this.settings.errorElement+"."+this.settings.errorClass,this.errorContext)},reset:function(){this.successList=[];this.errorList=[];this.errorMap={};this.toShow=c([]);this.toHide=c([]);this.currentElements=c([])},prepareForm:function(){this.reset();this.toHide=this.errors().add(this.containers)}, +prepareElement:function(a){this.reset();this.toHide=this.errorsFor(a)},check:function(a){a=this.clean(a);if(this.checkable(a))a=this.findByName(a.name).not(this.settings.ignore)[0];var b=c(a).rules(),d=false,e;for(e in b){var f={method:e,parameters:b[e]};try{var g=c.validator.methods[e].call(this,a.value.replace(/\r/g,""),a,f.parameters);if(g=="dependency-mismatch")d=true;else{d=false;if(g=="pending"){this.toHide=this.toHide.not(this.errorsFor(a));return}if(!g){this.formatAndAdd(a,f);return false}}}catch(h){this.settings.debug&& +window.console&&console.log("exception occured when checking element "+a.id+", check the '"+f.method+"' method",h);throw h;}}if(!d){this.objectLength(b)&&this.successList.push(a);return true}},customMetaMessage:function(a,b){if(c.metadata){var d=this.settings.meta?c(a).metadata()[this.settings.meta]:c(a).metadata();return d&&d.messages&&d.messages[b]}},customMessage:function(a,b){var d=this.settings.messages[a];return d&&(d.constructor==String?d:d[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+a.name+"")},formatAndAdd:function(a,b){var d=this.defaultMessage(a,b.method),e=/\$?\{(\d+)\}/g;if(typeof d=="function")d=d.call(this,b.parameters,a);else if(e.test(d))d=jQuery.format(d.replace(e,"{$1}"),b.parameters);this.errorList.push({message:d, +element:a});this.errorMap[a.name]=d;this.submitted[a.name]=d},addWrapper:function(a){if(this.settings.wrapper)a=a.add(a.parent(this.settings.wrapper));return a},defaultShowErrors:function(){for(var a=0;this.errorList[a];a++){var b=this.errorList[a];this.settings.highlight&&this.settings.highlight.call(this,b.element,this.settings.errorClass,this.settings.validClass);this.showLabel(b.element,b.message)}if(this.errorList.length)this.toShow=this.toShow.add(this.containers);if(this.settings.success)for(a= +0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight){a=0;for(b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass)}this.toHide=this.toHide.not(this.toShow);this.hideErrors();this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return c(this.errorList).map(function(){return this.element})},showLabel:function(a, +b){var d=this.errorsFor(a);if(d.length){d.removeClass().addClass(this.settings.errorClass);d.attr("generated")&&d.html(b)}else{d=c("<"+this.settings.errorElement+"/>").attr({"for":this.idOrName(a),generated:true}).addClass(this.settings.errorClass).html(b||"");if(this.settings.wrapper)d=d.hide().show().wrap("<"+this.settings.wrapper+"/>").parent();this.labelContainer.append(d).length||(this.settings.errorPlacement?this.settings.errorPlacement(d,c(a)):d.insertAfter(a))}if(!b&&this.settings.success){d.text(""); +typeof this.settings.success=="string"?d.addClass(this.settings.success):this.settings.success(d)}this.toShow=this.toShow.add(d)},errorsFor:function(a){var b=this.idOrName(a);return this.errors().filter(function(){return c(this).attr("for")==b})},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(a){var b=this.currentForm;return c(document.getElementsByName(a)).map(function(d,e){return e.form== +b&&e.name==a&&e||null})},getLength:function(a,b){switch(b.nodeName.toLowerCase()){case "select":return c("option:selected",b).length;case "input":if(this.checkable(b))return this.findByName(b.name).filter(":checked").length}return a.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):true},dependTypes:{"boolean":function(a){return a},string:function(a,b){return!!c(a,b.form).length},"function":function(a,b){return a(b)}},optional:function(a){return!c.validator.methods.required.call(this, +c.trim(a.value),a)&&"dependency-mismatch"},startRequest:function(a){if(!this.pending[a.name]){this.pendingRequest++;this.pending[a.name]=true}},stopRequest:function(a,b){this.pendingRequest--;if(this.pendingRequest<0)this.pendingRequest=0;delete this.pending[a.name];if(b&&this.pendingRequest==0&&this.formSubmitted&&this.form()){c(this.currentForm).submit();this.formSubmitted=false}else if(!b&&this.pendingRequest==0&&this.formSubmitted){c(this.currentForm).triggerHandler("invalid-form",[this]);this.formSubmitted= +false}},previousValue:function(a){return c.data(a,"previousValue")||c.data(a,"previousValue",{old:null,valid:true,message:this.defaultMessage(a,"remote")})}},classRuleSettings:{required:{required:true},email:{email:true},url:{url:true},date:{date:true},dateISO:{dateISO:true},dateDE:{dateDE:true},number:{number:true},numberDE:{numberDE:true},digits:{digits:true},creditcard:{creditcard:true}},addClassRules:function(a,b){a.constructor==String?this.classRuleSettings[a]=b:c.extend(this.classRuleSettings, +a)},classRules:function(a){var b={};(a=c(a).attr("class"))&&c.each(a.split(" "),function(){this in c.validator.classRuleSettings&&c.extend(b,c.validator.classRuleSettings[this])});return b},attributeRules:function(a){var b={};a=c(a);for(var d in c.validator.methods){var e=a.attr(d);if(e)b[d]=e}b.maxlength&&/-1|2147483647|524288/.test(b.maxlength)&&delete b.maxlength;return b},metadataRules:function(a){if(!c.metadata)return{};var b=c.data(a.form,"validator").settings.meta;return b?c(a).metadata()[b]: +c(a).metadata()},staticRules:function(a){var b={},d=c.data(a.form,"validator");if(d.settings.rules)b=c.validator.normalizeRule(d.settings.rules[a.name])||{};return b},normalizeRules:function(a,b){c.each(a,function(d,e){if(e===false)delete a[d];else if(e.param||e.depends){var f=true;switch(typeof e.depends){case "string":f=!!c(e.depends,b.form).length;break;case "function":f=e.depends.call(b,b)}if(f)a[d]=e.param!==undefined?e.param:true;else delete a[d]}});c.each(a,function(d,e){a[d]=c.isFunction(e)? +e(b):e});c.each(["minlength","maxlength","min","max"],function(){if(a[this])a[this]=Number(a[this])});c.each(["rangelength","range"],function(){if(a[this])a[this]=[Number(a[this][0]),Number(a[this][1])]});if(c.validator.autoCreateRanges){if(a.min&&a.max){a.range=[a.min,a.max];delete a.min;delete a.max}if(a.minlength&&a.maxlength){a.rangelength=[a.minlength,a.maxlength];delete a.minlength;delete a.maxlength}}a.messages&&delete a.messages;return a},normalizeRule:function(a){if(typeof a=="string"){var b= +{};c.each(a.split(/\s/),function(){b[this]=true});a=b}return a},addMethod:function(a,b,d){c.validator.methods[a]=b;c.validator.messages[a]=d!=undefined?d:c.validator.messages[a];b.length<3&&c.validator.addClassRules(a,c.validator.normalizeRule(a))},methods:{required:function(a,b,d){if(!this.depend(d,b))return"dependency-mismatch";switch(b.nodeName.toLowerCase()){case "select":return(a=c(b).val())&&a.length>0;case "input":if(this.checkable(b))return this.getLength(a,b)>0;default:return c.trim(a).length> +0}},remote:function(a,b,d){if(this.optional(b))return"dependency-mismatch";var e=this.previousValue(b);this.settings.messages[b.name]||(this.settings.messages[b.name]={});e.originalMessage=this.settings.messages[b.name].remote;this.settings.messages[b.name].remote=e.message;d=typeof d=="string"&&{url:d}||d;if(this.pending[b.name])return"pending";if(e.old===a)return e.valid;e.old=a;var f=this;this.startRequest(b);var g={};g[b.name]=a;c.ajax(c.extend(true,{url:d,mode:"abort",port:"validate"+b.name, +dataType:"json",data:g,success:function(h){f.settings.messages[b.name].remote=e.originalMessage;var j=h===true;if(j){var i=f.formSubmitted;f.prepareElement(b);f.formSubmitted=i;f.successList.push(b);f.showErrors()}else{i={};h=h||f.defaultMessage(b,"remote");i[b.name]=e.message=c.isFunction(h)?h(a):h;f.showErrors(i)}e.valid=j;f.stopRequest(b,j)}},d));return"pending"},minlength:function(a,b,d){return this.optional(b)||this.getLength(c.trim(a),b)>=d},maxlength:function(a,b,d){return this.optional(b)|| +this.getLength(c.trim(a),b)<=d},rangelength:function(a,b,d){a=this.getLength(c.trim(a),b);return this.optional(b)||a>=d[0]&&a<=d[1]},min:function(a,b,d){return this.optional(b)||a>=d},max:function(a,b,d){return this.optional(b)||a<=d},range:function(a,b,d){return this.optional(b)||a>=d[0]&&a<=d[1]},email:function(a,b){return this.optional(b)||/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(a)}, +url:function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)}, +date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a))},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9-]+/.test(a))return false;var d=0,e=0,f=false;a=a.replace(/\D/g,"");for(var g=a.length-1;g>= +0;g--){e=a.charAt(g);e=parseInt(e,10);if(f)if((e*=2)>9)e-=9;d+=e;f=!f}return d%10==0},accept:function(a,b,d){d=typeof d=="string"?d.replace(/,/g,"|"):"png|jpe?g|gif";return this.optional(b)||a.match(RegExp(".("+d+")$","i"))},equalTo:function(a,b,d){d=c(d).unbind(".validate-equalTo").bind("blur.validate-equalTo",function(){c(b).valid()});return a==d.val()}}});c.format=c.validator.format})(jQuery); +(function(c){var a={};if(c.ajaxPrefilter)c.ajaxPrefilter(function(d,e,f){e=d.port;if(d.mode=="abort"){a[e]&&a[e].abort();a[e]=f}});else{var b=c.ajax;c.ajax=function(d){var e=("port"in d?d:c.ajaxSettings).port;if(("mode"in d?d:c.ajaxSettings).mode=="abort"){a[e]&&a[e].abort();return a[e]=b.apply(this,arguments)}return b.apply(this,arguments)}}})(jQuery); +(function(c){!jQuery.event.special.focusin&&!jQuery.event.special.focusout&&document.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.handle.call(this,e)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)},handler:function(e){arguments[0]=c.event.fix(e);arguments[0].type=b;return c.event.handle.apply(this,arguments)}}});c.extend(c.fn,{validateDelegate:function(a, +b,d){return this.bind(b,function(e){var f=c(e.target);if(f.is(a))return d.apply(f,arguments)})}})})(jQuery); diff --git a/src/Test/Test/Scripts/jquery.validate.unobtrusive.js b/src/Test/Test/Scripts/jquery.validate.unobtrusive.js new file mode 100644 index 000000000..6d92c0d58 --- /dev/null +++ b/src/Test/Test/Scripts/jquery.validate.unobtrusive.js @@ -0,0 +1,319 @@ +/// +/// + +/*! +** Unobtrusive validation support library for jQuery and jQuery Validate +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ + +/*jslint white: true, browser: true, onevar: true, undef: true, nomen: true, eqeqeq: true, plusplus: true, bitwise: true, regexp: true, newcap: true, immed: true, strict: false */ +/*global document: false, jQuery: false */ + +(function ($) { + var $jQval = $.validator, + adapters, + data_validation = "unobtrusiveValidation"; + + function setValidationValues(options, ruleName, value) { + options.rules[ruleName] = value; + if (options.message) { + options.messages[ruleName] = options.message; + } + } + + function splitAndTrim(value) { + return value.replace(/^\s+|\s+$/g, "").split(/\s*,\s*/g); + } + + function getModelPrefix(fieldName) { + return fieldName.substr(0, fieldName.lastIndexOf(".") + 1); + } + + function appendModelPrefix(value, prefix) { + if (value.indexOf("*.") === 0) { + value = value.replace("*.", prefix); + } + return value; + } + + function onError(error, inputElement) { // 'this' is the form element + var container = $(this).find("[data-valmsg-for='" + inputElement[0].name + "']"), + replace = $.parseJSON(container.attr("data-valmsg-replace")) !== false; + + container.removeClass("field-validation-valid").addClass("field-validation-error"); + error.data("unobtrusiveContainer", container); + + if (replace) { + container.empty(); + error.removeClass("input-validation-error").appendTo(container); + } + else { + error.hide(); + } + } + + function onErrors(form, validator) { // 'this' is the form element + var container = $(this).find("[data-valmsg-summary=true]"), + list = container.find("ul"); + + if (list && list.length && validator.errorList.length) { + list.empty(); + container.addClass("validation-summary-errors").removeClass("validation-summary-valid"); + + $.each(validator.errorList, function () { + $("
  • ").html(this.message).appendTo(list); + }); + } + } + + function onSuccess(error) { // 'this' is the form element + var container = error.data("unobtrusiveContainer"), + replace = $.parseJSON(container.attr("data-valmsg-replace")); + + if (container) { + container.addClass("field-validation-valid").removeClass("field-validation-error"); + error.removeData("unobtrusiveContainer"); + + if (replace) { + container.empty(); + } + } + } + + function validationInfo(form) { + var $form = $(form), + result = $form.data(data_validation); + + if (!result) { + result = { + options: { // options structure passed to jQuery Validate's validate() method + errorClass: "input-validation-error", + errorElement: "span", + errorPlacement: $.proxy(onError, form), + invalidHandler: $.proxy(onErrors, form), + messages: {}, + rules: {}, + success: $.proxy(onSuccess, form) + }, + attachValidation: function () { + $form.validate(this.options); + }, + validate: function () { // a validation function that is called by unobtrusive Ajax + $form.validate(); + return $form.valid(); + } + }; + $form.data(data_validation, result); + } + + return result; + } + + $jQval.unobtrusive = { + adapters: [], + + parseElement: function (element, skipAttach) { + /// + /// Parses a single HTML element for unobtrusive validation attributes. + /// + /// The HTML element to be parsed. + /// [Optional] true to skip attaching the + /// validation to the form. If parsing just this single element, you should specify true. + /// If parsing several elements, you should specify false, and manually attach the validation + /// to the form when you are finished. The default is false. + var $element = $(element), + form = $element.parents("form")[0], + valInfo, rules, messages; + + if (!form) { // Cannot do client-side validation without a form + return; + } + + valInfo = validationInfo(form); + valInfo.options.rules[element.name] = rules = {}; + valInfo.options.messages[element.name] = messages = {}; + + $.each(this.adapters, function () { + var prefix = "data-val-" + this.name, + message = $element.attr(prefix), + paramValues = {}; + + if (message !== undefined) { // Compare against undefined, because an empty message is legal (and falsy) + prefix += "-"; + + $.each(this.params, function () { + paramValues[this] = $element.attr(prefix + this); + }); + + this.adapt({ + element: element, + form: form, + message: message, + params: paramValues, + rules: rules, + messages: messages + }); + } + }); + + jQuery.extend(rules, { "__dummy__": true }); + + if (!skipAttach) { + valInfo.attachValidation(); + } + }, + + parse: function (selector) { + /// + /// Parses all the HTML elements in the specified selector. It looks for input elements decorated + /// with the [data-val=true] attribute value and enables validation according to the data-val-* + /// attribute values. + /// + /// Any valid jQuery selector. + $(selector).find(":input[data-val=true]").each(function () { + $jQval.unobtrusive.parseElement(this, true); + }); + + $("form").each(function () { + var info = validationInfo(this); + if (info) { + info.attachValidation(); + } + }); + } + }; + + adapters = $jQval.unobtrusive.adapters; + + adapters.add = function (adapterName, params, fn) { + /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation. + /// The name of the adapter to be added. This matches the name used + /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). + /// [Optional] An array of parameter names (strings) that will + /// be extracted from the data-val-nnnn-mmmm HTML attributes (where nnnn is the adapter name, and + /// mmmm is the parameter name). + /// The function to call, which adapts the values from the HTML + /// attributes into jQuery Validate rules and/or messages. + /// + if (!fn) { // Called with no params, just a function + fn = params; + params = []; + } + this.push({ name: adapterName, params: params, adapt: fn }); + return this; + }; + + adapters.addBool = function (adapterName, ruleName) { + /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where + /// the jQuery Validate validation rule has no parameter values. + /// The name of the adapter to be added. This matches the name used + /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). + /// [Optional] The name of the jQuery Validate rule. If not provided, the value + /// of adapterName will be used instead. + /// + return this.add(adapterName, function (options) { + setValidationValues(options, ruleName || adapterName, true); + }); + }; + + adapters.addMinMax = function (adapterName, minRuleName, maxRuleName, minMaxRuleName, minAttribute, maxAttribute) { + /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where + /// the jQuery Validate validation has three potential rules (one for min-only, one for max-only, and + /// one for min-and-max). The HTML parameters are expected to be named -min and -max. + /// The name of the adapter to be added. This matches the name used + /// in the data-val-nnnn HTML attribute (where nnnn is the adapter name). + /// The name of the jQuery Validate rule to be used when you only + /// have a minimum value. + /// The name of the jQuery Validate rule to be used when you only + /// have a maximum value. + /// The name of the jQuery Validate rule to be used when you + /// have both a minimum and maximum value. + /// [Optional] The name of the HTML attribute that + /// contains the minimum value. The default is "min". + /// [Optional] The name of the HTML attribute that + /// contains the maximum value. The default is "max". + /// + return this.add(adapterName, [minAttribute || "min", maxAttribute || "max"], function (options) { + var min = options.params.min, + max = options.params.max; + + if (min && max) { + setValidationValues(options, minMaxRuleName, [min, max]); + } + else if (min) { + setValidationValues(options, minRuleName, min); + } + else if (max) { + setValidationValues(options, maxRuleName, max); + } + }); + }; + + adapters.addSingleVal = function (adapterName, attribute, ruleName) { + /// Adds a new adapter to convert unobtrusive HTML into a jQuery Validate validation, where + /// the jQuery Validate validation rule has a single value. + /// The name of the adapter to be added. This matches the name used + /// in the data-val-nnnn HTML attribute(where nnnn is the adapter name). + /// [Optional] The name of the HTML attribute that contains the value. + /// The default is "val". + /// [Optional] The name of the jQuery Validate rule. If not provided, the value + /// of adapterName will be used instead. + /// + return this.add(adapterName, [attribute || "val"], function (options) { + setValidationValues(options, ruleName || adapterName, options.params[attribute]); + }); + }; + + $jQval.addMethod("__dummy__", function (value, element, params) { + return true; + }); + + $jQval.addMethod("regex", function (value, element, params) { + var match; + if (this.optional(element)) { + return true; + } + + match = new RegExp(params).exec(value); + return (match && (match.index === 0) && (match[0].length === value.length)); + }); + + adapters.addSingleVal("accept", "exts").addSingleVal("regex", "pattern"); + adapters.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url"); + adapters.addMinMax("length", "minlength", "maxlength", "rangelength").addMinMax("range", "min", "max", "range"); + adapters.add("equalto", ["other"], function (options) { + var prefix = getModelPrefix(options.element.name), + other = options.params.other, + fullOtherName = appendModelPrefix(other, prefix), + element = $(options.form).find(":input[name=" + fullOtherName + "]")[0]; + + setValidationValues(options, "equalTo", element); + }); + adapters.add("required", function (options) { + // jQuery Validate equates "required" with "mandatory" for checkbox elements + if (options.element.tagName.toUpperCase() !== "INPUT" || options.element.type.toUpperCase() !== "CHECKBOX") { + setValidationValues(options, "required", true); + } + }); + adapters.add("remote", ["url", "type", "additionalfields"], function (options) { + var value = { + url: options.params.url, + type: options.params.type || "GET", + data: {} + }, + prefix = getModelPrefix(options.element.name); + + $.each(splitAndTrim(options.params.additionalfields || options.element.name), function (i, fieldName) { + var paramName = appendModelPrefix(fieldName, prefix); + value.data[paramName] = function () { + return $(options.form).find(":input[name='" + paramName + "']").val(); + }; + }); + + setValidationValues(options, "remote", value); + }); + + $(function () { + $jQval.unobtrusive.parse(document); + }); +}(jQuery)); \ No newline at end of file diff --git a/src/Test/Test/Scripts/jquery.validate.unobtrusive.min.js b/src/Test/Test/Scripts/jquery.validate.unobtrusive.min.js new file mode 100644 index 000000000..e0d8fd5c1 --- /dev/null +++ b/src/Test/Test/Scripts/jquery.validate.unobtrusive.min.js @@ -0,0 +1,5 @@ +/* +** Unobtrusive validation support library for jQuery and jQuery Validate +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ +(function(a){var d=a.validator,b,f="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function i(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function g(a){return a.substr(0,a.lastIndexOf(".")+1)}function e(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function l(c,d){var b=a(this).find("[data-valmsg-for='"+d[0].name+"']"),e=a.parseJSON(b.attr("data-valmsg-replace"))!==false;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(e){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function k(e,d){var c=a(this).find("[data-valmsg-summary=true]"),b=c.find("ul");if(b&&b.length&&d.errorList.length){b.empty();c.addClass("validation-summary-errors").removeClass("validation-summary-valid");a.each(d.errorList,function(){a("
  • ").html(this.message).appendTo(b)})}}function j(c){var b=c.data("unobtrusiveContainer"),d=a.parseJSON(b.attr("data-valmsg-replace"));if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");c.removeData("unobtrusiveContainer");d&&b.empty()}}function h(d){var b=a(d),c=b.data(f);if(!c){c={options:{errorClass:"input-validation-error",errorElement:"span",errorPlacement:a.proxy(l,d),invalidHandler:a.proxy(k,d),messages:{},rules:{},success:a.proxy(j,d)},attachValidation:function(){b.validate(this.options)},validate:function(){b.validate();return b.valid()}};b.data(f,c)}return c}d.unobtrusive={adapters:[],parseElement:function(b,i){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=h(f);c.options.rules[b.name]=e={};c.options.messages[b.name]=g={};a.each(this.adapters,function(){var c="data-val-"+this.name,i=d.attr(c),h={};if(i!==undefined){c+="-";a.each(this.params,function(){h[this]=d.attr(c+this)});this.adapt({element:b,form:f,message:i,params:h,rules:e,messages:g})}});jQuery.extend(e,{__dummy__:true});!i&&c.attachValidation()},parse:function(b){a(b).find(":input[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});a("form").each(function(){var a=h(this);a&&a.attachValidation()})}};b=d.unobtrusive.adapters;b.add=function(c,a,b){if(!b){b=a;a=[]}this.push({name:c,params:a,adapt:b});return this};b.addBool=function(a,b){return this.add(a,function(d){c(d,b||a,true)})};b.addMinMax=function(e,g,f,a,d,b){return this.add(e,[d||"min",b||"max"],function(b){var e=b.params.min,d=b.params.max;if(e&&d)c(b,a,[e,d]);else if(e)c(b,g,e);else d&&c(b,f,d)})};b.addSingleVal=function(a,b,d){return this.add(a,[b||"val"],function(e){c(e,d||a,e.params[b])})};d.addMethod("__dummy__",function(){return true});d.addMethod("regex",function(b,c,d){var a;if(this.optional(c))return true;a=(new RegExp(d)).exec(b);return a&&a.index===0&&a[0].length===b.length});b.addSingleVal("accept","exts").addSingleVal("regex","pattern");b.addBool("creditcard").addBool("date").addBool("digits").addBool("email").addBool("number").addBool("url");b.addMinMax("length","minlength","maxlength","rangelength").addMinMax("range","min","max","range");b.add("equalto",["other"],function(b){var h=g(b.element.name),i=b.params.other,d=e(i,h),f=a(b.form).find(":input[name="+d+"]")[0];c(b,"equalTo",f)});b.add("required",function(a){(a.element.tagName.toUpperCase()!=="INPUT"||a.element.type.toUpperCase()!=="CHECKBOX")&&c(a,"required",true)});b.add("remote",["url","type","additionalfields"],function(b){var d={url:b.params.url,type:b.params.type||"GET",data:{}},f=g(b.element.name);a.each(i(b.params.additionalfields||b.element.name),function(h,g){var c=e(g,f);d.data[c]=function(){return a(b.form).find(":input[name='"+c+"']").val()}});c(b,"remote",d)});a(function(){d.unobtrusive.parse(document)})})(jQuery); \ No newline at end of file diff --git a/src/Test/Test/Scripts/modernizr-1.7.js b/src/Test/Test/Scripts/modernizr-1.7.js new file mode 100644 index 000000000..7bc212a8c --- /dev/null +++ b/src/Test/Test/Scripts/modernizr-1.7.js @@ -0,0 +1,969 @@ +/*! +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* Modernizr v1.7 +* http://www.modernizr.com +* +* Developed by: +* - Faruk Ates http://farukat.es/ +* - Paul Irish http://paulirish.com/ +* +* Copyright (c) 2009-2011 +*/ + + +/* + * Modernizr is a script that detects native CSS3 and HTML5 features + * available in the current UA and provides an object containing all + * features with a true/false value, depending on whether the UA has + * native support for it or not. + * + * Modernizr will also add classes to the element of the page, + * one for each feature it detects. If the UA supports it, a class + * like "cssgradients" will be added. If not, the class name will be + * "no-cssgradients". This allows for simple if-conditionals in your + * CSS, giving you fine control over the look & feel of your website. + * + * @author Faruk Ates + * @author Paul Irish + * @copyright (c) 2009-2011 Faruk Ates. + * @contributor Ben Alman + */ + +window.Modernizr = (function(window,document,undefined){ + + var version = '1.7', + + ret = {}, + + /** + * !! DEPRECATED !! + * + * enableHTML5 is a private property for advanced use only. If enabled, + * it will make Modernizr.init() run through a brief while() loop in + * which it will create all HTML5 elements in the DOM to allow for + * styling them in Internet Explorer, which does not recognize any + * non-HTML4 elements unless created in the DOM this way. + * + * enableHTML5 is ON by default. + * + * The enableHTML5 toggle option is DEPRECATED as per 1.6, and will be + * replaced in 2.0 in lieu of the modular, configurable nature of 2.0. + */ + enableHTML5 = true, + + + docElement = document.documentElement, + docHead = document.head || document.getElementsByTagName('head')[0], + + /** + * Create our "modernizr" element that we do most feature tests on. + */ + mod = 'modernizr', + modElem = document.createElement( mod ), + m_style = modElem.style, + + /** + * Create the input element for various Web Forms feature tests. + */ + inputElem = document.createElement( 'input' ), + + smile = ':)', + + tostring = Object.prototype.toString, + + // List of property values to set for css tests. See ticket #21 + prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '), + + // Following spec is to expose vendor-specific style properties as: + // elem.style.WebkitBorderRadius + // and the following would be incorrect: + // elem.style.webkitBorderRadius + + // Webkit ghosts their properties in lowercase but Opera & Moz do not. + // Microsoft foregoes prefixes entirely <= IE8, but appears to + // use a lowercase `ms` instead of the correct `Ms` in IE9 + + // More here: http://github.com/Modernizr/Modernizr/issues/issue/21 + domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), + + ns = {'svg': 'http://www.w3.org/2000/svg'}, + + tests = {}, + inputs = {}, + attrs = {}, + + classes = [], + + featurename, // used in testing loop + + + + // todo: consider using http://javascript.nwbox.com/CSSSupport/css-support.js instead + testMediaQuery = function(mq){ + + var st = document.createElement('style'), + div = document.createElement('div'), + ret; + + st.textContent = mq + '{#modernizr{height:3px}}'; + docHead.appendChild(st); + div.id = 'modernizr'; + docElement.appendChild(div); + + ret = div.offsetHeight === 3; + + st.parentNode.removeChild(st); + div.parentNode.removeChild(div); + + return !!ret; + + }, + + + /** + * isEventSupported determines if a given element supports the given event + * function from http://yura.thinkweb2.com/isEventSupported/ + */ + isEventSupported = (function(){ + + var TAGNAMES = { + 'select':'input','change':'input', + 'submit':'form','reset':'form', + 'error':'img','load':'img','abort':'img' + }; + + function isEventSupported(eventName, element) { + + element = element || document.createElement(TAGNAMES[eventName] || 'div'); + eventName = 'on' + eventName; + + // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those + var isSupported = (eventName in element); + + if (!isSupported) { + // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element + if (!element.setAttribute) { + element = document.createElement('div'); + } + if (element.setAttribute && element.removeAttribute) { + element.setAttribute(eventName, ''); + isSupported = is(element[eventName], 'function'); + + // If property was created, "remove it" (by setting value to `undefined`) + if (!is(element[eventName], undefined)) { + element[eventName] = undefined; + } + element.removeAttribute(eventName); + } + } + + element = null; + return isSupported; + } + return isEventSupported; + })(); + + + // hasOwnProperty shim by kangax needed for Safari 2.0 support + var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty; + if (!is(_hasOwnProperty, undefined) && !is(_hasOwnProperty.call, undefined)) { + hasOwnProperty = function (object, property) { + return _hasOwnProperty.call(object, property); + }; + } + else { + hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ + return ((property in object) && is(object.constructor.prototype[property], undefined)); + }; + } + + /** + * set_css applies given styles to the Modernizr DOM node. + */ + function set_css( str ) { + m_style.cssText = str; + } + + /** + * set_css_all extrapolates all vendor-specific css strings. + */ + function set_css_all( str1, str2 ) { + return set_css(prefixes.join(str1 + ';') + ( str2 || '' )); + } + + /** + * is returns a boolean for if typeof obj is exactly type. + */ + function is( obj, type ) { + return typeof obj === type; + } + + /** + * contains returns a boolean for if substr is found within str. + */ + function contains( str, substr ) { + return (''+str).indexOf( substr ) !== -1; + } + + /** + * test_props is a generic CSS / DOM property test; if a browser supports + * a certain property, it won't return undefined for it. + * A supported CSS property returns empty string when its not yet set. + */ + function test_props( props, callback ) { + for ( var i in props ) { + if ( m_style[ props[i] ] !== undefined && ( !callback || callback( props[i], modElem ) ) ) { + return true; + } + } + } + + /** + * test_props_all tests a list of DOM properties we want to check against. + * We specify literally ALL possible (known and/or likely) properties on + * the element including the non-vendor prefixed one, for forward- + * compatibility. + */ + function test_props_all( prop, callback ) { + + var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), + props = (prop + ' ' + domPrefixes.join(uc_prop + ' ') + uc_prop).split(' '); + + return !!test_props( props, callback ); + } + + + /** + * Tests + * ----- + */ + + tests['flexbox'] = function() { + /** + * set_prefixed_value_css sets the property of a specified element + * adding vendor prefixes to the VALUE of the property. + * @param {Element} element + * @param {string} property The property name. This will not be prefixed. + * @param {string} value The value of the property. This WILL be prefixed. + * @param {string=} extra Additional CSS to append unmodified to the end of + * the CSS string. + */ + function set_prefixed_value_css(element, property, value, extra) { + property += ':'; + element.style.cssText = (property + prefixes.join(value + ';' + property)).slice(0, -property.length) + (extra || ''); + } + + /** + * set_prefixed_property_css sets the property of a specified element + * adding vendor prefixes to the NAME of the property. + * @param {Element} element + * @param {string} property The property name. This WILL be prefixed. + * @param {string} value The value of the property. This will not be prefixed. + * @param {string=} extra Additional CSS to append unmodified to the end of + * the CSS string. + */ + function set_prefixed_property_css(element, property, value, extra) { + element.style.cssText = prefixes.join(property + ':' + value + ';') + (extra || ''); + } + + var c = document.createElement('div'), + elem = document.createElement('div'); + + set_prefixed_value_css(c, 'display', 'box', 'width:42px;padding:0;'); + set_prefixed_property_css(elem, 'box-flex', '1', 'width:10px;'); + + c.appendChild(elem); + docElement.appendChild(c); + + var ret = elem.offsetWidth === 42; + + c.removeChild(elem); + docElement.removeChild(c); + + return ret; + }; + + // On the S60 and BB Storm, getContext exists, but always returns undefined + // http://github.com/Modernizr/Modernizr/issues/issue/97/ + + tests['canvas'] = function() { + var elem = document.createElement( 'canvas' ); + return !!(elem.getContext && elem.getContext('2d')); + }; + + tests['canvastext'] = function() { + return !!(ret['canvas'] && is(document.createElement( 'canvas' ).getContext('2d').fillText, 'function')); + }; + + // This WebGL test false positives in FF depending on graphics hardware. But really it's quite impossible to know + // wether webgl will succeed until after you create the context. You might have hardware that can support + // a 100x100 webgl canvas, but will not support a 1000x1000 webgl canvas. So this feature inference is weak, + // but intentionally so. + tests['webgl'] = function(){ + return !!window.WebGLRenderingContext; + }; + + /* + * The Modernizr.touch test only indicates if the browser supports + * touch events, which does not necessarily reflect a touchscreen + * device, as evidenced by tablets running Windows 7 or, alas, + * the Palm Pre / WebOS (touch) phones. + * + * Additionally, Chrome (desktop) used to lie about its support on this, + * but that has since been rectified: http://crbug.com/36415 + * + * We also test for Firefox 4 Multitouch Support. + * + * For more info, see: http://modernizr.github.com/Modernizr/touch.html + */ + + tests['touch'] = function() { + + return ('ontouchstart' in window) || testMediaQuery('@media ('+prefixes.join('touch-enabled),(')+'modernizr)'); + + }; + + + /** + * geolocation tests for the new Geolocation API specification. + * This test is a standards compliant-only test; for more complete + * testing, including a Google Gears fallback, please see: + * http://code.google.com/p/geo-location-javascript/ + * or view a fallback solution using google's geo API: + * http://gist.github.com/366184 + */ + tests['geolocation'] = function() { + return !!navigator.geolocation; + }; + + // Per 1.6: + // This used to be Modernizr.crosswindowmessaging but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['postmessage'] = function() { + return !!window.postMessage; + }; + + // Web SQL database detection is tricky: + + // In chrome incognito mode, openDatabase is truthy, but using it will + // throw an exception: http://crbug.com/42380 + // We can create a dummy database, but there is no way to delete it afterwards. + + // Meanwhile, Safari users can get prompted on any database creation. + // If they do, any page with Modernizr will give them a prompt: + // http://github.com/Modernizr/Modernizr/issues/closed#issue/113 + + // We have chosen to allow the Chrome incognito false positive, so that Modernizr + // doesn't litter the web with these test databases. As a developer, you'll have + // to account for this gotcha yourself. + tests['websqldatabase'] = function() { + var result = !!window.openDatabase; + /* if (result){ + try { + result = !!openDatabase( mod + "testdb", "1.0", mod + "testdb", 2e4); + } catch(e) { + } + } */ + return result; + }; + + // Vendors have inconsistent prefixing with the experimental Indexed DB: + // - Firefox is shipping indexedDB in FF4 as moz_indexedDB + // - Webkit's implementation is accessible through webkitIndexedDB + // We test both styles. + tests['indexedDB'] = function(){ + for (var i = -1, len = domPrefixes.length; ++i < len; ){ + var prefix = domPrefixes[i].toLowerCase(); + if (window[prefix + '_indexedDB'] || window[prefix + 'IndexedDB']){ + return true; + } + } + return false; + }; + + // documentMode logic from YUI to filter out IE8 Compat Mode + // which false positives. + tests['hashchange'] = function() { + return isEventSupported('hashchange', window) && ( document.documentMode === undefined || document.documentMode > 7 ); + }; + + // Per 1.6: + // This used to be Modernizr.historymanagement but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['history'] = function() { + return !!(window.history && history.pushState); + }; + + tests['draganddrop'] = function() { + return isEventSupported('dragstart') && isEventSupported('drop'); + }; + + tests['websockets'] = function(){ + return ('WebSocket' in window); + }; + + + // http://css-tricks.com/rgba-browser-support/ + tests['rgba'] = function() { + // Set an rgba() color and check the returned value + + set_css( 'background-color:rgba(150,255,150,.5)' ); + + return contains( m_style.backgroundColor, 'rgba' ); + }; + + tests['hsla'] = function() { + // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, + // except IE9 who retains it as hsla + + set_css('background-color:hsla(120,40%,100%,.5)' ); + + return contains( m_style.backgroundColor, 'rgba' ) || contains( m_style.backgroundColor, 'hsla' ); + }; + + tests['multiplebgs'] = function() { + // Setting multiple images AND a color on the background shorthand property + // and then querying the style.background property value for the number of + // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! + + set_css( 'background:url(//:),url(//:),red url(//:)' ); + + // If the UA supports multiple backgrounds, there should be three occurrences + // of the string "url(" in the return value for elem_style.background + + return new RegExp("(url\\s*\\(.*?){3}").test(m_style.background); + }; + + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + + tests['backgroundsize'] = function() { + return test_props_all( 'backgroundSize' ); + }; + + tests['borderimage'] = function() { + return test_props_all( 'borderImage' ); + }; + + + // Super comprehensive table about all the unique implementations of + // border-radius: http://muddledramblings.com/table-of-css3-border-radius-compliance + + tests['borderradius'] = function() { + return test_props_all( 'borderRadius', '', function( prop ) { + return contains( prop, 'orderRadius' ); + }); + }; + + // WebOS unfortunately false positives on this test. + tests['boxshadow'] = function() { + return test_props_all( 'boxShadow' ); + }; + + // FF3.0 will false positive on this test + tests['textshadow'] = function(){ + return document.createElement('div').style.textShadow === ''; + }; + + + tests['opacity'] = function() { + // Browsers that actually have CSS Opacity implemented have done so + // according to spec, which means their return values are within the + // range of [0.0,1.0] - including the leading zero. + + set_css_all( 'opacity:.55' ); + + // The non-literal . in this regex is intentional: + // German Chrome returns this value as 0,55 + // https://github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 + return /^0.55$/.test(m_style.opacity); + }; + + + tests['cssanimations'] = function() { + return test_props_all( 'animationName' ); + }; + + + tests['csscolumns'] = function() { + return test_props_all( 'columnCount' ); + }; + + + tests['cssgradients'] = function() { + /** + * For CSS Gradients syntax, please see: + * http://webkit.org/blog/175/introducing-css-gradients/ + * https://developer.mozilla.org/en/CSS/-moz-linear-gradient + * https://developer.mozilla.org/en/CSS/-moz-radial-gradient + * http://dev.w3.org/csswg/css3-images/#gradients- + */ + + var str1 = 'background-image:', + str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', + str3 = 'linear-gradient(left top,#9f9, white);'; + + set_css( + (str1 + prefixes.join(str2 + str1) + prefixes.join(str3 + str1)).slice(0,-str1.length) + ); + + return contains( m_style.backgroundImage, 'gradient' ); + }; + + + tests['cssreflections'] = function() { + return test_props_all( 'boxReflect' ); + }; + + + tests['csstransforms'] = function() { + return !!test_props([ 'transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ]); + }; + + + tests['csstransforms3d'] = function() { + + var ret = !!test_props([ 'perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective' ]); + + // Webkit’s 3D transforms are passed off to the browser's own graphics renderer. + // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in + // some conditions. As a result, Webkit typically recognizes the syntax but + // will sometimes throw a false positive, thus we must do a more thorough check: + if (ret && 'webkitPerspective' in docElement.style){ + + // Webkit allows this media query to succeed only if the feature is enabled. + // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }` + ret = testMediaQuery('@media ('+prefixes.join('transform-3d),(')+'modernizr)'); + } + return ret; + }; + + + tests['csstransitions'] = function() { + return test_props_all( 'transitionProperty' ); + }; + + + // @font-face detection routine by Diego Perini + // http://javascript.nwbox.com/CSSSupport/ + tests['fontface'] = function(){ + + var + sheet, bool, + head = docHead || docElement, + style = document.createElement("style"), + impl = document.implementation || { hasFeature: function() { return false; } }; + + style.type = 'text/css'; + head.insertBefore(style, head.firstChild); + sheet = style.sheet || style.styleSheet; + + var supportAtRule = impl.hasFeature('CSS2', '') ? + function(rule) { + if (!(sheet && rule)) return false; + var result = false; + try { + sheet.insertRule(rule, 0); + result = (/src/i).test(sheet.cssRules[0].cssText); + sheet.deleteRule(sheet.cssRules.length - 1); + } catch(e) { } + return result; + } : + function(rule) { + if (!(sheet && rule)) return false; + sheet.cssText = rule; + + return sheet.cssText.length !== 0 && (/src/i).test(sheet.cssText) && + sheet.cssText + .replace(/\r+|\n+/g, '') + .indexOf(rule.split(' ')[0]) === 0; + }; + + bool = supportAtRule('@font-face { font-family: "font"; src: url(data:,); }'); + head.removeChild(style); + return bool; + }; + + + // These tests evaluate support of the video/audio elements, as well as + // testing what types of content they support. + // + // We're using the Boolean constructor here, so that we can extend the value + // e.g. Modernizr.video // true + // Modernizr.video.ogg // 'probably' + // + // Codec values from : http://github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 + // thx to NielsLeenheer and zcorpan + + // Note: in FF 3.5.1 and 3.5.0, "no" was a return value instead of empty string. + // Modernizr does not normalize for that. + + tests['video'] = function() { + var elem = document.createElement('video'), + bool = !!elem.canPlayType; + + if (bool){ + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('video/ogg; codecs="theora"'); + + // Workaround required for IE9, which doesn't report video support without audio codec specified. + // bug 599718 @ msft connect + var h264 = 'video/mp4; codecs="avc1.42E01E'; + bool.h264 = elem.canPlayType(h264 + '"') || elem.canPlayType(h264 + ', mp4a.40.2"'); + + bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"'); + } + return bool; + }; + + tests['audio'] = function() { + var elem = document.createElement('audio'), + bool = !!elem.canPlayType; + + if (bool){ + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"'); + bool.mp3 = elem.canPlayType('audio/mpeg;'); + + // Mimetypes accepted: + // https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements + // http://bit.ly/iphoneoscodecs + bool.wav = elem.canPlayType('audio/wav; codecs="1"'); + bool.m4a = elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;'); + } + return bool; + }; + + + // Firefox has made these tests rather unfun. + + // In FF4, if disabled, window.localStorage should === null. + + // Normally, we could not test that directly and need to do a + // `('localStorage' in window) && ` test first because otherwise Firefox will + // throw http://bugzil.la/365772 if cookies are disabled + + // However, in Firefox 4 betas, if dom.storage.enabled == false, just mentioning + // the property will throw an exception. http://bugzil.la/599479 + // This looks to be fixed for FF4 Final. + + // Because we are forced to try/catch this, we'll go aggressive. + + // FWIW: IE8 Compat mode supports these features completely: + // http://www.quirksmode.org/dom/html5.html + // But IE8 doesn't support either with local files + + tests['localstorage'] = function() { + try { + return !!localStorage.getItem; + } catch(e) { + return false; + } + }; + + tests['sessionstorage'] = function() { + try { + return !!sessionStorage.getItem; + } catch(e){ + return false; + } + }; + + + tests['webWorkers'] = function () { + return !!window.Worker; + }; + + + tests['applicationcache'] = function() { + return !!window.applicationCache; + }; + + + // Thanks to Erik Dahlstrom + tests['svg'] = function(){ + return !!document.createElementNS && !!document.createElementNS(ns.svg, "svg").createSVGRect; + }; + + tests['inlinesvg'] = function() { + var div = document.createElement('div'); + div.innerHTML = ''; + return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; + }; + + // Thanks to F1lt3r and lucideer + // http://github.com/Modernizr/Modernizr/issues#issue/35 + tests['smil'] = function(){ + return !!document.createElementNS && /SVG/.test(tostring.call(document.createElementNS(ns.svg,'animate'))); + }; + + tests['svgclippaths'] = function(){ + // Possibly returns a false positive in Safari 3.2? + return !!document.createElementNS && /SVG/.test(tostring.call(document.createElementNS(ns.svg,'clipPath'))); + }; + + + // input features and input types go directly onto the ret object, bypassing the tests loop. + // Hold this guy to execute in a moment. + function webforms(){ + + // Run through HTML5's new input attributes to see if the UA understands any. + // We're using f which is the element created early on + // Mike Taylr has created a comprehensive resource for testing these attributes + // when applied to all input types: + // http://miketaylr.com/code/input-type-attr.html + // spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary + ret['input'] = (function(props) { + for (var i = 0, len = props.length; i>BEGIN IEPP + // Enable HTML 5 elements for styling in IE. + // fyi: jscript version does not reflect trident version + // therefore ie9 in ie7 mode will still have a jScript v.9 + if ( enableHTML5 && window.attachEvent && (function(){ var elem = document.createElement("div"); + elem.innerHTML = ""; + return elem.childNodes.length !== 1; })()) { + // iepp v1.6.2 by @jon_neal : code.google.com/p/ie-print-protector + (function(win, doc) { + var elems = 'abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video', + elemsArr = elems.split('|'), + elemsArrLen = elemsArr.length, + elemRegExp = new RegExp('(^|\\s)('+elems+')', 'gi'), + tagRegExp = new RegExp('<(\/*)('+elems+')', 'gi'), + ruleRegExp = new RegExp('(^|[^\\n]*?\\s)('+elems+')([^\\n]*)({[\\n\\w\\W]*?})', 'gi'), + docFrag = doc.createDocumentFragment(), + html = doc.documentElement, + head = html.firstChild, + bodyElem = doc.createElement('body'), + styleElem = doc.createElement('style'), + body; + function shim(doc) { + var a = -1; + while (++a < elemsArrLen) + // Use createElement so IE allows HTML5-named elements in a document + doc.createElement(elemsArr[a]); + } + function getCSS(styleSheetList, mediaType) { + var a = -1, + len = styleSheetList.length, + styleSheet, + cssTextArr = []; + while (++a < len) { + styleSheet = styleSheetList[a]; + // Get css from all non-screen stylesheets and their imports + if ((mediaType = styleSheet.media || mediaType) != 'screen') cssTextArr.push(getCSS(styleSheet.imports, mediaType), styleSheet.cssText); + } + return cssTextArr.join(''); + } + // Shim the document and iepp fragment + shim(doc); + shim(docFrag); + // Add iepp custom print style element + head.insertBefore(styleElem, head.firstChild); + styleElem.media = 'print'; + win.attachEvent( + 'onbeforeprint', + function() { + var a = -1, + cssText = getCSS(doc.styleSheets, 'all'), + cssTextArr = [], + rule; + body = body || doc.body; + // Get only rules which reference HTML5 elements by name + while ((rule = ruleRegExp.exec(cssText)) != null) + // Replace all html5 element references with iepp substitute classnames + cssTextArr.push((rule[1]+rule[2]+rule[3]).replace(elemRegExp, '$1.iepp_$2')+rule[4]); + // Write iepp custom print CSS + styleElem.styleSheet.cssText = cssTextArr.join('\n'); + while (++a < elemsArrLen) { + var nodeList = doc.getElementsByTagName(elemsArr[a]), + nodeListLen = nodeList.length, + b = -1; + while (++b < nodeListLen) + if (nodeList[b].className.indexOf('iepp_') < 0) + // Append iepp substitute classnames to all html5 elements + nodeList[b].className += ' iepp_'+elemsArr[a]; + } + docFrag.appendChild(body); + html.appendChild(bodyElem); + // Write iepp substitute print-safe document + bodyElem.className = body.className; + // Replace HTML5 elements with which is print-safe and shouldn't conflict since it isn't part of html5 + bodyElem.innerHTML = body.innerHTML.replace(tagRegExp, '<$1font'); + } + ); + win.attachEvent( + 'onafterprint', + function() { + // Undo everything done in onbeforeprint + bodyElem.innerHTML = ''; + html.removeChild(bodyElem); + html.appendChild(body); + styleElem.styleSheet.cssText = ''; + } + ); + })(window, document); + } + //>>END IEPP + + // Assign private properties to the return object with prefix + ret._enableHTML5 = enableHTML5; + ret._version = version; + + // Remove "no-js" class from element, if it exists: + docElement.className = docElement.className.replace(/\bno-js\b/,'') + + ' js ' + + // Add the new classes to the element. + + classes.join( ' ' ); + + return ret; + +})(this,this.document); \ No newline at end of file diff --git a/src/Test/Test/Scripts/modernizr-1.7.min.js b/src/Test/Test/Scripts/modernizr-1.7.min.js new file mode 100644 index 000000000..4b4fcc1e5 --- /dev/null +++ b/src/Test/Test/Scripts/modernizr-1.7.min.js @@ -0,0 +1,10 @@ +/*! +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +*/ +// Modernizr v1.7 www.modernizr.com +window.Modernizr=function(a,b,c){function G(){e.input=function(a){for(var b=0,c=a.length;b7)},r.history=function(){return !!(a.history&&history.pushState)},r.draganddrop=function(){return x("dragstart")&&x("drop")},r.websockets=function(){return"WebSocket"in a},r.rgba=function(){A("background-color:rgba(150,255,150,.5)");return D(k.backgroundColor,"rgba")},r.hsla=function(){A("background-color:hsla(120,40%,100%,.5)");return D(k.backgroundColor,"rgba")||D(k.backgroundColor,"hsla")},r.multiplebgs=function(){A("background:url(//:),url(//:),red url(//:)");return(new RegExp("(url\\s*\\(.*?){3}")).test(k.background)},r.backgroundsize=function(){return F("backgroundSize")},r.borderimage=function(){return F("borderImage")},r.borderradius=function(){return F("borderRadius","",function(a){return D(a,"orderRadius")})},r.boxshadow=function(){return F("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){B("opacity:.55");return/^0.55$/.test(k.opacity)},r.cssanimations=function(){return F("animationName")},r.csscolumns=function(){return F("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";A((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return D(k.backgroundImage,"gradient")},r.cssreflections=function(){return F("boxReflect")},r.csstransforms=function(){return!!E(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},r.csstransforms3d=function(){var a=!!E(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=w("@media ("+o.join("transform-3d),(")+"modernizr)"));return a},r.csstransitions=function(){return F("transitionProperty")},r.fontface=function(){var a,c,d=h||g,e=b.createElement("style"),f=b.implementation||{hasFeature:function(){return!1}};e.type="text/css",d.insertBefore(e,d.firstChild),a=e.sheet||e.styleSheet;var i=f.hasFeature("CSS2","")?function(b){if(!a||!b)return!1;var c=!1;try{a.insertRule(b,0),c=/src/i.test(a.cssRules[0].cssText),a.deleteRule(a.cssRules.length-1)}catch(d){}return c}:function(b){if(!a||!b)return!1;a.cssText=b;return a.cssText.length!==0&&/src/i.test(a.cssText)&&a.cssText.replace(/\r+|\n+/g,"").indexOf(b.split(" ")[0])===0};c=i('@font-face { font-family: "font"; src: url(data:,); }'),d.removeChild(e);return c},r.video=function(){var a=b.createElement("video"),c=!!a.canPlayType;if(c){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return c},r.audio=function(){var a=b.createElement("audio"),c=!!a.canPlayType;c&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"'),c.mp3=a.canPlayType("audio/mpeg;"),c.wav=a.canPlayType('audio/wav; codecs="1"'),c.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;"));return c},r.localstorage=function(){try{return!!localStorage.getItem}catch(a){return!1}},r.sessionstorage=function(){try{return!!sessionStorage.getItem}catch(a){return!1}},r.webWorkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache},r.svg=function(){return!!b.createElementNS&&!!b.createElementNS(q.svg,"svg").createSVGRect},r.inlinesvg=function(){var a=b.createElement("div");a.innerHTML="";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,f&&a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function p(a,b){var c=-1,d=a.length,e,f=[];while(++c + + + Debug + AnyCPU + + + 2.0 + {30327C08-7574-4D7E-AC95-6A58753C6855} + {E53F8FEA-EAE0-44A6-8774-FFD645390401};{349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Test + Test + v4.0 + false + false + + + true + full + false + bin\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\ + TRACE + prompt + 4 + + + true + bin\ + DEBUG;TRACE + full + AnyCPU + prompt + + + + ..\packages\EntityFramework.4.1.10331.0\lib\EntityFramework.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Global.asax + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Web.config + + + Web.config + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {4F7050F2-465F-4E10-8DB2-2FB97AC6AA43} + ImageProcessor.Web + + + {3B5DD734-FB7A-487D-8CE6-55E7AF9AEA7E} + ImageProcessor + + + + + + + + + + + + + False + True + 6261 + / + + + False + False + + + False + + + + + \ No newline at end of file diff --git a/src/Test/Test/Test.vsdoc b/src/Test/Test/Test.vsdoc new file mode 100644 index 000000000..9ffff5e7f --- /dev/null +++ b/src/Test/Test/Test.vsdoc @@ -0,0 +1,7 @@ + + + + default + + + diff --git a/src/Test/Test/Views/Account/ChangePassword.cshtml b/src/Test/Test/Views/Account/ChangePassword.cshtml new file mode 100644 index 000000000..04acddf07 --- /dev/null +++ b/src/Test/Test/Views/Account/ChangePassword.cshtml @@ -0,0 +1,53 @@ +@model Test.Models.ChangePasswordModel + +@{ + ViewBag.Title = "Change Password"; +} + +

    Change Password

    +

    + Use the form below to change your password. +

    +

    + New passwords are required to be a minimum of @Membership.MinRequiredPasswordLength characters in length. +

    + + + + +@using (Html.BeginForm()) { + @Html.ValidationSummary(true, "Password change was unsuccessful. Please correct the errors and try again.") +
    +
    + Account Information + +
    + @Html.LabelFor(m => m.OldPassword) +
    +
    + @Html.PasswordFor(m => m.OldPassword) + @Html.ValidationMessageFor(m => m.OldPassword) +
    + +
    + @Html.LabelFor(m => m.NewPassword) +
    +
    + @Html.PasswordFor(m => m.NewPassword) + @Html.ValidationMessageFor(m => m.NewPassword) +
    + +
    + @Html.LabelFor(m => m.ConfirmPassword) +
    +
    + @Html.PasswordFor(m => m.ConfirmPassword) + @Html.ValidationMessageFor(m => m.ConfirmPassword) +
    + +

    + +

    +
    +
    +} diff --git a/src/Test/Test/Views/Account/ChangePasswordSuccess.cshtml b/src/Test/Test/Views/Account/ChangePasswordSuccess.cshtml new file mode 100644 index 000000000..6b6dbff63 --- /dev/null +++ b/src/Test/Test/Views/Account/ChangePasswordSuccess.cshtml @@ -0,0 +1,8 @@ +@{ + ViewBag.Title = "Change Password"; +} + +

    Change Password

    +

    + Your password has been changed successfully. +

    diff --git a/src/Test/Test/Views/Account/LogOn.cshtml b/src/Test/Test/Views/Account/LogOn.cshtml new file mode 100644 index 000000000..127bd6933 --- /dev/null +++ b/src/Test/Test/Views/Account/LogOn.cshtml @@ -0,0 +1,48 @@ +@model Test.Models.LogOnModel + +@{ + ViewBag.Title = "Log On"; +} + +

    Log On

    +

    + Please enter your user name and password. @Html.ActionLink("Register", "Register") if you don't have an account. +

    + + + + +@Html.ValidationSummary(true, "Login was unsuccessful. Please correct the errors and try again.") + +@using (Html.BeginForm()) { +
    +
    + Account Information + +
    + @Html.LabelFor(m => m.UserName) +
    +
    + @Html.TextBoxFor(m => m.UserName) + @Html.ValidationMessageFor(m => m.UserName) +
    + +
    + @Html.LabelFor(m => m.Password) +
    +
    + @Html.PasswordFor(m => m.Password) + @Html.ValidationMessageFor(m => m.Password) +
    + +
    + @Html.CheckBoxFor(m => m.RememberMe) + @Html.LabelFor(m => m.RememberMe) +
    + +

    + +

    +
    +
    +} diff --git a/src/Test/Test/Views/Account/Register.cshtml b/src/Test/Test/Views/Account/Register.cshtml new file mode 100644 index 000000000..5240b01bd --- /dev/null +++ b/src/Test/Test/Views/Account/Register.cshtml @@ -0,0 +1,61 @@ +@model Test.Models.RegisterModel + +@{ + ViewBag.Title = "Register"; +} + +

    Create a New Account

    +

    + Use the form below to create a new account. +

    +

    + Passwords are required to be a minimum of @Membership.MinRequiredPasswordLength characters in length. +

    + + + + +@using (Html.BeginForm()) { + @Html.ValidationSummary(true, "Account creation was unsuccessful. Please correct the errors and try again.") +
    +
    + Account Information + +
    + @Html.LabelFor(m => m.UserName) +
    +
    + @Html.TextBoxFor(m => m.UserName) + @Html.ValidationMessageFor(m => m.UserName) +
    + +
    + @Html.LabelFor(m => m.Email) +
    +
    + @Html.TextBoxFor(m => m.Email) + @Html.ValidationMessageFor(m => m.Email) +
    + +
    + @Html.LabelFor(m => m.Password) +
    +
    + @Html.PasswordFor(m => m.Password) + @Html.ValidationMessageFor(m => m.Password) +
    + +
    + @Html.LabelFor(m => m.ConfirmPassword) +
    +
    + @Html.PasswordFor(m => m.ConfirmPassword) + @Html.ValidationMessageFor(m => m.ConfirmPassword) +
    + +

    + +

    +
    +
    +} diff --git a/src/Test/Test/Views/Home/About.cshtml b/src/Test/Test/Views/Home/About.cshtml new file mode 100644 index 000000000..d214ffe10 --- /dev/null +++ b/src/Test/Test/Views/Home/About.cshtml @@ -0,0 +1,14 @@ +@model List +@{ + ViewBag.Title = "About Us"; +} + +

    About

    +

    + @foreach(string image in Model) + { + string path = image + "?width=150"; + + @image + } +

    diff --git a/src/Test/Test/Views/Home/Index.cshtml b/src/Test/Test/Views/Home/Index.cshtml new file mode 100644 index 000000000..f735894c2 --- /dev/null +++ b/src/Test/Test/Views/Home/Index.cshtml @@ -0,0 +1,10 @@ +@{ + ViewBag.Title = "Home Page"; +} + +

    @ViewBag.Message

    +

    + To learn more about ASP.NET MVC visit http://asp.net/mvc. +

    +

    Test remote image

    + \ No newline at end of file diff --git a/src/Test/Test/Views/Shared/Error.cshtml b/src/Test/Test/Views/Shared/Error.cshtml new file mode 100644 index 000000000..8ee384bfd --- /dev/null +++ b/src/Test/Test/Views/Shared/Error.cshtml @@ -0,0 +1,9 @@ +@model System.Web.Mvc.HandleErrorInfo + +@{ + ViewBag.Title = "Error"; +} + +

    + Sorry, an error occurred while processing your request. +

    diff --git a/src/Test/Test/Views/Shared/_Layout.cshtml b/src/Test/Test/Views/Shared/_Layout.cshtml new file mode 100644 index 000000000..b6eb77d54 --- /dev/null +++ b/src/Test/Test/Views/Shared/_Layout.cshtml @@ -0,0 +1,33 @@ + + + + + @ViewBag.Title + + + + + +
    +
    +
    +

    My MVC Application

    +
    +
    + @Html.Partial("_LogOnPartial") +
    + +
    +
    + @RenderBody() +
    +
    +
    +
    + + diff --git a/src/Test/Test/Views/Shared/_LogOnPartial.cshtml b/src/Test/Test/Views/Shared/_LogOnPartial.cshtml new file mode 100644 index 000000000..46a854497 --- /dev/null +++ b/src/Test/Test/Views/Shared/_LogOnPartial.cshtml @@ -0,0 +1,7 @@ +@if(Request.IsAuthenticated) { + Welcome @User.Identity.Name! + [ @Html.ActionLink("Log Off", "LogOff", "Account") ] +} +else { + @:[ @Html.ActionLink("Log On", "LogOn", "Account") ] +} diff --git a/src/Test/Test/Views/Web.config b/src/Test/Test/Views/Web.config new file mode 100644 index 000000000..a4def2a3d --- /dev/null +++ b/src/Test/Test/Views/Web.config @@ -0,0 +1,58 @@ + + + + + +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Test/Test/Views/_ViewStart.cshtml b/src/Test/Test/Views/_ViewStart.cshtml new file mode 100644 index 000000000..efda124b1 --- /dev/null +++ b/src/Test/Test/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "~/Views/Shared/_Layout.cshtml"; +} \ No newline at end of file diff --git a/src/Test/Test/Web.Debug.config b/src/Test/Test/Web.Debug.config new file mode 100644 index 000000000..2c6dd51a7 --- /dev/null +++ b/src/Test/Test/Web.Debug.config @@ -0,0 +1,30 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Test/Test/Web.Release.config b/src/Test/Test/Web.Release.config new file mode 100644 index 000000000..4122d79bf --- /dev/null +++ b/src/Test/Test/Web.Release.config @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/Test/Test/Web.config b/src/Test/Test/Web.config new file mode 100644 index 000000000..b400b2c81 --- /dev/null +++ b/src/Test/Test/Web.config @@ -0,0 +1,117 @@ + + + + + + + +
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Test/Test/packages.config b/src/Test/Test/packages.config new file mode 100644 index 000000000..7ab92592f --- /dev/null +++ b/src/Test/Test/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/src/Test/packages/EntityFramework.4.1.10331.0/EntityFramework.4.1.10331.0.nupkg.REMOVED.git-id b/src/Test/packages/EntityFramework.4.1.10331.0/EntityFramework.4.1.10331.0.nupkg.REMOVED.git-id new file mode 100644 index 000000000..e80e99351 --- /dev/null +++ b/src/Test/packages/EntityFramework.4.1.10331.0/EntityFramework.4.1.10331.0.nupkg.REMOVED.git-id @@ -0,0 +1 @@ +6c2337ee82b7b6bc2dea37bb89cff832c057bef8 \ No newline at end of file diff --git a/src/Test/packages/EntityFramework.4.1.10331.0/lib/EntityFramework.dll.REMOVED.git-id b/src/Test/packages/EntityFramework.4.1.10331.0/lib/EntityFramework.dll.REMOVED.git-id new file mode 100644 index 000000000..cf7e7c6ba --- /dev/null +++ b/src/Test/packages/EntityFramework.4.1.10331.0/lib/EntityFramework.dll.REMOVED.git-id @@ -0,0 +1 @@ +cbb615d89365c6c7b290e4ad0cc2e226df9bbb61 \ No newline at end of file diff --git a/src/Test/packages/EntityFramework.4.1.10331.0/lib/EntityFramework.xml.REMOVED.git-id b/src/Test/packages/EntityFramework.4.1.10331.0/lib/EntityFramework.xml.REMOVED.git-id new file mode 100644 index 000000000..cbeb2b222 --- /dev/null +++ b/src/Test/packages/EntityFramework.4.1.10331.0/lib/EntityFramework.xml.REMOVED.git-id @@ -0,0 +1 @@ +61abdd39cc4cb11c00715a43ad2203c3e5e97415 \ No newline at end of file diff --git a/src/Test/packages/Modernizr.1.7/Content/Scripts/modernizr-1.7.js b/src/Test/packages/Modernizr.1.7/Content/Scripts/modernizr-1.7.js new file mode 100644 index 000000000..7bc212a8c --- /dev/null +++ b/src/Test/packages/Modernizr.1.7/Content/Scripts/modernizr-1.7.js @@ -0,0 +1,969 @@ +/*! +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* Modernizr v1.7 +* http://www.modernizr.com +* +* Developed by: +* - Faruk Ates http://farukat.es/ +* - Paul Irish http://paulirish.com/ +* +* Copyright (c) 2009-2011 +*/ + + +/* + * Modernizr is a script that detects native CSS3 and HTML5 features + * available in the current UA and provides an object containing all + * features with a true/false value, depending on whether the UA has + * native support for it or not. + * + * Modernizr will also add classes to the element of the page, + * one for each feature it detects. If the UA supports it, a class + * like "cssgradients" will be added. If not, the class name will be + * "no-cssgradients". This allows for simple if-conditionals in your + * CSS, giving you fine control over the look & feel of your website. + * + * @author Faruk Ates + * @author Paul Irish + * @copyright (c) 2009-2011 Faruk Ates. + * @contributor Ben Alman + */ + +window.Modernizr = (function(window,document,undefined){ + + var version = '1.7', + + ret = {}, + + /** + * !! DEPRECATED !! + * + * enableHTML5 is a private property for advanced use only. If enabled, + * it will make Modernizr.init() run through a brief while() loop in + * which it will create all HTML5 elements in the DOM to allow for + * styling them in Internet Explorer, which does not recognize any + * non-HTML4 elements unless created in the DOM this way. + * + * enableHTML5 is ON by default. + * + * The enableHTML5 toggle option is DEPRECATED as per 1.6, and will be + * replaced in 2.0 in lieu of the modular, configurable nature of 2.0. + */ + enableHTML5 = true, + + + docElement = document.documentElement, + docHead = document.head || document.getElementsByTagName('head')[0], + + /** + * Create our "modernizr" element that we do most feature tests on. + */ + mod = 'modernizr', + modElem = document.createElement( mod ), + m_style = modElem.style, + + /** + * Create the input element for various Web Forms feature tests. + */ + inputElem = document.createElement( 'input' ), + + smile = ':)', + + tostring = Object.prototype.toString, + + // List of property values to set for css tests. See ticket #21 + prefixes = ' -webkit- -moz- -o- -ms- -khtml- '.split(' '), + + // Following spec is to expose vendor-specific style properties as: + // elem.style.WebkitBorderRadius + // and the following would be incorrect: + // elem.style.webkitBorderRadius + + // Webkit ghosts their properties in lowercase but Opera & Moz do not. + // Microsoft foregoes prefixes entirely <= IE8, but appears to + // use a lowercase `ms` instead of the correct `Ms` in IE9 + + // More here: http://github.com/Modernizr/Modernizr/issues/issue/21 + domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), + + ns = {'svg': 'http://www.w3.org/2000/svg'}, + + tests = {}, + inputs = {}, + attrs = {}, + + classes = [], + + featurename, // used in testing loop + + + + // todo: consider using http://javascript.nwbox.com/CSSSupport/css-support.js instead + testMediaQuery = function(mq){ + + var st = document.createElement('style'), + div = document.createElement('div'), + ret; + + st.textContent = mq + '{#modernizr{height:3px}}'; + docHead.appendChild(st); + div.id = 'modernizr'; + docElement.appendChild(div); + + ret = div.offsetHeight === 3; + + st.parentNode.removeChild(st); + div.parentNode.removeChild(div); + + return !!ret; + + }, + + + /** + * isEventSupported determines if a given element supports the given event + * function from http://yura.thinkweb2.com/isEventSupported/ + */ + isEventSupported = (function(){ + + var TAGNAMES = { + 'select':'input','change':'input', + 'submit':'form','reset':'form', + 'error':'img','load':'img','abort':'img' + }; + + function isEventSupported(eventName, element) { + + element = element || document.createElement(TAGNAMES[eventName] || 'div'); + eventName = 'on' + eventName; + + // When using `setAttribute`, IE skips "unload", WebKit skips "unload" and "resize", whereas `in` "catches" those + var isSupported = (eventName in element); + + if (!isSupported) { + // If it has no `setAttribute` (i.e. doesn't implement Node interface), try generic element + if (!element.setAttribute) { + element = document.createElement('div'); + } + if (element.setAttribute && element.removeAttribute) { + element.setAttribute(eventName, ''); + isSupported = is(element[eventName], 'function'); + + // If property was created, "remove it" (by setting value to `undefined`) + if (!is(element[eventName], undefined)) { + element[eventName] = undefined; + } + element.removeAttribute(eventName); + } + } + + element = null; + return isSupported; + } + return isEventSupported; + })(); + + + // hasOwnProperty shim by kangax needed for Safari 2.0 support + var _hasOwnProperty = ({}).hasOwnProperty, hasOwnProperty; + if (!is(_hasOwnProperty, undefined) && !is(_hasOwnProperty.call, undefined)) { + hasOwnProperty = function (object, property) { + return _hasOwnProperty.call(object, property); + }; + } + else { + hasOwnProperty = function (object, property) { /* yes, this can give false positives/negatives, but most of the time we don't care about those */ + return ((property in object) && is(object.constructor.prototype[property], undefined)); + }; + } + + /** + * set_css applies given styles to the Modernizr DOM node. + */ + function set_css( str ) { + m_style.cssText = str; + } + + /** + * set_css_all extrapolates all vendor-specific css strings. + */ + function set_css_all( str1, str2 ) { + return set_css(prefixes.join(str1 + ';') + ( str2 || '' )); + } + + /** + * is returns a boolean for if typeof obj is exactly type. + */ + function is( obj, type ) { + return typeof obj === type; + } + + /** + * contains returns a boolean for if substr is found within str. + */ + function contains( str, substr ) { + return (''+str).indexOf( substr ) !== -1; + } + + /** + * test_props is a generic CSS / DOM property test; if a browser supports + * a certain property, it won't return undefined for it. + * A supported CSS property returns empty string when its not yet set. + */ + function test_props( props, callback ) { + for ( var i in props ) { + if ( m_style[ props[i] ] !== undefined && ( !callback || callback( props[i], modElem ) ) ) { + return true; + } + } + } + + /** + * test_props_all tests a list of DOM properties we want to check against. + * We specify literally ALL possible (known and/or likely) properties on + * the element including the non-vendor prefixed one, for forward- + * compatibility. + */ + function test_props_all( prop, callback ) { + + var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), + props = (prop + ' ' + domPrefixes.join(uc_prop + ' ') + uc_prop).split(' '); + + return !!test_props( props, callback ); + } + + + /** + * Tests + * ----- + */ + + tests['flexbox'] = function() { + /** + * set_prefixed_value_css sets the property of a specified element + * adding vendor prefixes to the VALUE of the property. + * @param {Element} element + * @param {string} property The property name. This will not be prefixed. + * @param {string} value The value of the property. This WILL be prefixed. + * @param {string=} extra Additional CSS to append unmodified to the end of + * the CSS string. + */ + function set_prefixed_value_css(element, property, value, extra) { + property += ':'; + element.style.cssText = (property + prefixes.join(value + ';' + property)).slice(0, -property.length) + (extra || ''); + } + + /** + * set_prefixed_property_css sets the property of a specified element + * adding vendor prefixes to the NAME of the property. + * @param {Element} element + * @param {string} property The property name. This WILL be prefixed. + * @param {string} value The value of the property. This will not be prefixed. + * @param {string=} extra Additional CSS to append unmodified to the end of + * the CSS string. + */ + function set_prefixed_property_css(element, property, value, extra) { + element.style.cssText = prefixes.join(property + ':' + value + ';') + (extra || ''); + } + + var c = document.createElement('div'), + elem = document.createElement('div'); + + set_prefixed_value_css(c, 'display', 'box', 'width:42px;padding:0;'); + set_prefixed_property_css(elem, 'box-flex', '1', 'width:10px;'); + + c.appendChild(elem); + docElement.appendChild(c); + + var ret = elem.offsetWidth === 42; + + c.removeChild(elem); + docElement.removeChild(c); + + return ret; + }; + + // On the S60 and BB Storm, getContext exists, but always returns undefined + // http://github.com/Modernizr/Modernizr/issues/issue/97/ + + tests['canvas'] = function() { + var elem = document.createElement( 'canvas' ); + return !!(elem.getContext && elem.getContext('2d')); + }; + + tests['canvastext'] = function() { + return !!(ret['canvas'] && is(document.createElement( 'canvas' ).getContext('2d').fillText, 'function')); + }; + + // This WebGL test false positives in FF depending on graphics hardware. But really it's quite impossible to know + // wether webgl will succeed until after you create the context. You might have hardware that can support + // a 100x100 webgl canvas, but will not support a 1000x1000 webgl canvas. So this feature inference is weak, + // but intentionally so. + tests['webgl'] = function(){ + return !!window.WebGLRenderingContext; + }; + + /* + * The Modernizr.touch test only indicates if the browser supports + * touch events, which does not necessarily reflect a touchscreen + * device, as evidenced by tablets running Windows 7 or, alas, + * the Palm Pre / WebOS (touch) phones. + * + * Additionally, Chrome (desktop) used to lie about its support on this, + * but that has since been rectified: http://crbug.com/36415 + * + * We also test for Firefox 4 Multitouch Support. + * + * For more info, see: http://modernizr.github.com/Modernizr/touch.html + */ + + tests['touch'] = function() { + + return ('ontouchstart' in window) || testMediaQuery('@media ('+prefixes.join('touch-enabled),(')+'modernizr)'); + + }; + + + /** + * geolocation tests for the new Geolocation API specification. + * This test is a standards compliant-only test; for more complete + * testing, including a Google Gears fallback, please see: + * http://code.google.com/p/geo-location-javascript/ + * or view a fallback solution using google's geo API: + * http://gist.github.com/366184 + */ + tests['geolocation'] = function() { + return !!navigator.geolocation; + }; + + // Per 1.6: + // This used to be Modernizr.crosswindowmessaging but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['postmessage'] = function() { + return !!window.postMessage; + }; + + // Web SQL database detection is tricky: + + // In chrome incognito mode, openDatabase is truthy, but using it will + // throw an exception: http://crbug.com/42380 + // We can create a dummy database, but there is no way to delete it afterwards. + + // Meanwhile, Safari users can get prompted on any database creation. + // If they do, any page with Modernizr will give them a prompt: + // http://github.com/Modernizr/Modernizr/issues/closed#issue/113 + + // We have chosen to allow the Chrome incognito false positive, so that Modernizr + // doesn't litter the web with these test databases. As a developer, you'll have + // to account for this gotcha yourself. + tests['websqldatabase'] = function() { + var result = !!window.openDatabase; + /* if (result){ + try { + result = !!openDatabase( mod + "testdb", "1.0", mod + "testdb", 2e4); + } catch(e) { + } + } */ + return result; + }; + + // Vendors have inconsistent prefixing with the experimental Indexed DB: + // - Firefox is shipping indexedDB in FF4 as moz_indexedDB + // - Webkit's implementation is accessible through webkitIndexedDB + // We test both styles. + tests['indexedDB'] = function(){ + for (var i = -1, len = domPrefixes.length; ++i < len; ){ + var prefix = domPrefixes[i].toLowerCase(); + if (window[prefix + '_indexedDB'] || window[prefix + 'IndexedDB']){ + return true; + } + } + return false; + }; + + // documentMode logic from YUI to filter out IE8 Compat Mode + // which false positives. + tests['hashchange'] = function() { + return isEventSupported('hashchange', window) && ( document.documentMode === undefined || document.documentMode > 7 ); + }; + + // Per 1.6: + // This used to be Modernizr.historymanagement but the longer + // name has been deprecated in favor of a shorter and property-matching one. + // The old API is still available in 1.6, but as of 2.0 will throw a warning, + // and in the first release thereafter disappear entirely. + tests['history'] = function() { + return !!(window.history && history.pushState); + }; + + tests['draganddrop'] = function() { + return isEventSupported('dragstart') && isEventSupported('drop'); + }; + + tests['websockets'] = function(){ + return ('WebSocket' in window); + }; + + + // http://css-tricks.com/rgba-browser-support/ + tests['rgba'] = function() { + // Set an rgba() color and check the returned value + + set_css( 'background-color:rgba(150,255,150,.5)' ); + + return contains( m_style.backgroundColor, 'rgba' ); + }; + + tests['hsla'] = function() { + // Same as rgba(), in fact, browsers re-map hsla() to rgba() internally, + // except IE9 who retains it as hsla + + set_css('background-color:hsla(120,40%,100%,.5)' ); + + return contains( m_style.backgroundColor, 'rgba' ) || contains( m_style.backgroundColor, 'hsla' ); + }; + + tests['multiplebgs'] = function() { + // Setting multiple images AND a color on the background shorthand property + // and then querying the style.background property value for the number of + // occurrences of "url(" is a reliable method for detecting ACTUAL support for this! + + set_css( 'background:url(//:),url(//:),red url(//:)' ); + + // If the UA supports multiple backgrounds, there should be three occurrences + // of the string "url(" in the return value for elem_style.background + + return new RegExp("(url\\s*\\(.*?){3}").test(m_style.background); + }; + + + // In testing support for a given CSS property, it's legit to test: + // `elem.style[styleName] !== undefined` + // If the property is supported it will return an empty string, + // if unsupported it will return undefined. + + // We'll take advantage of this quick test and skip setting a style + // on our modernizr element, but instead just testing undefined vs + // empty string. + + + tests['backgroundsize'] = function() { + return test_props_all( 'backgroundSize' ); + }; + + tests['borderimage'] = function() { + return test_props_all( 'borderImage' ); + }; + + + // Super comprehensive table about all the unique implementations of + // border-radius: http://muddledramblings.com/table-of-css3-border-radius-compliance + + tests['borderradius'] = function() { + return test_props_all( 'borderRadius', '', function( prop ) { + return contains( prop, 'orderRadius' ); + }); + }; + + // WebOS unfortunately false positives on this test. + tests['boxshadow'] = function() { + return test_props_all( 'boxShadow' ); + }; + + // FF3.0 will false positive on this test + tests['textshadow'] = function(){ + return document.createElement('div').style.textShadow === ''; + }; + + + tests['opacity'] = function() { + // Browsers that actually have CSS Opacity implemented have done so + // according to spec, which means their return values are within the + // range of [0.0,1.0] - including the leading zero. + + set_css_all( 'opacity:.55' ); + + // The non-literal . in this regex is intentional: + // German Chrome returns this value as 0,55 + // https://github.com/Modernizr/Modernizr/issues/#issue/59/comment/516632 + return /^0.55$/.test(m_style.opacity); + }; + + + tests['cssanimations'] = function() { + return test_props_all( 'animationName' ); + }; + + + tests['csscolumns'] = function() { + return test_props_all( 'columnCount' ); + }; + + + tests['cssgradients'] = function() { + /** + * For CSS Gradients syntax, please see: + * http://webkit.org/blog/175/introducing-css-gradients/ + * https://developer.mozilla.org/en/CSS/-moz-linear-gradient + * https://developer.mozilla.org/en/CSS/-moz-radial-gradient + * http://dev.w3.org/csswg/css3-images/#gradients- + */ + + var str1 = 'background-image:', + str2 = 'gradient(linear,left top,right bottom,from(#9f9),to(white));', + str3 = 'linear-gradient(left top,#9f9, white);'; + + set_css( + (str1 + prefixes.join(str2 + str1) + prefixes.join(str3 + str1)).slice(0,-str1.length) + ); + + return contains( m_style.backgroundImage, 'gradient' ); + }; + + + tests['cssreflections'] = function() { + return test_props_all( 'boxReflect' ); + }; + + + tests['csstransforms'] = function() { + return !!test_props([ 'transformProperty', 'WebkitTransform', 'MozTransform', 'OTransform', 'msTransform' ]); + }; + + + tests['csstransforms3d'] = function() { + + var ret = !!test_props([ 'perspectiveProperty', 'WebkitPerspective', 'MozPerspective', 'OPerspective', 'msPerspective' ]); + + // Webkit’s 3D transforms are passed off to the browser's own graphics renderer. + // It works fine in Safari on Leopard and Snow Leopard, but not in Chrome in + // some conditions. As a result, Webkit typically recognizes the syntax but + // will sometimes throw a false positive, thus we must do a more thorough check: + if (ret && 'webkitPerspective' in docElement.style){ + + // Webkit allows this media query to succeed only if the feature is enabled. + // `@media (transform-3d),(-o-transform-3d),(-moz-transform-3d),(-ms-transform-3d),(-webkit-transform-3d),(modernizr){ ... }` + ret = testMediaQuery('@media ('+prefixes.join('transform-3d),(')+'modernizr)'); + } + return ret; + }; + + + tests['csstransitions'] = function() { + return test_props_all( 'transitionProperty' ); + }; + + + // @font-face detection routine by Diego Perini + // http://javascript.nwbox.com/CSSSupport/ + tests['fontface'] = function(){ + + var + sheet, bool, + head = docHead || docElement, + style = document.createElement("style"), + impl = document.implementation || { hasFeature: function() { return false; } }; + + style.type = 'text/css'; + head.insertBefore(style, head.firstChild); + sheet = style.sheet || style.styleSheet; + + var supportAtRule = impl.hasFeature('CSS2', '') ? + function(rule) { + if (!(sheet && rule)) return false; + var result = false; + try { + sheet.insertRule(rule, 0); + result = (/src/i).test(sheet.cssRules[0].cssText); + sheet.deleteRule(sheet.cssRules.length - 1); + } catch(e) { } + return result; + } : + function(rule) { + if (!(sheet && rule)) return false; + sheet.cssText = rule; + + return sheet.cssText.length !== 0 && (/src/i).test(sheet.cssText) && + sheet.cssText + .replace(/\r+|\n+/g, '') + .indexOf(rule.split(' ')[0]) === 0; + }; + + bool = supportAtRule('@font-face { font-family: "font"; src: url(data:,); }'); + head.removeChild(style); + return bool; + }; + + + // These tests evaluate support of the video/audio elements, as well as + // testing what types of content they support. + // + // We're using the Boolean constructor here, so that we can extend the value + // e.g. Modernizr.video // true + // Modernizr.video.ogg // 'probably' + // + // Codec values from : http://github.com/NielsLeenheer/html5test/blob/9106a8/index.html#L845 + // thx to NielsLeenheer and zcorpan + + // Note: in FF 3.5.1 and 3.5.0, "no" was a return value instead of empty string. + // Modernizr does not normalize for that. + + tests['video'] = function() { + var elem = document.createElement('video'), + bool = !!elem.canPlayType; + + if (bool){ + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('video/ogg; codecs="theora"'); + + // Workaround required for IE9, which doesn't report video support without audio codec specified. + // bug 599718 @ msft connect + var h264 = 'video/mp4; codecs="avc1.42E01E'; + bool.h264 = elem.canPlayType(h264 + '"') || elem.canPlayType(h264 + ', mp4a.40.2"'); + + bool.webm = elem.canPlayType('video/webm; codecs="vp8, vorbis"'); + } + return bool; + }; + + tests['audio'] = function() { + var elem = document.createElement('audio'), + bool = !!elem.canPlayType; + + if (bool){ + bool = new Boolean(bool); + bool.ogg = elem.canPlayType('audio/ogg; codecs="vorbis"'); + bool.mp3 = elem.canPlayType('audio/mpeg;'); + + // Mimetypes accepted: + // https://developer.mozilla.org/En/Media_formats_supported_by_the_audio_and_video_elements + // http://bit.ly/iphoneoscodecs + bool.wav = elem.canPlayType('audio/wav; codecs="1"'); + bool.m4a = elem.canPlayType('audio/x-m4a;') || elem.canPlayType('audio/aac;'); + } + return bool; + }; + + + // Firefox has made these tests rather unfun. + + // In FF4, if disabled, window.localStorage should === null. + + // Normally, we could not test that directly and need to do a + // `('localStorage' in window) && ` test first because otherwise Firefox will + // throw http://bugzil.la/365772 if cookies are disabled + + // However, in Firefox 4 betas, if dom.storage.enabled == false, just mentioning + // the property will throw an exception. http://bugzil.la/599479 + // This looks to be fixed for FF4 Final. + + // Because we are forced to try/catch this, we'll go aggressive. + + // FWIW: IE8 Compat mode supports these features completely: + // http://www.quirksmode.org/dom/html5.html + // But IE8 doesn't support either with local files + + tests['localstorage'] = function() { + try { + return !!localStorage.getItem; + } catch(e) { + return false; + } + }; + + tests['sessionstorage'] = function() { + try { + return !!sessionStorage.getItem; + } catch(e){ + return false; + } + }; + + + tests['webWorkers'] = function () { + return !!window.Worker; + }; + + + tests['applicationcache'] = function() { + return !!window.applicationCache; + }; + + + // Thanks to Erik Dahlstrom + tests['svg'] = function(){ + return !!document.createElementNS && !!document.createElementNS(ns.svg, "svg").createSVGRect; + }; + + tests['inlinesvg'] = function() { + var div = document.createElement('div'); + div.innerHTML = ''; + return (div.firstChild && div.firstChild.namespaceURI) == ns.svg; + }; + + // Thanks to F1lt3r and lucideer + // http://github.com/Modernizr/Modernizr/issues#issue/35 + tests['smil'] = function(){ + return !!document.createElementNS && /SVG/.test(tostring.call(document.createElementNS(ns.svg,'animate'))); + }; + + tests['svgclippaths'] = function(){ + // Possibly returns a false positive in Safari 3.2? + return !!document.createElementNS && /SVG/.test(tostring.call(document.createElementNS(ns.svg,'clipPath'))); + }; + + + // input features and input types go directly onto the ret object, bypassing the tests loop. + // Hold this guy to execute in a moment. + function webforms(){ + + // Run through HTML5's new input attributes to see if the UA understands any. + // We're using f which is the element created early on + // Mike Taylr has created a comprehensive resource for testing these attributes + // when applied to all input types: + // http://miketaylr.com/code/input-type-attr.html + // spec: http://www.whatwg.org/specs/web-apps/current-work/multipage/the-input-element.html#input-type-attr-summary + ret['input'] = (function(props) { + for (var i = 0, len = props.length; i>BEGIN IEPP + // Enable HTML 5 elements for styling in IE. + // fyi: jscript version does not reflect trident version + // therefore ie9 in ie7 mode will still have a jScript v.9 + if ( enableHTML5 && window.attachEvent && (function(){ var elem = document.createElement("div"); + elem.innerHTML = ""; + return elem.childNodes.length !== 1; })()) { + // iepp v1.6.2 by @jon_neal : code.google.com/p/ie-print-protector + (function(win, doc) { + var elems = 'abbr|article|aside|audio|canvas|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video', + elemsArr = elems.split('|'), + elemsArrLen = elemsArr.length, + elemRegExp = new RegExp('(^|\\s)('+elems+')', 'gi'), + tagRegExp = new RegExp('<(\/*)('+elems+')', 'gi'), + ruleRegExp = new RegExp('(^|[^\\n]*?\\s)('+elems+')([^\\n]*)({[\\n\\w\\W]*?})', 'gi'), + docFrag = doc.createDocumentFragment(), + html = doc.documentElement, + head = html.firstChild, + bodyElem = doc.createElement('body'), + styleElem = doc.createElement('style'), + body; + function shim(doc) { + var a = -1; + while (++a < elemsArrLen) + // Use createElement so IE allows HTML5-named elements in a document + doc.createElement(elemsArr[a]); + } + function getCSS(styleSheetList, mediaType) { + var a = -1, + len = styleSheetList.length, + styleSheet, + cssTextArr = []; + while (++a < len) { + styleSheet = styleSheetList[a]; + // Get css from all non-screen stylesheets and their imports + if ((mediaType = styleSheet.media || mediaType) != 'screen') cssTextArr.push(getCSS(styleSheet.imports, mediaType), styleSheet.cssText); + } + return cssTextArr.join(''); + } + // Shim the document and iepp fragment + shim(doc); + shim(docFrag); + // Add iepp custom print style element + head.insertBefore(styleElem, head.firstChild); + styleElem.media = 'print'; + win.attachEvent( + 'onbeforeprint', + function() { + var a = -1, + cssText = getCSS(doc.styleSheets, 'all'), + cssTextArr = [], + rule; + body = body || doc.body; + // Get only rules which reference HTML5 elements by name + while ((rule = ruleRegExp.exec(cssText)) != null) + // Replace all html5 element references with iepp substitute classnames + cssTextArr.push((rule[1]+rule[2]+rule[3]).replace(elemRegExp, '$1.iepp_$2')+rule[4]); + // Write iepp custom print CSS + styleElem.styleSheet.cssText = cssTextArr.join('\n'); + while (++a < elemsArrLen) { + var nodeList = doc.getElementsByTagName(elemsArr[a]), + nodeListLen = nodeList.length, + b = -1; + while (++b < nodeListLen) + if (nodeList[b].className.indexOf('iepp_') < 0) + // Append iepp substitute classnames to all html5 elements + nodeList[b].className += ' iepp_'+elemsArr[a]; + } + docFrag.appendChild(body); + html.appendChild(bodyElem); + // Write iepp substitute print-safe document + bodyElem.className = body.className; + // Replace HTML5 elements with which is print-safe and shouldn't conflict since it isn't part of html5 + bodyElem.innerHTML = body.innerHTML.replace(tagRegExp, '<$1font'); + } + ); + win.attachEvent( + 'onafterprint', + function() { + // Undo everything done in onbeforeprint + bodyElem.innerHTML = ''; + html.removeChild(bodyElem); + html.appendChild(body); + styleElem.styleSheet.cssText = ''; + } + ); + })(window, document); + } + //>>END IEPP + + // Assign private properties to the return object with prefix + ret._enableHTML5 = enableHTML5; + ret._version = version; + + // Remove "no-js" class from element, if it exists: + docElement.className = docElement.className.replace(/\bno-js\b/,'') + + ' js ' + + // Add the new classes to the element. + + classes.join( ' ' ); + + return ret; + +})(this,this.document); \ No newline at end of file diff --git a/src/Test/packages/Modernizr.1.7/Content/Scripts/modernizr-1.7.min.js b/src/Test/packages/Modernizr.1.7/Content/Scripts/modernizr-1.7.min.js new file mode 100644 index 000000000..4b4fcc1e5 --- /dev/null +++ b/src/Test/packages/Modernizr.1.7/Content/Scripts/modernizr-1.7.min.js @@ -0,0 +1,10 @@ +/*! +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +*/ +// Modernizr v1.7 www.modernizr.com +window.Modernizr=function(a,b,c){function G(){e.input=function(a){for(var b=0,c=a.length;b7)},r.history=function(){return !!(a.history&&history.pushState)},r.draganddrop=function(){return x("dragstart")&&x("drop")},r.websockets=function(){return"WebSocket"in a},r.rgba=function(){A("background-color:rgba(150,255,150,.5)");return D(k.backgroundColor,"rgba")},r.hsla=function(){A("background-color:hsla(120,40%,100%,.5)");return D(k.backgroundColor,"rgba")||D(k.backgroundColor,"hsla")},r.multiplebgs=function(){A("background:url(//:),url(//:),red url(//:)");return(new RegExp("(url\\s*\\(.*?){3}")).test(k.background)},r.backgroundsize=function(){return F("backgroundSize")},r.borderimage=function(){return F("borderImage")},r.borderradius=function(){return F("borderRadius","",function(a){return D(a,"orderRadius")})},r.boxshadow=function(){return F("boxShadow")},r.textshadow=function(){return b.createElement("div").style.textShadow===""},r.opacity=function(){B("opacity:.55");return/^0.55$/.test(k.opacity)},r.cssanimations=function(){return F("animationName")},r.csscolumns=function(){return F("columnCount")},r.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";A((a+o.join(b+a)+o.join(c+a)).slice(0,-a.length));return D(k.backgroundImage,"gradient")},r.cssreflections=function(){return F("boxReflect")},r.csstransforms=function(){return!!E(["transformProperty","WebkitTransform","MozTransform","OTransform","msTransform"])},r.csstransforms3d=function(){var a=!!E(["perspectiveProperty","WebkitPerspective","MozPerspective","OPerspective","msPerspective"]);a&&"webkitPerspective"in g.style&&(a=w("@media ("+o.join("transform-3d),(")+"modernizr)"));return a},r.csstransitions=function(){return F("transitionProperty")},r.fontface=function(){var a,c,d=h||g,e=b.createElement("style"),f=b.implementation||{hasFeature:function(){return!1}};e.type="text/css",d.insertBefore(e,d.firstChild),a=e.sheet||e.styleSheet;var i=f.hasFeature("CSS2","")?function(b){if(!a||!b)return!1;var c=!1;try{a.insertRule(b,0),c=/src/i.test(a.cssRules[0].cssText),a.deleteRule(a.cssRules.length-1)}catch(d){}return c}:function(b){if(!a||!b)return!1;a.cssText=b;return a.cssText.length!==0&&/src/i.test(a.cssText)&&a.cssText.replace(/\r+|\n+/g,"").indexOf(b.split(" ")[0])===0};c=i('@font-face { font-family: "font"; src: url(data:,); }'),d.removeChild(e);return c},r.video=function(){var a=b.createElement("video"),c=!!a.canPlayType;if(c){c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"');var d='video/mp4; codecs="avc1.42E01E';c.h264=a.canPlayType(d+'"')||a.canPlayType(d+', mp4a.40.2"'),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"')}return c},r.audio=function(){var a=b.createElement("audio"),c=!!a.canPlayType;c&&(c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"'),c.mp3=a.canPlayType("audio/mpeg;"),c.wav=a.canPlayType('audio/wav; codecs="1"'),c.m4a=a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;"));return c},r.localstorage=function(){try{return!!localStorage.getItem}catch(a){return!1}},r.sessionstorage=function(){try{return!!sessionStorage.getItem}catch(a){return!1}},r.webWorkers=function(){return!!a.Worker},r.applicationcache=function(){return!!a.applicationCache},r.svg=function(){return!!b.createElementNS&&!!b.createElementNS(q.svg,"svg").createSVGRect},r.inlinesvg=function(){var a=b.createElement("div");a.innerHTML="";return(a.firstChild&&a.firstChild.namespaceURI)==q.svg},r.smil=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"animate")))},r.svgclippaths=function(){return!!b.createElementNS&&/SVG/.test(n.call(b.createElementNS(q.svg,"clipPath")))};for(var H in r)z(r,H)&&(v=H.toLowerCase(),e[v]=r[H](),u.push((e[v]?"":"no-")+v));e.input||G(),e.crosswindowmessaging=e.postmessage,e.historymanagement=e.history,e.addTest=function(a,b){a=a.toLowerCase();if(!e[a]){b=!!b(),g.className+=" "+(b?"":"no-")+a,e[a]=b;return e}},A(""),j=l=null,f&&a.attachEvent&&function(){var a=b.createElement("div");a.innerHTML="";return a.childNodes.length!==1}()&&function(a,b){function p(a,b){var c=-1,d=a.length,e,f=[];while(++c + /// Validates the selected form. This method sets up event handlers for submit, focus, + /// keyup, blur and click to trigger validation of the entire form or individual + /// elements. Each one can be disabled, see the onxxx options (onsubmit, onfocusout, + /// onkeyup, onclick). focusInvalid focuses elements when submitting a invalid form. + /// + /// + /// A set of key/value pairs that configure the validate. All options are optional. + /// + /// + + // if nothing is selected, return nothing; can't chain anyway + if (!this.length) { + options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" ); + return; + } + + // check if a validator for this form was already created + var validator = $.data(this[0], 'validator'); + if ( validator ) { + return validator; + } + + validator = new $.validator( options, this[0] ); + $.data(this[0], 'validator', validator); + + if ( validator.settings.onsubmit ) { + + // allow suppresing validation by adding a cancel class to the submit button + this.find("input, button").filter(".cancel").click(function() { + validator.cancelSubmit = true; + }); + + // when a submitHandler is used, capture the submitting button + if (validator.settings.submitHandler) { + this.find("input, button").filter(":submit").click(function() { + validator.submitButton = this; + }); + } + + // validate the form on submit + this.submit( function( event ) { + if ( validator.settings.debug ) + // prevent form submit to be able to see console output + event.preventDefault(); + + function handle() { + if ( validator.settings.submitHandler ) { + if (validator.submitButton) { + // insert a hidden input as a replacement for the missing submit button + var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); + } + validator.settings.submitHandler.call( validator, validator.currentForm ); + if (validator.submitButton) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://docs.jquery.com/Plugins/Validation/valid + valid: function() { + /// + /// Checks if the selected form is valid or if all selected elements are valid. + /// validate() needs to be called on the form before checking it using this method. + /// + /// + + if ( $(this[0]).is('form')) { + return this.validate().form(); + } else { + var valid = true; + var validator = $(this[0].form).validate(); + this.each(function() { + valid &= validator.element(this); + }); + return valid; + } + }, + // attributes: space seperated list of attributes to retrieve and remove + removeAttrs: function(attributes) { + /// + /// Remove the specified attributes from the first matched element and return them. + /// + /// + /// A space-seperated list of attribute names to remove. + /// + /// + + var result = {}, + $element = this; + $.each(attributes.split(/\s/), function(index, value) { + result[value] = $element.attr(value); + $element.removeAttr(value); + }); + return result; + }, + // http://docs.jquery.com/Plugins/Validation/rules + rules: function(command, argument) { + /// + /// Return the validations rules for the first selected element. + /// + /// + /// Can be either "add" or "remove". + /// + /// + /// A list of rules to add or remove. + /// + /// + + var element = this[0]; + + if (command) { + var settings = $.data(element.form, 'validator').settings; + var staticRules = settings.rules; + var existingRules = $.validator.staticRules(element); + switch(command) { + case "add": + $.extend(existingRules, $.validator.normalizeRule(argument)); + staticRules[element.name] = existingRules; + if (argument.messages) + settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); + break; + case "remove": + if (!argument) { + delete staticRules[element.name]; + return existingRules; + } + var filtered = {}; + $.each(argument.split(/\s/), function(index, method) { + filtered[method] = existingRules[method]; + delete existingRules[method]; + }); + return filtered; + } + } + + var data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.metadataRules(element), + $.validator.classRules(element), + $.validator.attributeRules(element), + $.validator.staticRules(element) + ), element); + + // make sure required is at front + if (data.required) { + var param = data.required; + delete data.required; + data = $.extend({required: param}, data); + } + + return data; + } +}); + +// Custom selectors +$.extend($.expr[":"], { + // http://docs.jquery.com/Plugins/Validation/blank + blank: function(a) {return !$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/filled + filled: function(a) {return !!$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/unchecked + unchecked: function(a) {return !a.checked;} +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +$.validator.format = function(source, params) { + /// + /// Replaces {n} placeholders with arguments. + /// One or more arguments can be passed, in addition to the string template itself, to insert + /// into the string. + /// + /// + /// The string to format. + /// + /// + /// The first argument to insert, or an array of Strings to insert + /// + /// + + if ( arguments.length == 1 ) + return function() { + var args = $.makeArray(arguments); + args.unshift(source); + return $.validator.format.apply( this, args ); + }; + if ( arguments.length > 2 && params.constructor != Array ) { + params = $.makeArray(arguments).slice(1); + } + if ( params.constructor != Array ) { + params = [ params ]; + } + $.each(params, function(i, n) { + source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); + }); + return source; +}; + +$.extend($.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: [], + ignoreTitle: false, + onfocusin: function(element) { + this.lastActive = element; + + // hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { + this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + this.addWrapper(this.errorsFor(element)).hide(); + } + }, + onfocusout: function(element) { + if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { + this.element(element); + } + }, + onkeyup: function(element) { + if ( element.name in this.submitted || element == this.lastElement ) { + this.element(element); + } + }, + onclick: function(element) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) + this.element(element); + // or option elements, check parent select in that case + else if (element.parentNode.name in this.submitted) + this.element(element.parentNode); + }, + highlight: function( element, errorClass, validClass ) { + $(element).addClass(errorClass).removeClass(validClass); + }, + unhighlight: function( element, errorClass, validClass ) { + $(element).removeClass(errorClass).addClass(validClass); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults + setDefaults: function(settings) { + /// + /// Modify default settings for validation. + /// Accepts everything that Plugins/Validation/validate accepts. + /// + /// + /// Options to set as default. + /// + /// + + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + accept: "Please enter a value with a valid extension.", + maxlength: $.validator.format("Please enter no more than {0} characters."), + minlength: $.validator.format("Please enter at least {0} characters."), + rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), + range: $.validator.format("Please enter a value between {0} and {1}."), + max: $.validator.format("Please enter a value less than or equal to {0}."), + min: $.validator.format("Please enter a value greater than or equal to {0}.") + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $(this.settings.errorLabelContainer); + this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); + this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = (this.groups = {}); + $.each(this.settings.groups, function(key, value) { + $.each(value.split(/\s/), function(index, name) { + groups[name] = key; + }); + }); + var rules = this.settings.rules; + $.each(rules, function(key, value) { + rules[key] = $.validator.normalizeRule(value); + }); + + function delegate(event) { + var validator = $.data(this[0].form, "validator"), + eventType = "on" + event.type.replace(/^validate/, ""); + validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] ); + } + $(this.currentForm) + .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate) + .validateDelegate(":radio, :checkbox, select, option", "click", delegate); + + if (this.settings.invalidHandler) + $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/form + form: function() { + /// + /// Validates the form, returns true if it is valid, false otherwise. + /// This behaves as a normal submit event, but returns the result. + /// + /// + + this.checkForm(); + $.extend(this.submitted, this.errorMap); + this.invalid = $.extend({}, this.errorMap); + if (!this.valid()) + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { + this.check( elements[i] ); + } + return this.valid(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/element + element: function( element ) { + /// + /// Validates a single element, returns true if it is valid, false otherwise. + /// This behaves as validation on blur or keyup, but returns the result. + /// + /// + /// An element to validate, must be inside the validated form. + /// + /// + + element = this.clean( element ); + this.lastElement = element; + this.prepareElement( element ); + this.currentElements = $(element); + var result = this.check( element ); + if ( result ) { + delete this.invalid[element.name]; + } else { + this.invalid[element.name] = true; + } + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/showErrors + showErrors: function(errors) { + /// + /// Show the specified messages. + /// Keys have to refer to the names of elements, values are displayed for those elements, using the configured error placement. + /// + /// + /// One or more key/value pairs of input names and messages. + /// + /// + + if(errors) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[name], + element: this.findByName(name)[0] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function(element) { + return !(element.name in errors); + }); + } + this.settings.showErrors + ? this.settings.showErrors.call( this, this.errorMap, this.errorList ) + : this.defaultShowErrors(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/resetForm + resetForm: function() { + /// + /// Resets the controlled form. + /// Resets input fields to their original value (requires form plugin), removes classes + /// indicating invalid elements and hides error messages. + /// + /// + + if ( $.fn.resetForm ) + $( this.currentForm ).resetForm(); + this.submitted = {}; + this.prepareForm(); + this.hideErrors(); + this.elements().removeClass( this.settings.errorClass ); + }, + + numberOfInvalids: function() { + /// + /// Returns the number of invalid fields. + /// This depends on the internal validator state. It covers all fields only after + /// validating the complete form (on submit or via $("form").valid()). After validating + /// a single element, only that element is counted. Most useful in combination with the + /// invalidHandler-option. + /// + /// + + return this.objectLength(this.invalid); + }, + + objectLength: function( obj ) { + var count = 0; + for ( var i in obj ) + count++; + return count; + }, + + hideErrors: function() { + this.addWrapper( this.toHide ).hide(); + }, + + valid: function() { + return this.size() == 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if( this.settings.focusInvalid ) { + try { + $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) + .filter(":visible") + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger("focusin"); + } catch(e) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep(this.errorList, function(n) { + return n.element.name == lastActive.name; + }).length == 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved + return $([]).add(this.currentForm.elements) + .filter(":input") + .not(":submit, :reset, :image, [disabled]") + .not( this.settings.ignore ) + .filter(function() { + !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this); + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) + return false; + + rulesCache[this.name] = true; + return true; + }); + }, + + clean: function( selector ) { + return $( selector )[0]; + }, + + errors: function() { + return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext ); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $([]); + this.toHide = $([]); + this.currentElements = $([]); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor(element); + }, + + check: function( element ) { + element = this.clean( element ); + + // if radio/checkbox, validate first element in group instead + if (this.checkable(element)) { + element = this.findByName(element.name).not(this.settings.ignore)[0]; + } + + var rules = $(element).rules(); + var dependencyMismatch = false; + for (var method in rules) { + var rule = { method: method, parameters: rules[method] }; + try { + var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result == "dependency-mismatch" ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result == "pending" ) { + this.toHide = this.toHide.not( this.errorsFor(element) ); + return; + } + + if( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch(e) { + this.settings.debug && window.console && console.log("exception occured when checking element " + element.id + + ", check the '" + rule.method + "' method", e); + throw e; + } + } + if (dependencyMismatch) + return; + if ( this.objectLength(rules) ) + this.successList.push(element); + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's "messages" metadata + customMetaMessage: function(element, method) { + if (!$.metadata) + return; + + var meta = this.settings.meta + ? $(element).metadata()[this.settings.meta] + : $(element).metadata(); + + return meta && meta.messages && meta.messages[method]; + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[name]; + return m && (m.constructor == String + ? m + : m[method]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for(var i = 0; i < arguments.length; i++) { + if (arguments[i] !== undefined) + return arguments[i]; + } + return undefined; + }, + + defaultMessage: function( element, method) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customMetaMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[method], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message == "function" ) { + message = message.call(this, rule.parameters, element); + } else if (theregex.test(message)) { + message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters); + } + this.errorList.push({ + message: message, + element: element + }); + + this.errorMap[element.name] = message; + this.submitted[element.name] = message; + }, + + addWrapper: function(toToggle) { + if ( this.settings.wrapper ) + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + return toToggle; + }, + + defaultShowErrors: function() { + for ( var i = 0; this.errorList[i]; i++ ) { + var error = this.errorList[i]; + this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + this.showLabel( error.element, error.message ); + } + if( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if (this.settings.success) { + for ( var i = 0; this.successList[i]; i++ ) { + this.showLabel( this.successList[i] ); + } + } + if (this.settings.unhighlight) { + for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) { + this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not(this.invalidElements()); + }, + + invalidElements: function() { + return $(this.errorList).map(function() { + return this.element; + }); + }, + + showLabel: function(element, message) { + var label = this.errorsFor( element ); + if ( label.length ) { + // refresh error/success class + label.removeClass().addClass( this.settings.errorClass ); + + // check if we have a generated label, replace the message then + label.attr("generated") && label.html(message); + } else { + // create label + label = $("<" + this.settings.errorElement + "/>") + .attr({"for": this.idOrName(element), generated: true}) + .addClass(this.settings.errorClass) + .html(message || ""); + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); + } + if ( !this.labelContainer.append(label).length ) + this.settings.errorPlacement + ? this.settings.errorPlacement(label, $(element) ) + : label.insertAfter(element); + } + if ( !message && this.settings.success ) { + label.text(""); + typeof this.settings.success == "string" + ? label.addClass( this.settings.success ) + : this.settings.success( label ); + } + this.toShow = this.toShow.add(label); + }, + + errorsFor: function(element) { + var name = this.idOrName(element); + return this.errors().filter(function() { + return $(this).attr('for') == name; + }); + }, + + idOrName: function(element) { + return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); + }, + + checkable: function( element ) { + return /radio|checkbox/i.test(element.type); + }, + + findByName: function( name ) { + // select by name and filter by form for performance over form.find("[name=...]") + var form = this.currentForm; + return $(document.getElementsByName(name)).map(function(index, element) { + return element.form == form && element.name == name && element || null; + }); + }, + + getLength: function(value, element) { + switch( element.nodeName.toLowerCase() ) { + case 'select': + return $("option:selected", element).length; + case 'input': + if( this.checkable( element) ) + return this.findByName(element.name).filter(':checked').length; + } + return value.length; + }, + + depend: function(param, element) { + return this.dependTypes[typeof param] + ? this.dependTypes[typeof param](param, element) + : true; + }, + + dependTypes: { + "boolean": function(param, element) { + return param; + }, + "string": function(param, element) { + return !!$(param, element.form).length; + }, + "function": function(param, element) { + return param(element); + } + }, + + optional: function(element) { + return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch"; + }, + + startRequest: function(element) { + if (!this.pending[element.name]) { + this.pendingRequest++; + this.pending[element.name] = true; + } + }, + + stopRequest: function(element, valid) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if (this.pendingRequest < 0) + this.pendingRequest = 0; + delete this.pending[element.name]; + if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) { + $(this.currentForm).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.formSubmitted = false; + } + }, + + previousValue: function(element) { + return $.data(element, "previousValue") || $.data(element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: {required: true}, + email: {email: true}, + url: {url: true}, + date: {date: true}, + dateISO: {dateISO: true}, + dateDE: {dateDE: true}, + number: {number: true}, + numberDE: {numberDE: true}, + digits: {digits: true}, + creditcard: {creditcard: true} + }, + + addClassRules: function(className, rules) { + /// + /// Add a compound class method - useful to refactor common combinations of rules into a single + /// class. + /// + /// + /// The name of the class rule to add + /// + /// + /// The compound rules + /// + /// + + className.constructor == String ? + this.classRuleSettings[className] = rules : + $.extend(this.classRuleSettings, className); + }, + + classRules: function(element) { + var rules = {}; + var classes = $(element).attr('class'); + classes && $.each(classes.split(' '), function() { + if (this in $.validator.classRuleSettings) { + $.extend(rules, $.validator.classRuleSettings[this]); + } + }); + return rules; + }, + + attributeRules: function(element) { + var rules = {}; + var $element = $(element); + + for (var method in $.validator.methods) { + var value = $element.attr(method); + if (value) { + rules[method] = value; + } + } + + // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs + if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) { + delete rules.maxlength; + } + + return rules; + }, + + metadataRules: function(element) { + if (!$.metadata) return {}; + + var meta = $.data(element.form, 'validator').settings.meta; + return meta ? + $(element).metadata()[meta] : + $(element).metadata(); + }, + + staticRules: function(element) { + var rules = {}; + var validator = $.data(element.form, 'validator'); + if (validator.settings.rules) { + rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; + } + return rules; + }, + + normalizeRules: function(rules, element) { + // handle dependency check + $.each(rules, function(prop, val) { + // ignore rule when param is explicitly false, eg. required:false + if (val === false) { + delete rules[prop]; + return; + } + if (val.param || val.depends) { + var keepRule = true; + switch (typeof val.depends) { + case "string": + keepRule = !!$(val.depends, element.form).length; + break; + case "function": + keepRule = val.depends.call(element, element); + break; + } + if (keepRule) { + rules[prop] = val.param !== undefined ? val.param : true; + } else { + delete rules[prop]; + } + } + }); + + // evaluate parameters + $.each(rules, function(rule, parameter) { + rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; + }); + + // clean number parameters + $.each(['minlength', 'maxlength', 'min', 'max'], function() { + if (rules[this]) { + rules[this] = Number(rules[this]); + } + }); + $.each(['rangelength', 'range'], function() { + if (rules[this]) { + rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; + } + }); + + if ($.validator.autoCreateRanges) { + // auto-create ranges + if (rules.min && rules.max) { + rules.range = [rules.min, rules.max]; + delete rules.min; + delete rules.max; + } + if (rules.minlength && rules.maxlength) { + rules.rangelength = [rules.minlength, rules.maxlength]; + delete rules.minlength; + delete rules.maxlength; + } + } + + // To support custom messages in metadata ignore rule methods titled "messages" + if (rules.messages) { + delete rules.messages; + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function(data) { + if( typeof data == "string" ) { + var transformed = {}; + $.each(data.split(/\s/), function() { + transformed[this] = true; + }); + data = transformed; + } + return data; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/addMethod + addMethod: function(name, method, message) { + /// + /// Add a custom validation method. It must consist of a name (must be a legal javascript + /// identifier), a javascript based function and a default string message. + /// + /// + /// The name of the method, used to identify and referencing it, must be a valid javascript + /// identifier + /// + /// + /// The actual method implementation, returning true if an element is valid + /// + /// + /// (Optional) The default message to display for this method. Can be a function created by + /// jQuery.validator.format(value). When undefined, an already existing message is used + /// (handy for localization), otherwise the field-specific messages have to be defined. + /// + /// + + $.validator.methods[name] = method; + $.validator.messages[name] = message != undefined ? message : $.validator.messages[name]; + if (method.length < 3) { + $.validator.addClassRules(name, $.validator.normalizeRule(name)); + } + }, + + methods: { + + // http://docs.jquery.com/Plugins/Validation/Methods/required + required: function(value, element, param) { + // check if dependency is met + if ( !this.depend(param, element) ) + return "dependency-mismatch"; + switch( element.nodeName.toLowerCase() ) { + case 'select': + // could be an array for select-multiple or a string, both are fine this way + var val = $(element).val(); + return val && val.length > 0; + case 'input': + if ( this.checkable(element) ) + return this.getLength(value, element) > 0; + default: + return $.trim(value).length > 0; + } + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/remote + remote: function(value, element, param) { + if ( this.optional(element) ) + return "dependency-mismatch"; + + var previous = this.previousValue(element); + if (!this.settings.messages[element.name] ) + this.settings.messages[element.name] = {}; + previous.originalMessage = this.settings.messages[element.name].remote; + this.settings.messages[element.name].remote = previous.message; + + param = typeof param == "string" && {url:param} || param; + + if ( this.pending[element.name] ) { + return "pending"; + } + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + var validator = this; + this.startRequest(element); + var data = {}; + data[element.name] = value; + $.ajax($.extend(true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + success: function(response) { + validator.settings.messages[element.name].remote = previous.originalMessage; + var valid = response === true; + if ( valid ) { + var submitted = validator.formSubmitted; + validator.prepareElement(element); + validator.formSubmitted = submitted; + validator.successList.push(element); + validator.showErrors(); + } else { + var errors = {}; + var message = response || validator.defaultMessage(element, "remote"); + errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; + validator.showErrors(errors); + } + previous.valid = valid; + validator.stopRequest(element, valid); + } + }, param)); + return "pending"; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/minlength + minlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/maxlength + maxlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/rangelength + rangelength: function(value, element, param) { + var length = this.getLength($.trim(value), element); + return this.optional(element) || ( length >= param[0] && length <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/min + min: function( value, element, param ) { + return this.optional(element) || value >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/max + max: function( value, element, param ) { + return this.optional(element) || value <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/range + range: function( value, element, param ) { + return this.optional(element) || ( value >= param[0] && value <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/email + email: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ + return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/url + url: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/date + date: function(value, element) { + return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/dateISO + dateISO: function(value, element) { + return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/number + number: function(value, element) { + return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/digits + digits: function(value, element) { + return this.optional(element) || /^\d+$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/creditcard + // based on http://en.wikipedia.org/wiki/Luhn + creditcard: function(value, element) { + if ( this.optional(element) ) + return "dependency-mismatch"; + // accept only digits and dashes + if (/[^0-9-]+/.test(value)) + return false; + var nCheck = 0, + nDigit = 0, + bEven = false; + + value = value.replace(/\D/g, ""); + + for (var n = value.length - 1; n >= 0; n--) { + var cDigit = value.charAt(n); + var nDigit = parseInt(cDigit, 10); + if (bEven) { + if ((nDigit *= 2) > 9) + nDigit -= 9; + } + nCheck += nDigit; + bEven = !bEven; + } + + return (nCheck % 10) == 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/accept + accept: function(value, element, param) { + param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; + return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/equalTo + equalTo: function(value, element, param) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { + $(element).valid(); + }); + return value == target.val(); + } + + } + +}); + +// deprecated, use $.validator.format instead +$.format = $.validator.format; + +})(jQuery); + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() +;(function($) { + var pendingRequests = {}; + // Use a prefilter if available (1.5+) + if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function(settings, _, xhr) { + var port = settings.port; + if (settings.mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } pendingRequests[port] = xhr; + } + }); + } else { + // Proxy ajax + var ajax = $.ajax; + $.ajax = function(settings) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if (mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + + return (pendingRequests[port] = ajax.apply(this, arguments)); + } + return ajax.apply(this, arguments); + }; + } +})(jQuery); + +// provides cross-browser focusin and focusout events +// IE has native support, in other browsers, use event caputuring (neither bubbles) + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target +;(function($) { + // only implement if not provided by jQuery core (since 1.4) + // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs + if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) { + $.each({ + focus: 'focusin', + blur: 'focusout' + }, function( original, fix ){ + $.event.special[fix] = { + setup:function() { + this.addEventListener( original, handler, true ); + }, + teardown:function() { + this.removeEventListener( original, handler, true ); + }, + handler: function(e) { + arguments[0] = $.event.fix(e); + arguments[0].type = fix; + return $.event.handle.apply(this, arguments); + } + }; + function handler(e) { + e = $.event.fix(e); + e.type = fix; + return $.event.handle.call(this, e); + } + }); + }; + $.extend($.fn, { + validateDelegate: function(delegate, type, handler) { + return this.bind(type, function(event) { + var target = $(event.target); + if (target.is(delegate)) { + return handler.apply(target, arguments); + } + }); + } + }); +})(jQuery); diff --git a/src/Test/packages/jQuery.Validation.1.8.0/Content/Scripts/jquery.validate.js b/src/Test/packages/jQuery.Validation.1.8.0/Content/Scripts/jquery.validate.js new file mode 100644 index 000000000..4ba1ad970 --- /dev/null +++ b/src/Test/packages/jQuery.Validation.1.8.0/Content/Scripts/jquery.validate.js @@ -0,0 +1,1162 @@ +/** +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* jQuery Validation Plugin 1.8.0 +* +* http://bassistance.de/jquery-plugins/jquery-plugin-validation/ +* http://docs.jquery.com/Plugins/Validation +* +* Copyright (c) 2006 - 2011 Jörn Zaefferer +*/ + +(function($) { + +$.extend($.fn, { + // http://docs.jquery.com/Plugins/Validation/validate + validate: function( options ) { + + // if nothing is selected, return nothing; can't chain anyway + if (!this.length) { + options && options.debug && window.console && console.warn( "nothing selected, can't validate, returning nothing" ); + return; + } + + // check if a validator for this form was already created + var validator = $.data(this[0], 'validator'); + if ( validator ) { + return validator; + } + + validator = new $.validator( options, this[0] ); + $.data(this[0], 'validator', validator); + + if ( validator.settings.onsubmit ) { + + // allow suppresing validation by adding a cancel class to the submit button + this.find("input, button").filter(".cancel").click(function() { + validator.cancelSubmit = true; + }); + + // when a submitHandler is used, capture the submitting button + if (validator.settings.submitHandler) { + this.find("input, button").filter(":submit").click(function() { + validator.submitButton = this; + }); + } + + // validate the form on submit + this.submit( function( event ) { + if ( validator.settings.debug ) + // prevent form submit to be able to see console output + event.preventDefault(); + + function handle() { + if ( validator.settings.submitHandler ) { + if (validator.submitButton) { + // insert a hidden input as a replacement for the missing submit button + var hidden = $("").attr("name", validator.submitButton.name).val(validator.submitButton.value).appendTo(validator.currentForm); + } + validator.settings.submitHandler.call( validator, validator.currentForm ); + if (validator.submitButton) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + return false; + } + return true; + } + + // prevent submit for invalid forms or custom submit handlers + if ( validator.cancelSubmit ) { + validator.cancelSubmit = false; + return handle(); + } + if ( validator.form() ) { + if ( validator.pendingRequest ) { + validator.formSubmitted = true; + return false; + } + return handle(); + } else { + validator.focusInvalid(); + return false; + } + }); + } + + return validator; + }, + // http://docs.jquery.com/Plugins/Validation/valid + valid: function() { + if ( $(this[0]).is('form')) { + return this.validate().form(); + } else { + var valid = true; + var validator = $(this[0].form).validate(); + this.each(function() { + valid &= validator.element(this); + }); + return valid; + } + }, + // attributes: space seperated list of attributes to retrieve and remove + removeAttrs: function(attributes) { + var result = {}, + $element = this; + $.each(attributes.split(/\s/), function(index, value) { + result[value] = $element.attr(value); + $element.removeAttr(value); + }); + return result; + }, + // http://docs.jquery.com/Plugins/Validation/rules + rules: function(command, argument) { + var element = this[0]; + + if (command) { + var settings = $.data(element.form, 'validator').settings; + var staticRules = settings.rules; + var existingRules = $.validator.staticRules(element); + switch(command) { + case "add": + $.extend(existingRules, $.validator.normalizeRule(argument)); + staticRules[element.name] = existingRules; + if (argument.messages) + settings.messages[element.name] = $.extend( settings.messages[element.name], argument.messages ); + break; + case "remove": + if (!argument) { + delete staticRules[element.name]; + return existingRules; + } + var filtered = {}; + $.each(argument.split(/\s/), function(index, method) { + filtered[method] = existingRules[method]; + delete existingRules[method]; + }); + return filtered; + } + } + + var data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.metadataRules(element), + $.validator.classRules(element), + $.validator.attributeRules(element), + $.validator.staticRules(element) + ), element); + + // make sure required is at front + if (data.required) { + var param = data.required; + delete data.required; + data = $.extend({required: param}, data); + } + + return data; + } +}); + +// Custom selectors +$.extend($.expr[":"], { + // http://docs.jquery.com/Plugins/Validation/blank + blank: function(a) {return !$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/filled + filled: function(a) {return !!$.trim("" + a.value);}, + // http://docs.jquery.com/Plugins/Validation/unchecked + unchecked: function(a) {return !a.checked;} +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +$.validator.format = function(source, params) { + if ( arguments.length == 1 ) + return function() { + var args = $.makeArray(arguments); + args.unshift(source); + return $.validator.format.apply( this, args ); + }; + if ( arguments.length > 2 && params.constructor != Array ) { + params = $.makeArray(arguments).slice(1); + } + if ( params.constructor != Array ) { + params = [ params ]; + } + $.each(params, function(i, n) { + source = source.replace(new RegExp("\\{" + i + "\\}", "g"), n); + }); + return source; +}; + +$.extend($.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: [], + ignoreTitle: false, + onfocusin: function(element) { + this.lastActive = element; + + // hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup && !this.blockFocusCleanup ) { + this.settings.unhighlight && this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + this.addWrapper(this.errorsFor(element)).hide(); + } + }, + onfocusout: function(element) { + if ( !this.checkable(element) && (element.name in this.submitted || !this.optional(element)) ) { + this.element(element); + } + }, + onkeyup: function(element) { + if ( element.name in this.submitted || element == this.lastElement ) { + this.element(element); + } + }, + onclick: function(element) { + // click on selects, radiobuttons and checkboxes + if ( element.name in this.submitted ) + this.element(element); + // or option elements, check parent select in that case + else if (element.parentNode.name in this.submitted) + this.element(element.parentNode); + }, + highlight: function( element, errorClass, validClass ) { + $(element).addClass(errorClass).removeClass(validClass); + }, + unhighlight: function( element, errorClass, validClass ) { + $(element).removeClass(errorClass).addClass(validClass); + } + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/setDefaults + setDefaults: function(settings) { + $.extend( $.validator.defaults, settings ); + }, + + messages: { + required: "This field is required.", + remote: "Please fix this field.", + email: "Please enter a valid email address.", + url: "Please enter a valid URL.", + date: "Please enter a valid date.", + dateISO: "Please enter a valid date (ISO).", + number: "Please enter a valid number.", + digits: "Please enter only digits.", + creditcard: "Please enter a valid credit card number.", + equalTo: "Please enter the same value again.", + accept: "Please enter a value with a valid extension.", + maxlength: $.validator.format("Please enter no more than {0} characters."), + minlength: $.validator.format("Please enter at least {0} characters."), + rangelength: $.validator.format("Please enter a value between {0} and {1} characters long."), + range: $.validator.format("Please enter a value between {0} and {1}."), + max: $.validator.format("Please enter a value less than or equal to {0}."), + min: $.validator.format("Please enter a value greater than or equal to {0}.") + }, + + autoCreateRanges: false, + + prototype: { + + init: function() { + this.labelContainer = $(this.settings.errorLabelContainer); + this.errorContext = this.labelContainer.length && this.labelContainer || $(this.currentForm); + this.containers = $(this.settings.errorContainer).add( this.settings.errorLabelContainer ); + this.submitted = {}; + this.valueCache = {}; + this.pendingRequest = 0; + this.pending = {}; + this.invalid = {}; + this.reset(); + + var groups = (this.groups = {}); + $.each(this.settings.groups, function(key, value) { + $.each(value.split(/\s/), function(index, name) { + groups[name] = key; + }); + }); + var rules = this.settings.rules; + $.each(rules, function(key, value) { + rules[key] = $.validator.normalizeRule(value); + }); + + function delegate(event) { + var validator = $.data(this[0].form, "validator"), + eventType = "on" + event.type.replace(/^validate/, ""); + validator.settings[eventType] && validator.settings[eventType].call(validator, this[0] ); + } + $(this.currentForm) + .validateDelegate(":text, :password, :file, select, textarea", "focusin focusout keyup", delegate) + .validateDelegate(":radio, :checkbox, select, option", "click", delegate); + + if (this.settings.invalidHandler) + $(this.currentForm).bind("invalid-form.validate", this.settings.invalidHandler); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/form + form: function() { + this.checkForm(); + $.extend(this.submitted, this.errorMap); + this.invalid = $.extend({}, this.errorMap); + if (!this.valid()) + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.showErrors(); + return this.valid(); + }, + + checkForm: function() { + this.prepareForm(); + for ( var i = 0, elements = (this.currentElements = this.elements()); elements[i]; i++ ) { + this.check( elements[i] ); + } + return this.valid(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/element + element: function( element ) { + element = this.clean( element ); + this.lastElement = element; + this.prepareElement( element ); + this.currentElements = $(element); + var result = this.check( element ); + if ( result ) { + delete this.invalid[element.name]; + } else { + this.invalid[element.name] = true; + } + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/showErrors + showErrors: function(errors) { + if(errors) { + // add items to error list and map + $.extend( this.errorMap, errors ); + this.errorList = []; + for ( var name in errors ) { + this.errorList.push({ + message: errors[name], + element: this.findByName(name)[0] + }); + } + // remove items from success list + this.successList = $.grep( this.successList, function(element) { + return !(element.name in errors); + }); + } + this.settings.showErrors + ? this.settings.showErrors.call( this, this.errorMap, this.errorList ) + : this.defaultShowErrors(); + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/resetForm + resetForm: function() { + if ( $.fn.resetForm ) + $( this.currentForm ).resetForm(); + this.submitted = {}; + this.prepareForm(); + this.hideErrors(); + this.elements().removeClass( this.settings.errorClass ); + }, + + numberOfInvalids: function() { + return this.objectLength(this.invalid); + }, + + objectLength: function( obj ) { + var count = 0; + for ( var i in obj ) + count++; + return count; + }, + + hideErrors: function() { + this.addWrapper( this.toHide ).hide(); + }, + + valid: function() { + return this.size() == 0; + }, + + size: function() { + return this.errorList.length; + }, + + focusInvalid: function() { + if( this.settings.focusInvalid ) { + try { + $(this.findLastActive() || this.errorList.length && this.errorList[0].element || []) + .filter(":visible") + .focus() + // manually trigger focusin event; without it, focusin handler isn't called, findLastActive won't have anything to find + .trigger("focusin"); + } catch(e) { + // ignore IE throwing errors when focusing hidden elements + } + } + }, + + findLastActive: function() { + var lastActive = this.lastActive; + return lastActive && $.grep(this.errorList, function(n) { + return n.element.name == lastActive.name; + }).length == 1 && lastActive; + }, + + elements: function() { + var validator = this, + rulesCache = {}; + + // select all valid inputs inside the form (no submit or reset buttons) + // workaround $Query([]).add until http://dev.jquery.com/ticket/2114 is solved + return $([]).add(this.currentForm.elements) + .filter(":input") + .not(":submit, :reset, :image, [disabled]") + .not( this.settings.ignore ) + .filter(function() { + !this.name && validator.settings.debug && window.console && console.error( "%o has no name assigned", this); + + // select only the first element for each name, and only those with rules specified + if ( this.name in rulesCache || !validator.objectLength($(this).rules()) ) + return false; + + rulesCache[this.name] = true; + return true; + }); + }, + + clean: function( selector ) { + return $( selector )[0]; + }, + + errors: function() { + return $( this.settings.errorElement + "." + this.settings.errorClass, this.errorContext ); + }, + + reset: function() { + this.successList = []; + this.errorList = []; + this.errorMap = {}; + this.toShow = $([]); + this.toHide = $([]); + this.currentElements = $([]); + }, + + prepareForm: function() { + this.reset(); + this.toHide = this.errors().add( this.containers ); + }, + + prepareElement: function( element ) { + this.reset(); + this.toHide = this.errorsFor(element); + }, + + check: function( element ) { + element = this.clean( element ); + + // if radio/checkbox, validate first element in group instead + if (this.checkable(element)) { + element = this.findByName( element.name ).not(this.settings.ignore)[0]; + } + + var rules = $(element).rules(); + var dependencyMismatch = false; + for (var method in rules ) { + var rule = { method: method, parameters: rules[method] }; + try { + var result = $.validator.methods[method].call( this, element.value.replace(/\r/g, ""), element, rule.parameters ); + + // if a method indicates that the field is optional and therefore valid, + // don't mark it as valid when there are no other rules + if ( result == "dependency-mismatch" ) { + dependencyMismatch = true; + continue; + } + dependencyMismatch = false; + + if ( result == "pending" ) { + this.toHide = this.toHide.not( this.errorsFor(element) ); + return; + } + + if( !result ) { + this.formatAndAdd( element, rule ); + return false; + } + } catch(e) { + this.settings.debug && window.console && console.log("exception occured when checking element " + element.id + + ", check the '" + rule.method + "' method", e); + throw e; + } + } + if (dependencyMismatch) + return; + if ( this.objectLength(rules) ) + this.successList.push(element); + return true; + }, + + // return the custom message for the given element and validation method + // specified in the element's "messages" metadata + customMetaMessage: function(element, method) { + if (!$.metadata) + return; + + var meta = this.settings.meta + ? $(element).metadata()[this.settings.meta] + : $(element).metadata(); + + return meta && meta.messages && meta.messages[method]; + }, + + // return the custom message for the given element name and validation method + customMessage: function( name, method ) { + var m = this.settings.messages[name]; + return m && (m.constructor == String + ? m + : m[method]); + }, + + // return the first defined argument, allowing empty strings + findDefined: function() { + for(var i = 0; i < arguments.length; i++) { + if (arguments[i] !== undefined) + return arguments[i]; + } + return undefined; + }, + + defaultMessage: function( element, method) { + return this.findDefined( + this.customMessage( element.name, method ), + this.customMetaMessage( element, method ), + // title is never undefined, so handle empty string as undefined + !this.settings.ignoreTitle && element.title || undefined, + $.validator.messages[method], + "Warning: No message defined for " + element.name + "" + ); + }, + + formatAndAdd: function( element, rule ) { + var message = this.defaultMessage( element, rule.method ), + theregex = /\$?\{(\d+)\}/g; + if ( typeof message == "function" ) { + message = message.call(this, rule.parameters, element); + } else if (theregex.test(message)) { + message = jQuery.format(message.replace(theregex, '{$1}'), rule.parameters); + } + this.errorList.push({ + message: message, + element: element + }); + + this.errorMap[element.name] = message; + this.submitted[element.name] = message; + }, + + addWrapper: function(toToggle) { + if ( this.settings.wrapper ) + toToggle = toToggle.add( toToggle.parent( this.settings.wrapper ) ); + return toToggle; + }, + + defaultShowErrors: function() { + for ( var i = 0; this.errorList[i]; i++ ) { + var error = this.errorList[i]; + this.settings.highlight && this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); + this.showLabel( error.element, error.message ); + } + if( this.errorList.length ) { + this.toShow = this.toShow.add( this.containers ); + } + if (this.settings.success) { + for ( var i = 0; this.successList[i]; i++ ) { + this.showLabel( this.successList[i] ); + } + } + if (this.settings.unhighlight) { + for ( var i = 0, elements = this.validElements(); elements[i]; i++ ) { + this.settings.unhighlight.call( this, elements[i], this.settings.errorClass, this.settings.validClass ); + } + } + this.toHide = this.toHide.not( this.toShow ); + this.hideErrors(); + this.addWrapper( this.toShow ).show(); + }, + + validElements: function() { + return this.currentElements.not(this.invalidElements()); + }, + + invalidElements: function() { + return $(this.errorList).map(function() { + return this.element; + }); + }, + + showLabel: function(element, message) { + var label = this.errorsFor( element ); + if ( label.length ) { + // refresh error/success class + label.removeClass().addClass( this.settings.errorClass ); + + // check if we have a generated label, replace the message then + label.attr("generated") && label.html(message); + } else { + // create label + label = $("<" + this.settings.errorElement + "/>") + .attr({"for": this.idOrName(element), generated: true}) + .addClass(this.settings.errorClass) + .html(message || ""); + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + label = label.hide().show().wrap("<" + this.settings.wrapper + "/>").parent(); + } + if ( !this.labelContainer.append(label).length ) + this.settings.errorPlacement + ? this.settings.errorPlacement(label, $(element) ) + : label.insertAfter(element); + } + if ( !message && this.settings.success ) { + label.text(""); + typeof this.settings.success == "string" + ? label.addClass( this.settings.success ) + : this.settings.success( label ); + } + this.toShow = this.toShow.add(label); + }, + + errorsFor: function(element) { + var name = this.idOrName(element); + return this.errors().filter(function() { + return $(this).attr('for') == name; + }); + }, + + idOrName: function(element) { + return this.groups[element.name] || (this.checkable(element) ? element.name : element.id || element.name); + }, + + checkable: function( element ) { + return /radio|checkbox/i.test(element.type); + }, + + findByName: function( name ) { + // select by name and filter by form for performance over form.find("[name=...]") + var form = this.currentForm; + return $(document.getElementsByName(name)).map(function(index, element) { + return element.form == form && element.name == name && element || null; + }); + }, + + getLength: function(value, element) { + switch( element.nodeName.toLowerCase() ) { + case 'select': + return $("option:selected", element).length; + case 'input': + if( this.checkable( element) ) + return this.findByName(element.name).filter(':checked').length; + } + return value.length; + }, + + depend: function(param, element) { + return this.dependTypes[typeof param] + ? this.dependTypes[typeof param](param, element) + : true; + }, + + dependTypes: { + "boolean": function(param, element) { + return param; + }, + "string": function(param, element) { + return !!$(param, element.form).length; + }, + "function": function(param, element) { + return param(element); + } + }, + + optional: function(element) { + return !$.validator.methods.required.call(this, $.trim(element.value), element) && "dependency-mismatch"; + }, + + startRequest: function(element) { + if (!this.pending[element.name]) { + this.pendingRequest++; + this.pending[element.name] = true; + } + }, + + stopRequest: function(element, valid) { + this.pendingRequest--; + // sometimes synchronization fails, make sure pendingRequest is never < 0 + if (this.pendingRequest < 0) + this.pendingRequest = 0; + delete this.pending[element.name]; + if ( valid && this.pendingRequest == 0 && this.formSubmitted && this.form() ) { + $(this.currentForm).submit(); + this.formSubmitted = false; + } else if (!valid && this.pendingRequest == 0 && this.formSubmitted) { + $(this.currentForm).triggerHandler("invalid-form", [this]); + this.formSubmitted = false; + } + }, + + previousValue: function(element) { + return $.data(element, "previousValue") || $.data(element, "previousValue", { + old: null, + valid: true, + message: this.defaultMessage( element, "remote" ) + }); + } + + }, + + classRuleSettings: { + required: {required: true}, + email: {email: true}, + url: {url: true}, + date: {date: true}, + dateISO: {dateISO: true}, + dateDE: {dateDE: true}, + number: {number: true}, + numberDE: {numberDE: true}, + digits: {digits: true}, + creditcard: {creditcard: true} + }, + + addClassRules: function(className, rules) { + className.constructor == String ? + this.classRuleSettings[className] = rules : + $.extend(this.classRuleSettings, className); + }, + + classRules: function(element) { + var rules = {}; + var classes = $(element).attr('class'); + classes && $.each(classes.split(' '), function() { + if (this in $.validator.classRuleSettings) { + $.extend(rules, $.validator.classRuleSettings[this]); + } + }); + return rules; + }, + + attributeRules: function(element) { + var rules = {}; + var $element = $(element); + + for (var method in $.validator.methods) { + var value = $element.attr(method); + if (value) { + rules[method] = value; + } + } + + // maxlength may be returned as -1, 2147483647 (IE) and 524288 (safari) for text inputs + if (rules.maxlength && /-1|2147483647|524288/.test(rules.maxlength)) { + delete rules.maxlength; + } + + return rules; + }, + + metadataRules: function(element) { + if (!$.metadata) return {}; + + var meta = $.data(element.form, 'validator').settings.meta; + return meta ? + $(element).metadata()[meta] : + $(element).metadata(); + }, + + staticRules: function(element) { + var rules = {}; + var validator = $.data(element.form, 'validator'); + if (validator.settings.rules) { + rules = $.validator.normalizeRule(validator.settings.rules[element.name]) || {}; + } + return rules; + }, + + normalizeRules: function(rules, element) { + // handle dependency check + $.each(rules, function(prop, val) { + // ignore rule when param is explicitly false, eg. required:false + if (val === false) { + delete rules[prop]; + return; + } + if (val.param || val.depends) { + var keepRule = true; + switch (typeof val.depends) { + case "string": + keepRule = !!$(val.depends, element.form).length; + break; + case "function": + keepRule = val.depends.call(element, element); + break; + } + if (keepRule) { + rules[prop] = val.param !== undefined ? val.param : true; + } else { + delete rules[prop]; + } + } + }); + + // evaluate parameters + $.each(rules, function(rule, parameter) { + rules[rule] = $.isFunction(parameter) ? parameter(element) : parameter; + }); + + // clean number parameters + $.each(['minlength', 'maxlength', 'min', 'max'], function() { + if (rules[this]) { + rules[this] = Number(rules[this]); + } + }); + $.each(['rangelength', 'range'], function() { + if (rules[this]) { + rules[this] = [Number(rules[this][0]), Number(rules[this][1])]; + } + }); + + if ($.validator.autoCreateRanges) { + // auto-create ranges + if (rules.min && rules.max) { + rules.range = [rules.min, rules.max]; + delete rules.min; + delete rules.max; + } + if (rules.minlength && rules.maxlength) { + rules.rangelength = [rules.minlength, rules.maxlength]; + delete rules.minlength; + delete rules.maxlength; + } + } + + // To support custom messages in metadata ignore rule methods titled "messages" + if (rules.messages) { + delete rules.messages; + } + + return rules; + }, + + // Converts a simple string to a {string: true} rule, e.g., "required" to {required:true} + normalizeRule: function(data) { + if( typeof data == "string" ) { + var transformed = {}; + $.each(data.split(/\s/), function() { + transformed[this] = true; + }); + data = transformed; + } + return data; + }, + + // http://docs.jquery.com/Plugins/Validation/Validator/addMethod + addMethod: function(name, method, message) { + $.validator.methods[name] = method; + $.validator.messages[name] = message != undefined ? message : $.validator.messages[name]; + if (method.length < 3) { + $.validator.addClassRules(name, $.validator.normalizeRule(name)); + } + }, + + methods: { + + // http://docs.jquery.com/Plugins/Validation/Methods/required + required: function(value, element, param) { + // check if dependency is met + if ( !this.depend(param, element) ) + return "dependency-mismatch"; + switch( element.nodeName.toLowerCase() ) { + case 'select': + // could be an array for select-multiple or a string, both are fine this way + var val = $(element).val(); + return val && val.length > 0; + case 'input': + if ( this.checkable(element) ) + return this.getLength(value, element) > 0; + default: + return $.trim(value).length > 0; + } + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/remote + remote: function(value, element, param) { + if ( this.optional(element) ) + return "dependency-mismatch"; + + var previous = this.previousValue(element); + if (!this.settings.messages[element.name] ) + this.settings.messages[element.name] = {}; + previous.originalMessage = this.settings.messages[element.name].remote; + this.settings.messages[element.name].remote = previous.message; + + param = typeof param == "string" && {url:param} || param; + + if ( this.pending[element.name] ) { + return "pending"; + } + if ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + var validator = this; + this.startRequest(element); + var data = {}; + data[element.name] = value; + $.ajax($.extend(true, { + url: param, + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + success: function(response) { + validator.settings.messages[element.name].remote = previous.originalMessage; + var valid = response === true; + if ( valid ) { + var submitted = validator.formSubmitted; + validator.prepareElement(element); + validator.formSubmitted = submitted; + validator.successList.push(element); + validator.showErrors(); + } else { + var errors = {}; + var message = response || validator.defaultMessage( element, "remote" ); + errors[element.name] = previous.message = $.isFunction(message) ? message(value) : message; + validator.showErrors(errors); + } + previous.valid = valid; + validator.stopRequest(element, valid); + } + }, param)); + return "pending"; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/minlength + minlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/maxlength + maxlength: function(value, element, param) { + return this.optional(element) || this.getLength($.trim(value), element) <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/rangelength + rangelength: function(value, element, param) { + var length = this.getLength($.trim(value), element); + return this.optional(element) || ( length >= param[0] && length <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/min + min: function( value, element, param ) { + return this.optional(element) || value >= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/max + max: function( value, element, param ) { + return this.optional(element) || value <= param; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/range + range: function( value, element, param ) { + return this.optional(element) || ( value >= param[0] && value <= param[1] ); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/email + email: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/email_address_validation/ + return this.optional(element) || /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/url + url: function(value, element) { + // contributed by Scott Gonzalez: http://projects.scottsplayground.com/iri/ + return this.optional(element) || /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/date + date: function(value, element) { + return this.optional(element) || !/Invalid|NaN/.test(new Date(value)); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/dateISO + dateISO: function(value, element) { + return this.optional(element) || /^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/number + number: function(value, element) { + return this.optional(element) || /^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/digits + digits: function(value, element) { + return this.optional(element) || /^\d+$/.test(value); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/creditcard + // based on http://en.wikipedia.org/wiki/Luhn + creditcard: function(value, element) { + if ( this.optional(element) ) + return "dependency-mismatch"; + // accept only digits and dashes + if (/[^0-9-]+/.test(value)) + return false; + var nCheck = 0, + nDigit = 0, + bEven = false; + + value = value.replace(/\D/g, ""); + + for (var n = value.length - 1; n >= 0; n--) { + var cDigit = value.charAt(n); + var nDigit = parseInt(cDigit, 10); + if (bEven) { + if ((nDigit *= 2) > 9) + nDigit -= 9; + } + nCheck += nDigit; + bEven = !bEven; + } + + return (nCheck % 10) == 0; + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/accept + accept: function(value, element, param) { + param = typeof param == "string" ? param.replace(/,/g, '|') : "png|jpe?g|gif"; + return this.optional(element) || value.match(new RegExp(".(" + param + ")$", "i")); + }, + + // http://docs.jquery.com/Plugins/Validation/Methods/equalTo + equalTo: function(value, element, param) { + // bind to the blur event of the target in order to revalidate whenever the target field is updated + // TODO find a way to bind the event just once, avoiding the unbind-rebind overhead + var target = $(param).unbind(".validate-equalTo").bind("blur.validate-equalTo", function() { + $(element).valid(); + }); + return value == target.val(); + } + + } + +}); + +// deprecated, use $.validator.format instead +$.format = $.validator.format; + +})(jQuery); + +// ajax mode: abort +// usage: $.ajax({ mode: "abort"[, port: "uniqueport"]}); +// if mode:"abort" is used, the previous request on that port (port can be undefined) is aborted via XMLHttpRequest.abort() +;(function($) { + var pendingRequests = {}; + // Use a prefilter if available (1.5+) + if ( $.ajaxPrefilter ) { + $.ajaxPrefilter(function(settings, _, xhr) { + var port = settings.port; + if (settings.mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + pendingRequests[port] = xhr; + } + }); + } else { + // Proxy ajax + var ajax = $.ajax; + $.ajax = function(settings) { + var mode = ( "mode" in settings ? settings : $.ajaxSettings ).mode, + port = ( "port" in settings ? settings : $.ajaxSettings ).port; + if (mode == "abort") { + if ( pendingRequests[port] ) { + pendingRequests[port].abort(); + } + return (pendingRequests[port] = ajax.apply(this, arguments)); + } + return ajax.apply(this, arguments); + }; + } +})(jQuery); + +// provides cross-browser focusin and focusout events +// IE has native support, in other browsers, use event caputuring (neither bubbles) + +// provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation +// handler is only called when $(event.target).is(delegate), in the scope of the jquery-object for event.target +;(function($) { + // only implement if not provided by jQuery core (since 1.4) + // TODO verify if jQuery 1.4's implementation is compatible with older jQuery special-event APIs + if (!jQuery.event.special.focusin && !jQuery.event.special.focusout && document.addEventListener) { + $.each({ + focus: 'focusin', + blur: 'focusout' + }, function( original, fix ){ + $.event.special[fix] = { + setup:function() { + this.addEventListener( original, handler, true ); + }, + teardown:function() { + this.removeEventListener( original, handler, true ); + }, + handler: function(e) { + arguments[0] = $.event.fix(e); + arguments[0].type = fix; + return $.event.handle.apply(this, arguments); + } + }; + function handler(e) { + e = $.event.fix(e); + e.type = fix; + return $.event.handle.call(this, e); + } + }); + }; + $.extend($.fn, { + validateDelegate: function(delegate, type, handler) { + return this.bind(type, function(event) { + var target = $(event.target); + if (target.is(delegate)) { + return handler.apply(target, arguments); + } + }); + } + }); +})(jQuery); diff --git a/src/Test/packages/jQuery.Validation.1.8.0/Content/Scripts/jquery.validate.min.js b/src/Test/packages/jQuery.Validation.1.8.0/Content/Scripts/jquery.validate.min.js new file mode 100644 index 000000000..b07c2ab2d --- /dev/null +++ b/src/Test/packages/jQuery.Validation.1.8.0/Content/Scripts/jquery.validate.min.js @@ -0,0 +1,53 @@ +/** +* Note: While Microsoft is not the author of this file, Microsoft is +* offering you a license subject to the terms of the Microsoft Software +* License Terms for Microsoft ASP.NET Model View Controller 3. +* Microsoft reserves all other rights. The notices below are provided +* for informational purposes only and are not the license terms under +* which Microsoft distributed this file. +* +* jQuery Validation Plugin 1.8.0 +* +* http://bassistance.de/jquery-plugins/jquery-plugin-validation/ +* http://docs.jquery.com/Plugins/Validation +* +* Copyright (c) 2006 - 2011 Jörn Zaefferer +*/ +(function(c){c.extend(c.fn,{validate:function(a){if(this.length){var b=c.data(this[0],"validator");if(b)return b;b=new c.validator(a,this[0]);c.data(this[0],"validator",b);if(b.settings.onsubmit){this.find("input, button").filter(".cancel").click(function(){b.cancelSubmit=true});b.settings.submitHandler&&this.find("input, button").filter(":submit").click(function(){b.submitButton=this});this.submit(function(d){function e(){if(b.settings.submitHandler){if(b.submitButton)var f=c("").attr("name", +b.submitButton.name).val(b.submitButton.value).appendTo(b.currentForm);b.settings.submitHandler.call(b,b.currentForm);b.submitButton&&f.remove();return false}return true}b.settings.debug&&d.preventDefault();if(b.cancelSubmit){b.cancelSubmit=false;return e()}if(b.form()){if(b.pendingRequest){b.formSubmitted=true;return false}return e()}else{b.focusInvalid();return false}})}return b}else a&&a.debug&&window.console&&console.warn("nothing selected, can't validate, returning nothing")},valid:function(){if(c(this[0]).is("form"))return this.validate().form(); +else{var a=true,b=c(this[0].form).validate();this.each(function(){a&=b.element(this)});return a}},removeAttrs:function(a){var b={},d=this;c.each(a.split(/\s/),function(e,f){b[f]=d.attr(f);d.removeAttr(f)});return b},rules:function(a,b){var d=this[0];if(a){var e=c.data(d.form,"validator").settings,f=e.rules,g=c.validator.staticRules(d);switch(a){case "add":c.extend(g,c.validator.normalizeRule(b));f[d.name]=g;if(b.messages)e.messages[d.name]=c.extend(e.messages[d.name],b.messages);break;case "remove":if(!b){delete f[d.name]; +return g}var h={};c.each(b.split(/\s/),function(j,i){h[i]=g[i];delete g[i]});return h}}d=c.validator.normalizeRules(c.extend({},c.validator.metadataRules(d),c.validator.classRules(d),c.validator.attributeRules(d),c.validator.staticRules(d)),d);if(d.required){e=d.required;delete d.required;d=c.extend({required:e},d)}return d}});c.extend(c.expr[":"],{blank:function(a){return!c.trim(""+a.value)},filled:function(a){return!!c.trim(""+a.value)},unchecked:function(a){return!a.checked}});c.validator=function(a, +b){this.settings=c.extend(true,{},c.validator.defaults,a);this.currentForm=b;this.init()};c.validator.format=function(a,b){if(arguments.length==1)return function(){var d=c.makeArray(arguments);d.unshift(a);return c.validator.format.apply(this,d)};if(arguments.length>2&&b.constructor!=Array)b=c.makeArray(arguments).slice(1);if(b.constructor!=Array)b=[b];c.each(b,function(d,e){a=a.replace(RegExp("\\{"+d+"\\}","g"),e)});return a};c.extend(c.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error", +validClass:"valid",errorElement:"label",focusInvalid:true,errorContainer:c([]),errorLabelContainer:c([]),onsubmit:true,ignore:[],ignoreTitle:false,onfocusin:function(a){this.lastActive=a;if(this.settings.focusCleanup&&!this.blockFocusCleanup){this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass);this.addWrapper(this.errorsFor(a)).hide()}},onfocusout:function(a){if(!this.checkable(a)&&(a.name in this.submitted||!this.optional(a)))this.element(a)}, +onkeyup:function(a){if(a.name in this.submitted||a==this.lastElement)this.element(a)},onclick:function(a){if(a.name in this.submitted)this.element(a);else a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(a,b,d){c(a).addClass(b).removeClass(d)},unhighlight:function(a,b,d){c(a).removeClass(b).addClass(d)}},setDefaults:function(a){c.extend(c.validator.defaults,a)},messages:{required:"This field is required.",remote:"Please fix this field.",email:"Please enter a valid email address.", +url:"Please enter a valid URL.",date:"Please enter a valid date.",dateISO:"Please enter a valid date (ISO).",number:"Please enter a valid number.",digits:"Please enter only digits.",creditcard:"Please enter a valid credit card number.",equalTo:"Please enter the same value again.",accept:"Please enter a value with a valid extension.",maxlength:c.validator.format("Please enter no more than {0} characters."),minlength:c.validator.format("Please enter at least {0} characters."),rangelength:c.validator.format("Please enter a value between {0} and {1} characters long."), +range:c.validator.format("Please enter a value between {0} and {1}."),max:c.validator.format("Please enter a value less than or equal to {0}."),min:c.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:false,prototype:{init:function(){function a(e){var f=c.data(this[0].form,"validator");e="on"+e.type.replace(/^validate/,"");f.settings[e]&&f.settings[e].call(f,this[0])}this.labelContainer=c(this.settings.errorLabelContainer);this.errorContext=this.labelContainer.length&& +this.labelContainer||c(this.currentForm);this.containers=c(this.settings.errorContainer).add(this.settings.errorLabelContainer);this.submitted={};this.valueCache={};this.pendingRequest=0;this.pending={};this.invalid={};this.reset();var b=this.groups={};c.each(this.settings.groups,function(e,f){c.each(f.split(/\s/),function(g,h){b[h]=e})});var d=this.settings.rules;c.each(d,function(e,f){d[e]=c.validator.normalizeRule(f)});c(this.currentForm).validateDelegate(":text, :password, :file, select, textarea", +"focusin focusout keyup",a).validateDelegate(":radio, :checkbox, select, option","click",a);this.settings.invalidHandler&&c(this.currentForm).bind("invalid-form.validate",this.settings.invalidHandler)},form:function(){this.checkForm();c.extend(this.submitted,this.errorMap);this.invalid=c.extend({},this.errorMap);this.valid()||c(this.currentForm).triggerHandler("invalid-form",[this]);this.showErrors();return this.valid()},checkForm:function(){this.prepareForm();for(var a=0,b=this.currentElements=this.elements();b[a];a++)this.check(b[a]); +return this.valid()},element:function(a){this.lastElement=a=this.clean(a);this.prepareElement(a);this.currentElements=c(a);var b=this.check(a);if(b)delete this.invalid[a.name];else this.invalid[a.name]=true;if(!this.numberOfInvalids())this.toHide=this.toHide.add(this.containers);this.showErrors();return b},showErrors:function(a){if(a){c.extend(this.errorMap,a);this.errorList=[];for(var b in a)this.errorList.push({message:a[b],element:this.findByName(b)[0]});this.successList=c.grep(this.successList, +function(d){return!(d.name in a)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){c.fn.resetForm&&c(this.currentForm).resetForm();this.submitted={};this.prepareForm();this.hideErrors();this.elements().removeClass(this.settings.errorClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b=0,d;for(d in a)b++;return b},hideErrors:function(){this.addWrapper(this.toHide).hide()}, +valid:function(){return this.size()==0},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{c(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(a){}},findLastActive:function(){var a=this.lastActive;return a&&c.grep(this.errorList,function(b){return b.element.name==a.name}).length==1&&a},elements:function(){var a=this,b={};return c([]).add(this.currentForm.elements).filter(":input").not(":submit, :reset, :image, [disabled]").not(this.settings.ignore).filter(function(){!this.name&& +a.settings.debug&&window.console&&console.error("%o has no name assigned",this);if(this.name in b||!a.objectLength(c(this).rules()))return false;return b[this.name]=true})},clean:function(a){return c(a)[0]},errors:function(){return c(this.settings.errorElement+"."+this.settings.errorClass,this.errorContext)},reset:function(){this.successList=[];this.errorList=[];this.errorMap={};this.toShow=c([]);this.toHide=c([]);this.currentElements=c([])},prepareForm:function(){this.reset();this.toHide=this.errors().add(this.containers)}, +prepareElement:function(a){this.reset();this.toHide=this.errorsFor(a)},check:function(a){a=this.clean(a);if(this.checkable(a))a=this.findByName(a.name).not(this.settings.ignore)[0];var b=c(a).rules(),d=false,e;for(e in b){var f={method:e,parameters:b[e]};try{var g=c.validator.methods[e].call(this,a.value.replace(/\r/g,""),a,f.parameters);if(g=="dependency-mismatch")d=true;else{d=false;if(g=="pending"){this.toHide=this.toHide.not(this.errorsFor(a));return}if(!g){this.formatAndAdd(a,f);return false}}}catch(h){this.settings.debug&& +window.console&&console.log("exception occured when checking element "+a.id+", check the '"+f.method+"' method",h);throw h;}}if(!d){this.objectLength(b)&&this.successList.push(a);return true}},customMetaMessage:function(a,b){if(c.metadata){var d=this.settings.meta?c(a).metadata()[this.settings.meta]:c(a).metadata();return d&&d.messages&&d.messages[b]}},customMessage:function(a,b){var d=this.settings.messages[a];return d&&(d.constructor==String?d:d[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+a.name+"")},formatAndAdd:function(a,b){var d=this.defaultMessage(a,b.method),e=/\$?\{(\d+)\}/g;if(typeof d=="function")d=d.call(this,b.parameters,a);else if(e.test(d))d=jQuery.format(d.replace(e,"{$1}"),b.parameters);this.errorList.push({message:d, +element:a});this.errorMap[a.name]=d;this.submitted[a.name]=d},addWrapper:function(a){if(this.settings.wrapper)a=a.add(a.parent(this.settings.wrapper));return a},defaultShowErrors:function(){for(var a=0;this.errorList[a];a++){var b=this.errorList[a];this.settings.highlight&&this.settings.highlight.call(this,b.element,this.settings.errorClass,this.settings.validClass);this.showLabel(b.element,b.message)}if(this.errorList.length)this.toShow=this.toShow.add(this.containers);if(this.settings.success)for(a= +0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight){a=0;for(b=this.validElements();b[a];a++)this.settings.unhighlight.call(this,b[a],this.settings.errorClass,this.settings.validClass)}this.toHide=this.toHide.not(this.toShow);this.hideErrors();this.addWrapper(this.toShow).show()},validElements:function(){return this.currentElements.not(this.invalidElements())},invalidElements:function(){return c(this.errorList).map(function(){return this.element})},showLabel:function(a, +b){var d=this.errorsFor(a);if(d.length){d.removeClass().addClass(this.settings.errorClass);d.attr("generated")&&d.html(b)}else{d=c("<"+this.settings.errorElement+"/>").attr({"for":this.idOrName(a),generated:true}).addClass(this.settings.errorClass).html(b||"");if(this.settings.wrapper)d=d.hide().show().wrap("<"+this.settings.wrapper+"/>").parent();this.labelContainer.append(d).length||(this.settings.errorPlacement?this.settings.errorPlacement(d,c(a)):d.insertAfter(a))}if(!b&&this.settings.success){d.text(""); +typeof this.settings.success=="string"?d.addClass(this.settings.success):this.settings.success(d)}this.toShow=this.toShow.add(d)},errorsFor:function(a){var b=this.idOrName(a);return this.errors().filter(function(){return c(this).attr("for")==b})},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(a){var b=this.currentForm;return c(document.getElementsByName(a)).map(function(d,e){return e.form== +b&&e.name==a&&e||null})},getLength:function(a,b){switch(b.nodeName.toLowerCase()){case "select":return c("option:selected",b).length;case "input":if(this.checkable(b))return this.findByName(b.name).filter(":checked").length}return a.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):true},dependTypes:{"boolean":function(a){return a},string:function(a,b){return!!c(a,b.form).length},"function":function(a,b){return a(b)}},optional:function(a){return!c.validator.methods.required.call(this, +c.trim(a.value),a)&&"dependency-mismatch"},startRequest:function(a){if(!this.pending[a.name]){this.pendingRequest++;this.pending[a.name]=true}},stopRequest:function(a,b){this.pendingRequest--;if(this.pendingRequest<0)this.pendingRequest=0;delete this.pending[a.name];if(b&&this.pendingRequest==0&&this.formSubmitted&&this.form()){c(this.currentForm).submit();this.formSubmitted=false}else if(!b&&this.pendingRequest==0&&this.formSubmitted){c(this.currentForm).triggerHandler("invalid-form",[this]);this.formSubmitted= +false}},previousValue:function(a){return c.data(a,"previousValue")||c.data(a,"previousValue",{old:null,valid:true,message:this.defaultMessage(a,"remote")})}},classRuleSettings:{required:{required:true},email:{email:true},url:{url:true},date:{date:true},dateISO:{dateISO:true},dateDE:{dateDE:true},number:{number:true},numberDE:{numberDE:true},digits:{digits:true},creditcard:{creditcard:true}},addClassRules:function(a,b){a.constructor==String?this.classRuleSettings[a]=b:c.extend(this.classRuleSettings, +a)},classRules:function(a){var b={};(a=c(a).attr("class"))&&c.each(a.split(" "),function(){this in c.validator.classRuleSettings&&c.extend(b,c.validator.classRuleSettings[this])});return b},attributeRules:function(a){var b={};a=c(a);for(var d in c.validator.methods){var e=a.attr(d);if(e)b[d]=e}b.maxlength&&/-1|2147483647|524288/.test(b.maxlength)&&delete b.maxlength;return b},metadataRules:function(a){if(!c.metadata)return{};var b=c.data(a.form,"validator").settings.meta;return b?c(a).metadata()[b]: +c(a).metadata()},staticRules:function(a){var b={},d=c.data(a.form,"validator");if(d.settings.rules)b=c.validator.normalizeRule(d.settings.rules[a.name])||{};return b},normalizeRules:function(a,b){c.each(a,function(d,e){if(e===false)delete a[d];else if(e.param||e.depends){var f=true;switch(typeof e.depends){case "string":f=!!c(e.depends,b.form).length;break;case "function":f=e.depends.call(b,b)}if(f)a[d]=e.param!==undefined?e.param:true;else delete a[d]}});c.each(a,function(d,e){a[d]=c.isFunction(e)? +e(b):e});c.each(["minlength","maxlength","min","max"],function(){if(a[this])a[this]=Number(a[this])});c.each(["rangelength","range"],function(){if(a[this])a[this]=[Number(a[this][0]),Number(a[this][1])]});if(c.validator.autoCreateRanges){if(a.min&&a.max){a.range=[a.min,a.max];delete a.min;delete a.max}if(a.minlength&&a.maxlength){a.rangelength=[a.minlength,a.maxlength];delete a.minlength;delete a.maxlength}}a.messages&&delete a.messages;return a},normalizeRule:function(a){if(typeof a=="string"){var b= +{};c.each(a.split(/\s/),function(){b[this]=true});a=b}return a},addMethod:function(a,b,d){c.validator.methods[a]=b;c.validator.messages[a]=d!=undefined?d:c.validator.messages[a];b.length<3&&c.validator.addClassRules(a,c.validator.normalizeRule(a))},methods:{required:function(a,b,d){if(!this.depend(d,b))return"dependency-mismatch";switch(b.nodeName.toLowerCase()){case "select":return(a=c(b).val())&&a.length>0;case "input":if(this.checkable(b))return this.getLength(a,b)>0;default:return c.trim(a).length> +0}},remote:function(a,b,d){if(this.optional(b))return"dependency-mismatch";var e=this.previousValue(b);this.settings.messages[b.name]||(this.settings.messages[b.name]={});e.originalMessage=this.settings.messages[b.name].remote;this.settings.messages[b.name].remote=e.message;d=typeof d=="string"&&{url:d}||d;if(this.pending[b.name])return"pending";if(e.old===a)return e.valid;e.old=a;var f=this;this.startRequest(b);var g={};g[b.name]=a;c.ajax(c.extend(true,{url:d,mode:"abort",port:"validate"+b.name, +dataType:"json",data:g,success:function(h){f.settings.messages[b.name].remote=e.originalMessage;var j=h===true;if(j){var i=f.formSubmitted;f.prepareElement(b);f.formSubmitted=i;f.successList.push(b);f.showErrors()}else{i={};h=h||f.defaultMessage(b,"remote");i[b.name]=e.message=c.isFunction(h)?h(a):h;f.showErrors(i)}e.valid=j;f.stopRequest(b,j)}},d));return"pending"},minlength:function(a,b,d){return this.optional(b)||this.getLength(c.trim(a),b)>=d},maxlength:function(a,b,d){return this.optional(b)|| +this.getLength(c.trim(a),b)<=d},rangelength:function(a,b,d){a=this.getLength(c.trim(a),b);return this.optional(b)||a>=d[0]&&a<=d[1]},min:function(a,b,d){return this.optional(b)||a>=d},max:function(a,b,d){return this.optional(b)||a<=d},range:function(a,b,d){return this.optional(b)||a>=d[0]&&a<=d[1]},email:function(a,b){return this.optional(b)||/^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$/i.test(a)}, +url:function(a,b){return this.optional(b)||/^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i.test(a)}, +date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a))},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(a)},number:function(a,b){return this.optional(b)||/^-?(?:\d+|\d{1,3}(?:,\d{3})+)(?:\.\d+)?$/.test(a)},digits:function(a,b){return this.optional(b)||/^\d+$/.test(a)},creditcard:function(a,b){if(this.optional(b))return"dependency-mismatch";if(/[^0-9-]+/.test(a))return false;var d=0,e=0,f=false;a=a.replace(/\D/g,"");for(var g=a.length-1;g>= +0;g--){e=a.charAt(g);e=parseInt(e,10);if(f)if((e*=2)>9)e-=9;d+=e;f=!f}return d%10==0},accept:function(a,b,d){d=typeof d=="string"?d.replace(/,/g,"|"):"png|jpe?g|gif";return this.optional(b)||a.match(RegExp(".("+d+")$","i"))},equalTo:function(a,b,d){d=c(d).unbind(".validate-equalTo").bind("blur.validate-equalTo",function(){c(b).valid()});return a==d.val()}}});c.format=c.validator.format})(jQuery); +(function(c){var a={};if(c.ajaxPrefilter)c.ajaxPrefilter(function(d,e,f){e=d.port;if(d.mode=="abort"){a[e]&&a[e].abort();a[e]=f}});else{var b=c.ajax;c.ajax=function(d){var e=("port"in d?d:c.ajaxSettings).port;if(("mode"in d?d:c.ajaxSettings).mode=="abort"){a[e]&&a[e].abort();return a[e]=b.apply(this,arguments)}return b.apply(this,arguments)}}})(jQuery); +(function(c){!jQuery.event.special.focusin&&!jQuery.event.special.focusout&&document.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.handle.call(this,e)}c.event.special[b]={setup:function(){this.addEventListener(a,d,true)},teardown:function(){this.removeEventListener(a,d,true)},handler:function(e){arguments[0]=c.event.fix(e);arguments[0].type=b;return c.event.handle.apply(this,arguments)}}});c.extend(c.fn,{validateDelegate:function(a, +b,d){return this.bind(b,function(e){var f=c(e.target);if(f.is(a))return d.apply(f,arguments)})}})})(jQuery); diff --git a/src/Test/packages/jQuery.Validation.1.8.0/jQuery.Validation.1.8.0.nupkg b/src/Test/packages/jQuery.Validation.1.8.0/jQuery.Validation.1.8.0.nupkg new file mode 100644 index 000000000..3a381a5b3 Binary files /dev/null and b/src/Test/packages/jQuery.Validation.1.8.0/jQuery.Validation.1.8.0.nupkg differ diff --git a/src/Test/packages/jQuery.vsdoc.1.5.1/Content/Scripts/jquery-1.5.1-vsdoc.js.REMOVED.git-id b/src/Test/packages/jQuery.vsdoc.1.5.1/Content/Scripts/jquery-1.5.1-vsdoc.js.REMOVED.git-id new file mode 100644 index 000000000..afb367d66 --- /dev/null +++ b/src/Test/packages/jQuery.vsdoc.1.5.1/Content/Scripts/jquery-1.5.1-vsdoc.js.REMOVED.git-id @@ -0,0 +1 @@ +8f56f29ea15515370d52a560396067bb28b52005 \ No newline at end of file diff --git a/src/Test/packages/jQuery.vsdoc.1.5.1/jQuery.vsdoc.1.5.1.nupkg.REMOVED.git-id b/src/Test/packages/jQuery.vsdoc.1.5.1/jQuery.vsdoc.1.5.1.nupkg.REMOVED.git-id new file mode 100644 index 000000000..fa97a2caa --- /dev/null +++ b/src/Test/packages/jQuery.vsdoc.1.5.1/jQuery.vsdoc.1.5.1.nupkg.REMOVED.git-id @@ -0,0 +1 @@ +f26892e9cc1b962bbca6e841091ff9be27dbb522 \ No newline at end of file diff --git a/src/Test/packages/repositories.config b/src/Test/packages/repositories.config new file mode 100644 index 000000000..fa38fcff0 --- /dev/null +++ b/src/Test/packages/repositories.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file