Datagrid Cell render with custom component - actionscript-3

A few hours ago I've asked how to create a custom component (textInput and label component and created a Component Definition) and with your answers I can do that now.
Problem 2: I'd like to use that component in a datagrid column so that the user can type a value in the textInput which will in turn update the underlying dataprovider.
I know I should use a cellrenderer like I've done with a checkbox column (also with help on the Net), but at this stage I'm only pulling my hair out.
Please help.

This might look messy as it's a modified example.
Make sure you have the DataGrid, Label and TextInput components in the library of the fla you want to try this:
// Import the required component classes.
import fl.controls.DataGrid;
import fl.controls.dataGridClasses.DataGridColumn;
import fl.data.DataProvider;
//get some data ready, notice data and label
var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 7; i++)
dp.addItem({data:'input '+(i+1),label:'label '+(i+1), title:"item " + (i+1)});
var dataCol:DataGridColumn = new DataGridColumn("data");
dataCol.cellRenderer = CustomCell;
var titleCol:DataGridColumn = new DataGridColumn("title");
var myDataGrid:DataGrid = new DataGrid();
myDataGrid.addColumn(dataCol);
myDataGrid.addColumn(titleCol);
myDataGrid.dataProvider = dp;
myDataGrid.rowHeight = 64;
myDataGrid.width = 500;
myDataGrid.rowCount = dp.length - 1;
myDataGrid.move(10, 10);
myDataGrid.editable = true;
addChild(myDataGrid);
And the CustomCell class looks like this:
package {
// Import the required component classes.
import fl.controls.listClasses.ICellRenderer;
import fl.controls.listClasses.ListData;
import fl.controls.Label;
import fl.controls.TextInput;
import fl.core.InvalidationType;
import fl.core.UIComponent;
import fl.data.DataProvider;
import flash.display.Sprite;
import flash.events.Event;
public class CustomCell extends UIComponent implements ICellRenderer {
protected var _data:Object;
protected var _listData:ListData;
protected var _selected:Boolean;
//the custom components
private var labelComponent:Label;
private var inputComponent:TextInput;
/**
* Constructor.
*/
public function CustomCell():void {
super();
init();
}
/**
* Draws the Label and TextInput components
*/
private function init():void{
labelComponent = new Label();
labelComponent.autoSize = 'right';
inputComponent = new TextInput();
inputComponent.editable = true;
addChild(labelComponent);
addChild(inputComponent);
inputComponent.x = labelComponent.width + 5;//5 pixels distance between components
inputComponent.drawFocus(true);
}
public function get data():Object {
return _data;
}
/**
* #private (setter)
*/
public function set data(value:Object):void {
_data = value;
//there's label data, update the label
if(_data.label) labelComponent.text = _data.label;
//there's data for the input, update that too
if(_data.data) inputComponent.text = _data.data;
}
public function get listData():ListData {
return _listData;
}
public function set listData(value:ListData):void {
_listData = value;
invalidate(InvalidationType.DATA);
invalidate(InvalidationType.STATE);
}
public function get selected():Boolean {
return _selected;
}
public function set selected(value:Boolean):void {
_selected = value;
invalidate(InvalidationType.STATE);
}
public function setMouseState(state:String):void {
}
}
}
The code mostly comes from this devnet article.
It works ok, as in, it's editable.
Solution is be a component class(a class extending fl.core.UIComponent), implementing the ICellRender interface so it can be set as a renderer, and containing the Label and TextInput components. Also data will be mapped to TextInput.text, so it can be easily edited.
If DataGrid is a bit bloated, and you want to use the Component Definition or something simpler. I guess you can hack together a solution using a List and setting a custom cellRenderer using styles.
I'm guessing custom clips are used as a cell renderer in the Plugins list on the tweenlite page.
HTH,
George

Related

Adding a child to the stage from a class AS3

