Showing posts with label Bridge. Show all posts
Showing posts with label Bridge. Show all posts

Sunday, 11 December 2011

Adobe XMP Panel - Improvements

After publishing the original post about creating a custom XMP panel ( see here ), I was contacted via the Resource Space forum about wether it was possible to create multiple sets of check boxes on the panel that related to different XMP fields, ie:

chk1,chk2,chk3 = XMP field 1
chk4,chk5,chk6 = XMP field 2

I posted on the forum that this would be easy to do using multiple handlers, and calling the relevant handler function when a checkbox is ticked.
Now many of you will note that this is not an elegant solution, but it would work, and was easy to explain!
I started thinking about this further, and came up with a more elegant way to achieve the task, that was also much easier to expand upon as there was only one place you needed to add extra code.
So this post will attempt to describe what I've done, and hopefully make it easier for others to reuse the code.

Step 1 - Layout the Panel
The first thing we need to do is layout the panel, as before this is done using the grid control, and using a naming convention for the id's of the checkboxes that relate to the label of the checkbox but without spaces.

<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Road Haulage"
id="_chkRoadHaulage"    click="updateCats(event,'docTitle')"/>
</mx:GridItem>

The label "Road Haulage" above has an id of "_chkRoadHaulage" so that we can reference it in code easily, as show in the previous article.
The major change to the layout of the checkboxes is the click event. The function that is called now has a second parameter that is the id of the XMP text input that the check box should modify.

<fi:XMPTextInput id="docTitle"
xmpPath="mc:MainCategory
xmpType="Localized"
name="docTitle"
visible="false"
/>

The second set of checkboxes have a different XMP Text Input.

<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="House"
id="_chkHouse"
click="updateCats(event,'docBuilding')"/>
</mx:GridItem>

<fi:XMPTextInput id="docBuilding"
xmpPath="mc:Building
xmpType="Localized"
name="docBuilding"
visible="false"
/>

I'm also setting visible to false as I don't want to give the user the ability to manually edit the field and screw everything up ;)

Step 2 - Code Changes
Next we need to make some changes to the code.
The first thing I thought about is how we handle multiple arrays and reference them easily?
I decided the easiest way to do this was to keep the cats array and store the arrays within that refernced with a key that is the id of the XMP text input.
So the new function that gets called when the panel has completed loading is now very simple.

private function catsReadHandler(event:Event):void
{
/*  This is called once the panel has completed initialising.
        */
// create an array for each xmp field
cats['docTitle'] = new Array();
cats['docBuilding'] = new Array();
//populate the arrays and tick the right boxes
chkBoxes('docTitle');
chkBoxes('docBuilding');
}


All we have to do now is create the sub array for each XMP text input.
The next part calls a new function that reads the text input, populates the array if it contains data and then ticks the boxes that were referenced in the text input. It's basically the same as the previous catsReadHandler function, but I'm using the this[controlID] nomenclature to reference the fields and the checkboxes.
This gets called for each set of checkboxes / XMP field in the panel.

Here's the function:

private function chkBoxes(xmpFieldName: String):void
{
// this takes the name of the xmp field, populates the array, and ticks the checkboxes
var outStr:String;
var catsString: String = this[xmpFieldName].text;
var chkName:String
var catStr:String;
if (catsString.length > 0)
{
// split the text by commas and populate the array
cats[xmpFieldName] = catsString.split(",");
for (var i:int = 0; i < cats[xmpFieldName].length; ++i) 
{
// remove spaces from the cat name and add to _chk to ref the box
chkName = "_chk";
catStr = cats[xmpFieldName][i];
catStr.replace(/s+/g, "");
chkName += catStr;
try {
// tick the box!
this[chkName].selected=true;
} catch (someError:Error)
{
this[xmpFieldName].text=i + " : " + " " + chkName + " " + someError.message;
}
}
}
}

As you can see, it's pretty much the same as before, but putting this in a separate function enables us to simplify the catsReadHandler function and only have to add extra code there.

The final piece of the puzzle is the function that gets called when a user checks / un-checks a box.
This now takes the id of the XMP text input to update, and so we can reference that directly without having to write a special function for each set of check boxes & XMP fields.

private function updateCats(event: Event,fieldName: String):void
{
//we use event.target to identify the control that triggered the function :)
var catName:String = event.target.label;
var outStr:String;
var cName:String;
var delItem:Boolean;
var localArray:Array;
// copy the main array so we can manipulate it.
localArray = cats[fieldName];
if (localArray.length == 0)
{
localArray.push(catName);
} else {
for (var i:int = 0; i < localArray.length; ++i) {
if (localArray[i] == catName)
{
localArray.splice(i,1);
delItem = true;
}
}
if (!delItem)
{
localArray.push(catName);
}
}
cats[fieldName]=localArray;
outStr = cats[fieldName].toString();
this[fieldName].text = outStr;
this[fieldName].dispatchEvent(new Event(Event.CHANGE));
} 

One of the major changes here apart from calling the function with the field id is that I'm copying the array before manipulating it and then copying it back to the main array after the manipulation is done.
I know in theory I shouldn't need to do this, but I was having issues manipulating the main array directly, so I chose this method because I couldn't be bothered to debug it any further than I already had ;)

