AdvancedDataGrid total sum of branch nodes - actionscript-3

Introduction:
I have an AdvancedDataGrid displaying hierarchical data illustrated by the image below:
The branch nodes "Prosjekt" and "Tiltak" display the sum of the leaf nodes below.
Problem: I want the root node "Tavle" to display the total sum of the branch nodes below. When i attempted to do this by adding the same SummaryRow the sum of the root node was not calculcated correctly(Every node's sum was calculated twice).
dg_Teknikktavles = new AutoSizingAdvancedDataGrid();
dg_Teknikktavles.sortExpertMode="true";
dg_Teknikktavles.headerHeight = 50;
dg_Teknikktavles.variableRowHeight = true;
dg_Teknikktavles.addEventListener(ListEvent.ITEM_CLICK,dg_TeknikktavlesItemClicked);
dg_Teknikktavles.editable="false";
dg_Teknikktavles.percentWidth=100;
dg_Teknikktavles.minColumnWidth =0.8;
dg_Teknikktavles.height = 1000;
var sumFieldArray:Array = new Array(context.brukerList.length);
for(var i:int = 0; i < context.brukerList.length; i++)
{
var sumField:SummaryField2 = new SummaryField2();
sumField.dataField = Ressurstavle.ressursKey + i;
sumField.summaryOperation = "SUM";
sumFieldArray[i] = sumField;
}
var summaryRow:SummaryRow = new SummaryRow();
summaryRow.summaryPlacement = "group";
summaryRow.fields = sumFieldArray;
var summaryRow2:SummaryRow = new SummaryRow();
summaryRow2.summaryPlacement = "group";
summaryRow2.fields = sumFieldArray;
var groupField1:GroupingField = new GroupingField();
groupField1.name = "tavle";
//groupField1.summaries = [summaryRow2];
var groupField2:GroupingField = new GroupingField();
groupField2.name = "kategori";
groupField2.summaries = [summaryRow];
var group:Grouping = new Grouping();
group.fields = [groupField1, groupField2];
var groupCol:GroupingCollection2 = new GroupingCollection2();
groupCol.source = ressursTavle;
groupCol.grouping = group;
groupCol.refresh();
Main Question: How do i get my AdvancedDataGrid's (dg_Teknikktavles) root node "Tavle" to correctly display the sum of the two branch nodes below?
Side Question: How do i add a red color to the numbers of the root node's summary row that exceed 5? E.g the column displaying 8 will exceed 5 in the root node's summary row, and should therefore be marked red
Thanks in advance!

This is a general answer, without code examples, but I had to do the same just couple of days ago, so my memory is still fresh :) Here's what I did:
Created a class A to represent an item renderer data, extended it from Proxy (I had field names defined at run time), and let it contain a collection of values as it's data member. Once accessed through flash_proxy::getPropery(fieldName) it would find a corresponding value in the data member containing the values and return it. Special note: implement IUID, just do it, it'll save you couple of days of frustration.
Extended A in B, added a children property containing ArrayCollection of A (don't try to experiment with other collection types, unless you want to find yourself examining tons of framework code, trust me, it's ugly and is impossible to identify as interesting). Let B override flash_proxy::getPropery - depending of your compiler this may, or may not be possible, if not possible - call some function from A.flash_proxy::getPropery() that you can override in B. Let this function query every instance of A, which is a child of B, asking the same thing, as DataGrid itself would, when building item renderers - this way you would get the total.
When creating a data provider. Create an ArrayCollection of B (again, don't try to experiment with other collections--unless you are ready for lots of frustration). Create Hierarchical data that uses this array collection as a source.
Colors - that's what you use item renderers for, just look up any tutorial on using item renderers, that must be pretty basic.

In case someone else has the same problem:
The initial problem that everything was summed twice, was the result of using the same Array of SummaryField2 (sumFieldArray in the code) for both grouping fields(GropingField2 tavle and kategori)
The Solution to the main question: was to create a new array of summaryfields for the root node(in my intial for loop):
//Summary fields for root node
var sumFieldRoot:SummaryField2 = new SummaryField2();
sumFieldRoot.dataField = Ressurstavle.ressursKey + i;
sumFieldRoot.summaryOperation = "SUM";
sumFieldArrayRoot[i] = sumFieldRoot;
Answer to the side question:
This was pretty much as easy as pointed out by wvxyw. Code for this solution below:
private function summary_styleFunction(data:Object, col:AdvancedDataGridColumn):Object
{
var output:Object;
var field:String = col.dataField;
if ( data.children != null )
{
if(data[field] >5){
output = {color:0xFF0000, fontWeight:"bold"}
}
else {
output = {color:0x006633, fontWeight:"bold"}
}
//output = {color:0x081EA6, fontWeight:"bold", fontSize:14}
}
return output;
}

Related

Using ItemCollection on a BoxFolder type with Box API only returns 100 results and cannot retrieve the remaining ones

For a while now, I've been using the Box API to connect Acumatica ERP to Box and everything has been going fine until recently. Whenever I try to use a BoxCollection type with the property ItemCollection, I'll only get the first 100 results no matter the limit I set in the GetInformationAsync(). Here is the code snippet:
[PermissionSet(SecurityAction.Assert, Name = "FullTrust")]
public BoxCollection<BoxItem> GetFolderItems(string folderId, int limit = 500, int offset = 0)
{
var response = new BoxCollection<BoxItem>();
var fieldsToGet = new List<string>() { BoxItem.FieldName, BoxItem.FieldDescription, BoxItem.FieldParent, BoxItem.FieldEtag, BoxFolder.FieldItemCollection };
response = Task.Run(() => Client.FoldersManager.GetFolderItemsAsync(folderId, limit, offset)).Result;
return response;
}
I then pass that information on to a BoxFolder type variable, and then try to use the ItemCollection.Entries property, but this only returns 100 results at a time, with no visible way to extract the remaining 61 (in my case, the Count = 161, but Entries = 100 always)
Another code snippet of the used variable, I am basically trying to get the folder ID based on the name of the folder inside Box:
private static void SyncProcess(BoxFolder rootFolder, string folderName)
{
var boxFolder = rootFolder.ItemCollection.Entries.SingleOrDefault(ic => ic.Type == "folder" && ic.Name == folderName);
}
I wasn't able to find anything related to that limit = 100 in the documentation and it only started to give me problems recently.
I had to create a work around by using the following:
var boxCollection = client.GetFolderItems(rootFolder.Id);
var boxFolder = boxCollection.Entries.SingleOrDefault(ic => ic.Type == "folder" && ic.Name == folderName);
I was just wondering if there was a better way to get the complete collection using the property ItemCollection.Entries like I used to, instead of having to fetch them again.
Thanks!
Box pages folder items to keep response times short. The default page size is 100 items. You must iterate through the pages to get all of the items. Here's a code snippet that'll get 100 items at a time until all items in the folder are fetched. You can request up to 1000 items at a time.
var items = new List<BoxItem>();
BoxCollection<BoxItem> result;
do
{
result = await Client.FoldersManager.GetFolderItemsAsync(folderId, 100, items.Count());
items.AddRange(result.Entries);
} while (items.Count() < result.TotalCount);
John's answer can lead to a duplicate values in your items collection if there will be external/shared folders in your list. Those are being hidden when you are calling "GetFolderItemsAsync" with "asUser" header set.
There is a comment about it in the Box API's codeset itself (https://github.com/box/box-windows-sdk-v2/blob/main/Box.V2/Managers/BoxFoldersManager.cs)
Note: If there are hidden items in your previous response, your next offset should be = offset + limit, not the # of records you received back.
The total_count returned may not match the number of entries when using enterprise scope, because external folders are hidden the list of entries.
Taking this into account, it's better to not rely on comparing the number of items retrieved and the TotalCount property.
var items = new List<BoxItem>();
BoxCollection<BoxItem> result;
int limit = 100;
int offset = 0;
do
{
result = await Client.FoldersManager.GetFolderItemsAsync(folderId, limit, offset);
offset += limit;
items.AddRange(result.Entries);
} while (offset < result.TotalCount);

How to push instantiated MC into Array dynamically?

Im really stuck. I have 5 MC´s that are being spliced from one array at a certain time. In that same function I want to push another movieclips into another array. The two arrays holds mc's that represent right or wrong answers. So when one question is being given a correct answer that questions visualisation alters.
This function holds a incrementing variable as I do want the mc's to be pushed by the user and one at the time. The thing is I cant seem to refer them properly.
I´ve tried
pQuestSum = this[pQuest + pQuestNumber];
and
pQuestSum = this[pQuest] + pQuestNumber;
and pretty much everything I´ve imagined would work...but the problem is I havent tried
the right thing.
when I trace pQuestSum (which would be the reference) I get an error saying thats its not a number.
this is one of 5 mc's named from 1-5:
var passedquest1:PassedQuest = new PassedQuest();
this is the vars that i try to to build a reference of
var pQuest = "passedquest";
var pQuestNumber = 1;
var pQuestSum;
var questCorrArray:Array = [];
if(event.target.hitTestObject(questArray[ix])){
removeChild(questArray[ix]);
questArray.splice(ix,1);
pQuestNumber ++;
pQuestSum = this[pQuest] + pQuestNumber;
trace("pQuestSum"); // NaN
questCorrArray.push(pQuestSum);
//trace(questArray.length);
pointsIncreased = false;
questPoints = 0;
}
How do I refer an existing movieclip when the reference consists of both a string and a number? Hope I made myself somewhat clear:)
If you had an instance of an object on your timeline called "passedquest1" (as an example), then you could access it this way:
var myObj = this["passedquest" + 1];
Or,
var pQuest = "passedquest";
var pQuestNumber = 1;
var myObj = this[pQuest+ pQuestNumber.toString()];
When you do this: pQuestSum = this[pQuest] + pQuestNumber;, you are trying add the number to an object (this[pQuest]), unless you have number/int var called "passedquest", this will result in NaN.

