Flex TextFlowUtil.importFromString ignoring whitespace in some cases. Is this a bug? - apache-flex

In the following complete functional Flex application the line breaks between the two links ought to be preserved when importing the input text into the TextFlow:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
minWidth="955" minHeight="600"
creationComplete="application1_creationCompleteHandler(event)"
>
<fx:Script>
<![CDATA[
import flashx.textLayout.formats.WhiteSpaceCollapse;
import mx.events.FlexEvent;
import spark.utils.TextFlowUtil;
protected function application1_creationCompleteHandler(event : FlexEvent) : void
{
input.text = "<a href='#'>link1</a>\n<a href='#'>link2</a>";
}
protected function button1_clickHandler(event : MouseEvent) : void
{
output.textFlow = TextFlowUtil.importFromString(input.text, WhiteSpaceCollapse.PRESERVE);
}
]]>
</fx:Script>
<s:RichEditableText text=""
id="input"
width="266" height="215"
x="10" y="30"
/>
<s:Label text="Input"
x="10" y="10"
/>
<s:Label text="Output"
x="8" y="286"
/>
<s:Button x="10" y="253"
click="button1_clickHandler(event)"
label="Import"
/>
<s:RichEditableText id="output"
width="399" height="212"
x="10" y="306"
/>
</s:Application>
But if you click "Import" you will see the two links created in the same line (no break).
This changes if you add any non-whitespace character between the links in the source text. Meaning, if you just add a "*" between the links the line breaks will be preserved.
What gives? Is this a Flex bug?

You may need to adjust the global XML parsing settings. These are accessible as static properties of the XML class. My hunch is that XML.ignoreWhitespace is true at the time of parsing. Try the following:
XML.ignoreWhitespace = false;
output.textFlow = TextFlowUtil.importFromString(input.text, WhiteSpaceCollapse.PRESERVE);

This is the way to solve this bug, and there is no need for changing any XML settings, simple and it works:
Exporting the TextFlow:
var richTextXML:xml = new XML("<"+"richTextXML"+"/>");
richTextXML.appendChild(getCdataXMl());
private function getCdataXMl():xml {
var textFlowStr:String = TextConverter.export(textFlow,TextConverter.TEXT_FIELD_HTML_FORMAT, ConversionType.STRING_TYPE).toString();
var textFlowXMl:xml = new XMl("<![CDATA["+textFlowStr+"]]>");
return textFlowXMl;
}
Importing TextFlow from XML:
var htmlTextInStr:String = richTextXMl.text();
var importtedTextFlow:TextFlow = TextConverter.importToFlow(htmlTextInStr,TextConverter.TEXT_FIELD_HTML_FORMAT);
textArea.textFlow = importtedTextFlow;

Related

Flex datagrid header row right click

