AS3 URLRequest in for Loop problem - for-loop

I read some data from a xml file, everything works great besides urls. I can't figure what's the problem with the "navigateURL" function or with the eventListener... on which square I click it opens the last url from the xml file
for(var i:Number = 0; i <= gamesInput.game.length() -1; i++)
{
var square:square_mc = new square_mc();
//xml values
var tGame_name:String = gamesInput.game.name.text()[i];//game name
var tGame_id:Number = gamesInput.children()[i].attributes()[2].toXMLString();//game id
var tGame_thumbnail:String = thumbPath + gamesInput.game.thumbnail.text()[i];//thumb path
var tGame_url:String = gamesInput.game.url.text()[i];//game url
addChild(square);
square.tgname_txt.text = tGame_name;
square.tgurl_txt.text = tGame_url;
//load & attach game thumb
var getThumb:URLRequest = new URLRequest(tGame_thumbnail);
var loadThumb:Loader = new Loader();
loadThumb.load(getThumb);
square.addChild(loadThumb);
//
square.y = squareY;
square.x = squareX;
squareX += square.width + 10;
square.buttonMode = true;
square.addEventListener(MouseEvent.CLICK, navigateURL);
}
function navigateURL(event:MouseEvent):void
{
var url:URLRequest = new URLRequest(tGame_url);
navigateToURL(url, "_blank");
trace(tGame_url);
}
Many thanks!

In navigateURL() you use tGame_url, but I think you'd rather use something like tgurl_txt.text which will be different for each square.

Looks like you're not attaching the event listener properly. Instead of this.addEventListener, attach it to the variable you created when creating new square_mc..... so:
square.addEventListener(MouseEvent.CLICK, navigateURL);

you should add the addEventListener on the Squares
mmm..still figuring how eventhandler function will ever get the correct tgame_url var.
What if you try this:
square.addEventListener(MouseEvent.CLICK, function navigateURL(event:MouseEvent):void
{
var url:URLRequest = new URLRequest(tGame_url);
navigateToURL(url, "_blank");
trace(tGame_url);
});

try tracing this:
function navigateURL(event:MouseEvent):void
{
var url:URLRequest = new URLRequest(tGame_url);
navigateToURL(url, "_blank");
//trace(tGame_url);
trace(event.currentTarget.tgurl_txt.text);
}
you should add the url to your square in the loop
square.theUrl = tGame_url;
in the event listener function you should be able to access it with
event.currentTarget.theUrl;

Related

How to get a loop to load movieclips with eventlisteners

