I've created a simple ALV grid and populated the grid with data, now the grid is displayed after the selection screen. I'm not using custom container and display the grid in full screen.
Is there a property of ALV grid object that enables toolbar with buttons filter, sort, etc, that is normally on top of the grid?
So far this is what I have:
TRY.
cl_salv_table=>factory(
IMPORTING
r_salv_table = gr_alv
CHANGING
t_table = tbl_data
).
CATCH cx_salv_msg.
ENDTRY.
* initialize the alv settings - nothing done here for the moment.
PERFORM define_settings USING gr_alv.
* Display the ALV
gr_alv->display( ).
Each ALV function is implemented as a separate CLASS in Simple ALV, so you have to handle them separately. You do not need a custom control.
In order to add the toolbar:
data: lr_func TYPE REF TO CL_SALV_FUNCTIONS_LIST.
"Functions
lr_func = gr_alv->get_functions( ).
lr_func->set_all( ).
Complete ALV display:
form display_results.
data: ls_key type salv_s_layout_key,
lo_table type ref to cl_salv_table,
lo_cols type ref to cl_salv_columns_table,
lo_events type ref to cl_salv_events_table,
lo_funcs type ref to cl_salv_functions_list,
lo_layout type ref to cl_salv_layout,
lo_display type ref to cl_salv_display_settings,
lo_selections type ref to cl_salv_selections.
try.
call method cl_salv_table=>factory
exporting
list_display = abap_false
importing
r_salv_table = lo_table
changing
t_table = gt_list.
catch cx_salv_msg . "#EC NO_HANDLER
endtry.
"Events
create object go_events.
lo_events = lo_table->get_event( ).
set handler go_events->double_click for lo_events.
"Layouts
ls_key-report = sy-repid.
lo_layout = lo_table->get_layout( ).
lo_layout->set_key( ls_key ).
lo_layout->set_default( abap_true ).
lo_layout->set_save_restriction( ).
lo_layout->set_initial_layout( p_var ).
lo_cols = lo_table->get_columns( ).
perform change_columns changing lo_cols.
"Functions
lo_funcs = lo_table->get_functions( ).
lo_funcs->set_all( ).
"Display Settings
lo_display = lo_table->get_display_settings( ).
lo_display->set_striped_pattern( abap_true ).
"Selections
lo_selections = lo_table->get_selections( ).
lo_selections->set_selection_mode( if_salv_c_selection_mode=>row_column ).
lo_table->display( ).
endform. " DISPLAY_RESULTS
This is confusing at first when you use the ALV object model. If you use the ALV in fullscreen mode you have to reference a GUI status in your program, and use the method SET_SCREEN_STATUS on your grid instance. It's explained in the SAP Help here.
It helps to copy the GUI status SALV_TABLE_STANDARD from function group SALV_METADATA_STATUS into your report as a starting point, and then you can remove any functions you don't need. For example, if you copied the status into your program as ALV_STATUS, you would write:
gr_alv->set_screen_status( report = sy-repid
pfstatus = 'ALV_STATUS' ).
If you want to use the class-based model of setting up ALV functions, you have to embed the grid object in a custom container in a screen.
Seems what you need to do is get an instance of CL_SALV_FUNCTIONS_LIST from your grid object like so:
data: lr_func TYPE REF TO CL_SALV_FUNCTIONS_LIST.
lr_func = gr_alv->get_functions( ).
lr_func->set_all( ).
But, from there, it seems you need to do a bit or work. My advice: Look at the documentation on classes CL_SALV_TABLE and CL_SALV_FUNCTIONS_LIST (that is, click the documentation button when you display the class in transaction SE24). The latter tells you exactly what you need to do.
(Also, a little hint: Put your processing logic inside the try-catch block, because if the initialization fails, you might catch that exception but go on to try call a method on an uninstantiated or uninitialized class).
add a customer container to your gui
create an object of the class cl_gui_custom_container and supply the name of your container
create an instance of the class cl_gui_alv_grid and supply the custom container object
use the method set_table_for_first_display
this will display a toolbar with all buttons. you can control which buttons you want in the toolbar with the IT_TOOLBAR_EXCLUDING parameter to the set_table_for_first_display method.
Related
See Datasource Paging Issue (Revised)
for the original question.
Markus, you were kind enough to help with out with the issue of incorporating a record count into a query using a calculated datasource. I have a search form with 15 widgets - a mix of date ranges, dropdowns, text values and ._contains, ._equals, ._greaterThanOrEquals, ._lessThanOrEquals, etc.
I have tested this extensively against mySQL SQL code and it works fine.
I have now added a 16th parameter PropertyNames, which is a list with binding #datasource.query.filters.Property.PropertyName._in and Options blank. The widget on the form is hidden because it is only used for additional filtering.
Logic such as the following is used, such that a particular logged-in user can only view their own properties. So if they perform a search and the Property is not specified we do:-
if (params.param_Property === null && canViewAllRecords === false) {
console.log(params.param_PropertyNames); // correct output
ds.filters.Property.PropertyName._in = params.param_PropertyNames;
}
The record count (records.length) is correct, and if I for loop through the array of records the record set is correct.
However, on the results page the table displays a larger resultset which omits the PropertyNames filter. So if I was to search on Status 'Open' (mySQL results 50) and then I add a single value ['Property Name London SW45'] for params.param_PropertyNames the record count is 6, the records array is 6 but the datasource display is 50. So the datasource is not filtering on the property array.
Initially I tried without adding the additional parameter and form widget and just using code such as
if (params.param_Property === null && canViewAllRecords === false) {
console.log(params.param_PropertyNames); // correct output
ds.filters.Property.PropertyName._in = properties; // an array of
properties to filter out
}
But this didn't work, hence the idea of adding a form widget and an additional parameter to the calculated recordcount datasource.
If I inspect at query.parameters then I see:-
"param_Status": "Open",
"param_PropertyNames": ["Property Name London SW45"],
If I inspect query.filters:-
name=param_Status, value=Open
name=param_PropertyNames, value=[]}]}
It looks as though the filter isn't set. Even hard coding
ds.filters.Property.PropertyName._in = ['Property Name London SW45'],
I get the same reuslt.
Have you got any idea what would be causing this issue and what I can do for a workaround ?
Using a server side solution I would suggest editing both your SQL datasource query script (server side) that is supposed to filter by this property list and including the same code in your server side script for your calculated Count datasource. The code would look something like this, not knowing your exact details:
var subquery = app.models.Directory.newQuery();
subquery.filters.PrimaryEmail._equals = Session.getActiveUser().getEmail();
subquery.prefetch.Property._add();
var results = subquery.run();
if(!results[0].CanViewAllRecords) {
query.filters.Property.PropertyName._in = results[0].Property.map(function(i) {return i.PropertyName;});
}
By adding this code you are filtering your directory by your current user and prefetching the Property relation table, then you set the filter only if your user canviewallRecords is false and use JS map function to create an array of the PropertyName field in the Property table. As I stated, your code may not be exactly the same depending on how you have to retrieve your user canviewallrecords property and then of course I don't know your relation between user and Property table either, is it one-to-many or other. But this should give you an idea how to implement this on server side.
I attempt to create a custom field for AEM form (AEM6.0 SP3) following how text field does it: /libs/fd/af/components/guidetextbox
I created init.jsp and widget.jsp with same content.
On widget.jsp, I then add some jQuery to autopopulate text field on focus out.
<script>
var thisField = '${guideid}${'_widget'}';
$(thisField).focusout(function() {
$(this).val('date ' + new Date());
});
</script>
On focus in, I type text 'ABC' then when focus-out I get text 'date ' however when submitting the data, text 'ABC' gets submitted.
Is there any AEM API I need to invoke (instead of just jQuery .val() function) in order for the changes to be recorded ?
Not the best solution, but we managed to get it work by first calling focus(), eg.
$(this).focus().val('date ' + new Date());
Better solution:
create a custom function eg
function initDatePicker(thisObj) { $(this).focusout(function() {thisObj.value = $(this).val();});}
Update .content.xml to call this within initScript eg.
<cq:template guideNodeClass="guideTextBox" jcr:primaryType="nt:unstructured" jcr:title="Datepicker input field" initScript="initDatePicker(this)"/>
the function will be immediately included when the widget is added to canvas.
In AEM Forms a Javascript model is maintained that stores the value and that model is used to submit the data. Now to pass on the value from the ui to the model XFA_EXIT_EVENT[1] has to be triggered. So after setting the value you must add this line of code to persist the value
$(this).trigger(xfalib.ut.XfaUtil.prototype.XFA_EXIT_EVENT)
Also a better way would be to create your own widget for this specific scenario. See [2] for more details. The article is for AEM Form 6.1 but it will work for AEM 6.0 as well.
[1] https://helpx.adobe.com/aem-forms/6/html5-forms/introduction-widgets.html
[2] https://helpx.adobe.com/aem-forms/6-1/custom-appearance-widget-adaptive-form.html
I have the following code in the init() of a report:
QueryBuildDataSource qbdsTable;
QueryOrderByField QueryOrderByFieldTransDate;
QueryOrderByField QueryOrderByFieldDimZone
QueryOrderByField QueryOrderByFieldDimCC;
;
super();
qbdsTable = query.dataSourceTable(tableNum(Table));
QueryOrderByFieldTransDate = qbdsTable.addOrderByField(fieldNum(Table, TransDate));
QueryOrderByFieldTransDate.autoSum(true);
QueryOrderByFieldDimZone = qbdsTable.addOrderByField(fieldNum(Table, DimZone),SortOrder::Descending);
QueryOrderByFieldDimZone.autoSum(true);
QueryOrderByFieldDimCC = qbdsTable.addOrderByField(fieldNum(Table, DimCostCenter));
QueryOrderByFieldDimCC.autoSum(true);
and the autosum property is functioning properly (I have set the SumAll property for the field I use to calculate these subtotals).
The problem is that, whenever I try to add an groupBy field or a selection field, the autosum property isn't honored anymore (the subtotals are not displayed anymore):
qbdsTable.addSelectionField(fieldNum(Table, AmountMST), selectionField::Sum);
or
qbdsTable.addGroupByField(fieldNum(Table, TransDate));
I have tried to use:
qbdsTable.addSortField(fieldNum(Table, TransDate));
qbdsTable.autoHeader(1, true);
but I have the same problem
Does anyone has an Idea how I can use both autosum and addGroupByField on the same datasorce of a report?
For historical reasons old style AX reports behaves differently when called directly (run on the report node) or through on a report menu item.
The execution order of the first is:
init
fetch
dialog
The second runs via class RunbaseReportStd in the following order:
init
dialog
fetch
This matters because you have change the query after the user has made any changes.
So move your code changes from init to fetch, like this:
public boolean fetch()
{
QueryBuildDataSource qbdsCustTrans = query.dataSourceTable(tableNum(CustTrans));
;
qbdsCustTrans.addSelectionField(fieldNum(CustTrans, AmountMST), selectionField::Sum);
qbdsCustTrans.addGroupByField(fieldNum(CustTrans, AccountNum));
qbdsCustTrans.addGroupByField(fieldNum(CustTrans, TransDate));
qbdsCustTrans.addGroupByField(fieldNum(CustTrans, CurrencyCode));
//info(qbdsCustTrans.toString());
return super();
}
This will only work, if called through the menu item.
Also, I could not get the auto-sum functionality to work, when added by code.
Instead you will have to add the order by and autosum using Sorting node of the report query.
I don't know why, but maybe this is because you use auto design, which is generated at run time.
I'm writing a GUI extension and using the Anquilla framework to get a list of Keywords within a Category. I'm obtaining an XML document for the list of keywords then working with that document within my extension.
My problem is that the returned XML doesn't contain the Keyword's 'Description' value. I have the Title and Key etc.
My original code looks like this:
var category = $models.getItem("CATEGORYTCMID:);
var list = category.getListKeywords();
list.getXml();
A typical node returned is this:
<tcm:Item ID="tcm:4-1749-1024"
Type="1024" Title="rate_one" Lock="0" IsRoot="true"
Modified="2012-12-17T23:01:59" FromPub="010 Schema"
Key="rate_one_value" IsAbstract="false"
CategoryTitle="TagSelector"
CategoryID="tcm:4-469-512" Icon="T1024L0P0"
Allow="268560384" Deny="96" IsNew="false"
Managed="1024"/></tcm:ListKeywords>
So I've tried using a Filter to give me additional column information:
var filter = new Tridion.ContentManager.ListFilter();
filter.columns = Tridion.Constants.ColumnFilter.EXTENDED;
var list = category.getListKeywords(filter);
Unfortunately this only gives the additional XML attributes:
IsShared="true" IsLocalized="false"
I'd really like the description value to be part of this XML without having to create a Keyword object from the XML. Is such a thing possible?
cough any ideas? cough
I'm afraid you'll have to load the Keyword itself to get the Description.
It's not used in any lists, so it's not returned in the XML.
You could always create a List Extender to add this information to the list, but try to be smart about it since this extender will execute everytime a GetList is called.
Won't save you from having to open every keyword in the list, but you'll be doing it server-side (with Core Service/NetTcp for instance) which will probably be easier and faster than opening each keyword with Anguilla.
In this instance I only need the one keyword, so I simply get it from the CMS. Getting an object in Anguilla is a bit weird, here's the code:
In your main code area:
var selectedKy = $models.getItem("TcmUriOfKeywordHere");
if (selectedKy.isLoaded()) {
p.selectedKy = selectedKy;
this.onselectedKyLoaded();
} else {
$evt.addEventHandler(selectedKy, "load", this.onselectedKyLoaded);
selectedKy.load();
}
It's worth noting how I store the keyword in the properties of the item, so I can obtain it in the onselectedKyLoaded function
The function called once the item is loaded
ContentBloom.ExampleGuiExtension.prototype.onselectedKyLoaded = function (event) {
var p = this.properties;
var selectedDescription = p.selectedKy.getDescription();
// do what you need to do with the description :)
};
I resolved this, thanks to the answer here: https://stackoverflow.com/a/12805939/1221032 - Cheers Nuno :)
I am putting together a functional design for a site which will aims use the Tridion 2012 UI/XM to manage pages. There are 2 regions on the page, a main content area on the left and a side-bar on the right. Ideally users should be able to drag and drop content into and within these regions. In an ideal world I would like to define the regions along the lines of
Side Bar: any CP for which the CT has the text 'Right' in it.
Main: all other CPs
Looking at the documentation it seems that you need to explicitly use CT/Schema ID pairs to define regions. Is there any possibility to do this in any other way?
At the very least I would like to be able to define that the side bar allows a certain fixed set of CT/Schema ID pairs, but have the main region as a catchall bucket.. Is this possible?
It is also possible that the Side Bar is split into 2 regions, above and below an advertisement. Both regions should allow the same types of CP - as far as I understand this is not possible - is this correct? Are there any ideas for workarounds?
To configure regions that take all Content Types, You need to get Publication AppData and loop through the content types and build your json markup for enabling this.You could write C# TBB which includes on each page template and does this logic, you can define some metadata at CT level which determines which region it will go in and build the Region JSON markup.
Below is the snippet to get all Component types to add in one region. You could change the logic to get only right just by checking the template name.
// get the publication from the engine -- using TemplateBase Util..
Publication thisPub = GetPublication();
XmlElement seAppdata = thisPub.LoadApplicationData("SiteEdit").GetAs<XmlElement>();
XmlNamespaceManager seNsMgr = new XmlNamespaceManager(new NameTable());
seNsMgr.AddNamespace("se", "http://www.sdltridion.com/2011/SiteEdit");
seNsMgr.AddNamespace("xlink", "http://www.w3.org/1999/xlink");
XmlNodeList contentTypes = (XmlNodeList)seAppdata.SelectNodes("//se:ContentTypes/se:ContentType", seNsMgr);
List<String> contentTypeJson = new List<String>();
foreach (XmlNode contentType in contentTypes)
{
string templateId = contentType.SelectSingleNode("se:ComponentTemplate/#xlink:href", seNsMgr).Value;
string componentId = contentType.SelectSingleNode("se:Component/#xlink:href", seNsMgr).Value;
Component thisSchema = (Component)engine.GetObject(componentId);
string schemaId = thisSchema.Schema.Id;
// Add json formated string for Content Types
contentTypeJson.Add(string.Format("{{schema: \"{0}\", template: \"{1}\"}}", schemaId, templateId));
}
// Final Markup - JSON
String allRegionSeText = string.Format("<!-- Start Region: {{title: \"All Region\", allowedComponentTypes: [{0}], minOccurs: 1, maxOccurs: 5 }} -->", string.Join(",", contentTypeJson.ToArray()));
// Push to the package to use in DWT..
package.PushItem("ALL_REGION", package.CreateStringItem(ContentType.Text, allRegionSeText));
Hope this helps.
Have you tried creating a region without specifying CT/Schema pairs? I remember seeing in an early implementation that you could drop a content type anywhere because regions hadn't been configured properly (or perhaps at all).