I'm trying to do something very basic, add a movie clip from the library to the stage. I created a class called link that extends MovieClip. I have two movie clips in the library, the first one that is initially loaded contains buttons that should remove the first movie clip and load the second. However, I am unable to load the second using addChild(). I'm hoping that there is something obvious that I'm missing. I'm fairly new to AS3. Here is the code from the link class:
package classes.GEN1P0
{
import flash.display.SimpleButton;
import flash.display.*;
import flash.events.*;
import fl.controls.*;
import fl.transitions.easing.*;
import fl.transitions.TweenEvent;
public class link extends MovieClip
{
public var links:Vector.<SimpleButton > = new Vector.<SimpleButton >;
public var sourceMap:redMC = new redMC ;
public var sourceMap2:blueMC = new blueMC ;
public var panZoomMap:PanZoomMap = new PanZoomMap(sourceMap,704,556.3);
public var diagram:PanZoomMap = new PanZoomMap(sourceMap2,704.45,556.3);
public var zoomControls:ZoomControls = new ZoomControls(panZoomMap);
public var zoomControls2:ZoomControls = new ZoomControls(diagram);
public var navWindow:NavigatorWindow = new NavigatorWindow(panZoomMap);
public var navWindow2:NavigatorWindow = new NavigatorWindow(diagram);
public function link()
{
}
/////////////////////////////
//Vector for Links
/////////////////////////////
public function linkVector():void
{
for (var m:int = 0; m < 2; m++)
{
var linkNumb:SimpleButton = RunWise_GEN_1P0_Electrical_Schematic.sourceMap.panner.schematic.link_movies.getChildAt(m);
if ((linkNumb is SimpleButton))
{
links.push(linkNumb);
var linkName:String = links[m].name;
}
}
for (var n:int = 0; n<links.length; n++)
{
links[n].addEventListener(MouseEvent.CLICK, linkTo);
}
function linkTo(e:Event):void
{
addChild(diagram);
diagram.x = 291.35;
diagram.y = 22.15;
RunWise_GEN_1P0_Electrical_Schematic.panZoomMap.parent.removeChild(RunWise_GEN_1P0_Electrical_Schematic.panZoomMap);
switch (e.target.name)
{
case "PPOS":
trace ("PPOS");
break;
case "PYKPOS":
trace ("PYKPOS");
break;
}
}
}
//End Code Here
}
}
Should be as simple as
var _link:link = new link();
addChild(_link);
Note: Typically when you create a Class in AS3 you use uppercase for the first letter of the Class name
public class Link extends MovieClip

Actionscript making intro of a game

