In D365 Finance and Operations, on form TaxExempt, General Section, there are several fields like CodeType, CodeName, CompanyList (dropdown menu).
User should type in desired values (types and names).
In next section Property VAT - setup there is command button New. When clicked on that button, it should create line with values which are taken from General Section: Company(from company list selection), Sales Tax Code (from code type) and Name (from Code Name). For now, it only creates blank line. Is there some advice how this can be performed ?
The method that will accomplish your goal is the initValue on the form datasource. After the super() call, add default values from other fields located on your form. A sample might look like this:
[DataSource]
class TaxExemptCodeTable
{
/// <summary>
/// Default values from other form controls/fields on new record creation
/// </summary>
public void initValue()
{
super();
TaxExemptCodeTable.Value = CustomFormControl.text();
//etc.
}
}
If you are creating an extension, there are actually multiple events for this depending on the existing baseline code. OnInitValue would be the analogue to compare to the non-extension solution mentioned above, but if there is existing code on this it might overwrite your field if there is already defaulting logic on the formdatasource. This is because the event will fire as one of the last methods called by the framework in the super() call, but before any code placed after the super(). This complicates the extension scenario.
If this is the case, you could look into defaulting values on the OnCreated event which will fire after the previous events and "base"/"out of the box code" that might already exist on these methods and/or events. This would overwrite any existing defaulting/init logic with the values you specify in the oncreated event, while also giving you the context of the form to work with (as opposed to table level events which would not have form controls/values to use which seems mandatory for your requirements)
Related
I would like to do some transformation on the submitted data based on which button on my form was clicked. Here is the scenario:
I have a text field named chain which holds the command chain.
I have a command text field that holds the last command.
I have two buttons on the form: Send and Send chain.
When Send is clicked, chain is set to command, erasing whatever was in it before. When Send chain is clicked, however, the contents of command are added to the end of chain after a delimiter, effectively creating a chain of commands.
I need to be able to check to see which of these two buttons was clicked so that I can set chain to the appropriate value.
Why am I not doing this in the controller? The problem is that I need to modify the value of chain. Since I cannot modify the values of an already-submitted form, I need to do this in an event, I assume. As I mentioned above, I need chain to either be equal to command, or chain + [delimiter] + command.
If I understood you correctly, you don't need an event at all. There is a built in method isClicked() since #2.3. Example from documentation:
if ($form->isValid()) {
// ... do something
// the save_and_add button was clicked
if ($form->get('save_and_add')->isClicked()) {
// probably redirect to the add page again
}
// redirect to the show page for the just submitted item
}
Link to chapter.
Update
Normally, the code I showed can be used in event - FormEvents::POST_SUBMIT to be precise, but as you already pointed out, you can not modify form data after submission. To be honest I can't come up with perfectly working example, so I will share an idea hoping it will lead to something, or at least until someone else suggest something else.
How about you add a hidden non mapped field to your form. Then (assuming you are allowed to use javascript in your form) you can attach event listener on when form is submitted and based on which button was clicked you can populate that hidden field with specific value. After that you can add event listener to your FormType and read the value of this hidden field, using FormEvents::POST_SET_DATA.
I am sorry I can not be more helpful as of now. If I came up with something I will edit my post.
Here is what I needed to do:
I couldn't use the POST_submit event because I couldn't transform the data after submission.
The isClicked() of both buttons returned false in other events, because the form hadn't been submitted yet.
The solution is something like this:
$data = $event->getData();
if (isset($data['send']) {
// transform the values here
}
In other words, the only way to check if a particular button was clicked is to check to see if its name exists as an index in the array returned by $event->getData().
When you create a Data Extender for a CME list – for instance to add a column for the Schema as in this example – it all works fine and dandy whenever you do actions that force a List reload.
However, some actions don’t force a list reload (like editing a component in a folder, then saving & closing) and it looks like Anguilla is loading the data for the item that changed using a different mechanism that loads only the data for the item in question (which makes sense).
If I would want my extended list view to behave properly and also load my additional attributes whenever a given item changes (instead of only when the list view is reloaded) what else do I need to do?
I found how Anguilla takes care of this. When you implement a Data Extender, you are extending the information regarding the items displayed in the list, which basically means that you are extending the Data (Model) behind the item in question.
Each Item in Tridion has its own class in the Anguilla Framework, for example a Component has its own Tridion.ContentManager.Component javascript "class".
Having said this, and going back to the example that shows the schema name of the component, we are not actually extending the model, since that information is already available in the component. However, we need to overwrite the methods exposed on each used for displaying information in the lists the item is in, in this case a Component.
So, when we deal with a Data Extender, if we want a full implementation of this functionality, we not only need to define the data extender:
<ext:dataextender
name="IntelligentDataExtender"
type="Com.Tridion.PS.Extensions.IntelligentDataExtender,PS.GUI.Extensions">
<ext:description>Shows extra info</ext:description>
</ext:dataextender>
But also we need to define what's the column we are adding:
<ext:lists>
<ext:add>
<ext:extension name="IntelligentColumnExtender"
assignid="IntelligentDataColumnExtender">
<ext:listDefinition>
<ext:selectornamespaces/>
<ext:columns>
<column
xmlns="http://www.sdltridion.com/2009/GUI/extensions/List"
id="IntelligentData"
type="data"
title="Additional Info"
selector="#ExtendedInfo"
translate="String"/>
</ext:columns>
</ext:listDefinition>
<ext:apply>
<ext:view name="DashboardView" />
</ext:apply>
</ext:extension>
</ext:add>
</ext:lists>
Once we have this, the GUI will display the column we just added: "Additional Info"
Well, now we need to achieve the list refreshing when the item is edited/checked-out and in, etc...
For that, we need to extend the model and implement a few methods in the Object we are extending. In this example I am extending the Page object, so whenever a page is edited, the row in the list we want to update gets refreshed, together with the rest of the cells in the table.
To extend the model we need to define what types are we extending, in this example I am going to use the "Page" class as an example. First of all you need to define the model extension in the config file of your Editor:
<cfg:group name="Com.Tridion.PS.Extensions.UI.Model"
merger="Tridion.Web.UI.Core.Configuration.Resources.DomainModelProcessor"
merge="always">
<cfg:domainmodel name="Com.Tridion.PS.Extensions.UI.Model">
<cfg:fileset>
<cfg:file type="script">/Scripts/PSPage.js</cfg:file>
</cfg:fileset>
<cfg:services />
</cfg:domainmodel>
</cfg:group>
and
<ext:modelextensions>
<cfg:itemtypes>
<cfg:itemtype id="tcm:64" implementation="Com.Tridion.PS.Extensions.UI.PSPage" />
</cfg:itemtypes>
</ext:modelextensions>
As you can see I am extending the Page by using the "Com.Tridion.PS.Extensions.UI.PSPage" class that is defined in the Javascript file "/Scripts/PSPage.js".
The only method that handles the row refreshing is the following:
Com.Tridion.PS.Extensions.UI.PSPage.prototype.getListItemXmlAttributes
= function PSPage$getListItemXmlAttributes(customAttributes) {
var attribs = {};
var p = this.properties;
if (customAttributes) {
for (var attr in customAttributes) {
attribs[attr] = customAttributes[attr];
}
}
//This adds my custom column back when the item is updated
attribs["ExtendedInfo"] = p.extendedInfo;
return this.callBase(
"Tridion.ContentManager.Page",
"getListItemXmlAttributes",
[attribs])
};
As you can see I am implementing the "ExtendedInfo" attribute which is the one displayed in my additional column.
There's more than just adding a Data Extender when dealing with adding a column to our lists. I will write a post in my blog here to provide with a fully working example.
I hope it makes sense.
Well, Jaime correctly described how CME updates changed items in Lists. But I want to add some additional information on how List controls, domain model List and Items are interact with each other. This might help you building your own extension with similar functionality.
Most of the domain model List items inherit from Tridion.ContentManager.ListTcmItems class. On the moment when any List item, based on mentioned class, is loaded it will be registered in Lists Registry (and un-registered when the List is unloaded). This will allow Model to use registered Lists as source of static data for Items and to update changed Items data in these Lists.
Update Item static data
For example, we have loaded ListCategories and there is only one Category in the List:
var pub = $models.getItem("tcm:0-1-1");
var list = pub.getListCategories();
list.load();
// After list is loaded
list.getXml();
That getXml() returns an XML like (simplified):
<tcm:ListCategories>
<tcm:Item ID="tcm:1-4-512" Type="512" Title="Keys" />
</tcm:ListCategories>
After that, if you try to get some static data for Category "Keys" it will be already set:
var category = $models.getItem("tcm:1-4-512");
category.isLoaded(); // return false
category.isStaticLoaded(); // return false
category.getTitle(); // return undefined
category.getStaticTitle(); // return "Keys"!
That is possible because $models.getItem call will do two things: it will return an existing (or create a new) domain model object and call $models.updateItemData method with it. This method call will go through all registered Lists in the Lists Registry and for all Lists whose TimeStamp bigger than Item's Last Update TimeStamp will call list.updateItemData with the model object.
The updateItemData method will check if the passed Item is in the list and if it is, then the Item will be updated with the static data that is available from the List.
Updating data of changed Items in the List
When a domain model Item is modified (updated, removed, created new) one of these methods is called:
$models.itemUpdated
$models.itemRemoved
These methods will go through the Lists in Lists Registry and call list.itemUpdated (or list.itemRemoved). These methods will check is passed Item is contained in their List and if so they will update the List xml from the Item data.
For that purpose there is a getListItemXmlNode method in the Tridion.ContentManager.Item class. This method will build List xml node based on the array of attributes, provided by getListItemXmlAttributes method on the Item. That's what Jaime mentioned in his answer.
If the List xml was updated, one of these events will be fired on List object:
itemadd
itemupdate
itemremove
Listening to these events on a List object in your view will allow you to timely update your List Control.
So if you want this mechanism to work with your extension, stick to these rules:
If you are creating new domain model List object - it should inherit Tridion.ContentManager.ListTcmItems class or it should implement the getId(), itemUpdated(item), itemsUpdated(item), itemRemoved(item) and updateItemData(item) methods
If you want to see changes in List control - attach handlers to corresponding events on the domain model List object and update your List control
If you are creating new domain model Item - it should inherit Tridion.ContentManager.Item class and you should improve getListItemXmlAttributes method to return correct array of attributes for the List
The CME will indeed update the items in the list dynamically after the save occurs, without going to the server.
To do so, it calls a method named "getListItemXml" which returns the update XML element for the list. It will then update or add this element, which will update or add the item in the list view.
getListItemXml is a method of the different Model objects.
So how do you take advantage of this? I'm not sure.
Perhaps you could overwrite the method (or maybe getListItemXmlAttributes is best) with your own to add the additional data?
There is also an "itemupdate" event fired whenever an item is updated in the list.
You can hook into that by doing something like this:
var myEventHandler = function(event)
{
$log.message("Item updated. TridionEvent object passed: " + event);
}
var view = $display.getView();
var list = view.getListObject("uri-of-Folder");
list.addEventListener("itemupdate", myEventHandler);
I suppose you could use that to update the list entry for the item after the fact.
Be sure to call removeEventHandler at some point too.
None of this is optimal, obviously.
But I don't know of any extension point that would solve this particular problem.
I think I would (attempt to) implement this by monitoring the items in a folder periodically and updating that list after this polling mechanism had detected a change in that folder.
For example, I would write some javascript timeout or interval that runs in the background and checks the items in the current folder. If it detects a change, it triggers the update of the list.
Alternatively, you could also try to intercept the action that changed your list (e.g. the creation of a new item), maybe by means of an event system, and as such update your list. I don't think this is much different than the first approach, as I think it still implies some level of polling from the GUI side.
On a form data source (SalesLine) I have a validateWrite method, which in turn calls the super() method to call the validateWrite method on the SalesLine table, amongst other checks.
In the SaleLine table I have custom functionality for recording, and sometime stopping, data changes.
I don't want this functionality to be triggered when I write to SalesLine from my new form. Therefore I want to check a condition, within the validateWrite method on the SalesLine table, to find out if the validateWrite was called form my new form. This will allow me to skip the data change recording/stopping if the SalesLine write was called from my new form.
What is the correct approach?
I could create a boolean recordSaveChecks and set it before calling SalesLine.write(), but is there a better way?
Edit: To clarify, I do not have form specific custom verification to add, I have a system-wide verification (therefore sits on the SaleLine Table), which needs to be skipped when called from from 1 specific form.
The best option may be to move the customization that is form specific onto the form's data source rather than on the table itself. But if you true want to add form-specific code to the table, you can see an example in Tables\Address.update(), where it checks this.dataSource().formRun().name() to determine if it has been called from the relevant form.
You could put your code on the SalesLine DataSource in the ValidateWrite() method, before the super call Something like this:
ret = YourCheckGoesHere;
if(ret)
{
ret = super();
}
else
{
info("Why validation failed goes here");
}
return ret;
Then you've implemented the validation logic into Table not into the Form because you need the validation to be system wide but you need to prevent this validation when Insert/Update the record.
I think you can by override write() method of Form DataSource and use SalesLine.doInsert(); and SalesLine.doUpdate();
I'm working on a Flex application that uses MVVM. I understand that it's recommended to have a 1-to-1 mapping between views and view-models. How then do I avoid performing the same calculations and storing the same data multiple times if it is needed in multiple views?
For example, imagine I have a program that can load project files consisting of a list of items. The main tab of my application has an export button that should only be enabled if the project is valid so I create an isExportEnabled property on the view-model for the main tab. The calculation for this involves validating the project by iterating though every item in the project and verifying some attribute on each. I have another tab which has a "Print Project Summary" button so the view-model for this tab has isPrintEnabled. The value for this flag is based on the same criteria, whether or not the project is valid.
I could put the logic for determining these values in their respective view-models, but then I end up calculating the same value twice when a project is loaded.
I could move isValid to the domain model but then the model becomes denormalized. What if a saved project is edited without updating the "isValid" flag?
I could make a global "Project View-Model" for the entire project which calculates whether the project is valid and then have isExportEnabled and isPrintEnabled delegate to that. However, then I end up with a hierarchy of view-models which this article recommends avoiding: Applying the Presentation Model in Flex
It feels like there is no correct way to do this.
So you are saying you have two user gestures, "Export" and "Print Project Summary" which are based on the same validation function ? If I read your post right then you can calculate it once and have two getters
private var _isValid:Boolean;
public function validate():Boolean
{
_isValid = //result of calculation
dispatchEvent( new Event("isValidChange") )
}
[Bindable(Event="isValidChange")]
public function get canExport():Boolean
{
return _isValid;
}
[Bindable(Event="isValidChange")]
public function get canPrint():Boolean
{
return _isValid;
}
Now some may say that since they are returning the same value that we should get rid of the getters and return a simple [Bindable] public value. But since this seems like the first implementation of this feature, having two separate functions allows you some robustness in the face of changing UI and validation requirements.
Personally, if the validation logic got too heavy, I would keep that value on my model "public var isValidProject" since the model should know if it is valid or not. Then the presentation layer would use that value to determine how to represent to the user that this is an invalid project (popups,alerts,error strings ).
In other cases, I would make a setter on the presenter of those buttons "set selectedProject" and run my validation and/or change my buttons enablement state there.
I would really be interested to hear others thoughts on this.
I have form with VendTable grid for example, which has CustAccount field.
I want to place button, click on which will open CustTable form where all customers are visible.
If I just put CustTable menuitem, then clicking on it will open CustTable form, but in this form only one record is displayed - one that has the same AccountNum as in vendTable.CustAccount.
How to open whole custTable? Is there better solution than create button, and then use ClassFactory::FormRunOnClient to display form?
PS. I need button, so RMB->"Go to the Main Table Form" doesn't count.
The problem is that the VendTable record is applied as an argument to the CustTable form, which then creates a dynalink. The solution is to avoid the argument.
Override the clicked method in the CustTable display menu item like this:
void clicked()
{
this.menufunction().run(new Args(element));
}
This calls the CustTable form with the caller object only and without the record argument.
I know this is a fairly old question but if someone comes here looking for the answer, just call method clearDynalinks() on the object QueryBuildDataSource.
For example, you have created a Form and it is automatically filtering your Datasource because of the Dynalinks that Dynamics creates automatically, you solve it by putting the following code inside the init() method, on your form Datasource:
QueryBuildDatasource qbds;
;
qbds = this.query().dataSourceTable(tablenum(MyTableName));
qbds.clearDynalinks();
// Next line is optional, it clears initial ranges
qbds.clearRanges();
// if you need to add any ranges you can do it right after you clear the initial dynalinks / ranges
Hope it helps...
You have 2 options, you can either create a button and override its clicked() method, or
use a MenuItemButton and assign an Action MenuItem to it.
Using MenuItems is a best practice, because it allows you to use the AX security & configuration framework. You can associate a class to the MenuItem and in the class' main() method you can run the FormRunOnClient() stuff as needed.