diff --git a/src/Umbraco.Core/ApplicationContext.cs b/src/Umbraco.Core/ApplicationContext.cs index b90f3f3e94..45901847eb 100644 --- a/src/Umbraco.Core/ApplicationContext.cs +++ b/src/Umbraco.Core/ApplicationContext.cs @@ -100,7 +100,7 @@ namespace Umbraco.Core // public bool IsConfigured { - // fixme - let's do this for the time being + // fixme - we should not do this - ok for now get { return Configured; diff --git a/src/Umbraco.Core/Attempt.cs b/src/Umbraco.Core/Attempt.cs index 6054056645..ca2bbe35fe 100644 --- a/src/Umbraco.Core/Attempt.cs +++ b/src/Umbraco.Core/Attempt.cs @@ -2,135 +2,165 @@ using System; namespace Umbraco.Core { - public struct AttemptOutcome + /// + /// Provides ways to create attempts. + /// + public static class Attempt { - private readonly bool _success; - - public AttemptOutcome(bool success) + /// + /// Creates a successful attempt with a result. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(T result) { - _success = success; + return Attempt.Succeed(result); } - public AttemptOutcome IfFailed(Func> nextAttempt, Action onSuccess, Action onFail = null) - { - if (_success == false) - { - return ExecuteNextAttempt(nextAttempt, onSuccess, onFail); - } - - //return a successful outcome since the last one was successful, this allows the next AttemptOutcome chained to - // continue properly. - return new AttemptOutcome(true); - } - - public AttemptOutcome IfSuccessful(Func> nextAttempt, Action onSuccess, Action onFail = null) - { - if (_success) - { - return ExecuteNextAttempt(nextAttempt, onSuccess, onFail); - } - //return a failed outcome since the last one was not successful, this allows the next AttemptOutcome chained to - // continue properly. - return new AttemptOutcome(false); - } - - private AttemptOutcome ExecuteNextAttempt(Func> nextAttempt, Action onSuccess, Action onFail = null) - { - var attempt = nextAttempt(); - if (attempt.Success) - { - onSuccess(attempt.Result); - return new AttemptOutcome(true); - } - - if (onFail != null) - { - onFail(attempt.Error); - } - return new AttemptOutcome(false); - } - } - - /// - /// Represents the result of an operation attempt - /// - /// - /// - [Serializable] - public struct Attempt - { - private readonly bool _success; - private readonly T _result; - private readonly Exception _error; - - /// - /// Gets a value indicating whether this represents a successful operation. - /// - /// - public bool Success - { - get { return _success; } - } - - /// - /// Gets the error associated with an unsuccessful attempt. - /// - /// The error. - public Exception Error { get { return _error; } } - - /// - /// Gets the parse result. - /// - /// - public T Result - { - get { return _result; } - } - /// - /// Perform the attempt with callbacks + /// Creates a failed attempt with a result. /// - /// - /// - /// - public static AttemptOutcome Try(Attempt attempt, Action onSuccess, Action onFail = null) + /// The type of the attempted operation result. + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(T result) + { + return Attempt.Fail(result); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The type of the attempted operation result. + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(T result, Exception exception) + { + return Attempt.Fail(result, exception); + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// The type of the attempted operation result. + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt If(bool success, T result) + { + return Attempt.SucceedIf(success, result); + } + + + /// + /// Executes an attempt function, with callbacks. + /// + /// The type of the attempted operation result. + /// The attempt returned by the attempt function. + /// An action to execute in case the attempt succeeds. + /// An action to execute in case the attempt fails. + /// The outcome of the attempt. + /// Runs or depending on the + /// whether the attempt function reports a success or a failure. + public static Outcome Try(Attempt attempt, Action onSuccess, Action onFail = null) { if (attempt.Success) { onSuccess(attempt.Result); - return new AttemptOutcome(true); + return Outcome.Success; } if (onFail != null) { - onFail(attempt.Error); + onFail(attempt.Exception); } - return new AttemptOutcome(false); + + return Outcome.Failure; } - /// - /// Represents an unsuccessful parse operation - /// - public static readonly Attempt False = new Attempt(false, default(T)); + /// + /// Represents the outcome of an attempt. + /// + /// Can be a success or a failure, and allows for attempts chaining. + public struct Outcome + { + private readonly bool _success; - /// - /// Initializes a new instance of the struct. - /// - /// If set to true this tuple represents a successful parse result. - /// The parse result. - /// - public Attempt(bool success, T result) - { - _success = success; - _result = result; - _error = null; - } + /// + /// Gets an outcome representing a success. + /// + public static readonly Outcome Success = new Outcome(true); - public Attempt(Exception error) - { - _success = false; - _result = default(T); - _error = error; - } - } + /// + /// Gets an outcome representing a failure. + /// + public static readonly Outcome Failure = new Outcome(false); + + private Outcome(bool success) + { + _success = success; + } + + /// + /// Executes another attempt function, if the previous one failed, with callbacks. + /// + /// The type of the attempted operation result. + /// The attempt function to execute, returning an attempt. + /// An action to execute in case the attempt succeeds. + /// An action to execute in case the attempt fails. + /// If it executes, returns the outcome of the attempt, else returns a success outcome. + /// + /// Executes only if the previous attempt failed, else does not execute and return a success outcome. + /// If it executes, then runs or depending on the + /// whether the attempt function reports a success or a failure. + /// + public Outcome OnFailure(Func> nextFunction, Action onSuccess, Action onFail = null) + { + return _success + ? Success + : ExecuteNextFunction(nextFunction, onSuccess, onFail); + } + + /// + /// Executes another attempt function, if the previous one succeeded, with callbacks. + /// + /// The type of the attempted operation result. + /// The attempt function to execute, returning an attempt. + /// An action to execute in case the attempt succeeds. + /// An action to execute in case the attempt fails. + /// If it executes, returns the outcome of the attempt, else returns a failed outcome. + /// + /// Executes only if the previous attempt succeeded, else does not execute and return a success outcome. + /// If it executes, then runs or depending on the + /// whether the attempt function reports a success or a failure. + /// + public Outcome OnSuccess(Func> nextFunction, Action onSuccess, Action onFail = null) + { + return _success + ? ExecuteNextFunction(nextFunction, onSuccess, onFail) + : Failure; + } + + private static Outcome ExecuteNextFunction(Func> nextFunction, Action onSuccess, Action onFail = null) + { + var attempt = nextFunction(); + + if (attempt.Success) + { + onSuccess(attempt.Result); + return Success; + } + + if (onFail != null) + { + onFail(attempt.Exception); + } + + return Failure; + } + } + + } } \ No newline at end of file diff --git a/src/Umbraco.Core/Attempt{T}.cs b/src/Umbraco.Core/Attempt{T}.cs new file mode 100644 index 0000000000..4a348247d4 --- /dev/null +++ b/src/Umbraco.Core/Attempt{T}.cs @@ -0,0 +1,164 @@ +using System; +using Umbraco.Core.Dynamics; + +namespace Umbraco.Core +{ + /// + /// Represents the result of an operation attempt. + /// + /// The type of the attempted operation result. + [Serializable] + public struct Attempt + { + private readonly bool _success; + private readonly T _result; + private readonly Exception _exception; + + /// + /// Gets a value indicating whether this was successful. + /// + public bool Success + { + get { return _success; } + } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + public Exception Exception { get { return _exception; } } + + /// + /// Gets the exception associated with an unsuccessful attempt. + /// + /// Keep it for backward compatibility sake. + [Obsolete(".Error is obsolete, you should use .Exception instead.", false)] + public Exception Error { get { return _exception; } } + + /// + /// Gets the attempt result. + /// + public T Result + { + get { return _result; } + } + + // optimize, use a singleton failed attempt + private static readonly Attempt Failed = new Attempt(false, default(T), null); + + /// + /// Represents an unsuccessful attempt. + /// + /// Keep it for backward compatibility sake. + [Obsolete(".Failed is obsolete, you should use Attempt.Fail() instead.", false)] + public static readonly Attempt False = Failed; + + // private - use Succeed() or Fail() methods to create attempts + private Attempt(bool success, T result, Exception exception) + { + _success = success; + _result = result; + _exception = exception; + } + + /// + /// Initialize a new instance of the struct with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// Keep it for backward compatibility sake. + [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] + public Attempt(bool success, T result) + : this(success, result, null) + { } + + /// + /// Initialize a new instance of the struct representing a failed attempt, with an exception. + /// + /// The exception causing the failure of the attempt. + /// Keep it for backward compatibility sake. + [Obsolete("Attempt ctors are obsolete, you should use Attempt.Succeed(), Attempt.Fail() or Attempt.If() instead.", false)] + public Attempt(Exception exception) + : this(false, default(T), exception) + { } + + /// + /// Creates a successful attempt. + /// + /// The successful attempt. + public static Attempt Succeed() + { + return new Attempt(true, default(T), null); + } + + /// + /// Creates a successful attempt with a result. + /// + /// The result of the attempt. + /// The successful attempt. + public static Attempt Succeed(T result) + { + return new Attempt(true, result, null); + } + + /// + /// Creates a failed attempt. + /// + /// The failed attempt. + public static Attempt Fail() + { + return Failed; + } + + /// + /// Creates a failed attempt with an exception. + /// + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(Exception exception) + { + return new Attempt(false, default(T), exception); + } + + /// + /// Creates a failed attempt with a result. + /// + /// The result of the attempt. + /// The failed attempt. + public static Attempt Fail(T result) + { + return new Attempt(false, result, null); + } + + /// + /// Creates a failed attempt with a result and an exception. + /// + /// The result of the attempt. + /// The exception causing the failure of the attempt. + /// The failed attempt. + public static Attempt Fail(T result, Exception exception) + { + return new Attempt(false, result, exception); + } + + /// + /// Creates a successful or a failed attempt. + /// + /// A value indicating whether the attempt is successful. + /// The attempt. + public static Attempt SucceedIf(bool condition) + { + return condition ? new Attempt(true, default(T), null) : Failed; + } + + /// + /// Creates a successful or a failed attempt, with a result. + /// + /// A value indicating whether the attempt is successful. + /// The result of the attempt. + /// The attempt. + public static Attempt SucceedIf(bool condition, T result) + { + return new Attempt(condition, result, null); + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/Configuration/UmbracoSettings/ObjectExtensions.cs b/src/Umbraco.Core/Configuration/UmbracoSettings/ObjectExtensions.cs index fce45a0cd3..614e23ea13 100644 --- a/src/Umbraco.Core/Configuration/UmbracoSettings/ObjectExtensions.cs +++ b/src/Umbraco.Core/Configuration/UmbracoSettings/ObjectExtensions.cs @@ -50,14 +50,14 @@ namespace Umbraco.Core.Configuration.UmbracoSettings try { var converted = (T)input; - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } - return !result.Success ? Attempt.False : new Attempt(true, (T)result.Result); + return !result.Success ? Attempt.Fail() : Attempt.Succeed((T)result.Result); } /// @@ -69,22 +69,22 @@ namespace Umbraco.Core.Configuration.UmbracoSettings /// public static Attempt TryConvertTo(this object input, Type destinationType) { - if (input == null) return Attempt.False; + if (input == null) return Attempt.Fail(); - if (destinationType == typeof(object)) return new Attempt(true, input); + if (destinationType == typeof(object)) return Attempt.Succeed(input); - if (input.GetType() == destinationType) return new Attempt(true, input); + if (input.GetType() == destinationType) return Attempt.Succeed(input); if (input is string && destinationType.IsEnum) { try { var output = Enum.Parse(destinationType, (string)input, true); - return new Attempt(true, output); + return Attempt.Succeed(output); } catch (Exception e) - { - return new Attempt(e); + { + return Attempt.Succeed(e); } } @@ -99,11 +99,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings try { var casted = Convert.ChangeType(input, destinationType); - return new Attempt(true, casted); + return Attempt.Succeed(casted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } } @@ -114,11 +114,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings try { var converted = inputConverter.ConvertTo(input, destinationType); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } @@ -130,11 +130,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings try { var converted = boolConverter.ConvertFrom(input); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } } @@ -145,11 +145,11 @@ namespace Umbraco.Core.Configuration.UmbracoSettings try { var converted = outputConverter.ConvertFrom(input); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } @@ -159,15 +159,15 @@ namespace Umbraco.Core.Configuration.UmbracoSettings try { var casted = Convert.ChangeType(input, destinationType); - return new Attempt(true, casted); + return Attempt.Succeed(casted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } - return Attempt.False; + return Attempt.Fail(); } public static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) diff --git a/src/Umbraco.Core/Constants-Examine.cs b/src/Umbraco.Core/Constants-Examine.cs new file mode 100644 index 0000000000..f040634506 --- /dev/null +++ b/src/Umbraco.Core/Constants-Examine.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Umbraco.Core +{ + public static partial class Constants + { + public static class Examine + { + /// + /// The alias of the internal member searcher + /// + public const string InternalMemberSearcher = "InternalMemberSearcher"; + + /// + /// The alias of the internal content searcher + /// + public const string InternalSearcher = "InternalSearcher"; + } + } +} diff --git a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs index a7bb20ef5b..1338ee03e2 100644 --- a/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs +++ b/src/Umbraco.Core/Dynamics/DynamicInstanceHelper.cs @@ -82,7 +82,7 @@ namespace Umbraco.Core.Dynamics null, thisObject, args); - return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); + return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundProperty)); } catch (MissingMethodException) { @@ -97,7 +97,7 @@ namespace Umbraco.Core.Dynamics null, thisObject, args); - return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); + return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundMethod)); } catch (MissingMethodException) { @@ -106,13 +106,13 @@ namespace Umbraco.Core.Dynamics try { result = FindAndExecuteExtensionMethod(thisObject, args, binder.Name, findExtensionMethodsOnTypes); - return new Attempt(true, new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); + return Attempt.Succeed(new TryInvokeMemberResult(result, TryInvokeMemberSuccessReason.FoundExtensionMethod)); } catch (TargetInvocationException ext) { //don't log here, we return this exception because the caller may need to do something specific when //this exception occurs. - return new Attempt(ext); + return Attempt.Fail(ext); } catch (Exception ex) { @@ -124,16 +124,16 @@ namespace Umbraco.Core.Dynamics sb.Append(t + ","); } LogHelper.Error(sb.ToString(), ex); - return new Attempt(ex); + return Attempt.Fail(ex); } } - return Attempt.False; + return Attempt.Fail(); } } catch (Exception ex) { LogHelper.Error("An unhandled exception occurred in method TryInvokeMember", ex); - return new Attempt(ex); + return Attempt.Fail(ex); } } diff --git a/src/Umbraco.Core/Dynamics/DynamicXml.cs b/src/Umbraco.Core/Dynamics/DynamicXml.cs index 0e0d14d9c9..a84389d2e5 100644 --- a/src/Umbraco.Core/Dynamics/DynamicXml.cs +++ b/src/Umbraco.Core/Dynamics/DynamicXml.cs @@ -201,7 +201,7 @@ namespace Umbraco.Core.Dynamics //this is the result of an extension method execution gone wrong so we return dynamic null if (attempt.Result.Reason == DynamicInstanceHelper.TryInvokeMemberSuccessReason.FoundExtensionMethod - && attempt.Error != null && attempt.Error is TargetInvocationException) + && attempt.Exception != null && attempt.Exception is TargetInvocationException) { result = new DynamicNull(); return true; @@ -261,7 +261,7 @@ namespace Umbraco.Core.Dynamics var attributes = xmlElement.Attributes(name).Select(attr => attr.Value).ToArray(); if (attributes.Any()) { - return new Attempt>(true, attributes); + return Attempt>.Succeed(attributes); } if (!attributes.Any() && xmlElement.Name == "root" && xmlElement.Elements().Count() == 1) @@ -271,12 +271,12 @@ namespace Umbraco.Core.Dynamics if (childElements.Any()) { //we've found a match by the first child of an element called 'root' (strange, but sure) - return new Attempt>(true, childElements); + return Attempt>.Succeed(childElements); } } //no deal - return Attempt>.False; + return Attempt>.Fail(); } /// @@ -293,7 +293,7 @@ namespace Umbraco.Core.Dynamics //Check if we've got any matches, if so then return true if (elements.Any()) { - return new Attempt>(true, elements); + return Attempt>.Succeed(elements); } if (!elements.Any() && xmlElement.Name == "root" && xmlElement.Elements().Count() == 1) @@ -303,12 +303,12 @@ namespace Umbraco.Core.Dynamics if (childElements.Any()) { //we've found a match by the first child of an element called 'root' (strange, but sure) - return new Attempt>(true, childElements); + return Attempt>.Succeed(childElements); } } //no deal - return Attempt>.False; + return Attempt>.Fail(); } private bool HandleIEnumerableXElement(IEnumerable elements, out object result) diff --git a/src/Umbraco.Core/ObjectExtensions.cs b/src/Umbraco.Core/ObjectExtensions.cs index 879c1a60e8..96e402a9b2 100644 --- a/src/Umbraco.Core/ObjectExtensions.cs +++ b/src/Umbraco.Core/ObjectExtensions.cs @@ -58,14 +58,14 @@ namespace Umbraco.Core try { var converted = (T) input; - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } - return !result.Success ? Attempt.False : new Attempt(true, (T)result.Result); + return !result.Success ? Attempt.Fail() : Attempt.Succeed((T)result.Result); } /// @@ -80,15 +80,18 @@ namespace Umbraco.Core //if it is null and it is nullable, then return success with null if (input == null && destinationType.IsGenericType && destinationType.GetGenericTypeDefinition() == typeof (Nullable<>)) { - return new Attempt(true, null); + return Attempt.Succeed(null); } //if its not nullable then return false - if (input == null) return Attempt.False; + if (input == null) return Attempt.Fail(); - if (destinationType == typeof(object)) return new Attempt(true, input); + if (destinationType == typeof(object)) return Attempt.Succeed(input); - if (input.GetType() == destinationType) return new Attempt(true, input); + if (input.GetType() == destinationType) return Attempt.Succeed(input); + + //check for string so that overloaders of ToString() can take advantage of the conversion. + if (destinationType == typeof(string)) return Attempt.Succeed(input.ToString()); if (!destinationType.IsGenericType || destinationType.GetGenericTypeDefinition() != typeof(Nullable<>)) { @@ -101,11 +104,11 @@ namespace Umbraco.Core try { var casted = Convert.ChangeType(input, destinationType); - return new Attempt(true, casted); + return Attempt.Succeed(casted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } } @@ -116,11 +119,11 @@ namespace Umbraco.Core try { var converted = inputConverter.ConvertTo(input, destinationType); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } @@ -132,11 +135,11 @@ namespace Umbraco.Core try { var converted = boolConverter.ConvertFrom(input); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } } @@ -147,11 +150,11 @@ namespace Umbraco.Core try { var converted = outputConverter.ConvertFrom(input); - return new Attempt(true, converted); + return Attempt.Succeed(converted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } @@ -161,15 +164,15 @@ namespace Umbraco.Core try { var casted = Convert.ChangeType(input, destinationType); - return new Attempt(true, casted); + return Attempt.Succeed(casted); } catch (Exception e) { - return new Attempt(e); + return Attempt.Fail(e); } } - return Attempt.False; + return Attempt.Fail(); } internal static void CheckThrowObjectDisposed(this IDisposable disposable, bool isDisposed, string objectname) @@ -358,11 +361,11 @@ namespace Umbraco.Core try { var output = value.ToXmlString(type); - return new Attempt(true, output); + return Attempt.Succeed(output); } catch (NotSupportedException ex) { - return new Attempt(ex); + return Attempt.Fail(ex); } } diff --git a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs index cb5d7c95a1..4b6b513bf2 100644 --- a/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs +++ b/src/Umbraco.Core/Persistence/Mappers/MappingResolver.cs @@ -67,19 +67,19 @@ namespace Umbraco.Core.Persistence.Mappers if (mapper == null) { - return Attempt.False; + return Attempt.Fail(); } try { var instance = Activator.CreateInstance(mapper) as BaseMapper; return instance != null - ? new Attempt(true, instance) - : Attempt.False; + ? Attempt.Succeed(instance) + : Attempt.Fail(); } catch (Exception ex) { LogHelper.Error(typeof(MappingResolver), "Could not instantiate mapper of type " + mapper, ex); - return new Attempt(ex); + return Attempt.Fail(ex); } } diff --git a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs index a92dc72f25..3119c55df6 100644 --- a/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs +++ b/src/Umbraco.Core/Persistence/Migrations/Syntax/Alter/Expressions/AlterColumnExpression.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Persistence.Migrations.Syntax.Alter.Expressions // SqlSyntaxContext.SqlSyntaxProvider.GetQuotedColumnName(Column.Name), // SqlSyntaxContext.SqlSyntaxProvider.Format(Column)); - return string.Format(SqlSyntaxContext.SqlSyntaxProvider.AlterColumn, + return string.Format(SqlSyntaxContext.SqlSyntaxProvider.AlterColumn, SqlSyntaxContext.SqlSyntaxProvider.GetQuotedTableName(TableName), SqlSyntaxContext.SqlSyntaxProvider.Format(Column)); diff --git a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs index 06fa303123..6425c9f566 100644 --- a/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs +++ b/src/Umbraco.Core/Persistence/Repositories/RepositoryBase.cs @@ -118,9 +118,9 @@ namespace Umbraco.Core.Persistence.Repositories var rEntity = _cache.GetById(typeof(TEntity), key); if (rEntity != null) { - return new Attempt(true, (TEntity) rEntity); + return Attempt.Succeed((TEntity) rEntity); } - return Attempt.False; + return Attempt.Fail(); } protected abstract IEnumerable PerformGetAll(params TId[] ids); diff --git a/src/Umbraco.Core/PluginManager.cs b/src/Umbraco.Core/PluginManager.cs index 039605d935..50df137ece 100644 --- a/src/Umbraco.Core/PluginManager.cs +++ b/src/Umbraco.Core/PluginManager.cs @@ -243,7 +243,7 @@ namespace Umbraco.Core { var filePath = GetPluginListFilePath(); if (!File.Exists(filePath)) - return Attempt>.False; + return Attempt>.Fail(); try { @@ -263,7 +263,7 @@ namespace Umbraco.Core if (xml.Root == null) - return Attempt>.False; + return Attempt>.Fail(); var typeElement = xml.Root.Elements() .SingleOrDefault(x => @@ -273,18 +273,16 @@ namespace Umbraco.Core //return false but specify this exception type so we can detect it if (typeElement == null) - return new Attempt>(new CachedPluginNotFoundInFile()); + return Attempt>.Fail(new CachedPluginNotFoundInFile()); //return success - return new Attempt>( - true, - typeElement.Elements("add") + return Attempt.Succeed(typeElement.Elements("add") .Select(x => (string)x.Attribute("type"))); } catch (Exception ex) { //if the file is corrupted, etc... return false - return new Attempt>(ex); + return Attempt>.Fail(ex); } } @@ -655,7 +653,7 @@ namespace Umbraco.Core //here we need to identify if the CachedPluginNotFoundInFile was the exception, if it was then we need to re-scan //in some cases the plugin will not have been scanned for on application startup, but the assemblies haven't changed //so in this instance there will never be a result. - if (fileCacheResult.Error != null && fileCacheResult.Error is CachedPluginNotFoundInFile) + if (fileCacheResult.Exception != null && fileCacheResult.Exception is CachedPluginNotFoundInFile) { //we don't have a cache for this so proceed to look them up by scanning LoadViaScanningAndUpdateCacheFile(typeList, resolutionType, finder); diff --git a/src/Umbraco.Core/Profiling/WebProfiler.cs b/src/Umbraco.Core/Profiling/WebProfiler.cs index 3a1974279e..d3fd0daae0 100644 --- a/src/Umbraco.Core/Profiling/WebProfiler.cs +++ b/src/Umbraco.Core/Profiling/WebProfiler.cs @@ -143,16 +143,16 @@ namespace Umbraco.Core.Profiling private Attempt TryGetRequest(object sender) { var app = sender as HttpApplication; - if (app == null) return Attempt.False; + if (app == null) return Attempt.Fail(); try { var req = app.Request; - return new Attempt(true, new HttpRequestWrapper(req)); + return Attempt.Succeed(new HttpRequestWrapper(req)); } catch (HttpException ex) { - return new Attempt(ex); + return Attempt.Fail(ex); } } } diff --git a/src/Umbraco.Core/PublishedContentHelper.cs b/src/Umbraco.Core/PublishedContentHelper.cs index ebb7b5c083..b663f3f904 100644 --- a/src/Umbraco.Core/PublishedContentHelper.cs +++ b/src/Umbraco.Core/PublishedContentHelper.cs @@ -93,7 +93,7 @@ namespace Umbraco.Core /// internal static Attempt ConvertPropertyValue(object currentValue, PublishedPropertyDefinition propertyDefinition) { - if (currentValue == null) return Attempt.False; + if (currentValue == null) return Attempt.Fail(); //First, we need to check the v7+ PropertyValueConverters var converters = PropertyValueConvertersResolver.Current.Converters @@ -135,7 +135,7 @@ namespace Umbraco.Core .Select(p => p.ConvertPropertyValue(currentValue)) .Where(converted => converted.Success)) { - return new Attempt(true, converted.Result); + return Attempt.Succeed(converted.Result); } //if none of the converters worked, then we'll process this from what we know @@ -148,17 +148,17 @@ namespace Umbraco.Core decimal dResult; if (decimal.TryParse(sResult, System.Globalization.NumberStyles.Number, System.Globalization.CultureInfo.CurrentCulture, out dResult)) { - return new Attempt(true, dResult); + return Attempt.Succeed(dResult); } } //process string booleans as booleans if (sResult.InvariantEquals("true")) { - return new Attempt(true, true); + return Attempt.Succeed(true); } if (sResult.InvariantEquals("false")) { - return new Attempt(true, false); + return Attempt.Succeed(false); } //a really rough check to see if this may be valid xml @@ -177,16 +177,16 @@ namespace Umbraco.Core if (UmbracoConfiguration.Current.UmbracoSettings.Scripting.NotDynamicXmlDocumentElements.Any( tag => string.Equals(tag.Element, documentElement, StringComparison.CurrentCultureIgnoreCase)) == false) { - return new Attempt(true, new DynamicXml(e)); + return Attempt.Succeed(new DynamicXml(e)); } - return Attempt.False; + return Attempt.Fail(); } catch (Exception) { - return Attempt.False; + return Attempt.Fail(); } } - return Attempt.False; + return Attempt.Fail(); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Publishing/PublishingStrategy.cs b/src/Umbraco.Core/Publishing/PublishingStrategy.cs index 5f874a00b0..899567bf8d 100644 --- a/src/Umbraco.Core/Publishing/PublishingStrategy.cs +++ b/src/Umbraco.Core/Publishing/PublishingStrategy.cs @@ -25,7 +25,7 @@ namespace Umbraco.Core.Publishing { LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); } @@ -35,7 +35,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedHasExpired)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedHasExpired)); } //Check if the Content is Awaiting Release to verify that it can in fact be published @@ -44,7 +44,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedAwaitingRelease)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedAwaitingRelease)); } //Check if the Content is Trashed to verify that it can in fact be published @@ -53,7 +53,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", content.Name, content.Id)); - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedIsTrashed)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedIsTrashed)); } content.ChangePublishedState(PublishedState.Published); @@ -62,7 +62,7 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been published.", content.Name, content.Id)); - return new Attempt(true, new PublishStatus(content)); + return Attempt.Succeed(new PublishStatus(content)); } /// @@ -125,7 +125,7 @@ namespace Umbraco.Core.Publishing //We're going to populate the statuses with all content that is already published because below we are only going to iterate over // content that is not published. We'll set the status to "AlreadyPublished" statuses.AddRange(fetchedContent.Where(x => x.Published) - .Select(x => new Attempt(true, new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished)))); + .Select(x => Attempt.Succeed(new PublishStatus(x, PublishStatusType.SuccessAlreadyPublished)))); int? firstLevel = null; @@ -169,7 +169,7 @@ namespace Umbraco.Core.Publishing //the publishing has been cancelled. LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -183,7 +183,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published because some of it's content is not passing validation rules.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedContentInvalid))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedContentInvalid))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -197,7 +197,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' has expired and could not be published.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedHasExpired))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedHasExpired))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -211,7 +211,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is awaiting release and could not be published.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedAwaitingRelease))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedAwaitingRelease))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -225,7 +225,7 @@ namespace Umbraco.Core.Publishing LogHelper.Info( string.Format("Content '{0}' with Id '{1}' is trashed and could not be published.", item.Name, item.Id)); - statuses.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedIsTrashed))); + statuses.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedIsTrashed))); //Does this document apply to our rule to cancel it's children being published? CheckCancellingOfChildPublishing(item, parentsIdsCancelled, includeUnpublishedDocuments); @@ -239,7 +239,7 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been published.", item.Name, item.Id)); - statuses.Add(new Attempt(true, new PublishStatus(item))); + statuses.Add(Attempt.Succeed(new PublishStatus(item))); } } @@ -349,7 +349,7 @@ namespace Umbraco.Core.Publishing { LogHelper.Info( string.Format("Content '{0}' with Id '{1}' will not be published, the event was cancelled.", item.Name, item.Id)); - result.Add(new Attempt(false, new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); + result.Add(Attempt.Fail(new PublishStatus(item, PublishStatusType.FailedCancelledByEvent))); continue; } @@ -370,7 +370,7 @@ namespace Umbraco.Core.Publishing string.Format("Content '{0}' with Id '{1}' has been unpublished.", item.Name, item.Id)); - result.Add(new Attempt(true, new PublishStatus(item))); + result.Add(Attempt.Succeed(new PublishStatus(item))); } return result; diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index 1c64ef9667..fc8daac769 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1482,7 +1482,7 @@ namespace Umbraco.Core.Services string.Format( "Content '{0}' with Id '{1}' could not be published because its parent or one of its ancestors is not published.", content.Name, content.Id)); - result.Add(new Attempt(false, new PublishStatus(content, PublishStatusType.FailedPathNotPublished))); + result.Add(Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedPathNotPublished))); return result; } @@ -1493,7 +1493,7 @@ namespace Umbraco.Core.Services string.Format("Content '{0}' with Id '{1}' could not be published because of invalid properties.", content.Name, content.Id)); result.Add( - new Attempt(false, + Attempt.Fail( new PublishStatus(content, PublishStatusType.FailedContentInvalid) { InvalidProperties = ((ContentBase) content).LastInvalidProperties @@ -1599,7 +1599,7 @@ namespace Umbraco.Core.Services { if (Saving.IsRaisedEventCancelled(new SaveEventArgs(content), this)) { - return new Attempt(false, new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); + return Attempt.Fail(new PublishStatus(content, PublishStatusType.FailedCancelledByEvent)); } } @@ -1669,7 +1669,7 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Publish, "Save and Publish performed by user", userId, content.Id); - return new Attempt(publishStatus.StatusType == PublishStatusType.Success, publishStatus); + return Attempt.If(publishStatus.StatusType == PublishStatusType.Success, publishStatus); } } diff --git a/src/Umbraco.Core/TypeHelper.cs b/src/Umbraco.Core/TypeHelper.cs index 731a8de1f4..9e19b46c31 100644 --- a/src/Umbraco.Core/TypeHelper.cs +++ b/src/Umbraco.Core/TypeHelper.cs @@ -95,11 +95,11 @@ namespace Umbraco.Core { if (types.Length == 0) { - return Attempt.False; + return Attempt.Fail(); } if (types.Length == 1) { - return new Attempt(true, types[0]); + return Attempt.Succeed(types[0]); } foreach (var curr in types) @@ -112,11 +112,11 @@ namespace Umbraco.Core //if this type is the base for all others if (isBase) { - return new Attempt(true, curr); + return Attempt.Succeed(curr); } } - return Attempt.False; + return Attempt.Fail(); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 397b894e06..32941531c3 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -114,7 +114,7 @@ - + @@ -239,6 +239,8 @@ + + diff --git a/src/Umbraco.Core/UriExtensions.cs b/src/Umbraco.Core/UriExtensions.cs index 5de4285469..2fda214e01 100644 --- a/src/Umbraco.Core/UriExtensions.cs +++ b/src/Umbraco.Core/UriExtensions.cs @@ -1,8 +1,6 @@ using System; using System.IO; using System.Linq; -using System.Text; -using System.Web; using Umbraco.Core.Configuration; using Umbraco.Core.IO; @@ -75,6 +73,7 @@ namespace Umbraco.Core /// internal static bool IsClientSideRequest(this Uri url) { + // fixme - but really, is this OK? we should accept either no url, or .aspx, and everything else is out var toIgnore = new[] { ".js", ".css", ".ico", ".png", ".jpg", ".jpeg", ".gif", ".html", ".svg" }; return toIgnore.Any(x => Path.GetExtension(url.LocalPath).InvariantEquals(x)); } @@ -88,7 +87,7 @@ namespace Umbraco.Core /// Everything else remains unchanged, except for the fragment which is removed. public static Uri Rewrite(this Uri uri, string path) { - if (!path.StartsWith("/")) + if (path.StartsWith("/") == false) throw new ArgumentException("Path must start with a slash.", "path"); return uri.IsAbsoluteUri @@ -106,9 +105,9 @@ namespace Umbraco.Core /// Everything else remains unchanged, except for the fragment which is removed. public static Uri Rewrite(this Uri uri, string path, string query) { - if (!path.StartsWith("/")) + if (path.StartsWith("/") == false) throw new ArgumentException("Path must start with a slash.", "path"); - if (query.Length > 0 && !query.StartsWith("?")) + if (query.Length > 0 && query.StartsWith("?") == false) throw new ArgumentException("Query must start with a question mark.", "query"); if (query == "?") query = ""; @@ -171,15 +170,14 @@ namespace Umbraco.Core var path = uri.GetSafeAbsolutePath(); if (uri.IsAbsoluteUri) { - if (path != "/" && !path.EndsWith("/")) + if (path != "/" && path.EndsWith("/") == false) uri = new Uri(uri.GetLeftPart(UriPartial.Authority) + path + "/" + uri.Query); return uri; } - else - { - if (path != "/" && !path.EndsWith("/")) - uri = new Uri(path + "/" + uri.Query, UriKind.Relative); - } + + if (path != "/" && path.EndsWith("/") == false) + uri = new Uri(path + "/" + uri.Query, UriKind.Relative); + return uri; } diff --git a/src/Umbraco.Core/XmlHelper.cs b/src/Umbraco.Core/XmlHelper.cs index 382f8975a2..b120eed8b8 100644 --- a/src/Umbraco.Core/XmlHelper.cs +++ b/src/Umbraco.Core/XmlHelper.cs @@ -50,7 +50,7 @@ namespace Umbraco.Core { try { - doc = new XPathDocument(new XmlTextReader(new StringReader(xml))); + doc = CreateXPathDocument(xml); return true; } catch (Exception) @@ -63,23 +63,68 @@ namespace Umbraco.Core /// /// Tries to create a new XPathDocument from a property value. /// - /// The alias of the property. /// The value of the property. /// The XPath document. /// A value indicating whether it has been possible to create the document. - public static bool TryCreateXPathDocumentFromPropertyValue(string alias, object value, out XPathDocument doc) + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXPathDocumentFromPropertyValue(object value, out XPathDocument doc) { - // In addition, DynamicNode strips dashes in elements or attributes - // names but really, this is ugly enough, and using dashes should be - // illegal in content type or property aliases anyway. + // DynamicNode.ConvertPropertyValueByDataType first cleans the value by calling + // XmlHelper.StripDashesInElementOrAttributeName - this is because the XML is + // to be returned as a DynamicXml and element names such as "value-item" are + // invalid and must be converted to "valueitem". But we don't have that sort of + // problem here - and we don't need to bother with dashes nor dots, etc. doc = null; var xml = value as string; - if (xml == null) return false; - xml = xml.Trim(); - if (xml.StartsWith("<") == false || xml.EndsWith(">") == false || xml.Contains('/') == false) return false; - if (UmbracoConfiguration.Current.UmbracoSettings.Scripting.NotDynamicXmlDocumentElements.Any(x => x.Element.InvariantEquals(alias))) return false; - return TryCreateXPathDocument(xml, out doc); + if (xml == null) return false; // no a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + if (TryCreateXPathDocument(xml, out doc) == false) return false; // string can't be parsed into xml + + var nav = doc.CreateNavigator(); + if (nav.MoveToFirstChild()) + { + var name = nav.LocalName; // must not match an excluded tag + if (LegacyUmbracoSettings.NotDynamicXmlDocumentElements.All(x => x.InvariantEquals(name) == false)) return true; + } + + doc = null; + return false; + } + + /// + /// Tries to create a new XElement from a property value. + /// + /// The value of the property. + /// The Xml element. + /// A value indicating whether it has been possible to create the element. + /// The value can be anything... Performance-wise, this is bad. + public static bool TryCreateXElementFromPropertyValue(object value, out XElement elt) + { + // see note above in TryCreateXPathDocumentFromPropertyValue... + + elt = null; + var xml = value as string; + if (xml == null) return false; // not a string + if (CouldItBeXml(xml) == false) return false; // string does not look like it's xml + if (IsXmlWhitespace(xml)) return false; // string is whitespace, xml-wise + + try + { + elt = XElement.Parse(xml, LoadOptions.None); + } + catch + { + elt = null; + return false; // string can't be parsed into xml + } + + var name = elt.Name.LocalName; // must not match an excluded tag + if (LegacyUmbracoSettings.NotDynamicXmlDocumentElements.All(x => x.InvariantEquals(name) == false)) return true; + + elt = null; + return false; } /// @@ -137,8 +182,8 @@ namespace Umbraco.Core } } } - + // used by DynamicNode only, see note in TryCreateXPathDocumentFromPropertyValue public static string StripDashesInElementOrAttributeNames(string xml) { using (var outputms = new MemoryStream()) @@ -289,17 +334,10 @@ namespace Umbraco.Core /// public static bool CouldItBeXml(string xml) { - if (string.IsNullOrEmpty(xml) == false) - { - xml = xml.Trim(); + if (string.IsNullOrEmpty(xml)) return false; - if (xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains("/")) - { - return true; - } - } - - return false; + xml = xml.Trim(); + return xml.StartsWith("<") && xml.EndsWith(">") && xml.Contains('/'); } /// diff --git a/src/Umbraco.Tests/AttemptTests.cs b/src/Umbraco.Tests/AttemptTests.cs index 9887dc1ea1..5fd4da7f8e 100644 --- a/src/Umbraco.Tests/AttemptTests.cs +++ b/src/Umbraco.Tests/AttemptTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using NUnit.Framework; using Umbraco.Core; @@ -7,26 +7,35 @@ namespace Umbraco.Tests [TestFixture] public class AttemptTests { - [Test] public void Chained_Attempts() { - Attempt.Try(new Attempt(true, "success!"), - s => Assert.AreEqual("success!", s), - exception => Assert.Fail("It was successful")) - .IfFailed(() => new Attempt(true, 123), - i => Assert.Fail("The previous attempt was successful!"), - exception => Assert.Fail("The previous attempt was successful!")) - .IfSuccessful(() => new Attempt(new Exception("Failed!")), - d => Assert.Fail("An exception was thrown"), - exception => Assert.AreEqual("Failed!", exception.Message)) - .IfSuccessful(() => new Attempt(true, 987), - i => Assert.Fail("The previous attempt failed!"), - exception => Assert.Fail("The previous attempt failed!")) - .IfFailed(() => new Attempt(true, "finished"), - i => Assert.AreEqual("finished", i), - exception => Assert.Fail("It was successful")); - } + Attempt.Try(Attempt.Succeed("success!"), + s => Assert.AreEqual("success!", s), + exception => Assert.Fail("Should have been successful.")) + // previous one was a success so that one SHOULD NOT run + // and report success + .OnFailure(() => Attempt.Succeed(123), + i => Assert.Fail("The previous attempt was successful!"), + exception => Assert.Fail("The previous attempt was successful!")) + + // previous one did not run, last run was a success so that one SHOULD run + // and report failure + .OnSuccess(() => Attempt.Fail(new Exception("Failed!")), + d => Assert.Fail("Should have failed."), + exception => Assert.AreEqual("Failed!", exception.Message)) + + // previous one did run and was a failure so that one SHOULD NOT run + .OnSuccess(() => Attempt.Succeed(987), + i => Assert.Fail("The previous attempt failed!"), + exception => Assert.Fail("The previous attempt failed!")) + + // previous one did not run, last run was a failure so that one SHOULD run + // then why does it run? + .OnFailure(() => Attempt.Succeed("finished"), + i => Assert.AreEqual("finished", i), + exception => Assert.Fail("Should have been successful.")); + } } -} \ No newline at end of file +} diff --git a/src/Umbraco.Tests/ObjectExtensionsTests.cs b/src/Umbraco.Tests/ObjectExtensionsTests.cs index 8c333e4296..acbd747e6f 100644 --- a/src/Umbraco.Tests/ObjectExtensionsTests.cs +++ b/src/Umbraco.Tests/ObjectExtensionsTests.cs @@ -113,5 +113,22 @@ namespace Umbraco.Tests Assert.AreEqual(DateTime.Equals(dateTime.Date, result.Result.Date), testCase.Value); } } + [Test] + public virtual void CanConvertObjectToString_Using_ToString_Overload() + { + var result = new MyTestObject().TryConvertTo(); + + Assert.IsTrue(result.Success); + Assert.AreEqual("Hello world", result.Result); + } + + + private class MyTestObject + { + public override string ToString() + { + return "Hello world"; + } + } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs new file mode 100644 index 0000000000..57d1ee3492 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Subpage.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + /// + /// Represents a Subpage which acts as the strongly typed model for a Doc Type + /// with alias "Subpage" and "Subpage" as the allowed child type. + /// + /// Similar to the Textpage this model could also be generated, but it could also + /// act as a Code-First model by using the attributes shown on the various properties, + /// which decorate the model with information about the Document Type, its + /// Property Groups and Property Types. + /// + public class Subpage : TypedModelBase + { + public Subpage(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return Resolve(Property()); } } + + public string BodyText { get { return Resolve(Property()); } } + + public Textpage Parent + { + get + { + return Parent(); + } + } + + public IEnumerable Subpages + { + get + { + return Children(ContentTypeAlias()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs new file mode 100644 index 0000000000..fff1ae9107 --- /dev/null +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/Textpage.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using Umbraco.Core.Models; + +namespace Umbraco.Tests.PublishedContent.StronglyTypedModels +{ + /// + /// Represents a Textpage which acts as the strongly typed model for a Doc Type + /// with alias "Textpage" and "Subpage" as the allowed child type. + /// + /// The basic properties are resolved by convention using the Resolve-Type-PropertyTypeAlias + /// convention available through the base class' protected Resolve-method and Property-delegate. + /// + /// The Textpage allows the use of Subpage and Textpage as child doc types, which are exposed as a + /// collection using the Children-Type-ContentTypeAlias convention available through the + /// base class' protected Children-method and ContentTypeAlias-delegate. + /// + /// + /// This code can easily be generated using simple conventions for the types and names + /// of the properties that this type of strongly typed model exposes. + /// + public class Textpage : TypedModelBase + { + public Textpage(IPublishedContent publishedContent) : base(publishedContent) + { + } + + public string Title { get { return Resolve(Property()); } } + + public string BodyText { get { return Resolve(Property()); } } + + public string AuthorName { get { return Resolve(Property()); } } + + public DateTime Date { get { return Resolve(Property()); } } + + public Textpage Parent + { + get + { + return Parent(); + } + } + + public IEnumerable Textpages + { + get + { + return Children(ContentTypeAlias()); + } + } + + public IEnumerable Subpages + { + get + { + return Children(ContentTypeAlias()); + } + } + } +} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs deleted file mode 100644 index 47f2f817f1..0000000000 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TextpageModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Umbraco.Core.Models; - -namespace Umbraco.Tests.PublishedContent.StronglyTypedModels -{ - public class TextpageModel : TypedModelBase - { - public TextpageModel(IPublishedContent publishedContent) : base(publishedContent) - { - } - - public string Title { get { return Resolve(Property(), DefaultString); } } - - public string BodyText { get { return Resolve(Property(), DefaultString); } } - - public string AuthorName { get { return Resolve(Property()); } } - - public DateTime Date { get { return Resolve(Property(), DefaultDateTime); } } - } -} \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs index 4cc6e2ebf2..d606be3c5b 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/TypedModelBase.cs @@ -1,12 +1,34 @@ using System; +using System.Collections.Generic; +using System.Data.Entity.Design.PluralizationServices; +using System.Globalization; using System.Reflection; +using System.Linq; using Umbraco.Core; using Umbraco.Core.Models; using Umbraco.Web; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { - public abstract class TypedModelBase + /// + /// Represents the abstract base class for a 'TypedModel', which basically wraps IPublishedContent + /// underneath a strongly typed model like "Textpage" and "Subpage". + /// Because IPublishedContent is used under the hood there is no need for additional mapping, so the + /// only added cost should be the creation of the objects, which the IPublishedContent instance is + /// passed into. + /// + /// This base class exposes a simple way to write property getters by convention without + /// using the string alias of a PropertyType (this is resolved by the use of the Property delegate). + /// + /// This base class also exposes query options like Parent, Children, Ancestors and Descendants, + /// which can be used for collections of strongly typed child models/objects. These types of collections + /// typically corresponds to 'allowed child content types' on a Doc Type (at different levels). + /// + /// The IPublishedContent properties are also exposed through this base class, but only + /// by casting the typed model to IPublishedContent, so the properties doesn't show up by default: + /// ie. ((IPublishedContent)textpage).Url + /// + public abstract class TypedModelBase : IPublishedContent { private readonly IPublishedContent _publishedContent; @@ -15,56 +37,168 @@ namespace Umbraco.Tests.PublishedContent.StronglyTypedModels _publishedContent = publishedContent; } - public readonly Func Property = MethodBase.GetCurrentMethod; - public static string DefaultString = default(string); - public static int DefaultInteger = default(int); - public static bool DefaultBool = default(bool); - public static DateTime DefaultDateTime = default(DateTime); + protected readonly Func Property = MethodBase.GetCurrentMethod; + protected readonly Func ContentTypeAlias = MethodBase.GetCurrentMethod; - public T Resolve(MethodBase methodBase) + #region Properties + + protected T Resolve(MethodBase methodBase) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias); + } + + protected T Resolve(string propertyTypeAlias) + { return _publishedContent.GetPropertyValue(propertyTypeAlias); } - public T Resolve(MethodBase methodBase, T ifCannotConvert) + protected T Resolve(MethodBase methodBase, T ifCannotConvert) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias, ifCannotConvert); + } + + protected T Resolve(string propertyTypeAlias, T ifCannotConvert) + { return _publishedContent.GetPropertyValue(propertyTypeAlias, false, ifCannotConvert); } - public T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) + protected T Resolve(MethodBase methodBase, bool recursive, T ifCannotConvert) { var propertyTypeAlias = methodBase.ToUmbracoAlias(); + return Resolve(propertyTypeAlias, recursive, ifCannotConvert); + } + + protected T Resolve(string propertyTypeAlias, bool recursive, T ifCannotConvert) + { return _publishedContent.GetPropertyValue(propertyTypeAlias, recursive, ifCannotConvert); } + #endregion - public string ResolveString(MethodBase methodBase) + #region Querying + protected T Parent() where T : TypedModelBase { - return Resolve(methodBase); + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + return (T) constructorInfo.Invoke(new object[] {_publishedContent.Parent}); } - public int ResolveInt(MethodBase methodBase) + protected IEnumerable Children(MethodBase methodBase) where T : TypedModelBase { - return Resolve(methodBase); + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Children(docTypeAlias); } - public bool ResolveBool(MethodBase methodBase) + protected IEnumerable Children(string docTypeAlias) where T : TypedModelBase { - return Resolve(methodBase); + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if(constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Children.Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); } - public DateTime ResolveDate(MethodBase methodBase) + protected IEnumerable Ancestors(MethodBase methodBase) where T : TypedModelBase { - return Resolve(methodBase); + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Ancestors(docTypeAlias); } + + protected IEnumerable Ancestors(string docTypeAlias) where T : TypedModelBase + { + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Ancestors().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); + } + + protected IEnumerable Descendants(MethodBase methodBase) where T : TypedModelBase + { + var docTypeAlias = methodBase.CleanCallingMethodName(); + return Descendants(docTypeAlias); + } + + protected IEnumerable Descendants(string docTypeAlias) where T : TypedModelBase + { + var constructorInfo = typeof(T).GetConstructor(new[] { typeof(IPublishedContent) }); + if (constructorInfo == null) + throw new Exception("No valid constructor found"); + + string singularizedDocTypeAlias = docTypeAlias.ToSingular(); + + return _publishedContent.Descendants().Where(x => x.DocumentTypeAlias == singularizedDocTypeAlias) + .Select(x => (T)constructorInfo.Invoke(new object[] { x })); + } + #endregion + + #region IPublishedContent + int IPublishedContent.Id { get { return _publishedContent.Id; } } + int IPublishedContent.TemplateId { get { return _publishedContent.TemplateId; } } + int IPublishedContent.SortOrder { get { return _publishedContent.SortOrder; } } + string IPublishedContent.Name { get { return _publishedContent.Name; } } + string IPublishedContent.UrlName { get { return _publishedContent.UrlName; } } + string IPublishedContent.DocumentTypeAlias { get { return _publishedContent.DocumentTypeAlias; } } + int IPublishedContent.DocumentTypeId { get { return _publishedContent.DocumentTypeId; } } + string IPublishedContent.WriterName { get { return _publishedContent.WriterName; } } + string IPublishedContent.CreatorName { get { return _publishedContent.CreatorName; } } + int IPublishedContent.WriterId { get { return _publishedContent.WriterId; } } + int IPublishedContent.CreatorId { get { return _publishedContent.CreatorId; } } + string IPublishedContent.Path { get { return _publishedContent.Path; } } + DateTime IPublishedContent.CreateDate { get { return _publishedContent.CreateDate; } } + DateTime IPublishedContent.UpdateDate { get { return _publishedContent.UpdateDate; } } + Guid IPublishedContent.Version { get { return _publishedContent.Version; } } + int IPublishedContent.Level { get { return _publishedContent.Level; } } + string IPublishedContent.Url { get { return _publishedContent.Url; } } + PublishedItemType IPublishedContent.ItemType { get { return _publishedContent.ItemType; } } + IPublishedContent IPublishedContent.Parent { get { return _publishedContent.Parent; } } + IEnumerable IPublishedContent.Children { get { return _publishedContent.Children; } } + + ICollection IPublishedContent.Properties { get { return _publishedContent.Properties; } } + + object IPublishedContent.this[string propertyAlias] { get { return _publishedContent[propertyAlias]; } } + + IPublishedContentProperty IPublishedContent.GetProperty(string alias) + { + return _publishedContent.GetProperty(alias); + } + #endregion } + /// + /// Extension methods for MethodBase, which are used to clean the name of the calling method "get_BodyText" + /// to "BodyText" and then make it camel case according to the UmbracoAlias convention "bodyText". + /// There is also a string extension for making plural words singular, which is used when going from + /// something like "Subpages" to "Subpage" for Children/Ancestors/Descendants Doc Type aliases. + /// public static class TypeExtensions { + public static string CleanCallingMethodName(this MethodBase methodBase) + { + return methodBase.Name.Replace("get_", ""); + } + public static string ToUmbracoAlias(this MethodBase methodBase) { - return methodBase.Name.Replace("get_", "").ToUmbracoAlias(); + return methodBase.CleanCallingMethodName().ToUmbracoAlias(); + } + + public static string ToSingular(this string pluralWord) + { + var service = PluralizationService.CreateService(new CultureInfo("en-US")); + if (service.IsPlural(pluralWord)) + return service.Singularize(pluralWord); + + return pluralWord; } } } \ No newline at end of file diff --git a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs index 6e27886b83..ae0a3c062e 100644 --- a/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs +++ b/src/Umbraco.Tests/PublishedContent/StronglyTypedModels/UmbracoTemplatePage`T.cs @@ -3,6 +3,12 @@ using Umbraco.Web.Mvc; namespace Umbraco.Tests.PublishedContent.StronglyTypedModels { + /// + /// Represents a basic extension of the UmbracoTemplatePage, which allows you to specify + /// the type of a strongly typed model that inherits from TypedModelBase. + /// The model is exposed as TypedModel. + /// + /// Type of the model to create/expose public abstract class UmbracoTemplatePage : UmbracoTemplatePage where T : TypedModelBase { protected override void InitializePage() diff --git a/src/Umbraco.Tests/Umbraco.Tests.csproj b/src/Umbraco.Tests/Umbraco.Tests.csproj index 3fd67c5fba..66d91c0129 100644 --- a/src/Umbraco.Tests/Umbraco.Tests.csproj +++ b/src/Umbraco.Tests/Umbraco.Tests.csproj @@ -93,6 +93,7 @@ + True ..\packages\SqlServerCE.4.0.0.0\lib\System.Data.SqlServerCe.dll @@ -255,7 +256,8 @@ - + + diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/_module.js b/src/Umbraco.Web.UI.Client/src/common/directives/_module.js index e94b7f1c48..4d6d0ecfe6 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/_module.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/_module.js @@ -1,2 +1,4 @@ -angular.module("umbraco.directives", ["umbraco.directives.editors"]); -angular.module("umbraco.directives.editors", []); \ No newline at end of file +angular.module("umbraco.directives", ["umbraco.directives.editors", "umbraco.directives.html", "umbraco.directives.validation"]); +angular.module("umbraco.directives.editors", []); +angular.module("umbraco.directives.html", []); +angular.module("umbraco.directives.validation", []); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/readme.md b/src/Umbraco.Web.UI.Client/src/common/directives/html/readme.md new file mode 100644 index 0000000000..b7d8a1056f --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/readme.md @@ -0,0 +1,2 @@ +The html directives does nothing but display html, with simple one-way binding +and is therefore simpler to use then alot of the strict property bound ones \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js new file mode 100644 index 0000000000..89adf18abf --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbcontrolgroup.directive.js @@ -0,0 +1,20 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbProperty +* @restrict E +**/ +angular.module("umbraco.directives.html") + .directive('umbControlGroup', function () { + return { + scope: { + label: "@", + description: "@", + hideLabel: "@", + alias: "@" + }, + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/directives/html/umb-control-group.html' + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpane.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpane.directive.js new file mode 100644 index 0000000000..93ce6f13a4 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpane.directive.js @@ -0,0 +1,14 @@ +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbProperty +* @restrict E +**/ +angular.module("umbraco.directives.html") + .directive('umbPane', function () { + return { + transclude: true, + restrict: 'E', + replace: true, + templateUrl: 'views/directives/html/umb-pane.html' + }; + }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/umbpanel.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js similarity index 66% rename from src/Umbraco.Web.UI.Client/src/common/directives/umbpanel.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js index 0dc74840c7..0b098fc44d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/umbpanel.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/html/umbpanel.directive.js @@ -1,14 +1,14 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:umbPanel -* @restrict E -**/ -angular.module("umbraco.directives") - .directive('umbPanel', function(){ - return { - restrict: 'E', - replace: true, - transclude: 'true', - templateUrl: 'views/directives/umb-panel.html' - }; +/** +* @ngdoc directive +* @name umbraco.directives.directive:umbPanel +* @restrict E +**/ +angular.module("umbraco.directives.html") + .directive('umbPanel', function(){ + return { + restrict: 'E', + replace: true, + transclude: 'true', + templateUrl: 'views/directives/html/umb-panel.html' + }; }); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js new file mode 100644 index 0000000000..a402065708 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valCustom.directive.js @@ -0,0 +1,123 @@ +/** + * General-purpose validator for ngModel. + * angular.js comes with several built-in validation mechanism for input fields (ngRequired, ngPattern etc.) but using + * an arbitrary validation function requires creation of a custom formatters and / or parsers. + * The ui-validate directive makes it easy to use any function(s) defined in scope as a validator function(s). + * A validator function will trigger validation on both model and input changes. + * + * @example + * @example + * @example + * @example + * + * @param val-custom {string|object literal} If strings is passed it should be a scope's function to be used as a validator. + * If an object literal is passed a key denotes a validation error key while a value should be a validator function. + * In both cases validator function should take a value to validate as its argument and should return true/false indicating a validation result. + */ + + /* + This code comes from the angular UI project, we had to change the directive name and module + but other then that its unmodified + */ +angular.module('umbraco.directives.validation') +.directive('valCustom', function () { + + return { + restrict: 'A', + require: 'ngModel', + link: function (scope, elm, attrs, ctrl) { + var validateFn, watch, validators = {}, + validateExpr = scope.$eval(attrs.valCustom); + + if (!validateExpr){ return;} + + if (angular.isString(validateExpr)) { + validateExpr = { validator: validateExpr }; + } + + angular.forEach(validateExpr, function (exprssn, key) { + validateFn = function (valueToValidate) { + var expression = scope.$eval(exprssn, { '$value' : valueToValidate }); + if (angular.isObject(expression) && angular.isFunction(expression.then)) { + // expression is a promise + expression.then(function(){ + ctrl.$setValidity(key, true); + }, function(){ + ctrl.$setValidity(key, false); + }); + return valueToValidate; + } else if (expression) { + // expression is true + ctrl.$setValidity(key, true); + return valueToValidate; + } else { + // expression is false + ctrl.$setValidity(key, false); + return undefined; + } + }; + validators[key] = validateFn; + ctrl.$formatters.push(validateFn); + ctrl.$parsers.push(validateFn); + }); + + function apply_watch(watch) + { + //string - update all validators on expression change + if (angular.isString(watch)) + { + scope.$watch(watch, function(){ + angular.forEach(validators, function(validatorFn){ + validatorFn(ctrl.$modelValue); + }); + }); + return; + } + + //array - update all validators on change of any expression + if (angular.isArray(watch)) + { + angular.forEach(watch, function(expression){ + scope.$watch(expression, function() + { + angular.forEach(validators, function(validatorFn){ + validatorFn(ctrl.$modelValue); + }); + }); + }); + return; + } + + //object - update appropriate validator + if (angular.isObject(watch)) + { + angular.forEach(watch, function(expression, validatorKey) + { + //value is string - look after one expression + if (angular.isString(expression)) + { + scope.$watch(expression, function(){ + validators[validatorKey](ctrl.$modelValue); + }); + } + + //value is array - look after all expressions in array + if (angular.isArray(expression)) + { + angular.forEach(expression, function(intExpression) + { + scope.$watch(intExpression, function(){ + validators[validatorKey](ctrl.$modelValue); + }); + }); + } + }); + } + } + // Support for val-custom-watch + if (attrs.valCustomWatch){ + apply_watch( scope.$eval(attrs.valCustomWatch) ); + } + } + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valHighlight.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valHighlight.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js index b276c8c931..5d4b6cf9f9 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valHighlight.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valHighlight.directive.js @@ -1,30 +1,30 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:valHighlight -* @restrict A -* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second -**/ -function valHighlight($timeout) { - return { - restrict: "A", - link: function (scope, element, attrs, ctrl) { - - scope.$watch(function() { - return scope.$eval(attrs.valHighlight); - }, function(newVal, oldVal) { - if (newVal === true) { - element.addClass("highlight-error"); - $timeout(function () { - //set the bound scope property to false - scope[attrs.valHighlight] = false; - }, 1000); - } - else { - element.removeClass("highlight-error"); - } - }); - - } - }; -} +/** +* @ngdoc directive +* @name umbraco.directives.directive:valHighlight +* @restrict A +* @description Used on input fields when you want to signal that they are in error, this will highlight the item for 1 second +**/ +function valHighlight($timeout) { + return { + restrict: "A", + link: function (scope, element, attrs, ctrl) { + + scope.$watch(function() { + return scope.$eval(attrs.valHighlight); + }, function(newVal, oldVal) { + if (newVal === true) { + element.addClass("highlight-error"); + $timeout(function () { + //set the bound scope property to false + scope[attrs.valHighlight] = false; + }, 1000); + } + else { + element.removeClass("highlight-error"); + } + }); + + } + }; +} angular.module('umbraco.directives').directive("valHighlight", valHighlight); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js new file mode 100644 index 0000000000..5e7f042825 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valcompare.directive.js @@ -0,0 +1,22 @@ +angular.module('umbraco.directives.validation') + .directive('valCompare',function () { + return { + require: "ngModel", + link: function(scope, elem, attrs, ctrl) { + var otherInput = elem.inheritedData("$formController")[attrs.valCompare]; + + ctrl.$parsers.push(function(value) { + if(value === otherInput.$viewValue) { + ctrl.$setValidity("valCompare", true); + return value; + } + ctrl.$setValidity("valCompare", false); + }); + + otherInput.$parsers.push(function(value) { + ctrl.$setValidity("valCompare", value === ctrl.$viewValue); + return value; + }); + } + }; +}); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js similarity index 98% rename from src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js index 42a9214cb6..55eb2341a0 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valpropertymsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valpropertymsg.directive.js @@ -1,151 +1,151 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:valPropertyMsg -* @restrict A -* @element textarea -* @requires formController -* @description This directive is used to control the display of the property level validation message. -* We will listen for server side validation changes -* and when an error is detected for this property we'll show the error message -**/ -function valPropertyMsg(serverValidationManager) { - return { - scope: { - property: "=property" - }, - require: "^form", //require that this directive is contained within an ngForm - replace: true, //replace the element with the template - restrict: "E", //restrict to element - template: "
{{errorMsg}}
", - - /** - Our directive requries a reference to a form controller - which gets passed in to this parameter - */ - link: function (scope, element, attrs, formCtrl) { - - //assign the form control to our isolated scope so we can watch it's values - scope.formCtrl = formCtrl; - - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - var hasError = false; - - //create properties on our custom scope so we can use it in our template - scope.errorMsg = ""; - - //listen for error changes - scope.$watch("formCtrl.$error", function () { - if (formCtrl.$invalid) { - - //first we need to check if the valPropertyMsg validity is invalid - if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { - //since we already have an error we'll just return since this means we've already set the - // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe - return; - } - else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { - //check if it's one of the properties that is invalid in the current content property - hasError = true; - //update the validation message if we don't already have one assigned. - if (showValidation && scope.errorMsg === "") { - var err; - //this can be null if no property was assigned - if (scope.property) { - err = serverValidationManager.getPropertyError(scope.property.alias, ""); - } - scope.errorMsg = err ? err.errorMsg : "Property has errors"; - } - } - else { - hasError = false; - scope.errorMsg = ""; - } - } - else { - hasError = false; - scope.errorMsg = ""; - } - }, true); - - //listen for the forms saving event - scope.$on("saving", function (ev, args) { - showValidation = true; - if (hasError && scope.errorMsg === "") { - var err; - //this can be null if no property was assigned - if (scope.property) { - err = serverValidationManager.getPropertyError(scope.property.alias, ""); - } - scope.errorMsg = err ? err.errorMsg : "Property has errors"; - } - else if (!hasError) { - scope.errorMsg = ""; - } - }); - - //listen for the forms saved event - scope.$on("saved", function (ev, args) { - showValidation = false; - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - }); - - //We need to subscribe to any changes to our model (based on user input) - // This is required because when we have a server error we actually invalidate - // the form which means it cannot be resubmitted. - // So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - scope.$watch("property.value", function (newValue) { - //we are explicitly checking for valServer errors here, since we shouldn't auto clear - // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg - // is the only one, then we'll clear. - - var errCount = 0; - for (var e in scope.formCtrl.$error) { - errCount++; - } - - if ((errCount === 1 && scope.formCtrl.$error.valPropertyMsg !== undefined) || - (formCtrl.$invalid && scope.formCtrl.$error.valServer !== undefined)) { - scope.errorMsg = ""; - formCtrl.$setValidity('valPropertyMsg', true); - } - }, true); - - //listen for server validation changes - // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for - // validation changes to fields in the property this is because some server side validators may not - // return the field name for which the error belongs too, just the property for which it belongs. - // It's important to note that we need to subscribe to server validation changes here because we always must - // indicate that a content property is invalid at the property level since developers may not actually implement - // the correct field validation in their property editors. - - if (scope.property) { //this can be null if no property was assigned - serverValidationManager.subscribe(scope.property.alias, "", function(isValid, propertyErrors, allErrors) { - hasError = !isValid; - if (hasError) { - //set the error message to the server message - scope.errorMsg = propertyErrors[0].errorMsg; - //flag that the current validator is invalid - formCtrl.$setValidity('valPropertyMsg', false); - } - else { - scope.errorMsg = ""; - //flag that the current validator is valid - formCtrl.$setValidity('valPropertyMsg', true); - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function() { - serverValidationManager.unsubscribe(scope.property.alias, ""); - }); - } - } - }; -} +/** +* @ngdoc directive +* @name umbraco.directives.directive:valPropertyMsg +* @restrict A +* @element textarea +* @requires formController +* @description This directive is used to control the display of the property level validation message. +* We will listen for server side validation changes +* and when an error is detected for this property we'll show the error message +**/ +function valPropertyMsg(serverValidationManager) { + return { + scope: { + property: "=property" + }, + require: "^form", //require that this directive is contained within an ngForm + replace: true, //replace the element with the template + restrict: "E", //restrict to element + template: "
{{errorMsg}}
", + + /** + Our directive requries a reference to a form controller + which gets passed in to this parameter + */ + link: function (scope, element, attrs, formCtrl) { + + //assign the form control to our isolated scope so we can watch it's values + scope.formCtrl = formCtrl; + + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + var hasError = false; + + //create properties on our custom scope so we can use it in our template + scope.errorMsg = ""; + + //listen for error changes + scope.$watch("formCtrl.$error", function () { + if (formCtrl.$invalid) { + + //first we need to check if the valPropertyMsg validity is invalid + if (formCtrl.$error.valPropertyMsg && formCtrl.$error.valPropertyMsg.length > 0) { + //since we already have an error we'll just return since this means we've already set the + // hasError and errorMsg properties which occurs below in the serverValidationManager.subscribe + return; + } + else if (element.closest(".umb-control-group").find(".ng-invalid").length > 0) { + //check if it's one of the properties that is invalid in the current content property + hasError = true; + //update the validation message if we don't already have one assigned. + if (showValidation && scope.errorMsg === "") { + var err; + //this can be null if no property was assigned + if (scope.property) { + err = serverValidationManager.getPropertyError(scope.property.alias, ""); + } + scope.errorMsg = err ? err.errorMsg : "Property has errors"; + } + } + else { + hasError = false; + scope.errorMsg = ""; + } + } + else { + hasError = false; + scope.errorMsg = ""; + } + }, true); + + //listen for the forms saving event + scope.$on("saving", function (ev, args) { + showValidation = true; + if (hasError && scope.errorMsg === "") { + var err; + //this can be null if no property was assigned + if (scope.property) { + err = serverValidationManager.getPropertyError(scope.property.alias, ""); + } + scope.errorMsg = err ? err.errorMsg : "Property has errors"; + } + else if (!hasError) { + scope.errorMsg = ""; + } + }); + + //listen for the forms saved event + scope.$on("saved", function (ev, args) { + showValidation = false; + scope.errorMsg = ""; + formCtrl.$setValidity('valPropertyMsg', true); + }); + + //We need to subscribe to any changes to our model (based on user input) + // This is required because when we have a server error we actually invalidate + // the form which means it cannot be resubmitted. + // So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + scope.$watch("property.value", function (newValue) { + //we are explicitly checking for valServer errors here, since we shouldn't auto clear + // based on other errors. We'll also check if there's no other validation errors apart from valPropertyMsg, if valPropertyMsg + // is the only one, then we'll clear. + + var errCount = 0; + for (var e in scope.formCtrl.$error) { + errCount++; + } + + if ((errCount === 1 && scope.formCtrl.$error.valPropertyMsg !== undefined) || + (formCtrl.$invalid && scope.formCtrl.$error.valServer !== undefined)) { + scope.errorMsg = ""; + formCtrl.$setValidity('valPropertyMsg', true); + } + }, true); + + //listen for server validation changes + // NOTE: we pass in "" in order to listen for all validation changes to the content property, not for + // validation changes to fields in the property this is because some server side validators may not + // return the field name for which the error belongs too, just the property for which it belongs. + // It's important to note that we need to subscribe to server validation changes here because we always must + // indicate that a content property is invalid at the property level since developers may not actually implement + // the correct field validation in their property editors. + + if (scope.property) { //this can be null if no property was assigned + serverValidationManager.subscribe(scope.property.alias, "", function(isValid, propertyErrors, allErrors) { + hasError = !isValid; + if (hasError) { + //set the error message to the server message + scope.errorMsg = propertyErrors[0].errorMsg; + //flag that the current validator is invalid + formCtrl.$setValidity('valPropertyMsg', false); + } + else { + scope.errorMsg = ""; + //flag that the current validator is valid + formCtrl.$setValidity('valPropertyMsg', true); + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function() { + serverValidationManager.unsubscribe(scope.property.alias, ""); + }); + } + } + }; +} angular.module('umbraco.directives').directive("valPropertyMsg", valPropertyMsg); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js index 3edb230d0d..bf37d73392 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valregex.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valregex.directive.js @@ -1,45 +1,45 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valRegex - * @restrict A - * @description A custom directive to allow for matching a value against a regex string. - * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string - **/ -function valRegex() { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, elm, attrs, ctrl) { - - var regex; - try { - regex = new RegExp(scope.$eval(attrs.valRegex)); - } - catch(e) { - regex = new RegExp(attrs.valRegex); - } - - var patternValidator = function (viewValue) { - //NOTE: we don't validate on empty values, use required validator for that - if (!viewValue || regex.test(viewValue)) { - // it is valid - ctrl.$setValidity('valRegex', true); - //assign a message to the validator - ctrl.errorMsg = ""; - return viewValue; - } - else { - // it is invalid, return undefined (no model update) - ctrl.$setValidity('valRegex', false); - //assign a message to the validator - ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; - return undefined; - } - }; - - ctrl.$formatters.push(patternValidator); - ctrl.$parsers.push(patternValidator); - } - }; -} +/** + * @ngdoc directive + * @name umbraco.directives.directive:valRegex + * @restrict A + * @description A custom directive to allow for matching a value against a regex string. + * NOTE: there's already an ng-pattern but this requires that a regex expression is set, not a regex string + **/ +function valRegex() { + return { + require: 'ngModel', + restrict: "A", + link: function (scope, elm, attrs, ctrl) { + + var regex; + try { + regex = new RegExp(scope.$eval(attrs.valRegex)); + } + catch(e) { + regex = new RegExp(attrs.valRegex); + } + + var patternValidator = function (viewValue) { + //NOTE: we don't validate on empty values, use required validator for that + if (!viewValue || regex.test(viewValue)) { + // it is valid + ctrl.$setValidity('valRegex', true); + //assign a message to the validator + ctrl.errorMsg = ""; + return viewValue; + } + else { + // it is invalid, return undefined (no model update) + ctrl.$setValidity('valRegex', false); + //assign a message to the validator + ctrl.errorMsg = "Value is invalid, it does not match the correct pattern"; + return undefined; + } + }; + + ctrl.$formatters.push(patternValidator); + ctrl.$parsers.push(patternValidator); + } + }; +} angular.module('umbraco.directives').directive("valRegex", valRegex); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valserver.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valserver.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js index 3a4b9df09b..db03549d6d 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valserver.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserver.directive.js @@ -1,63 +1,63 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valServer - * @restrict A - * @description This directive is used to associate a content property with a server-side validation response - * so that the validators in angular are updated based on server-side feedback. - **/ -function valServer(serverValidationManager) { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, element, attr, ctrl) { - - if (!scope.model || !scope.model.alias){ - throw "valServer can only be used in the scope of a content property object"; - } - var currentProperty = scope.model; - - //default to 'value' if nothing is set - var fieldName = "value"; - if (attr.valServer) { - fieldName = scope.$eval(attr.valServer); - if (!fieldName) { - //eval returned nothing so just use the string - fieldName = attr.valServer; - } - } - - //subscribe to the changed event of the view model. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - ctrl.$viewChangeListeners.push(function () { - if (ctrl.$invalid) { - ctrl.$setValidity('valServer', true); - } - }); - - //subscribe to the server validation changes - serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { - if (!isValid) { - ctrl.$setValidity('valServer', false); - //assign an error msg property to the current validator - ctrl.errorMsg = propertyErrors[0].errorMsg; - } - else { - ctrl.$setValidity('valServer', true); - //reset the error message - ctrl.errorMsg = ""; - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - serverValidationManager.unsubscribe(currentProperty.alias, fieldName); - }); - } - }; -} +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServer + * @restrict A + * @description This directive is used to associate a content property with a server-side validation response + * so that the validators in angular are updated based on server-side feedback. + **/ +function valServer(serverValidationManager) { + return { + require: 'ngModel', + restrict: "A", + link: function (scope, element, attr, ctrl) { + + if (!scope.model || !scope.model.alias){ + throw "valServer can only be used in the scope of a content property object"; + } + var currentProperty = scope.model; + + //default to 'value' if nothing is set + var fieldName = "value"; + if (attr.valServer) { + fieldName = scope.$eval(attr.valServer); + if (!fieldName) { + //eval returned nothing so just use the string + fieldName = attr.valServer; + } + } + + //subscribe to the changed event of the view model. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + ctrl.$viewChangeListeners.push(function () { + if (ctrl.$invalid) { + ctrl.$setValidity('valServer', true); + } + }); + + //subscribe to the server validation changes + serverValidationManager.subscribe(currentProperty.alias, fieldName, function (isValid, propertyErrors, allErrors) { + if (!isValid) { + ctrl.$setValidity('valServer', false); + //assign an error msg property to the current validator + ctrl.errorMsg = propertyErrors[0].errorMsg; + } + else { + ctrl.$setValidity('valServer', true); + //reset the error message + ctrl.errorMsg = ""; + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + serverValidationManager.unsubscribe(currentProperty.alias, fieldName); + }); + } + }; +} angular.module('umbraco.directives').directive("valServer", valServer); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valserverfield.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valserverfield.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js index da79253c2f..3e853f800a 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valserverfield.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valserverfield.directive.js @@ -1,54 +1,54 @@ -/** - * @ngdoc directive - * @name umbraco.directives.directive:valServerField - * @restrict A - * @description This directive is used to associate a content field (not user defined) with a server-side validation response - * so that the validators in angular are updated based on server-side feedback. - **/ -function valServerField(serverValidationManager) { - return { - require: 'ngModel', - restrict: "A", - link: function (scope, element, attr, ctrl) { - - if (!attr.valServerField) { - throw "valServerField must have a field name for referencing server errors"; - } - - var fieldName = attr.valServerField; - - //subscribe to the changed event of the view model. This is required because when we - // have a server error we actually invalidate the form which means it cannot be - // resubmitted. So once a field is changed that has a server error assigned to it - // we need to re-validate it for the server side validator so the user can resubmit - // the form. Of course normal client-side validators will continue to execute. - ctrl.$viewChangeListeners.push(function () { - if (ctrl.$invalid) { - ctrl.$setValidity('valServerField', true); - } - }); - - //subscribe to the server validation changes - serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { - if (!isValid) { - ctrl.$setValidity('valServerField', false); - //assign an error msg property to the current validator - ctrl.errorMsg = fieldErrors[0].errorMsg; - } - else { - ctrl.$setValidity('valServerField', true); - //reset the error message - ctrl.errorMsg = ""; - } - }); - - //when the element is disposed we need to unsubscribe! - // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain - // but they are a different callback instance than the above. - element.bind('$destroy', function () { - serverValidationManager.unsubscribe(null, fieldName); - }); - } - }; -} +/** + * @ngdoc directive + * @name umbraco.directives.directive:valServerField + * @restrict A + * @description This directive is used to associate a content field (not user defined) with a server-side validation response + * so that the validators in angular are updated based on server-side feedback. + **/ +function valServerField(serverValidationManager) { + return { + require: 'ngModel', + restrict: "A", + link: function (scope, element, attr, ctrl) { + + if (!attr.valServerField) { + throw "valServerField must have a field name for referencing server errors"; + } + + var fieldName = attr.valServerField; + + //subscribe to the changed event of the view model. This is required because when we + // have a server error we actually invalidate the form which means it cannot be + // resubmitted. So once a field is changed that has a server error assigned to it + // we need to re-validate it for the server side validator so the user can resubmit + // the form. Of course normal client-side validators will continue to execute. + ctrl.$viewChangeListeners.push(function () { + if (ctrl.$invalid) { + ctrl.$setValidity('valServerField', true); + } + }); + + //subscribe to the server validation changes + serverValidationManager.subscribe(null, fieldName, function (isValid, fieldErrors, allErrors) { + if (!isValid) { + ctrl.$setValidity('valServerField', false); + //assign an error msg property to the current validator + ctrl.errorMsg = fieldErrors[0].errorMsg; + } + else { + ctrl.$setValidity('valServerField', true); + //reset the error message + ctrl.errorMsg = ""; + } + }); + + //when the element is disposed we need to unsubscribe! + // NOTE: this is very important otherwise when this controller re-binds the previous subscriptsion will remain + // but they are a different callback instance than the above. + element.bind('$destroy', function () { + serverValidationManager.unsubscribe(null, fieldName); + }); + } + }; +} angular.module('umbraco.directives').directive("valServerField", valServerField); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valshowvalidation.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valshowvalidation.directive.js index 85a4748a85..60d0c1c104 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valshowvalidation.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valshowvalidation.directive.js @@ -1,32 +1,32 @@ -/** -* @ngdoc directive -* @name umbraco.directives.directive:valShowValidation -* @restrict A -* @description Used to toggle the show-validation class on the element containing the form elements to validate. -* This is used because we don't want to show validation messages until after the form is submitted and then reset -* the process when the form is successful. We do this by listening to the current controller's saving and saved events. -**/ -function valShowValidation(serverValidationManager) { - return { - restrict: "A", - link: function (scope, element, attr, ctrl) { - - //we should show validation if there are any msgs in the server validation collection - if (serverValidationManager.items.length > 0) { - element.addClass("show-validation"); - } - - //listen for the forms saving event - scope.$on("saving", function (ev, args) { - element.addClass("show-validation"); - }); - - //listen for the forms saved event - scope.$on("saved", function (ev, args) { - element.removeClass("show-validation"); - }); - - } - }; -} +/** +* @ngdoc directive +* @name umbraco.directives.directive:valShowValidation +* @restrict A +* @description Used to toggle the show-validation class on the element containing the form elements to validate. +* This is used because we don't want to show validation messages until after the form is submitted and then reset +* the process when the form is successful. We do this by listening to the current controller's saving and saved events. +**/ +function valShowValidation(serverValidationManager) { + return { + restrict: "A", + link: function (scope, element, attr, ctrl) { + + //we should show validation if there are any msgs in the server validation collection + if (serverValidationManager.items.length > 0) { + element.addClass("show-validation"); + } + + //listen for the forms saving event + scope.$on("saving", function (ev, args) { + element.addClass("show-validation"); + }); + + //listen for the forms saved event + scope.$on("saved", function (ev, args) { + element.removeClass("show-validation"); + }); + + } + }; +} angular.module('umbraco.directives').directive("valShowValidation", valShowValidation); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js index 1df0c3459d..463a47397f 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valtab.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtab.directive.js @@ -1,40 +1,40 @@ - -/** -* @ngdoc directive -* @name umbraco.directives.directive:valTab -* @restrict A -* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data. -**/ -function valTab() { - return { - require: "^form", - restrict: "A", - link: function (scope, element, attr, formCtrl) { - - var tabId = "tab" + scope.tab.id; - - //assign the form control to our isolated scope so we can watch it's values - scope.formCtrl = formCtrl; - scope.tabHasError = false; - - //watch the current form's validation for the current field name - scope.$watch("formCtrl.$valid", function () { - var tabContent = element.closest(".umb-panel").find("#" + tabId); - - if (formCtrl.$invalid) { - //check if the validation messages are contained inside of this tabs - if (tabContent.find(".ng-invalid").length > 0) { - scope.tabHasError = true; - } - else { - scope.tabHasError = false; - } - } - else { - scope.tabHasError = false; - } - }); - } - }; -} + +/** +* @ngdoc directive +* @name umbraco.directives.directive:valTab +* @restrict A +* @description Used to show validation warnings for a tab to indicate that the tab content has validations errors in its data. +**/ +function valTab() { + return { + require: "^form", + restrict: "A", + link: function (scope, element, attr, formCtrl) { + + var tabId = "tab" + scope.tab.id; + + //assign the form control to our isolated scope so we can watch it's values + scope.formCtrl = formCtrl; + scope.tabHasError = false; + + //watch the current form's validation for the current field name + scope.$watch("formCtrl.$valid", function () { + var tabContent = element.closest(".umb-panel").find("#" + tabId); + + if (formCtrl.$invalid) { + //check if the validation messages are contained inside of this tabs + if (tabContent.find(".ng-invalid").length > 0) { + scope.tabHasError = true; + } + else { + scope.tabHasError = false; + } + } + else { + scope.tabHasError = false; + } + }); + } + }; +} angular.module('umbraco.directives').directive("valTab", valTab); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js similarity index 97% rename from src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js rename to src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js index 273e153b37..65f441b061 100644 --- a/src/Umbraco.Web.UI.Client/src/common/directives/valtogglemsg.directive.js +++ b/src/Umbraco.Web.UI.Client/src/common/directives/validation/valtogglemsg.directive.js @@ -1,64 +1,64 @@ -function valToggleMsg(serverValidationManager) { - return { - require: "^form", - restrict: "A", - - /** - Our directive requries a reference to a form controller which gets passed in to this parameter - */ - link: function (scope, element, attr, formCtrl) { - - if (!attr.valToggleMsg){ - throw "valToggleMsg requires that a reference to a validator is specified"; - } - if (!attr.valMsgFor){ - throw "valToggleMsg requires that the attribute valMsgFor exists on the element"; - } - if (!formCtrl[attr.valMsgFor]) { - throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name; - } - - //assign the form control to our isolated scope so we can watch it's values - scope.formCtrl = formCtrl; - - //if there's any remaining errors in the server validation service then we should show them. - var showValidation = serverValidationManager.items.length > 0; - - //add a watch to the validator for the value (i.e. myForm.value.$error.required ) - scope.$watch("formCtrl." + attr.valMsgFor + ".$error." + attr.valToggleMsg, function () { - if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { - element.show(); - } - else { - element.hide(); - } - }); - - scope.$on("saving", function(ev, args) { - showValidation = true; - if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { - element.show(); - } - else { - element.hide(); - } - }); - - scope.$on("saved", function (ev, args) { - showValidation = false; - element.hide(); - }); - - } - }; -} - -/** -* @ngdoc directive -* @name umbraco.directives.directive:valToggleMsg -* @restrict A -* @element input -* @requires formController -* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ? -**/ +function valToggleMsg(serverValidationManager) { + return { + require: "^form", + restrict: "A", + + /** + Our directive requries a reference to a form controller which gets passed in to this parameter + */ + link: function (scope, element, attr, formCtrl) { + + if (!attr.valToggleMsg){ + throw "valToggleMsg requires that a reference to a validator is specified"; + } + if (!attr.valMsgFor){ + throw "valToggleMsg requires that the attribute valMsgFor exists on the element"; + } + if (!formCtrl[attr.valMsgFor]) { + throw "valToggleMsg cannot find field " + attr.valMsgFor + " on form " + formCtrl.$name; + } + + //assign the form control to our isolated scope so we can watch it's values + scope.formCtrl = formCtrl; + + //if there's any remaining errors in the server validation service then we should show them. + var showValidation = serverValidationManager.items.length > 0; + + //add a watch to the validator for the value (i.e. myForm.value.$error.required ) + scope.$watch("formCtrl." + attr.valMsgFor + ".$error." + attr.valToggleMsg, function () { + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg] && showValidation) { + element.show(); + } + else { + element.hide(); + } + }); + + scope.$on("saving", function(ev, args) { + showValidation = true; + if (formCtrl[attr.valMsgFor].$error[attr.valToggleMsg]) { + element.show(); + } + else { + element.hide(); + } + }); + + scope.$on("saved", function (ev, args) { + showValidation = false; + element.hide(); + }); + + } + }; +} + +/** +* @ngdoc directive +* @name umbraco.directives.directive:valToggleMsg +* @restrict A +* @element input +* @requires formController +* @description This directive will show/hide an error based on: is the value + the given validator invalid? AND, has the form been submitted ? +**/ angular.module('umbraco.directives').directive("valToggleMsg", valToggleMsg); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js index 5e23495765..30b039e622 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/entity.resource.js @@ -263,6 +263,38 @@ function entityResource($q, $http, umbRequestHelper) { 'Failed to retreive document data for ids ' + idQuery); }, + /** + * @ngdoc method + * @name umbraco.resources.entityResource#searchDocuments + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of content entities, given a query + * + * ##usage + *
+         * entityResource.searchDocuments("news")
+         *    .then(function(contentArray) {
+         *        var myDoc = contentArray; 
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {String} Query search query + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + searchDocuments: function (query) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchDocuments", + query)), + 'Failed to retreive document data for query ' + query); + }, + /** * @ngdoc method * @name umbraco.resources.entityResource#getMediaById @@ -329,8 +361,40 @@ function entityResource($q, $http, umbRequestHelper) { "GetMediaByIds", idQuery)), 'Failed to retreive media data for ids ' + idQuery); + }, + + /** + * @ngdoc method + * @name umbraco.resources.entityResource#searchMedia + * @methodOf umbraco.resources.entityResource + * + * @description + * Gets an array of medoa entities, given a query + * + * ##usage + *
+         * entityResource.searchMedia("news")
+         *    .then(function(mediaArray) {
+         *        var myDoc = mediaArray; 
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @param {String} Query search query + * @returns {Promise} resourcePromise object containing the entity array. + * + */ + searchMedia: function (query) { + + return umbRequestHelper.resourcePromise( + $http.get( + umbRequestHelper.getApiUrl( + "entityApiBaseUrl", + "SearchMedia", + query)), + 'Failed to retreive media data for query ' + query); } - + }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js b/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js index 5c7b379704..2134efc594 100644 --- a/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js +++ b/src/Umbraco.Web.UI.Client/src/common/resources/user.resource.js @@ -68,6 +68,36 @@ function userResource($q, $http, umbRequestHelper) { "userApiBaseUrl", "GetAll")), 'Failed to retreive all users'); + }, + + /** + * @ngdoc method + * @name umbraco.resources.userResource#changePassword + * @methodOf umbraco.resources.userResource + * + * @description + * Changes the current users password + * + * ##usage + *
+         * contentResource.getAll()
+         *    .then(function(userArray) {
+         *        var myUsers = userArray; 
+         *        alert('they are here!');
+         *    });
+         * 
+ * + * @returns {Promise} resourcePromise object containing the user array. + * + */ + changePassword: function (oldPassword, newPassword) { + return umbRequestHelper.resourcePromise( + $http.post( + umbRequestHelper.getApiUrl( + "userApiBaseUrl", + "PostChangePassword"), + { oldPassword: oldPassword, newPassword: newPassword }), + 'Failed to change password'); } }; } diff --git a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js index 40407d69db..21dea1da17 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/dialog.service.js @@ -101,13 +101,15 @@ angular.module('umbraco.services') }; scope.close = function(data) { - if (dialog.closeCallback) { + if (dialog.closeCallback) { dialog.closeCallback(data); - } + } - dialog.element.modal('hide'); - dialog.element.remove(); - $("#" + dialog.element.attr("id")).remove(); + if(dialog.element){ + dialog.element.modal('hide'); + dialog.element.remove(); + $("#" + dialog.element.attr("id")).remove(); + } }; //if iframe is enabled, inject that instead of a template @@ -260,8 +262,10 @@ angular.module('umbraco.services') * @param {Object} args if specified this object will be sent to any callbacks registered on the dialogs. */ close: function (dialog, args) { - dialog.scope.close(); - + if(dialog && dialog.scope){ + dialog.scope.close(); + } + //removeDialog(dialog, args); }, @@ -323,7 +327,7 @@ angular.module('umbraco.services') * * @description * Opens a mcaro picker in a modal, the callback returns a object representing the macro and it's parameters - * @param {Object} options mediapicker dialog options object + * @param {Object} options macropicker dialog options object * @param {$scope} options.scope dialog scope * @param {Function} options.callback callback function * @returns {Object} modal object @@ -334,6 +338,44 @@ angular.module('umbraco.services') return openDialog(options); }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#iconPicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a icon picker in a modal, the callback returns a object representing the selected icon + * @param {Object} options iconpicker dialog options object + * @param {$scope} options.scope dialog scope + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + iconPicker: function (options) { + options.template = 'views/common/dialogs/iconPicker.html'; + options.show = true; + return openDialog(options); + }, + + /** + * @ngdoc method + * @name umbraco.services.dialogService#treePicker + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a tree picker in a modal, the callback returns a object representing the selected tree item + * @param {Object} options iconpicker dialog options object + * @param {$scope} options.scope dialog scope + * @param {$scope} options.section tree section to display + * @param {$scope} options.multiPicker should the tree pick one or multiple items before returning + * @param {Function} options.callback callback function + * @returns {Object} modal object + */ + treePicker: function (options) { + options.template = 'views/common/dialogs/treePicker.html'; + options.show = true; + return openDialog(options); + }, + /** * @ngdoc method * @name umbraco.services.dialogService#propertyDialog @@ -354,6 +396,19 @@ angular.module('umbraco.services') return openDialog(options); }, + /** + * @ngdoc method + * @name umbraco.services.dialogService#ysodDialog + * @methodOf umbraco.services.dialogService + * + * @description + * Opens a dialog to an embed dialog + */ + embedDialog: function (options) { + options.template = 'views/common/dialogs/rteembed.html'; + options.show = true; + return openDialog(options); + }, /** * @ngdoc method * @name umbraco.services.dialogService#ysodDialog diff --git a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js index 37d396b667..0918a1b71c 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/navigation.service.js @@ -114,7 +114,7 @@ angular.module('umbraco.services') * @param {string} sectionAlias The alias of the section the tree should load data from */ showTree: function (sectionAlias) { - if (!this.ui.stickyNavigation && sectionAlias !== this.ui.currentSection) { + if (sectionAlias !== this.ui.currentSection) { this.ui.currentSection = sectionAlias; setMode("tree"); } @@ -346,7 +346,7 @@ angular.module('umbraco.services') var dialog = dialogService.open( { - container: $("#dialog div.umb-panel-body"), + container: $("#dialog div.umb-modalcolumn-body"), scope: scope, inline: true, show: true, diff --git a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js index 6b5a4cd7b5..54a5ae1999 100644 --- a/src/Umbraco.Web.UI.Client/src/common/services/user.service.js +++ b/src/Umbraco.Web.UI.Client/src/common/services/user.service.js @@ -72,7 +72,6 @@ angular.module('umbraco.services') }, logout: function () { - return authResource.performLogout() .then(function (data) { currentUser = null; diff --git a/src/Umbraco.Web.UI.Client/src/less/forms.less b/src/Umbraco.Web.UI.Client/src/less/forms.less index e7ae20e154..fb999c74a0 100644 --- a/src/Umbraco.Web.UI.Client/src/less/forms.less +++ b/src/Umbraco.Web.UI.Client/src/less/forms.less @@ -11,6 +11,19 @@ margin-left: 0px !Important; } +small.umb-detail, +label small { + color: #b3b3b3 !important; + text-decoration: none; + display: block; + font-weight: normal +} +label.control-label { + padding-top: 8px !important; +} + +.controls-row label{padding: 0px 10px 0px 10px; vertical-align: center} + //utill styll to hide child untill hover .hover-show{visibility: hidden;} *:hover > .hover-show{visibility: visible;} @@ -436,10 +449,13 @@ input[type="checkbox"][readonly] { } } - +//non-html5 states, pp: added default ng-invalid class input.highlight-error, select.highlight-error, -textarea.highlight-error { +textarea.highlight-error, +input.ng-dirty.ng-invalid, +select.ng-dirty.ng-invalid, +textarea.ng-dirty.ng-invalid{ border-color: #953b39 !important; -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392; diff --git a/src/Umbraco.Web.UI.Client/src/less/grid.less b/src/Umbraco.Web.UI.Client/src/less/grid.less index 8c6fc0636d..31d48f0b1b 100644 --- a/src/Umbraco.Web.UI.Client/src/less/grid.less +++ b/src/Umbraco.Web.UI.Client/src/less/grid.less @@ -25,9 +25,6 @@ body { padding: 20px } -.fill { - height: 100% -} #layout { position: relative; @@ -42,19 +39,17 @@ body { margin: 0; } -#contentwrapper { - height: 100%; +#contentwrapper, #contentcolumn { + position: absolute; + top: 0px; bottom: 0px; right: 0px; left: 80px; z-index: 10; margin: 0 } #contentcolumn { - position: relative; - margin-left: 80px; - z-index: 10; - height: 100%;} + left: 0px; +} -.content-column-body{height: 100%;} #contentcolumn iframe { display: block; position: relative; @@ -121,20 +116,16 @@ body { #tree { padding: 0px; - position: relative; z-index: 100 !important; - height: 100%; overflow: auto; } #tree .umb-tree { - padding: 15px 0px 20px 0px; + padding: 0px 0px 20px 0px; } #search-results { - position: relative; - z-index: 100; - padding: 15px 0px 20px 0px; + z-index: 200; } #contextMenu { diff --git a/src/Umbraco.Web.UI.Client/src/less/main.less b/src/Umbraco.Web.UI.Client/src/less/main.less index 226dc40a06..222b4493aa 100644 --- a/src/Umbraco.Web.UI.Client/src/less/main.less +++ b/src/Umbraco.Web.UI.Client/src/less/main.less @@ -146,17 +146,7 @@ div.umb-codeeditor .umb-btn-toolbar { margin: auto } -small.umb-detail, -label small { - color: #b3b3b3 !important; - text-decoration: none; - display: block; - font-weight: normal -} -label { - padding-top: 8px !important; -} /* FORM GRID */ diff --git a/src/Umbraco.Web.UI.Client/src/less/modals.less b/src/Umbraco.Web.UI.Client/src/less/modals.less index a6ada828a3..faab7f1709 100644 --- a/src/Umbraco.Web.UI.Client/src/less/modals.less +++ b/src/Umbraco.Web.UI.Client/src/less/modals.less @@ -3,44 +3,54 @@ /* Modalcolumn is used for menu panels */ .umb-modalcolumn { - background: #fff; + background: white; border-left: #f6f6f6 1px solid; -} -.umb-modalcolumn .umb-panel-header { white-space: nowrap } -.umb-modalcolumn .umb-panel-body { - padding: 0px + +.umb-modalcolumn-header { + background: @grayLighter; + border-bottom: 1px solid @grayLight; + height: 79px; + padding: 20px 20px 0px 20px; } -.no-padding .umb-panel-body { + +.umb-modalcolumn-header h1{ + margin: 0; + font-size: @fontSizeLarge; + font-weight: 400; + padding-top: 10px !important; + text-transform: capitalize !important; +} + +.umb-modalcolumn-body { + padding: 0px; + background: white; + top: 101px; + position: absolute; + left: 0px; + right: 0px; + bottom: 0px; +} + +.no-padding .umb-modalcolumn-body { padding: 0px } -.umb-modalcolumn .umb-panel-header .btn { +.umb-modalcolumn .umb-modalcolumn-header .btn { position: absolute; top: 13px; right: 15px } -.umb-modalcolumn .umb-modal-close-icon { - position: absolute; - top: 17px; - left: 27px; -} -.umb-modalcolumn .umb-modal-close-icon i { - background: url(close.png); - width: 18px; - height: 18px -} .umb-modalcolumn iframe { border: none; padding: 0px; margin: 0px; } -.umb-modalcolumn h1 { - padding-top: 10px !important; - text-transform: capitalize !important; -} + + + /* umb.dialog is used for the dialogs on the conent tree*/ @@ -131,7 +141,7 @@ -.umb-modal .thumbnail { +.umb-thumbnails .thumbnail { padding: 0; border:none; } @@ -140,43 +150,46 @@ margin: 0 0 20px 0 }*/ -.umb-modal .thumbnails > li { +.umb-thumbnails > li { margin: 0 20px 20px 0; text-align: center; } -.umb-modal .thumbnail img { - /*height: 100px;*/ -} +.umb-thumbnails i{margin: auto;} -.umb-modal .thumbnails .selected img, .umb-modal .thumbnails img:hover { +.umb-thumbnails .selected img, .umb-thumbnails img:hover { opacity: 0.5 } -.umb-modal .thumbnails > li.folder { +.umb-thumbnails > li.folder { width: 100px; /*height: 100px;*/ display: block; - background: @grayLighter; text-align: center; - font-size: 12px + font-size: 12px; } -.umb-modal .thumbnails > li.folder .icon-folder{ - color: @grayLight; +.umb-thumbnails a{ + outline: none; + border:none !important; + box-shadow:none !important; +} + +.umb-thumbnails > li.folder .icon-folder{ display: block; font-size: 90px; height: 75px; width:100px; + color: @grayLight; } -.umb-modal .thumbnails > li.folder a { +.umb-thumbnails > li.folder a { width: 100px; height: 100px; display: block } -.umb-modal .thumbnails > li.folder a:hover { +.umb-thumbnails > li.folder a:hover { text-decoration: none } diff --git a/src/Umbraco.Web.UI.Client/src/less/panel.less b/src/Umbraco.Web.UI.Client/src/less/panel.less index 7d64cd3065..07799ba1cd 100644 --- a/src/Umbraco.Web.UI.Client/src/less/panel.less +++ b/src/Umbraco.Web.UI.Client/src/less/panel.less @@ -1,15 +1,30 @@ // Panel // ------------------------- -.umb-panel{background: white;} +.umb-panel{ + background: white; + position: absolute; + top: 0px; bottom: 0px; left: 0px; right: 0px;} + +.umb-panel-nobody{padding-top: 100px;} .umb-panel-header { - height: 79px; - padding: 20px 20px 0px 20px; background: @grayLighter; border-bottom: 1px solid @grayLight; - height: 79px; + position: absolute; + height: 99px; + top: 0px; + right: 0px; + left: 0px; } +.umb-panel-body { + top: 101px; + left: 0px; + right: 0px; + bottom: 20px; + position: relative; + clear: both; +} .umb-panel-header h1.umb-headline-editor { cursor: text; @@ -18,20 +33,31 @@ .umb-panel-header h1.umb-headline-editor { cursor: text; } +.umb-headline-editor-wrapper, +h1.headline{height: 40px; padding: 30px 0 0 20px;} -.umb-headline-editor-wrapper{height: 50px;} .umb-headline-editor-wrapper h1, h1.headline { margin: 0; font-size: @fontSizeLarge; font-weight: 400; + text-align: left; } +.umb-headline-editor-wrapper .help-inline{display: none !Important;} +.umb-headline-editor-wrapper.error h1{ + border-bottom: 1px dashed @red; color: @red; cursor: pointer; +}; + +.umb-panel-header .umb-nav-tabs{ + bottom: -1px; + position: absolute; + padding: 0px 0px 0px 20px; +} .umb-panel-header h1, { margin: 0; font-size: @fontSizeLarge; font-weight: 400; color: @gray; - padding: 4px 0px 4px 6px; line-height: 22px; width: 100%; } @@ -61,11 +87,10 @@ .umb-panel-header .umb-btn-toolbar { float: right; + padding: 20px 20px 0 0; } -.umb-panel-body { - clear: both; -} + .umb-panel-footer { margin: 0; diff --git a/src/Umbraco.Web.UI.Client/src/less/tree.less b/src/Umbraco.Web.UI.Client/src/less/tree.less index 11614962e4..3111c5a944 100644 --- a/src/Umbraco.Web.UI.Client/src/less/tree.less +++ b/src/Umbraco.Web.UI.Client/src/less/tree.less @@ -111,14 +111,15 @@ cursor: pointer } -.umb-tree ins:before { - font-size: 11px; +.umb-tree ins { + font-size: 12px; } .umb-tree .icon { vertical-align: middle; margin: 1px 13px 1px 0px; - color: #414141 + color: #414141; + font-size: 15px; } .umb-tree i.noSpr { display: inline-block; @@ -448,7 +449,7 @@ height:1px; } body.touch .umb-tree .icon{font-size: 17px;} -body.touch .umb-tree ins{font-size: 20px; visibility: visible; padding: 7px;} +body.touch .umb-tree ins{font-size: 14px; visibility: visible; padding: 7px;} body.touch .umb-tree li div { padding-top: 8px; padding-bottom: 8px; diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html index 1d30bdceb8..34bab065da 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dashboard.html @@ -2,7 +2,7 @@
-

