|
Connecting Select form fields based on data selections Pt 3 |
I've been exploring various methods of using JQuery to populate Select form fields. In the previous article I used an intermediary file to act as a handler for the JSON returned object. In this last article I have removed the extra handler template, interfacing directly with the CFC.
Firstly there are some small changes to the JQuery url call.
2<scr/ipt src="select-chain.js" type="text/javascript" charset="utf-8"></script>
3<scr/ipt type="text/javascript">
4 jQuery.noConflict();
5 jQuery(function () {
6 var type = jQuery('#series');
7 var sp = jQuery('#issueno');
8 var selectedSeries = jQuery('#series').val();
9
10 type.selectChain({
11 target: sp,
12 url: 'jquerySeries.cfc?method=getIssueNos',
13 type: 'post',
14 data: { ajax: true, returnformat: 'plain', series: selectedSeries }
15 }).trigger('change');
16
17 });
18</script>
Now we are directly referencing the CFC. Simply append the function name as a url parameter. In the "DATA" element we place the data we are passing in the form of key value pairs. This will build all the values into the JQuery url, so the final URL would be:
At this point I was getting an parse error, until I found the 'returnFormat' value. Coldfusion will return WDDX encoded packets by default, problem was my handler was looking for Json. Adding this value will stop this.
Next we have the same form as before, there are no changes at all: (here for completeness really)
2 <form action="" method="post">
3 <select name="series" id="series">
4 <cfoutput query="variables.series">
5 <option value="#variables.series.intId#">#variables.series.varName#</option>
6 </cfoutput>
7 </select>
8 <select name="issueno" id="issueno">
9 <option></option>
10 </select>
11 <button type="submit">Submit</button>
12 </form>
Lastly there are one or two small changes to our CFC function.
2 <cfargument name="series" type="numeric" required="false" hint="Id of the publication">
3 <cfset var result = "">
4
5 <cfquery name="result" datasource="#application.ds#">
6 SELECT [intIssueNo]
7 FROM [dbo].[tbl_cn_series_issueNos]
8 where intSeriesId = <cfqueryparam value="#arguments.series#" cfsqltype="cf_sql_integer">
9 </cfquery>
10
11 <cfsetting showdebugoutput="false">
12 <cfset ojson = createObject("component","cfjson")>
13 <cfset theresults = ojson.encode(listToArray(valuelist(result.intIssueNo)))>
14 <cfsetting enablecfoutputonly="true">
15 <cfreturn theresults>
16 </cffunction>
The access method has changed, so we add an "access=remote" value to expose the function as a service. Then we serialize the query result into JSON, and return it. This seems like a much more succinct way of doing this.
|
Connecting Select form fields based on data selections Pt 2 |
I want to be able to dynamically change a second select field based on the value of the first select field.
Following on from the cfajaxproxy example I wrote last week, where I discovered I could not use ColdFusion 8 functionality on my live server, I have arrived at a variant solution.
In this example I am using a similar JQuery url request to process the output from a CFC. The first template is the form itself. This includes references to the JQuery libraries and the binding of the response to the form elements. It also builds the url request to the 'request_processor.cfm' file, which handles the CFC.
2<scr/ipt src="select-chain.js" type="text/javascript" charset="utf-8"></script>
3<scr/ipt type="text/javascript">
4 jQuery.noConflict();
5 jQuery(function () {
6 var type = jQuery('#series');
7 var sp = jQuery('#issueno');
8
9 type.selectChain({
10 target: sp,
11 url: 'request_processor.cfm',
12 type: 'post',
13 data: { ajax: true }
14 }).trigger('change');
15
16 });
17</script>
Next build a simple form, and populate the first select field with data from a method.
2 <cfdump var="#form#">
3<cfelse>
4 <cfset variables.series = createObject("component","jquerySeries"). getSeries()>
5 <form action="" method="post">
6 <select name="series" id="series">
7 <cfoutput query="variables.series">
8 <option value="#variables.series.intId#">#variables.series.varName#</option>
9 </cfoutput>
10 </select>
11 <select name="issueno" id="issueno">
12 <option></option>
13 </select>
14 <button type="submit">Submit</button>
15 </form>
16</cfif>
Next is the intermediately file that handles the data manipulation, this is triggered when the user chooses an option from the first select field. It passes through the selected value, and returns the query object. This is then serialised using the cfJson object, to return the data in Json. If you do anything like this remember to watch your debug output, it was destroying my Json response for a good ten minutes before I remembered to turn it off. Doh!
2<cfsetting enablecfoutputonly="true">
3<cfparam name="form.series" default="8">
4<cfset variables.issues = createObject("component","jquerySeries").getIssueNos(series=form.series)>
5<cfset ojson = createObject("component","cfjson")>
6<cfset theresults = ojson.encode(listToArray(valuelist(variables.issues.intIssueNo)))>
7<cfoutput>#theresults#</cfoutput>
Finally a CFC that performs the database functions. This is a pretty straight forward CFC that performs two database queries, the second based on an id passed in from the first.
2
3 <cffunction name="getSeries" output="false" hint="Returns publication series">
4 <cfset var result = "">
5
6 <cfquery name="result" datasource="#application.ds#">
7 SELECT [intId],[varName]
8 FROM [dbo].[table]
9 Where intActive = '1'
10 Order by varName
11 </cfquery>
12
13 <cfreturn result>
14 </cffunction>
15
16 <cffunction name="getIssueNos" output="false" hint="returns related series issue numbers">
17 <cfargument name="series" type="numeric" required="false" hint="Id of the publication">
18 <cfset var result = "">
19
20 <cfquery name="result" datasource="#application.ds#">
21 SELECT [intIssueNo]
22 FROM [dbo].[table]
23 where intSeriesId = <cfqueryparam value="#arguments.series#" cfsqltype="cf_sql_integer">
24 </cfquery>
25
26 <cfreturn result>
27 </cffunction>
28</cfcomponent>
Once you have these elements hooked up you'll see that the response from the first select field changes the values in the second field. You can download a rar'd version of the code base here.
This works well, but I'm not massively happy about the 'remote_processor' file. I think I'll see if there is a way of directly calling the CFC, and moving the JSON serialisation into the functions.
|
Connecting Select form fields based on data selections Pt 1 |
Whilst building a new form I discovered that I needed two select fields, one that contained a series of publication titles, and the second that contained the Issue numbers of whatever title was selected in the first field.
IE two selects, with the first controlling the seconds data.
I had a search around and it seems like there are many ways to do this, this by far seems to be the best, but it has a fatal flaw (detailed at the end).
Start off by creating a function call to setup your initial state. This will set the default value of the first select field, and populate the second field with its options.
2<!--- Set the default Series --->
3<cfset defaultSeries = 7>
4
5<scri pt type="text/javascript">
6var done = false;
7function SelectDefault(x,val) {
8 if(!done) {
9 var dd = document.getElementById('intId');
10 for(var i = 0; i < dd.length; i++){
11 if(dd.options[i].value == val){
12 dd.selectedIndex = i;
13 }
14 }
15 done = true;
16 }
17}
18</scri pt>
Next create the form, and use the cfajaxproxy to bind the default values set above.
The form itself is a pair of cfselects, with the values bound to the response from a CFC (Series). I really like the way the notation works with this, the cfc:Name . function is really intuitive.
Notice how the binding on the second cfselect is passing through the value from the first 'intId' in this example. This is the argument passed to the CFC.
2<cfajaxproxy bind="javascript:SelectDefault({intId},#defaultSeries#)">
3
4<cfform name="exampleform" method="post">
5
6<cfselect name="intId" id="intId" value="intId" display="varName" bindonload="true" bind="cfc:Series.GetIssues()"/>
7<cfselect name="issueNo" id="issueNo" value="intIssueNo" display="intIssueNo" bind="cfc:Series.GetIssueNos({intId})"/>
8<cfinput type="submit" name="submit" value="Submit"/>
9</cfform>
10</body>
11</html>
So that is our form. The next code block is the CFC. It is a relatively straight forward CFC with two functions, each returning a list of data. The second requires an argument passed in from the cfselect above.
2 <cffunction name="GetIssues" access="remote" returntype="query">
3
4 <cfquery name="data" datasource="#application.dsn#">
5 SELECT [intId],[varName],
6FROM [dbo].table
7 Order by varName
8 </cfquery>
9
10 <cfreturn data>
11 </cffunction>
12
13 <cffunction name="GetIssueNos" access="remote" returntype="query">
14 <cfargument name="intId">
15 <cfquery name="data" datasource="#application.dsn#">
16 SELECT [intIssueNo]
17 FROM [dbo].table
18 where intSeriesId = <cfqueryparam value="#arguments.intId#" cfsqltype="cf_sql_integer">
19 </cfquery>
20 <cfreturn data>
21
22 </cffunction>
23
24</cfcomponent>
This just returns two different query objects. When this is all hooked up it works fantastically. Changing the first select changes the issue numbers in the second select.
My problem lies in the fact that this is all ColdFusion 8 functionality. Both cfajaxproxy and cfselect are coldfusion server version 8 tags. My hosting company is still using version 7! Which I discovered when I rolled this code out. (doh!)
So I have to find another solution, which follows shortly.
N.B. Thanks to several of Ben Forta and Ben Nadel's blog posts around similar subjects for guidance on this, they got me over a few stumbling blocks.
|
Cross site Script hacking using the GET method |
I've dealt with Cross Site scripting (XSS) attacks before ( http://www.mccran.co.uk/index.cfm/2009/4/6/Cross-Site-scripting-hack-test-form), so I'm familiar with the principles involved. In this example there is a subtle difference.
In the example above the vulnerability was created by POSTING a text string through the form action. In this example we will examine a similar vulnerability using GET. IE we will simply pass the attacking string through the url of the form, setting the form field value in the traditional 'url?variable=N' way.
To demonstrate this create a simple form:
2
3<form>
4
5<input type="text" name="formValue" size="20" value="<cfoutput>#attributes.formValue#</cfoutput>">
6<input type="submit" name="Action" value="Send">
7
8</form>
Call your form in a browser. Now append on the end of that url the text string below.
?attributes.formValue==>"><%2Ftitle><%2Fiframe><%2Fscript><%2Fform><%2Ftd><%2Ftr>
Reading through the string you'll notice that it is an Iframe constructor that is calling a url, in this case www.Google.com.
As the url is setting the value of 'attributes.formValue' this will be inserted into the form on the submit action. We are not posting it, so it will not be picked up by any custom POST action code.
One interesting point to mention here is that testing this in IE 8, it will actually be blocked by default, as it has detected that scripts are running over different domains.
So if you are in the habit of writing POST detection scripts, make sure you handle any other submissions as well!