I'm making a game in full classes. so the timeline is empty. But I want to use another scene for the game intro, after the intro, it will proceed to the main menu of the game which I have created. Anyone got an idea? I haven't found any since a week ago... I don't really know about how to operate scenes from code in classes. Please help. Thanks!
Here is the main code :
package
{
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.display.MovieClip;
public class Main extends Sprite
{
public var field:Array;
//CALL EVERY CLASS
public var _money:Money = new Money();
public var _gold:Gold;
public var _hero:Hero;
public var _enemy:Enemy;
private var _pause:MovieClip = new Pause();
private var pauseLayer:MovieClip = new PauseLayer();
private var ts:TitleScreen;
public function Main():void
{
ts = new TitleScreen();
addChild(ts);
}
//GAME FUNCTION
public function onStage():void
{
_hero = new Hero(this);
_enemy = new Enemy(this);
_gold = new Gold(this);
setupField();
_gold.goldSet(stage);
_money.addText(stage);
_hero.displayHero(stage);
_enemy.displayEnemy(stage);
setPause();
_pause.addEventListener(MouseEvent.CLICK, pauseGame);
}
private function setPause():void
{
addChild(_pause);
_pause.x = 620;
_pause.y = 50;
_pause.buttonMode = true;
}
private function pauseGame (e:MouseEvent):void
{
stage.frameRate = 0;
addChild(pauseLayer);
pauseLayer.alpha = 0.5;
pauseLayer.parent.setChildIndex(pauseLayer,numChildren-1);
}
//SET UP FIELD ARRAY
private function setupField():void
{
var fieldSprite:Sprite=new Sprite();
addChild(fieldSprite);
fieldSprite.graphics.lineStyle(4);
field=new Array();
for (var a:int=0; a<6; a++)
{
field[a]=new Array();
for (var b:int=0; b<10; b++)
{
field[a][b]=0;
}
}
//DRAW FIELD
for (var i:int=0; i<5; i++)
{
for (var j:int=0; j<9; j++)
{
fieldSprite.graphics.drawRect(75+65*j,50+75*i,65,75);
}
}
}
}
}
Titlescreen class :
package
{
import flash.display.MovieClip;
import flash.events.MouseEvent;
import flash.display.Sprite;
public class TitleScreen extends Sprite
{
private var playBtn:MovieClip = new Resume();
public function TitleScreen()
{
playBtn.x = 50;
playBtn.y = 50;
playBtn.addEventListener(MouseEvent.CLICK, Play);
}
private function Play(e:MouseEvent):void
{
trace("a");
}
}
}
The most simple way would be using wrapper Sprites to hold each set of objects you probably want to be available as whole, say main menu, upgrades, storyline, etc etc. Then you just shift them in and out of display list to display corresponding "scene" with your Main class responsible of transition flow. But to do this you need to shift your game core functionality out of Main class into say Game class. That's how I have done the same thing in my game:
public class Main extends MovieClip {
private var so:SharedObject;
private var ui:UserInterface;
private var ts:TitleScreen;
private function init(e:Event = null):void
{
ui = new UserInterface();
ts = new TitleScreen();
ts.newButtonClicked = newGame;
ts.loadButtonClicked = loadGame;
ui.gotoMapBehavior = wentToMap;
addChild(ts);
}
Here, UserInterface is a class that has gaming logic inside, and TitleScreen is a main menu class. The functions are callbacks in Main:
private function newGame():void {
removeChild(ts); // hide title
if (!contains(ui)) addChild(ui);
SoundManager.playMusic(SoundManager.MUSIC_LEVELSELECT);
}
private function loadGame():void {
newGame();
ui.loadBattle(); // this should make UI load the battle from shared object (either supplied or received by itself)
}
private function wentToMap():void {
// we have just executed "go to map" from UI
removeChild(ui);
addChild(ts);
checkSO();
SoundManager.playMusic(SoundManager.MUSIC_INTRO);
}
The actual gaming logic does not interact with Main at all, except for shared object which is common for the entire project, but the link is received normally via SharedObject.getLocal(someName). The code is ugly, but could do for starters.
Save your game as SWF and make another project with timeline-animated intro. When the intro ends, make your project to load your game. Loader class can load other swf files. So, you don't need to edit your game classes.

Adding eventlisteners in main document class for external classes

I have a small project I'm trying to help learn as3. It is a variation from the book Foundation Game Design with Actionscript 3.0. I am using an fla only to have a document class. All art is loaded within the as files. In the book, he just put all the code in the document class, I followed along and it worked as expected. I am trying to break out the code into separate classes to get a handle on OOP. One class makes a background - Background.as, one makes a character - Character.as, and one makes a button, which I instantiate 6 times for 6 different buttons - GameButton.as. And of course there is GameWorld.as which is the document class. Everything loads and shows up as expected. However when I try and add an eventListener for the buttons, I don't get any response. I have tried putting the eventListener in the GameButton.as and also tried it in the GameWorld.as neither of which has worked. Also I pass a reference to the stage when instantiating the various classes, because when I tried to addChild in the GameWorld.as, nothing would show up. I searched the site and found something similar, but it didn't seem to help. Thank you in advance for any advice you my have. Here is the code:
GameWorld.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.DisplayObject
import flash.events.MouseEvent;
import GameButton;
import Character;
import Background;
[SWR(width = "550", height = "400", backgroundColor = "#FFFFFF", frameRate = "60")]
public class GameWorld extends Sprite
{
//public variables
//Background
public var gameBackground:Background;
//Character
public var catCharacter:Character;
//Buttons
public var upButton:GameButton;
public var downButton:GameButton;
public var growButton:GameButton;
public var shrinkButton:GameButton;
public var vanishButton:GameButton;
public var spinButton:GameButton;
public function GameWorld ()
{
//Add the background to the stage
gameBackground = new Background("../images/background.png", stage);
//Add the character(s) to the stage
catCharacter = new Character("../images/character.png", stage);
//Set initial character position
catCharacter.CharacterPos(225, 150);
//Add the buttons to the stage
upButton = new GameButton("../images/up.png", stage, 25, 25);
downButton = new GameButton("../images/down.png", stage, 25, 85);
growButton = new GameButton("../images/grow.png", stage, 25, 145);
shrinkButton = new GameButton("../images/shrink.png", stage, 425, 25);
vanishButton = new GameButton("../images/vanish.png", stage, 425, 85);
spinButton = new GameButton("../images/spin.png", stage, 425, 145);
//Button event handlers
upButton.addEventListener(MouseEvent.CLICK, upButtonHandler);
}
public function upButtonHandler(event:MouseEvent)
{
trace("You clicked the up button!");
catCharacter.CharacterMove(15);
}
}
}
GameButton.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
import flash.events.MouseEvent;
public class GameButton extends Sprite
{
//public variables
public var stageRef:Stage;
public var urlRequest:URLRequest;
public var gameButtonLoader:Loader;
public var gameButtonSprite:Sprite;
//Constructor
public function GameButton (urlRequest:String, stageRef:Stage, xPos:Number, yPos:Number)
{
this.stageRef = stageRef
this.urlRequest = new URLRequest();
gameButtonLoader = new Loader();
gameButtonSprite = new Sprite();
this.urlRequest.url = urlRequest;
gameButtonLoader.load(this.urlRequest);
gameButtonSprite.addChild(gameButtonLoader);
this.stageRef.addChild(gameButtonSprite);
gameButtonSprite.buttonMode = true;
gameButtonSprite.x = xPos;
gameButtonSprite.y = yPos;
}
}
}
Character.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
public class Character
{
//private variables
private var stageRef:Stage;
private var urlRequest:URLRequest;
private var characterLoader:Loader;
private var characterSprite:Sprite;
//public variables
public var character_x_pos:Number;
public var character_y_pos:Number;
//Constructor
public function Character (urlRequest:String, stageRef:Stage)
{
this.stageRef = stageRef;
this.urlRequest = new URLRequest();
characterLoader = new Loader();
characterSprite = new Sprite();
this.urlRequest.url = urlRequest;
characterLoader.load (this.urlRequest);
characterSprite.addChild (characterLoader);
this.stageRef.addChild (characterSprite);
characterSprite.mouseEnabled = false;
}
//Set the position of the character
public function CharacterPos(xPos:Number, yPos:Number):void
{
characterSprite.x = xPos;
characterSprite.y = yPos;
}
//Move the position of the character
public function CharacterMove( yPos:Number):void
{
characterSprite.y -= yPos;
}
}
}
Background.as
package
{
import flash.net.URLRequest;
import flash.display.Loader;
import flash.display.Sprite;
import flash.display.Stage;
public class Background
{
//Private variables
private var stageRef:Stage;
private var urlRequest:URLRequest;
private var backgroundLoader:Loader;
private var backgroundSprite:Sprite;
//Constructor
public function Background (urlRequest:String, stageRef:Stage)
{
this.stageRef = stageRef;
this.urlRequest = new URLRequest();
backgroundLoader = new Loader();
backgroundSprite = new Sprite();
this.urlRequest.url = urlRequest;
backgroundLoader.load (this.urlRequest);
backgroundSprite.addChild (backgroundLoader);
this.stageRef.addChild (backgroundSprite);
backgroundSprite.mouseEnabled = false;
}
}
}
All art is loaded within the as files.
This is not an approach I recommend. There's a reason God gave us the Flash IDE--and it's not to write code! Any time you're spending on layout and viduals in code is just wasted, unless you have an actual requirement to change the visuals at runtime. The fact that your paths are all hard-coded suggests that you don't have that requirement.
So let's step back and imagine that you have a Symbol that contains 6 Symbols that you've created as just Flash buttons (when you select Button as the Symbol type). These will be SimpleButtons, but in the Class below we're just going to type them as DisplayObject. The Class doesn't care what they are, but using Simplebutton gives them up, over, down and hit states that require no code.
Note that the below assumes you have "automatically declare stage instances" off, which is IMO the best way to do things.
package view {
public class NavBar extends Sprite {
//because you put these on stage in the Symbol, they will be available in the constructor
public var upButton:DisplayObject;
public var downButton:DisplayObject;
public var growButton:DisplayObject;
public var shrinkButton:DisplayObject;
public var rotateButton:DisplayObject;
public var vanishButton:DisplayObject;
//makes it easier to do the same setup on all buttons
protected var allButtons:Vector.<DisplayObject> = <DisplayObject>([upButton, downButton, growButton, shrinkButton, rotateButton, vanishButton]);
public function NavBar() {
super();
for each (var btn:DisplayObject in allButtons) {
btn.buttonMode = true;
btn.mouseChildren = false;
btn.addEventListener(MouseEvent.CLICK, onButtonClick);
}
}
protected function onButtonClick(e:MouseEvent):void {
switch (e.target) {
case upButton:
dispatchEvent(new CharacterEvent(CharacterEvent.UP));
break;
case downButton:
dispatchEvent(new CharacterEvent(CharacterEvent.DOWN));
break;
case growButton:
dispatchEvent(new CharacterEvent(CharacterEvent.GROW));
break;
case shrinkButton:
dispatchEvent(new CharacterEvent(CharacterEvent.SHRINK));
break;
case rotateButton:
dispatchEvent(new CharacterEvent(CharacterEvent.ROTATE));
break;
case vanishButton:
dispatchEvent(new CharacterEvent(CharacterEvent.VANISH));
break;
default:
break;
}
}
}
}
Note that there's zero layout code. This code is dependent on a custom Event Class. I'm going to write that Event Class so that it always bubbles. That way, it can be dispatched anywhere on the display list and received at the top level:
package control {
class CharacterEvent extends Event {
public static var UP:String = 'characterUp';
public static var DOWN:String = 'characterDown';
public static var GROW:String = 'characterGrow';
public static var SHRINK:String = 'characterShrink';
public static var ROTATE:String = 'characterRotate';
public static var VANISH:String = 'characterVanish';
public function CharacterEvent(type:String) {
super(type, true, true);//always bubbles
}
public function clone():Event {
return new CharacterEvent(type);
}
}
}
Now, if you want to manually handle instantiation of the Symbol that has view.NavBar as its base class, it will look like this:
package {
public var navBar:NavBar;
class GameWorld {
public function GameWorld() {
try {
var navClass:Class = getDefinitionByName('NavBarSymbol') as Class;
} catch (e:Error) {
trace('You need to have a Library symbol called NavBarSymbol');
}
if (navClass) {
navBar = new navClass() as NavBar;
//unnecessary layout code here
//Note that this is NOT the responsibility of the child Class!
addChild(navBar);
}
//instantiate character
...
//by listening to the whole Document, you can add other things that
//dispatch Character events on the display list, like a keyboard listener
addEventListener(CharacterEvent.UP, moveCharacterUp);
//listeners for the rest of the character events...
}
public function moveCharacterUp(e:CharacterEvent):void {
//character move logic
}
//other handlers
}
}
Personally, I'd just add the navBar to the stage, and then there's no need to manage it at all (not even reference it with a variable), simply add the event listeners for the various character events.
The root of your problem doesn't seem to be the character code. However, I'm going to give you a few "best practice" pointers about it.
The convention in AS3 is for Class members (properties and methods) to be camel case starting with a lower case letter. So, characterPos() and characterMove().
Your Class already contains character in the name, so really these should just be pos() and move() (though there's no need now to shorten position()).
The only thing your child Classes are doing with their references to the parent are adding themselves. They don't need and shouldn't have a reference to the parent for this purpose. It is the parent's responsibility to add the Children (or the responsibility of the Flash Player if you use the stage).
That said, you could give your Character a reference to the parent Class typed as IEventDispatcher and allow the Character to listen to this channel. This concept is called an event bus.
Note that the reason that so many people do what you're doing is that Adobe failed to document how to properly use OOP with the timeline. Unfortunately, by the time a few of us started documenting that around late 2009/early 2010, the damage was done and everyone assumed that if you wanted to write good code you had to pretend the timeline and stage didn't exist.
I know I've covered a lot of ground, and probably most of what I said directly contradicts what you thought you knew, so please don't hesitate to ask any questions you might have.

ActionScript - Access List/DataProvider From Custom CellRenderer

the code below sets up a List object in the main controller class that uses a custom cell renderer (CustomListCell class). the CustomListCell class creates a Button object for the cell that will be used to delete itself from the List's DataProvider.
how can i properly access the parent List object from its custom cell renderer?
//Controller Class
private function createList():void
{
provider = new DataProvider(data);
list = new List();
list.width = 200;
list.height = 400;
list.rowHeight = 50;
list.dataProvider = provider;
list.setStyle("cellRenderer", CustomListCell);
}
-----
//CustomListCell Class
import fl.controls.Button;
public class CustomListCell extends Sprite implements ICellRenderer
{
public function CustomListCell()
{
var button:Button = new Button();
button.label = "Delete Cell";
button.addEventListener(MouseEvent_MOUSE_DOWN, deleteCellHandler);
addChild(button);
}
private function deleteCellHandler(evt:MouseEvent):void
{
//Access List/DataProvider Here
}
//required implemented ICellRenderer functions follow
}
UPDATE
the following is my working custom renderer that implements ICellRenderer with Flash v3 List component. the List's dataProvider consists of 2 elements for each cell: randomColor and randomNumber.
package
{
//Imports
import fl.controls.Button;
import fl.controls.List;
import fl.controls.listClasses.ICellRenderer;
import fl.controls.listClasses.ListData;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.geom.ColorTransform;
//Class
public class TouchListRenderer extends Sprite implements ICellRenderer
{
//Properties
private var cellWidthProperty:Number;
private var cellHeightProperty:Number;
private var dataProperty:Object;
private var listDataProperty:ListData;
private var selectedProperty:Boolean;
//Cell Display Objects
private var backgroundCanvas:MySprite = new MySprite();
private var numberTextField:TextField = new TextField();
private var button:Button = new Button();
//Constructor
public function TouchListRenderer()
{
}
//Size Setter (Getter Functions Intentionally Omitted)
public function setSize(width:Number, height:Number):void
{
cellWidthProperty = width;
cellHeightProperty = height;
}
//Data Setter
public function set data(value:Object):void
{
dataProperty = value;
}
//Data Getter
public function get data():Object
{
return dataProperty;
}
//List Data Setter
public function set listData(value:ListData):void
{
listDataProperty = value;
}
//List Data Getter
public function get listData():ListData
{
return listDataProperty;
}
//Selected Setter
public function set selected(value:Boolean):void
{
selectedProperty = value;
layout();
}
//Selected Getter
public function get selected():Boolean
{
return selectedProperty;
}
//Size And Layout
private function layout():void
{
var newColor:ColorTransform = new ColorTransform();
newColor.color = dataProperty.randomColor;
backgroundCanvas.transform.colorTransform = newColor;
backgroundCanvas.scaleX = cellWidthProperty / backgroundCanvas.width;
backgroundCanvas.scaleY = cellHeightProperty / backgroundCanvas.height;
numberTextField.text = dataProperty.randomNumber;
numberTextField.autoSize = TextFieldAutoSize.LEFT;
numberTextField.textColor = 0xFFFFFF;
numberTextField.x = 50;
numberTextField.y = cellHeightProperty / 2 - numberTextField.height / 2;
numberTextField.border = true;
numberTextField.selectable = false;
button.label = "Delete";
button.x = cellWidthProperty - button.width - 50;
button.y = cellHeightProperty / 2 - button.height / 2;
button.drawNow();
button.addEventListener(MouseEvent.MOUSE_DOWN, buttonClickEventHandler);
addChild(backgroundCanvas);
addChild(numberTextField);
addChild(button);
}
//Button Click Event Handler
private function buttonClickEventHandler(evt:MouseEvent):void
{
List(listDataProperty.owner).removeItemAt(listDataProperty.index);
}
//Style Setter
public function setStyle(style:String, value:Object):void
{
}
//Mouse State Setter
public function setMouseState(state:String):void
{
}
}
}
package
{
import flash.display.Sprite;
public class MySprite extends Sprite
{
public function MySprite()
{
graphics.beginFill(0xFF0000);
graphics.drawRect(0, 0, 10, 10);
graphics.endFill();
}
}
}
ugh! the answer was in front of me the whole time! next time remind me to check the docs:
List(listData.owner)
fl.controls.listClasses.ListData.owner
There are multiple ways to do this.
Here is a very hacky solution: Use an icon, and have that icon dispatch a close event.
The idea is you'll place a custom MovieClip in each list cell as icon. That icon will dispatch an event with the index of the cell clicked so you can remove it.
1st step: Make a basic custom event to pass cell index through:
package{
import flash.events.Event;
public class CloseEvent extends Event{
public static const CLOSE:String = 'close';
public var index:int;
public function CloseEvent(type:String,bubbles:Boolean = true,cancelable:Boolean=true){
super(type,bubbles,cancelable);
}
}
}
2nd step:: Draw a close icon or something, convert it to MovieClip and Export for Actionscript
3rd step: Add the event listener to dispatch the custom event when the close icon is clicked.
Inside the close icon Movie Clip I've placed the following actions:
import fl.controls.listClasses.CellRenderer;
//setup click
buttonMode = true;
if(parent) parent.mouseChildren = true;
addEventListener(MouseEvent.MOUSE_DOWN,dispatchClose);
//setup event
var closeEvent:CloseEvent = new CloseEvent(CloseEvent.CLOSE,true);
if(parent) closeEvent.index = CellRenderer(parent).listData.index;
//listen to click and pass on
function dispatchClose(event:MouseEvent):void {
dispatchEvent(closeEvent);
}
Very basic stuff, listen for mouse down, create an event and set the index and dispatch that event on click. The icon is added to a cell renderer, therefor the cell render is it's parent which it has a listData property among others, which holds the index of the cell.
So here's how the test snippet looks:
import fl.data.DataProvider;
var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 30 ; i++) dp.addItem({label:'item'+(i+1),icon:Close});
ls.dataProvider = dp;
addEventListener(CloseEvent.CLOSE,deleteItem);
function deleteItem(event:CloseEvent):void {
ls.removeItemAt(event.index);
}
Since the CloseEvent bubbles, we can catch it from outside the cell renderer's icon and tell the list to remove the item at that index. It's possible to do that within the icon, but it will be necessary to 'climb' up the hierarchy all the way to the list, and it's pretty hacky already.
I did this because, I was probably as lazy as #TheDarkIn1978 :P to implement the ICellRenderer functions. Then I looked at question code again and didn't understand why the custom cell extends a Sprite, when CellRenderer already implements the ICellRenderer functions already.
So here is my attempt to do it in a less hacky manner:
package{
import fl.controls.*;
import fl.controls.listClasses.*;
import fl.data.*;
import flash.events.*;
public class SCListCell extends CellRenderer implements ICellRenderer{
protected var closeButton:Button;
protected var closeEvent:CloseEvent;
override protected function configUI():void {
super.configUI();
closeButton = new Button();
closeButton.label = 'x';
closeButton.buttonMode = true;
closeButton.setSize(30,20);
closeButton.drawNow();
closeButton.addEventListener(MouseEvent.CLICK,close);
addChild(closeButton);
closeEvent = new CloseEvent(CloseEvent.CLOSE);
}
private function close(event:MouseEvent):void{
closeEvent.index = listData.index;
dispatchEvent(closeEvent);
}
override protected function drawLayout():void{
mouseChildren = true;
closeButton.x = width-closeButton.width;
}
}
}
Used the same CloseEvent to pass the index, and the custom cell has direct access to the listData object to fetch the index, so the sample snippet looks like this:
import fl.data.DataProvider;
var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 30 ; i++) dp.addItem({label:'item'+(i+1)});
ls.dataProvider = dp;
addEventListener(CloseEvent.CLOSE,deleteItem);
function deleteItem(event:CloseEvent):void {
ls.removeItemAt(event.index);
}
ls.setStyle('cellRenderer',SCListCell);
So to answer your question:
how can i properly access the parent
List object from its custom cell
renderer?
You can use the listData property of the cell renderer.
You can if you want to, but it means going up a few levels:
package{
import fl.controls.*;
import fl.controls.listClasses.*;
import fl.data.*;
import flash.events.*;
public class SCListCell extends CellRenderer implements ICellRenderer{
protected var closeButton:Button;
override protected function configUI():void {
super.configUI();
closeButton = new Button();
closeButton.label = 'x';
closeButton.buttonMode = true;
closeButton.setSize(30,20);
closeButton.drawNow();
closeButton.addEventListener(MouseEvent.CLICK,close);
addChild(closeButton);
}
private function close(event:MouseEvent):void{
List(this.parent.parent.parent).removeItemAt(listData.index);
}
override protected function drawLayout():void{
mouseChildren = true;
closeButton.x = width-closeButton.width;
}
}
}
Which leaves the list creation part as simple as:
import fl.data.DataProvider;
var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 30 ; i++) dp.addItem({label:'item'+(i+1)});
ls.dataProvider = dp;
ls.setStyle('cellRenderer',SCListCell);
CloseEvent isn't needed in this case.
HTH