I wanted the scene load 5 different movie clips (named B1-B5). Each movie clip is placed on a specific x and y. Each movie clip grows/shrinks on roll over/roll out....
I got the code working by typing everything out and duplicating each section per time but it's messy and I'd like to clean up the code by getting a loop to do it (if it's possible?).
This is the code that works but I'd have to duplicate it per movie clip (changing the obvious bits)...
var scene1:MovieClip = new B1();
addChild(scene1);
scene1.x = 170.30;
scene1.y = 231.15;
scene1.addEventListener(MouseEvent.MOUSE_OVER, onRollOverEvent1);
scene1.addEventListener(MouseEvent.MOUSE_OUT, onRollOutEvent1);
function onRollOverEvent1(e:MouseEvent) {
scene1.width=25.9;
scene1.height=25;
}
function onRollOutEvent1(e:MouseEvent) {
scene1.width = 20.9;
scene1.height = 20;
}
Below is what I've tried out but have been stuck for a good while...
for (var i:int=1; i<5; i++){
var scene[i]:MovieClip = new "B"+i();
addChild("scene"+i);
//var scene[i]:MovieClip = new B[i]();
scene[i].addEventListener(MouseEvent.MOUSE_OVER, onRollOverEvent);
scene[i].addEventListener(MouseEvent.MOUSE_OUT, onRollOutEvent)
function onRollOverEvent(e:MouseEvent) {
scene[i].width=25.9;
scene[i].height=25;
}
function onRollOutEvent(e:MouseEvent) {
scene[i].width = 20.9;
scene[i].height = 20;
}
}
scene1.x = 170.30;
scene1.y = 231.15;
scene2.x = 284.30;
scene2.y = 250.75;
scene3.x = 377.30;
scene3.y = 280.15;
scene4.x = 444.30;
scene4.y = 321.15;
scene5.x = 196.30;
scene5.y = 172.15;
First, lets go through your mistakes.
new "B"+i();
At the very best that translates to calling a number i as function and adding the result to "B" as a String. But even new "B1"() is not the same as new B1(). There is, in fact, a method getDefinitionByName(..) that allows to address a class via its name, but I don't recommend to use it because it is advanced topic.
var scene[i]:MovieClip
You just cannot define variables scene1, scene2, etc this way. The closest thing you can actually devise is the square bracket notation: this["scene" + i] = ....
addChild("scene"+i);
The argument must be a DisplayObject instance, not a String.
for (...)
{
...
function onRollOverEvent(e:MouseEvent)
...
}
Do not define functions inside other functions or loops.
scene[i].width = 20.9;
scene[i].height = 20;
By the end of your loop i will be equal to 5, so, what do you think such a record will address?
Then, the solution.
When you come to scaling your working solution to multiple instances, you are to go algorithmic. Loops and Arrays are your friends.
// Lets devise a list of classes and (x,y) coordinates.
var Designs:Array = [
null, // the 0-th element
{id:B1, x:170, y:230},
{id:B2, x:285, y:250},
];
for (var i:int = 1; i < Design.length; i++)
{
// Retrieve a record for the future object.
var aDesign:Object = Designs[i];
// Get a reference to the object's class.
var aClass:Class = aDesign.id;
// Create the object. Yes, you CAN omit () with
// the "new" operator if there are no mandatory arguments.
var aThing:Movieclip = new aClass;
// Set coordinates from the design record.
aThing.x = aDesign.x;
aThing.y = aDesign.y;
// Add to the display list.
addChild(aThing);
// Subscribe the event handlers.
aThing.addEventListener(MouseEvent.MOUSE_OVER, onOver);
aThing.addEventListener(MouseEvent.MOUSE_OUT, onOut);
// Save the object's reference for the later use.
// If you'd need to address, say, 3rd object,
// you do it as following:
// Designs[3].instance
aDesign.instance = aThing;
}
function onOver(e:MouseEvent):void
{
// You subscribed all of the objects to this one event handler.
// This is the correct way to learn, which one of the objects
// is under the mouse and is dispatching the said event.
var aThing:MovieClip = e.currentTarget as MovieClip;
// Change the object's size.
aThing.width = 26;
aThing.height = 25;
}
function onOut(e:MouseEvent):void
{
// Get the source of the dispatched event.
var aThing:MovieClip = e.currentTarget as MovieClip;
// Change the object's size.
aThing.width = 21;
aThing.height = 20;
}

UPDATED: Javascript logic to fix in a small function (SVG, obtaining absolute coords)

NEW:
So here is the code at codepen:
http://codepen.io/cmer41k/pen/pRJNww/
Currently function UpdateCoords(draggable) - is commented out in the code.
What I wanted is to update on mouseup event the coordinates of the path (circle as path here) to the absolute ones and remove transform attribute.
But I am failing to do that;(( sorry only learning
OLD:
In my code I have an svg element (path) that gets dragged around the root svg obj (svg) via transform="translate(x,y)" property.
I wanted to update such path element's attribute "d" (the string that describes all coords) to use absolute coordinates and get rid of transformed\translate thing.
Basically:
was: d="M10,10 30,10 20,30" + transform="translate(20,0);
to be: d="M30,10 50,10 40,30" + transform="translate(0,0)" (or if we can delete the transform - even better)
So I did the code that does the thing for me, but there is a bug that prevents proper result.
I am sure I am doing something wrong in here:
var v = Object.keys(path.controlPoints).length
// controlPoints here is just a place in path object where I store the coords for the path.
var matrix = path.transform.baseVal.consolidate();
//I validated that the above line does give me proper transform matrix with proper x,y translated values. Now below I am trying to loop through and update all control points (coordinates) of the path
for (i=0; i<v; i++) {
var position = svg.createSVGPoint();
position.x = path.controlPoints["p"+i].x;
position.y = path.controlPoints["p"+i].y;
// so for each of path's control points I create intermediate svgpoint that can leverage matrix data (or so I think) to "convert" old coords into the new ones.
position = position.matrixTransform(matrix);
path.controlPoints["p"+i].x = position.x;
path.controlPoints["p"+i].y = position.y;
}
// I am sure I am doing something wrong here, maybe its because I am not "cleaning"/resetting this position thing in this loop or smth?
Sorry I am not a programmer, just learning stuff and the question is - in this code snipped provided the goal that I described - is something wrong with how I handle "position"?
Alright, the code snipped is now functioning properly!
So after I figured how to obtain properly the matrix I still had a weird displacement for any subsequent draggables.
I became clear that those displacements happen even before my function.
I debugged it a bit and realized that I was not clearing the ._x and ._y params that I use for dragging.
Now code works!
http://codepen.io/cmer41k/pen/XpbpQJ
var svgNS = "http://www.w3.org/2000/svg";
var draggable = null;
var canvas = {};
var inventory = {};
var elementToUpdate = {};
//debug
var focusedObj = {};
var focusedObj2 = {};
// to be deleted
window.onload = function() {
canvas = document.getElementById("canvas");
inventory = document.getElementById("inventory");
AddListeners();
}
function AddListeners() {
document.getElementById("svg").addEventListener("mousedown", Drag);
document.getElementById("svg").addEventListener("mousemove", Drag);
document.getElementById("svg").addEventListener("mouseup", Drag);
}
// Drag function //
function Drag(e) {
var t = e.target, id = t.id, et = e.type; m = MousePos(e); //MousePos to ensure we obtain proper mouse coordinates
if (!draggable && (et == "mousedown")) {
if (t.className.baseVal=="inventory") { //if its inventory class item, this should get cloned into draggable
copy = t.cloneNode(true);
copy.onmousedown = copy.onmouseup = copy.onmousemove = Drag;
copy.removeAttribute("id");
copy._x = 0;
copy._y = 0;
canvas.appendChild(copy);
draggable = copy;
dPoint = m;
}
else if (t.className.baseVal=="draggable") { //if its just draggable class - it can be dragged around
draggable = t;
dPoint = m;
}
}
// drag the spawned/copied draggable element now
if (draggable && (et == "mousemove")) {
draggable._x += m.x - dPoint.x;
draggable._y += m.y - dPoint.y;
dPoint = m;
draggable.setAttribute("transform", "translate(" +draggable._x+","+draggable._y+")");
}
// stop drag
if (draggable && (et == "mouseup")) {
draggable.className.baseVal="draggable";
UpdateCoords(draggable);
console.log(draggable);
draggable._x = 0;
draggable._y = 0;
draggable = null;
}
}

Flex 4.6 Optimizing View appearance ContentCache VS override data for Mobile App

I read in this article http://www.adobe.com/devnet/flex/articles/flex-mobile-performance-checklist.html that I should not initialize a View's appearance in a creationComplete handler. Instead, I should change view's appearance in an overridden data setter.
The section in the article is:
Override the data setter instead of using bindings or initializing a View's appearance in a creationComplete handler
1-First, I would like to know if I got this right by doing the following:
//My code is loading a set of images and adding them in a View.
//On creationComplete of the View I am adding the images in case this is the first time
//the view is shown. In case the view has been already accessed I use the data:
protected function view1_creationCompleteHandler(event:FlexEvent):void
{
if(!data) //On first creation of the view I create the data object
{
data = new Object();
data.imageArray = new Array(); //set an array that will cache my images.
for(var i:int = 0; i<36;i++)
{
var img:Image = new Image();
img.source = 'assets/0'+i.toString()+'.png';
container.addElement(img);
(data.imageArray as Array).push(img);//Override the data for next time!
}
}
else//Next time use the save images
{
for(var ix:int = 0; ix<(data.imageArray as Array).length;ix++)
{
container.addElement((data.imageArray as Array)[ix]);
}
}
}
If I am doing this correctly, I would like to know which approach is best. The above one, or the next one I am going to show which uses the images contentLoader with caching and queuing enabled with a ContentCache:
protected function view1_creationCompleteHandler(event:FlexEvent):void
{
{
for(var i:int = 0; i<36;i++)
{
var img:Image = new Image();
img.contentLoader = ldr;
img.contentLoaderGrouping = 'gr1';
img.source = 'assets/0'+i.toString()+'.png';
container.addElement(img);
}
}
<fx:Declarations>
<s:ContentCache id="ldr" enableQueueing="true"
maxActiveRequests="1" maxCacheEntries="36"/>
</fx:Declarations>
Also if someone could tell me what is the contentLoaderGrouping for. I would be very grateful.
Thanks a lot!!!
PS:By the way both approaches work. The first approach is instant while the second approach shows the images beeing added in a very smooth way which actually gives a cool effect.
Neither. The point of the suggestion was to NOT alter the displaylist after creationComplete, which requires an additional update cycle. Instead you should inject the data property when you push your view on the stack, and initiate your changes in the setter. Using the ContentCache has nothing to do with it (and can sometimes cause additional overhead if not used correctly).
override public function set data(value:Object):void
{
super.data = value;
//this was poorly optimized, so I made it
//a little better...
var imageArray:Array = (value == null || value.imageArray == null)?
null : value.imageArray as Array;
if(imageArray == null) //On first creation of the view I create the data object
{
imageArray = new Array(36); //set an array that will cache my images.
for(var i:int = 0; i<36;i++)
{
var img:Image = new Image();
img.source = 'assets/0'+i.toString()+'.png';
container.addElement(img);
imageArray[i] = img;
}
super.data = {imageArray:imageArray}
}
else//Next time use the save images
{
var n:int = imageArray.length;
for (var j:int = 0; j < n; j++)
{
container.addElement(IVisualElement(imageArray[j]));
}
}
}
EDIT
I was mistaken about when the data property is set during the view life-cycle.
Here is how it works:
So you are correct that container would be null at that point. I was going to write up an example for you, but I'm having trouble figuring out what your end goal is here. Is there a specific reason you are storing the images on the data property? I think what you might actually want to do is this:
private var _data:Object = {cache: new ContentCache()};
protected function show_clickHandler(event:MouseEvent):void
{
this.navigator.pushView(views.MyView, _data);
}
And in the view...
<s:View xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" title="MyView">
<fx:Script>
<![CDATA[
import spark.components.Image;
import spark.core.ContentCache;
override protected function createChildren():void
{
super.createChildren();
//you might want to do a sanity first check to make sure the
//data was passed in correctly...
var cache:ContentCache = ContentCache(this.data.cache);
for(var i:int = 0; i < 36; i++)
{
var img:Image = new Image();
img.contentLoader = cache;
img.source = 'assets/0' + i.toString() + '.png';
container.addElement(img);
}
}
]]>
</fx:Script>
<s:VGroup id="container" />
</s:View>

how to create multiple slickgrids with a single function

I have a problem which I cannot seem to solve. I need to create a function which loops over an array of datasets and creates an independent slickgrids for each dataset. The catch is that the functions need to be bound to each grid independently. For example:
// this part works fine
for(var i=0; i<domain.length; i++){
dataView = new Slick.Data.DataView();
grid = new Slick.Grid('#' + domain[i].name, dataView, domain[i].columns, domain[i].options);
var data = domain[i].data;
// this works well and I am able to create several slickgrid tables
... etc ...
The problem is that every grid is now called "grid". Therefore, when I bind a function like this:
// controls the higlighting of the active row
grid.highlightActiveRow = function () {
var currentCell;
currentCell = this.getActiveCell();
I get a result which affects all grids (or in some cases only one grid).
How do I create multiple, independent grids with associated functions??? The problems seems to be that I have created one object "grid" and then assign all functions using the syntax grid.xxx - but I dont know how to create a unique object on each itteration.
Any help would be most appreciated.
PS: slickgrid is just amazing!!
Thanks
//****** UPDATE *********
#Jokob, #user700284
Thank you both for your help. Here is where I have manged to get to:
var dataView;
function buildTable() {
for(i = 0; i<domains.length; i++){
dataView = new Slick.Data.DataView();
var d = domains[i];
grid = new Slick.Grid('#' + d.name, dataView, d.columns, grids.options);
var data = d.data;
grid.init();
dataView.beginUpdate();
dataView.setItems(data);
// dataView.setFilter(filter); -- will be reinstated once i have this working
dataView.endUpdate();
arrOfGrids.push(grid);
};
};
Jakob - for now i am sticking to "for(i)" until I can wrap my head around your comment - which seems very sensible.
But, using the above, the grid data are not populating. I am not getting any js errors and the column headers are populating but not the data. The reference to d.data is definitely correct as I can see the data using the Chrome js debugger.
Any ideas? Many thanks for your help so far
Instead of assign all new grids to grid (in which case you overwrite the old one everytime you create a new one), push them to an array:
var arrayOfGrids = [];
for(var i=0; i<domain.length; i++) {
dataView = new Slick.Data.DataView();
arrayOfGrids.push(new Slick.Grid('#' + domain[i].name, dataView, domain[i].columns, domain[i].options));
// ....
Then, when you want to something with your grids, like adding the highlight-function, you loop over the array and do it for each element:
for ( var i=0; i<arrayOfGrids.length; i++ ) {
arrayOfGrids[i].highlightActiveRow = function () {
var currentCell;
currentCell = this.getActiveCell();
// ... etc...
BONUS
While we're at it, I would recommend that you use the forEach method that's available on the array-object when iterating over the arrays, rather than the for-loop. The unlike the loop, forEach creates a proper scope for your variables and it gets rid of the useless i-iteration variable:
var arrayOfGrids = [];
domain.forEach(function(d) {
dataView = new Slick.Data.DataView();
arrayOfGrids.push(new Slick.Grid('#' + d.name, dataView, d.columns, d.options));
// ....
And then the same for the other loop of course :)
You could try adding each of the grid instances to an array.You will be able to handle each of the grids differently if you want, by means of <arrray>[<array-index>]
var gridArr = [];
// this part works fine
for(var i=0; i<domain.length; i++){
dataView = new Slick.Data.DataView();
var grid = new Slick.Grid('#' + domain[i].name, dataView, domain[i].columns, domain[i].options);
var data = domain[i].data;
// this works well and I am able to create several slickgrid tables
... etc ...
gridArr.push(grid)
Then if you say gridArr[0] you can access the 1st grid,gridArr[1] second grid and so on.
Just in case anybody else is following this question - here is the working solution:
Many many thanks to #Jokob and #user700284
// default filter function
function filter(item) {
return true; // this is just a placeholder for now
}
var dataView;
function buildTables() {
for(i = 0; i<domains.length; i++){
dataView = new Slick.Data.DataView();
var d = domains[i];
grid = new Slick.Grid('#' + d.name, dataView, d.columns, options);
var data = d.data;
grid.init();
dataView.beginUpdate();
dataView.setItems(data);
dataView.setFilter(filter);
dataView.endUpdate();
grid.invalidate();
grid.render();
arrOfGrids.push(grid);
};
};

Store & retrieve the identifiers of a multipliable widget's instances

The aim is to remove only the last row at any time and only by the last remove button.
There is a user interface which building up as a multiplication of the same row. The number of rows are controlled by 'Add' & 'Remove' buttons which are also elements of the row. The problem is that the hidden widgets - that are applied for each row to distinguish the instances by storing their row numbers - are storing the very same number which is the last one. Except the first (0) hidden widget which stores the proper number (0). Where am I missing the point? How should this be resolved?
As per the remove buttons have two different purposes (not detailed here), we use a cacheService to distinguish the last row from all the others. Only the last row should be removed at any time.
var cache = CacheService.getPrivateCache();
we clear the cache and create the first instance
function doGet() {
var app = UiApp.createApplication();
app.add(app.createVerticalPanel().setId('mainContainer'));
cache.removeAll([]);
ui(0);
cache.put('numberOfInstances',0);
return app; }
each instance is held by a horizontal panel which contains the mentioned hidden widget, a label which informs about the instance number, and the Add & Remove buttons.
function ui(instance) {
var app = UiApp.getActiveApplication();
var eventContainer = app.createHorizontalPanel()
.setId('eventContainer' + instance);
var instanceContainer = app.createHidden('instanceContainer',instance);
var showInstance = app.createLabel(instance)
.setId('showInstance' + instance);
var addButton = app.createButton('Add')
.setId('add' + instance)
.addClickHandler(app.createClientHandler()
.forEventSource().setEnabled(false)) //avoiding multiple click during server response
.addClickHandler(app.createServerHandler('add')
.addCallbackElement(instanceContainer));
var removeButton = app.createButton('X')
.addClickHandler(app.createServerHandler('remove')
.addCallbackElement(instanceContainer));
app.getElementById('mainContainer')
.add(eventContainer
.add(instanceContainer)
.add(showInstance)
.add(addButton)
.add(removeButton));
return app; }
and the event handling...
function add(inst) {
var app = UiApp.getActiveApplication();
var instance = Number(inst.parameter.instanceContainer);
ui(instance+1);
cache.put('numberOfInstances',instance+1);
return app; }
function remove(inst) {
var app = UiApp.getActiveApplication();
var instance = Number(inst.parameter.instanceContainer);
var numberOfInstances = cache.get('numberOfInstances')
if( (instance != 0) && (instance = numberOfInstances) ) {
app.getElementById('mainContainer').remove(app.getElementById('eventContainer' + instance));
cache.put('numberOfInstances',instance-1);
app.getElementById('add' + (instance-1)).setEnabled(true); } //avoiding multiple click during server response
return app; }
The aim is to remove only the last row at any time and only by the last remove button.
Many Thanks.
Why don't you simply use a clientHandler just as you did on the 'add' button? You could target the preceding 'remove' button and disable it each time you create a new one and change /update each time you remove one row.
EDIT : I can suggest you something, feel free to have a look, I changed a bit the approach but it is working and I hope you'll find it at least interesting ;-)
Link to the online test
function doGet() {
var app = UiApp.createApplication();
var counter = app.createHidden().setName('counter').setId('counter').setValue('1');
var mainContainer = app.createVerticalPanel().setId('mainContainer')
app.add(mainContainer.add(counter));
var event1Container = app.createHorizontalPanel()
var showInstance = app.createLabel('1')
var addButton = app.createButton('Add')
.setId('add1')
.addClickHandler(app.createClientHandler()
.forEventSource().setEnabled(false)) //avoiding multiple click during server response
.addClickHandler(app.createServerHandler('add')
.addCallbackElement(mainContainer));
var removeButton = app.createButton('X')
.setId('remove1')
.addClickHandler(app.createServerHandler('remove')
.addCallbackElement(mainContainer));
mainContainer.add(event1Container
.add(showInstance)
.add(addButton)
.add(removeButton));
return app; }
function add(inst) {
var app = UiApp.getActiveApplication();
var hiddenVal =inst.parameter.counter;
var counterVal = Number(hiddenVal);
var mainContainer = app.getElementById('mainContainer')
var counter = app.getElementById('counter')
++ counterVal
counter.setValue(counterVal.toString())
var eventContainer = app.createHorizontalPanel().setId('eventContainer'+counterVal)
var showInstance = app.createLabel(counterVal.toString())
var addButton = app.createButton('Add')
.setId('add'+counterVal)
.addClickHandler(app.createClientHandler()
.forEventSource().setEnabled(false)) //avoiding multiple click during server response
.addClickHandler(app.createServerHandler('add')
.addCallbackElement(mainContainer));
var removeButton = app.createButton('X')
.setId('remove'+counterVal)
.addClickHandler(app.createServerHandler('remove')
.addCallbackElement(mainContainer));
app.add(eventContainer
.add(showInstance)
.add(addButton)
.add(removeButton));
if(counterVal>1){app.getElementById('remove'+(counterVal-1)).setEnabled(false)}
return app; }
function remove(inst) {
var app = UiApp.getActiveApplication();
var counterVal = Number(inst.parameter.counter);
var counter = app.getElementById('counter')
if(counterVal ==1) {return app}
var maincontainer = app.getElementById('mainContainer')
app.getElementById('eventContainer' + counterVal).setVisible(false)
--counterVal
counter.setValue(counterVal.toString())
app.getElementById('add'+counterVal).setEnabled(true)
app.getElementById('remove'+counterVal).setEnabled(true)
return app;
}
NOTE : I didn't make use of .remove(widget) since this is a fairly new method and I don't know exactly how it works... I'll test it later. Until then I used setVisible(false) instead, sorry about that :-)
Note 2 : I didn't use the cache since the hidden widget is sufficient to keep track of what is going on... if you needed it for something else then you could always add it back .

Resources