coldfusion: listContains and listFind - list

Whats the difference between listContains() and listFind() / listFindNoCase()?
They are all list functions, take the same parameters, and return the same result.

listContains looks for the value anywhere in a string, so for example
<cfset list = '1,2,33,4,5' />
<cfdump var="#listContains(list,3)#">
Would return 3 because a 3 is found in the 3rd list item.
listFind looks for the value AS one of the list items.
<cfdump var="#listFind(list,3)#">
Returns 0 because 3 is not one of the list items.

Related

Why does this function not return a string that will work ListToArray or ValueList?

I have created a function within a format.cfc component that returns a string without any HTML code in it:
<cffunction name="RemoveHTML" access="public" returntype="string" output="false" hint="Returns a string without any html">
<cfargument name="UserString" required="yes">
<cfset var result = "#REReplaceNoCase(Canonicalize(ARGUMENTS.UserString,false,true),'<[^>]*(?:>|$)', '', 'ALL')#">
<cfreturn result>
</cffunction>
I now want to split the string at each space and convert it into a list. So I tried using ValueList() and ListToArray() but they don't like the value returned from the function.
Using ValueList() I get an error saying:
Complex constructs are not supported with function ValueList
Or I get this error when using ListToArray:
Complex object types cannot be converted to simple values
I'm basically just doing this:
<!--- ValueList() --->
<title>#ValueList(Application.Format.RemoveHTML(UserString = rsProduct.Title), ' ')#</title>
<!--- ListToArray() --->
<title>#ListToArray(Application.Format.RemoveHTML(UserString = rsProduct.Title), ' ')#</title>
If I remove the ListToArray() or ValueList() function then I get back what I expect - a product title string with no HTML in it.
So why is the function not returning a string even though it looks like one? Or am I missing something completely obvious?
As others have noted in the comments, ValueList is designed to return a list of values that are contained in a column of a query object. It won't work with a string value.
ListToArray converts a list to an array. You can't then output an array in your HTML. So ListToArray is working fine, it's when you try to display it in a cfoutput that the error occurs.
It's a good idea to use the in-built encoding functions in CF, for example encodeForHTML. So you can do something like:
<title>#encodeForHTML(Application.Format.RemoveHTML(UserString = rsProduct.Title))#</title>
encodeForHTML, can accept an optional boolean 2nd argument (which is false by default), to indicate if you want to canonicalize the string. So you may want to do that instead of calling Canonicalize in your custom RemoveHTML function. After all your function is called RemoveHTML not RemoveHTMLAndCanonicalize :)
update
In response to OP's comment.
To get a comma delimited list from your 'space delimited' string, then you can use the replace function. Something like:
<title>#encodeForHTML(replace(RemoveHTML(rsProduct.Title), " ", ",", "all"))#</title>
You can of course, put the replace inside your custom function, I'm just demonstrating how it works.
You'll need to be aware, that it'll replace all spaces with a comma, so if you have 2 or more spaces in a row then it'll show ,, (depending on how many spaces). To get around that you can use a regular expression like so:
<title>#encodeForHTML(reReplace(RemoveHTML(rsProduct.Title), " +", ",", "all"))#</title>
You can also use listChangeDelims intead of reReplace as it ignores empty elements.
<title>#encodeForHTML(listChangeDelims(RemoveHTML(rsProduct.Title), ",", " "))#</title>
Personally I'd go with the regular expression version as it's more powerful, you'll want to wrap it up in a function though to keep the view nice and clean.
I think there are multiple ways to do this, consider below is my text
<cfset myText = "Samsung Galaxy Note 4"/>
first method by using simple replace function
<cfset firstSolution = replace(myText," ",",","all")/>
<cfdump var="#firstSolution#" />
Second method by using reReplace method
<cfset secondSolution = rEReplace(myText,"\s+",",","all")/>
<cfdump var="#secondSolution#" />
If I would be you I would have use second method cause if by any chance I have multiple spaces in my string then instead of getting multiple ',' I will get single ',' given string is used in the title of the page, I would not take any risk have incorrect title.

