Parameterised iframe control (for SSRS)

Please post general news and welcomes here
wing_lau
Posts: 1
Joined: Wed Jul 12, 2017 3:54 pm

Parameterised iframe control (for SSRS)

Postby wing_lau » Thu Dec 14, 2017 4:14 pm

I thought I'd share with you all our iframe control. This has developed purely for embedding parameterised our SSRS reports but I suppose it can be used to embed any page (that allows embedding).

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("&amp;");
}
	 
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>
<your root folder>\scripts\propertiescustom.js

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);
			}));				
		}
	}
}

<your root folder>\root\WEB-INF\controls\iframewithparams_24x24.png (attached. This is just an icon file)

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 "&parameter=value"
2. Only label populated gives you "&parameter" without the "="
3. If you want "&parameter=" 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:
Clicked.png
Clicked.png (15.76 KiB) Viewed 980 times
Example 2 - Clicking on a different row in the grid updates the SSRS report.
Clicked2.png
Clicked2.png (15.52 KiB) Viewed 980 times
Attachments
iframewithparams_24x24.png
Logo file for the control
iframewithparams_24x24.png (477 Bytes) Viewed 980 times

Who is online

Users browsing this forum: No registered users and 0 guests