Update value of one field based on another - save

I have a template two text fields. A user creates an item based on this template and enters content into the first text field.
On item save, I want to be able to manipulate that value, somehow, and write it to the second field when the item is saved.
I have read about 3 ways of doing this - item:saved, item:saving or item save rules engine. I'm looking for an explanation in the difference of these approaches and if you had to choose which one would you pick?

Item Saved is an event fired after an item has been saved. I wouldn't use that because it will chain events once you want to update again the same item. There is ways to avoid that but I would rather use Item:Saving, which is fired before the item has been saved.
Rules engine, I don't know exactly how it would fit your needs.
A sample of a item saving that changes the item name prior to being saved:
public class ItemNameReplacementEventHandler
{
/// Called when [item saving].
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
public void OnItemSaving(object sender, EventArgs args){
var item = GetContextItem(args);
if (!ShouldChange(item))
return;
//do the replace
Regex pattern = new Regex(this.ReplaceFromRegexPattern);
string newName = pattern.Replace(item.Name, this.ReplaceToString);
item.Name = newName;
}
private static Item GetContextItem(EventArgs args)
{
return Event.ExtractParameter(args, 0) as Item;
}
}
Just change the value of a field, or in this case the item name, and it is enough.
The configuration is like this:
<events>
<event name="item:saving">
<handler patch:before="handler[#type='Sitecore.Tasks.ItemEventHandler, Sitecore.Kernel']"
type="TYPE, ASSEMBLY" method="OnItemSaving">
</handler>
</event>
</events>

Related

Field and values connection in Storm

I have a fundamental question in storm. I can clearly understand some basic things. For example i have a main class with this code inside:
...
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(SENTENCE_SPOUT_ID, new SentenceSpout());
builder.setBolt(SPLIT_BOLT_ID, new SplitSentenceBolt()).shuffleGrouping(SENTENCE_SPOUT_ID);
builder.setBolt(COUNT_BOLT_ID, new WordCountBolt(), 3).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word"));
builder.setBolt(REPORT_BOLT_ID, new ReportBolt()).globalGrouping(COUNT_BOLT_ID);
...
and i understand that 1st element(ex. "SENTENCE_SPOUT_ID") is the id of the bolt/spout in order to show the connection between 2 of them. The 2nd element(ex.new SentenceSpout()) specifies the spout or bold that we set in our topology. 3rd element marks the num of tasks that we need for this certain bolt spout.
Then we use .fieldsGrouping or .shuffleGrouping etc to specify the type of grouping and then between the parenthesis the 1st element is the connection with the bolt/spout that takes the input and the 2nd (ex. new Fields("word")) determines the fields that we will group by.
Inside the code of one of the bolts:
public class SplitSentenceBolt extends BaseRichBolt{
private OutputCollector collector;
public void prepare(Map config, TopologyContext context, OutputCollector collector) {
this.collector = collector;
}
public void execute(Tuple tuple) {
this.collector.emit(a, new Values(word, time, name));
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("word"));
}
}
At this.collector.emit(a, new Values(word, time, name)); a is the stream_ID and values(...) are the elements of the tuple.
At declarer.declare(new Fields("word")); word must be one of the previous values. Am i right to all the previous?
So my question is: that in declarer.declare(new Fields("word")); word must be the same with word in this.collector.emit(a, new Values(word, time, name)); and the same with the word in builder.setBolt(COUNT_BOLT_ID, new WordCountBolt(), 3).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word")); ????
The number and order of the fields you declare in declareOutputFields should match the fields you emit.
Two changes I'd recommend:
For now use the default stream by omitting the first parameter: collector.emit(new Values(word, time, name));
Make sure you declare the same number of fields: declarer.declare(new Fields("word", "time", "name"));

Get required fields for some WorkItem

Is it possible to get such XML, using TFS API or other tools?
This XML contains information about the fields that must be filled in translation work item to another status.
Screen here http://sqlrefactorstudio.com/content/png/TFS%20Work%20item%20required%20fields.png
Using the TFS API in the simple example below will write out the required fields for a given work item.
/// <summary>
/// Writes out the required fields for a work item.
/// </summary>
/// <param name="workItemId">The ID of a work item.</param>
private static void _GetRequiredFieldsForWorkItem(int workItemId)
{
using (TeamProjectPicker tpp = new TeamProjectPicker(TeamProjectPickerMode.SingleProject, false, new UICredentialsProvider()))
{
if (tpp.ShowDialog() == DialogResult.OK)
{
TfsTeamProjectCollection projectCollection = tpp.SelectedTeamProjectCollection;
WorkItemStore store = projectCollection.GetService<WorkItemStore>();
Console.WriteLine("Required Work Item Fields");
Console.WriteLine("-------------------------------");
WorkItem item = store.GetWorkItem(workItemId);
foreach (Field field in item.Fields)
{
if (field.IsRequired)
{
Console.WriteLine(field.ReferenceName);
}
}
}
}
}