I got a Datagrid in my Flex application.
I need to make appear a context menu when the header row is right-clicked.
The latter context menu must not appear when the rest of the datagrid items (the ones containing data) are clicked.
Edit: the application runs in AIR environment, so i got no flash-player troubles
In flex, and more generally in flash, there is no way to catch the the right click event.
I'm not sure about the right mouse click, cos flex apps run in flash player, and right click brings up its menu.
The best bet would be to use headerRelease event on your DatagRid. In your event handler you can then create your menu (maybe in a popup or some hovering panel?) and then do what you need to do there.
Read more about it here
edit:
Maybe you could use a contextMenu class, and attach it to your dataGrid.contextMenu?
Below code may help you: -
I have created sample in which i have added only one ITEM. You can convert it and change logic as per requirement. My idea is to provide one of the base logic. You may gey better solution but this can work for you.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600"
>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:layout>
<s:VerticalLayout/>
</s:layout>
<mx:DataGrid id="myDG" width="350">
<mx:dataProvider>
<mx:ArrayCollection>
<mx:source>
<fx:Object Artist="" Price="11.99"
Album="Slanted and Enchanted" />
<fx:Object Artist=""
Album="Brighten the Corners" Price="11.99" />
</mx:source>
</mx:ArrayCollection>
</mx:dataProvider>
<mx:columns>
<mx:DataGridColumn dataField="Artist" headerRenderer="StackLabelRenderer"/>
<mx:DataGridColumn dataField="Album" headerRenderer="StackLabelRenderer"/>
<mx:DataGridColumn id="price" dataField="Price" headerRenderer="StackLabelRenderer"/>
</mx:columns>
</mx:DataGrid>
</s:Application>
StackLabelRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Label xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
click="dispatchClickEvent()">
<fx:Script>
<![CDATA[
import mx.controls.DataGrid;
import mx.controls.dataGridClasses.DataGridColumn;
import mx.controls.dataGridClasses.DataGridListData;
import mx.core.mx_internal;
private function dispatchClickEvent():void
{
trace("Item Clicked")
}
import mx.controls.Alert;
[Bindable]
private var cm:ContextMenu;
override protected function createChildren():void
{
cm = new ContextMenu();
cm.hideBuiltInItems();
cm.addEventListener(ContextMenuEvent.MENU_SELECT, contextMenu_menuSelect);
this.contextMenu = cm;
}
private function contextMenu_menuSelect(evt:ContextMenuEvent):void {
//condition to check length of column data length
var allNull:Boolean=true;
var columnName:String = DataGridColumn(data).headerText;
for each(var o:Object in DataGrid(owner).dataProvider) {
if(o[columnName] != "") {
allNull=false;
break;
}
}
if(!allNull)
{
var cmi:ContextMenuItem = new ContextMenuItem("First Element...", true);
cmi.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, contextMenuItem_menuItemSelect);
cm.customItems = [cmi];
}
}
private function contextMenuItem_menuItemSelect(evt:ContextMenuEvent):void {
}
]]>
</fx:Script>
</mx:Label>

Dynamically create an embedded image