Save items(MovieClips) and dynamically create them

I made an invetory in AS3 which allows me to put items on slots in a closet, or in slots in the inventory. It completely works, but there is one problem.
In the game you are supposed to be able to buy new items and add them to the closet. I want this to be saved so that it is available the next time you play.
To do this, I want to save an Array to a SharedObject, then create the items dynamically from the array.
Right now I'm using the old fashioned hard coding for each object;
Itemwrench = new WrenchItem();
Itemwrench.x = par.toolCloset.kast_1.slotTC1.x + 400;
Itemwrench.y = par.toolCloset.kast_1.slotTC1.y + 245;
Itemwrench.gotoAndStop(2);
Itemwrench.name = "slotTC1";
Itemwrench.TC = 1;
NotinventoryParentTC.addChild(Itemwrench);
However, to add them dynamically I'd have to use getChildByName before it is added to the stage, which is not possible.
If possible could you show me how to do this correctly?
The information you need:
-The items are all stored in a closet with slots(Instances in the closet movieclip).
-The items need to get the name slotTC + the integer in a for loop.
-The name of the items change according to the slot number they are assigned when you take them out of the closet or put them back into the closet.
for(var i:int = 0; i < itemsInTC.length - itemsInTC.indexOf(e.currentTarget.name) - 1; i++)
{
nextSlotTC = "slotTC" + (itemsInTC.indexOf(e.currentTarget.name) +2 +i);
trace("Next Slot: " + nextSlotTC);
TempStrTC = "slotTC" + (itemsInTC.indexOf(e.currentTarget.name) +1 +i).toString();
trace("temp string: " + TempStrTC);
NotinventoryParentTC.getChildByName(nextSlotTC).x =
par.toolCloset.kast_1.getChildByName(TempStrTC).x + 400;
NotinventoryParentTC.getChildByName(nextSlotTC).y =
par.toolCloset.kast_1.getChildByName(TempStrTC).y + 245;
if(Boolean(NotinventoryParentTC.getChildByName(nextSlotTC)))
{
NotinventoryParentTC.getChildByName(nextSlotTC).name = TempStrTC;
}
}
This way I assign a new name and place them in the slot with the new name they received.
So now my question:
How do make it so that you can save the items to a shared object so that they are in the closet the next time you play the game.
Sorry for the long question.
Thanks in advance,
Milan.
You cannot directly store a DisplayObject in a SharedObject, as it contains memory links which will not be valid if you load such an object. A comon way to work around this is to store a significant data portion of that object. For example, you devise a following structure:
class SlotStructure {
public var slotID:int;
public var itemID:int;
public var itemName:String;
public var itemParameters:Array; // stuff simple types here
}
Then, for each of your items in inventory, you generate a SlotStructure object describing a particular inventory object. For your wrench it could look like this:
var ss:SlotStructure=new SlotStructure();
ss.slotID=1;
ss.itemID=getID(item); // assuming a function that returns a type of an item
ss.itemName=item.name;
ss.itemParameters=new Array();
for (var param:String in item) ss.itemParameters.push({name:param,value:item[param]});
Then you store an array of these into your SharedObject. To retrieve an inventory from a SharedObject you do:
public static const
registerClassAlias("SlotStructure",SlotStructure); // to be able to typecast from SO
for (var i:int=0;i<slots.length;i++) {
var ss:SlotStructure=slots[i];
var item:Item=new getClassFromID(ss.itemID)(); // a function that returns class
// say 1 - wrench, 2 - short sword, 3 - long sword, etc, one type=one ID
for each (var o:Object in ss.itemParameters)
item[o.name]=o.value;
placeIntoSlot(item,ss.slotID); // this does manipulation with x&y and display
}
A function getClassByID() might look like this:
private static const CLASSES:Array=
[StoneItem,WrenchItem,ShortswordItem,LongswordItem,...];
// manually stuff all your items in this!
public function getClassByID(id:int):Class {
return CLASSES[id];
}
The entire solution can be tailored to particular task, for example, in my game I have gems, that differ by location, type, size and score, so I store just these values and then I create new gems, set location, type, size and score with one function that sets all the other relevant parameters of that gem to align with stored info, and call it after making a gem with new Gem(). Your items might too be only worthy of a class name and ID in the class table, so store these with slot numbers and create objects that will have all their properties already set.