The complete file can be found here, feel free to play around with it.




Monday, 28 November 2011

Custom Adobe Bridge Panel

A client recently requested a layout for a custom panel in Bridge which could not be done within the simple generic panel supplied by Adobe, and so had to be coded in Flash Builder.

After installing Flash Builder and the XMP Panel toolkit, it was time to start work!
Having not used flash / flex before, it was a bit of a learning curve, so this post attempts to describe what I have done, and how it works!

So the requirement was to have a grid of checkboxes on the panel, and as they were checked / unchecked they would update a text field which would be saved as part of the XMP metadata.

This field would then be read by ResourceSpace, which would associate those keywords in the field with categories and auto generate themes.

The keywords in the XMP field needed to be separated by a comma for this to happen.

So, the problem is twofold.
First, how to update the text field when the checkbox is ticked?
Second, how to tick the relevant checkboxes when the panel is loaded if there is text in the field?

So, I've laid out a grid in the panel, with checkboxes on it, which looks like this.
The text box underneath is the field that will contain the keywords.

The code for the grid is:
<mx:FormItem label="Categories" direction="horizontal" name="categories">
<mx:Grid name="mgrid" id="mygrid">
<mx:GridRow width="100%" height="100%">
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Dockers"
id="_chkDockers"
name="_chkDockers"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Trains"
id="_chkTrains"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Aviation"
id="_chkAviation"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Boat"
id="_chkBoat"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox label="Education"
id="_chkEducation"
name="_chkEducation"
click="updateCats(event)"/>
</mx:GridItem>
<mx:GridItem width="100%" height="100%">
<mx:CheckBox width="51
label="Road
click="updateCats(event)" 
id="_chkRoad"/>
</mx:GridItem>
</mx:GridRow>
</mx:Grid>
</mx:FormItem>


Each checkbox is contained within a grid item, and has an ID associated with it. It also calls a function updateCats() when the box is clicked, more about this later.

The code for the text field is thus:
<fi:XMPFormItem 
id="ftitle"
name="ftitle"
label="$$$/xmp/sdk/custompanels/Dave3/Title=Main Category:
labelTooltip="$$$/xmp/sdk/custompanels/Dave3/TitleToolTip=The title of the document, or the name given to the resource. Typically, it will be a name by which the resource is formally known.">
<fi:XMPTextInput id="docTitle"
xmpPath="mc:MainCategory
xmpType="Localized"
name="docTitle"
/>
</fi:XMPFormItem>
 The things of note here are the XMPTextInput which is mapped to a custom namespace mc:MainCategory.

So how does it work?

First we create a global array that will hold the list of categories that have been ticked.

private var cats:Array = new Array();

Then when each checkbox is ticked / unticked it calls the function updateCats(Event).
Because it passes an event, we can use the event.target to determine which box was ticked, and retrieve it's label and id.
Once we have these, we can add or remove it from the array as necessary.

Here's the code for the updateCats() function:

private function updateCats(event: Event):void

{

//we can use the event.target to identify the control that triggered the function :)

var catName:String = event.target.label;

var outStr:String;

var cName:String;
var delItem:Boolean;
if (cats.length == 0)
{
cats.push(catName);
} else {
for (var i:int = 0; i < cats.length; ++i) {
if (cats[i] == catName)
{
cats.splice(i,1);
delItem = true;
}
}
if (!delItem)
{
cats.push(catName);
}
}
outStr = cats.toString();
docTitle.text = outStr;
docTitle.dispatchEvent(new Event(Event.CHANGE));
}

Once the array has been update, we then convert it to a string and set the text field to display that string. Finally we tell the text field that it's been updated by sending it a change event.

It's not the most elegant code, but it works!

So, onto the second problem, how do we tick the boxes when the panel loads?
Because we have given each checkbox an id, we can reference them in code with this as:
this[elementID];

As we know that we have given the checkboxes id's that follow _chkFieldName, we can easily reference these.

When the panel has completed loading, we call another function called catsReadHandler() that reads the text field, and if it is not empty creates the cats array and then cycles through the array and ticks the boxes.

Here's the function:

private function catsReadHandler(event:Event):void

{

/*  This is called once the panel has completed initialising.

This will update the checkboxes to indicate which ones are ticked, 

and will populate the cats array

*/
var outStr:String;
var catsString: String = docTitle.text;
var chkName:String
var catStr:String;
if (catsString.length > 0)
{
cats = catsString.split(",");
for (var i:int = 0; i < cats.length; ++i) 
{
chkName = "_chk";
catStr = cats[i];
catStr.replace(/s+/g, "");
chkName += catStr;
try {
// this["control id"] allows us to reference the checkboxes
this[chkName].selected=true;
} catch (someError:Error)
{
docTitle.text=i + " : " + " " + chkName + " " + someError.message;
}
}
}
}

One important part is:


catStr.replace(/s+/g, "");

Which removes any spaces in the name of the check box, ie turning Road Haulage into RoadHaulage.
We then append this to _chk to get the name of the checkbox!

Here's the complete file, feel free to play around with it! categories.mxml

I'll be happy to answer any questions, but please bear in mind this is my first attempt at programming in flash / flex / actionscript. Of course, any suggestions for improvement will be welcomed !!