Coldfusion: How to split a string into a set of variables

I'm trying to teach myself ColdFusion.
I have a string coming in from the database in this format:
domain.com
<br/>
www.facebook.com/facebookpage
<br/>
http://instagram.com/instagrampage
It is all coming from #getRacer.txtDescription#. The format of this text will always be the same.
I need to split it into 3 variables. I tried this (derived from the example on the adobe website)
<h3>ListToArray Example</h3>
<cfset myList = ValueList(getRacer.txtDescription)>
<p>My list is a list with <cfoutput>#ListLen(myList)#</cfoutput> elements.
<cfset myArrayList = ListToArray(myList,'<br/>')>
<p>My array list is an array with
<cfoutput>#ArrayLen(myArrayList)#</cfoutput> elements.
I somehow ended up with 11 items in the array.
Thank you
This should work.
<cfset TestSTring = "domain.com<br/>www.facebook.com/facebookpage<br/>http://instagram.com/instagrampage">
<cfset a = TestString.Split("<br/>")>
The reason ListtoArray is displaying 11 items is because ColdFusion treats each character in the delimiter string (<br/>) as a separate delimiter
Based on #Leigh's comment updating my answer to ensure people should learn the Coldfusion APIs rather than fiddling with Java functions, <cfset a = ListToArray(TestString, "<br/>", false, true)> will also work. Thanks Leigh.
Note: The false at the end is for the includeEmptyFields flag and the true is for the multiCharacterDelimiter flag. See the docs.
<cfset myList = ReplaceNoCase(getRacer.txtDescription,'<br/>','|','ALL')>
<cfset myArrayList = ListToArray(myList,'|')>
I chose a pipe character because it is unlikely to already exist in your string. If you wanted to account for the possibility that your BR tag may or may not use XML syntax then you could you regex:
<cfset myList = ReReplaceNoCase(str,'<br/?>','|','ALL')>
<cfset myArrayList = ListToArray(myList,'|')>

Deleting associated objects from an array ColdFusion

