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.ComponentModel;
using System.ComponentModel.DataAnnotations;
using NzbDrone.Core.Repository;
+using NzbDrone.Web.Helpers.Validation;
namespace NzbDrone.Web.Models
{
@@ -12,48 +13,56 @@ public class IndexerSettingsModel
[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 @@
+ 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 @@
-
-
+
+