I have one form with a table and an "ADD ITEMS" button. On clicking "ADD ITEMS", a 2nd form is opened where the user can add details. On clicking "Submit" on the 2nd Page, the details should get added as a new row in the table in the first screen.
I am using an array and struct to add/append in the array .
Every time a user tries to add a new row, I'm creating a new struct in the existing array(on page 1) and try to display the array on the first page.
<cfset arrayRequisition[k] = StructNew() />
<cfset arrayRequisition[k].Part_Nbr = #Variables.ReqDescription# />
<cfset arrayRequisition[k].Description = #Variables.EQT_DESCRIPTION# />
<cfset arrayRequisition[k].Quantity = #Variables.RQT_QUANTITY# />
<cfset arrayRequisition[k].Status = "pending" />
<cfset arrayRequisition[k].Line_No = #i# />
Here K is the count of rows added and incremented every time. However I am getting the error when I'm doing so:
"Array field 1 not found".
When I fix that, the table displays only the last row.
<cfoutput>
<table border="1">
<tr>
<td style="font-weight:bold;">Part_Nbr </td>
<td style="font-weight:bold;">Description </td>
<td style="font-weight:bold;">Quantity </td>
<td style="font-weight:bold;">Status </td>
<td style="font-weight:bold;">Line_No</td>
</tr>
<cfdump var="#i#">
<cfloop from="1" to="#arrayLen(arrayRequisition)#" index="j">
<cfdump var="#arrayLen(arrayRequisition)#">
<tr>
<td>#arrayRequisition[j].Part_Nbr#</td>
<td>#arrayRequisition[j].Description#</td>
<td>#arrayRequisition[j].Quantity#</td>
<td>#arrayRequisition[j].Status#</td>
<td style="text-align:right;">#arrayRequisition[j].Line_No#</td>
</tr>
</cfloop>
</table>
</cfoutput>
From what I understand, during the transition between the two pages, my previous struct data /array data gets lost. How can I retain the values of my array in this case?
It sounds like you have two different ColdFusion templates (.cfm files). If that is the case then you need to understand that the variables you are using in your ColdFusion templates are only available during that browser request. Once the ColdFusion templates run and the response is sent back to the user those variables are "lost". Here is a page that gives an overview of the life cycle in ColdFusion applications: Elements of a ColdFusion application.
In order to persist the values for subsequent requests you could (this is not an all inclusive list):
Pass ALL of the values with each form submission
Store your variables in a persistent scope, like the session
Use a database to store the values
Then depending on which option you choose, your ColdFusion templates will need to be changed to store the values in the persistent place (your second template) and read the existing values from the persistent place (your first template).
It would also be possible to rewrite the entire process to make use of AJAX calls from a single ColdFusion template. Where the AJAX functions would call on a new ColdFusion CFC that would need to be written that controls the updating and retrieving of the variable values. I believe this option would be a more extensive rewrite for you however.
Check to make sure that arrayRequisition is indeed an array. By using the syntax you show in the first example, CF would create a structure, not an array. Doing varName[x] = someVar will actually give you a structure with a key of the value of the variable x
To create an array you would need to create a struct that contains the keys Part_nbr, Description, etc and then call arrayAppend( arrayRequisition, newStruct)
Related
Take the following code...
<cfset thisToken = createUUID()>
<cflock scope="SESSION" type="EXCLUSIVE" timeout="10">
<cfif not isDefined("session.allTokens")><cfset session.allTokens = ""></cfif>
<cfset session.allTokens = ListAppend(session.allTokens, thisToken)>
</cflock>
<cfoutput>
#thisToken#
<br><br>
#hash(thisToken, "SHA-512")#
<br><br>
#session.allTokens#
</cfoutput>
.... a few html lines, nothing special ....
<input type="hidden" name="token" value="#hash(thisToken, "SHA-512")#">
If I run this page in multiple browser tabs, the first tab works fine, but in all other tabs after that the hash in the hidden (when viewing source) does not equal the hash in the cfouput right above it. It's strange, the hash in the cfoutput displays a new unique UUID for all tabs but the hidden always contains the same value in tabs 2, 3, 4, 5, .....
Why is the hash in the hidden not unique and matching the hash in the cfoutput?
I see what's up now. For some reason, in Chrome, "view source" isn't always showing the proper source for the tab. Not sure why. But if I do "Inspect Element" instead and view the hidden that way it does in fact contain the correct unique value in each tab. Must be some issue with Chrome when viewing source when multiple tabs have the same URL but slightly different content.
If there is a better way to go about this (which is quite likely), please let me know how to go about it.
I'm working on some code that is supposed to dynamically set the form variables as regular variables so that we can be lazy and not have to refer to the variable with form.somevariable name.
That part works perfectly. Until I start testing for URL conflicts in which a URL variable has the same name. For instance. . .
I have a form that passes two variables; FirstName and LastName. If I hit the page, the form shows up, I input a first and last name and click submit. The code works perfectly.
However, if I have URL variables with the same names, the code reports the url variable values instead of the form values.
Some sample values;
url.FirstName = Joe
url.LastName = Black
form.FirstName = Steve
form.LastName = White
My code that exposes the form variable will correctly find the form field names, but then when I 'evaluate' the value of the given form field, it will return the value of the URL variable of the same name rather than the form variable.
What I am really wanting (as I described briefly up above) is to have code that automatically converts client, URL and Form variables into 'regular variables' so that you don't have to write lots of extra code grabbing them later on. Frameworks like CFWHEELS and ColdBox do this by default, but at the company I work out, we aren't using any of them. I need it to expose the URL variables, but give presidence to form variables if they have the same name, because they are likely to be intended to do an update or such.
The code follows Feel free to ignore the code for the URL and client variables if you wish as they don't directly affect how the form code works, I have tested with them commented out and I get the same result. I provided all of it to give a more complete idea of what I have been toying with so far. Please note that I don't normally use 'evaluate'. There is probably a better way to go, but I don't know what it is.
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ ++++++++++++++
First Name
Last Name
URL variables:
<cfloop index="i" list="#paramsurl#">
<cfset myDynVar = Evaluate(i)>
<!--- Let's output the dynamically created variable as a test --->
#i# = #myDynVar#<br />
</cfloop>
<cfoutput>
<b>Field Names:</b> #Form.FieldNames#
<p>
<b>Field Values:</b><br>
<cfloop INDEX="TheField" list="#Form.FieldNames#">
#TheField# = #Evaluate(TheField)#<br>
<cfset TheField = Evaluate(TheField)>
</cfloop>
</p>
Lets try and output the two form fields without using the "form." notation<br>
FirstName : #FirstName# <br />
LastName : #LastName#
</cfoutput>
The client variables currently available are:
<cfset nVarCounter = 1>
<cfloop list="#GetClientVariablesList()#" index="whichClientVar">
#whichClientVar# : #client[whichClientVar]#<br />
<cfset whichClientVar = Evaluate(whichClientVar)>
</cfloop>
You should always scope your variables. When you use evaluate it runs through the scope order and it pulls the values out of the url scope before it gets to the form scope
You can use associative array notation to pull the data (as seen below).
<cfoutput>
<b>Field Names:</b> #Form.FieldNames#
<p>
<b>Field Values:</b><br>
<cfloop INDEX="TheField" list="#Form.FieldNames#">
#TheField# = #form[TheField]#<br><!--- specify form scope --->
<cfset myField = structKeyExists(url,TheField) ? url.TheField : form.TheField>
</cfloop>
</p>
</cfoutput>
You can 'copy' the values from form scope and url scope into the variables scope by using structAppend().
structAppend( variables, form, true );
structAppend( variables, url, false );
In the first line, any element of the form scope is copied to the variables scope and if a variable already exists with the same name in variables scope, it will overwrite that value with the value from the form scope.
In the second line, elements form URL scope are copied to variables scope but if a variable already exists in the variables scope, it is NOT overwritten.
You can do this for ANY scope and any other ColdFusion structure. You can also reorder them so that one scope has precedence over the others.
In CF 10 or Railo 4, you could use the defaults() function of the Underscore.cfc library to succinctly accomplish what you're trying to do. Example:
// instantiate Underscore library
_ = new Underscore();
// copy values from form and url scopes into the variables scope
_.defaults(variables, form, url);
This function "fills in" any undefined values in the first struct to the values in the subsequent structs. It works from left to right, so in this example it gives precedence to values in form over the values in url.
Disclaimer: I wrote the Underscore.cfc library.
Please refer to the following Adobe documentation for order of precedence:
http://help.adobe.com/en_US/ColdFusion/9.0/Developing/WSc3ff6d0ea77859461172e0811cbec09af4-7fdf.html
If you want to reverse this precedence for some reason you should be able to just set all of your FORM fields into the variables scope...
Maybe something like ...
<cfloop collection=#form# item="varName">
<cfset SetVariable("variables.#varName#", evaluate("FORM." & varName))>
</cfloop>
Thanks for all of the great ideas.
Following is what I ended up going with.
<cfset scopes = "url,client,form">
<cfloop list="#scopes#" index="i">
<cfloop list="#structKeyList( evaluate( i ) )#" index="j">
<cfset structInsert( VARIABLES, j, evaluate( i & '["' & j & '"]' ), true ) />
</cfloop>
</cfloop>
<cfoutput>
Lets try and output the two form fields without using the "form." notation and make sure that the URL variables are NOT over writing the Form ones<br>
FirstName : #FirstName# <br />
LastName : #LastName#<br />
</cfoutput>
<cfdump var="#VARIABLES#" abort="1" label="Combined Variables Scope stuff" />
How would one go about building a multiselect box in Coldfusion without using CFForm or CFSelect?
This is to pull values from a DB so its not just a static select box it is dynamic.
This is my first time every trying to code in ColdFusion, I have always been a .Net person so this is a bit of a change for me.
The reason why I am needing this is because I've gotten hired into a department at work that uses Coldfusion but from what the Lead developer told me is they do not use CFForm and seeing as how CFSelect requires to be inside CFForm I need a different way of doing this.
Use plain old HTML, for example:
<cfquery name="qryUsers" datasource="datasourcename">
SELECT [User].[UserID], [User].[FirstName]
FROM [User]
</cfquery>
<cfoutput>
<form ...>
<select name="users" multiple="multiple">
<option value="">- please select -</option>
<cfloop query="qryUsers">
<option value="#UserID#">#FirstName#</option>
</cfloop>
</select>
</form>
</cfoutput>
I am obfuscating URL's in my app (which is great), but I'd like to disable this for pagination URL's because I'd like the user to be able to enter whatever number they like.
Settings.cfm:
<cfset set(obfuscateURLs = true) />
Home.cfc (controller):
<cffunction name="home">
<cfparam name="params.page" default="1" />
<cfset linkList = model("link").findAll(
select="linkTitle,linkPoints,linkID,linkAuthority,linkCreated,linkUpVoteCount,linkDownVoteCount,linkCommentCount,userName,userID",
include="user",
order="linkPoints DESC",
handle="linkListPaging",
page=params.page,
perPage=5
) />
</cffunction>
Home.cfm (view)
<ul class="pagination">
<cfoutput>
#paginationLinks(
route="paginateLatest",
handle="linkListPaging",
page=1,
name="page",
windowSize=5,
prependToPage="<li>",
appendToPage="</li>",
classForCurrent="current"
)#
</cfoutput>
</ul>
Can I do DeObfuscate on an as needed basis?
Thanks,
Michael
The setting to obfuscate params is an all-or-nothing deal. Just as you can't override this behavior for linkTo(), you cannot override it for paginationLinks() either.
I would suggest building a plugin as I bet there will be other developers out there who would want this in the future. There may be a way to tell the controller to not obfuscate/deobfuscate a parameter named page. You would need to update both how urlFor() works as well as how the controller deobfuscates as it handles incoming requests. You may also consider providing a configuration option to use set() to "blacklist" a set of params keys to never be obfuscated (with page being a default).
I'm using CFGRID and CFGRIDUPDATE to insert values into a database. The problem is, each record needs to get an additional field that isn't in the grid. Is there any way to save that additional field to the record, or do I have to whip up an alternative to CFGRID?
Basically, I have a bunch of users I'm entering into a grid. The page is getting a category id. I want all of the users to be saved with that category id.
Another thing that would work is if I could get a list of all primary keys, including those for the just-created records, and update all of them with the category id. But it looks like CFGRIDUPDATE doesn't return any information about the rows that were created.
-- original answer removed --
Based on your comment to my original answer, there is now an implied assumption that Category_ID is a foreign key, possibly on a join table, that cannot (for whatever reason) be included in the initial display query--or, that you simply wish to include a dynamic variable into the grid which will be included during inline inserts, without the user intervening (ie. physically preventing them from selecting the Category_ID themselves, but that it will still be dynamic in some capacity, say...by being fed from a URL var).
If correct, I believe the true question leans closer to:
Can a CFGRID update multiple tables/dynamic columns via CFGRIDUPDATE?
Short answer: No
Long answer: Yes, but not via CFGRIDUPDATE--rather, via CFQUERY and a little more work on the CFGRID, via CFC binds and the CFAJAXPROXY.
Solution:
1) You'll need two files for this solution. File #1 is your Component which wraps the query functionality; we'll call it cfgrid.cfc
<cfcomponent>
<cfset this.dsn = "gridexample" />
<cffunction name="getUsers" returntype="any" access="remote" output="false">
<cfargument name="page" />
<cfargument name="pageSize" />
<cfargument name="gridsortcolumn" />
<cfargument name="gridsortdirection" />
<cfset var getUsers = 0 />
<cfquery name="getUsers" datasource="#this.dsn#">
SELECT Users.UserID, Users.FirstName, UserCategories.Category_ID
FROM Users
INNER JOIN UserCategories ON (Users.User_ID = UserCategories.UserID)
<cfif arguments.gridsortcolumn neq "" or arguments.gridsortdirection neq "">
order by #arguments.gridsortcolumn# #arguments.gridsortdirection#
</cfif>
</cfquery>
<cfreturn QueryConvertForGrid(getUsers, page, pageSize) />
</cffunction>
<cffunction name="addNewUser" returntype="string" access="remote" output="false">
<cfargument name="fullname" type="string" required="true" />
<cfargument name="category_id" type="numeric" required="true" />
<cfquery datasource="#this.dsn#">
INSERT INTO Users
(
fullname
)
VALUES
(
<cfqueryparam cfsqltype="cf_sql_varchar" value="#arguments.fullname#">
)
</cfquery>
<cfquery name="getPkey" datasource="#this.dsn#">
SELECT Max(User_ID) as PKey
FROM Users
</cfquery>
<cfquery datasource="#this.dsn#">
INSERT INTO UserCategories
(
User_ID,
Category_ID
)
VALUES
(
<cfqueryparam cfsqltype="cf_sql_integer" value="#getPKey.PKey#" />
<cfqueryparam cfsqltype="cf_sql_integer" value="#arguments.category_id#" />
)
</cfquery>
<cfreturn "User Added" />
</cffunction>
<cffunction name="editUser" access="remote">
<cfargument name="gridaction">
<cfargument name="gridrow">
<cfargument name="gridchanged">
</cffunction>
</cfcomponent>
Pay attention to these key pieces of the CFC:
a) getUsers() returns the current user data along with their CategoryID. You'll have to re-write this query to match your schema, but the key takeaway is that this is a data population query, so all the data that's necessary for creating a user should also be present for updating the user as well. This also assumes you only have 1 CategoryID per User (which many developers de-normalize by leaving the CategoryID on the Users table--I'll leave that to your discretion).
b) addNewUser() takes expects the form/grid submission to pass along the new name--as well as the category_id--but we know ahead of time we're not going to be asking that the Category_ID be filled out by the person entering the form/grid data--we'll do that programmatically. The end result is still the same, however--the query is going to need to know both values. For brevity, I've left off <CFTRANSACTION> calls, but bear it in mind--you're about to execute three queries in succession, one of which (the 3rd) depends on dynamic data from the other (the 1st and 2nd)--so you'll need to keep concurrency in mind when you move forward with this type of design.
c) Disregard editUser() for now--you will need to populate it at some point--it simply needs to exist for this demonstration to work.
2) The second file you'll need is the front end--the grid itself, we'll call it cfgrid.cfm.
We'll go through this one, top to bottom, as its quite large, and each chunk of code will need explanation:
<cfparam name="URL.Category_ID" default=4 />
The first line of the template parameterizes a URL variable, which we want to use to programmatically provide behind-the-scenes assignment to new users. Use your own mechanism to supply a dynamic Category_ID as needed.
<cfajaxproxy cfc="cfgrid" jsclassname="dataproxy">
This line causes ColdFusion to create a javascript object named 'dataproxy' and wrap it in a container necessary to provide access the core functions that exist in the CFC you point it to...and in this case, you are pointing it to 'cfgrid', which is our first file mentioned above (cfgrid.cfc). Therefore, you can now comfortably expect that you have a javascript object with getUsers() and addNewUser() methods.
<html>
<head>
<script type="text/javascript" src="/CFIDE/scripts/ajax/ext/package/toolbar/toolbar.js"></script>
Here, you begin your HTML document tags, and include a reference to one of the Ajax libraries included in ColdFusion, the toolbar.js file.
<script type="text/javascript">
var dataproxy = new dataproxy();
dataproxy.setCallbackHandler(handleResult);
function handleResult(response)
{
alert(response);
}
Here, you create a local instance of the dataproxy object (remember above, from the <CFAJAXPROXY> call) and assign a callback handler, which points to another javascript function 'handleResult'. This is the process you employ when dealing with asynchronous communication--a fundamental part of working in Ajax.
function init()
{
grid = ColdFusion.Grid.getGridObject("UserGrid");
var gridHead = grid.getView().getHeaderPanel(true);
var tbar = new Ext.Toolbar(gridHead);
tbar.addButton({text:"Add User", handler:onAdd });
}
This init() function creates the javascript objects necessary to drive communication to the cfgrid. You obtain a reference to the cfgrid via ColdFusion.Grid.getGridObject, and from there, access the grid's header, which allows you to target to toolbar, and add a button "Add User", which you then programmatically decide to call a new function when it is clicked...that function is named "onAdd"...
function onAdd(button,event)
{
ColdFusion.Window.show('addUserWin');
}
Unsurprisingly, here is that onAdd() function, which displays a new window to add a user (its the window that contains the input field for the user's fullname).
Finally, the new AddUserWin Window above is going to need its own function to add a user, since we will need to provide the Category_ID dynamically--as opposed to letting the user supply it. So, addUser() will do just that:
function addUser()
{
var f = document.frmUser;
dataproxy.addNewUser(
f.txtFullname.value,
f.txtCategory_ID.value
);
ColdFusion.Window.hide('addUserWin');
grid.refresh();
}
</script>
</head>
In addUser(), we refer to the <FORM> below by its name (frmUser), and call our javascript proxy object's addNewUser() method--which maps to the CFC. As expected, it'll need to know the values of the new user, so we'll pass it the value of txtFullname and txtCategory_ID. We finish by hiding the window and refreshing the grid.
Remember, we're asynchronous now, so we don't need to read a result and display it--that result will fire via the callback handler assigned above in the handleResult() method.
Now, build the arguments that will feed the population of the CFGRID:
<cfset args = StructNew() />
<cfset args.name = "UserGrid" />
<cfset args.format = "html" />
<cfset args.bindOnLoad = "true" />
<cfset args.bind = "cfc:cfgrid.getUsers({cfgridpage},{cfgridpagesize},{cfgridsortcolumn},{cfgridsortdirection})" />
<cfset args.selectmode = "edit" />
<cfset args.onchange = "cfc:cfgrid.editUser({cfgridaction},{cfgridrow},{cfgridchanged})" />
Here we:
1. Name the grid "UserGrid" (as we have referred to it by that name in javascript above),
2. Make it render using html,
3. Tell it to bind its data when the page loads,
4. Bind that data via the cfgrid.cfc, calling the getUsers() method (and passing in the current parameters of the cfgrid via its page, pagesize, sortcolumn, and sortdirection values),
5. Make it editable, and
6. Assign an onChange handler, in case we also want to allow the users to be edited. This last part (unfortunately) is necessary, so I was unable to strip it out for this example.
Now, build the <CFFORM> and <CFGRID>:
<cfform>
<cfgrid attributeCollection="#args#">
<cfgridcolumn name="User_ID" display="false">
<cfgridcolumn name="Category_ID" display="false">
<cfgridcolumn name="FullName" header="Full Name">
</cfgrid>
</cfform>
Here, we populate the grid with our arguments specified above, and you'll note we display only the "FullName" field on the grid, yet User_ID and Category_ID are still present, and a part of the dataset; they simply aren't displayed to the front-end.
Last but not least, the window that will pop-up when a user clicks the "Add New User" button, which provides the interface we need to allow user entry, while at the same time, control (behind the scenes) what Category_ID is dynamically supplied:
<cfwindow name="addUserWin" modal="true" resizable="false" title="Add New User">
<form name="frmUser">
<input type="hidden" name="txtCategory_ID" value="<cfoutput>#URL.Category_ID#</cfoutput>" />
<table width="100%">
<tr>
<td>Fullname</td>
<td><input type="text" name="txtFullname" value=""></td>
</tr>
<tr>
<td colspan="2"><input type="button" value="Add User" onclick="javascript:addUser();"></td>
</tr>
</form>
</cfwindow>
This call to CFWINDOW provides the necessary "pop-up" to encapsulate the form. Note that the form name is frmUser, as we have referred to it above in code. Also, note that the name of the fields match (including their case) what are referred to in javascript. This form displays the Fullname field to the user to fill out, while the Category_ID stays hidden, but is still programmatically driven by you--via your URL parameter at the top of this code example. Finally, the button, when clicked, fires the addUser() method, which you'll recall is the one that talks to the javascript object--which in turn--talks to the CFC--and commits your data to the database.
Finally, just before you complete this template, don't forget to fire your init() javascript function!
<cfset ajaxOnLoad("init")>
</html>
Hope this helps.
Adapted from CRUD with cfgrid html format (Source: Anuj Gakhar)
There is another way. It involves handling directly the form variables which are posted by CFGRID. Here's some example code:
form.cfm:
<cfform method="post" action="post.cfm">
<cfoutput><input type="hidden" name="ParentID" value="#ParentID#"></cfoutput>
<cfgrid format="html" name="GridData" query="Records" insert="yes" delete="yes" selectmode="edit">
<cfgridcolumn name="RecordID" display="no">
<cfgridcolumn name="RecordName" width="150" header="Name" headeralign="left" dataalign="left" select="Yes" display="Yes">
<cfgridcolumn name="RecordColor" width="150" header="Color" headeralign="left" dataalign="left" select="Yes" display="Yes">
</cfgrid>
<br />
<input type="submit" value="Save Records" />
</cfoutput>
</cfform>
then in post.cfm
<cfif isDefined("GridData.RowStatus.Action") and isArray(GridData.RowStatus.Action)>
<cfloop from="1" to="#ArrayLen(GridData.RowStatus.Action)#" index="i">
<cfswitch expression="#GridData.RowStatus.Action[i]#">
<cfcase value="I">
<cfquery name="Records_INSERT" datasource="#request.maindatasource#" blockfactor="100">
INSERT INTO Records (RecordName, RecordColor, RecordParent)
VALUES (
<cfqueryparam cfsqltype="cf_sql_varchar" value="#Trim(GridData.RecordName[i])#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#Trim(GridData.RecordColor[i])#">,
<cfqueryparam cfsqltype="cf_sql_integer" value="#Val(ParentID)#">
)
</cfquery>
</cfcase>
<cfcase value="U">
<cfquery name="Records_UPDATE" datasource="#request.maindatasource#" blockfactor="100">
UPDATE Records
SET
RecordName = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Trim(GridData.RecordName[i])#">,
RecordColor = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Trim(GridData.RecordColor[i])#">
WHERE
RecordID=<cfqueryparam cfsqltype="cf_sql_integer" value="#GridData.original.RecordID[i]#">
</cfquery>
</cfcase>
<cfcase value="D">
<cfquery name="Records_DELETE" datasource="#request.maindatasource#" blockfactor="100">
DELETE
FROM Records
WHERE
RecordID=<cfqueryparam cfsqltype="cf_sql_integer" value="#GridData.original.RecordID[i]#">
</cfquery>
</cfcase>
</cfswitch>
</cfloop>
</cfif>