I have a shopping cart array which holds the product information of items in the cart. Some products which are added will be at a special discount if purchased with the "main item".
If someone adds an item which has special offers associated to it, I set a key in the item structure called mainitem with a value of yes, all the subsequent special offer items associated to the main item have key mainitem set as no and have another key named mainitemid (this is the mainitem uid).
If some one deletes the mainitem I need to ensure any related special offer items are deleted as well. This is what I am having trouble with, can't quite work out how to find them all.
I am using form.itemID to supply the item id of the product being deleted. I need to make sure the item being deleted is a main item; if so, loop through the rest of the cart and find any item with mainitemid equal to form.itemid and remove them, then delete the mainitem.
mainitem is defined as session.mycart[i].mainitem
maintitenid is defined as session.mycart[i].mainitemid
<cfloop index="i" from="1" to="#arrayLen(session.mycart)#">
</cfloop>
Do I need to create two loops or could I do it with one? I'm not sure of my conditional statements.
Solution for OPs specific issue
Revised to offer a more complete solution
<!--- First of all, will need to go 1 to ArrayLen() to find the main item id. Doesn't matter what order you go here, but main items should be inserted before children so may as well start at the bottom --->
<cfloop index="i" from="1" to="#ArrayLen(session.mycart)#">
<!--- Is this the main item? --->
<cfif session.mycart[i].id EQ form.itemID>
<!--- It's found the item, is it a main item? We've found the item so entering here to break the loop --->
<cfif session.mycart[i].mainitem>
<!--- Go from ArrayLen() to 1, as if you go 1 to ArrayLen() when deleting you confuse Coldfusion --->
<cfloop index="j" from="#ArrayLen(session.mycart)#" to="1" step="-1">
<cfif session.mycart[j].id EQ form.itemID OR session.mycart[j].mainitemid EQ form.itemID>
<cfset ArrayDeleteAt(session.mycart,j)>
</cfif>
</cfloop>
</cfif>
<!--- This loop is done, so break out --->
<cfbreak>
</cfif>
</cfloop>
In your post, you state you are looping from index 1 to index ArrayLen(...). If you are deleting items from the array though, Coldfusion is a bit straightforward and doesn't really pay attention and so when you delete element 2 of a 5-element array, the array becomes 4 elements long (because you deleted one) and the element which was index 3 is now index 2, thus it is missed.
The way around this is to start at the end and work backwards. As long as you are deleting at most 1 record at a time, then this is perfectly valid as it will continue reducing the index it is currently checking until you get down to 1, which will be the first element.
This way you can go through element 5, 4, 3, 2, delete element 2, and it will then check index 1 which now will still be the same item as it was when you started the loop and thus no skipping is experienced.
Some blurb on how to deal with this
I misread the question, or it got edited while writing this, but the below is applicable so leaving it here still
Have you considered having special offer items as children to the main item, as then it encapsulates the entire offer together and deleting the parent deletes the child. I've a similar issue where items have options associated with them, and to allow hierarchy to be observed I decided on creating a tree combined with a reference array. Just as a rough example to show the principle, take a look at this
<cfscript>
// Create a main item structure
stcMainItem = {
id = CreateUUID(),
somekey = somevalue,
someotherkey = someothervalue,
cost = 123
};
// Create some child item structures, special offers for ex
stcSpecialOfferItem1 = {
id = CreateUUID(),
parent = stcMainItem.id,
cost = 45
};
stcSpecialOfferItem2 = {
id = CreateUUID(),
parent = stcMainItem.id,
cost = 45
};
// Each is added to a reference array
arrItemReference = [];
arrItemRefernce.add(stcMainItem);
arrItemRefernce.add(stcSpecialOfferItem1);
arrItemRefernce.add(stcSpecialOfferItem2);
// Now you decide to delete the main item and direct parents
strIDToDelete = stcMainItem.id;
for (i=ArrayLen(arrItemReference);i>=1;i--) {
if (
arrItemReference[i].id == strIDToDelete
||
arrItemReference[i].parent == strIDToDelete
) {
ArrayDeleteAt(arrItemReference,i);
}
}
</cfscript>
In my actual code I've done this by way of creating an Item.cfc with methods to deal with the above and to cascade up the tree deleting grandchildren etc, but the principle is sound. Essentially you have methods that allow the items to be exposed as both a flat array and as a hierarchy of parents, children, and siblings.
Informational point
As an aside, you keep interchanging "array" and "structure" when the two are slightly different unlike languages like PHP where array is used to refere to both terms. An array contains values with a numeric index from 1 up to n, while a structure is an object that holds values related to arbitary keys. The reason that there is a difference though is that they don't build off the same base java objects so some methods that work on one don't work on the other.
<cfloop index="i" from="1" to="#arrayLen(session.mycart)#">
<cfif session.mycart[i].mainitem eq "no" AND session.mycart[i].mainitemid EQ form.itemid>
<cfset arrayDeleteAt(session.myCart,i) />
<cfset i = i - 1 /><!--- this is necessary to keep you from running out of the end of the loop --->
</cfif>
</cfloop>
This isnt tested of course and you may have to work with it to put your exact variable names in, but I think it should get you there. Let me know if I have misunderstood the question.
I don't see a way to do this in one loop, as I view it as a two step process. First you have to see if the item id you deleted is a main item, then go back through the array and delete the rest of the associated items.
Add StructKeyExists() as necessary.
<!--- Find Main Item --->
<cfset isMainItem = false />
<cfloop from='1' to='#ArrayLen( Session.MyCart )#' index='item'>
<cfif Session.MyCart[ item ].ItemID EQ Form.ItemID AND Session.MyCart[ item ].MainItem EQ 'Yes'>
<cfset isMainItem = true />
<cfbreak />
</cfif>
</cfloop>
<cfif isMainItem>
<!--- Clean up special offer items --->
<cfloop from='1' to='#ArrayLen( Session.MyCart )#' index='item'>
<cfif Session.MyCart[ item ].MainItemID EQ Form.ItemID AND Session.MyCart[ item ].MainItem EQ 'No'>
<cfset ArrayDeleteAt( Sesion.MyCart, Item ) />
<cfset item-- />
</cfif>
</cfloop>
</cfif>