{{dashboard.name}}

+

{{dashboard.name}}

diff --git a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html index 7b33b6b66e..2b61e549a0 100644 --- a/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html +++ b/src/Umbraco.Web.UI.Client/src/views/common/dialogs/iconpicker.html @@ -17,7 +17,7 @@
-
+
-
- -
+
Your profile
diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html b/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html new file mode 100644 index 0000000000..db41e8666a --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/ChangePassword.html @@ -0,0 +1,49 @@ +
+

Change password

+

Enter your current password, then repeat your new password to change it

+ + + + + + + Required + + + + Old password was not correct + + + + + + + Required + + + + + + + Required + + + + You must re-enter the new password + + + + + + + + +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js index 0d894946ba..bf5e20a152 100644 --- a/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js +++ b/src/Umbraco.Web.UI.Client/src/views/dashboard/dashboard.tabs.controller.js @@ -1,6 +1,4 @@ function startUpVideosDashboardController($scope, xmlhelper, $log, $http) { - - //xmlHelper.parseFeed("http://umbraco.org/feeds/videos/getting-started").then(function(feed){ //}); @@ -16,5 +14,22 @@ function startUpVideosDashboardController($scope, xmlhelper, $log, $http) { }); }); } +angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController); -angular.module("umbraco").controller("Umbraco.Dashboard.StartupVideosController", startUpVideosDashboardController); \ No newline at end of file +function ChangePasswordDashboardController($scope, xmlhelper, $log, userResource) { + //this is the model we will pass to the service + $scope.profile = {}; + + $scope.changePassword = function (p) { + userResource.changePassword(p.oldPassword, p.newPassword).then(function () { + alert("changed"); + $scope.passwordForm.$setValidity(true); + }, function () { + alert("not changed"); + //this only happens if there is a wrong oldPassword sent along + $scope.passwordForm.oldpass.$setValidity("oldPassword", false); + }); + } +} + +angular.module("umbraco").controller("Umbraco.Dashboard.StartupChangePasswordController", ChangePasswordDashboardController); \ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html index c8dda561cc..4e851e159e 100644 --- a/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html +++ b/src/Umbraco.Web.UI.Client/src/views/datatype/edit.html @@ -25,7 +25,7 @@
-
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-control-group.html b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-control-group.html new file mode 100644 index 0000000000..35c24d45c2 --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-control-group.html @@ -0,0 +1,13 @@ +
+
+
+ + +
+
+
+
+
diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-pane.html b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-pane.html new file mode 100644 index 0000000000..7c94769d2d --- /dev/null +++ b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-pane.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-panel.html b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-panel.html similarity index 94% rename from src/Umbraco.Web.UI.Client/src/views/directives/umb-panel.html rename to src/Umbraco.Web.UI.Client/src/views/directives/html/umb-panel.html index 62460be6d5..a963eff309 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-panel.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/html/umb-panel.html @@ -1,3 +1,3 @@ -
- -
+
+ +
diff --git a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html b/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html index dc16d0ed1f..1886a3f569 100644 --- a/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html +++ b/src/Umbraco.Web.UI.Client/src/views/directives/umb-contextmenu.html @@ -1,12 +1,12 @@ -
-
+

{{nav.ui.dialogTitle}}

-
+