Add movieclips (and controlling them) in a datagrid in AS3

I created a column in a datagrid which displays a movieclip (every row the same movieclip appears.
How can I control which frame is displayed in each row?
using this.parent from inside the movieclip doesn't work.
What's the path to the specific row in which a movieclip resides?
Thanks
I've done a test using this devnet article:
Notice that in data I pass another object that has source, refence used to display a clip
and frame, used to control the frame of the clip.
//imports
import fl.controls.DataGrid;
import fl.controls.dataGridClasses.DataGridColumn;
import fl.data.DataProvider;
//data
var dp:DataProvider = new DataProvider();
//populate the provider, specifying the source (linkageID and frame of the clip to display)
for(var i:int = 0 ; i < 7; i++)
dp.addItem({data:{source:'Star',frame:i+1}, title:"clip Star at frame"+(i+1)+""});
var dataCol:DataGridColumn = new DataGridColumn("data");
dataCol.cellRenderer = ClipCell;
var titleCol:DataGridColumn = new DataGridColumn("title");
var myDataGrid:DataGrid = new DataGrid();
myDataGrid.addColumn(dataCol);
myDataGrid.addColumn(titleCol);
myDataGrid.dataProvider = dp;
myDataGrid.rowHeight = 64;
myDataGrid.width = 500;
myDataGrid.rowCount = dp.length - 1;
addChild(myDataGrid);
And here is the ClipCell used a cell renderer in the data column:
package {
// Import the required component classes.
import fl.controls.listClasses.ICellRenderer;
import fl.controls.listClasses.ListData;
import fl.controls.Label;
import fl.controls.TextInput;
import fl.core.InvalidationType;
import fl.core.UIComponent;
import fl.data.DataProvider;
import flash.display.MovieClip;
import flash.events.Event;
import flash.utils.*;
public class ClipCell extends UIComponent implements ICellRenderer {
protected var _data:Object;
protected var _listData:ListData;
protected var _selected:Boolean;
private var _clip:MovieClip;
/**
* Constructor.
*/
public function ClipCell():void {
super();
}
/**
* Gets or sets the cell's internal _data property.
*/
public function get data():Object {
return _data;
}
/**
* #private (setter)
*/
public function set data(value:Object):void {
_data = value;
if(value.data.source) {
var Clip:Class = getDefinitionByName(value.data.source) as Class;
_clip = new Clip();
if(_clip.numFrames > 1) _clip.stop();
addChildAt(_clip,0);
}
if(value.data.frame && _clip) _clip.gotoAndStop(value.data.frame);
}
public function get caca():String{return 'caca';}
/**
* Gets or sets the cell's internal _listData property.
*/
public function get listData():ListData {
return _listData;
}
/**
* #private (setter)
*/
public function set listData(value:ListData):void {
_listData = value;
invalidate(InvalidationType.DATA);
invalidate(InvalidationType.STATE);
}
/**
* Gets or sets the cell's internal _selected property.
*/
public function get selected():Boolean {
return _selected;
}
/**
* #private (setter)
*/
public function set selected(value:Boolean):void {
_selected = value;
invalidate(InvalidationType.STATE);
}
/**
* Sets the internal mouse state.
*/
public function setMouseState(state:String):void {
}
}
}
I have a clip in the library called Star exported for actionscript.
Inside it I placed a bit of code to see if I can get to the ClipCell:
var cell:ClipCell = this.parent as ClipCell;
trace(cell.data.title);
for(var i in cell.data.data)
trace(i+':'+cell.data.data[i]);
and I get this output:
clip Star at frame1
frame:1
source:Star
clip:[object Star]
HTH