Jump to content
JasperReports Library 7.0 is now available ×
  • This documentation is an older version of JasperReports® IO User Guide. View the latest documentation.

    Most reports rendered by the JasperReports IO service have interactive abilities such as column sorting provided by a feature called JIVE: Jaspersoft Interactive Viewer and Editor. The JIVE UI is the interface of the report viewer which can be implemented in client applications using the JasperReports IO JavaScript API.

    Not only does the JIVE UI allow users to sort and filter regular reports, it also provides many opportunities for you to further customize the appearance and behavior of your reports through the JasperReports IO JavaScript API.

    This chapter contains the following sections:

    Interacting With JIVE UI Components
    Using Floating Headers
    Changing the Chart Type
    Changing the Chart Properties
    Undo and Redo Actions
    Sorting Table Columns
    Filtering Table Columns
    Formatting Table Columns
    Conditional Formatting on Table Columns
    Sorting Crosstab Columns
    Sorting Crosstab Rows
    Implementing Search in Reports
    Providing Bookmarks in Reports
    Disabling the JIVE UI

    Interacting With JIVE UI Components

    The JasperReports IO report interface exposes the updateComponent function that gives your script access to the JIVE UI. Using the updateComponent function, you can programmatically interact with the JIVE UI to do such things as set the sort order on a specified column, add a filter, and change the chart type. In addition, the undoAll function acts as a reset.

    For the API reference of the JasperReports IO report interface, see Report Functions.

    First, your script must enable the default JIVE UI to make its components available after running a report:

    var report = jrioClient.report({    resource: "/samples/reports/TableReport",    defaultJiveUi : {        enabled: true    }});...var components = report.data().components;[/code]                    

    The components that can be modified are columns and charts. These components of the JIVE UI have an ID, but it may change from execution to execution. To refer to these components, create your report in JRXML and use the net.sf.jasperreports.components.name property to name them. In the case of a column, this property should be set on the column definition in the table model. In Jaspersoft Studio, you can select the column in the Outline View, then go to Properties > Advanced, and under Misc > Properties you can define custom properties.

    Then you can reference the component by this name, for example a column named sales, and use the updateComponent function to modify it.

    Or:

    We can also get an object that represents the named component of the JIVE UI:

    This example assumes you have a report whose components already have names, in this case, columns named ORDERID and SHIPNAME:

    jrio.config({...});jrio(function(jrioClient) {	//render report from provided resource	var report = jrioClient.report({		resource: "/samples/reports/OrdersTable",		container: "#reportContainer",		success: printComponentsNames,		error: handleError	});		  	$("#resetAll").on("click", function() {		report.undoAll();	});		  	$("#changeOrders").on("click", function() {		report.updateComponent("ORDERID", {			sort: {				order: "asc"			},			filter: {				operator: "greater_or_equal",				value: 10900			}		}).fail(handleError);	});		  	$("#sortCustomers").on("click", function() {		report.updateComponent("SHIPNAME", {			sort: {				order: "desc"			}		}).fail(handleError);	});		  	//show error		  	function handleError(err) {		alert(err.message);	}	function printComponentsNames(data) {		data.components.forEach(function(c) {			console.log("Component Name: " + c.name, "Component Label: " + c.label);		});	}});[/code]                    

    The associated HTML has buttons that will invoke the JavaScript actions on the JIVE UI:

    <!-- Provide the URL to jrio.js --><script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script><button id="resetAll">Reset All</button><button id="changeOrders">View Top Orders</button><button id="sortCustomers">Sort Customers</button><!-- Provide a container for the report --><div id="reportContainer"></div>[/code]                    

    Using Floating Headers

    One feature of the JIVE UI for tables and crosstabs is the floating header. When you turn on floating headers, the header rows of a table or crosstab float at the top of the container when you scroll down. The report container must allow scrolling for this to take effect. This means that CSS property overflow with values like scroll or auto must be specifically set for the report container.

    To turn on floating headers for your interactive reports, set the following parameters when you enable the JIVE UI:

    var report = jrioClient.report({    resource: "/samples/reports/TableReport",    defaultJiveUi : {        floatingTableHeadersEnabled: true,        floatingCrosstabHeadersEnabled: true    }});[/code]                    

    Changing the Chart Type

    If you have the name of a chart component, you can easily set a new chart type and redraw the chart.

    Or:

    The following example creates a drop-down menu that lets users change the chart type. You could also set the chart type according to other states in your client.

    This code also relies on the report.chart.types interface described in JavaScript API Reference - report.

    As shown in the following HTML, the control for the chart type is created dynamically by the JavaScript:

    Changing the Chart Properties

    Those chart components that are based on Highcharts have a lot of interactivity such as built-in zooming and animation. The built-in zooming lets users select data, for example columns in a chart, but it can also interfere with touch interfaces. With the JasperReports IO JavaScript API, you have full control over these features and you can choose to allow your users access to them or not. For example, animation can be slow on mobile devices, so you could turn off both zooming and animation. Alternatively, if your users have a range of mobile devices, tablets, and desktop computers, then you could give users the choice of turning on or off these properties themselves.

    The following example creates buttons to toggle several chart properties and demonstrates how to control them programmatically. First the HTML to create the buttons:

    Here are the API calls to set the various chart properties:

    Undo and Redo Actions

    The JIVE UI supports undo and redo actions that you can access programmatically with the JasperReports IO JavaScript API. As in many applications, undo and redo actions act like a stack, and the canUndo and canRedo events notify your page you are at either end of the stack.

    jrio.config({    ...});jrio(function(jrioClient) {     var chartComponent,        report = jrioClient.report({            resource: "/samples/reports/highcharts/HighchartsChart",            container: "#reportContainer",            events: {                canUndo: function(canUndo) {                    if (canUndo) {                        $("#undo, #undoAll").removeAttr("disabled");                    } else {                        $("#undo, #undoAll").attr("disabled", "disabled");                    }                },                canRedo: function(canRedo) {                    if (canRedo) {                        $("#redo").removeAttr("disabled");                    } else {                        $("#redo").attr("disabled", "disabled");                    }                }            },            success: function(data) {                chartComponent = data.components.pop();                $("option[value='" + chartComponent.chartType + "']").attr("selected", "selected");            }        });    var chartTypeSelect = buildChartTypeSelect(jrioClient.report);    chartTypeSelect.on("change", function() {							        report.updateComponent(chartComponent.id, {            chartType: $(this).val()        })            .done(function(component) {            chartComponent = component;	     console.log("ttttt:" + $(this).val());        })            .fail(function(error) {            console.log(error);            alert(error);        });    });     $("#undo").on("click", function() {        report.undo().fail(function(err) {            alert(err);        });    });     $("#redo").on("click", function() {        report.redo().fail(function(err) {            alert(err);        });    });     $("#undoAll").on("click", function () {        report.undoAll().fail(function (err) {            alert(err);        });    });});function buildChartTypeSelect(report) {    chartTypeSelect = $("#chartType");    var chartTypes = report.chart.types;    chartTypeSelect = $("#chartType");    $.each(chartTypes, function (index, type) {        chartTypeSelect.append("<option value="" + type + "">" + type + "</option>");    });    return chartTypeSelect;}[/code]                    

    Associated HTML:

    Sorting Table Columns

    This code example shows how to set the three possible sorting orders on a column in the JIVE UI: ascending, descending, and no sorting.

    jrio.config({    ...});jrio(function(jrioClient) {     var report = jrioClient.report({        resource:"/samples/reports/TableReport",        container: "#reportContainer",        error: showError    });     $("#sortAsc").on("click", function () {        report.updateComponent("name", {            sort: {                order: "asc"            }        })        .fail(showError);    });     $("#sortDesc").on("click", function () {        report.updateComponent("name", {            sort: {                order: "desc"            }        })        .fail(showError);    });     $("#sortNone").on("click", function () {        report.updateComponent("name", {            sort: {}        }).fail(showError);    });     function showError(err) {        alert(err);    }});[/code]                    

    Associated HTML:

    Filtering Table Columns

    This code example shows how to define filters on columns of various data types (dates, strings, numeric) in the JIVE UI. It also shows several filter operator such as equal, greater, between, contain (for string matching), and before (for times and dates).

    jrio.config({    ...});jrio(function(jrioClient) {     var report = jrioClient.report({                       resource:"/samples/reports/OrdersTable",            container: "#reportContainer",            error: function(err) {                alert(err);            }        });     $("#setTimestampRange").on("click", function() {        report.updateComponent("ORDERDATE", {             filter: {                operator: "between",                value: [$("#betweenDates1").val(), $("#betweenDates2").val()]            }        }).fail(handleError);    });    $("#resetTimestampFilter").on("click", function() {        report.updateComponent("ORDERDATE", {             filter: {}        }).fail(handleError);    });     $("#setStringContains").on("click", function() {        report.updateComponent("SHIPNAME", {             filter: {                operator: "contain",                value: $("#stringContains").val()            }        }).fail(handleError);    });     $("#resetString").on("click", function() {        report.updateComponent("SHIPNAME", {             filter: {}        }).fail(handleError);    });     $("#setNumericGreater").on("click", function() {        report.updateComponent("ORDERID", {             filter: {                operator: "greater",                value: parseFloat($("#numericGreater").val(), 10)            }        }).fail(handleError);    });     $("#resetNumeric").on("click", function() {        report.updateComponent("ORDERID", {             filter: {}        }).fail(handleError);    }); }); function handleError(err) {      console.log(err);      alert(err); }[/code]                    

    Associated HTML:

    <script src="http://code.jquery.com/jquery-2.1.0.js"></script><script src="http://underscorejs.org/underscore-min.js"></script><!-- Provide the URL to jrio.js --><script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script> <input type="text" value="1997-01-10T00:00:00" id="betweenDates1"/> -<input type="text" id="betweenDates2" value="1997-10-24T00:00:00"/> <button id="setTimestampRange">Set timestamp range</button><button id="resetTimestampFilter">Reset timestamp filter</button><br/><br/><input type="text" value="ctu" id="stringContains"/><button id="setStringContains">Set string column contains</button><button id="resetString">Reset string filter</button> <br/><br/><input type="text" value="10500" id="numericGreater"/><button id="setNumericGreater">Set numeric column greater than</button><button id="resetNumeric">Reset numeric filter</button> <!-- Provide a container for the report --><div id="reportContainer"></div>[/code]                    

    Formatting Table Columns

    The JIVE UI allows you to format columns by setting the alignment, color, font, size, and background of text in both headings and cells. You can also set the numeric format of cells, such as the precision, negative indicator, and currency.

    jrio.config({    ...});jrio(function(jrioClient) {  var columns,    report = jrioClient.report({      resource: "/samples/reports/TableReport",      container: "#reportContainer",      events: {        reportCompleted: function(status, error) {          if (status === "ready") {            columns = _.filter(report.data().components, function(component) {              return component.componentType == "tableColumn";            });            var column4 = columns[4];            $("#label").val(column4.label);            $("#headingFormatAlign").val(column4.headingFormat.align);            $("#headingFormatBgColor").val(column4.headingFormat.backgroundColor);            $("#headingFormatFontSize").val(column4.headingFormat.font.size);            $("#headingFormatFontColor").val(column4.headingFormat.font.color);            $("#headingFormatFontName").val(column4.headingFormat.font.name);[/code]                    
                if (column4.headingFormat.font.bold) {              $("#headingFormatFontBold").attr("checked", "checked");            } else {              $("#headingFormatFontBold").removeAttr("checked");            }            if (column4.headingFormat.font.italic) {              $("#headingFormatFontItalic").attr("checked", "checked");            } else {              $("#headingFormatFontItalic").removeAttr("checked");            }            if (column4.headingFormat.font.underline) {              $("#headingFormatFontUnderline").attr("checked", "checked");            } else {              $("#headingFormatFontUnderline").removeAttr("checked");            }            $("#detailsRowFormatAlign").val(column4.detailsRowFormat.align);            $("#detailsRowFormatBgColor").val(column4.detailsRowFormat.backgroundColor);            $("#detailsRowFormatFontSize").val(column4.detailsRowFormat.font.size);            $("#detailsRowFormatFontColor").val(column4.detailsRowFormat.font.color);            $("#detailsRowFormatFontName").val(column4.detailsRowFormat.font.name);            if (column4.detailsRowFormat.font.bold) {              $("#detailsRowFormatFontBold").attr("checked", "checked");            } else {              $("#detailsRowFormatFontBold").removeAttr("checked");            }            if (column4.detailsRowFormat.font.italic) {              $("#detailsRowFormatFontItalic").attr("checked", "checked");            } else {              $("#detailsRowFormatFontItalic").removeAttr("checked");            }            if (column4.detailsRowFormat.font.underline) {              $("#detailsRowFormatFontUnderline").attr("checked", "checked");            } else {              $("#detailsRowFormatFontUnderline").removeAttr("checked");            }          }        }      },      error: function(err) {        alert(err);      }    });   $("#changeHeadingFormat").on("click", function() {    report.updateComponent(columns[4].id, {      headingFormat: {        align: $("#headingFormatAlign").val(),        backgroundColor: $("#headingFormatBgColor").val(),        font: {          size: parseFloat($("#headingFormatFontSize").val()),          color: $("#headingFormatFontColor").val(),          underline: $("#headingFormatFontUnderline").is(":checked"),          bold: $("#headingFormatFontBold").is(":checked"),          italic: $("#headingFormatFontItalic").is(":checked"),          name: $("#headingFormatFontName").val()        }						}[/code]                    
        }).fail(function(e) {      alert(e);    });  });  $("#changeDetailsRowFormat").on("click", function() {    report.updateComponent(columns[4].id, {      detailsRowFormat: {        align: $("#detailsRowFormatAlign").val(),        backgroundColor: $("#detailsRowFormatBgColor").val(),        font: {          size: parseFloat($("#detailsRowFormatFontSize").val()),          color: $("#detailsRowFormatFontColor").val(),          underline: $("#detailsRowFormatFontUnderline").is(":checked"),          bold: $("#detailsRowFormatFontBold").is(":checked"),          italic: $("#detailsRowFormatFontItalic").is(":checked"),          name: $("#detailsRowFormatFontName").val()        }      }    }).fail(function(e) {      alert(e);    });  });  $("#changeLabel").on("click", function() {    report.updateComponent(columns[4].id, {      label: $("#label").val()    }).fail(function(e) {      alert(e);    });  });});[/code]                    

    The associated HTML has static controls for selecting all the formatting options that the script above can modify in the report.

    <script src="http://code.jquery.com/jquery-2.1.0.js"></script><script src="http://underscorejs.org/underscore-min.js"></script><!-- Provide the URL to jrio.js --><script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script> <div >    <h3>Heading format for 5th column</h3>    Align:  <select id="headingFormatAlign">                <option value="left">left</option>                <option value="center">center</option>                <option value="right">right</option></select>    <br/>    Background color: <input type="text" id="headingFormatBgColor" value=""/>    <br/>    Font size: <input type="text" id="headingFormatFontSize" value=""/>    <br/>    Font color: <input type="text" id="headingFormatFontColor" value=""/>    <br/>    Font name: <input type="text" id="headingFormatFontName" value=""/>    <br/>    Bold: <input type="checkbox" id="headingFormatFontBold" value="true"/>    <br/>    Italic: <input type="checkbox" id="headingFormatFontItalic" value="true"/>    <br/>    Underline: <input type="checkbox" id="headingFormatFontUnderline" value="true"/>    <br/><br/>    <button id="changeHeadingFormat">Change heading format</button></div><div >    <h3>Details row format for 5th column</h3>    Align: <select id="detailsRowFormatAlign">        <option value="left">left</option>        <option value="center">center</option>        <option value="right">right</option></select>    <br/>    Background color: <input type="text" id="detailsRowFormatBgColor" value=""/>    <br/>    Font size: <input type="text" id="detailsRowFormatFontSize" value=""/>    <br/>    Font color: <input type="text" id="detailsRowFormatFontColor" value=""/>    <br/>    Font name: <input type="text" id="detailsRowFormatFontName" value=""/>    <br/>    Bold: <input type="checkbox" id="detailsRowFormatFontBold" value="true"/>    <br/>    Italic: <input type="checkbox" id="detailsRowFormatFontItalic" value="true"/>    <br/>    Underline: <input type="checkbox" id="detailsRowFormatFontUnderline" value="true"/>    <br/><br/>    <button id="changeDetailsRowFormat">Change details row format</button></div><div >    <h3>Change label of 5th column</h3>    <br/>    Label <input type="text" id="label"/>    <br/>    <button id="changeLabel">Change label</button></div><div ></div> <!-- Provide a container for the report --><div id="reportContainer"></div>[/code]                    

    Conditional Formatting on Table Columns

    The JIVE UI also supports conditional formatting so that you can change the appearance of a cell's contents based on its value. This example highlights cells in a given column that have a certain value by changing their text color and the cell background color. Note that the column name must be known ahead of time, for example by looking at your JRXML.

    jrio.config({    ...});jrio(function(jrioClient) {    // column name from JRXML (field name by default)    var report = jrioClient.report({            resource: "/samples/reports/OrdersTable",            container: "#reportContainer",            error: showError        });     $("#changeConditions").on("click", function() {            report.updateComponent("ORDERID", {                conditions: [                    {                        operator: "greater",                        value: 10500,                        backgroundColor: null,                        font: {                            color: "FF0000",                            bold: true,                            underline: true,                            italic: true                        }                    },                    {                        operator: "between",                        value: [10900, 11000],                        backgroundColor: "00FF00",                        font: {                            color: "0000FF"                        }                    }                ]            })            .then(printConditions)            .fail(showError);    });     function printConditions(component){        console.log("Conditions: "+ component.conditions);    }     function showError(err) {           alert(err);    }});[/code]                    

    This example has a single button that allows the user to apply the conditional formatting when the report is loaded:

    <script src="http://code.jquery.com/jquery-2.1.0.js"></script><script src="http://underscorejs.org/underscore-min.js"></script><!-- Provide the URL to jrio.js --><script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script> <button id="changeConditions">Change conditions for numeric column</button> <!-- Provide a container for the report --><div id="reportContainer"></div>[/code]                    

    Sorting Crosstab Columns

    Crosstabs are more complex and do not have as many formatting options. This example shows how to sort the values in a given column of a crosstab (the rows are rearranged). Note that the code is slightly different than Sorting Table Columns.

    jrio.config({    ...});jrio(function(jrioClient) {     var column2,    report = jrioClient.report({        resource: "/samples/reports/crosstabs/OrdersReport",        container: "#reportContainer",        events: {            reportCompleted: function(status, error) {                if (status === "ready") {                    var columns = _.filter(report.data().components, function(component) {                        return component.componentType == "crosstabDataColumn";                    });                     column2 = columns[1];                    console.log(columns);                }            }        },        error: function(err) {            alert(err);        }    });     $("#sortAsc").on("click", function () {        report.updateComponent(column2.id, {            sort: {                order: "asc"            }        }).fail(function(e) {            alert(e);        });    });     $("#sortDesc").on("click", function() {        report.updateComponent(column2.id, {            sort: {                order: "desc"            }        }).fail(function(e) {            alert(e);        });    });     $("#sortNone").on("click", function() {        report.updateComponent(column2.id, {            sort: {}        }).fail(function(e) {            alert(e);        });    });});[/code]                    

    The associated HTML has the buttons to trigger the sorting:

    <script src="http://code.jquery.com/jquery-2.1.0.js"></script><script src="http://underscorejs.org/underscore-min.js"></script><!-- Provide the URL to jrio.js --><script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script> <button id="sortAsc">Sort 2nd column ascending</button><button id="sortDesc">Sort 2nd column descending</button><button id="sortNone">Do not sort on 2nd column</button> <!-- Provide a container for the report --><div id="reportContainer"></div>[/code]                    

    Sorting Crosstab Rows

    This example shows how to sort the values in a given row of a crosstab (the columns are rearranged).

    jrio.config({    ...});jrio(function(jrioClient) {     var row,    report = jrioClient.report({        resource: "/samples/reports/crosstabs/OrdersReport",        container: "#reportContainer",        events: {            reportCompleted: function(status, error) {                if (status === "ready") {                    row = _.filter(report.data().components, function(component) {                        return component.componentType == "crosstabRowGroup";                    })[0];                }            }        },        error: function(err) {            alert(err);        }    });[/code]
        $("#sortAsc").on("click", function() {        report.updateComponent(row.id, {            sort: {                order: "asc"            }        }).fail(function(e) {            alert(e);        });    });     $("#sortDesc").on("click", function() {        report.updateComponent(row.id, {            sort: {                order: "desc"            }        }).fail(function (e) {            alert(e);        });    });     $("#sortNone").on("click", function () {        report.updateComponent(row.id, {            sort: {}        }).fail(function(e) {            alert(e);        });    });});[/code]                    

    The associated HTML has the buttons to trigger the sorting:

    <script src="http://code.jquery.com/jquery-2.1.0.js"></script><script src="http://underscorejs.org/underscore-min.js"></script><!-- Provide the URL to jrio.js --><script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script> <button id="sortAsc">Sort rows ascending</button><button id="sortDesc">Sort rows descending</button><button id="sortNone">Do not sort rows</button> <!-- Provide a container for the report --><div id="reportContainer"></div>[/code]                    

    Implementing Search in Reports

    The JIVE UI supports a search capability within the report. The following example relies on a page with a simple search input.

    <input id="search-query" type="input" /><button id="search-button">Search</button><!--Provide container to render your visualization--><div id="reportContainer"></div>[/code]                    

    Then you can use the search function to return a list of matches in the report. In this example, the search button triggers the function and passes the search term. It uses the console to display the results, but you can use them to locate the search term in a paginated report.

    jrio.config({    ...});jrio(function(jrioClient) {      //render report from provided resource    var report = jrioClient.report({        resource: "/samples/reports/TableReport",        error: handleError,        container: "#reportContainer"    });     $("#search-button").click(function(){        report        .search($("#search-query").val())            .done(function(results){                 !results.length && console.log("The search did not return any results!");                for (var i = 0; i < results.length; i++) {                    console.log("found " + results[i].hitCount + " results on page: #" +                                results[i].page);                }            })            .fail(handleError);    });      //show error    function handleError(err){        alert(err.message);    } });[/code]                    

    The search function supports several arguments to refine the search:

    Providing Bookmarks in Reports

    The JIVE UI also supports bookmarks that are embedded within the report. You must create your report with bookmarks, but then the JasperReports IO JavaScript API can make them available on your page. The following example has a container for the bookmarks and one for the report:

    <div>    <h4>Bookmarks</h4>    <div id="bookmarksContainer"></div></div><!--Provide container to render your visualization--><div id="reportContainer"></div>[/code]                    

    Then you need a function to read the bookmarks in the report and place them in the container. A handler then responds to clicks on the bookmarks.

    jrio.config({    ...});jrio(function(jrioClient) {     //render report from provided resource    var report = jrioClient.report({        resource: "/samples/reports/TableReport",        error: handleError,        container: "#reportContainer",        events: {            bookmarksReady: handleBookmarks        }    });     //show error    function handleError(err){        alert(err.message);    }     $("#bookmarksContainer").on("click", ".jr_bookmark", function(evt) {        report.pages({            anchor: $(this).data("anchor")        }).run();    });     // handle bookmarks    function handleBookmarks(bookmarks, container) {        var li, ul = $("<ul/>");        !container && $("#bookmarksContainer").empty();           container = container || $("#bookmarksContainer");         $.each(bookmarks, function(i, bookmark) {            li = $("<li><span class='jr_bookmark' title='Anchor: " + bookmark.anchor + ", page: " + bookmark.page + "' data-anchor='" + bookmark.anchor + "' data-page='" + bookmark.page + "'>" + bookmark.anchor + "</span></li>");            bookmark.bookmarks && handleBookmarks(bookmark.bookmarks, li);            ul.append(li);        });         container.append(ul);    }});[/code]                    

    Disabling the JIVE UI

    The JIVE UI is enabled by default on all reports that support it. When the JIVE UI is disabled, the report is static and neither users nor your script can interact with the report elements. You can disable it in your jrioClient.report call as shown in the following example:

    jrio.config({    ...});jrio(function(jrioClient) {     jrioClient.report({        resource: "/samples/reports/TableReport",        container: "#reportContainer",        defaultJiveUi: { enabled: false },        error: function (err) {            alert(err.message);        }    });});[/code]                    

    Associated HTML:

    <script src="http://bi.example.com:8080/jriojsapi/client/jrio.js"></script><p>JIVE UI is disabled on this report:</p><div id="reportContainer">Loading...</div>[/code]                        

    Open topic with navigation


    User Feedback

    Recommended Comments

    There are no comments to display.



    Guest
    This is now closed for further comments

×
×
  • Create New...