When I select an item I want to check some fields before it will be displayed in the Field Editor and then change values on other fields.
So I need to subscribe to an event, but such event doesn't exist out of the box as I can see. Is there a way to hook to item selection action or I need to create a custom event, if so - where do I need to raise it?
Sounds like you need to create a custom validator - this blog post describes the process:
https://www.habaneroconsulting.com/stories/insights/2016/creating-a-custom-field-validator-in-sitecore
In summary:
Create a new field rule (Field validators are located in /sitecore/system/Settings/Validation Rules/Field Rules/) linking to your assembly. The blog post above gives the following example of a field validator
[Serializable]
namespace MySitecore.Project.Validators
{
// This validator ensures that the description attribute of a link is specified
public class LinkTextValidator : StandardValidator
{
public override string Name
{
get { return "Link text validator"; }
}
public LinkTextValidator() {}
public LinkTextValidator(SerializationInfo info, StreamingContext context) : base(info, context) { }
protected override ValidatorResult Evaluate()
{
Field field = this.GetField();
if (field == null)
return ValidatorResult.Valid;
string str1 = this.ControlValidationValue;
if (string.IsNullOrEmpty(str1) || string.Compare(str1, "<link>", StringComparison.InvariantCulture) == 0)
return ValidatorResult.Valid;
XmlValue xmlValue = new XmlValue(str1, "link");
string attribute = xmlValue.GetAttribute("text");
if (!string.IsNullOrEmpty(xmlValue.GetAttribute("text")))
return ValidatorResult.Valid;
this.Text = this.GetText("Description is missing in the link field \"{0}\".", field.DisplayName);
// return the failed result value defined in the parameters for this validator; if no Result parameter
// is defined, the default value FatalError will be used
return this.GetFailedResult(ValidatorResult.CriticalError);
}
protected override ValidatorResult GetMaxValidatorResult()
{
return this.GetFailedResult(ValidatorResult.Error);
}
}
}
Credit to: MICHAEL ARMSTRONG
Related
New to Selenium and working on few hands on activity. Below is my query:
An application has been developed which enrolls the user details to the shipment company. Using the application user can track their shipment status and details.
URL: http://webapps.tekstac.com/Handling_Regular_Expression/
Test Procedure:
Use the template code.
Don't make any changes in DriverSetup file.
Only in the suggested section add the code to,
Invoke the driver using getWebDriver() method defined in DriverSetup()
Implement the following methods:
public void setFormValues(WebDriver driver)
Identify the shipment for user text box and set the value as “Shamili”
Identify the Search button and click on the same
public WebElement getNameResultElement(WebDriver driver)
In the result page, find the element of 'Shamili' and return it.
public WebElement getShipmentResultElement(WebDriver driver)
In the result page, find the element of 'SHIP1236' and return it.
public WebElement getEmailResultElement(WebDriver driver)
In the result page, find the element of 'shamili93#gamil.com' and return it.
Get the e-mail from div id "e- mail" and shipment id from div id "shipment".
Validate the values of e-mail and shipmentId using the below methods.
public boolean validateEmail(String eMailID) {
public boolean validateShipmentId(String shipmentId)
Validation pattern is given below,
mail:\\b[A-Z0-9a-z-]+#[a-z]+\\.[a-z]{2,4}\\b
ShipmentID:[A-Z0-9]{8}
Return 'true' or 'false' boolean value based on the findings
This is the code that I have done so far. Saw few regEx examples online but none seems to work when I compile it and I am stuck.
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.By;
import org.openqa.selenium.WebElement;
public class NameLocator { //DO NOT change the class name
public static String baseUrl; //Assign 'http://webapps.tekstac.com/Handling_Regular_Expression/' for baseUrl
public static WebDriver driver;
public WebDriver createDriver()
{
DriverSetup ds = new DriverSetup();
driver = ds.getWebDriver();
baseUrl = "http://webapps.tekstac.com/Handling_Regular_Expression/";
return driver;
//Create driver. Assign it to static variable 'driver' and return it
}
public void navigate(WebDriver driver){
driver.navigate().to(baseUrl);
//Navigate to the baseUrl
}
public void setFormValues(WebDriver driver)
{
WebElement userId = driver.findElement(By.id("userId"));
userId.sendKeys("Shamili");
WebElement track = driver.findElement(By.id("track"));
track.click();
//set the value for 'Shipment for user' and submit form
}
public WebElement getNameResultElement(WebDriver driver) {
return driver.findElement(By.xpath("/html/body/div/table/tbody/tr[1]/td[2"));
//Find the element of 'Shamili' and return it
}
public WebElement getShipmentResultElement(WebDriver driver) {
return driver.findElement(By.id("shipment"));
//Find the element of 'SHIP1236' and return it
}
public WebElement getEmailResultElement(WebDriver driver) {
return driver.findElement(By.id("e- mail"));
//Find the element of 'shamili93#gamil.com' and return it
}
public boolean validateEmail(String eMailID) {
String Emailregex = "^[A-Za-z]\\w{5,29}$";
//Validate email using regex.
}
public boolean validateShipmentId(String shipmentId) {
String Shipmentregex = "[A-Z0-9]{8}";
//Validate shipmentId using regex
}
public static void main(String[] args)
{
NameLocator reg=new NameLocator();
//Add required code here
}
}
There is a custom field, that I declared for the Customer DAC:
public class CustomerExt : PXCacheExtension<Customer>
{
#region UsrDemoField
[PXDBString(255)]
[PXUIField(DisplayName = "Demo Field")]
public virtual string UsrDemoField { get; set; }
public abstract class usrDemoField : IBqlField { }
#endregion
}
Attempts to modify the ARInvoice Customer selector with the Customize Selector Columns popup didn't seem to work. How can I add my custom field into the ARInvoice customer selector?
Be aware, since Acumatica ERP build #17.201.0043, it's possible to customize the list of columns defined for AR Invoices' Customer lookup via the Customize Selector Columns dialog (available in the Data Class section of the Customization Manager). For step-by-step instructions please check the screenshot below:
To modify AR Invoices' Customer lookup on Acumatica ERP ver. 6.1 and earlier, please follow the steps below:
The definition of PXCustomizeSelectorColumns generated by the Customize Selector Columns popup brilliantly works with the majority of selectors inside Acumatica ERP. Basically, PXCustomizeSelectorColumns simply replaces originally defined columns for a selector with the custom set of columns during an initialization of PXCache:
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, AllowMultiple = false)]
public class PXCustomizeSelectorColumns: PXEventSubscriberAttribute
{
private readonly Type[] _columns;
public PXCustomizeSelectorColumns(params Type[] columns)
{
_columns = columns;
}
public override void CacheAttached(PXCache cache)
{
cache.SetAltered(this.FieldName, true);
foreach (PXEventSubscriberAttribute attr in cache.GetAttributes(null, this.FieldName))
{
PXSelectorAttribute sel = attr as PXSelectorAttribute;
if (sel == null)
continue;
sel.SetFieldList(_columns);
sel.Headers = null;
}
}
}
So what can cause the PXCustomizeSelectorColumns attribute to fail and not replace selector's originally defined columns? Any time the SetColumns method is executed on an instance of PXDimensionSelectorAttribute or PXSelectorAttribute after PXCache was initialized, there is no chance for PXCustomizeSelectorColumns to do its job.
[PXDBInt()]
[PXUIField(DisplayName = "Customer", Visibility = PXUIVisibility.Visible)]
[Serializable]
public class CustomerAttribute : AcctSubAttribute
{
...
public virtual void FieldSelecting(PXCache sender, PXFieldSelectingEventArgs e)
{
if (this.AttributeLevel == PXAttributeLevel.Item || e.IsAltered)
{
PopulateFields(sender);
}
PXFieldSelecting handler = GetAttribute<PXDimensionSelectorAttribute>().FieldSelecting;
handler(sender, e);
}
protected virtual void PopulateFields(PXCache sender)
{
if (_FieldList == null)
{
_FieldList = new string[this._fields.Length];
_HeaderList = new string[this._fields.Length];
for (int i = 0; i < this._fields.Length; i++)
{
Type cacheType = BqlCommand.GetItemType(_fields[i]);
PXCache cache = sender.Graph.Caches[cacheType];
if (cacheType.IsAssignableFrom(typeof(BAccountR)) ||
_fields[i].Name == typeof(BAccountR.acctCD).Name ||
_fields[i].Name == typeof(BAccountR.acctName).Name)
{
_FieldList[i] = _fields[i].Name;
}
else
{
_FieldList[i] = cacheType.Name + "__" + _fields[i].Name;
}
_HeaderList[i] = PXUIFieldAttribute.GetDisplayName(cache, _fields[i].Name);
}
}
var attr = GetAttribute<PXDimensionSelectorAttribute>().GetAttribute<PXSelectorAttribute>();
attr.SetColumns(_FieldList, _HeaderList);
}
...
}
With that said, to add a custom field into the ARInvoice Customer selector, one should replace all attributes declared for the ARInvoice.CustomerID field and redefine columns for the Customer selector within the CustomerActive attribute:
[PXDefault()]
[CustomerActive(typeof(Search<BAccountR.bAccountID>),
new Type[]
{
typeof(BAccountR.acctCD),
typeof(BAccountR.acctName),
typeof(CustomerExt.usrDemoField),
typeof(Address.addressLine1),
typeof(Address.addressLine2),
typeof(Address.postalCode),
typeof(CustomerAttribute.Contact.phone1),
typeof(Address.city),
typeof(Address.countryID),
typeof(CustomerAttribute.Location.taxRegistrationID),
typeof(Customer.curyID),
typeof(CustomerAttribute.Contact.salutation),
typeof(Customer.customerClassID),
typeof(Customer.status)
},
Visibility = PXUIVisibility.SelectorVisible, DescriptionField = typeof(Customer.acctName), Filterable = true, TabOrder = 2)]
After publishing the customization, custom Demo Field should finally appear in the ARInvoice Customer selector:
To enable searching against a custom field inside the ARInvoice Customer selector, open Invoices and Memos screen in the Layout Editor and type UsrDemoField as the GridProperties.FastFilterFields property of the Customer selector:
In C++ we can use QAbstractItemModel::setData() to modify the model's data. Searching the internet I only found how to read data from model to display it in a delegate. There are also some examples of adding and removing rows, but I could not find how to change the data of specific model index. Something like:
Slider {
onValueChanged: myModel.setData(0, {amount: value})
}
How can I modify data in a model from in QML?
In my projects I follow a different way to read/write data from/to QML models
I simply create two .qml file one to display and the another is a helper file to do database operations, and I create a model that inherits QAbstractItemModel and add four functions to it:
MyCustomObject * at(int index); // get an item to display
void reload(); // to notify QML view on update/delete
bool insert(MyCustomObject *p_myCustomObject); // insert an item in model or database
bool update(MyCustomObject *p_myCustomObject);// update an item to model or database
bool doDelete(int myCustomObjectID);// delete an item from model or database
then I create a local object to read/write, and when displaying these data I populate the local object value from the model and when I want to save I write that object to database
add this property to your main display class
property MyCustomObject myCustomObject : MyCustomObject{} // to read/write UI value ti/from it
and here is a helper class that reads UI values and insert, update or delete to/from models
Note: this class is for one of my applications, though just read it and modify it to meet your needs
import QtQuick 2.0
import DOO.Commands.Son 1.0
import DOOTypes 1.0
QtObject {
// read ui values into local son
function readUIValues()
{
var v_son = SonFactory.createObject()
v_son.name = sonName.text
v_son.image = sonImage.picture
v_son.age = sonAge.text
v_son.entryDate = Date.fromLocaleDateString(Qt.locale(), sonEntryDate.text, "dd-MM-yyyy")
v_son.commingFrom = sonCommingFrom.text
v_son.disabilityKind.kind = sonDisabilityKind.currentIndex
v_son.caseDescription = sonCaseDescription.text
return v_son
}
// simple UI validation
function validateUIValues()
{
if(sonName.text == "") return false
if(sonImage.picture == "") return false
if(sonAge.text < 1 || sonAge.text > 100) return false
if(Date.fromLocaleDateString(Qt.locale(), sonEntryDate.text, "dd-MM-yyyy") == "Invalid Date") return false
if(sonCommingFrom.text == "") return false
if(sonDisabilityKind.text == "") return false
if(sonCaseDescription.text == "") return false
return true
}
// save or update a son into database
function save()
{
if (!validateUIValues())
{
dooNotifier.showMessage("خطأ","ليدك مدخلات غير صحيحة، يُرجى التأكد من إدخال قيم صحيحة")
return
}
var v_son = readUIValues()
if(disMode === DOO.CreateNew)
{
if(SonResultsModel.insert(v_son))
{
dooNotifier.showMessage("تم بنجاح","تم إضافة الابن بنجاح")
sonDisplay.hide()
}
else
{
dooNotifier.showMessage("فشل","فشل إضافة الابن")
DOOLogger.log(SonResultsModel.lasrErrorText())
}
}
else
{
//get the ID of the son bieng edited
v_son.sonID = son.sonID
if(SonResultsModel.update(v_son))
{
dooNotifier.showMessage("تم بنجاح","تم تحديث الابن بنجاح")
sonDisplay.hide()
}
else
{
dooNotifier.showMessage("فشل","فشل تحديث الابن")
DOOLogger.log(SonResultsModel.lasrErrorText())
}
}
v_son.destroy()
}
function doDelete(sonID)
{
if(SonResultsModel.doDelete(sonID)) {
dooNotifier.showMessage("تم بنجاح","تم حذف الابن بنجاح")
sonDisplay.hide()
}
else dooNotifier.showMessage("فشل","فشل حذف الابن")
}
}
this is the way I use to read and write to/from databases I hope it helps
The glass mapper will return null object or (no items) for SitecoreQuery and SitecoreChildren attribute that are placed on the GlassModels. These attributes don't take any such parameter where I can specify them to return items if they don't exist in the the context lanaguge. The items e.g. exist in EN but don't exist in en-ES. I need to put a lot of null check in my views to avoid Null exception and makes the views or controller very messy. It is lot of boiler plate code that one has to write to make it work.
In Page Editor the SitecoreChildren returns item and content authors can create items in that langauge version by editing any field on the item. This automatically creates the item in that langauge. However the same code will fail in Preview mode as SitecoreChidren will return null and you see null pointer exception.
SitecoreQuery doesn't return any items in page editor and then Content Authors wont be able to create items in Page editor.
To make the experience good if we can pass a parameter to SiteocreQuery attribute so it disable VsersionCount and returns the items if they dont exist in that langauge.
This is actually not possible. There is an issue on GitHub which would make it easy to create a custom attribute to handle this very easy. Currently you need to create a new type mapper and copy all the code from the SitecoreQueryMapper. I have written a blog post here about how you can create a custom type mapper. You need to create the following classes (example for the SitecoreQuery).
New configuration:
public class SitecoreSharedQueryConfiguration : SitecoreQueryConfiguration
{
}
New attribute:
public class SitecoreSharedQueryAttribute : SitecoreQueryAttribute
{
public SitecoreSharedQueryAttribute(string query) : base(query)
{
}
public override AbstractPropertyConfiguration Configure(PropertyInfo propertyInfo)
{
var config = new SitecoreSharedQueryConfiguration();
this.Configure(propertyInfo, config);
return config;
}
}
New type mapper:
public class SitecoreSharedQueryTypeMapper : SitecoreQueryMapper
{
public SitecoreSharedQueryTypeMapper(IEnumerable<ISitecoreQueryParameter> parameters)
: base(parameters)
{
}
public override object MapToProperty(AbstractDataMappingContext mappingContext)
{
var scConfig = Configuration as SitecoreQueryConfiguration;
var scContext = mappingContext as SitecoreDataMappingContext;
using (new VersionCountDisabler())
{
if (scConfig != null && scContext != null)
{
string query = this.ParseQuery(scConfig.Query, scContext.Item);
if (scConfig.PropertyInfo.PropertyType.IsGenericType)
{
Type outerType = Glass.Mapper.Sc.Utilities.GetGenericOuter(scConfig.PropertyInfo.PropertyType);
if (typeof(IEnumerable<>) == outerType)
{
Type genericType = Utilities.GetGenericArgument(scConfig.PropertyInfo.PropertyType);
Func<IEnumerable<Item>> getItems;
if (scConfig.IsRelative)
{
getItems = () =>
{
try
{
return scContext.Item.Axes.SelectItems(query);
}
catch (Exception ex)
{
throw new MapperException("Failed to perform query {0}".Formatted(query), ex);
}
};
}
else
{
getItems = () =>
{
if (scConfig.UseQueryContext)
{
var conQuery = new Query(query);
var queryContext = new QueryContext(scContext.Item.Database.DataManager);
object obj = conQuery.Execute(queryContext);
var contextArray = obj as QueryContext[];
var context = obj as QueryContext;
if (contextArray == null)
contextArray = new[] { context };
return contextArray.Select(x => scContext.Item.Database.GetItem(x.ID));
}
return scContext.Item.Database.SelectItems(query);
};
}
return Glass.Mapper.Sc.Utilities.CreateGenericType(typeof(ItemEnumerable<>), new[] { genericType }, getItems, scConfig.IsLazy, scConfig.InferType, scContext.Service);
}
throw new NotSupportedException("Generic type not supported {0}. Must be IEnumerable<>.".Formatted(outerType.FullName));
}
{
Item result;
if (scConfig.IsRelative)
{
result = scContext.Item.Axes.SelectSingleItem(query);
}
else
{
result = scContext.Item.Database.SelectSingleItem(query);
}
return scContext.Service.CreateType(scConfig.PropertyInfo.PropertyType, result, scConfig.IsLazy, scConfig.InferType, null);
}
}
}
return null;
}
public override bool CanHandle(AbstractPropertyConfiguration configuration, Context context)
{
return configuration is SitecoreSharedQueryConfiguration;
}
}
And configure the new type mapper in your glass config (mapper and parameters for the constructor):
container.Register(Component.For<AbstractDataMapper>().ImplementedBy<SitecoreSharedQueryTypeMapper>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemIdNoBracketsParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemEscapedPathParameter>>().LifeStyle.Transient);
container.Register(Component.For<IEnumerable<ISitecoreQueryParameter>>().ImplementedBy<List<ItemDateNowParameter>>().LifeStyle.Transient);
You can then simply change the SitecoreQuery attribute on your model to SitecoreSharedQuery:
[SitecoreSharedQuery("./*")]
public virtual IEnumerable<YourModel> YourItems { get; set; }
For the children you could either use the shared query mapper and querying the children or create the same classes for a new SitecoreSharedChildren query.
Edit: Added bindings for IEnumerable<ISitecoreQueryParameter> as they are missing and therefor it threw an error.
<li>#if (string.IsNullOrWhiteSpace(topLinks.Target.Text))
{
topLinks.Target.Text = "EMPTY DESCRIPTION";
}
#(RenderLink(topLinks, x => x.Target, isEditable: true))
</li>
I need a way to catch it when a Content Editor has set up a link, but not actually put a Link Description in. At the moment it just renders spaces. The above works, but it's clunky and I need to put it everywhere I use a RenderLink. How do I default the text if it's empty?
I've created an extension method to work around it.
Note that I've extended GlassHtml and not GlassView because you may want to pass a different model type than the one that's used for the view.
namespace ParTech.MvcDemo.Context.Extensions
{
using System;
using System.Linq.Expressions;
using System.Web;
using Glass.Mapper.Sc;
using Glass.Mapper.Sc.Fields;
public static class GlassHtmlExtensions
{
public static HtmlString RenderLinkWithDefaultText<T>(this GlassHtml glassHtml, T model, Expression<Func<T, object>> field, object attributes = null, bool isEditable = true, string defaultText = null)
{
var linkField = field.Compile().Invoke(model) as Link;
if (linkField == null || string.IsNullOrEmpty(linkField.Text))
{
return new HtmlString(glassHtml.RenderLink(model, field, attributes, isEditable, defaultText));
}
return new HtmlString(glassHtml.RenderLink(model, field, attributes, isEditable));
}
}
}
You can now do this in your view:
#(((GlassHtml)this.GlassHtml).RenderLinkWithDefaultText(MyModel, x => x.LinkField, null, true, "Static default text"))
Still a bit hacky because you need to cast the IGlassHtml to GlassHtml, but it works.
If you always have the correct model defined for you view (and thus don't need to specify the model parameter) you could put this extension method on GlassView.