I have the following DevExpress control in program (using WPF):
public class EditCustomListsModel : ViewModelBase
{
...
private string _bookIds;
public string BookIds
{
get => _bookIds;
set
{
_bookIds = value;
OnPropertyChanged(nameof(BookIds));
}
}
public EditCustomListsModel(IApplicationService appService, CustomList customerList = null)
{
this.appService = appService;
if (customerList != null)
{
_windowMode = WindowMode.Editing;
CustomList = CloneEntityHelper.CustomListClone(customerList);
BookIds = BookIdCollectionToString(); <--------- line related to element
}
else
{
_windowMode = WindowMode.Adding;
CustomList = new CustomList();
}
}
/// <summary>
/// Метод преобразования коллекции BookId в строку
/// </summary>
/// <returns></returns>
private string BookIdCollectionToString() =>
CustomList?.CustomListDetails?.Any() == true
? string.Join(Environment.NewLine, CustomList.CustomListDetails.Select(p => p.BookId))
: string.Empty;
}
How can I customize a data control so that the following regular expression is used when entering (editing) data:
^[1-9]\d{0,8}(?:\r\n[1-9]\d{0,8})*$
That is, only strings should be displayed in the control, each of which contains an integer and CR + LF character (s), the last (s) may not be.
If write like this:
<dxe:TextEdit Grid.Row="1" Grid.RowSpan="2" Grid.Column="0" TextWrapping="Wrap" AcceptsReturn="True"
VerticalContentAlignment="Top" VerticalScrollBarVisibility="Auto"
EditValue="{Binding BookIds, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
MaskShowPlaceHolders="False" Mask="[1-9]\d{0,8}(?:\r\n[1-9]\d{0,8})*" MaskType="RegEx" MaskAutoComplete="None"/>
That in the markup MaskType="RegEx" is displayed by a wavy line and the syntax error is written.
Related
I am using sitecore 8.1 with MVC and i was required to have a single-line text & Email fields in web forms for marketers with placeholder text.
I have successfully created a custom text field with placeholder but have a minor issue that it's not a shared field or multi-cultured supported.
I have followed #azadeh-khojandi reply for mvc here
I have a last solution to take a dictionary key in placeholder and from code get key's value which is not supposed to be a good idea.
Any hint or guide ?
Classes:
[ValidationProperty("Text")]
public class SingleLineText : Sitecore.Form.Web.UI.Controls.SingleLineText, IPlaceholderField
{
[VisualCategory("Custom Properties")]
[VisualProperty("Placeholder", 2)]
[DefaultValue("")]
public string PlaceHolder { get; set; }
protected override void OnInit(EventArgs e)
{
// Set placeholder text, if present
if (!string.IsNullOrEmpty(PlaceHolder))
{
textbox.Attributes["placeholder"] = Helper.GetDictionaryItem(PlaceHolder);
}
base.OnInit(e);
}
}
public class ExtendedSingleLineTextField : Sitecore.Forms.Mvc.ViewModels.Fields.SingleLineTextField, IPlaceholderField
{
[VisualCategory("Custom Properties")]
[VisualProperty("Placeholder", 2)]
[DefaultValue("")]
public string PlaceHolder { get; set; }
}
public interface IPlaceholderField
{
string PlaceHolder { get; set; }
}
public static class BootstrapEditorHtmlHelperExtension
{
public static MvcHtmlString ExtendedBootstrapEditor(this HtmlHelper helper, string expression, string placeholderText, string inlineStyle, string[] classes)
{
var str = string.Empty;
var viewModel = helper.ViewData.Model as IViewModel;
if (viewModel != null)
{
var styleSettings = viewModel as IStyleSettings;
if (styleSettings != null)
{
str = styleSettings.CssClass;
}
if (string.IsNullOrEmpty(placeholderText))
{
placeholderText = viewModel.Title;
}
}
return helper.Editor(expression, new
{
htmlAttributes = new
{
#class = (string.Join(" ", classes) + " form-control" + (string.IsNullOrEmpty(str) ? string.Empty : " " + str) + (helper.ViewData.Model is SingleLineTextField ? " dangerousSymbolsCheck" : string.Empty)),
placeholder = placeholderText,
style = (inlineStyle ?? string.Empty)
}
});
}
}
View for Custom Field:
#using (Html.BeginField())
{
#Html.ExtendedBootstrapEditor("value", Model.PlaceHolder, "", new[] { "" })
}
I have resolve this issue by adding [Localize] attribute for PLaceholder in "ExtendedSingleLineTextField" and "SingleLineText" class.
This was defined here Sitecore.Form.Core.Attributes.LocalizeAttribute and also for further customization reference.
Page 27:
https://sdn.sitecore.net/upload/sdn5/products/web_forms2/web_forms_for_marketers_v2_reference_usletter.pdf
I've created my own model property, which says "ErrorMessage could not be found":
[DataType(DataType.EmailAddress)]
[Required]
[Display(Name = "Email")]
[StringLength(80, ErrorMessage = "Email too large.")]
[RegularExpressionTimeOut(#"^([a-zA-Z0-9._-]+)#(outlook|hotmail|yahoo)\.\w{2,}$", 5),
ErrorMessage = "Invalid email."] // 5 seconds
public string Email { get; set; }
And the class of this property:
public class RegularExpressionTimeOut :
System.ComponentModel.DataAnnotations.ValidationAttribute
{
public System.TimeSpan TimeOut { get; set; }
public string Pattern { get; set; }
//public string ErrorMessage { get; set; }
//new System.TimeSpan(0, 0, 5)
/// <summary>
/// Constructor
/// </summary>
/// <param name="pattern">Regular expression as string</param>
/// <param name="timeOut">Number of seconds</param>
public RegularExpressionTimeOut(string pattern, int timeOut)
{
this.Pattern = pattern;
this.TimeOut = new System.TimeSpan(0, 0, timeOut);
}
public override bool IsValid(object value)
{
//if (!string.IsNullOrEmpty(this.ErrorMessage) && ...)
return (new System.Text.RegularExpressions.Regex(this.Pattern,
System.Text.RegularExpressions.RegexOptions.None, this.TimeOut))
.IsMatch(this.Pattern);
}
}
Then, the question is, how can I show the ErrorMessage?
Updated with the FormatErrorMessage
My web don't show the error message anyway.
public class RegularExpressionTimeOut : System.ComponentModel.DataAnnotations.ValidationAttribute
{
public System.TimeSpan TimeOut { get; set; }
public string Pattern { get; set; }
private const string DefaultErrorMessage = "Invalid email.";
//private string ErrorMessage;
/// <summary>
/// Constructor
/// </summary>
/// <param name="pattern">Regular expression as string</param>
/// <param name="timeOut">Number of seconds</param>
public RegularExpressionTimeOut(string pattern, int timeOut) : base(DefaultErrorMessage)
{
this.Pattern = pattern;
this.TimeOut = new System.TimeSpan(0, 0, timeOut);
//this.ErrorMessage = errorMessage;
}
public override bool IsValid(object value)
{
//if (!string.IsNullOrEmpty(this.ErrorMessage) && ...)
return (new System.Text.RegularExpressions.Regex(this.Pattern, System.Text.RegularExpressions.RegexOptions.None, this.TimeOut)).IsMatch(this.Pattern);
}
public override string FormatErrorMessage(string ErrorMessage)
{
return string.Format(ErrorMessageString, ErrorMessage);
}
}
You RegularExpressionTimeOut constructor does not have a parameter for ErrorMessage. The typical usage is
public class RegularExpressionTimeOut : ValidationAttribute
{
private const string DefaultErrorMessage = "Invalid email";
public RegularExpressionTimeOut(string pattern, int timeOut) : base(DefaultErrorMessage)
and usually an override is added to format a specific message provide in the Attribute where you access values such as the property name (although in your case you don't seem to be needing or wanting that.
public override string FormatErrorMessage(string name)
{
return string.Format(ErrorMessageString, name, .....);
}
Side note: your closing parenthesis is not located correctly. It should be
[RegularExpressionTimeOut(#"....", 5, ErrorMessage = "Invalid email.")]
Edit
Your not actually testing the value of the Email property which is posted back (you just testing that the regex pattern matches itself. You need to change the IsValid() method to
public override bool IsValid(object value)
{
string email = (string)value; // cast it to a string for use in the Regex method
return (new Regex(this.Pattern, RegexOptions.None, this.TimeOut)).IsMatch(email);
}
Note also that the regex pattern you have shown should take a few millisecond to test so its not clear why you would need this. In addition, you do not have any associated client side validation (your attribute does not implement ICientValitable and in any case there would be no point since javascript regex does not have a timeout) so the message would only be returned if you return the view when ModelState.IsValid == false. In that case, your message "Invalid email" would be rather confusing to the user and it should probably indicate that it needs to be #outlook or #hotmail etc.
I'm developing an application where I need to save a List of objects of type ExerciseObject. I don't understand why this data isn't persisting in the IsolatedStorageSettings after application restart while all of my other data is (including other objects I created).
Here is the ExerciseObject class in which I create a list containing this object type to be stored to IsolatedStorageSettings. The interesting part is that while the application is open, the data is saved, it's just once I restart the app that only the List of ExerciseObject data is lost.
public class ExerciseObject
{
public ExerciseObject(string description, int caloriesBurned, bool burned)
{
this.Description = description;
this.CaloriesBurned = caloriesBurned;
this.Burned = burned; // true if activity, false if food
if (this.Burned) // text should be green
this.TextColor = new SolidColorBrush(Colors.Green);
else
this.TextColor = new SolidColorBrush(Colors.Red);
}
public string Description { get; set; }
public int CaloriesBurned { get; set; }
public bool Burned { get; set; }
public SolidColorBrush TextColor { get; set; }
}
This is how I am adding to the list:
ExerciseObject exerciseObj = new ExerciseObject(this.txtActivity.Text, int.Parse(this.txtBurned.Text), true);
List<ExerciseObject> tempList = (List<ExerciseObject>)IsolatedStorageSettings.ApplicationSettings["ListExerciseObjects"];
tempList.Add(exerciseObj);
IsolatedStorageSettings.ApplicationSettings["ListExerciseObjects"] = tempList;
And this is how I am accessing the List:
// Get the list of exercise objects from the isolated storage
List<ExerciseObject> exerciseObjects = (List<ExerciseObject>)IsolatedStorageSettings.ApplicationSettings["ListExerciseObjects"];
// Setting data context of listBox to the list of exercise objects for now
this.listBoxEntries.DataContext = exerciseObjects;
I tried your example, and it seems that the type SolidColorBrush is not serializable. The phone internals calls the iso storage "Save" method when the app exists and this is failing silently. to repro, remove the TextColor property or apply the "IgnoreDataMemberAttribute" on the property and observe that the issue goes away.
There are various ways to go about fixing this. I would personally derive the type of brush to apply at runtime, from your "burned" property.
I have attached a working example of your code which now stores the actual color as opposed to a SolidColorBrush object if you still wish to go store it.
Main.cs
// Constructor
public MainPage()
{
InitializeComponent();
BindExercises();
}
private void AddExercise(object sender, RoutedEventArgs e)
{
var exercise = new ExerciseObject("Activity added at: " + DateTime.Now.Ticks, (DateTime.Now.Second + 200), true);
IsolatedStorageSettingsManager.AddToCollection("ListExerciseObjects", exercise);
this.BindExercises();
}
private void BindExercises()
{
// Setting data context of listBox to the list of exercise objects for now
this.listBoxEntries.ItemsSource = IsolatedStorageSettingsManager.Get<IEnumerable<ExerciseObject>>("ListExerciseObjects").ToObservableCollection();
}
private void RemoveAllExercises(object sender, RoutedEventArgs e)
{
IsolatedStorageSettingsManager.Remove("ListExerciseObjects");
this.BindExercises();
}
public static class EnumerableExtensions
{
public static ObservableCollection<T> ToObservableCollection<T>(this IEnumerable<T> myList)
{
if (myList == null) return null;
var oc = new ObservableCollection<T>();
foreach (var item in myList)
oc.Add(item);
return oc;
}
}
public class ExerciseObject
{
public ExerciseObject() { }
public ExerciseObject(string description, int caloriesBurned, bool burned)
{
this.Description = description;
this.CaloriesBurned = caloriesBurned;
this.Burned = burned; // true if activity, false if food
if (this.Burned) // text should be green
this.Color = Colors.Green;
else
this.Color = Colors.Red;
}
public string Description { get; set; }
public int CaloriesBurned { get; set; }
public bool Burned { get; set; }
public Color Color { get; set; }
[IgnoreDataMemberAttribute]
public SolidColorBrush TextColor
{
get
{
return new SolidColorBrush(this.Color);
}
}
}
public class IsolatedStorageSettingsManager
{
private static readonly IsolatedStorageSettings isolatedStorageSettings = IsolatedStorageSettings.ApplicationSettings;
public static void Add<T>(string key, T value)
{
if (isolatedStorageSettings.Contains(key))
{
isolatedStorageSettings[key] = value;
}
else
{
isolatedStorageSettings.Add(key, value);
}
Save();
}
public static T Get<T>(string key, T #default = default(T))
{
T value;
if (isolatedStorageSettings.TryGetValue(key, out value))
{
return value;
}
return #default; // TODO: tell it what to do if the key is not found.
}
/// <summary>
/// Special [very crude] method which handles collections.
/// </summary>
/// <typeparam name="T">
/// The type of object to be serialized.
/// </typeparam>
/// <param name="key">
/// The key to assign to the object.
/// </param>
/// <param name="newValue">
/// The new record to add.
/// </param>
/// <returns>
/// The newly updated collection.
/// </returns>
public static IEnumerable<T> AddToCollection<T>(string key, T newValue) where T : class
{
List<T> currentValues;
if (isolatedStorageSettings.Contains(key))
{
currentValues = isolatedStorageSettings[key] as List<T>;
if (currentValues == null)
{
throw new InvalidCastException("The current values in the isolated storage settings " + key + "is not of a valid type");
}
currentValues.Add(newValue);
isolatedStorageSettings[key] = currentValues;
}
else
{
currentValues = new List<T> { newValue };
isolatedStorageSettings.Add(key, currentValues);
}
Save();
return currentValues;
}
public static void Remove(string key)
{
if (isolatedStorageSettings.Contains(key))
{
isolatedStorageSettings.Remove(key);
Save();
}
}
private static void Save()
{
isolatedStorageSettings.Save();
}
}
Xaml
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button Content="Add Exercise" Click="AddExercise" />
<Button Grid.Row="1" Content="Clear All" Click="RemoveAllExercises" />
<ListBox x:Name="listBoxEntries" Grid.Row="2">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Description }" />
<TextBlock Margin="15 0 0 0" Text="{Binding CaloriesBurned }" Foreground="{Binding TextColor}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
We have a multi-site solution (Site 1 and Site 2), and I need to be able to determine if an item for which we're getting the URL (in the LinkProvider, which is custom) belongs to the current context site (Sitecore.Context.Site), or is part of a different site. Is there a good way to do this?
Basically, we just need to be able to find out to which site the item is associated. We can do the comparison between that value and the current context site.
I suggest you make an extension method for the Item class that returns a SiteInfo object containing the definition of the site it belongs to.
Unfortunately I don't have my laptop here with all my code, so I just typed it in Visual Studio and made sure it build, but I'm pretty sure it works:
public static class Extensions
{
public static Sitecore.Web.SiteInfo GetSite(this Sitecore.Data.Items.Item item)
{
var siteInfoList = Sitecore.Configuration.Factory.GetSiteInfoList();
foreach (Sitecore.Web.SiteInfo siteInfo in siteInfoList)
{
if (item.Paths.FullPath.StartsWith(siteInfo.RootPath))
{
return siteInfo;
}
}
return null;
}
}
So now you can call the GetSite() method on all Item objects and retrieve the SiteInfo for that item.
You can use that to check if it matches your Sitecore.Context.Site, for example by doing:
SiteInfo siteInfo = itemYouNeedToCheck.GetSite();
bool isContextSiteItem = Sitecore.Context.Site.SiteInfo.Equals(siteInfo);
EDIT: I just thought that you could also do it shorter, like this:
public static Sitecore.Web.SiteInfo GetSite(this Sitecore.Data.Items.Item itemYouNeedToCheck)
{
return Sitecore.Configuration.Factory.GetSiteInfoList()
.FirstOrDefault(x => itemYouNeedToCheck.Paths.FullPath.StartsWith(x.RootPath));
}
So pick whatever you like best :)
/// <summary>
/// Get the site info from the <see cref="SiteContextFactory"/> based on the item's path.
/// </summary>
/// <param name="item">The item.</param>
/// <returns>The <see cref="SiteInfo"/>.</returns>
public static SiteInfo GetSiteInfo(this Item item)
{
return SiteContextFactory.Sites
.Where(s => !string.IsNullOrWhiteSpace(s.RootPath) && item.Paths.Path.StartsWith(s.RootPath, StringComparison.OrdinalIgnoreCase))
.OrderByDescending(s => s.RootPath.Length)
.FirstOrDefault();
}
I upvoted Ruud van Falier's answer then after a few round of testing I realized that it only works in certain scenarios. Couldn't cancel the vote, so I made some modification to the code here:
public static SiteInfo GetSite(this Item item)
{
var siteInfoList = Sitecore.Configuration.Factory.GetSiteInfoList();
SiteInfo currentSiteinfo = null;
var matchLength = 0;
foreach (var siteInfo in siteInfoList)
{
if (item.Paths.FullPath.StartsWith(siteInfo.RootPath, StringComparison.OrdinalIgnoreCase) && siteInfo.RootPath.Length > matchLength)
{
matchLength = siteInfo.RootPath.Length;
currentSiteinfo = siteInfo;
}
}
return currentSiteinfo;
}
So the issue was that other built-in sites normally have shorter paths like "/sitecore/content" which will match with your content path before it reaches the actual site configuration. So this code is trying to return the best match.
If you are using Sitecore 9.3+ then you will want to use IItemSiteResolver through dependency injection instead.
IItemSiteResolver _siteResolver;
public MyClass(Sitecore.Sites.IItemSiteResolver siteResolver) {
_siteResolver = siteResolver;
}
public void DoWork(Item item) {
Sitecore.Web.SiteInfo site = _siteResolver.ResolveSite(item);
...
}
public static SiteInfo GetSiteInfo(this Item item)
{
return Sitecore.Links.LinkManager.ResolveTargetSite(item);
}
This is what I use for our multisite solution.
The FormatWith is just a helper for string.Format.
public static SiteInfo GetSite(this Item item)
{
List<SiteInfo> siteInfoList = Factory.GetSiteInfoList();
SiteInfo site = null;
foreach (SiteInfo siteInfo in siteInfoList)
{
var siteFullPath = "{0}{1}".FormatWith(siteInfo.RootPath, siteInfo.StartItem);
if (string.IsNullOrWhiteSpace(siteFullPath))
{
continue;
}
if (item.Paths.FullPath.StartsWith(siteFullPath, StringComparison.InvariantCultureIgnoreCase))
{
site = siteInfo;
break;
}
}
return site;
}
And to avoid dependencies, for unit test purposes, I've created a method to extract this information from web.config directly:
public static SiteInfoVM GetSiteInfoForPath(string itemPath)
{
var siteInfos = GetSiteInfoFromXml();
return siteInfos
.Where(i => i.RootPath != "/sitecore/content" && itemPath.StartsWith(i.RootPath))
//.Dump("All Matches")
.OrderByDescending(i => i.RootPath.Length).FirstOrDefault();
}
static List<SiteInfoVM> GetSiteInfoFromXml()
{
XmlNode sitesNode = Sitecore.Configuration.ConfigReader.GetConfigNode("sites");//.Dump();
var result = sitesNode.Cast<XmlNode>()
.Where(xn => xn.Attributes != null && xn.Attributes["rootPath"] != null
//&& (xn.Attributes["targetHostName"]!=null || xn.Attributes["name"].Value)
)
.Select(xn => new {
Name = xn.Attributes["name"].Value,
RootPath = xn.Attributes["rootPath"].Value,
StartItem = xn.Attributes["startItem"].Value,
Language = xn.Attributes["language"] != null ? xn.Attributes["language"].Value : null,
TargetHostName = (xn.Attributes["targetHostName"] != null) ? xn.Attributes["targetHostName"].Value : null,
SiteXml = xn.OuterXml
})
.Select(x => new SiteInfoVM(x.Name, x.RootPath, x.StartItem, x.Language, x.TargetHostName, x.SiteXml))
.ToList();
return result;
}
public class SiteInfoVM
{
public SiteInfoVM(string name, string rootPath, string startItem, string lang, string tgtHostName, string siteXml)
{
Name = name;
TargetHostName = tgtHostName;
RootPath = rootPath;
StartItem = startItem;
Language = lang;
SiteXml = siteXml;
}
public string Name { get; set; }
public string RootPath { get; set; }
public string StartItem { get; set; }
public string Language { get; set; }
public string TargetHostName { get;set; }
public string SiteXml { get; set; }
}
public static class SiteResolver
{
public static SiteContext ResolveSitebyItem(Item contentItem)
{
var site = Factory.GetSiteInfoList().FirstOrDefault(
x => contentItem.Paths.Path.Contains(x.RootPath) &&
x.RootPath != "/sitecore/content" &&
x.Domain == "extranet"
);
if (site is SiteInfo siteInfo)
{
return new SiteContext(siteInfo);
}
return Sitecore.Context.Site;
}
}
I believe this is better solution http://firebreaksice.com/sitecore-context-site-resolution/
public static Sitecore.Web.SiteInfo GetSite(Sitecore.Data.Items.Item item)
{
var siteInfoList = Sitecore.Configuration.Factory.GetSiteInfoList();
foreach (Sitecore.Web.SiteInfo siteInfo in siteInfoList)
{
var homePage = Sitecore.Context.Database.GetItem(siteInfo.RootPath + siteInfo.StartItem);
if (homePage != null && homePage.Axes.IsAncestorOf(item))
{
return siteInfo;
}
}
return null;
}
I have the following .cs file;
using System;
using System.Collections.Generic;
using Sitecore.Data;
using Sitecore.Data.Items;
using Sitecore.Diagnostics;
using Sitecore.Events;
using Sitecore.SecurityModel;
namespace LocationItemEventHandler
{
public class ItemEventHandler
{
private static readonly SynchronizedCollection<ID> MProcess = new SynchronizedCollection<ID>();
/// <summary>
/// This custom event auto-populates latitude/longitude co-ordinate when a location item is saved
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
protected void OnItemSaved(object sender, EventArgs args)
{
var item = Event.ExtractParameter(args, 0) as Item;
if (item != null && !MProcess.Contains(item.ID))
{
if (item.TemplateID.Equals("{E490971E-758E-4A75-9C8D-67EC2C6321CA}"))
{
string errMessage = "";
string responseCode = "";
string address = item.Fields["Address"].Value;
if (1=1)
{
string latitude = "100";
string longitude = "200";
MProcess.Add(item.ID);
try
{
var latlngField = item.Fields["Google LatLng"];
using (new SecurityDisabler())
{
item.Editing.BeginEdit();
latlngField.SetValue(latitude + " - " + longitude, true);
Sitecore.Context.ClientPage.ClientResponse.Alert(
string.Format(
"Fields updated automatically\r\nLatitude: {0}\r\nLongitude: {1}",
latitude, longitude));
item.Editing.EndEdit();
}
}
catch (Exception exception)
{
Log.Error(exception.Message, this);
}
finally
{
MProcess.Remove(item.ID);
}
}
}
}
}
}
}
This is in a code file LocationItemEventHandler.cs in App_Code.
this is in the web.config
<event name="item:saved">
<handler type="LocationItemEventHandler.ItemEventHandler, LocationItemEventHandler" method="OnItemSaved"/>
</event>
When i try to save an item i get a "Could not resolve type name".
What am i missing?
The class name you have configured does not match the actual class name.
Edit:
If the handler is in App_Code, i believe you should leave off the assembly name in the type reference. Better yet, don't use App_Code.