Nested bean : a collection inside an object

I got a simple POJO class that i wish to display / update in a form
Using the BeanItem class and the binding of component data, i was able to quickly display the first attributes of may data class. However i've hit a wall for tow related attributes :
my class posses a set of available status, as a list of object 'AppStatus'. it also possess a current status, that is one of the status in the 'available' list.
I would like to display the list in the form as a combobox, with the current status selected.
I'we managed to associate the 'available' attribute with a combobox, but i can't seem to be able to fill this combobox when setting the data source (method setItemDataSource). How do i get the avalaible status list and the current status from my Item ?
I could always use a workaround and add a parameter to the method to get the source objet in addition to the BeanItem, but i would prefer to avoid this if the Item properties can give me my attribute.
Regards
Edit : shortened exemple, with code from Eric R.
class Status {
String id;
Sting label
+ setter /getter
}
class App {
String AppId;
String AppLabel
ArrayList<Status> availablestatus;
Status currentStatus
+setter/getter
}
in the form extension, in the createField of the fieldfactory i added the following lines
if ("status".equals(propertyId)) {
// create the combobox
ComboBox status = new ComboBox(
texts.getString("application.label.status"));
status.setItemCaptionMode(AbstractSelect.ITEM_CAPTION_MODE_PROPERTY);
status.setItemCaptionPropertyId("label");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
IndexedContainer container = new IndexedContainer(
(Collection<ApplicationStatus>) item.getItemProperty(
"availableStatus").getValue());
status.setContainerDataSource(container);
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
return status;
} else...
this didn't work, i do get a combobox, with the correct number of lines, but all empties.
i tried to use a beanContainer instead of a IndexedContainer
BeanContainer<String, ApplicationStatus> container =
new BeanContainer<String, ApplicationStatus>(ApplicationStatus.class);
container.addAll((Collection<ApplicationStatus>) item
.getItemProperty("availableStatus").
container.setBeanIdProperty("id");
the result is slightly better, since i do have the available values in the combobox.
only the currentValue is not selected...
I also tried to use a nestedbean property to get the id of the currentstatus, but the result is still not valid... i get a combobox, with the correct value selected, but i can not see others values anymore, since the combobox is readonly ?(even with setReadOnly(false);)
I suggest my way to resolve this. I don't think this is the nicest way, but it's works.
The beanItem class contains all you need.
I did the following in a simple project and it's work verry well :
ComboBox status = new ComboBox("ComboBox");
status.setImmediate(true);
status.setNullSelectionAllowed(false);
for(Status st : (Collection<Status>)item.getItemProperty("availableStatus").getValue()) {
status.addItem(st);
status.setItemCaption(st, st.getLabel());
}
status.setPropertyDataSource(item.getItemProperty("currentStatus"));
Hope it's works.
Regards Éric
From the vaadin demo site you can get this sample that show how to fill a combobox with countries. You could do the same i would guess (not sure I understand your problem 100%):
myForm.setFormFieldFactory(new MyFormFieldFactory ());
private class MyFormFieldFactory extends DefaultFieldFactory {
final ComboBox countries = new ComboBox("Country");
public MyFormFieldFactory () {
countries.setWidth(COMMON_FIELD_WIDTH);
countries.setContainerDataSource(ExampleUtil.getISO3166Container());
countries
.setItemCaptionPropertyId(ExampleUtil.iso3166_PROPERTY_NAME);
countries.setItemIconPropertyId(ExampleUtil.iso3166_PROPERTY_FLAG);
countries.setFilteringMode(ComboBox.FILTERINGMODE_STARTSWITH);
}
#Override
public Field createField(Item item, Object propertyId,
Component uiContext) {
Field f = (Field)item;
if ("countryCode".equals(propertyId)) {
// filtering ComboBox w/ country names
return countries;
}
return f;
}
}

Prevent selection of a particular item in spark list

I have a Spark List which has a custom itemRenderer for rendering each item in the List.
I wish to prevent an item in that list from being selected (based on some custom logic) by the user.
What is the best way I can achieve this?
Here's how my List is defined:
<s:List id="myList" itemRenderer="com.sample.MyItemRenderer" />
and of course, I have a item renderer defined as the class com.sample.MyItemRenderer.
The selection of items is handled by the list alone as far as I know, so I would say that you can manage it from there. I would have a field on the Objects that are in the list called "selectable" or something like that and when the list item is changing check to see if the new item is actually selectable and if it isn't then you can either have it clear the selection or reset to the previous selection. You can accomplish that by reacting to the "changing" event on the list component and calling "preventDefault" on the IndexChangeEvent as follows:
protected function myList_changingHandler(event:IndexChangeEvent):void {
var newItem:MyObject = myList.dataProvider.getItemAt(event.newIndex) as MyObject;
if(!newItem.selectable) {
event.preventDefault();
}
}
// Jumping ahead ...
<s:List id="myList" changing="myList_changingHandler(event)" // ... continue implementation
The relevant part of the MyObject class is as follows:
public class MyObject {
private var _selectable:Boolean;
public function MyObject(){
}
public function set selectable(value:Boolean):void {
_selectable = value;
}
public function get selectable():Boolean {
return _selectable;
}
}

Text on TextBox with UpdateSourceTrigger=PropertyChanged is not updated when coercion of text input results in unchanged source value

I have a text box whose Text property has a TwoWay MultiBinding with UpdateSourceTrigger set to PropertyChanged. The first Binding is to a dependency property (Value) which has a PropertyChangedCallBack function that rounds the value to one decimal place.
The purpose of the text box is to perform the rounding as the user types rather than when the text box loses focus, hence why UpdateSourceTrigger is set to PropertyChanged.
The problem I am having is that if text is entered that does NOT result in Value changing, the Text property and Value become out of sync. Only if the rounding operation causes Value to change does Text get updated on the fly. E.g., if Text and Value are both 123.4 and the user types 1 after this, Value is rounded to the same value (123.4), but Text shows 123.41. However, if 9 is then typed after the 4, Value is rounded up to 123.5. And because of this actual change, Text is then updated to the same (123.5).
Is there any way of forcing a text box to update from its source even when the source hasn't changed since the last trigger? I have tried using BindingExpressionBase.UpdateTarget() but this only works when UpdateSourceTrigger is set to Explicit, which can't be used as Value no longer gets updated prior to a suitable time where UpdateTarget could be called (such as a TextInput handler). I have tried other methods such as explicitly updating the Text value from the bound Value, forcing an actual change to Value temporarily to invoke an update, but these "hacks" either don't work or cause other problems.
Any help would be greatly appreciated.
The code is below.
XAML snippet
<TextBox>
<TextBox.Text>
<MultiBinding Converter="{local:NumberFormatConverter}"
UpdateSourceTrigger="Explicit"
Mode="TwoWay">
<Binding Path="Value"
RelativeSource="{RelativeSource AncestorType={x:Type Window}}"
Mode="TwoWay" />
</MultiBinding>
</TextBox.Text>
</TextBox>
C# snippet
public static readonly DependencyProperty ValueProperty =
DependencyProperty.Register(
"Value", typeof(decimal), typeof(MainWindow),
new FrameworkPropertyMetadata(0m,
new PropertyChangedCallback(OnValueChanged)));
private static void OnValueChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
{
obj.SetValue(ValueProperty, Math.Round((decimal)args.NewValue, 1));
}
Converter class required
public class NumberFormatConverter : MarkupExtension, IMultiValueConverter
{
public static NumberFormatConverter Instance { private set; get; }
static NumberFormatConverter()
{
Instance = new NumberFormatConverter();
}
public override object ProvideValue(IServiceProvider serviceProvider_)
{
return Instance;
}
#region Implementation of IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return values[0].ToString();
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
var result = 0m;
if (value != null)
{
decimal.TryParse(value.ToString(), out result);
}
return new object[] { result };
}
#endregion
}
I did a little digging on the Internet, and it turns out this was broken in WPF 4. Someone with an almost-identical problem to me posted here:
http://www.go4answers.com/Example/textbox-shows-old-value-being-coerced-137799.aspx
'Answer 8' states this was broken in WPF 4 and suggests a solution, which is to actually use UpdateSourceTrigger="Explicit" but to handle the TextChanged event and call BindingExpression.UpdateSource() to force changes in the text box to be reflected in the underlying value as if UpdateSourceTrigger="PropertyChanged", as per this post:
Coerce a WPF TextBox not working anymore in .NET 4.0
I implemented this, but lo and behold there were further side effects, in particular that every keystroke caused the caret to jump to the start of the text box due to updating the source and raising a PropertyChanged event. And also, any leading or trailing zeros or decimal places entered with the intention of entering further digits would get wiped out immediately. So, a simple condition to check the parsed decimal value of the text box versus the underlying value resolved this.
The following event handler is all that was needed:
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var tb = (TextBox)e.Source;
MultiBindingExpression binding = BindingOperations.GetMultiBindingExpression(tb, TextBox.TextProperty);
decimal result = 0m;
decimal.TryParse(tb.Text, out result);
if ((decimal)GetValue(ValueProperty) != result && binding != null)
{
int caretIndex = tb.CaretIndex;
binding.UpdateSource();
tb.CaretIndex = caretIndex;
}
}

Resources