I need to dynamically create a LinkButton with an icon. The names of files (icons) have the format images/icon_0.png, images/icon_1.png, ... images/icon_1000.png. But I don't know the specific image for this button. I only know the index of the icon.
I tried this, with no success:
var path:String = "#Embed(source='images/icon_" + imageindex + ".png')";
myButton.setStyle("icon", path);
I get a runtime error:
Type Coercion failed:
*cannot convert "#Embed(source='images/icons/icon_427.png')" to Class*
Sorry that will not work.
Since imageindex is a compile-time variable, then embedding tag will trigger an error message.
Why not to override the button and add extra property like 'iconPath' that will expect a string path instead of a Class object. This way you can manually set (inside extended Button) the icon.source = iconPath without having to use embed.
pls try this.
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" creationComplete="application1_creationCompleteHandler(event)"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
[Embed(source='icon_1.png')]
[Bindable]
private var linkButtonIcon:Class;
protected function application1_creationCompleteHandler(event:FlexEvent):void
{
lnkbutton.setStyle("icon",linkButtonIcon);
}
protected function button1_clickHandler(event:MouseEvent):void
{
[Embed(source='icon_2.png')]
var linkButtonIcon2:Class;
lnkbutton.setStyle("icon",linkButtonIcon2);
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:VGroup>
<mx:LinkButton label="test" id="lnkbutton"/>
<s:Button label="change Icon" click="button1_clickHandler(event)"/>
</s:VGroup>
</s:Application>

Unknown error with <mx:Repeater> in Flex Builder 4.5.1 Mobile

I recently learned how to work with API's in Flash Builder. So I made this (note: it has no errors and it works in a normal Flex application:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="957" minWidth="800" minHeight="600"
creationComplete="initApp(event)">
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
<s:HTTPService id="myRequest"
resultFormat="e4x"
url="{api_request}"
result="getMyPhotos(event)">
</s:HTTPService>
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.collections.XMLListCollection;
import mx.events.FlexEvent;
import mx.rpc.events.ResultEvent;
[Bindable]
public var api_request:String;
protected function initApp(event:FlexEvent):void
{
btnClear.addEventListener(MouseEvent.CLICK, leegAlleVelden);
btnSearch.addEventListener(MouseEvent.CLICK, zoekOpFlickr);
flickrFotoCollection.addEventListener(MouseEvent.CLICK, haalFoto);
}
protected function zoekOpFlickr(event:MouseEvent):void
{
var api_URL:String = 'http://api.flickr.com/services/rest/';
var api_method:String = 'flickr.photos.search';
var api_key:String = '290845e3c32e4e10a9e4b09fd993ddb9';
var api_tags:String = txtSearchString.text;
api_request = api_URL + '?method=' + api_method +
'&api_key=' + api_key + '&tags=' + api_tags;
myRequest.send(); // hier maak je connectie met flickr
}
//http://api.flickr.com/services/rest/?method=flickr.photos.search&api_key=290845e3c32e4e10a9e4b09fd993ddb9&tags=cat&format=rest&api_sig=93e43cc21b7209d2e59af67736d27eb3
protected function leegAlleVelden(event:MouseEvent):void
{
txtSearchString.text="";
}
protected function getMyPhotos(event:ResultEvent):void
{
//photos allemaal ingelezen
flickrFotoCollection.dataProvider = new XMLListCollection(myRequest.lastResult.photos.photo);
}
public function haalFoto(selectedFoto:Object):String {
// http://farm{farm-id}.static.flickr.com/{server-id}/{id}_{secret}_[mstb].jpg
var farm_id:String = selectedFoto.#farm;
var server_id:String = selectedFoto.#server;
var id:String = selectedFoto.#id;
var secret:String = selectedFoto.#secret;
var size:String = 't';
var api_selectedPhotoURL:String = "http://farm"+farm_id+".static.flickr.com/"+server_id+
"/"+id+"_"+secret+"_"+size+".jpg";
return api_selectedPhotoURL;
}
public function toonFoto(event:MouseEvent):void
{
}
]]>
</fx:Script>
<s:BorderContainer x="0" y="21" width="800" height="100%" backgroundColor="#000000">
<s:HGroup x="30" y="14">
<s:TextInput x="0" y="23" width="267" id="txtSearchString"/>
<s:Button x="289" y="24" label="Search" id="btnSearch" click="zoekOpFlickr(event);"/>
<s:Button x="394" y="24" label="Clear" id="btnClear" click="leegAlleVelden(event);"/>
</s:HGroup>
<s:HGroup x="30" y="54" width="742" height="500">
<mx:Tile width="399" height="500">
<mx:Repeater id="flickrFotoCollection" dataProvider="{myRequest.lastResult.photos.photo}">
<s:Image id="fotoKlein"
source="{haalFoto(flickrFotoCollection.currentItem)}"
click="toonFoto(event);"
width="80" height="80"
scaleMode="stretch"/>
</mx:Repeater>
</mx:Tile>
<s:Image id="fotoGroot" width="336" height="500"/>
</s:HGroup>
</s:BorderContainer>
</s:Application>
Next, I wanted to see if I could get it to work in a 4.5.1 mobile application. I didn't get any errors except for a "could not resolve <mx:Repeater>..." At line 87. So for some reason it doesn't recognize the <mx:Repeater> tag?
I tried to find the cause of this problem, but didn't find it.
Is there a reason why it works in a normal application, but not in a mobile application?
The MX Components are not supported in a Mobile Project. For that code to compile in a Flash Builder mobile project you will have to manually add the SWC with MX components to the library path. Be prepared for poor performance, though.

How do I use data from the main window in a sub-window?

