In my last entry, I talked about how the cfgrid control could be enhanced to make it a bit more powerful. The basic idea is to use a detail panel to show items that won't quite fit in the grid, or won't work in the grid. My example code was a bit too simple, however, and didn't really show a good example of binding a truly dynamic select. The example I gave actually had a static select. (On a side note, I left "bind" code in the cfselect that doesn't actually work.)
Here is the enhanced code:
<cfloop index="x" from="1" to="10">
<cfset queryAddRow(q)>
<cfset querySetCell(q, "name", "Name #x#")>
<cfset querySetCell(q, "age", randRange(20,50) )>
<cfif x mod 2>
<cfset querySetCell(q, "gender", "Male")>
<cfelse>
<cfset querySetCell(q, "gender", "Female")>
</cfif>
<cfset querySetCell(q, "department", randRange(1, 4))>
</cfloop>
<cfsavecontent variable="onChange">
if( people.dataProvider[people.selectedIndex]['gender'] == 'Male' ) {
gender.selectedIndex=0; } else { gender.selectedIndex=1;
}
for(var i=0;i < department.getLength();i++) if(department.getItemAt(i).data == people.dataProvider[people.selectedIndex]['department']) {department.selectedIndex=i }
</cfsavecontent>
<cfform format="flash" name="foo" width="400">
<cfgrid name="people" query="q" rowHeaders=false
onChange="#onChange#">
<cfgridcolumn name="name" header="Name">
<cfgridcolumn name="age">
<cfgridcolumn name="gender" display="false">
<cfgridcolumn name="department" display="false">
</cfgrid>
<cfformgroup type="panel" label="Detail View">
<cfinput type="text" name="moo" bind="{people.dataProvider[people.selectedIndex]['name']}">
<cfselect name="gender">
<option value="Male">Male</option>
<option value="Female">Female</option>
</cfselect>
<cfselect name="department">
<cfloop from="1" to="4" index="x">
<cfoutput><option value="#x#">Department #x#</option></cfoutput>
</cfloop>
</cfselect>
</cfformgroup>
</cfform>
So what's different? I modified the 'fake' source query to include a department ID value. You can imagine this as being a foreign key value. The next big change is that I moved the ActionScript into a cfsavecontent block. This allows me to a bit more verbose and simply pass the value to the onChange attribute of the cfgrid tag. With me so far? The ActionScript contains both the simple gender code I had last time, and a truly dynamic check against the department drop down. Basically we check the data value of each item in the drop down. (In Flash, the data value is like "value" portion of the option tag.) I compare this against the currently selected row in the grid and when a match is made, I set the selectedIndex.
Anyway, I hope this is a slightly better example.
Archived Comments
Ray
There have been a few as yet (but hey it's the w/e) unanswered
queries on a similar blog at as fusion.
Re your onChange="#onChange#". Is there any way of effecting more than one action so there would be a say #onChange2# as well
when selecting the grid row
cheers
Andy - I may not know what you mean. If you wanted to do X, Y, and Z in your onChange, just add more code. In fact, in my example, I do two things, change the gender and change the department, so you could just add more stuff.
Thank You so much for the update... I'm gonna put it to the test right away!
Ok, it works great! I don't have to do anything to the cfselect statements at all, I just add a new line in the <cfsavecontent> section for each select box I have. I have been using a different variable for each line because I wasn't sure how "local" the var statement is in this context.
Is it possible to have an additional item in the grid that doesn't exist in the query?
Say you wanted to have a list of inventory items and have the user modify how many of X were purchased... Is the best way to modify the query to artificially add qty=0? What happens when you want to use the same code to edit an existing invoice? An outer join on the inventory and invoice tables?
Or is there a better way?
Ray - thanks for the late reply
Sorry I didn't make it very clear. Not sure this will help
cf your example if I want the onChange="#onChange#" to invoke a query I have something along the lines - sorry cannot get at actual code
<cfsavecontent variable="onChange">
// set up flash remoting
// set up response handler
var responseHandler = {};
responseHandler.onResult = function( results: Object ):Void {
battingGrid.dataProvider = results;
// call service
myService.getBatting
</cfsavecontent>
This works fine as you can see at
http://www.majorleaguechart...
However, with the onchange I also want to call pitching stats with
myService.getPitching
pitchingGrid.dataProvider = results2;
In Flash you just have two seperate functions to handle the seperate resultsets but in the adapted asfusion version I'm not sure how this can be done
Chris:
2 ways. One, you can modify an existing query using queryAddColumn. You would then populate the data using querySetCell. Or - you can remove the query value from cfgrid, and use cfgridrow.
Ray:
I can't thank you enough - following your new post I finally got my page to work as planned. I'm simply amazed that I can dynamically populate a select list based on the grid row chosen by calling a cfc via flash remoting and then bind that grid row to the select to show the proper item in the select. CF is beautiful!
On another note Ray, do you play Socom at all on PS2? I've seen some mentions of Halo - just wondering if you ever tried Socom?
Todd: Nope. I don't play the ps2 much anymore since I got the xbox. I still have it around, and will be playing GTA:SA soon, but I don't play it very often.
A lot of my buddies say the same thing about the PS2 after they get an XBOX. I guess I have the whole Microsoft chip-on-the-shoulder thing when it comes to gaming. Don't know how long I can hold out though, with talks of the PS3 being in the $400 range before adding all the peripherals (such as external only hdd, etc).
Andy - ah - sorry. I haven't done any remoting with flash forms yet. :(
Andy:
Not sure this is the most efficient way, but it works. Create two variables (via cfsavecontent) which contain two separate remoting calls. In the onchange, simply reference both variables, for example:
onchange="#onchange1##onchange2#"
If anyone has a more efficient way, feel free to correct me, but I know that works.
Todd
Thanks works fine. I'd tried with a comma between and then googled without success
Ray
Look forward to incorporating your cfcharts as well in a week or so
Between you as ASFusion I have a great form that I can filter by a text box and synchronize my cfselects... now I have a dumb question about the cfgrids. I would like to maintain the Flash Form look and feel throughout my ColdFusion Application and give users some cool features too. I would like to use CFGRID to allow a user to preview report data (say a list of orders) and either Print a pre-made Report, Export the list to excel/plain text or jump the the record details edit page (if they have rights). to make a long story short, what's the easiest way to link to another form from a data grid, passing the selected item?
Ok, here I am merrily Synching my text and select boxes on a form to a datagrid and everything is fine until I get to a date field... The date field was not originally in the grid so when the bind command pulled from SQL I got the full date. Adding the mask attribute to the text box did nothing. I finally added the dates to the grid and set display="no" and then they showed up fine on the "edit" form. I don't suppose there's anyway to mask the Bind output for a particular date format?
I don't think so. I think you could perhaps use onClick and ActionScript to format the date nicely. You can also use QofQ and make a copy of the original date into something a bit simpler.
mwhite: your question on passing the 'selected item', when you submit a form that has a grid in it, you get the selected item. Do a cfdump on form and you will see. If the grid was named foo, it will be form.foo.SOMETHING. I always forget and do a dump myself.
you were right again, I dumped the form and found the field (I forgot you could dump a form). I added the date columns into the datagrid with display=no and the date thing is working so now onto the next problem... I have the orders cfgrid on the left and the edit form for the order itself on the right. They synchronize nicely will all the fields, etc. but now I need to show the line items in another cfgrid below the other two panels and keep it synchronized with the selected order in the grid above. Is there a handy one-line "onChange" actionscript thingy I can do to requery the line items grid below? I have a component that has a query to pull the line items for a given orderID initially but then I'm stuck.
nice article by Leo Schuman on the basics of actionscript in coldfusion flash forms:
http://www.macromedia.com/d...
This will help me take my flash forms to the next level (and maybe I won't have to ask you so many questions!"
Ray,
Thank you for the example. I'm new to CF and learning a lot! Is it possible to bind a datagrid to radio buttons? I'm modifying the asfusion address book example. I have a grid on the left and edit form on the right. I was able to bind to one radio button that changes values but I need to bind the same grid row to multiple radio buttons. Any ideas?
Michele, a Ask a Jedi question came in concerning this (maybe from you?), so I plan on showing an example. It may be a few days, but it is in my queue.
I have two cfselects populated by a query via remoting.
I want to click one button and have both cfselect values be submitted (onclick) to the <cfsavecontent> area and push them on to my .cfc using action script.
I am doing this with cfgrids, but am wondering how to do this with regular cfform values.
Does anyone have an example of this to look at?
I have two cfselects populated by a query via remoting.
I want to click one button and have both cfselect values be submitted (onclick) to the <cfsavecontent> area and push them on to my .cfc using action script.
I am doing this with cfgrids, but am wondering how to do this with regular cfform values.
Does anyone have an example of this to look at?
I have two cfselects populated by a query via remoting.
I want to click one button and have both cfselect values be submitted (onclick) to the <cfsavecontent> area and push them on to my .cfc using action script.
I am doing this with cfgrids, but am wondering how to do this with regular cfform values.
Does anyone have an example of this to look at?
Tried to get this to work, but i'm not doing very well :(. I have a query which populates my cfelect, which i store a display name (WineryName) and the ID (WineryID) as the value. In my grid, i don't want to display the WineryID, just the WineryName. I've tried the following:
function updateWinerySelect(){
for(var i=0;i < WineryName.getLength();i++)
if(WineryName.getItemAt(i).data == Wine_Grid.dataProvider[Wine_Grid.selectedIndex]['WineryName']) {WineryName.selectedIndex=i }
<cfgrid name="Wine_Grid" rowheaders="no">
<cfgridcolumn name="VarietalClass" header="Type" type="string_nocase" dataalign="left">
<cfgridcolumn name="Varietal" header="Varietal" type="string_nocase" dataalign="left">
<cfgridcolumn name="WineryName" header="Winery" type="string_nocase" dataalign="left">
<cfgridcolumn name="WineName" header="Name" type="string_nocase" dataalign="left">
<cfgridcolumn name="Vintage" header="Vintage" width="60" type="string_nocase" dataalign="center">
<cfgridcolumn name="Appellation" header="Appellation" type="string_nocase" dataalign="center">
<cfgridcolumn name="UseName" header="Use" type="string_nocase" dataalign="center">
<cfgridcolumn name="WineActive" header="Active" width="50" type="boolean" dataalign="center">
</cfgrid>
<cfselect name="WineryName" label="Winery" query="qWinery" display="WineryName" value="WineryID" queryPosition="below" required="yes" width="300"><option value="">Choose a Winery</option></cfselect>
But no luck. Any suggestions? I have 5 selects in all that i want to bind.
Thanks for your time,
David
}
David:
I'd caution against using the query attribute of a cfselect within a flash form. Doing so will cause a recompile on your flash form which leads to much slower load times. Instead try:
<cfselect name="WineryName" label="Winery" required="yes" width="300">
<option value="">Choose a Winery</option>
<cfoutput query="qWinery">
<option value="#qWinery.WineryID#">#qWinery.WineryName#</option>
</cfoutput>
</cfselect>
To bind the select to the grid you'll need two parts - one is a function that binds FROM the grid TO the select - which you have done - but you need to add a call to that function in the onchange of your grid.
Part two is an onchange event in your select - which will bind FROM the select TO the grid. In your case you say you'd like to display the text, not the value. Are you going to be saving changes from the grid? If so, you'll likely want to save BOTH the value and the text. The text will be for display purposes, and the value will be for the database updating. So it's probably a good idea to bring both elements into the grid and add a display="no" to the value. You'll technically only have to bind one of these columns to the select (since doing both would just be redundant) which you already have...Now onto the onchange event for the select:
<cfselect name="WineryName" label="Winery" required="yes" width="300"
onchange="
//remove these comments
//this event will bind the WineryName from this select back
//to the grid - note that i'm calling selectedItem.text
Wine_Grid.dataProvider.editField(Wine_Grid.selectedIndex, 'WineryName', WineryName.selectedItem.text);
//remove these too
//this will bind the value to the grid
//as i said, you should add a column to your grid for the ID
Wine_Grid.dataProvider.editField(Wine_Grid.selectedIndex, 'WineryID', WineryID.data);
">
<option value="">Choose a Winery</option>
<cfoutput query="qWinery">
<option value="#qWinery.WineryID#">#qWinery.WineryName#</option>
</cfoutput>
</cfselect>
One last thing - make sure your bind function is correct:
function updateWinerySelect(){
for(var i=0;i < WineryName.getLength();i++)
if(WineryName.getItemAt(i).data == Wine_Grid.dataProvider[Wine_Grid.selectedIndex]['WineryID']) {WineryName.selectedIndex=i }
--OR--
function updateWinerySelect(){
for(var i=0;i < WineryName.getLength();i++)
if(WineryName.getItemAt(i).text == Wine_Grid.dataProvider[Wine_Grid.selectedIndex]['WineryName']) {WineryName.selectedIndex=i }
Notice I'm checking for WineryName.blah.text = the WineryName OR WineryName.blah.data = the WineryID. Either one should work.
Sorry for the LONG winded response - HTH. :)
Hi todd,
Thanks a million for getting back to me on this. I tried the code for the binding the select to the grid, as i'll be updating from my fields and not my grid (so i'm assuming seeing all of this is taken from the realestate application on asfusion, but part 2 with all update info is not out yet so i'm guessing here :) ), but i'm getting something funky. If i decide to use the text route, when i select on an item from the grid, i get the very last record in the grid binding to my select. If i use data, i dont' get anything. My function looks correct:
function updateWinerySelect(){
for(var i=0;i < WinerySelect.getLength();i++)
if(WinerySelect.getItemAt(i).text == Wine_Grid.dataProvider[Wine_Grid.selectedIndex]['WineryName'])
{WinerySelect.selectedIndex=i}
}
I've renamed the select to WinerySelect for easier reading. I added WineryID as a column in the grid as follows:
<cfgrid name="Wine_Grid" rowheaders="no" onchange="{updateWinerySelect();}">
<cfgridcolumn name="WineryID" display="no">
<cfgridcolumn name="VarietalClass" header="Type" type="string_nocase" dataalign="left">
<cfgridcolumn name="Varietal" header="Varietal" type="string_nocase" dataalign="left">
<cfgridcolumn name="WineryName" header="Winery" type="string_nocase" dataalign="left">
<cfgridcolumn name="WineName" header="Name" type="string_nocase" dataalign="left">
<cfgridcolumn name="Vintage" header="Vintage" width="60" type="string_nocase" dataalign="center">
<cfgridcolumn name="Appellation" header="Appellation" type="string_nocase" dataalign="center">
<cfgridcolumn name="UseName" header="Use" type="string_nocase" dataalign="center">
<cfgridcolumn name="WineActive" header="Active" width="50" type="boolean" dataalign="center">
</cfgrid>
And changed my Select statement as the following:
<cfselect name="WinerySelect" label="Winery" required="yes" width="300">
<option value="">Choose a Winery</option>
<cfoutput query="qWinery">
<option value="#qWinery.WineryID#">#qWinery.WineryName#</option>
</cfoutput>
</cfselect>
Again, if i'm not using the bind from the select TO the grid, then i won't need anything in my onchange of my select correct? as that will make it so that if i select something then it automatically changes it in the grid. Its only after i update or add a new record that i want it to appear in the grid. Any suggestions?
Thanks for taking so much of your time! :)
Much appreciation,
David
You're correct - if you're don't need to bind back, leave out the onchange of the select.
Remove the squiggly brackets from the onchange in your grid - it should be:
<cfgrid name="Wine_Grid" rowheaders="no" onchange="updateWinerySelect();">
Try using this for your function:
function updateWinerySelect(){
for(var i=0;i < WinerySelect.getLength();i++)
{
if(WinerySelect.getItemAt(i).text.toLowerCase() == Wine_Grid.selectedItem.WineryName.toLowerCase())
WinerySelect.selectedIndex=i
}
//i like to throw an else in to select the first item if all else
//fails but you don't have to
else
{
WinerySelect.selectedIndex = 0;
}
}
I'm thinking that should work - but it's untested (obviously).
I just noticed something else:
in your for loop, use .length, not getLength()
like this:
function updateWinerySelect(){
for(var i=0;i < WinerySelect.length; i++)
{
if(WinerySelect.getItemAt(i).text.toLowerCase() == Wine_Grid.selectedItem.WineryName.toLowerCase())
WinerySelect.selectedIndex=i
}
//i like to throw an else in to select the first item if all else
//fails but you don't have to
else
{
WinerySelect.selectedIndex = 0;
}
}
Todd,
Just tried too.. no go. I even tried it on another select to maybe if it had to do with my data in the grid, but it does the same thing on Varietals. Just goes straight to the last record in the list, which makes me think it has something to do with the for loop. If you want to see the whole page (maybe there is something else i'm doing but not being able to see), send me an email Dpanzarella@hotmail.com and i'll attach it for you. Just don't want to clutter this blog with unnecessary code :).
thanks again.. You rock! :)
David
OK.. its official.. this thing is clearly driving me insane! :) Can't figure out why this for loop just goes straight to the end of the select list. Especially when there is an else statement setting it to 0 when there is no match. When i try to use data nothing even happens but i know the value is in the grid because i have it in a cffitem bind that shows the WineryID.
for(var i=0;i < WinerySelect.length; i++){
if(WinerySelect.getItemAt(i).text == WineGrid.dataProvider[WineGrid.selectedIndex]['WineryName'])
{WinerySelect.selectedIndex = i;}
else
{WinerySelect.selectedIndex = 0;}
}
Here is the loop again. I think i'm right by saying that the above loop starts by iterating through each item in the WinerySelect dropdown:
<cfselect name="WinerySelect" label="Winery" required="yes" width="300">
<option value="0">Choose a Winery</option>
<cfoutput query="qWinery">
<option value="#qWinery.WineryID#">#qWinery.WineryName#</option>
</cfoutput>
</cfselect>
It then asks: if the current iteration of the select matches the current item Selected (indexed) in the grid, then show that current iteration as selected in the WinerySelect. Otherwise, set the WinerySelect to 0 (the value of choose a winery.
Am i wrong? or just Mad!!?!
Anyone?
Thanks :)
david
Someone gave me this to bind my status selection field to the grid. You have to have a blank option on the cfselect.
status is the form field
Status is the query field
Add the onChange to the cfgrid.
onchange="{status.selectedIndex=0}
for(var i:Number = 0; i<status.length; i++)
{if(status.getItemAt([i]).data==data.selectedItem.Status)status.selectedIndex=i}"
hth
I feel so far behind you all, but I just haven't had time to tackle Flash Forms until now. Hope someone is still monitoring this thread!
I have the standard Flash form dealie with the grid on top and the input fields below. It's set to keep the input/update fields disabled until someone click on an item in the grid (in order to prevent someone from typing away thinking they're updating a field; I never did see a post on how to auto-select the first item in the grid, so if there's a solution for that, I'm all ears!)
Anyway, my problem is with the date field. ALL the other fields merrily update and bind and reflect their updates, but not my three date fields. Aarrgh!
In the grid, here is how one of the date fields are defined:
<cfgridcolumn name="paidthrough" header="Paid Thru" mask="M/D/YYYY" headerAlign="center" dataAlign="center" width="80" font="Verdana" fontSize="10" bold="no" italic="no" select="no" display="yes" headerFontSize="9" headerBold="no" headeritalic="no">
In the input field, it looks like this:
<cfinput type="DateField" name="paidthrough" value='#DateFormat(sqlMembers.paidthrough,"m/d/yyyy")#' width="100" label="Paid Through:" bind="{gridMembers.dataProvider[gridMembers.selectedIndex]['paidthrough']}" mask="M/D/YYYY" enabled="false" selectedDate='#DateFormat(sqlMembers.paidthrough,"m/d/yyyy")#'
onChange="gridMembers.dataProvider.editField(gridMembers.selectedIndex,'paidthrough',paidthrough.value);">
So for crying out loud, why won't the date populate? Once upon a time I did get it to populate, but it kept changing the date on me to the previous day.
Thanks for the help!
RLS:
Try changing your datefield to access the selectedDate property of the datefield in the onchange (i'm assuming that is what is not binding back to the grid).
Like this:
<cfinput type="DateField" ...
onChange="gridMembers.dataProvider.editField(gridMembers.selectedIndex,'paidthrough',paidthrough.selectedDate);">
Also, to select a grid row by default add this script and call it onload of the form. Replace contactList in this example with the name of your grid. This basically creates an event listener for the grid which will wait for the query data to load and then select the first item when it is complete.
<code>
function onFormLoad(){
var listener:Object = {};
//put the controls in scope to avoid calling _root
var contactList:mx.controls.DataGrid = contactList;
listener.modelChanged = function(evt):Void {
alert('Data loaded... select first item');
<!--- remove listener, so that we are not longer notified of model changes --->
contactList.removeEventListener('modelChanged',listener);
<!--- select first item --->
if (contactList.dataProvider.length){
contactList.selectedIndex = 0;
}
}
contactList.addEventListener('modelChanged',listener);
}
</code>
Thank you - we're close with that!
First, on the listener code. If I use cfscript, I get "Local variables must be initialized.". If I use script language= javascript, I get a ton of errors, most yelling at me about things like "The statement 'javascript' is incomplete." and "ActionScript 2.0 class scripts may only define class or interface constructs." (javascript is not a real strong suit for me. Can you tell?)
On the date -- it's really weird. EVERY date I select, no matter whether it's the first, the end of the month, or in the middle, they back up one day when I go through the update process. I'm not doing any variable gymnastics, either. Here's an example of the start of the code (snipped to the relevant parts, of course), which is followed by a simply SQL Update command of all fields:
<CFLOOP INDEX="UpdateNumber" FROM="1" TO="#ArrayLen(gridMembers.id)#">
<CFSET varpaidthrough=gridMembers.paidthrough[#UpdateNumber#]>
(then update that record and loop back for the next grid line)
... </CFLOOP>
I feel I'm right on the verge of success. I could even live without the listener object no problem as my disabled-until-clicked looks fine to the customer. But the date backup is weird city.
I know exactly what's happening - I'll hit you offline in a little bit (just had to jump on a conference call) - unless Ray doesn't mind me clogging up his comments here....
Thanks, Todd. I can only imagine, though, that there is someone out there looking for this answer like I have been and they will shower you with praise and adulation.
For the date issue, I hope you have a clue. I looked in the database afterward and the dates are being stored correctly; it's when the grid displays them that they end up back-a-day.
Here's half your answer - for the script - put it inside a cfformitem, type = script...
see this post over at asfusion for clarification: http://www.asfusion.com/blo...
As for the date issue, Yes, I know what's happening, and I think I'll blog it later today when i'm off these calls. I will follow up here with a link when I post it.
http://cfsilence.com/blog/c...