as3 dynamic variable within a loop

I need to have the screenPage var accessible for later calls. I am dynamically creating several pages. The issue is when I call screenPage.startDrag(); then it only drags the last page. How can I add a var in the name or make all the screenPages accessible through code?
Here is code in short:
var screenPage:MovieClip;
for(var p = 1; daTotalPages >= p; p++)
{
screenPage = new theFlagScreen();
}
Can I make screenPage a dynamic var and add like a 1 at the end and then a 2 and so forth with the loop?
Why not use an array ?
var pages:Array = new Array;
for(var p = 1; daTotalPages >= p; p++)
{
var screenPage:MovieClip = new theFlagScreen();
pages.push(screenPage)
}
Now you have an array containing all of your instances.
var myPage:MovieClip = pages[5] as MovieClip;
myPage.startDrag();
Also, the code you have above is creating an instance each time through, but since you then go ahead and create a new one each iteration and assign it to the same variable without storing it or adding it to the display list.... once you create a new one, the last one is marked for garbage collection.
The result is only the LAST instance you created still exists as it's the only one that has a variable that references it.
screenPage is a reference to just ONE thing. In your loop you keep overwriting it as you go and so you end up with it pointing to the very last instance of theFlagScreen... all the others are now lost.
You should follow prototypical's advice and store these instances in an array, that way you don't lose them.