I've just started working on a photo viewer type desktop AIR app with Flex. From the main window I can launch sub-windows, but in these sub-windows I can't seem to access the data I collected in the main window.
How can I access this data?
Or, how can I send this data to the sub-window on creation? It doesn't need to be dynamically linked.
myMain.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="260" height="200"
title="myMain">
<fx:Declarations>
</fx:Declarations>
<fx:Script>
<![CDATA[
public function openWin():void {
new myWindow().open();
}
public var myData:Array = new Array('The Eiffel Tower','Paris','John Doe');
]]>
</fx:Script>
<s:Button x="10" y="10" width="240" label="open a sub-window" click="openWin();"/>
</s:WindowedApplication>
myWindow.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Window name="myWindow"
title="myWindow"
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
width="640" height="360">
<mx:Script>
<![CDATA[
]]>
</mx:Script>
<mx:Label id="comment" x="10" y="10" text=""/>
<mx:Label id="location" x="10" y="30" text=""/>
<mx:Label id="author" x="10" y="50" text=""/>
</mx:Window>
I realize this might be a very easy question but I have searched the web, read and watched tutorials on random AIR subjects for a few days and couldn't find it. The risk of looking like a fool is worth it now, I want to get on with my first app!
You could add an attribute to your window class, and pass the data from the application.
With an attribute and a setter function :
myWindow.mxml :
<![CDATA[
private var _data : Array;
public function set data(data : Array) : void {
this._data = data;
}
]]>
main
<![CDATA[
public function openWin():void {
var w : myWindow = new myWindow();
w.data = myData;
w.open();
}
public var myData:Array = new Array('The Eiffel Tower',
'Paris','John Doe');
]]>
You could also do it by adding a constructor parameter to your window, but you will have to write your Window component in ActionScript.
(Also : you might want to use MyWindow for the name of your component instead of myWindow, but that's just conventionnal nitpicking).
Also, note that there is a singleton variable Application.application that is accessible to all classes in an Application ; however I don't know if this applies to a WindowedApplication, and either way it is not the recommended approach.

Working on a Global Search tool - Just like on MAC

Hi I am working on a search tool for my website in Flex. I want it to work exactly like the "Spotlight" tool on MAC desktop. "http://www.recipester.org/images/6/66/How_to_Use_Spotlight_to_Search_on_Mac_OS_X_42.png" The link is to an image of spotlight.
I want to create almost the same thing in FLEX.
What I currently have is a "Autocomplete" box, and I get all the data I want in it. Code for the autocomplete is below:
<auto:AutoComplete borderStyle="none" id="txt_friends_search"
textAlign="left" prompt="Search Friends" dataProvider="{all_friends_list}"
allowEditingNewValues="true" allowMultipleSelection="true" allowNewValues="true"
backspaceAction="remove" labelField="label"
autoSelectEnabled="false" matchType="anyPart"
height="23" right="400" top="1" dropDownItemRenderer="{new ClassFactory(weather.index_cloud_global_search_item_renderer)}" />
And my ItemRenderer looks like :
<?xml version="1.0" encoding="utf-8"?>
<mx:HBox
xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%" height="100%"
verticalGap="0" horizontalGap="0"
creationComplete="init()"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
verticalAlign="middle">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.collections.ArrayCollection;
import com.hillelcoren.utils.StringUtils;
import mx.utils.StringUtil;
import mx.events.FlexEvent;
import mx.controls.List;
public function init():void
{
}
]]>
</mx:Script>
<mx:HBox width="100%" verticalGap="0" horizontalGap="0">
<mx:HBox borderThickness="1" width="75" borderStyle="solid" horizontalAlign="left" horizontalScrollPolicy="off">
<mx:Label id="type" text="{data.type}" fontSize="12"/>
</mx:HBox>
<mx:HBox borderThickness="1" width="75" borderStyle="solid" horizontalAlign="left" horizontalScrollPolicy="off">
<!--mx:Label id="nameLabel" text="{data.label}" fontSize="12"/-->
<mx:List id="names" dataProvider="{all}"
</mx:HBox>
</mx:HBox>
<!--mx:Box id="colorBox" borderStyle="solid" width="50" height="25"/-->
<mx:Spacer width="15"/>
This shows the type and label of everything, example:
Friends ABC
Friends XYZ
Messages This is the message
Messages example for messages
Files filename1
Files filename123
I believe you get my point there.
But what I want to create is something like:
Friends ABC
XYZ
Messages This is the message
example for messages
Files filename1
filename123
MoreFiles
Can someone plz help me in this.
I actually have no idea how to move forward in this.
Let me know if you want more clarification on anything.
Regards
Zeeshan
Since you're offering a bounty, I'll submit a different answer (as the previous one is technically valid).
Step #1: Download the Adobe Autocomplete Component integrate the class into your project.
Step #2: Create a new component that is derived from AutoComplete (I called mine SpotlightField.mxml)
<?xml version="1.0" encoding="utf-8"?>
<AutoComplete
xmlns="com.adobe.flex.extras.controls.*"
xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="init()"
labelField="value"
itemRenderer="SpotlightFieldRenderer">
<mx:Script>
<![CDATA[
private function init() : void
{
this.filterFunction = substringFilterFunction;
}
private function substringFilterFunction(element:*, text:String):Boolean
{
var label:String = this.itemToLabel(element);
return(label.toLowerCase().indexOf(text.toLowerCase())!=-1);
}
]]>
</mx:Script>
</AutoComplete>
Step #3: Create the ItemRenderer you want to apply to this new component (I called mine SpotlightFieldRenderer.mxml). Note that the code is the same as the previous example, but I'll post it again for completeness.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
]]>
</mx:Script>
<mx:HBox width="100%">
<mx:Label width="100" text="{data.type}" />
<mx:Label text="{data.value}" />
</mx:HBox>
</mx:Canvas>
Step #4: Update the AutoComplete.as class as follows:
/**
* #private
* Updates the dataProvider used for showing suggestions
*/
private function updateDataProvider():void
{
dataProvider = tempCollection;
collection.filterFunction = templateFilterFunction;
collection.refresh();
sort_and_filter(collection);
//In case there are no suggestions, check there is something in the localHistory
if(collection.length==0 && keepLocalHistory)
{
var so:SharedObject = SharedObject.getLocal("AutoCompleteData");
usingLocalHistory = true;
dataProvider = so.data.suggestions;
usingLocalHistory = false;
collection.filterFunction = templateFilterFunction;
collection.refresh();
}
}
private function sort_and_filter(source:Object):Object
{
if (source && source.length > 1) {
trace (source.length);
source.sortOn('type', Array.CASEINSENSITIVE);
var last:String = "";
for each(var entry:Object in source) {
var current:String = entry.type;
if (current != last)
last = current
else
entry.type = "";
last = entry.type;
}
}
return source;
}
You'll notice that the sort_and_filter function is defined, and called on the collection within updateDataProvider. The app now looks like this:
That's it. The sample application now looks like this:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">
<mx:Script>
<![CDATA[
[Bindable]
private var items:Array = [
{ type:'friends', value:'abc' },
{ type:'friends', value:'xyz' },
{ type:'messages', value:'this is the message' },
{ type:'messages', value:'example for messages' },
{ type:'files', value:'filename1' },
{ type:'files', value:'filename123' },
];
]]>
</mx:Script>
<local:SpotlightField dataProvider="{items}" width="400" />
</mx:Application>
Let me know if you have any further questions. There is still a bit of work to do depending on how you want to display the results, but this should get you 95% of the way there ;)
You may want to try something like this. This is just a sample I whipped up, but the basics are there for you to apply to your solution. What this is doing is creating a custom item render (as you've already done), but the container that it's rendering, it adjusts the data set slightly within set dataProvider so that it sorts and filters.
Obviously, you can expand upon this even further to add common icons, formatted text ... etc. The renderer has an explicit width set for the first "column" text. This is to better align results, but should probably be done while the list is being built (based on the string lengths of the values in the result set). Cheers ;)
Application.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*">
<mx:Script>
<![CDATA[
[Bindable]
private var items:Array = [
{ type:'friends', value:'abc' },
{ type:'friends', value:'xyz' },
{ type:'messages', value:'this is the message' },
{ type:'messages', value:'example for messages' },
{ type:'files', value:'filename1' },
{ type:'files', value:'filename123' },
];
]]>
</mx:Script>
<local:SpotlightComboBox
dataProvider="{items}"
width="400" />
</mx:Application>
SpotlightComboBox.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:ComboBox
xmlns:mx="http://www.adobe.com/2006/mxml"
itemRenderer="SpotlightComboBoxItemRenderer">
<mx:Script>
<![CDATA[
override public function set dataProvider(value:Object):void
{
super.dataProvider = sort_and_filter(value as Array);
}
private function sort_and_filter(source:Array):Array
{
if (source && source.length > 1) {
source.sortOn('type', Array.CASEINSENSITIVE);
var last:String = "";
for each(var entry:Object in source) {
var current:String = entry.type;
if (current != last)
last = current
else
entry.type = "";
last = entry.type;
}
}
return source;
}
]]>
</mx:Script>
</mx:ComboBox>
SpotlightComboBoxItemRenderer.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
]]>
</mx:Script>
<mx:HBox width="100%">
<mx:Label width="100" text="{data.type}" />
<mx:Label text="{data.value}" />
</mx:HBox>
</mx:Canvas>

Resources