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.