Linq-to-SQL with a table valued UDF user defined function

I am new to Linq and trying to get a handle on how to bind a drop down to a SQL user defined function.
//Populate the Pledge dropdown
var db = new App_Data.MyDBDataContext();
int? partnerID = Convert.ToInt32(Request.QueryString["PartnerID"]);
var pledges =
from p in db.ufn_AvailablePledgesByPartner(partnerID)
select new
{
PledgeAndPartnerName = p.PledgeAndPartnerName,
PledgeID = p.PledgeID
};
DropDownList ddlPledgeID = (DropDownList)DetailsViewContribution.FindControl("DropDownListPledgeID");
ddlPledgeID.DataSource = pledges;
ddlPledgeID.DataTextField = pledges.PledgeAndPartnerName;
ddlPledgeID.DataValueField = pledges.PledgeID;
The current problem is the last 2 lines where I'm trying to reference properties of the anonymous class. "'System.Linq.IQueryable' does not contain a definition for 'PledgeAndPartnerName' and no extension method..." I naively thought the compiler was supposed to figure this out, but obviously I'm assuming C# is now more dynamic than it really is.
Thanks for any input.
Try this:
ddlPledgeID.DataTextField = "PledgeAndPartnerName";
ddlPledgeID.DataValueField = "PledgeID";