From 1a5b20a48bff8f55f1da83f6e958ecbd09d940ab Mon Sep 17 00:00:00 2001 From: Mark McDowall Date: Fri, 24 Feb 2012 08:22:13 -0800 Subject: [PATCH] Validation is working on Settings -> Indexers again. --- .../Helpers/Validation/RequiredIfAttribute.cs | 86 ++++++++++ NzbDrone.Web/Models/IndexerSettingsModel.cs | 9 + NzbDrone.Web/NzbDrone.Web.csproj | 2 + .../Scripts/conditional-validation.js | 43 +++++ NzbDrone.Web/Views/Settings/Indexers.cshtml | 155 +++++++++++++----- .../Views/Shared/_ReferenceLayout.cshtml | 8 +- NzbDrone.Web/Web.config | 4 +- 7 files changed, 263 insertions(+), 44 deletions(-) create mode 100644 NzbDrone.Web/Helpers/Validation/RequiredIfAttribute.cs create mode 100644 NzbDrone.Web/Scripts/conditional-validation.js diff --git a/NzbDrone.Web/Helpers/Validation/RequiredIfAttribute.cs b/NzbDrone.Web/Helpers/Validation/RequiredIfAttribute.cs new file mode 100644 index 000000000..ed147caf6 --- /dev/null +++ b/NzbDrone.Web/Helpers/Validation/RequiredIfAttribute.cs @@ -0,0 +1,86 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web; +using System.Web.Mvc; + +namespace NzbDrone.Web.Helpers.Validation +{ + public class RequiredIfAttribute : ValidationAttribute, IClientValidatable + { + private RequiredAttribute _innerAttribute = new RequiredAttribute(); + + public string DependentProperty { get; set; } + public object TargetValue { get; set; } + + public RequiredIfAttribute(string dependentProperty, object targetValue) + { + this.DependentProperty = dependentProperty; + this.TargetValue = targetValue; + } + + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + // get a reference to the property this validation depends upon + var containerType = validationContext.ObjectInstance.GetType(); + var field = containerType.GetProperty(this.DependentProperty); + + if (field != null) + { + // get the value of the dependent property + var dependentvalue = field.GetValue(validationContext.ObjectInstance, null); + + // compare the value against the target value + if ((dependentvalue == null && this.TargetValue == null) || + (dependentvalue != null && dependentvalue.Equals(this.TargetValue))) + { + // match => means we should try validating this field + if (!_innerAttribute.IsValid(value)) + // validation failed - return an error + return new ValidationResult(this.ErrorMessage, new[] { validationContext.MemberName }); + } + } + + return ValidationResult.Success; + } + + public IEnumerable GetClientValidationRules(ModelMetadata metadata, ControllerContext context) + { + var rule = new ModelClientValidationRule() + { + ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()), + ValidationType = "requiredif", + }; + + string depProp = BuildDependentPropertyId(metadata, context as ViewContext); + + // find the value on the control we depend on; + // if it's a bool, format it javascript style + // (the default is True or False!) + string targetValue = (this.TargetValue ?? "").ToString(); + if (this.TargetValue.GetType() == typeof(bool)) + targetValue = targetValue.ToLower(); + + rule.ValidationParameters.Add("dependentproperty", depProp); + rule.ValidationParameters.Add("targetvalue", targetValue); + + yield return rule; + } + + private string BuildDependentPropertyId(ModelMetadata metadata, ViewContext viewContext) + { + // build the ID of the property + string depProp = viewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(this.DependentProperty); + // unfortunately this will have the name of the current field appended to the beginning, + // because the TemplateInfo's context has had this fieldname appended to it. Instead, we + // want to get the context as though it was one level higher (i.e. outside the current property, + // which is the containing object (our Person), and hence the same level as the dependent property. + var thisField = metadata.PropertyName + "_"; + if (depProp.StartsWith(thisField)) + // strip it off again + depProp = depProp.Substring(thisField.Length); + return depProp; + } + } +} \ No newline at end of file diff --git a/NzbDrone.Web/Models/IndexerSettingsModel.cs b/NzbDrone.Web/Models/IndexerSettingsModel.cs index 722cdc95c..8198b4a6b 100644 --- a/NzbDrone.Web/Models/IndexerSettingsModel.cs +++ b/NzbDrone.Web/Models/IndexerSettingsModel.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using NzbDrone.Core.Repository; +using NzbDrone.Web.Helpers.Validation; namespace NzbDrone.Web.Models { @@ -12,48 +13,56 @@ namespace NzbDrone.Web.Models [DisplayName("Username")] [Description("Username for NZB Matrix")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NzbMatrixEnabled", true, ErrorMessage = "USername Required when NZBMatrix is enabled")] public String NzbMatrixUsername { get; set; } [DataType(DataType.Text)] [DisplayName("API Key")] [Description("API Key for NZB Matrix")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NzbMatrixEnabled", true, ErrorMessage = "API Key Required when NZBMatrix is enabled")] public String NzbMatrixApiKey { get; set; } [DataType(DataType.Text)] [DisplayName("UID")] [Description("User ID for Nzbs.org")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NzbsOrgEnabled", true, ErrorMessage = "UID Required when Nzbs.org is enabled")] public String NzbsOrgUId { get; set; } [DataType(DataType.Text)] [DisplayName("Hash")] [Description("Hash for Nzbs.org")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NzbsOrgEnabled", true, ErrorMessage = "Hash Required when Nzbs.org is enabled")] public String NzbsOrgHash { get; set; } [DataType(DataType.Text)] [DisplayName("UID")] [Description("User ID for NZBsRus")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NzbsRUsEnabled", true, ErrorMessage = "UID Required when NzbsRus is enabled")] public String NzbsrusUId { get; set; } [DataType(DataType.Text)] [DisplayName("Hash")] [Description("Hash for NZBsRus")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NzbsRUsEnabled", true, ErrorMessage = "Hash Required when NzbsRus is enabled")] public String NzbsrusHash { get; set; } [DataType(DataType.Text)] [DisplayName("Username")] [Description("Username for Newzbin")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NewzbinEnabled", true, ErrorMessage = "Username Required when Newzbin is enabled")] public String NewzbinUsername { get; set; } [DataType(DataType.Text)] [DisplayName("Password")] [Description("Password for Newzbin")] [DisplayFormat(ConvertEmptyStringToNull = false)] + [RequiredIf("NewzbinEnabled", true, ErrorMessage = "Password Required when Newzbin is enabled")] public String NewzbinPassword { get; set; } [DisplayName("NZBs.org")] diff --git a/NzbDrone.Web/NzbDrone.Web.csproj b/NzbDrone.Web/NzbDrone.Web.csproj index 7840b37d0..af22938c2 100644 --- a/NzbDrone.Web/NzbDrone.Web.csproj +++ b/NzbDrone.Web/NzbDrone.Web.csproj @@ -131,6 +131,7 @@ + @@ -331,6 +332,7 @@ + diff --git a/NzbDrone.Web/Scripts/conditional-validation.js b/NzbDrone.Web/Scripts/conditional-validation.js new file mode 100644 index 000000000..6c6421726 --- /dev/null +++ b/NzbDrone.Web/Scripts/conditional-validation.js @@ -0,0 +1,43 @@ +/// +/// + +$.validator.addMethod('requiredif', + function (value, element, parameters) { + var id = '#' + parameters['dependentproperty']; + + // get the target value (as a string, + // as that's what actual value will be) + var targetvalue = parameters['targetvalue']; + targetvalue = + (targetvalue == null ? '' : targetvalue).toString(); + + // get the actual value of the target control + // note - this probably needs to cater for more + // control types, e.g. radios + var control = $(id); + var controltype = control.attr('type'); + var actualvalue = + controltype === 'checkbox' ? + (control.attr('checked') == "checked" ? "true" : "false") : + control.val(); + + // if the condition is true, reuse the existing + // required field validator functionality + if (targetvalue === actualvalue) + return $.validator.methods.required.call( + this, value, element, parameters); + + return true; + } +); + +$.validator.unobtrusive.adapters.add( + 'requiredif', + ['dependentproperty', 'targetvalue'], + function (options) { + options.rules['requiredif'] = { + dependentproperty: options.params['dependentproperty'], + targetvalue: options.params['targetvalue'] + }; + options.messages['requiredif'] = options.message; + }); diff --git a/NzbDrone.Web/Views/Settings/Indexers.cshtml b/NzbDrone.Web/Views/Settings/Indexers.cshtml index 4406e11aa..e1aabd36c 100644 --- a/NzbDrone.Web/Views/Settings/Indexers.cshtml +++ b/NzbDrone.Web/Views/Settings/Indexers.cshtml @@ -1,19 +1,46 @@ @using NzbDrone.Web.Helpers @model NzbDrone.Web.Models.IndexerSettingsModel @{ Layout = null; } + +
RSS feeds are checked every 25 minutes for new episodes.
-
- Nzbs.Org
-
- NZBMatrix
-
- NZBsRus
-
- Newzbin
-
- Newznab
+ @Html.CheckBox("nzbsOrgStatus", @Model.NzbsOrgEnabled, new{ @class = "indexerStatusButton" }) + + + @Html.CheckBox("nzbMatrixStatus", @Model.NzbMatrixEnabled, new { @class = "indexerStatusButton" }) + + + @Html.CheckBox("nzbsRusStatus", @Model.NzbsRUsEnabled, new { @class = "indexerStatusButton" }) + + + @Html.CheckBox("newzbinStatus", @Model.NewzbinEnabled, new { @class = "indexerStatusButton" }) + + + @Html.CheckBox("newznabStatus", @Model.NewznabEnabled, new { @class = "indexerStatusButton" }) +
@using (Html.BeginForm("SaveIndexers", "Settings", FormMethod.Post, new { id = "IndexersForm", name = "IndexersForm", @class = "settingsForm" })) @@ -28,10 +55,12 @@ @Html.CheckBoxFor(m => m.NzbsOrgEnabled, new { @class = "inputClass checkClass enabledCheck" }) @Html.TextBoxFor(m => m.NzbsOrgUId, new { @class = "inputClass" }) @Html.TextBoxFor(m => m.NzbsOrgHash, new { @class = "inputClass" })
@@ -44,10 +73,12 @@ @Html.CheckBoxFor(m => m.NzbMatrixEnabled, new { @class = "inputClass checkClass enabledCheck" }) @Html.TextBoxFor(m => m.NzbMatrixUsername, new { @class = "inputClass" }) @Html.TextBoxFor(m => m.NzbMatrixApiKey, new { @class = "inputClass" }) @@ -62,10 +93,12 @@ @Html.CheckBoxFor(m => m.NzbsRUsEnabled, new { @class = "inputClass checkClass enabledCheck" }) @Html.TextBoxFor(m => m.NzbsrusUId, new { @class = "inputClass" }) @Html.TextBoxFor(m => m.NzbsrusHash, new { @class = "inputClass" }) @@ -78,10 +111,12 @@ @Html.CheckBoxFor(m => m.NewzbinEnabled, new { @class = "inputClass checkClass enabledCheck" }) @Html.TextBoxFor(m => m.NewzbinUsername, new { @class = "inputClass" }) @Html.TextBoxFor(m => m.NewzbinPassword, new { @class = "inputClass", type = "password" }) @@ -98,7 +133,7 @@

Add Newznab Provider + height="20px" /> Add Newznab Provider
@foreach (var provider in Model.NewznabDefinitions) @@ -117,47 +152,96 @@ @Html.TextBoxFor(m => m.Retention, new { @class = "inputClass" })
- + + + Please check your settings and re-save } + + diff --git a/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml b/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml index 32826c621..14c3123e7 100644 --- a/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml +++ b/NzbDrone.Web/Views/Shared/_ReferenceLayout.cshtml @@ -21,16 +21,18 @@ @Html.IncludeScript("jquery-1.7.1.min.js") @Html.IncludeScript("jquery-ui-1.8.17.min.js") @Html.IncludeScript("jquery.livequery.js") - @Html.IncludeScript("MicrosoftAjax.js") - @Html.IncludeScript("MicrosoftMvcAjax.js") @Html.IncludeScript("jquery.gritter.js") @Html.IncludeScript("jquery.form.js") @Html.IncludeScript("jquery-tgc-countdown-1.0.js") @Html.IncludeScript("jquery.watermark.min.js") @Html.IncludeScript("jquery.hotkeys.js") - @Html.IncludeScript("jquery.validate.min.js") + @Html.IncludeScript("jquery.signalR.min.js") + @Html.IncludeScript("jquery.validate.js") + @Html.IncludeScript("jquery.validate.unobtrusive.js") @Html.IncludeScript("jquery.cookie.js") @Html.IncludeScript("doTimeout.js") + @Html.IncludeScript("conditional-validation.js") + @Html.IncludeScript("NzbDrone/localSearch.js") @Html.IncludeScript("NzbDrone/AutoComplete.js") @Html.IncludeScript("NzbDrone/Notification.js") diff --git a/NzbDrone.Web/Web.config b/NzbDrone.Web/Web.config index 62bcdbd62..bb088106a 100644 --- a/NzbDrone.Web/Web.config +++ b/NzbDrone.Web/Web.config @@ -8,8 +8,8 @@ - - + +