hi i have been searching and i cant find any tutorial about how to add a button in my view_form part of a custom module.
i wanted to add a button and make it call a method i made every time it is clicked.
in xml view form :
<label name="fieldstring"/>
<field name="fieldstring"/>
<button name="dosomething"/>
code:
def dosomething(cls,records):
#treatement
is there any example module that is using a button associated to a treatment??
In order to add a button to a view you have to make 3 steps:
Add the button to the _buttons dictionary of ModelView class. Normally this is done in the setup method of your class. Here you can define the icon and the states (when the button is invisible for example). If nothing needed you can define it with an empty dictionary.
For example:
#classmethod
def __setup__(cls):
super(Class, cls).__setup__()
cls._buttons.update({
'mybutton': {},
})
More complex examples can be found on tryton modules, for example:
http://hg.tryton.org/modules/account_invoice/file/84a41902ff5d/invoice.py#l224
Declare your method and decorate it with ModelView.button (in order to check access right to this button). For example:
#classmethod
#ModelView.button
def mybutton(cls, records)
#DO whatever you want with records
NOTE that the name method must be the one that you use as key of the _buttons dictionary in step1.
And finally add it to the view. You can find all the attributes that can be used on:
http://doc.tryton.org/3.2/trytond/doc/topics/views/index.html?highlight=button#button
Note that the string and name attributes are mandatory.
Also the name must be the name of the method to call, defined in step 2.
You can find some examples in:
http://hg.tryton.org/modules/account_invoice/file/84a41902ff5d/view/invoice_form.xml#l51
Related
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)
I have a task to export a repeat grid's content to Excel. I have read an
article, but I still can't realize how to properly use it. I tried to repeat article's steps to provide pzRDExportWrapper, but after I click "Save" button I get the error:
Method: Rule-Obj-Activity instance not found:
Sb-FW-CTrackFW-Work.pzRDExportWrapper. Details: Invalid value for
Activity name passed to ActivityAssembler.
Could anybody give me any suggestions? Thank you.
You invoke activity from another activity which applies to class Sb-FW-CTrackFW-Work. Rule Resolution use primary context Sb-FW-CTrackFW-Work class and try invoke activity pzRDExportWrapper from it and you get error (because rule resolution can't found invoked activity in this class).
Activity pzRDExportWrapper applies to Rule-Obj-Report-Definition class. Try invoke from it.
Try activity step as below:
Call Rule-Obj-Report-Definition.pzRDExportWrapper
Or use step page for this step which defined as applies to Rule-Obj-Report-Definition class(you can declare it on Pages&Classes tab)
Okay. I have resolved the issue (thank you njc). I have two sections on a lone web page.
A context of the first section is my custom data page Co-Name-FW-Data-Search. The Search page has some single value properties which are initialized by an user via an UI.
The second section is a repeat grid section, a report definition as a source. My Search page pointed out in a Pages and Classes tab. Also there is a Page, which is created by report definition and contains results. The report definition takes Search’s values as parameters.
So, I have created an activity and passed the Search page and a Cods-Pega-List MyResultList as parameters. There are some steps in the activity:
Check if Search is null. If true- skip step; else - transfer Search properties into Params props with Data Transform.
Set Param.exportmode = "excel"
Call pzRDExportWrapper with Step Page MyResultList.pyReportDefinition. Pass current parameter page.
P.S.: If it doesn’t work try to play with report definition’s settings.
P.P.S.: An only minus of pzExportWrapper is that it invokes report definition again.
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().
On the CustListPage I have added functionality on the ribbon which opens a popup/dialog form from where you can see related Customers.
How do I, on selection of one of the related customers on my dialog, jump to that customer on the listpage?
What I’ve tried so far:
I’ve added a method (lets call it setActiveRecord) on the CustTableListPageInteraction class that should set the activeRecord on the listpage to the record I’m passing to this method. I’m passing the CustTableListPageInteraction class to the dialog form, using a parm method. Now on my dialog form I can see that I receive the CustTableListPageInteraction class. However, I’m unable to execute my method setActiveRecord on the CustTableListPageInteraction, because I get a “object not initialized”.
What is the best route to follow to update a ListPage from a called object? Because the listpage is driven by the class, I find it very difficult to pass parameters back.
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.