Am i at the end of a list ColdFusion

In ColdFusion how do I tell if I'm at the end of a list. I know about listLast, but this just returns the last data in the list. I want to know when the list has finished. I need to know this so i can change a string accordingly.
ie. mystring = product with list1, list2 and listlast
Its so i know when to add the "and" to my output.
Jason
listLen() will give you the total of items. Then as you loop through the list check if you've reach listLen()
How about using ListLen(list [, delimiters ])? It returns the number of elements in a list.
<cfscript>
var i = 1;
var listLength = ListLen(mystring);
for (i = 1; i lte listLength; i++)
{
product = ListGetAt(mystring, i);
}
</cfscript>
Here's a reference of other List Functions.
#Jason Congerton
use index="i" (or anything) and output in the list using #i#
You could also use
<cfloop from="1" to="listlen(yourlist)#" index="i">
Number #i# #ListGetAt(yourList, i)#<br>
</cfloop>
i gives you the placement in the list and the ListGetAt() function pulls out the value in that place in the list. This will work if your list is 1 or 10,000 names.
CF function list.
http://livedocs.adobe.com/coldfusion/8/htmldocs/help.html?content=functions-pt0_13.html
specifically, look at ListLast() function.
The last user in the list is: ListLast(temp)
Not sure what you're doing that requires you to know the last element of the list but you can also just use cfloop list =
<cfset myList = "me,myself,i">
<cfoutput>
<cfloop list = "#myList#" index = "i">
#i#
</cfloop>
</cfoutput>

How to re-sort a structure using byte ordering lexicographically?

Developing with an API, I have a structure in ColdFusion. I need to re-sort the structure using byte ordering lexicographically.
"Sort the parameters by name lexicographically [sic] (byte ordering, the standard sorting, not natural or case insensitive). If the parameters have the same name, then sort by the value."
Taking a structure in ColdFusion 9, how can I reorder it to comply with the above? JAVA Lib?
Thanks
I'm going to post this as a separate answer, because I believe my first one is incorrect... Let's try this one instead:
<cfset myStruct = structNew() />
<cfset mystruct["Apple"] = 1 />
<cfset mystruct["Banana"] = 2 />
<cfset mystruct["car"] = 5 />
<cfset mystruct["Tomato"] = 3 />
<cfset mystruct["aardvark"] = 4 />
<cfset Keys = StructKeyArray(myStruct) />
<cfset ArraySort(Keys, "textnocase") />
<cfdump var="#Keys#">
That will give you an array of keys sorted lexicographically, ignoring all casing. The StructSort function was sorting on the key values, not the key names.
Sorry for my confusion, but aren't lexicographic sorting and natural sorting (at least with Java Strings) the same thing? If so, take a look at the Java TreeMap and see if this works the way you want it to.
<cfset myStruct = structNew() />
<cfset mystruct["Apple"] = 1 />
<cfset mystruct["Banana"] = 2 />
<cfset mystruct["car"] = 5 />
<cfset mystruct["Tomato"] = 3 />
<cfset mystruct["aardvark"] = 4 />
<cfset myMap = createObject("java","java.util.TreeMap").init(myStruct) />
<cfdump var="#myMap#">
You can't technically sort a structure, and guarantee that the structure will maintain it's order. ColdFusion has a bad habit of arbitrarily (at least as far as I can tell) reordering structures when you add/remove nodes.
You can, however, get a list of sorted keys, which you can then use to loop through your struct. Use the StructSort method to get an array of sorted key names. You can specify the sort order as textnocase to get your lexicographic order (disregards all casing).
You would then need to do some additional sorting to get them by value after that... If you want more detail, you'll be better off posting some code you've already tried that's not quite working.