diff --git a/NpgsqlContentProvider/NpgsqlSkillProvider.cs b/NpgsqlContentProvider/NpgsqlSkillProvider.cs new file mode 100644 index 00000000..34336505 --- /dev/null +++ b/NpgsqlContentProvider/NpgsqlSkillProvider.cs @@ -0,0 +1,356 @@ +// +// NpgsqlSkillProvider.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using Yavsc.Model.Skill; +using System.Configuration; +using System.Collections.Specialized; +using System.Collections.Generic; +using Npgsql; +using NpgsqlTypes; + +namespace WorkFlowProvider +{ + /// + /// Npgsql skill provider. + /// + public class NpgsqlSkillProvider : SkillProvider + { + /// + /// Initializes a new instance of the class. + /// + public NpgsqlSkillProvider () + { + + } + string connectionString = null; + string applicationName = null; + + /// + /// Initialize this object using the specified name and config. + /// + /// Name. + /// Config. + public override void Initialize (string name, NameValueCollection config) + { + if (string.IsNullOrWhiteSpace (config ["connectionStringName"])) + throw new ConfigurationErrorsException ("No name for Npgsql connection string found"); + + connectionString = ConfigurationManager.ConnectionStrings [config ["connectionStringName"]].ConnectionString; + applicationName = config ["applicationName"] ?? "/"; + } + + #region implemented abstract members of SkillProvider + /// + /// Gets the user skills. + /// + /// The user skills. + /// Username. + public override PerformerProfile GetUserSkills (string username) + { + var skills = new List (); + var profile = new PerformerProfile (username); + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = + " select u._id, u.skillid, s.name, " + + " u.comment, u.rate from userskills u, " + + " skill s " + + " where u.skillid = s._id and " + + " u.username = :uname " + + " and applicationname = :app " + + " order by u.rate desc"; + cmd.Parameters.AddWithValue ("uname", NpgsqlTypes.NpgsqlDbType.Varchar, username); + cmd.Parameters.AddWithValue ("app", NpgsqlTypes.NpgsqlDbType.Varchar, applicationName); + cmd.Prepare (); + using (var rdr = cmd.ExecuteReader ()) { + if (rdr.HasRows) while (rdr.Read ()) { + skills.Add (new UserSkill () { + Id = rdr.GetInt64 (0), + SkillId = rdr.GetInt64 (1), + SkillName = rdr.GetString (2), + Comment = rdr.GetString (3), + Rate = rdr.GetInt32(4) + }); + } + profile.Skills = skills.ToArray (); + } + } + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = + "select uniqueid from profiles where username = :user and applicationname = :app"; + cmd.Parameters.AddWithValue ("user", NpgsqlTypes.NpgsqlDbType.Varchar, username); + cmd.Parameters.AddWithValue ("app", NpgsqlTypes.NpgsqlDbType.Varchar, applicationName); + profile.Id = (long) cmd.ExecuteScalar (); + } + cnx.Close (); + } + return profile; + } + + /// + /// Create the specified skill. + /// + /// skill. + public override long Declare (Skill skill) + { + long res = 0; + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + if (skill.Id == 0) { + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = "insert into skill (name,rate) values (:name,:rate) returning _id"; + cmd.Parameters.AddWithValue ("name", NpgsqlTypes.NpgsqlDbType.Varchar, skill.Name); + cmd.Parameters.AddWithValue ("rate", + NpgsqlTypes.NpgsqlDbType.Integer, skill.Rate); + res = (long)cmd.ExecuteScalar (); + } + } else { + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = "update skill set name = :name, rate = :rate where _id = :sid"; + cmd.Parameters.AddWithValue ("name", NpgsqlTypes.NpgsqlDbType.Bigint, skill.Id); + cmd.Parameters.AddWithValue ("rate", + NpgsqlTypes.NpgsqlDbType.Integer, skill.Rate); + cmd.ExecuteNonQuery (); + } + } + cnx.Close (); + } + return res; + } + + /// + /// Declares the userskill. + /// + /// The userskill. + /// userskill. + public override long Declare (UserSkillDeclaration userskill) + { + long res=0; + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + if (userskill.Id == 0) { + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = "insert into userskills" + + " (username, applicationname, skillid, rate, comment) " + + " values (:uname,:app,:sid,:rate,:cmnt) returning _id"; + cmd.Parameters.AddWithValue ("uname", + NpgsqlTypes.NpgsqlDbType.Varchar, userskill.UserName); + cmd.Parameters.AddWithValue ("app", + NpgsqlTypes.NpgsqlDbType.Varchar, applicationName); + cmd.Parameters.AddWithValue ("sid", + NpgsqlTypes.NpgsqlDbType.Bigint, userskill.SkillId); + cmd.Parameters.AddWithValue ("rate", + NpgsqlTypes.NpgsqlDbType.Integer, userskill.Rate); + cmd.Parameters.AddWithValue ("cmnt", + NpgsqlTypes.NpgsqlDbType.Varchar, userskill.Comment); + userskill.Id = res = (long)cmd.ExecuteScalar (); + } + } else { + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = "update userskills" + + " set rate = :rate," + + " comment = :cmnt) " + + " where _id = :usid "; + cmd.Parameters.AddWithValue ("comment", + NpgsqlTypes.NpgsqlDbType.Varchar, userskill.Comment); + cmd.Parameters.AddWithValue ("rate", + NpgsqlTypes.NpgsqlDbType.Integer, userskill.Rate); + cmd.Parameters.AddWithValue ("usid", + NpgsqlTypes.NpgsqlDbType.Bigint, userskill.Id); + cmd.ExecuteNonQuery (); + } + } + cnx.Close (); + } + return res; + } + + /// + /// Rate the specified user's skill. + /// It creates the record describing the user's skill + /// if not existent. + /// + /// UserSkillRating. + public override long Rate (UserSkillRating userSkill) + { + // TODO Use the Author value to choose + // between a self rating that goes into the `userskills` table + // and a client rating that goes into the + // `statisfaction` table. + long usid = 0; + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + if (userSkill.Id == 0) { + cmd.CommandText = "insert into userskills " + + " ( skillid, rate, username, applicationname ) " + + " values ( :sid, :rate, :uname, :app ) " + + " returning _id "; + cmd.Parameters.AddWithValue ("sid", NpgsqlDbType.Bigint, userSkill.Id); + cmd.Parameters.AddWithValue ("rate", NpgsqlDbType.Integer, userSkill.Rate); + cmd.Parameters.AddWithValue ("uname", NpgsqlDbType.Varchar, userSkill.Performer); + cmd.Parameters.AddWithValue ("app", NpgsqlDbType.Varchar, applicationName); + usid = (long) cmd.ExecuteScalar (); + } else { + cmd.CommandText = "update userskills " + + " set rate = :rate " + + " where _id = :usid "; + cmd.Parameters.AddWithValue ("rate", NpgsqlDbType.Integer, userSkill.Rate); + cmd.Parameters.AddWithValue ("usid", NpgsqlDbType.Bigint, userSkill.Id); + cmd.ExecuteNonQuery (); + } + + } + cnx.Close (); + } + return usid; + } + + /// + /// Rate the specified skill. + /// The access to this method + /// should be restricted to an Admin, + /// or a rating engine + /// + /// Skill. + public override void Rate (SkillRating skill) + { + // TODO Use the Author value to choose + // between a global setting for the application + // and an user setting on his needs + + // when the `Author` value is not null, + // it's concerning a rating on a need of the Author + // if not, it's a need of the site + + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = "update skill set rate = :rate where _id = :sid"; + cmd.Parameters.AddWithValue ("sid", NpgsqlTypes.NpgsqlDbType.Bigint, skill.Id); + cmd.Parameters.AddWithValue ("rate", NpgsqlTypes.NpgsqlDbType.Integer, skill.Rate); + cmd.ExecuteNonQuery (); + } + cnx.Close (); + } + } + /// + /// Finds the skill identifier. + /// + /// The skill identifier. + /// Pattern. + public override Skill[] FindSkill (string pattern) + { + List skills = new List (); + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = "select _id, name, rate from skill where name like :name order by rate desc"; + cmd.Parameters.AddWithValue ("name", NpgsqlTypes.NpgsqlDbType.Varchar, pattern); + cmd.Prepare (); + using (var rdr = cmd.ExecuteReader ()) { + if (rdr.HasRows) while (rdr.Read ()) { + skills.Add (new Skill () { + Id = (long)rdr.GetInt64 (0), + Name = (string)rdr.GetString (1), + Rate = (int) rdr.GetInt32(2) + }); + } + } + } + cnx.Close (); + } + return skills.ToArray (); + } + + /// + /// Finds the performer. + /// + /// The performer. + /// Skill identifiers. + public override string[] FindPerformer (long[] skillIds) + { + var res = new List (); + + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + + cmd.CommandText = " select username from userskills " + + " where skillid = :sid " + + " order by rate desc "; + cmd.Parameters.AddWithValue ("sid", NpgsqlDbType.Bigint, 0); + cmd.Prepare (); + + foreach ( long sid in skillIds ) + { + cmd.Parameters ["sid"].Value = sid; + using (var rdr = cmd.ExecuteReader ()) { + string uname = rdr.GetString (0); + if (!res.Contains(uname)) + res.Add (uname); + } + } + } + cnx.Close (); + } + return res.ToArray (); + } + + /// + /// Deletes the skill. + /// + /// Skill identifier. + public override void DeleteSkill (long skillId) + { + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = " delete from skill " + + " where _id = :sid "; + cmd.Parameters.AddWithValue ("sid", NpgsqlTypes.NpgsqlDbType.Bigint,skillId); + cmd.ExecuteNonQuery (); + } + cnx.Close (); + } + } + + /// + /// Deletes the user skill. + /// + /// User skill identifier. + public override void DeleteUserSkill(long userSkillId) { + using (NpgsqlConnection cnx=new NpgsqlConnection(connectionString)) { + cnx.Open (); + using (NpgsqlCommand cmd = cnx.CreateCommand ()) { + cmd.CommandText = " delete from userskills " + + " where _id = :usid "; + cmd.Parameters.AddWithValue ("usid", NpgsqlTypes.NpgsqlDbType.Bigint,userSkillId); + cmd.ExecuteNonQuery (); + } + cnx.Close (); + } + } + #endregion + } +} + diff --git a/WebControls/RateControl.cs b/WebControls/RateControl.cs new file mode 100644 index 00000000..272cf8e0 --- /dev/null +++ b/WebControls/RateControl.cs @@ -0,0 +1,90 @@ +// +// RateControl.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using System.Web.Mvc; +using Yavsc.Model; + +namespace Yavsc +{ + /// + /// Rate control. + /// + public class RateControl : ViewUserControl where TModel : IRating + { + /// + /// Initializes a new instance of the Yavsc.Blogs.RateControl class. + /// + public RateControl () + { + } + + /// + /// Gets or sets the rate, that is, an integer between 0 and 100 + /// + /// The rate. + public int Rate + { + get { return (int) ViewState["rate"]; } + set { + ViewState["rate"] = value; + int rate = value; + int rounded = (rate / 10); + HasHalf = rounded % 2 == 1; + NbFilled = (int)rounded / 2; + NbEmpty = (5 - NbFilled) - ((HasHalf)?1:0) ; + } + } + + /// + /// Gets the nb filed. + /// + /// The nb filed. + public int NbFilled { + set { ViewState["nbfilled"] = value; } + get { return (int) ViewState["nbfilled"]; } + } + + /// + /// Gets the nb empty. + /// + /// The nb empty. + public int NbEmpty { + set { ViewState["nbempty"] = value; } + get { return (int) ViewState["nbempty"]; } + } + + /// + /// Gets a value indicating whether this instance has half. + /// + /// true if this instance has half; otherwise, false. + public bool HasHalf { + set { ViewState["hashalf"] = value; } + get { return (bool) ViewState["hashalf"]; } + } + + protected override void OnInit (EventArgs e) + { + base.OnInit (e); + Rate = this.Model.Rate; + } + } +} + diff --git a/web/ApiControllers/SkillController.cs b/web/ApiControllers/SkillController.cs new file mode 100644 index 00000000..0a0ce260 --- /dev/null +++ b/web/ApiControllers/SkillController.cs @@ -0,0 +1,109 @@ +// +// SkillController.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using Yavsc.Model.Skill; +using System.Web.Http; +using Yavsc.Model; + +namespace Yavsc.ApiControllers +{ + /// + /// Skill controller. + /// + public class SkillController : YavscController + { + /// + /// Create or update the specified Skill. + /// + /// the Skill objet. + [ValidateAjaxAttribute,Authorize(Roles="Profiler")] + public long DeclareSkill (Skill s) { + if (ModelState.IsValid) { + SkillManager.DeclareSkill (s); + } + return s.Id; + } + + /// + /// Declares the user's skill, + /// This call should be in charge of + /// the user himself, it's an user + /// declaration, and as a result, + /// this only is a declarative caracterisation of + /// the user's skills. + /// + /// The skill. + /// Dec. + [Authorize()] + public long DeclareUserSkill (UserSkillDeclaration dec) { + return SkillManager.DeclareUserSkill(dec); + } + + /// + /// Rate the specified user's skill. + /// A way for an effective client to rate + /// the service or good he should have got. + /// This is the king's value + /// + /// The user skill rating. + [Authorize()] + public long RateUserSkill (UserSkillRating rate) { + return SkillManager.RateUserSkill(User.Identity.Name, rate); + } + + /// + /// Rates the skill. + /// + /// Skill rating. + [Authorize()] + public void RateSkill (SkillRating rate) { + SkillManager.RateSkill(User.Identity.Name,rate); + } + + /// + /// Finds the skill identifier. + /// + /// The skill identifier. + /// Pattern. + public Skill [] FindSkill (string pattern){ + return SkillManager.FindSkill(pattern); + } + + /// + /// Finds the performer. + /// + /// The performer. + /// Skill identifiers. + public string [] FindPerformer (long []skillIds){ + return SkillManager.FindPerformer(skillIds); + } + /// + /// Deletes the skill. + /// + /// Skill identifier. + [Authorize(Roles="Moderator")] + public void DeleteSkill (long skillId) + { + SkillManager.DeleteSkill (skillId); + } + } +} + diff --git a/web/App_Data/Sql/Skills.sql b/web/App_Data/Sql/Skills.sql new file mode 100644 index 00000000..17a152a7 --- /dev/null +++ b/web/App_Data/Sql/Skills.sql @@ -0,0 +1,68 @@ + +-- Table: skill + +-- DROP TABLE skill; + +CREATE TABLE skill +( + _id bigserial NOT NULL, + name character varying(2024) NOT NULL, + rate integer NOT NULL DEFAULT 50, + CONSTRAINT skill_pkey PRIMARY KEY (_id), + CONSTRAINT skill_name_key UNIQUE (name) +) +WITH ( + OIDS=FALSE +); + + +-- Table: userskills + +-- DROP TABLE userskills; + +CREATE TABLE userskills +( + applicationname character varying(512) NOT NULL, + username character varying(512) NOT NULL, + comment character varying, + skillid bigint NOT NULL, -- Skill identifier + rate integer NOT NULL, + _id bigserial NOT NULL, -- The id ... + CONSTRAINT userskills_pkey PRIMARY KEY (applicationname, username, skillid), + CONSTRAINT userskills_applicationname_fkey FOREIGN KEY (applicationname, username) + REFERENCES users (applicationname, username) MATCH SIMPLE + ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT userskills_skillid_fkey FOREIGN KEY (skillid) + REFERENCES skill (_id) MATCH SIMPLE + ON UPDATE CASCADE ON DELETE CASCADE, + CONSTRAINT userskills__id_key UNIQUE (_id) +) +WITH ( + OIDS=FALSE +); +COMMENT ON COLUMN userskills.skillid IS 'Skill identifier'; +COMMENT ON COLUMN userskills._id IS 'The id ...'; + +-- Table: satisfaction + +-- DROP TABLE satisfaction; + +CREATE TABLE satisfaction +( + _id bigserial NOT NULL, + userskillid bigint, -- the user's skill reference + rate integer, -- The satisfaction rating associated by a client to an user's skill + comnt character varying(8192), -- The satisfaction textual comment associated by a client to an user's skill, it could be formatted ala Markdown + CONSTRAINT satisfaction_pkey PRIMARY KEY (_id), + CONSTRAINT satisfaction_userskillid_fkey FOREIGN KEY (userskillid) + REFERENCES userskills (_id) MATCH SIMPLE + ON UPDATE CASCADE ON DELETE CASCADE +) +WITH ( + OIDS=FALSE +); +COMMENT ON COLUMN satisfaction.userskillid IS 'the user''s skill reference'; +COMMENT ON COLUMN satisfaction.rate IS 'The satisfaction rating associated by a client to an user''s skill'; +COMMENT ON COLUMN satisfaction.comnt IS 'The satisfaction textual comment associated by a client to an user''s skill, it could be formatted ala Markdown'; + + diff --git a/web/App_Themes/clear/style.css b/web/App_Themes/clear/style.css new file mode 100644 index 00000000..35a599ff --- /dev/null +++ b/web/App_Themes/clear/style.css @@ -0,0 +1,465 @@ + +body { + background-color: grey; + color: #303030; + font-family: 'Arial', cursive; + padding: 0; + margin: 0; +} +.tagname { color: #D0FFD0; } +.tagname+.tagname:before { content: ', '; } +.tagname:hover { background-color: red; cursor:pointer; } + +/* Start by setting display:none to make this hidden. + Then we position it in relation to the viewport window + with position:fixed. Width, height, top and left speak + for themselves. Background we set to 80% white with + our animation centered, and no-repeating */ +.modal { + display: none; + position: fixed; + z-index: 1000; + top: 0; + left: 0; + height: 100%; + width: 100%; + background: rgba( 255, 255, 255, .8 ) + url('/App_Themes/images/FhHRx.gif') + 50% 50% + no-repeat; + overflow: auto; +} +.dispmodal { + position: fixed; + z-index: 1000; + top: 0; + left: 0; + right: 0; + width: 100%; + overflow-y: auto; + overflow-x: hidden; +} + +body.loading { + overflow: hidden; +} + +body.loading .modal { + display: block; +} +.iconsmall { max-height: 1.3em; max-width: 1.3em; } + +input, textarea, checkbox { + color: #FFA0A0; + background-color: black; + } + +.post .photo { max-width: 100%; } + +.blogbanner { float: left; top:0; } +.subtitle { font-size:small; font-style: italic; } +header { +transition: margin 2s, padding 2s; + padding: 0; + margin: 0; + display: block; + background: url("/App_Themes/images/live-concert-388160_1280.jpg") 50% 0 repeat fixed; + } + +#logo { + padding: 0; + margin: 0; + min-height: 12em; + background: url("/App_Themes/images/logo.s.png") 1em 1em no-repeat fixed; +} + + + +h1, h2, h3 { background-color: rgba(256,256,256,.5); } + +nav { +transition: margin 2s, padding 2s; + margin: 2em; + padding: 2em; + display: block; + border-radius:1em; + background: url("/App_Themes/images/live-concert-388160_1280.jpg") 50% 10em repeat fixed ; + justify-content: space-around; +} +nav li { display: inline-block; } +main { +transition: margin 2s, padding 2s; + margin: 2em; + padding: 2em; + display: block; + border-radius:1em; + background: url("/App_Themes/images/musician-923526_1.nbbi.jpg") 50% 20em repeat fixed ; + } + +footer { + transition: margin 2s, padding 2s; + background: url("/App_Themes/images/live-concert-388160_1280.jpg") 50% 30em repeat fixed ; + margin: 0; + margin-top: 2em; + padding: 2em; + display: block; + clear: both; + font-size: smaller; + justify-content: space-around; +} +footer { + border-radius:1em; + padding:1em; + } +legend { + border-radius:5px; + padding:.5em; + background-color: rgba(240,240,240,.5); + } +#copyr { text-align: center; display: block; background-color: rgba(250,250,250,.8); } +footer p { display:inline-block; } + +footer img { max-height: 3em; vertical-align: middle; } +a img, h1 img, .menuitem img { vertical-align: middle; } + +#gspacer { + background-color: rgba(209,209,209,.8); + border-radius:1em; + margin:1em; padding:1em; display: inline-block } + +form { + background-color: rgba(150,150,256,0.8); + } + +fieldset { + background-color: rgba(216,216,256,0.8); + border-radius:5px; border: solid 1px #000060; +} + +.post video, .post img { + max-width:100%; + max-height:75%; + padding: 1em; + } + +aside { + display: inline-block; + float: right; + max-width: 40em; +} + +.postpreview { + display: inline-block; + padding: 1em; + background-color: rgba(233,233,233,0.8); + border-radius:1em; + max-width: 100%; +} + +img.postpreviewphoto, .postpreview video, .postpreview img { + padding: 1em; + width: 100%; + } + +.post { + display:block; + padding: 1em; + background-color: rgba(256,256,256,0.8); + border-radius:1em; + } +.hiddenpost { background-color: rgba(160,160,160,0.5); } +.fullwidth { width: 100%; } + +textarea.fullwidth { min-height:10em; } +a { color: rgb(0,56,0); } +a:hover { +background-color: rgba(160,160,160,.7); + } + +footer a { + transition: margin 2s, padding 2s; + border-radius:1em; + margin:1em; + padding:1em; + text-decoration: none; + display:inline-block; + color: black; + font-weight: bold; + border: solid black 1px; + background-color: rgba(220,220,220,.8); + cursor: pointer; + font-family: 'Arial', cursive; +} +.panel { max-width: 24em; display:inline-block; padding: 1em; } +.panel,.bshpanel, aside { + background-color: rgba(200,200,200,.8); + border-radius: 1em; + padding: 1em; +} +.spanel { + max-width: 18em; + display: inline-block; + padding: .3em; +} +.xspanel { + max-width:13em; + display: inline-block; + padding:.2em; +} +.xxspanel { + max-width:7em; + display: inline-block; + padding:.1em; +} +.hint { + display: inline; + font-style: italic; + font-size: smaller; +} +.hint::before { +content: "("; +} +.hint::after { +content: ")"; +} + + +.usertitleref { + border-radius: 1em; + background-color:rgba(256,256,212,0.6); + font-family: 'Arial', cursive; + padding: 1em; +} +label { + font-size: medium; +} +.editable { + margin: .5em; + min-height:1em; + border-radius: 1em; + border: dashed rgb(200,200,256) 2px; +} + +.notification { + font-size: large; + background-color: rgba(264,264,128,0.5); + border: solid green 1px; + padding: 1em; + border-radius:1em; + margin:1em; + padding:1em; + } +.dirty { + background-color: rgba(256,228,128,0.5); +} +.error, #error { + color: #f88; + font-size: large; + background-color: rgba(256,.5); +} +.validation-summary-errors{ + color: #f88; + background-color: rgba(256,256,139,0.5); +} + +.hidden { display:none; } + +ul.preview li:nth-child(-n+10) { + display:inline; + font-size:xx-small; +} + +ul.preview li:nth-child(n) { + display:none; +} + +.validation-summary-errors{ + color: #f88; +} + +a.menuitem { + display:inline-block; + color: black; + font-weight: bold; + border-radius:1em; + border: solid black 1px; + background-color: rgba(220,220,220,.8); + cursor: pointer; + font-family: 'Arial', cursive; + padding: 1em; +} + + + .actionlink { + text-decoration: none; + display:inline-block; + color: black; + font-weight: bold; + border-radius:1em; + border: solid black 1px; + background-color: rgba(220,220,220,.8); + cursor: pointer; + font-family: 'Arial', cursive; + padding: 1em; +} + +input, select, textarea { + color: black; + background-color:rgba(256,256,256,0.8); + border: solid 1px rgb(128,128,128); + border-radius:1em; + font-family: 'Arial', cursive; +} + +a:active { + background-color:rgba(184,180,132,0.9); +} + + input:hover, textarea:hover { + color: white; + background-color:rgba(164,164,164,0.8); +} +.code { + font-family: "monospace"; + background-color: rgba(230,230,230,0.5); + border-radius:25px; + white-space: pre-wrap; +} +#avatar { + float: left; + margin:1em; + } + +.comment { + border-radius:25px; + font-size: smaller; + } + +.onhover { + display:none; + position: absolute; +} + +.ohafter:hover + .onhover, .ohinside:hover > .onhover { + display:block; + z-index:2; + padding:5px; margin:5px; + background-color: rgba(240,240,250,.8); +} + +.input-validation-error { border: solid 1px red; } +.field-validation-error { color: red; } + +.c2 { font-size: small; font-style: italic; } +.c3 { font-size: x-small; font-style: italic; } + +header h1, .actionlink, .menuitem {transition: padding 2s; padding:1em;} + +@media print { + body {background-color:white;color:black;} + .control, .actionlink, .menuitem, nav { display:none;} + } + + .bshpanel { display:block; } + .bsh { display: none; } + .c3 { display:initial; } + .c3-alt { display:none; } + +@media all and (max-width: 640px) { +#logo { + min-height: 6em; + background: url("/App_Themes/images/logo.xs.png") 0 0 no-repeat fixed; +} + +header { + background: url("/App_Themes/images/live-concert-388160_1280.s.jpg") 50% 0 repeat fixed; + } + +#avatar { + margin:.5em; + } + +header h1, .actionlink, .menuitem { padding:.5em;} + + nav { + margin: 1em; + padding: 1em; + background: url("/App_Themes/images/live-concert-388160_1280.s.jpg") 50% 10% repeat fixed ; + } + main { + margin: 1em; + padding: 1em; + background: url("/App_Themes/images/musician-923526_1.nbbi.xs.jpg") 50% 20em repeat fixed ; + } + footer { + background: url("/App_Themes/images/live-concert-388160_1280.s.jpg") 50% 90% repeat fixed ; + } + footer a { + border-radius:.5em; + margin:.5em; + padding:.5em; + } + + .notification { + padding: .5em; + border-radius:.5em; + margin:.5em; + padding:.5em; + } + .menuitem { + display: block; + } + .post { + margin:.3em; + padding:.3em; + } + .usertitleref{ + padding:.3em; + } + .bshpanel { cursor:zoom-in; } + + .c2 { display:initial; } + .c2-alt { display:none; } + .c3 { display:none; } + .c3-alt { display:initial; } + #gspacer { + border-radius:.5em; + margin:.5em; padding:.5em; } +} + +@media all and (max-width: 350px) { +#logo { + min-height: 3em; + background: url("/App_Themes/images/logo.xxs.png") 0 0 no-repeat fixed; +} +header { + background: url("/App_Themes/images/live-concert-388160_1280.xxs.jpg") -1em -1em repeat fixed; + } + +header h1, .actionlink, .menuitem { padding:.2em;} + + nav { + margin: .5em; + padding: .5em; + background: url("/App_Themes/images/live-concert-388160_1280.xxs.jpg") 50% 10% repeat fixed ; + } + main { + margin: .5em; + padding: .5em; + background: url("/App_Themes/images/musician-923526_1.nbbi.xxs.jpg") 50% 20em repeat fixed ; + } + footer { + background: url("/App_Themes/images/live-concert-388160_1280.xxs.jpg") 50% 90% repeat fixed ; + } + footer { + border-radius:.2em; + margin:.2em; + padding:.2em; + } + .c2 { display:none; } + .c2-alt { display:initial; } + #gspacer { + border-radius:.2em; + margin:.2em; padding:.2em; } +} + diff --git a/web/App_Themes/dark/style.css b/web/App_Themes/dark/style.css new file mode 100644 index 00000000..201837dc --- /dev/null +++ b/web/App_Themes/dark/style.css @@ -0,0 +1,345 @@ + +body { + font-family: 'Arial', cursive; + padding: 0; + margin: 0; + background-color: black; + color: #D0FFD0; +} +.tagname { color: #D0FFD0; } +.tagname+.tagname:before { content: ', '; } +.tagname:hover { cursor:pointer; background-color: red; } + + +.rate { + border-radius:1em; + border: solid rgb(128,128,0) 1px; + background-color: rgba(20,20,20,.8); + color: yellow; +} +.rate:hover { border-color: green; + background-color:rgba(30,0,124,0.9); } + +input, textarea, checkbox { + color: #FFFFA0; + background-color: black; + } + +header { + transition: margin 2s, padding 2s; + padding: 0; + margin: 0; + padding-top: 2em; + padding-bottom:2em; + display: block; + background: url("/App_Themes/images/star-939235_1280.jpg") -3em -3em no-repeat fixed; + } + +header h1, header a { +transition:padding 2s; +background-color: rgba(0,0,0,.5); +margin:0; padding:1em; +} + +nav { +transition: margin 2s, padding 2s; + margin: 2em; + padding: 2em; + display: block; + border-radius:1em; + background: url("/App_Themes/images/helix-nebula-1400x1400.s.jpg") 50% 10% repeat fixed ; + justify-content: space-around; +} + +main { +transition: margin 2s, padding 2s; + margin: 2em; + padding: 2em; + display: block; + border-radius:1em; + background: url("/App_Themes/images/p8-av4.png") 50% 20em no-repeat fixed ; + } + +footer { +transition: margin 2s, padding 2s; + background: url("/App_Themes/images/helix-nebula-1400x1400.s.jpg") 50% 90% repeat fixed ; + margin: 0; + margin-top: 2em; + padding: 2em; + display: block; + clear: both; + font-size: smaller; + justify-content: space-around; +} + +#copyr { text-align: center; display: block; background-color: rgba(20,20,20,.8); } +footer p { display:inline-block; } + +footer img { max-height: 3em; vertical-align: middle; } +a img, h1 img, .menuitem img { vertical-align: middle; } +fieldset { + background-color: rgba(16,16,64,0.8); + border-radius:5px; border: solid 1px #000060; +} + +legend { + background-color: rgba(0,0,32,.5); + } + +#gspacer { + background-color: rgba(20,20,20,.8); + border-radius:1em; + margin:1em; padding:1em; display: inline-block } + + +main video, main img { + max-width:100%; + max-height:75%; + padding: 1em; + } + +aside { + display: inline-block; + float: right; + max-width: 40em; +} + +.postpreview { + display: inline-block; + max-width: 40em; + padding: 1em; + background-color: rgba(0,0,32,0.8); + border-radius:1em; +} +.postpreview video, .postpreview img { + max-width: 100%; + padding: 1em; + } +.post { + display:block; + padding: 1em; + background-color: rgba(0,0,32,0.8); + color: #eee; + border-radius:1em; + } +.hiddenpost { background-color: rgba(16,16,16,0.5); } +.fullwidth { width: 100%; } + +textarea.fullwidth { min-height:10em; } + +.thanks { + max-width: 10%; + text-align: center; + font-size:smaller; + display:inline; + bottom:0; +} +.panel { +display:inline-block; + } + +.panel,.bshpanel, aside { + background-color: rgba(20,20,20,.8); + border-radius: 1em; + padding: 1em; +} +.spanel { + max-width: 18em; + display: inline-block; + padding: .3em; +} +.xspanel { + max-width:13em; + display: inline-block; + padding:.2em; +} +.xxspanel { + max-width:7em; + display: inline-block; + padding:.1em; +} +.hint { + display: inline; + font-style: italic; + font-size: smaller; +} +.hint::before { +content: "("; +} +.hint::after { +content: ")"; +} + + +.usertitleref { + border-radius: 1em; + background-color:rgba(0,0,32,0.6); + font-family: 'Arial', cursive; + padding: 1em; +} +label { + font-size: medium; +} +.editable { + margin: .5em; + min-height:1em; + border-radius: 1em; + border: dashed rgb(020,20,256) 2px; +} + +.notification { + font-size: large; + background-color: rgba(64,64,0,0.5); + border: solid green 1px; + padding: 1em; + border-radius:1em; + margin:1em; + padding:1em; + } +.dirty { + background-color: rgba(128,128,0,0.5); +} +.error, #error { + color: #f88; + font-size: large; + background-color: rgba(256,0,0,0.5); +} +.validation-summary-errors{ + color: #f88; + background-color: rgba(256,0,0,0.5); +} + +.hidden { display:none; } + +ul.preview li:nth-child(-n+10) { + display:inline; + font-size:xx-small; +} + +ul.preview li:nth-child(n) { + display:none; +} + +.validation-summary-errors{ + color: #f88; +} + +a { + text-decoration: none; +} + +.actionlink, .menuitem, a { + border-color: rgb(128,128,0); + background-color: rgba(20,20,20,.8); + color: yellow; +} + +input, select, textarea { + color: white; + background-color:rgba(0,0,64,0.8); + border-color: rgb(128,128,128); +} + +a:hover { + background-color:rgba(30,0,124,0.9); + border-color: green ; +} +a:active { + background-color:rgba(124,0,32,0.9); +} + +input:hover, textarea:hover { + color: white; + background-color:rgba(64,64,64,0.8); +} +.code { + background-color: rgba(0,0,256,0.1); +} + + +@media all and (max-width: 640px) { +header { + padding-top:1em; + padding-bottom:1em; + background: url("/App_Themes/images/star-939235_1280.s.jpg") 0 0 no-repeat fixed; + } + +#avatar { + margin:.5em; + } + +header h1, header a , .actionlink, .menuitem, a { padding:.5em;} + + nav { + margin: 1em; + padding: 1em; + background: url("/App_Themes/images/helix-nebula-1400x1400.s.jpg") 50% 10% repeat fixed ; + } + main { + margin: 1em; + padding: 1em; + background: url("/App_Themes/images/p8-av4.s.jpg") 50% 20em no-repeat fixed ; + } + footer { + background: url("/App_Themes/images/helix-nebula-1400x1400.s.jpg") 50% 90% repeat fixed ; + } + footer a { + border-radius:.5em; + margin:.5em; + padding:.5em; + } + + .notification { + padding: .5em; + border-radius:.5em; + margin:.5em; + padding:.5em; + } + .menuitem { + display: block; + } + .post { + margin:.3em; + padding:.3em; + } + .usertitleref{ + padding:.3em; + } + .bshpanel { cursor:zoom-in; } + + .c2 { display:initial; } + .c2-alt { display:none; } + .c3 { display:none; } + .c3-alt { display:initial; } +} + +@media all and (max-width: 350px) { +header { + padding-top:.5em; + padding-bottom:.5em; + background: url("/App_Themes/images/star-939235_1280.xxs.jpg") 0 0 no-repeat fixed; + } + +header h1, header a { padding:.2em;} + + nav { + margin: .5em; + padding: .5em; + background: url("/App_Themes/images/helix-nebula-1400x1400.xxs.jpg") 50% 10% repeat fixed ; + } + main { + margin: .5em; + padding: .5em; + background: url("/App_Themes/images/p8-av4.xxs.jpg") 50% 20em repeat fixed ; + } + footer { + background: url("/App_Themes/images/helix-nebula-1400x1400.xxs.jpg") 50% 10% repeat fixed ; + } + footer a { + border-radius:.2em; + margin:.2em; + padding:.2em; + } + .c2 { display:none; } + .c2-alt { display:initial; } +} diff --git a/web/App_Themes/delete.gif b/web/App_Themes/delete.gif new file mode 100644 index 00000000..43c6ca87 Binary files /dev/null and b/web/App_Themes/delete.gif differ diff --git a/web/App_Themes/images/facebook.png b/web/App_Themes/images/facebook.png new file mode 100644 index 00000000..f76fa1fc Binary files /dev/null and b/web/App_Themes/images/facebook.png differ diff --git a/web/App_Themes/images/helix-nebula.l.jpg b/web/App_Themes/images/helix-nebula.l.jpg new file mode 100644 index 00000000..4ad74b48 Binary files /dev/null and b/web/App_Themes/images/helix-nebula.l.jpg differ diff --git a/web/App_Themes/images/helix-nebula.s.jpg b/web/App_Themes/images/helix-nebula.s.jpg new file mode 100644 index 00000000..25da63ad Binary files /dev/null and b/web/App_Themes/images/helix-nebula.s.jpg differ diff --git a/web/App_Themes/images/helix-nebula.xs.jpg b/web/App_Themes/images/helix-nebula.xs.jpg new file mode 100644 index 00000000..54c5f77f Binary files /dev/null and b/web/App_Themes/images/helix-nebula.xs.jpg differ diff --git a/web/App_Themes/images/logo-1.jpg b/web/App_Themes/images/logo-1.jpg new file mode 100644 index 00000000..8583a5c7 Binary files /dev/null and b/web/App_Themes/images/logo-1.jpg differ diff --git a/web/App_Themes/images/logo-1.png b/web/App_Themes/images/logo-1.png new file mode 100644 index 00000000..4c256a99 Binary files /dev/null and b/web/App_Themes/images/logo-1.png differ diff --git a/web/App_Themes/images/logo.jpg b/web/App_Themes/images/logo.jpg new file mode 100644 index 00000000..0cb4b4e9 Binary files /dev/null and b/web/App_Themes/images/logo.jpg differ diff --git a/web/App_Themes/images/logo.s.png b/web/App_Themes/images/logo.s.png new file mode 100644 index 00000000..17f5a0aa Binary files /dev/null and b/web/App_Themes/images/logo.s.png differ diff --git a/web/App_Themes/images/logo.xs.png b/web/App_Themes/images/logo.xs.png new file mode 100644 index 00000000..a6258a51 Binary files /dev/null and b/web/App_Themes/images/logo.xs.png differ diff --git a/web/App_Themes/images/logo.xxs.png b/web/App_Themes/images/logo.xxs.png new file mode 100644 index 00000000..8fc7e20f Binary files /dev/null and b/web/App_Themes/images/logo.xxs.png differ diff --git a/web/App_Themes/images/p8-av4.xxs.png b/web/App_Themes/images/p8-av4.xxs.png new file mode 100644 index 00000000..629f7bcd Binary files /dev/null and b/web/App_Themes/images/p8-av4.xxs.png differ diff --git a/web/App_Themes/images/star-939235_1280.xs.jpg b/web/App_Themes/images/star-939235_1280.xs.jpg new file mode 100644 index 00000000..00ab82d0 Binary files /dev/null and b/web/App_Themes/images/star-939235_1280.xs.jpg differ diff --git a/web/App_Themes/images/twiter.png b/web/App_Themes/images/twiter.png new file mode 100644 index 00000000..75ab78a8 Binary files /dev/null and b/web/App_Themes/images/twiter.png differ diff --git a/web/AuthorizeAttribute.cs b/web/AuthorizeAttribute.cs new file mode 100644 index 00000000..05ddb9ab --- /dev/null +++ b/web/AuthorizeAttribute.cs @@ -0,0 +1,58 @@ +// +// AuthorizeAttribute.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; + +namespace Yavsc +{ + /// + /// Authorize attribute. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = true)] + public class AuthorizeAttribute: System.Web.Mvc.AuthorizeAttribute + { + /// + /// Handles the unauthorized request. + /// + /// Filter context. + protected override void HandleUnauthorizedRequest(System.Web.Mvc.AuthorizationContext filterContext) + { + if (filterContext.HttpContext.Request.IsAuthenticated) + { + if (string.IsNullOrWhiteSpace (Users) && !string.IsNullOrEmpty (Roles)) + { + // let the client know which role were allowed here + // filterContext.ActionDescriptor.ControllerDescriptor. + filterContext.Result = new System.Web.Mvc.RedirectResult ("~/Home/RestrictedArea"); + filterContext.Controller.ViewData ["ActionName"] = filterContext.ActionDescriptor.ActionName; + filterContext.Controller.ViewData ["ControllerName"] = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; + filterContext.Controller.ViewData ["Roles"] = Roles; + filterContext.Controller.ViewData ["Users"] = Users; + } + else filterContext.Result = new System.Web.Mvc.HttpStatusCodeResult((int)System.Net.HttpStatusCode.Forbidden); + } + else + { + base.HandleUnauthorizedRequest(filterContext); + } + } + } +} + diff --git a/web/Helpers/Google/GoogleHelpers.cs b/web/Helpers/Google/GoogleHelpers.cs new file mode 100644 index 00000000..f60f802e --- /dev/null +++ b/web/Helpers/Google/GoogleHelpers.cs @@ -0,0 +1,98 @@ +// +// GoogleHelpers.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using Yavsc.Model.Google; +using System.Web.Profile; +using System.Configuration; +using System.Web; + +namespace Yavsc.Helpers.Google +{ + /// + /// Google helpers. + /// + public static class GoogleHelpers + { + /// + /// Gets the events. + /// + /// The events. + /// Profile. + /// Mindate. + /// Maxdate. + public static CalendarEventList GetEvents(this ProfileBase profile, DateTime mindate, DateTime maxdate) + { + string gcalid = (string) profile.GetPropertyValue ("gcalid"); + if (string.IsNullOrWhiteSpace (gcalid)) + throw new ArgumentException ("NULL gcalid"); + CalendarApi c = new CalendarApi (clientApiKey); + string creds = OAuth2.GetFreshGoogleCredential (profile); + return c.GetCalendar (gcalid, mindate, maxdate, creds); + } + /// + /// Gets the calendars. + /// + /// The calendars. + /// Profile. + public static CalendarList GetCalendars (this ProfileBase profile) + { + string cred = OAuth2.GetFreshGoogleCredential (profile); + CalendarApi c = new CalendarApi (clientApiKey); + return c.GetCalendars (cred); + } + + private static string clientId = ConfigurationManager.AppSettings ["GOOGLE_CLIENT_ID"]; + private static string clientSecret = ConfigurationManager.AppSettings ["GOOGLE_CLIENT_SECRET"]; + private static string clientApiKey = ConfigurationManager.AppSettings ["GOOGLE_API_KEY"]; + /// + /// Login the specified response, state and callBack. + /// + /// Response. + /// State. + /// Call back. + public static void Login(this HttpResponseBase response, string state, string callBack) + { + OAuth2 oa = new OAuth2 (callBack, clientId,clientSecret); + oa.Login (response, state); + } + /// + /// Cals the login. + /// + /// Response. + /// State. + /// Call back. + public static void CalLogin(this HttpResponseBase response, string state, string callBack) + { + OAuth2 oa = new OAuth2 (callBack,clientId,clientSecret); + oa.GetCalendarScope (response, state); + } + /// + /// Creates the O auth2. + /// + /// The O auth2. + /// Call back. + public static OAuth2 CreateOAuth2(string callBack) + { + return new OAuth2 (callBack,clientId,clientSecret); + } + } +} + diff --git a/web/Scripts/jquery.unobtrusive-ajax.js b/web/Scripts/jquery.unobtrusive-ajax.js new file mode 100644 index 00000000..330c04ed --- /dev/null +++ b/web/Scripts/jquery.unobtrusive-ajax.js @@ -0,0 +1,189 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. Insofar as a script file is dual licensed under GPL, + * Microsoft neither took the code under GPL nor distributes it thereunder but + * under the terms set out in this paragraph. All notices and licenses + * below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ +/*! +** 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_target = "unobtrusiveAjaxClickTarget", + 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; + case "REPLACE-WITH": + $(update).replaceWith(data); + 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 = parseInt(element.getAttribute("data-ajax-loading-duration"), 10) || 0; + + $.extend(options, { + type: element.getAttribute("data-ajax-method") || undefined, + url: element.getAttribute("data-ajax-url") || undefined, + cache: !!element.getAttribute("data-ajax-cache"), + beforeSend: function (xhr) { + var result; + asyncOnBeforeSend(xhr, method); + result = getFunction(element.getAttribute("data-ajax-begin"), ["xhr"]).apply(element, arguments); + if (result !== false) { + loading.show(duration); + } + return result; + }, + complete: function () { + loading.hide(duration); + getFunction(element.getAttribute("data-ajax-complete"), ["xhr", "status"]).apply(element, 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(element, arguments); + }, + error: function () { + getFunction(element.getAttribute("data-ajax-failure"), ["xhr", "status", "error"]).apply(element, arguments); + } + }); + + 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(); + } + + $(document).on("click", "a[data-ajax=true]", function (evt) { + evt.preventDefault(); + asyncRequest(this, { + url: this.href, + type: "GET", + data: [] + }); + }); + + $(document).on("click", "form[data-ajax=true] input[type=image]", 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); + }); + + $(document).on("click", "form[data-ajax=true] :submit", function (evt) { + var name = evt.currentTarget.name, + target = $(evt.target), + form = $(target.parents("form")[0]); + + form.data(data_click, name ? [{ name: name, value: evt.currentTarget.value }] : []); + form.data(data_target, target); + + setTimeout(function () { + form.removeData(data_click); + form.removeData(data_target); + }, 0); + }); + + $(document).on("submit", "form[data-ajax=true]", function (evt) { + var clickInfo = $(this).data(data_click) || [], + clickTarget = $(this).data(data_target), + isCancel = clickTarget && clickTarget.hasClass("cancel"); + evt.preventDefault(); + if (!isCancel && !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/web/Scripts/jquery.unobtrusive-ajax.min.js b/web/Scripts/jquery.unobtrusive-ajax.min.js new file mode 100644 index 00000000..333d7070 --- /dev/null +++ b/web/Scripts/jquery.unobtrusive-ajax.min.js @@ -0,0 +1,19 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. Insofar as a script file is dual licensed under GPL, + * Microsoft neither took the code under GPL nor distributes it thereunder but + * under the terms set out in this paragraph. All notices and licenses + * below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ +/* +** Unobtrusive Ajax support library for jQuery +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ +(function(a){var b="unobtrusiveAjaxClick",d="unobtrusiveAjaxClickTarget",h="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 e(a){return a==="GET"||a==="POST"}function g(b,a){!e(a)&&b.setRequestHeader("X-HTTP-Method-Override",a)}function i(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;case"REPLACE-WITH":a(c).replaceWith(b);break;default:a(c).html(b)}})}function f(b,d){var j,k,f,h;j=b.getAttribute("data-ajax-confirm");if(j&&!window.confirm(j))return;k=a(b.getAttribute("data-ajax-loading"));h=parseInt(b.getAttribute("data-ajax-loading-duration"),10)||0;a.extend(d,{type:b.getAttribute("data-ajax-method")||undefined,url:b.getAttribute("data-ajax-url")||undefined,cache:!!b.getAttribute("data-ajax-cache"),beforeSend:function(d){var a;g(d,f);a=c(b.getAttribute("data-ajax-begin"),["xhr"]).apply(b,arguments);a!==false&&k.show(h);return a},complete:function(){k.hide(h);c(b.getAttribute("data-ajax-complete"),["xhr","status"]).apply(b,arguments)},success:function(a,e,d){i(b,a,d.getResponseHeader("Content-Type")||"text/html");c(b.getAttribute("data-ajax-success"),["data","status","xhr"]).apply(b,arguments)},error:function(){c(b.getAttribute("data-ajax-failure"),["xhr","status","error"]).apply(b,arguments)}});d.data.push({name:"X-Requested-With",value:"XMLHttpRequest"});f=d.type.toUpperCase();if(!e(f)){d.type="POST";d.data.push({name:"X-HTTP-Method-Override",value:f})}a.ajax(d)}function j(c){var b=a(c).data(h);return!b||!b.validate||b.validate()}a(document).on("click","a[data-ajax=true]",function(a){a.preventDefault();f(this,{url:this.href,type:"GET",data:[]})});a(document).on("click","form[data-ajax=true] input[type=image]",function(c){var g=c.target.name,e=a(c.target),f=a(e.parents("form")[0]),d=e.offset();f.data(b,[{name:g+".x",value:Math.round(c.pageX-d.left)},{name:g+".y",value:Math.round(c.pageY-d.top)}]);setTimeout(function(){f.removeData(b)},0)});a(document).on("click","form[data-ajax=true] :submit",function(e){var g=e.currentTarget.name,f=a(e.target),c=a(f.parents("form")[0]);c.data(b,g?[{name:g,value:e.currentTarget.value}]:[]);c.data(d,f);setTimeout(function(){c.removeData(b);c.removeData(d)},0)});a(document).on("submit","form[data-ajax=true]",function(h){var e=a(this).data(b)||[],c=a(this).data(d),g=c&&c.hasClass("cancel");h.preventDefault();if(!g&&!j(this))return;f(this,{url:this.action,type:this.method||"GET",data:e.concat(a(this).serializeArray())})})})(jQuery); \ No newline at end of file diff --git a/web/Scripts/jquery.validate-vsdoc.js b/web/Scripts/jquery.validate-vsdoc.js new file mode 100644 index 00000000..920d8eb3 --- /dev/null +++ b/web/Scripts/jquery.validate-vsdoc.js @@ -0,0 +1,1288 @@ +/* +* 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.14.0 +*/ + +/* +* 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 - v1.14.0 - 2/4/2013 +* https://github.com/jzaefferer/jquery-validation +* Copyright (c) 2013 Jörn Zaefferer; Licensed MIT +* +*/ + +(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/web/Scripts/jquery.validate.js b/web/Scripts/jquery.validate.js new file mode 100644 index 00000000..4e979bcf --- /dev/null +++ b/web/Scripts/jquery.validate.js @@ -0,0 +1,1398 @@ +/*! + * jQuery Validation Plugin v1.14.0 + * + * http://jqueryvalidation.org/ + * + * Copyright (c) 2015 Jörn Zaefferer + * Released under the MIT license + */ +(function( factory ) { + if ( typeof define === "function" && define.amd ) { + define( ["jquery"], factory ); + } else { + factory( jQuery ); + } +}(function( $ ) { + +$.extend($.fn, { + // http://jqueryvalidation.org/validate/ + validate: function( options ) { + + // if nothing is selected, return nothing; can't chain anyway + if ( !this.length ) { + if ( 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; + } + + // Add novalidate tag if HTML5. + this.attr( "novalidate", "novalidate" ); + + validator = new $.validator( options, this[ 0 ] ); + $.data( this[ 0 ], "validator", validator ); + + if ( validator.settings.onsubmit ) { + + this.on( "click.validate", ":submit", function( event ) { + if ( validator.settings.submitHandler ) { + validator.submitButton = event.target; + } + + // allow suppressing validation by adding a cancel class to the submit button + if ( $( this ).hasClass( "cancel" ) ) { + validator.cancelSubmit = true; + } + + // allow suppressing validation by adding the html5 formnovalidate attribute to the submit button + if ( $( this ).attr( "formnovalidate" ) !== undefined ) { + validator.cancelSubmit = true; + } + }); + + // validate the form on submit + this.on( "submit.validate", function( event ) { + if ( validator.settings.debug ) { + // prevent form submit to be able to see console output + event.preventDefault(); + } + function handle() { + var hidden, result; + if ( validator.settings.submitHandler ) { + if ( validator.submitButton ) { + // insert a hidden input as a replacement for the missing submit button + hidden = $( "" ) + .attr( "name", validator.submitButton.name ) + .val( $( validator.submitButton ).val() ) + .appendTo( validator.currentForm ); + } + result = validator.settings.submitHandler.call( validator, validator.currentForm, event ); + if ( validator.submitButton ) { + // and clean up afterwards; thanks to no-block-scope, hidden can be referenced + hidden.remove(); + } + if ( result !== undefined ) { + return result; + } + 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://jqueryvalidation.org/valid/ + valid: function() { + var valid, validator, errorList; + + if ( $( this[ 0 ] ).is( "form" ) ) { + valid = this.validate().form(); + } else { + errorList = []; + valid = true; + validator = $( this[ 0 ].form ).validate(); + this.each( function() { + valid = validator.element( this ) && valid; + errorList = errorList.concat( validator.errorList ); + }); + validator.errorList = errorList; + } + return valid; + }, + + // http://jqueryvalidation.org/rules/ + rules: function( command, argument ) { + var element = this[ 0 ], + settings, staticRules, existingRules, data, param, filtered; + + if ( command ) { + settings = $.data( element.form, "validator" ).settings; + staticRules = settings.rules; + existingRules = $.validator.staticRules( element ); + switch ( command ) { + case "add": + $.extend( existingRules, $.validator.normalizeRule( argument ) ); + // remove messages from rules, but allow them to be set separately + delete existingRules.messages; + 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; + } + filtered = {}; + $.each( argument.split( /\s/ ), function( index, method ) { + filtered[ method ] = existingRules[ method ]; + delete existingRules[ method ]; + if ( method === "required" ) { + $( element ).removeAttr( "aria-required" ); + } + }); + return filtered; + } + } + + data = $.validator.normalizeRules( + $.extend( + {}, + $.validator.classRules( element ), + $.validator.attributeRules( element ), + $.validator.dataRules( element ), + $.validator.staticRules( element ) + ), element ); + + // make sure required is at front + if ( data.required ) { + param = data.required; + delete data.required; + data = $.extend( { required: param }, data ); + $( element ).attr( "aria-required", "true" ); + } + + // make sure remote is at back + if ( data.remote ) { + param = data.remote; + delete data.remote; + data = $.extend( data, { remote: param }); + } + + return data; + } +}); + +// Custom selectors +$.extend( $.expr[ ":" ], { + // http://jqueryvalidation.org/blank-selector/ + blank: function( a ) { + return !$.trim( "" + $( a ).val() ); + }, + // http://jqueryvalidation.org/filled-selector/ + filled: function( a ) { + return !!$.trim( "" + $( a ).val() ); + }, + // http://jqueryvalidation.org/unchecked-selector/ + unchecked: function( a ) { + return !$( a ).prop( "checked" ); + } +}); + +// constructor for validator +$.validator = function( options, form ) { + this.settings = $.extend( true, {}, $.validator.defaults, options ); + this.currentForm = form; + this.init(); +}; + +// http://jqueryvalidation.org/jQuery.validator.format/ +$.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" ), function() { + return n; + }); + }); + return source; +}; + +$.extend( $.validator, { + + defaults: { + messages: {}, + groups: {}, + rules: {}, + errorClass: "error", + validClass: "valid", + errorElement: "label", + focusCleanup: false, + focusInvalid: true, + errorContainer: $( [] ), + errorLabelContainer: $( [] ), + onsubmit: true, + ignore: ":hidden", + ignoreTitle: false, + onfocusin: function( element ) { + this.lastActive = element; + + // Hide error label and remove error class on focus if enabled + if ( this.settings.focusCleanup ) { + if ( this.settings.unhighlight ) { + this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); + } + this.hideThese( this.errorsFor( element ) ); + } + }, + onfocusout: function( element ) { + if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { + this.element( element ); + } + }, + onkeyup: function( element, event ) { + // Avoid revalidate the field when pressing one of the following keys + // Shift => 16 + // Ctrl => 17 + // Alt => 18 + // Caps lock => 20 + // End => 35 + // Home => 36 + // Left arrow => 37 + // Up arrow => 38 + // Right arrow => 39 + // Down arrow => 40 + // Insert => 45 + // Num lock => 144 + // AltGr key => 225 + var excludedKeys = [ + 16, 17, 18, 20, 35, 36, 37, + 38, 39, 40, 45, 144, 225 + ]; + + if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { + return; + } else 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 ) { + if ( element.type === "radio" ) { + this.findByName( element.name ).addClass( errorClass ).removeClass( validClass ); + } else { + $( element ).addClass( errorClass ).removeClass( validClass ); + } + }, + unhighlight: function( element, errorClass, validClass ) { + if ( element.type === "radio" ) { + this.findByName( element.name ).removeClass( errorClass ).addClass( validClass ); + } else { + $( element ).removeClass( errorClass ).addClass( validClass ); + } + } + }, + + // http://jqueryvalidation.org/jQuery.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.", + 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 = {} ), + rules; + $.each( this.settings.groups, function( key, value ) { + if ( typeof value === "string" ) { + value = value.split( /\s/ ); + } + $.each( value, function( index, name ) { + groups[ name ] = key; + }); + }); + rules = this.settings.rules; + $.each( rules, function( key, value ) { + rules[ key ] = $.validator.normalizeRule( value ); + }); + + function delegate( event ) { + var validator = $.data( this.form, "validator" ), + eventType = "on" + event.type.replace( /^validate/, "" ), + settings = validator.settings; + if ( settings[ eventType ] && !$( this ).is( settings.ignore ) ) { + settings[ eventType ].call( validator, this, event ); + } + } + + $( this.currentForm ) + .on( "focusin.validate focusout.validate keyup.validate", + ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + + "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + + "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + + "[type='radio'], [type='checkbox']", delegate) + // Support: Chrome, oldIE + // "select" is provided as event.target when clicking a option + .on("click.validate", "select, option, [type='radio'], [type='checkbox']", delegate); + + if ( this.settings.invalidHandler ) { + $( this.currentForm ).on( "invalid-form.validate", this.settings.invalidHandler ); + } + + // Add aria-required to any Static/Data/Class required fields before first validation + // Screen readers require this attribute to be present before the initial submission http://www.w3.org/TR/WCAG-TECHS/ARIA2.html + $( this.currentForm ).find( "[required], [data-rule-required], .required" ).attr( "aria-required", "true" ); + }, + + // http://jqueryvalidation.org/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://jqueryvalidation.org/Validator.element/ + element: function( element ) { + var cleanElement = this.clean( element ), + checkElement = this.validationTargetFor( cleanElement ), + result = true; + + this.lastElement = checkElement; + + if ( checkElement === undefined ) { + delete this.invalid[ cleanElement.name ]; + } else { + this.prepareElement( checkElement ); + this.currentElements = $( checkElement ); + + result = this.check( checkElement ) !== false; + if ( result ) { + delete this.invalid[ checkElement.name ]; + } else { + this.invalid[ checkElement.name ] = true; + } + } + // Add aria-invalid status for screen readers + $( element ).attr( "aria-invalid", !result ); + + if ( !this.numberOfInvalids() ) { + // Hide error containers on last error + this.toHide = this.toHide.add( this.containers ); + } + this.showErrors(); + return result; + }, + + // http://jqueryvalidation.org/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 ); + }); + } + if ( this.settings.showErrors ) { + this.settings.showErrors.call( this, this.errorMap, this.errorList ); + } else { + this.defaultShowErrors(); + } + }, + + // http://jqueryvalidation.org/Validator.resetForm/ + resetForm: function() { + if ( $.fn.resetForm ) { + $( this.currentForm ).resetForm(); + } + this.submitted = {}; + this.lastElement = null; + this.prepareForm(); + this.hideErrors(); + var i, elements = this.elements() + .removeData( "previousValue" ) + .removeAttr( "aria-invalid" ); + + if ( this.settings.unhighlight ) { + for ( i = 0; elements[ i ]; i++ ) { + this.settings.unhighlight.call( this, elements[ i ], + this.settings.errorClass, "" ); + } + } else { + elements.removeClass( this.settings.errorClass ); + } + }, + + numberOfInvalids: function() { + return this.objectLength( this.invalid ); + }, + + objectLength: function( obj ) { + /* jshint unused: false */ + var count = 0, + i; + for ( i in obj ) { + count++; + } + return count; + }, + + hideErrors: function() { + this.hideThese( this.toHide ); + }, + + hideThese: function( errors ) { + errors.not( this.containers ).text( "" ); + this.addWrapper( errors ).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) + return $( this.currentForm ) + .find( "input, select, textarea" ) + .not( ":submit, :reset, :image, :disabled" ) + .not( this.settings.ignore ) + .filter( function() { + if ( !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() { + var errorClass = this.settings.errorClass.split( " " ).join( "." ); + return $( this.settings.errorElement + "." + 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 ); + }, + + elementValue: function( element ) { + var val, + $element = $( element ), + type = element.type; + + if ( type === "radio" || type === "checkbox" ) { + return this.findByName( element.name ).filter(":checked").val(); + } else if ( type === "number" && typeof element.validity !== "undefined" ) { + return element.validity.badInput ? false : $element.val(); + } + + val = $element.val(); + if ( typeof val === "string" ) { + return val.replace(/\r/g, "" ); + } + return val; + }, + + check: function( element ) { + element = this.validationTargetFor( this.clean( element ) ); + + var rules = $( element ).rules(), + rulesCount = $.map( rules, function( n, i ) { + return i; + }).length, + dependencyMismatch = false, + val = this.elementValue( element ), + result, method, rule; + + for ( method in rules ) { + rule = { method: method, parameters: rules[ method ] }; + try { + + result = $.validator.methods[ method ].call( this, val, 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" && rulesCount === 1 ) { + 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 ) { + if ( this.settings.debug && window.console ) { + console.log( "Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method.", e ); + } + if ( e instanceof TypeError ) { + e.message += ". Exception occurred when checking element " + element.id + ", check the '" + rule.method + "' method."; + } + + 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 HTML5 data attribute + // return the generic message if present and no method specific message is present + customDataMessage: function( element, method ) { + return $( element ).data( "msg" + method.charAt( 0 ).toUpperCase() + + method.substring( 1 ).toLowerCase() ) || $( element ).data( "msg" ); + }, + + // 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.customDataMessage( 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 = $.validator.format( message.replace( theregex, "{$1}" ), rule.parameters ); + } + this.errorList.push({ + message: message, + element: element, + method: rule.method + }); + + 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() { + var i, elements, error; + for ( i = 0; this.errorList[ i ]; i++ ) { + error = this.errorList[ i ]; + if ( 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 ( i = 0; this.successList[ i ]; i++ ) { + this.showLabel( this.successList[ i ] ); + } + } + if ( this.settings.unhighlight ) { + for ( 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 place, group, errorID, + error = this.errorsFor( element ), + elementID = this.idOrName( element ), + describedBy = $( element ).attr( "aria-describedby" ); + if ( error.length ) { + // refresh error/success class + error.removeClass( this.settings.validClass ).addClass( this.settings.errorClass ); + // replace message on existing label + error.html( message ); + } else { + // create error element + error = $( "<" + this.settings.errorElement + ">" ) + .attr( "id", elementID + "-error" ) + .addClass( this.settings.errorClass ) + .html( message || "" ); + + // Maintain reference to the element to be placed into the DOM + place = error; + if ( this.settings.wrapper ) { + // make sure the element is visible, even in IE + // actually showing the wrapped element is handled elsewhere + place = error.hide().show().wrap( "<" + this.settings.wrapper + "/>" ).parent(); + } + if ( this.labelContainer.length ) { + this.labelContainer.append( place ); + } else if ( this.settings.errorPlacement ) { + this.settings.errorPlacement( place, $( element ) ); + } else { + place.insertAfter( element ); + } + + // Link error back to the element + if ( error.is( "label" ) ) { + // If the error is a label, then associate using 'for' + error.attr( "for", elementID ); + } else if ( error.parents( "label[for='" + elementID + "']" ).length === 0 ) { + // If the element is not a child of an associated label, then it's necessary + // to explicitly apply aria-describedby + + errorID = error.attr( "id" ).replace( /(:|\.|\[|\]|\$)/g, "\\$1"); + // Respect existing non-error aria-describedby + if ( !describedBy ) { + describedBy = errorID; + } else if ( !describedBy.match( new RegExp( "\\b" + errorID + "\\b" ) ) ) { + // Add to end of list if not already present + describedBy += " " + errorID; + } + $( element ).attr( "aria-describedby", describedBy ); + + // If this element is grouped, then assign to all elements in the same group + group = this.groups[ element.name ]; + if ( group ) { + $.each( this.groups, function( name, testgroup ) { + if ( testgroup === group ) { + $( "[name='" + name + "']", this.currentForm ) + .attr( "aria-describedby", error.attr( "id" ) ); + } + }); + } + } + } + if ( !message && this.settings.success ) { + error.text( "" ); + if ( typeof this.settings.success === "string" ) { + error.addClass( this.settings.success ); + } else { + this.settings.success( error, element ); + } + } + this.toShow = this.toShow.add( error ); + }, + + errorsFor: function( element ) { + var name = this.idOrName( element ), + describer = $( element ).attr( "aria-describedby" ), + selector = "label[for='" + name + "'], label[for='" + name + "'] *"; + + // aria-describedby should directly reference the error element + if ( describer ) { + selector = selector + ", #" + describer.replace( /\s+/g, ", #" ); + } + return this + .errors() + .filter( selector ); + }, + + idOrName: function( element ) { + return this.groups[ element.name ] || ( this.checkable( element ) ? element.name : element.id || element.name ); + }, + + validationTargetFor: function( element ) { + + // If radio/checkbox, validate first element in group instead + if ( this.checkable( element ) ) { + element = this.findByName( element.name ); + } + + // Always apply ignore filter + return $( element ).not( this.settings.ignore )[ 0 ]; + }, + + checkable: function( element ) { + return ( /radio|checkbox/i ).test( element.type ); + }, + + findByName: function( name ) { + return $( this.currentForm ).find( "[name='" + name + "']" ); + }, + + 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 ) { + return param; + }, + "string": function( param, element ) { + return !!$( param, element.form ).length; + }, + "function": function( param, element ) { + return param( element ); + } + }, + + optional: function( element ) { + var val = this.elementValue( element ); + return !$.validator.methods.required.call( this, val, 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" ) + }); + }, + + // cleans up all forms and elements, removes validator-specific events + destroy: function() { + this.resetForm(); + + $( this.currentForm ) + .off( ".validate" ) + .removeData( "validator" ); + } + + }, + + classRuleSettings: { + required: { required: true }, + email: { email: true }, + url: { url: true }, + date: { date: true }, + dateISO: { dateISO: true }, + number: { number: true }, + digits: { digits: true }, + creditcard: { creditcard: true } + }, + + addClassRules: function( className, rules ) { + if ( className.constructor === String ) { + this.classRuleSettings[ className ] = rules; + } else { + $.extend( this.classRuleSettings, className ); + } + }, + + classRules: function( element ) { + var rules = {}, + classes = $( element ).attr( "class" ); + + if ( classes ) { + $.each( classes.split( " " ), function() { + if ( this in $.validator.classRuleSettings ) { + $.extend( rules, $.validator.classRuleSettings[ this ]); + } + }); + } + return rules; + }, + + normalizeAttributeRule: function( rules, type, method, value ) { + + // convert the value to a number for number inputs, and for text for backwards compability + // allows type="date" and others to be compared as strings + if ( /min|max/.test( method ) && ( type === null || /number|range|text/.test( type ) ) ) { + value = Number( value ); + + // Support Opera Mini, which returns NaN for undefined minlength + if ( isNaN( value ) ) { + value = undefined; + } + } + + if ( value || value === 0 ) { + rules[ method ] = value; + } else if ( type === method && type !== "range" ) { + + // exception: the jquery validate 'range' method + // does not test for the html5 'range' type + rules[ method ] = true; + } + }, + + attributeRules: function( element ) { + var rules = {}, + $element = $( element ), + type = element.getAttribute( "type" ), + method, value; + + for ( method in $.validator.methods ) { + + // support for in both html5 and older browsers + if ( method === "required" ) { + value = element.getAttribute( method ); + + // Some browsers return an empty string for the required attribute + // and non-HTML5 browsers might have required="" markup + if ( value === "" ) { + value = true; + } + + // force non-HTML5 browsers to return bool + value = !!value; + } else { + value = $element.attr( method ); + } + + this.normalizeAttributeRule( rules, type, 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; + }, + + dataRules: function( element ) { + var rules = {}, + $element = $( element ), + type = element.getAttribute( "type" ), + method, value; + + for ( method in $.validator.methods ) { + value = $element.data( "rule" + method.charAt( 0 ).toUpperCase() + method.substring( 1 ).toLowerCase() ); + this.normalizeAttributeRule( rules, type, method, value ); + } + return rules; + }, + + staticRules: function( element ) { + var rules = {}, + 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" ], function() { + if ( rules[ this ] ) { + rules[ this ] = Number( rules[ this ] ); + } + }); + $.each([ "rangelength", "range" ], function() { + var parts; + if ( rules[ this ] ) { + if ( $.isArray( rules[ this ] ) ) { + rules[ this ] = [ Number( rules[ this ][ 0 ]), Number( rules[ this ][ 1 ] ) ]; + } else if ( typeof rules[ this ] === "string" ) { + parts = rules[ this ].replace(/[\[\]]/g, "" ).split( /[\s,]+/ ); + rules[ this ] = [ Number( parts[ 0 ]), Number( parts[ 1 ] ) ]; + } + } + }); + + if ( $.validator.autoCreateRanges ) { + // auto-create ranges + if ( rules.min != null && rules.max != null ) { + rules.range = [ rules.min, rules.max ]; + delete rules.min; + delete rules.max; + } + if ( rules.minlength != null && rules.maxlength != null ) { + rules.rangelength = [ rules.minlength, rules.maxlength ]; + delete rules.minlength; + delete rules.maxlength; + } + } + + 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://jqueryvalidation.org/jQuery.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://jqueryvalidation.org/required-method/ + required: function( value, element, param ) { + // check if dependency is met + if ( !this.depend( param, element ) ) { + return "dependency-mismatch"; + } + if ( element.nodeName.toLowerCase() === "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; + } + if ( this.checkable( element ) ) { + return this.getLength( value, element ) > 0; + } + return value.length > 0; + }, + + // http://jqueryvalidation.org/email-method/ + email: function( value, element ) { + // From https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address + // Retrieved 2014-01-14 + // If you have a problem with this implementation, report a bug against the above spec + // Or use custom methods to implement your own email validation + return this.optional( element ) || /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test( value ); + }, + + // http://jqueryvalidation.org/url-method/ + url: function( value, element ) { + + // Copyright (c) 2010-2013 Diego Perini, MIT licensed + // https://gist.github.com/dperini/729294 + // see also https://mathiasbynens.be/demo/url-regex + // modified to allow protocol-relative URLs + return this.optional( element ) || /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test( value ); + }, + + // http://jqueryvalidation.org/date-method/ + date: function( value, element ) { + return this.optional( element ) || !/Invalid|NaN/.test( new Date( value ).toString() ); + }, + + // http://jqueryvalidation.org/dateISO-method/ + dateISO: function( value, element ) { + return this.optional( element ) || /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test( value ); + }, + + // http://jqueryvalidation.org/number-method/ + number: function( value, element ) { + return this.optional( element ) || /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/.test( value ); + }, + + // http://jqueryvalidation.org/digits-method/ + digits: function( value, element ) { + return this.optional( element ) || /^\d+$/.test( value ); + }, + + // http://jqueryvalidation.org/creditcard-method/ + // based on http://en.wikipedia.org/wiki/Luhn_algorithm + creditcard: function( value, element ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + // accept only spaces, digits and dashes + if ( /[^0-9 \-]+/.test( value ) ) { + return false; + } + var nCheck = 0, + nDigit = 0, + bEven = false, + n, cDigit; + + value = value.replace( /\D/g, "" ); + + // Basing min and max length on + // http://developer.ean.com/general_info/Valid_Credit_Card_Types + if ( value.length < 13 || value.length > 19 ) { + return false; + } + + for ( n = value.length - 1; n >= 0; n--) { + cDigit = value.charAt( n ); + nDigit = parseInt( cDigit, 10 ); + if ( bEven ) { + if ( ( nDigit *= 2 ) > 9 ) { + nDigit -= 9; + } + } + nCheck += nDigit; + bEven = !bEven; + } + + return ( nCheck % 10 ) === 0; + }, + + // http://jqueryvalidation.org/minlength-method/ + minlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || length >= param; + }, + + // http://jqueryvalidation.org/maxlength-method/ + maxlength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || length <= param; + }, + + // http://jqueryvalidation.org/rangelength-method/ + rangelength: function( value, element, param ) { + var length = $.isArray( value ) ? value.length : this.getLength( value, element ); + return this.optional( element ) || ( length >= param[ 0 ] && length <= param[ 1 ] ); + }, + + // http://jqueryvalidation.org/min-method/ + min: function( value, element, param ) { + return this.optional( element ) || value >= param; + }, + + // http://jqueryvalidation.org/max-method/ + max: function( value, element, param ) { + return this.optional( element ) || value <= param; + }, + + // http://jqueryvalidation.org/range-method/ + range: function( value, element, param ) { + return this.optional( element ) || ( value >= param[ 0 ] && value <= param[ 1 ] ); + }, + + // http://jqueryvalidation.org/equalTo-method/ + 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 ); + if ( this.settings.onfocusout ) { + target.off( ".validate-equalTo" ).on( "blur.validate-equalTo", function() { + $( element ).valid(); + }); + } + return value === target.val(); + }, + + // http://jqueryvalidation.org/remote-method/ + remote: function( value, element, param ) { + if ( this.optional( element ) ) { + return "dependency-mismatch"; + } + + var previous = this.previousValue( element ), + validator, data; + + 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 ( previous.old === value ) { + return previous.valid; + } + + previous.old = value; + validator = this; + this.startRequest( element ); + data = {}; + data[ element.name ] = value; + $.ajax( $.extend( true, { + mode: "abort", + port: "validate" + element.name, + dataType: "json", + data: data, + context: validator.currentForm, + success: function( response ) { + var valid = response === true || response === "true", + errors, message, submitted; + + validator.settings.messages[ element.name ].remote = previous.originalMessage; + if ( valid ) { + submitted = validator.formSubmitted; + validator.prepareElement( element ); + validator.formSubmitted = submitted; + validator.successList.push( element ); + delete validator.invalid[ element.name ]; + validator.showErrors(); + } else { + errors = {}; + message = response || validator.defaultMessage( element, "remote" ); + errors[ element.name ] = previous.message = $.isFunction( message ) ? message( value ) : message; + validator.invalid[ element.name ] = true; + validator.showErrors( errors ); + } + previous.valid = valid; + validator.stopRequest( element, valid ); + } + }, param ) ); + return "pending"; + } + } + +}); + +// 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() + +var pendingRequests = {}, + ajax; +// 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 + 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(); + } + pendingRequests[port] = ajax.apply(this, arguments); + return pendingRequests[port]; + } + return ajax.apply(this, arguments); + }; +} + +})); \ No newline at end of file diff --git a/web/Scripts/jquery.validate.min.js b/web/Scripts/jquery.validate.min.js new file mode 100644 index 00000000..643837b9 --- /dev/null +++ b/web/Scripts/jquery.validate.min.js @@ -0,0 +1,4 @@ +/*! jQuery Validation Plugin - v1.14.0 - 6/30/2015 + * http://jqueryvalidation.org/ + * Copyright (c) 2015 Jörn Zaefferer; Licensed MIT */ +!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){a.extend(a.fn,{validate:function(b){if(!this.length)return void(b&&b.debug&&window.console&&console.warn("Nothing selected, can't validate, returning nothing."));var c=a.data(this[0],"validator");return c?c:(this.attr("novalidate","novalidate"),c=new a.validator(b,this[0]),a.data(this[0],"validator",c),c.settings.onsubmit&&(this.on("click.validate",":submit",function(b){c.settings.submitHandler&&(c.submitButton=b.target),a(this).hasClass("cancel")&&(c.cancelSubmit=!0),void 0!==a(this).attr("formnovalidate")&&(c.cancelSubmit=!0)}),this.on("submit.validate",function(b){function d(){var d,e;return c.settings.submitHandler?(c.submitButton&&(d=a("").attr("name",c.submitButton.name).val(a(c.submitButton).val()).appendTo(c.currentForm)),e=c.settings.submitHandler.call(c,c.currentForm,b),c.submitButton&&d.remove(),void 0!==e?e:!1):!0}return c.settings.debug&&b.preventDefault(),c.cancelSubmit?(c.cancelSubmit=!1,d()):c.form()?c.pendingRequest?(c.formSubmitted=!0,!1):d():(c.focusInvalid(),!1)})),c)},valid:function(){var b,c,d;return a(this[0]).is("form")?b=this.validate().form():(d=[],b=!0,c=a(this[0].form).validate(),this.each(function(){b=c.element(this)&&b,d=d.concat(c.errorList)}),c.errorList=d),b},rules:function(b,c){var d,e,f,g,h,i,j=this[0];if(b)switch(d=a.data(j.form,"validator").settings,e=d.rules,f=a.validator.staticRules(j),b){case"add":a.extend(f,a.validator.normalizeRule(c)),delete f.messages,e[j.name]=f,c.messages&&(d.messages[j.name]=a.extend(d.messages[j.name],c.messages));break;case"remove":return c?(i={},a.each(c.split(/\s/),function(b,c){i[c]=f[c],delete f[c],"required"===c&&a(j).removeAttr("aria-required")}),i):(delete e[j.name],f)}return g=a.validator.normalizeRules(a.extend({},a.validator.classRules(j),a.validator.attributeRules(j),a.validator.dataRules(j),a.validator.staticRules(j)),j),g.required&&(h=g.required,delete g.required,g=a.extend({required:h},g),a(j).attr("aria-required","true")),g.remote&&(h=g.remote,delete g.remote,g=a.extend(g,{remote:h})),g}}),a.extend(a.expr[":"],{blank:function(b){return!a.trim(""+a(b).val())},filled:function(b){return!!a.trim(""+a(b).val())},unchecked:function(b){return!a(b).prop("checked")}}),a.validator=function(b,c){this.settings=a.extend(!0,{},a.validator.defaults,b),this.currentForm=c,this.init()},a.validator.format=function(b,c){return 1===arguments.length?function(){var c=a.makeArray(arguments);return c.unshift(b),a.validator.format.apply(this,c)}:(arguments.length>2&&c.constructor!==Array&&(c=a.makeArray(arguments).slice(1)),c.constructor!==Array&&(c=[c]),a.each(c,function(a,c){b=b.replace(new RegExp("\\{"+a+"\\}","g"),function(){return c})}),b)},a.extend(a.validator,{defaults:{messages:{},groups:{},rules:{},errorClass:"error",validClass:"valid",errorElement:"label",focusCleanup:!1,focusInvalid:!0,errorContainer:a([]),errorLabelContainer:a([]),onsubmit:!0,ignore:":hidden",ignoreTitle:!1,onfocusin:function(a){this.lastActive=a,this.settings.focusCleanup&&(this.settings.unhighlight&&this.settings.unhighlight.call(this,a,this.settings.errorClass,this.settings.validClass),this.hideThese(this.errorsFor(a)))},onfocusout:function(a){this.checkable(a)||!(a.name in this.submitted)&&this.optional(a)||this.element(a)},onkeyup:function(b,c){var d=[16,17,18,20,35,36,37,38,39,40,45,144,225];9===c.which&&""===this.elementValue(b)||-1!==a.inArray(c.keyCode,d)||(b.name in this.submitted||b===this.lastElement)&&this.element(b)},onclick:function(a){a.name in this.submitted?this.element(a):a.parentNode.name in this.submitted&&this.element(a.parentNode)},highlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).addClass(c).removeClass(d):a(b).addClass(c).removeClass(d)},unhighlight:function(b,c,d){"radio"===b.type?this.findByName(b.name).removeClass(c).addClass(d):a(b).removeClass(c).addClass(d)}},setDefaults:function(b){a.extend(a.validator.defaults,b)},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.",maxlength:a.validator.format("Please enter no more than {0} characters."),minlength:a.validator.format("Please enter at least {0} characters."),rangelength:a.validator.format("Please enter a value between {0} and {1} characters long."),range:a.validator.format("Please enter a value between {0} and {1}."),max:a.validator.format("Please enter a value less than or equal to {0}."),min:a.validator.format("Please enter a value greater than or equal to {0}.")},autoCreateRanges:!1,prototype:{init:function(){function b(b){var c=a.data(this.form,"validator"),d="on"+b.type.replace(/^validate/,""),e=c.settings;e[d]&&!a(this).is(e.ignore)&&e[d].call(c,this,b)}this.labelContainer=a(this.settings.errorLabelContainer),this.errorContext=this.labelContainer.length&&this.labelContainer||a(this.currentForm),this.containers=a(this.settings.errorContainer).add(this.settings.errorLabelContainer),this.submitted={},this.valueCache={},this.pendingRequest=0,this.pending={},this.invalid={},this.reset();var c,d=this.groups={};a.each(this.settings.groups,function(b,c){"string"==typeof c&&(c=c.split(/\s/)),a.each(c,function(a,c){d[c]=b})}),c=this.settings.rules,a.each(c,function(b,d){c[b]=a.validator.normalizeRule(d)}),a(this.currentForm).on("focusin.validate focusout.validate keyup.validate",":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], [type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], [type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], [type='radio'], [type='checkbox']",b).on("click.validate","select, option, [type='radio'], [type='checkbox']",b),this.settings.invalidHandler&&a(this.currentForm).on("invalid-form.validate",this.settings.invalidHandler),a(this.currentForm).find("[required], [data-rule-required], .required").attr("aria-required","true")},form:function(){return this.checkForm(),a.extend(this.submitted,this.errorMap),this.invalid=a.extend({},this.errorMap),this.valid()||a(this.currentForm).triggerHandler("invalid-form",[this]),this.showErrors(),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(b){var c=this.clean(b),d=this.validationTargetFor(c),e=!0;return this.lastElement=d,void 0===d?delete this.invalid[c.name]:(this.prepareElement(d),this.currentElements=a(d),e=this.check(d)!==!1,e?delete this.invalid[d.name]:this.invalid[d.name]=!0),a(b).attr("aria-invalid",!e),this.numberOfInvalids()||(this.toHide=this.toHide.add(this.containers)),this.showErrors(),e},showErrors:function(b){if(b){a.extend(this.errorMap,b),this.errorList=[];for(var c in b)this.errorList.push({message:b[c],element:this.findByName(c)[0]});this.successList=a.grep(this.successList,function(a){return!(a.name in b)})}this.settings.showErrors?this.settings.showErrors.call(this,this.errorMap,this.errorList):this.defaultShowErrors()},resetForm:function(){a.fn.resetForm&&a(this.currentForm).resetForm(),this.submitted={},this.lastElement=null,this.prepareForm(),this.hideErrors();var b,c=this.elements().removeData("previousValue").removeAttr("aria-invalid");if(this.settings.unhighlight)for(b=0;c[b];b++)this.settings.unhighlight.call(this,c[b],this.settings.errorClass,"");else c.removeClass(this.settings.errorClass)},numberOfInvalids:function(){return this.objectLength(this.invalid)},objectLength:function(a){var b,c=0;for(b in a)c++;return c},hideErrors:function(){this.hideThese(this.toHide)},hideThese:function(a){a.not(this.containers).text(""),this.addWrapper(a).hide()},valid:function(){return 0===this.size()},size:function(){return this.errorList.length},focusInvalid:function(){if(this.settings.focusInvalid)try{a(this.findLastActive()||this.errorList.length&&this.errorList[0].element||[]).filter(":visible").focus().trigger("focusin")}catch(b){}},findLastActive:function(){var b=this.lastActive;return b&&1===a.grep(this.errorList,function(a){return a.element.name===b.name}).length&&b},elements:function(){var b=this,c={};return a(this.currentForm).find("input, select, textarea").not(":submit, :reset, :image, :disabled").not(this.settings.ignore).filter(function(){return!this.name&&b.settings.debug&&window.console&&console.error("%o has no name assigned",this),this.name in c||!b.objectLength(a(this).rules())?!1:(c[this.name]=!0,!0)})},clean:function(b){return a(b)[0]},errors:function(){var b=this.settings.errorClass.split(" ").join(".");return a(this.settings.errorElement+"."+b,this.errorContext)},reset:function(){this.successList=[],this.errorList=[],this.errorMap={},this.toShow=a([]),this.toHide=a([]),this.currentElements=a([])},prepareForm:function(){this.reset(),this.toHide=this.errors().add(this.containers)},prepareElement:function(a){this.reset(),this.toHide=this.errorsFor(a)},elementValue:function(b){var c,d=a(b),e=b.type;return"radio"===e||"checkbox"===e?this.findByName(b.name).filter(":checked").val():"number"===e&&"undefined"!=typeof b.validity?b.validity.badInput?!1:d.val():(c=d.val(),"string"==typeof c?c.replace(/\r/g,""):c)},check:function(b){b=this.validationTargetFor(this.clean(b));var c,d,e,f=a(b).rules(),g=a.map(f,function(a,b){return b}).length,h=!1,i=this.elementValue(b);for(d in f){e={method:d,parameters:f[d]};try{if(c=a.validator.methods[d].call(this,i,b,e.parameters),"dependency-mismatch"===c&&1===g){h=!0;continue}if(h=!1,"pending"===c)return void(this.toHide=this.toHide.not(this.errorsFor(b)));if(!c)return this.formatAndAdd(b,e),!1}catch(j){throw this.settings.debug&&window.console&&console.log("Exception occurred when checking element "+b.id+", check the '"+e.method+"' method.",j),j instanceof TypeError&&(j.message+=". Exception occurred when checking element "+b.id+", check the '"+e.method+"' method."),j}}if(!h)return this.objectLength(f)&&this.successList.push(b),!0},customDataMessage:function(b,c){return a(b).data("msg"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase())||a(b).data("msg")},customMessage:function(a,b){var c=this.settings.messages[a];return c&&(c.constructor===String?c:c[b])},findDefined:function(){for(var a=0;aWarning: No message defined for "+b.name+"")},formatAndAdd:function(b,c){var d=this.defaultMessage(b,c.method),e=/\$?\{(\d+)\}/g;"function"==typeof d?d=d.call(this,c.parameters,b):e.test(d)&&(d=a.validator.format(d.replace(e,"{$1}"),c.parameters)),this.errorList.push({message:d,element:b,method:c.method}),this.errorMap[b.name]=d,this.submitted[b.name]=d},addWrapper:function(a){return this.settings.wrapper&&(a=a.add(a.parent(this.settings.wrapper))),a},defaultShowErrors:function(){var a,b,c;for(a=0;this.errorList[a];a++)c=this.errorList[a],this.settings.highlight&&this.settings.highlight.call(this,c.element,this.settings.errorClass,this.settings.validClass),this.showLabel(c.element,c.message);if(this.errorList.length&&(this.toShow=this.toShow.add(this.containers)),this.settings.success)for(a=0;this.successList[a];a++)this.showLabel(this.successList[a]);if(this.settings.unhighlight)for(a=0,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 a(this.errorList).map(function(){return this.element})},showLabel:function(b,c){var d,e,f,g=this.errorsFor(b),h=this.idOrName(b),i=a(b).attr("aria-describedby");g.length?(g.removeClass(this.settings.validClass).addClass(this.settings.errorClass),g.html(c)):(g=a("<"+this.settings.errorElement+">").attr("id",h+"-error").addClass(this.settings.errorClass).html(c||""),d=g,this.settings.wrapper&&(d=g.hide().show().wrap("<"+this.settings.wrapper+"/>").parent()),this.labelContainer.length?this.labelContainer.append(d):this.settings.errorPlacement?this.settings.errorPlacement(d,a(b)):d.insertAfter(b),g.is("label")?g.attr("for",h):0===g.parents("label[for='"+h+"']").length&&(f=g.attr("id").replace(/(:|\.|\[|\]|\$)/g,"\\$1"),i?i.match(new RegExp("\\b"+f+"\\b"))||(i+=" "+f):i=f,a(b).attr("aria-describedby",i),e=this.groups[b.name],e&&a.each(this.groups,function(b,c){c===e&&a("[name='"+b+"']",this.currentForm).attr("aria-describedby",g.attr("id"))}))),!c&&this.settings.success&&(g.text(""),"string"==typeof this.settings.success?g.addClass(this.settings.success):this.settings.success(g,b)),this.toShow=this.toShow.add(g)},errorsFor:function(b){var c=this.idOrName(b),d=a(b).attr("aria-describedby"),e="label[for='"+c+"'], label[for='"+c+"'] *";return d&&(e=e+", #"+d.replace(/\s+/g,", #")),this.errors().filter(e)},idOrName:function(a){return this.groups[a.name]||(this.checkable(a)?a.name:a.id||a.name)},validationTargetFor:function(b){return this.checkable(b)&&(b=this.findByName(b.name)),a(b).not(this.settings.ignore)[0]},checkable:function(a){return/radio|checkbox/i.test(a.type)},findByName:function(b){return a(this.currentForm).find("[name='"+b+"']")},getLength:function(b,c){switch(c.nodeName.toLowerCase()){case"select":return a("option:selected",c).length;case"input":if(this.checkable(c))return this.findByName(c.name).filter(":checked").length}return b.length},depend:function(a,b){return this.dependTypes[typeof a]?this.dependTypes[typeof a](a,b):!0},dependTypes:{"boolean":function(a){return a},string:function(b,c){return!!a(b,c.form).length},"function":function(a,b){return a(b)}},optional:function(b){var c=this.elementValue(b);return!a.validator.methods.required.call(this,c,b)&&"dependency-mismatch"},startRequest:function(a){this.pending[a.name]||(this.pendingRequest++,this.pending[a.name]=!0)},stopRequest:function(b,c){this.pendingRequest--,this.pendingRequest<0&&(this.pendingRequest=0),delete this.pending[b.name],c&&0===this.pendingRequest&&this.formSubmitted&&this.form()?(a(this.currentForm).submit(),this.formSubmitted=!1):!c&&0===this.pendingRequest&&this.formSubmitted&&(a(this.currentForm).triggerHandler("invalid-form",[this]),this.formSubmitted=!1)},previousValue:function(b){return a.data(b,"previousValue")||a.data(b,"previousValue",{old:null,valid:!0,message:this.defaultMessage(b,"remote")})},destroy:function(){this.resetForm(),a(this.currentForm).off(".validate").removeData("validator")}},classRuleSettings:{required:{required:!0},email:{email:!0},url:{url:!0},date:{date:!0},dateISO:{dateISO:!0},number:{number:!0},digits:{digits:!0},creditcard:{creditcard:!0}},addClassRules:function(b,c){b.constructor===String?this.classRuleSettings[b]=c:a.extend(this.classRuleSettings,b)},classRules:function(b){var c={},d=a(b).attr("class");return d&&a.each(d.split(" "),function(){this in a.validator.classRuleSettings&&a.extend(c,a.validator.classRuleSettings[this])}),c},normalizeAttributeRule:function(a,b,c,d){/min|max/.test(c)&&(null===b||/number|range|text/.test(b))&&(d=Number(d),isNaN(d)&&(d=void 0)),d||0===d?a[c]=d:b===c&&"range"!==b&&(a[c]=!0)},attributeRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)"required"===c?(d=b.getAttribute(c),""===d&&(d=!0),d=!!d):d=f.attr(c),this.normalizeAttributeRule(e,g,c,d);return e.maxlength&&/-1|2147483647|524288/.test(e.maxlength)&&delete e.maxlength,e},dataRules:function(b){var c,d,e={},f=a(b),g=b.getAttribute("type");for(c in a.validator.methods)d=f.data("rule"+c.charAt(0).toUpperCase()+c.substring(1).toLowerCase()),this.normalizeAttributeRule(e,g,c,d);return e},staticRules:function(b){var c={},d=a.data(b.form,"validator");return d.settings.rules&&(c=a.validator.normalizeRule(d.settings.rules[b.name])||{}),c},normalizeRules:function(b,c){return a.each(b,function(d,e){if(e===!1)return void delete b[d];if(e.param||e.depends){var f=!0;switch(typeof e.depends){case"string":f=!!a(e.depends,c.form).length;break;case"function":f=e.depends.call(c,c)}f?b[d]=void 0!==e.param?e.param:!0:delete b[d]}}),a.each(b,function(d,e){b[d]=a.isFunction(e)?e(c):e}),a.each(["minlength","maxlength"],function(){b[this]&&(b[this]=Number(b[this]))}),a.each(["rangelength","range"],function(){var c;b[this]&&(a.isArray(b[this])?b[this]=[Number(b[this][0]),Number(b[this][1])]:"string"==typeof b[this]&&(c=b[this].replace(/[\[\]]/g,"").split(/[\s,]+/),b[this]=[Number(c[0]),Number(c[1])]))}),a.validator.autoCreateRanges&&(null!=b.min&&null!=b.max&&(b.range=[b.min,b.max],delete b.min,delete b.max),null!=b.minlength&&null!=b.maxlength&&(b.rangelength=[b.minlength,b.maxlength],delete b.minlength,delete b.maxlength)),b},normalizeRule:function(b){if("string"==typeof b){var c={};a.each(b.split(/\s/),function(){c[this]=!0}),b=c}return b},addMethod:function(b,c,d){a.validator.methods[b]=c,a.validator.messages[b]=void 0!==d?d:a.validator.messages[b],c.length<3&&a.validator.addClassRules(b,a.validator.normalizeRule(b))},methods:{required:function(b,c,d){if(!this.depend(d,c))return"dependency-mismatch";if("select"===c.nodeName.toLowerCase()){var e=a(c).val();return e&&e.length>0}return this.checkable(c)?this.getLength(b,c)>0:b.length>0},email:function(a,b){return this.optional(b)||/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/.test(a)},url:function(a,b){return this.optional(b)||/^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i.test(a)},date:function(a,b){return this.optional(b)||!/Invalid|NaN/.test(new Date(a).toString())},dateISO:function(a,b){return this.optional(b)||/^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.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!1;var c,d,e=0,f=0,g=!1;if(a=a.replace(/\D/g,""),a.length<13||a.length>19)return!1;for(c=a.length-1;c>=0;c--)d=a.charAt(c),f=parseInt(d,10),g&&(f*=2)>9&&(f-=9),e+=f,g=!g;return e%10===0},minlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d},maxlength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||d>=e},rangelength:function(b,c,d){var e=a.isArray(b)?b.length:this.getLength(b,c);return this.optional(c)||e>=d[0]&&e<=d[1]},min:function(a,b,c){return this.optional(b)||a>=c},max:function(a,b,c){return this.optional(b)||c>=a},range:function(a,b,c){return this.optional(b)||a>=c[0]&&a<=c[1]},equalTo:function(b,c,d){var e=a(d);return this.settings.onfocusout&&e.off(".validate-equalTo").on("blur.validate-equalTo",function(){a(c).valid()}),b===e.val()},remote:function(b,c,d){if(this.optional(c))return"dependency-mismatch";var e,f,g=this.previousValue(c);return this.settings.messages[c.name]||(this.settings.messages[c.name]={}),g.originalMessage=this.settings.messages[c.name].remote,this.settings.messages[c.name].remote=g.message,d="string"==typeof d&&{url:d}||d,g.old===b?g.valid:(g.old=b,e=this,this.startRequest(c),f={},f[c.name]=b,a.ajax(a.extend(!0,{mode:"abort",port:"validate"+c.name,dataType:"json",data:f,context:e.currentForm,success:function(d){var f,h,i,j=d===!0||"true"===d;e.settings.messages[c.name].remote=g.originalMessage,j?(i=e.formSubmitted,e.prepareElement(c),e.formSubmitted=i,e.successList.push(c),delete e.invalid[c.name],e.showErrors()):(f={},h=d||e.defaultMessage(c,"remote"),f[c.name]=g.message=a.isFunction(h)?h(b):h,e.invalid[c.name]=!0,e.showErrors(f)),g.valid=j,e.stopRequest(c,j)}},d)),"pending")}}});var b,c={};a.ajaxPrefilter?a.ajaxPrefilter(function(a,b,d){var e=a.port;"abort"===a.mode&&(c[e]&&c[e].abort(),c[e]=d)}):(b=a.ajax,a.ajax=function(d){var e=("mode"in d?d:a.ajaxSettings).mode,f=("port"in d?d:a.ajaxSettings).port;return"abort"===e?(c[f]&&c[f].abort(),c[f]=b.apply(this,arguments),c[f]):b.apply(this,arguments)})}); \ No newline at end of file diff --git a/web/Scripts/jquery.validate.unobtrusive.js b/web/Scripts/jquery.validate.unobtrusive.js new file mode 100644 index 00000000..444ad24c --- /dev/null +++ b/web/Scripts/jquery.validate.unobtrusive.js @@ -0,0 +1,429 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. Insofar as a script file is dual licensed under GPL, + * Microsoft neither took the code under GPL nor distributes it thereunder but + * under the terms set out in this paragraph. All notices and licenses + * below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ +/*! +** 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 escapeAttributeValue(value) { + // As mentioned on http://api.jquery.com/category/selectors/ + return value.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g, "\\$1"); + } + + 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='" + escapeAttributeValue(inputElement[0].name) + "']"), + replaceAttrValue = container.attr("data-valmsg-replace"), + replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) !== false : null; + + 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(event, 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"), + replaceAttrValue = container.attr("data-valmsg-replace"), + replace = replaceAttrValue ? $.parseJSON(replaceAttrValue) : null; + + if (container) { + container.addClass("field-validation-valid").removeClass("field-validation-error"); + error.removeData("unobtrusiveContainer"); + + if (replace) { + container.empty(); + } + } + } + + function onReset(event) { // 'this' is the form element + var $form = $(this), + key = '__jquery_unobtrusive_validation_form_reset'; + if ($form.data(key)) { + return; + } + // Set a flag that indicates we're currently resetting the form. + $form.data(key, true); + try { + $form.data("validator").resetForm(); + } finally { + $form.removeData(key); + } + + $form.find(".validation-summary-errors") + .addClass("validation-summary-valid") + .removeClass("validation-summary-errors"); + $form.find(".field-validation-error") + .addClass("field-validation-valid") + .removeClass("field-validation-error") + .removeData("unobtrusiveContainer") + .find(">*") // If we were using valmsg-replace, get the underlying error + .removeData("unobtrusiveContainer"); + } + + function validationInfo(form) { + var $form = $(form), + result = $form.data(data_validation), + onResetProxy = $.proxy(onReset, form), + defaultOptions = $jQval.unobtrusive.options || {}, + execInContext = function (name, args) { + var func = defaultOptions[name]; + func && $.isFunction(func) && func.apply(form, args); + } + + if (!result) { + result = { + options: { // options structure passed to jQuery Validate's validate() method + errorClass: defaultOptions.errorClass || "input-validation-error", + errorElement: defaultOptions.errorElement || "span", + errorPlacement: function () { + onError.apply(form, arguments); + execInContext("errorPlacement", arguments); + }, + invalidHandler: function () { + onErrors.apply(form, arguments); + execInContext("invalidHandler", arguments); + }, + messages: {}, + rules: {}, + success: function () { + onSuccess.apply(form, arguments); + execInContext("success", arguments); + } + }, + attachValidation: function () { + $form + .off("reset." + data_validation, onResetProxy) + .on("reset." + data_validation, onResetProxy) + .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 + }); + } + }); + + $.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. + + // $forms includes all forms in selector's DOM hierarchy (parent, children and self) that have at least one + // element with data-val=true + var $selector = $(selector), + $forms = $selector.parents() + .addBack() + .filter("form") + .add($selector.find("form")) + .has("[data-val=true]"); + + $selector.find("[data-val=true]").each(function () { + $jQval.unobtrusive.parseElement(this, true); + }); + + $forms.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)); + }); + + $jQval.addMethod("nonalphamin", function (value, element, nonalphamin) { + var match; + if (nonalphamin) { + match = value.match(/\W/g); + match = match && match.length >= nonalphamin; + } + return match; + }); + + if ($jQval.methods.extension) { + adapters.addSingleVal("accept", "mimtype"); + adapters.addSingleVal("extension", "extension"); + } else { + // for backward compatibility, when the 'extension' validation method does not exist, such as with versions + // of JQuery Validation plugin prior to 1.10, we should use the 'accept' method for + // validating the extension, and ignore mime-type validations as they are not supported. + adapters.addSingleVal("extension", "extension", "accept"); + } + + adapters.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.addMinMax("minlength", "minlength").addMinMax("maxlength", "minlength", "maxlength"); + 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").filter("[name='" + escapeAttributeValue(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 () { + var field = $(options.form).find(":input").filter("[name='" + escapeAttributeValue(paramName) + "']"); + // For checkboxes and radio buttons, only pick up values from checked fields. + if (field.is(":checkbox")) { + return field.filter(":checked").val() || field.filter(":hidden").val() || ''; + } + else if (field.is(":radio")) { + return field.filter(":checked").val() || ''; + } + return field.val(); + }; + }); + + setValidationValues(options, "remote", value); + }); + adapters.add("password", ["min", "nonalphamin", "regex"], function (options) { + if (options.params.min) { + setValidationValues(options, "minlength", options.params.min); + } + if (options.params.nonalphamin) { + setValidationValues(options, "nonalphamin", options.params.nonalphamin); + } + if (options.params.regex) { + setValidationValues(options, "regex", options.params.regex); + } + }); + + $(function () { + $jQval.unobtrusive.parse(document); + }); +}(jQuery)); \ No newline at end of file diff --git a/web/Scripts/jquery.validate.unobtrusive.min.js b/web/Scripts/jquery.validate.unobtrusive.min.js new file mode 100644 index 00000000..c935136c --- /dev/null +++ b/web/Scripts/jquery.validate.unobtrusive.min.js @@ -0,0 +1,19 @@ +/* NUGET: BEGIN LICENSE TEXT + * + * Microsoft grants you the right to use these script files for the sole + * purpose of either: (i) interacting through your browser with the Microsoft + * website or online service, subject to the applicable licensing or use + * terms; or (ii) using the files as included with a Microsoft product subject + * to that product's license terms. Microsoft reserves all other rights to the + * files not expressly granted by Microsoft, whether by implication, estoppel + * or otherwise. Insofar as a script file is dual licensed under GPL, + * Microsoft neither took the code under GPL nor distributes it thereunder but + * under the terms set out in this paragraph. All notices and licenses + * below are for informational purposes only. + * + * NUGET: END LICENSE TEXT */ +/* +** Unobtrusive validation support library for jQuery and jQuery Validate +** Copyright (C) Microsoft Corporation. All rights reserved. +*/ +(function(a){var d=a.validator,b,e="unobtrusiveValidation";function c(a,b,c){a.rules[b]=c;if(a.message)a.messages[b]=a.message}function j(a){return a.replace(/^\s+|\s+$/g,"").split(/\s*,\s*/g)}function f(a){return a.replace(/([!"#$%&'()*+,./:;<=>?@\[\\\]^`{|}~])/g,"\\$1")}function h(a){return a.substr(0,a.lastIndexOf(".")+1)}function g(a,b){if(a.indexOf("*.")===0)a=a.replace("*.",b);return a}function m(c,e){var b=a(this).find("[data-valmsg-for='"+f(e[0].name)+"']"),d=b.attr("data-valmsg-replace"),g=d?a.parseJSON(d)!==false:null;b.removeClass("field-validation-valid").addClass("field-validation-error");c.data("unobtrusiveContainer",b);if(g){b.empty();c.removeClass("input-validation-error").appendTo(b)}else c.hide()}function l(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 k(d){var b=d.data("unobtrusiveContainer"),c=b.attr("data-valmsg-replace"),e=c?a.parseJSON(c):null;if(b){b.addClass("field-validation-valid").removeClass("field-validation-error");d.removeData("unobtrusiveContainer");e&&b.empty()}}function n(){var b=a(this),c="__jquery_unobtrusive_validation_form_reset";if(b.data(c))return;b.data(c,true);try{b.data("validator").resetForm()}finally{b.removeData(c)}b.find(".validation-summary-errors").addClass("validation-summary-valid").removeClass("validation-summary-errors");b.find(".field-validation-error").addClass("field-validation-valid").removeClass("field-validation-error").removeData("unobtrusiveContainer").find(">*").removeData("unobtrusiveContainer")}function i(b){var c=a(b),f=c.data(e),i=a.proxy(n,b),g=d.unobtrusive.options||{},h=function(e,d){var c=g[e];c&&a.isFunction(c)&&c.apply(b,d)};if(!f){f={options:{errorClass:g.errorClass||"input-validation-error",errorElement:g.errorElement||"span",errorPlacement:function(){m.apply(b,arguments);h("errorPlacement",arguments)},invalidHandler:function(){l.apply(b,arguments);h("invalidHandler",arguments)},messages:{},rules:{},success:function(){k.apply(b,arguments);h("success",arguments)}},attachValidation:function(){c.off("reset."+e,i).on("reset."+e,i).validate(this.options)},validate:function(){c.validate();return c.valid()}};c.data(e,f)}return f}d.unobtrusive={adapters:[],parseElement:function(b,h){var d=a(b),f=d.parents("form")[0],c,e,g;if(!f)return;c=i(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})}});a.extend(e,{__dummy__:true});!h&&c.attachValidation()},parse:function(c){var b=a(c),e=b.parents().addBack().filter("form").add(b.find("form")).has("[data-val=true]");b.find("[data-val=true]").each(function(){d.unobtrusive.parseElement(this,true)});e.each(function(){var a=i(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});d.addMethod("nonalphamin",function(c,d,b){var a;if(b){a=c.match(/\W/g);a=a&&a.length>=b}return a});if(d.methods.extension){b.addSingleVal("accept","mimtype");b.addSingleVal("extension","extension")}else b.addSingleVal("extension","extension","accept");b.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.addMinMax("minlength","minlength").addMinMax("maxlength","minlength","maxlength");b.add("equalto",["other"],function(b){var i=h(b.element.name),j=b.params.other,d=g(j,i),e=a(b.form).find(":input").filter("[name='"+f(d)+"']")[0];c(b,"equalTo",e)});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:{}},e=h(b.element.name);a.each(j(b.params.additionalfields||b.element.name),function(i,h){var c=g(h,e);d.data[c]=function(){var d=a(b.form).find(":input").filter("[name='"+f(c)+"']");return d.is(":checkbox")?d.filter(":checked").val()||d.filter(":hidden").val()||"":d.is(":radio")?d.filter(":checked").val()||"":d.val()}});c(b,"remote",d)});b.add("password",["min","nonalphamin","regex"],function(a){a.params.min&&c(a,"minlength",a.params.min);a.params.nonalphamin&&c(a,"nonalphamin",a.params.nonalphamin);a.params.regex&&c(a,"regex",a.params.regex)});a(function(){d.unobtrusive.parse(document)})})(jQuery); \ No newline at end of file diff --git a/web/Scripts/yavsc.rate.js b/web/Scripts/yavsc.rate.js new file mode 100644 index 00000000..bed6b5f4 --- /dev/null +++ b/web/Scripts/yavsc.rate.js @@ -0,0 +1,57 @@ +(function() { + (function(jQuery) { + return jQuery.widget('Yavsc.rate', { + options: { + target: null, + disabled: false + }, + sendRate: function (rating,callback) { + Yavsc.ajax(this.options.target+'/Rate', rating, callback); + }, + _create: function() { + var $ratectl = $(this.element); + var _this = this; + $ratectl.onChanged = function (newrate) { + // build the five stars + _this.updateRate($ratectl,newrate); + }; + var id = $ratectl.data('id'); + $ratectl.addClass('rate'); + $ratectl.click(function (e) { + var oset = $ratectl.offset(); + var x = ((e.pageX - oset.left) * 100 ) / $ratectl.width(); + // here, x may be greater than 100, or lower than 0 here, + // depending on padding & mergin on the $ratectl node, + // when it's a span, and there is a line return within, + // the values on second star line are false. + // time to sanitize + x = Math.ceil(x); + if (x<0) x = 0; + if (x>100) x = 100; + var data = { Id: id, Rate: x }; + _this.sendRate(data, function () { + $ratectl.onChanged(x); }); + }); + }, + updateRate: function (ctl,rate) { + var rounded = Math.round(rate / 10); + var HasHalf = (rounded % 2 == 1); + var NbFilled = Math.floor(rounded / 2); + var NbEmpty = (5 - NbFilled) - ((HasHalf)?1:0) ; + ctl.empty(); + var i=0; + for (i=0; i'); + if (HasHalf) + ctl.append(''); + for (var j=0; j'); + }, +})})(jQuery); +}).call(this); + + $(document).ready(function(){ + $('[data-type="rate-skill"]').rate({target: 'Skill/RateSkill'}); + $('[data-type="rate-user-skill"]').rate({target: 'Skill/RateUserSkill'}); + $('[data-type="rate-bill"]').rate({target: 'Blogs/Rate'}); + }); diff --git a/web/Scripts/yavsc.skills.js b/web/Scripts/yavsc.skills.js new file mode 100644 index 00000000..a43d176f --- /dev/null +++ b/web/Scripts/yavsc.skills.js @@ -0,0 +1,20 @@ + +var Skills = (function(){ +var self = {}; +self.updateSkill = function () { } ; + +self.createSkill = function (skillname,callback) { +var skill = { name: skillname } ; + console.log('creating the skill : '+skill.name+' ... '); +Yavsc.ajax('Skill/DeclareSkill',skill,callback); +}; +self.deleteSkill = function (sid,callback) { +Yavsc.ajax('Skill/DeleteSkill',sid,callback); +}; +self.declareUserSkill = function (usersdec,callback) { + console.log("creating the user's skill [ "+usersdec.skillid +', '+usersdec.comment+' ] ... '); +Yavsc.ajax('Skill/DeclareUserSkill',usersdec,callback); +}; + +return self; +})(); diff --git a/web/Views/Admin/AddUserToRole.ascx b/web/Views/Admin/AddUserToRole.ascx new file mode 100644 index 00000000..214282db --- /dev/null +++ b/web/Views/Admin/AddUserToRole.ascx @@ -0,0 +1,29 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> +<%= Html.ValidationSummary() %> +<% using(Html.BeginForm("AddUserToRole", "Admin")) + { %> +
    +
    + <% if (ViewData ["UserName"] != null) { %> +<%= Html.Hidden("RoleName",ViewData ["RoleName"]) %> +<% } else { %> +
    + +<%= Html.ValidationMessage("UserName", "*") %> +
    +<% } %> + +<% if (ViewData ["RoleName"] != null) { %> +<%= Html.Hidden("RoleName",ViewData ["RoleName"]) %> +<% } else { %> +
    + + +<%= Html.ValidationMessage("RoleName", "*") %> +
    +<% } %> + +<%= Html.Hidden("ReturnUrl", Request.Url.PathAndQuery ) %> + +
    +<% } %> \ No newline at end of file diff --git a/web/Views/Admin/UserCard.ascx b/web/Views/Admin/UserCard.ascx new file mode 100644 index 00000000..75d3b947 --- /dev/null +++ b/web/Views/Admin/UserCard.ascx @@ -0,0 +1,3 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> + + diff --git a/web/Views/Blogs/RateControl.ascx b/web/Views/Blogs/RateControl.ascx new file mode 100644 index 00000000..ff2ad8d2 --- /dev/null +++ b/web/Views/Blogs/RateControl.ascx @@ -0,0 +1,6 @@ +<%@ Control Language="C#" Inherits="Yavsc.RateControl" %> +
    <% int i = 0; for (; i<% } +%><% if (HasHalf) { %><% i++; } +%><% for (int j=0; j<% } +%>
    \ No newline at end of file diff --git a/web/Views/FrontOffice/RateControl.ascx b/web/Views/FrontOffice/RateControl.ascx new file mode 100644 index 00000000..ff2ad8d2 --- /dev/null +++ b/web/Views/FrontOffice/RateControl.ascx @@ -0,0 +1,6 @@ +<%@ Control Language="C#" Inherits="Yavsc.RateControl" %> +
    <% int i = 0; for (; i<% } +%><% if (HasHalf) { %><% i++; } +%><% for (int j=0; j<% } +%>
    \ No newline at end of file diff --git a/web/Views/FrontOffice/RateSkillControl.ascx b/web/Views/FrontOffice/RateSkillControl.ascx new file mode 100644 index 00000000..d61cc1e4 --- /dev/null +++ b/web/Views/FrontOffice/RateSkillControl.ascx @@ -0,0 +1,7 @@ +<%@ Control Language="C#" Inherits="Yavsc.RateControl" %> +<% Rate = Model.Rate; %> +
    <% int i = 0; for (; i<% } +%><% if (HasHalf) { %><% i++; } +%><% for (int j=0; j<% } +%>
    \ No newline at end of file diff --git a/web/Views/FrontOffice/RateUserSkillControl.ascx b/web/Views/FrontOffice/RateUserSkillControl.ascx new file mode 100644 index 00000000..718b22ea --- /dev/null +++ b/web/Views/FrontOffice/RateUserSkillControl.ascx @@ -0,0 +1,7 @@ +<%@ Control Language="C#" Inherits="Yavsc.RateControl" %> +<% Rate = Model.Rate; %> +
    <% int i = 0; for (; i<% } +%><% if (HasHalf) { %><% i++; } +%><% for (int j=0; j<% } +%>
    \ No newline at end of file diff --git a/web/Views/FrontOffice/Skills.aspx b/web/Views/FrontOffice/Skills.aspx new file mode 100644 index 00000000..7fcac761 --- /dev/null +++ b/web/Views/FrontOffice/Skills.aspx @@ -0,0 +1,38 @@ +<%@ Page Title="Skills" Language="C#" MasterPageFile="~/Models/App.master" Inherits="System.Web.Mvc.ViewPage>" %> + + + + + + + + + + +
      +<% foreach (var skill in Model) { %> +
    • +<%= skill.Name %> <%=Html.Partial("RateSkillControl", skill) %> +
    • +<% } %> +
    + +
    diff --git a/web/Views/FrontOffice/UserCard.ascx b/web/Views/FrontOffice/UserCard.ascx new file mode 100644 index 00000000..e55eaaf3 --- /dev/null +++ b/web/Views/FrontOffice/UserCard.ascx @@ -0,0 +1,2 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> + diff --git a/web/Views/FrontOffice/UserSkills.aspx b/web/Views/FrontOffice/UserSkills.aspx new file mode 100644 index 00000000..91ae4482 --- /dev/null +++ b/web/Views/FrontOffice/UserSkills.aspx @@ -0,0 +1,51 @@ +<%@ Page Title="Skills" Language="C#" MasterPageFile="~/Models/App.master" Inherits="System.Web.Mvc.ViewPage" %> + + + + + + + + +
      +<% foreach (var userskill in Model.Skills) { %> +
    • +

      <%= userskill.SkillName %>


      <%= userskill.Comment %>
      <%=Html.Partial("RateUserSkillControl", userskill) %> +
    • +<% } %> +
    + +
    diff --git a/web/Views/Home/Contact.template.aspx b/web/Views/Home/Contact.template.aspx new file mode 100644 index 00000000..0c30f7cc --- /dev/null +++ b/web/Views/Home/Contact.template.aspx @@ -0,0 +1,37 @@ +<%@ Page Title="Contact" Language="C#" MasterPageFile="~/Models/App.master" Inherits="System.Web.Mvc.ViewPage" %> + + +
    +

    +Directeur :
    +Adresse postale :
    +Tél. : +33
    +Tél. : +33
    +SIREN :
    +SIRET :
    +Activité Principalement Exercée (APE) :
    +

    +<% using (Html.BeginForm("Contact", "Home")) { %> +
    +Message +

    +<%= Html.Label("email") %>: +<%= Html.ValidationMessage("email") %>
    +<%= Html.TextBox("email") %> +

    +

    +<%= Html.Label("reason") %>: +<%= Html.ValidationMessage("reason") %>
    +<%= Html.TextBox("reason") %> +

    +

    +<%= Html.Label("body") %>: +<%= Html.ValidationMessage("body") %>
    +<%= Html.TextArea("body") %> +

    +
    +"> + +<% } %> +
    +
    \ No newline at end of file diff --git a/web/Views/Home/RestrictedArea.aspx b/web/Views/Home/RestrictedArea.aspx new file mode 100644 index 00000000..e9fee24e --- /dev/null +++ b/web/Views/Home/RestrictedArea.aspx @@ -0,0 +1,7 @@ +<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Models/App.master"%> + + Ce contenu est d'accès restreint : <<%= Html.Encode(ViewData["ControllerName"]) %>/<%= Html.Encode(ViewData["ActionName"]) %>> + + Demandez à l'administrateur les autorisations suffisantes pour accèder à cet emplacement. + + \ No newline at end of file diff --git a/yavscModel/Calendar/BookingQuery.cs b/yavscModel/Calendar/BookingQuery.cs new file mode 100644 index 00000000..804bb5ac --- /dev/null +++ b/yavscModel/Calendar/BookingQuery.cs @@ -0,0 +1,79 @@ +// +// AskForADate.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2014 Paul Schneider +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Yavsc.Model.Calendar +{ + /// + /// Ask for A date. + /// + public class BookingQuery + { + + /// + /// Gets or sets the prefered date. + /// + /// The prefered date. + [DataType(DataType.Date)] + [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)] + [Display(ResourceType=typeof(LocalizedText),Name="StartDate")] + public DateTime StartDate { get; set; } + + /// + /// Gets or sets the minimum time. + /// + /// The minimum time. + [RegularExpression("\\d\\d:\\d\\d")] + [Display(ResourceType=typeof(LocalizedText),Name="StartHour")] + [Required(ErrorMessage= "S'il vous plait, saisissez une heure de début d'intervention")] + public string StartHour { get; set; } + + /// + /// Gets or sets the max date. + /// + /// The max date. + [Display(Name="EndDate",ResourceType=typeof(LocalizedText))] + [DataType(DataType.Date)] + [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)] + public DateTime EndDate { get; set; } + + + /// + /// Gets or sets the minimal duration. + /// + /// The duration. + [RegularExpression("\\d\\d:\\d\\d")] + [Required(ErrorMessage= "S'il vous plait, saisissez une heure de fin d'intervention")] + [Display(Name="EndHour",ResourceType=typeof(LocalizedText))] + public string EndHour { get; set; } + + /// + /// Gets or sets the role. + /// + /// The role. + [Required(ErrorMessage= "S'il vous plait, saisissez le ou les types d'intervention souhaité")] + [Display(Name="Role",ResourceType=typeof(LocalizedText))] + public string [] Roles { get; set; } + } +} + diff --git a/yavscModel/IAuthorized.cs b/yavscModel/IAuthorized.cs new file mode 100644 index 00000000..0350a1c6 --- /dev/null +++ b/yavscModel/IAuthorized.cs @@ -0,0 +1,37 @@ +// +// IAuthorized.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; + +namespace Yavsc.Model +{ + /// + /// I authorized. + /// + public interface IAuthorized { + /// + /// Gets or sets the author. + /// + /// The author. + string Author { get; set; } + } + +} diff --git a/yavscModel/IComment.cs b/yavscModel/IComment.cs new file mode 100644 index 00000000..f9fd738e --- /dev/null +++ b/yavscModel/IComment.cs @@ -0,0 +1,30 @@ +// +// ICommented.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; + +namespace Yavsc.Model +{ + + public interface IComment: IIdentified { + string Comment { get; set; } + } +} diff --git a/yavscModel/IIdentified.cs b/yavscModel/IIdentified.cs new file mode 100644 index 00000000..34bb0403 --- /dev/null +++ b/yavscModel/IIdentified.cs @@ -0,0 +1,30 @@ +// +// IIdentified.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; + +namespace Yavsc.Model +{ + public interface IIdentified + { + long Id { get; set; } + } +} + diff --git a/yavscModel/IRating.cs b/yavscModel/IRating.cs new file mode 100644 index 00000000..213c241c --- /dev/null +++ b/yavscModel/IRating.cs @@ -0,0 +1,38 @@ +// +// IIdentified.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; + +namespace Yavsc.Model +{ + + /// + /// I rating. + /// + public interface IRating: IIdentified + { + /// + /// Gets or sets the rate. + /// + /// The rate. + int Rate { get; set; } + } +} + diff --git a/yavscModel/Manager.cs b/yavscModel/Manager.cs new file mode 100644 index 00000000..90abdeef --- /dev/null +++ b/yavscModel/Manager.cs @@ -0,0 +1,63 @@ +// +// Manager.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; +using System.Configuration; +using System.Reflection; +using System.Configuration.Provider; + +namespace Yavsc.Model { + /// + /// Manager. + /// + public static class ManagerHelper { + + /// + /// Gets the provider. + /// + /// The provider. + public static TProvider GetDefaultProvider (string configSetion) where TProvider: ProviderBase + { + DataProviderConfigurationSection config = ConfigurationManager.GetSection (configSetion) as DataProviderConfigurationSection; + if (config == null) + throw new ConfigurationErrorsException ( + string.Format( + "The providers configuration bloc `{0}` was not found", + configSetion)); + ProviderSettings celt = + config.Providers [config.DefaultProvider]; + if (config == null) + throw new ConfigurationErrorsException ( + string.Format ( + "The default provider `{0}` was not found ", + config.DefaultProvider)); + Type provtype = Type.GetType (celt.Type); + if (provtype == null) + throw new ProviderException ( + string.Format ( + "Provider type '{0}' was not found",celt.Type)); + ConstructorInfo ci = provtype.GetConstructor (Type.EmptyTypes); + ProviderBase bp = ci.Invoke (Type.EmptyTypes) as ProviderBase; + bp.Initialize (celt.Name, celt.Parameters); + return bp as TProvider; + } + } +} diff --git a/yavscModel/RolesAndMembers/UserNameBase.cs b/yavscModel/RolesAndMembers/UserNameBase.cs new file mode 100644 index 00000000..f6a51328 --- /dev/null +++ b/yavscModel/RolesAndMembers/UserNameBase.cs @@ -0,0 +1,39 @@ +// +// UserNameBase.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; + +namespace Yavsc.Model.RolesAndMembers +{ + public class UserNameBase { + /// + /// Gets or sets the name of the user. + /// + /// The name of the user. + [Localizable(true), Required(ErrorMessage = "S'il vous plait, entrez un nom d'utilisateur") + ,Display(ResourceType=typeof(LocalizedText),Name="User_name"),RegularExpression("([a-z]|[A-Z]|[0-9] )+")] + public string UserName { get; set; } + + } + +} diff --git a/yavscModel/RolesAndMembers/UserRole.cs b/yavscModel/RolesAndMembers/UserRole.cs new file mode 100644 index 00000000..7abeddd2 --- /dev/null +++ b/yavscModel/RolesAndMembers/UserRole.cs @@ -0,0 +1,43 @@ +// +// UserRole.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel; + +namespace Yavsc.Model.RolesAndMembers +{ + /// + /// User role. + /// + public class UserRole : UserNameBase + { + /// + /// Gets or sets the role. + /// + /// The role. + [Required] + [StringLength(255)] + [DisplayName("Nom du rôle")] + public string Role + { get; set; } + } +} + diff --git a/yavscModel/Skill/PerformerProfile.cs b/yavscModel/Skill/PerformerProfile.cs new file mode 100644 index 00000000..27f9747f --- /dev/null +++ b/yavscModel/Skill/PerformerProfile.cs @@ -0,0 +1,110 @@ +// +// PerformerProfile.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using Yavsc.Model.RolesAndMembers; +using Yavsc.Model.Skill; +using System.Web.Security; +using System.Web.Profile; +using System.Collections.Generic; +using Yavsc.Model; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Linq; + +namespace Yavsc.Model.Skill +{ + /// + /// Performer profile. + /// + public class PerformerProfile: IRating, IIdentified + { + /// + /// Initializes a new instance of the class. + /// + public PerformerProfile() + { + } + + /// + /// Gets or sets the name of the user. + /// + /// The name of the user. + [Localizable(true), Required(ErrorMessage = "S'il vous plait, entrez un nom d'utilisateur") + ,Display(ResourceType=typeof(LocalizedText),Name="User_name"),RegularExpression("([a-z]|[A-Z]|[0-9] )+")] + public string UserName { get; set; } + public long Id { get; set; } + /// + /// Gets or sets the skills. + /// + /// The skills. + public virtual IEnumerable Skills { get; set; } + /// + /// Initializes a new instance of the class. + /// + /// Username. + public PerformerProfile(string username) + { + UserName = username; + } + + /// + /// Gets the user. + /// + /// The user. + public MembershipUser GetUser() + { + return Membership.GetUser (UserName); + } + private Profile yavscCLientProfile = null; + + /// + /// Gets the yavsc C lient profile. + /// + /// The yavsc C lient profile. + public Profile YavscCLientProfile + { + get { + if (yavscCLientProfile == null) + yavscCLientProfile = new Profile ( + ProfileBase.Create (UserName)); + return yavscCLientProfile; + } + } + /// + /// Gets or sets the rate. + /// + /// The rate. + public int Rate { + get ; + set ; + } + /// + /// Determines whether this instance references the specified skillId. + /// + /// true if this instance has skill the specified skillId; otherwise, false. + /// Skill identifier. + public bool HasSkill(long skillId) + { + return Skills.Any (x => x.SkillId == skillId); + } + } +} + diff --git a/yavscModel/Skill/Skill.cs b/yavscModel/Skill/Skill.cs new file mode 100644 index 00000000..3ec4d78c --- /dev/null +++ b/yavscModel/Skill/Skill.cs @@ -0,0 +1,55 @@ +// +// Skill.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; +using System.Configuration; +using System.Reflection; +using System.Configuration.Provider; +using System.ComponentModel.DataAnnotations; + +namespace Yavsc.Model.Skill +{ + /// + /// Skill. + /// + public class Skill : IRating { + + /// + /// Gets or sets the identifier. + /// + /// The identifier. + public long Id { get; set; } + + /// + /// Gets or sets the name. + /// + /// The name. + [Required(ErrorMessage = "Please, specify a skill name")] + public string Name { get; set; } + + /// + /// Gets or sets the rate. + /// + /// The rate. + public int Rate { get; set; } + } + +} diff --git a/yavscModel/Skill/SkillManager.cs b/yavscModel/Skill/SkillManager.cs new file mode 100644 index 00000000..2cfef8cc --- /dev/null +++ b/yavscModel/Skill/SkillManager.cs @@ -0,0 +1,127 @@ +// +// SkillManager.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using System.Configuration; +using System.Reflection; +using System.Configuration.Provider; + +namespace Yavsc.Model.Skill +{ + + /// + /// Skill manager. + /// + public static class SkillManager + { + private static SkillProvider provider = null; + /// + /// Gets the provider. + /// + /// The provider. + private static SkillProvider Provider { + get { + if (provider == null) + provider = ManagerHelper.GetDefaultProvider + ("system.web/skillProviders"); + return provider; + } + } + + /// + /// Create or modifies the specified skill. + /// + /// the skill. + public static long DeclareSkill(Skill skill) { + Provider.Declare(skill); + return skill.Id; + } + /// + /// Declares the skill. + /// + /// The skill. + /// Dec. + public static long DeclareUserSkill(UserSkillDeclaration dec) { + return Provider.Declare(dec); + } + + /// + /// Rates the user skill. + /// + /// The user skill. + /// Username. + /// User skill. + public static long RateUserSkill(string username, UserSkillRating userSkill) { + return Provider.Rate(userSkill); + } + /// + /// Rates the skill. + /// + /// Username. + /// Skill. + public static void RateSkill(string username, SkillRating skill) { + Provider.Rate(skill); + } + + /// + /// Finds the skills. + /// + /// The skill identifier. + /// Pattern. + public static Skill [] FindSkill(string pattern){ + return Provider.FindSkill(pattern); + } + + /// + /// Finds the performer. + /// + /// The performer. + /// Skill identifiers. + public static string [] FindPerformer(long []skillIds){ + return Provider.FindPerformer(skillIds); + } + /// + /// Deletes the skill. + /// + /// Skill identifier. + public static void DeleteSkill (long skillId) + { + Provider.DeleteSkill (skillId); + } + /// + /// Deletes the user skill. + /// + /// Skill identifier. + public static void DeleteUserSkill (long skillId) + { + Provider.DeleteUserSkill (skillId); + } + /// + /// Gets the user skills. + /// + /// The user skills. + /// User name. + public static PerformerProfile GetUserSkills(string userName) + { + return Provider.GetUserSkills (userName); + } + } +} + diff --git a/yavscModel/Skill/SkillProvider.cs b/yavscModel/Skill/SkillProvider.cs new file mode 100644 index 00000000..d510a5df --- /dev/null +++ b/yavscModel/Skill/SkillProvider.cs @@ -0,0 +1,89 @@ +// +// SkillProvider.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; +using System.Configuration.Provider; + +namespace Yavsc.Model.Skill +{ + /// + /// Skill provider. + /// + public abstract class SkillProvider: ProviderBase + { + /// + /// Declare the specified skill. + /// + /// Skill. + public abstract long Declare(Skill skill) ; + + /// + /// Declare the specified user skill. + /// + /// Userskill. + public abstract long Declare(UserSkillDeclaration userskill) ; + + /// + /// Rate the specified user skill. + /// + /// Userskill. + public abstract long Rate(UserSkillRating userskill) ; + + /// + /// Rate the specified skill. + /// + /// Skill. + public abstract void Rate(SkillRating skill) ; + + /// + /// Finds the skill identifier. + /// + /// The skill identifier. + /// Pattern. + public abstract Skill [] FindSkill(string pattern); + + /// + /// Gets the user skills. + /// + /// The user skills. + /// Username. + public abstract PerformerProfile GetUserSkills(string username) ; + + /// + /// Finds the performer. + /// + /// The performer. + /// Skill identifiers. + public abstract string [] FindPerformer(long []skillIds); + + /// + /// Deletes the skill. + /// + /// Skill identifier. + public abstract void DeleteSkill(long skillId); + + /// + /// Deletes the user skill. + /// + /// User skill identifier. + public abstract void DeleteUserSkill(long userSkillId); + } +} + diff --git a/yavscModel/Skill/SkillRating.cs b/yavscModel/Skill/SkillRating.cs new file mode 100644 index 00000000..071af6dd --- /dev/null +++ b/yavscModel/Skill/SkillRating.cs @@ -0,0 +1,49 @@ +// +// SkillRating.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; + +namespace Yavsc.Model.Skill +{ + /// + /// Skill rating. + /// + public class SkillRating : IRating, IAuthorized + { + /// + /// Gets or sets the skill identifier. + /// + /// The skill identifier. + public long Id { get;set ; } + + /// + /// Gets or sets the rate. + /// + /// The rate. + public int Rate { get; set; } + + /// + /// Gets or sets the author. + /// + /// The author. + public string Author { get; set; } + } +} + diff --git a/yavscModel/Skill/UserSkill.cs b/yavscModel/Skill/UserSkill.cs new file mode 100644 index 00000000..74416f37 --- /dev/null +++ b/yavscModel/Skill/UserSkill.cs @@ -0,0 +1,64 @@ +// +// SkillDeclaration.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +using System; + +namespace Yavsc.Model.Skill +{ + + /// + /// User skill. + /// + public class UserSkill : IComment, IRating + { + /// + /// Gets or sets the identifier for this + /// user's skill. + /// + /// The user's skill identifier. + public long Id { get ; set ; } + + /// + /// Gets or sets the skill identifier. + /// + /// The skill identifier. + public long SkillId { get ; set ; } + + /// + /// Gets or sets the name of the skill. + /// + /// The name of the skill. + public string SkillName { get; set; } + + + /// + /// Gets or sets the rate. + /// + /// The rate. + public int Rate { get ; set ; } + /// + /// Gets or sets the comment. + /// + /// The comment. + public string Comment { get; set; } + + } +} + diff --git a/yavscModel/Skill/UserSkillComment.cs b/yavscModel/Skill/UserSkillComment.cs new file mode 100644 index 00000000..4294d14c --- /dev/null +++ b/yavscModel/Skill/UserSkillComment.cs @@ -0,0 +1,40 @@ +// +// UserSkillComment.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; + +namespace Yavsc.Model.Skill +{ + + /// + /// User skill comment. + /// + public class UserSkillComment: UserSkillReference, IComment + { + + /// + /// Gets or sets the comment. + /// + /// The comment. + public string Comment { get; set; } + } + +} diff --git a/yavscModel/Skill/UserSkillDeclaration.cs b/yavscModel/Skill/UserSkillDeclaration.cs new file mode 100644 index 00000000..fc941b43 --- /dev/null +++ b/yavscModel/Skill/UserSkillDeclaration.cs @@ -0,0 +1,36 @@ +// +// UserSkillDeclaration.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; + +namespace Yavsc.Model.Skill +{ + public class UserSkillDeclaration : UserSkill { + + /// + /// Gets or sets the name of the user. + /// + /// The name of the user. + public string UserName { get; set; } + + } + +} diff --git a/yavscModel/Skill/UserSkillRating.cs b/yavscModel/Skill/UserSkillRating.cs new file mode 100644 index 00000000..dea80cd6 --- /dev/null +++ b/yavscModel/Skill/UserSkillRating.cs @@ -0,0 +1,45 @@ +// +// SkillRating.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; + +namespace Yavsc.Model.Skill +{ + /// + /// Skill rating. + /// + public class UserSkillRating: UserSkillReference, IRating, IAuthorized + { + + /// + /// Gets or sets the rate. + /// + /// The rate. + public int Rate { get; set; } + + /// + /// Gets or sets the author. + /// + /// The author. + public string Author { get; set; } + } + +} diff --git a/yavscModel/Skill/UserSkillReference.cs b/yavscModel/Skill/UserSkillReference.cs new file mode 100644 index 00000000..1e77cfb4 --- /dev/null +++ b/yavscModel/Skill/UserSkillReference.cs @@ -0,0 +1,49 @@ +// +// UserSkillReference.cs +// +// Author: +// Paul Schneider +// +// Copyright (c) 2015 GNU GPL +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . + +using System; + +namespace Yavsc.Model.Skill +{ + /// + /// User skill reference. + /// + public class UserSkillReference { + /// + /// Gets or sets the user's skill identifier. + /// + /// The skill identifier. + public long Id { get;set ; } + + /// + /// Gets or sets the skill identifier. + /// + /// The skill identifier. + public long SkillId { get;set ; } + + /// + /// Gets or sets the name of the performer. + /// + /// The name of the user. + public string Performer { get; set; } + } + +}