First a disclaimer. I am not a JavaScript nor Web developer. The code has been cobbled together from various places, including existing code from other Rapid controls. (Thanks Gareth!).
Why this control?
I like the Google charts in Rapid but we already have a lot of in-house, tried-and-tested SSRS reports here. We wanted to use them in a Rapid site. This control allows you not just to embed a SSRS page, but also pass in values as url parameters from any other Rapid controls, which refreshes the report. For instance, on the standard Rapid Grid control, you can set up a Row-Click event which passes the ID from the row in your dataset into the embedded chart.
Read about SSRS parameters here:
https://docs.microsoft.com/en-us/sql/re ... -reference
There are three files:
<your root folder>\WEB-INF\controls\iframewithparams.control.xml
Code: Select all
<?xml version="1.0" encoding="ISO-8859-1" ?>
<controls xmlVersion="1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../schemas/control.xsd">
<control>
<type>iFramewithparams</type>
<name>iFrameWithParams</name>
<image>images/iframewithparams_24x24.png</image>
<helpHtml>Puts an iFrame onto the page. Allows URL parameters.</helpHtml>
<addToNewApplications>true</addToNewApplications>
<addToNewResponsiveApplications>false</addToNewResponsiveApplications>
<noDefaultName>true</noDefaultName>
<canUserAdd>true</canUserAdd>
<canUserMove>true</canUserMove>
<canUserAddPeers>true</canUserAddPeers>
<properties>
<property>
<key>name</key>
<name>Name</name>
<changeValueJavaScript>text</changeValueJavaScript>
<helpHtml>Name that can be given to the control. Names are used to help identify controls uniquely.</helpHtml>
</property>
<property>
<key>urladdress</key>
<name>Webpage</name>
<setConstructValueFunction>return ""</setConstructValueFunction>
<changeValueJavaScript>text</changeValueJavaScript>
<refreshHtml>true</refreshHtml>
<helpHtml>Webpage to display</helpHtml>
</property>
<property>
<key>urlparams</key>
<name>Parameters</name>
<setConstructValueFunction>return [{value:"", label:"Param1"}, {value:"", label:"Param2"}];</setConstructValueFunction>
<changeValueJavaScript>iframewithparams</changeValueJavaScript>
<refreshHtml>true</refreshHtml>
<helpHtml>URL parameters appended on the end of the web address</helpHtml>
</property>
<property>
<key>roles</key>
<name>User roles</name>
<changeValueJavaScript>roles</changeValueJavaScript>
<helpHtml>Only allows the viewing of the panel based on the type of privileges a user has.</helpHtml>
</property>
<property>
<key>hidden</key>
<name>Hidden</name>
<changeValueJavaScript>checkbox</changeValueJavaScript>
<helpHtml>Whether the panel hidden when the page first loads.</helpHtml>
</property>
</properties>
<getHtmlFunction>
<![CDATA[
// get the style classes for this control
classes = getStyleClasses(this);
// if they start with "alert" do not include "iFrame" as this messes up bootstrap
if (!classes && classes.indexOf("alert") != 1) classes = "iFrame" + classes;
// return our html for adding to our parent object
var params = [];
var paramstring = "";
if (this.urlparams) {
paramstring = "?";
var urlparams = this.urlparams;
for (var i in urlparams) {
if (urlparams[i].value) {
params.push(urlparams[i].label + (urlparams[i].value.indexOf("=") >= 0 ? urlparams[i].value : "=" + urlparams[i].value));
} else {
params.push(urlparams[i].label);
}
}
paramstring += params.join("&");
}
return "<iframe id='" + this.id + "' class='" + classes + "'" + (this.text ? "" : " style='min-height:15px;'") + (this.urladdress ? " src='" + this.urladdress + paramstring + "'" : "") + "></iframe>";
]]>
</getHtmlFunction>
<saveJavaScript>
<![CDATA[
// hide if hidden
if (this.hidden) this.object.hide();
]]>
</saveJavaScript>
<getDataFunction>
<![CDATA[
return $("#" + id).val();
]]>
</getDataFunction>
<setDataJavaScript>
<![CDATA[
var control = $("#" + id);
var value = "";
if (data != null && data !== undefined) {
data = makeDataObject(data, field);
if (data.rows && data.rows[0]) {
if (field && data.fields && data.fields.length > 0) {
for (var i in data.fields) {
if (data.fields[i] && data.fields[i].toLowerCase() == field.toLowerCase()) {
value = data.rows[0][i];
break;
}
}
} else {
if (data.rows[0][0] != null && data.rows[0][0] !== undefined) {
value = data.rows[0][0];
}
}
}
}
var iframe_src = control.attr('src');
var iframe_src_split = iframe_src.split("?");
var iframe_base_url = iframe_src_split[0];
var iframe_params = iframe_src_split[1].split("&");
var param_count = iframe_params.length;
var found = 0;
for (var i = 0; i < param_count; i++) {
var param = iframe_params[i].split("=");
if(param[0].toLowerCase() == field.toLowerCase()) {
found = 1;
if(value.indexOf("=") >= 0) {
iframe_params[i] = param[0] + value;
} else {
iframe_params[i] = param[0] + "=" + value;
}
}
}
if(!found) {
if(value) {
iframe_params.push(field + (value.indexOf("=") >= 0 ? value : "=" + value));
} else {
iframe_params.push(field);
}
}
var newsrc = iframe_base_url + "?" + iframe_params.join("&");
control.attr('src',newsrc);
if (changeEvents) control.trigger("change");
]]>
</setDataJavaScript>
<events>
<event>
<type>load</type>
<name>onload</name>
</event>
</events>
<styles>
<style>
<name>iFrame</name>
<getAppliesToFunction>
<![CDATA[
return "#" + this.id;
]]>
</getAppliesToFunction>
</style>
</styles>
</control>
</controls>
Code: Select all
// any JavaScript in this file is not overwritten by the Ant build script.
// if you have custom controls with property JavaScript best to put it here
function Property_iframewithparams(cell, control, property, details) {
// retrieve or create the dialogue
var dialogue = getDialogue(cell, control, property, details, 200, "Parameters", {sizeX: true});
// grab a reference to the table
var table = dialogue.find("table").first();
// make sure table is empty
table.children().remove();
// if we're using a value list and this version still has them
if (control.valueList && _version.valueLists && _version.valueLists.length > 0) {
// set the text to the value list
cell.text(control.valueList);
// remove any current useCodes
dialogue.find("div.useCodes").remove();
} else {
var urlparams = [];
// set the value if it exists
if (control.urlparams) urlparams = control.urlparams;
// make some text
var text = "";
for (var i = 0; i < urlparams.length; i++) {
text += urlparams[i].label;
if (control.codes) text += " (" + urlparams[i].value + ")";
if (i < urlparams.length - 1) text += ",";
}
// add a descrption if nothing yet
if (!text) text = "Click to add...";
// append the adjustable form control
cell.text(text);
// add a heading
table.append("<tr><td><b>Parameter</b></td><td colspan='2'><b>Value</b></td></tr>");
// show options
for (var i in urlparams) {
// add the line
table.append("<tr><td><input class='label' value='" + escapeApos(urlparams[i].label) + "' /></td><td><input class='value' value='" + escapeApos(urlparams[i].value) + "' /></td><td style='width:32px;padding:0;'><img class='delete' src='images/bin_16x16.png' style='float:right;' /><img class='reorder' src='images/moveUpDown_16x16.png' style='float:right;' /></td></tr>");
// find the code
var valueEdit = table.find("input.value").last();
// add a listener
addListener( valueEdit.keyup( {control : control, urlparams: urlparams}, function(ev) {
// get the input
var input = $(ev.target);
// update value
ev.data.urlparams[input.parent().parent().index()-1].value = input.val();
// update html
rebuildHtml(ev.data.control);
}));
// find the label
var textEdit = table.find("input.label").last();
// add a listener
addListener( textEdit.keyup( {control : control, urlparams: urlparams}, function(ev) {
// get the input
var input = $(ev.target);
// update text
ev.data.urlparams[input.parent().parent().index()-1].label = input.val();
// update html
rebuildHtml(ev.data.control);
}));
}
// have an add row
table.append("<tr><td colspan='3'><span class='propertyAction'>add...</span></td></tr>");
// get a reference to the add
var add = table.find("span.propertyAction").last();
// add a listener
addListener( add.click( {cell: cell, control: control, property: property, details: details}, function(ev) {
// add a blank option
ev.data.control.urlparams.push({value: "", label: ""});
// refresh
Property_iframewithparams(ev.data.cell, ev.data.control, ev.data.property, ev.data.details);
}));
// find the deletes
var buttonDelete = table.find("img.delete");
// add a listener
addListener( buttonDelete.click( {cell: cell, control: control, property: property, details: details}, function(ev) {
// get the del image
var delImage = $(ev.target);
// remove from parameters
ev.data.control.urlparams.splice(delImage.parent().parent().index()-1,1);
// remove row
delImage.parent().parent().remove();
// update html if top row
if (delImage.parent().index() == 1) rebuildHtml(ev.data.control);
// refresh
Property_iframewithparams(ev.data.cell, ev.data.control, ev.data.property, ev.data.details);
}));
// add reorder listeners
addReorder(urlparams, table.find("img.reorder"), function() {
// refresh the html and regenerate the mappings
rebuildHtml(control);
// refresh the property
Property_iframewithparams(cell, control, property, details);
});
}
// only if this version has value lists
if (_version.valueLists && _version.valueLists.length > 0) {
// check we don't have a value list select already
if (!dialogue.find("select")[0]) {
// add the select
dialogue.append("Value list <select><option value=''>None</option>" + getValueListsOptions(control.valueList) + "</select>");
// get a reference
var valueListSelect = dialogue.find("select");
// add a listener
addListener( valueListSelect.change( {cell: cell, control: control, property: property, details: details}, function(ev) {
// get the value
ev.data.control.valueList = $(ev.target).val();
// refresh
Property_iframewithparams(ev.data.cell, ev.data.control, ev.data.property, ev.data.details);
// rebuild
rebuildHtml(ev.data.control);
}));
}
}
}
Note that propertiescustom.js is where all custom properties should end up (I think). So don't overwrite it if you have stuff in there!
When the three files are in place, reload the controls from the Admin console. Then you should be able to add it to your application.
The control has three important properties:
Name
Must not be blank if you want to pass data into this control.
Webpage
This is what goes into the src attribute of the iframe element. If this is blank, you just get an empty panel.
Parameters (optional)
This is what gets appended to the base url. This is configured using parameter-value pairs.
1. Both parameter and value populated gives you "¶meter=value"
2. Only label populated gives you "¶meter" without the "="
3. If you want "¶meter=" by itself, then put "=" into the value box. (A bit hacky but hey). This lets you pass in an empty parameter which you can dynamically change later.
Limitations
1. This suffers from all the layout limitations of using iframes. E.g. it's difficult (but not impossible) to set up the iframe to resize itself based on its contents.
2. Cross site scripting problems. A lot of sites won't allow you to embed them inside an iframe.
Example 1 - Embedded report with a Rapid grid below it: Example 2 - Clicking on a different row in the grid updates the SSRS report.