Flex multiple selection drop down list with scrolling - apache-flex

I'm working in flex and I made a custom drop down where there are check boxes to allow the user to select multiple options. I used this template.
However this does not have scrolling because if you allow scrolling for some reason the checkboxes start to mess up. For instance if you have options 1 to 8 and only 1 to 5 are shown. You select option 1 and then scroll down to select option 7. When you scroll up the checkboxes start to switch around like option 3 all of a sudden is showing selected. Keep scrolling up and down and the checkbox selection just changes on it's own. I think this is a rendering issue and the actual selection data isn't changed at all (it knows only option 1 and option 7 were selected). Any ideas on how to fix this?
public function onOpen(event:DropDownEvent):void
{//load the checkboxes and set the mouse tracker
activateAllCheckBoxes();
this.scroller.verticalScrollBar.addEventListener(Event.CHANGE, list_verticalScrollBar_change);
callLater(observeMouse);
}
private function list_verticalScrollBar_change(evt:Event):void
{
//currentlySelectedCheckBoxes = selectedCheckboxes;
UpdateCheckBoxesWhenScrolling();
selectedIndex = -1;
}
protected function UpdateCheckBoxesWhenScrolling():void
{
for (var c:int = 0; c < dataGroup.numElements; c++) {
var obj:DropDownCheckBox = dataGroup.getElementAt(c) as DropDownCheckBox;
if(obj!=null)
{
var judgDebtorFromCheckBox:JudgDebtor = (obj.data) as JudgDebtor;
if(FindInCurrentList(judgDebtorFromCheckBox.JudgmentDebtorId)>0)
{
obj.checkbox.selected = true;
}
else
{
obj.checkbox.selected = false;
}
}
}
}
private function FindInCurrentList(ID:int):int
{
for(var i:int=0;i<currentlySelectedCheckBoxes.length;i++)
{
var JD:JudgDebtor = currentlySelectedCheckBoxes.getItemAt(i) as JudgDebtor;
if(JD.JudgmentDebtorId == ID)
return 1;
}
return -1;
}
So above code I register a scroll event listener on the drop down. It will update the drop down entries which has a check box and it uses an array collection called: currentlySelectedCheckBoxes. I debug the UpdateCheckBoxesWhenScrolling function and it's working fine, in other words it will check off the ones selected but for some reason it still is showing the wrong results for instance 11 entries in the list and only the second one is selected I scroll down and I can't see the the second entry but all of a sudden the last entry is showing that it's checked off.

This happens because the drop down list reuses the renderers when you scroll. For example if you have checked 1st item and scroll, the renderer for that is reused to display the item that becomes visible when you scroll. So the last item shows as checked. To avoid messing up the selection, you will have to do the following in the renderer that you are using
override public function set data(value:Object):void
{
super.data = value;
//inspect the property which indicates whether to select the checkbox or not
//and set the value of selected property accordingly
}
Hope this helps

Related

Disabling a row in a DOJO / Gridx grid

I have a grid I created in Gridx which lists a bunch of users. Upon clicking a ROW in the grid (any part of that row), a dialog pops up and shows additional information about that user and actions that can be done for that user (disable user, ignore user, etc.) - when one of these options is selected from the pop up, I want to DISABLE that row. The logic for getting the row, etc. I can take care of, but I can't figure out how to make a grid row actually "appear" disabled and how to make that row no longer clickable.
Is there a simple way to do this? If you aren't familiar with gridx, solutions that apply to EnhancedGrids or other Dojo grids are also appreciated.
Alright now that I have a little more information here is a solution:
Keep a list of all the rows you have disabled so far either inside the Grid widget or in its parent code. Then on the onRowClick listener I would write code like this:
on(grid, "onRowClick", function(e) {
if(disabledRows[rowIndex]) {
return;
}
// Do whatever pop up stuff you want and after
// a user selects the value, you can "disable"
// your row afterwards by adding it to the disabled
// list so that it can no longer be clicked on.
var rowIndex = e.rowIndex;
disabledRows[rowIndex] = true;
// This is just some random class I made up but
// you can use css to stylize the row however you want
var rowNode = e.rowNode;
domClass.add(rowNode, "disabled");
});
Note that domClass is what I named "dojo/dom-class". Hope this helps!
This is perhaps not exactly what you are seaching for:
If you want to hide one or more rows by your own filterfunction you could just add to these rows in the DOM your own class for nodisplay. Here I show you a function for display only those rows which have in a choiceable field/column a value inside your filterlist.
function hideRowFilter(gridId, fieldName, filterList)
{
var store = gridId.store;
var rowId;
store.query(function(object){
rowId = gridId.row(object.id,true).node();
if (filterList.indexOf(object[fieldName]) == -1)
domClass.add(rowId, "noDisplay"); // anzeigen
else
domClass.remove(rowId, "noDisplay"); // verstecken
});
}
CSS:
.noDisplay { display: none; }
So I can for example display only the entries with a myState of 3 or 4 with this call:
hideRowFilter(gridId, 'myState', [3, 4]);
Note that domClass is what I named "dojo/dom-class"

Cursor moves to the start location on selection in spark combobox?

I have a . I provide arraylist as its data provider. my question is why moves to the ing location in when I select any item using enter key. Also when I press space from keyboard, again moves to ing location. How can I fix this? Thanks
protected function onInputKeyDown(e:KeyboardEvent):void
{
if(e.keyCode == 13)
{
AddPath(cb.textInput.text);
cb.dataProvider = recentList;
}
}
here recentList is a Bindable ArrayList. Every Time when I enter anything in ComboBox and press Enter, The cursor moves to the beginning in the Text Area of ComboBox. AddPath function simply adds the new data to the recentList.
When you set cb.dataProvider = recentList; you are essentially assigning a new pointer which overrides the previous list and resets the cursor.
You should be able to create a variable containing the selected item and manually set the ComboBox to that item on click/enter after you carry out the cb.dataProvider = recentList;
protected function onInputKeyDown(e:KeyboardEvent):void
{
if(e.keyCode == 13)
{
var selectedItem:String = cb.selectedItem
AddPath(cb.textInput.text);
cb.dataProvider = recentList;
cb.selectedItem(selectedItem);
}
}
Apologies if the code isn't perfect, but the theory should be right.

How to keep a list from scrolling on dataProvider refresh/update/change?

I have a simple list and a background refresh protocol.
When the list is scrolled down, the refresh scrolls it back to the top. I want to stop this.
I have tried catching the COLLECTION_CHANGE event and
validateNow(); // try to get the component to reset to the new data
list.ensureIndexIsVisible(previousIndex); // actually, I search for the previous data id in the IList, but that's not important
This fails because the list resets itself after the change (in DataGroup.commitProperties).
I hate to use a Timer, ENTER_FRAME, or callLater(), but I cannot seem to figure out a way.
The only other alternatives I can see is sub-classing the List so it can catch the dataProviderChanged event the DataGroup in the skin is throwing.
Any ideas?
Actually MUCH better solution to this is to extend DataGroup. You need to override this.
All the solutions here create a flicker as the scrollbar gets resetted to 0 and the it's set back to the previous value. That looks wrong. This solution works without any flicker and the best of all, you just change DataGroup to FixedDataGroup in your code and it works, no other changes in code are needed ;).
Enjoy guys.
public class FixedDataGroup extends spark.components.DataGroup
{
private var _dataProviderChanged:Boolean;
private var _lastScrollPosition:Number = 0;
public function FixedDataGroup()
{
super();
}
override public function set dataProvider(value:IList):void
{
if ( this.dataProvider != null && value != this.dataProvider )
{
dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
super.dataProvider = value;
if ( value != null )
{
value.addEventListener(CollectionEvent.COLLECTION_CHANGE, onDataProviderChanged);
}
}
override protected function commitProperties():void
{
var lastScrollPosition:Number = _lastScrollPosition;
super.commitProperties();
if ( _dataProviderChanged )
{
verticalScrollPosition = lastScrollPosition;
}
}
private function onDataProviderChanged(e:CollectionEvent):void
{
_dataProviderChanged = true;
invalidateProperties();
}
override public function set verticalScrollPosition(value:Number):void
{
super.verticalScrollPosition = value;
_lastScrollPosition = value;
}
}
I ll try to explain my approach...If you are still unsure let me know and I ll give you the source code as well.
1) Create a variable to store the current scroll position of the viewport.
2) Add Event listener for Event.CHANGE and MouseEvent.MOUSE_WHEEL on the scroller and update the variable created in step 1 with the current scroll position;
3) Add a event listener on your viewport for FlexEvent.UpdateComplete and set the scroll position to the variable stored.
In a nutshell, what we are doing is to have the scroll position stored in variable every time user interacts with it and when our viewport is updated (due to dataprovider change) we just set the scroll position we have stored previously in the variable.
I have faced this problem before and solved it by using a data proxy pattern with a matcher. Write a matcher for your collection that supports your list by updating only changed objects and by updating only attributes for existing objects. The goal is to avoid creation of new objects when your data source refreshes.
When you have new data for the list (after a refresh), loop through your list of new data objects, copying attributes from these objects into the objects in the collection supporting your list. Typically you will match the objects based on id. Any objects in the new list that did not exist in the old one get added. Your scroll position will normally not change and any selections are usually kept.
Here is an example.
for each(newObject:Object in newArrayValues){
var found:Boolean = false;
for each(oldObject:Object in oldArrayValues){
if(oldObject.id == newObject.id){
found = true;
oldObject.myAttribute = newObject.myAttribute;
oldObject.myAttribute2 = newObject.myAttribute2;
}
}
if(!found){
oldArrayValues.addItem(newObject);
}
}
My solution for this problem was targeting a specific situation, but it has the advantage of being very simple so perhaps you can draw something that fits your needs from it. Since I don't know exactly what issue you're trying to solve I'll give you a description of mine:
I had a List that was progressively loading data from the server. When the user scrolled down and the next batch of items would be added to the dataprovider, the scrollposition would jump back to the start.
The solution for this was as simple as stopping the propagation of the COLLECTION_CHANGE event so that the List wouldn't catch it.
myDataProvider.addEventListener(
CollectionEvent.COLLECTION_CHANGE, preventRefresh
);
private function preventRefresh(event:CollectionEvent):void {
event.stopImmediatePropagation();
}
You have to know that this effectively prevents a redraw of the List component, hence any added items would not be shown. This was not an issue for me since the items would be added at the end of the List (outside the viewport) and when the user would scroll, the List would automatically be redrawn and the new items would be displayed. Perhaps in your situation you can force the redraw if need be.
When all items had been loaded I could then remove the event listener and return to the normal behavior of the List component.

Silverlight DataGrid with vertical Scrollbar problem

I have a Datagrid filled with a table. Now the vertical scrollbar shows up because the table doesn't fit. That's fine so far. Now in the last column I have defined a Button in the xaml file. All these buttons have the same callback, but I can distinguish from the selectedIndex of the table what this callback should do. Because clicking the button automatically also selects the line in the DataGrid where this button lives. That's fine so far. Now in my app, for some rows I want to disable the Button, because it has no meaning for that specific row. So what I did is take a subscription on event Load of each Button and let the callback set the MaxWidth = 0, if the button has no meaning. This works fine too, but only initially. As soon as I start dragging the scrollbar, at random places in the Button column buttons show up, or wrong buttons get MaxWidth = 0. I have the strong feeling that cells that scrolled out at the top are being reused at the bottom, but I don't get an event, or at least I don't know which event I should subscribe on. I don't know how to identify the scrollbar. Has anyone a suggestion to tackle this problem?
I finally found a solution to this problem myself, and I will post it for the record.
The event you should subscribe on is LoadingRow (generated by the DataGrid).
In the callback
void TableLoadingRow(object sender, DataGridRowEventArgs e)
you can identify an element in a cell by using VisualTreeHelper for instance as follows:
private void ButtonSetMaxWidth(DependencyObject reference, int maxWidth)
{
if (reference != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(reference); i++)
{
var child = VisualTreeHelper.GetChild(reference, i);
if (child.GetType() == typeof(Button))
{
Button b = (Button)child;
if (b.Name == "TheNameOfTheButtonInTheXAML")
{
b.MaxWidth = maxWidth;
return;
}
}
ButtonSetMaxWidth(child, maxWidth);
}
}
return;
}

asp.net hidden field not setting new value, need disabled alternative

I have a hidden field on my page
<input runat="server" type="hidden" id="selectedIndex" />
and it is being set by this bunch of code, an onclick event to a gridview's row:
var gridViewCtlId = '<%=GridView.ClientID%>';
var selectedIndex = '#<%=selectedIndex.ClientID%>';
var itemVisible = '<%=ItemVisible.ClientID%>';
var gridViewCtl = null;
var curSelRow = null;
var previousRowIndx = null;
window.onload = function showQuery()
{
if ($(selectedIndex).val() != undefined)
{
if ($(selectedIndex).val() != '')
{
var prevRowID = $(selectedIndex).val();
var prevRow = getSelectedRow(prevRowID);
prevRow.style.backgroundColor = '#1A8CD4';
}
}
}
function getGridViewControl(rowIdx)
{
if (gridViewCtl == null)
{
gridViewCtl = document.getElementById(gridViewCtlId);
}
}
function onGridViewRowSelected(rowIdx)
{
if (document.getElementById(gridViewCtlId).disabled == false)
{
var selRowCCA = getSelectedRow(rowIdx);
if (curSelRow != null)
{
var previousRow = getSelectedRow(previousRowIndx);
var CountIdx = previousRowIndx % 2;
if (document.getElementById(itemVisible) == null)
{
if (CountIdx == 0)
{
previousRow.style.backgroundColor = 'Silver';
}
else
{
previousRow.style.backgroundColor = 'White';
}
}
}
if (null != selRow)
{
previousRowIndx = rowIdx;
curSelRow = selRow;
selRow.style.backgroundColor = '#1A8CD4';
}
}
}
function getSelectedRow(rowIdx)
{
getGridViewControl(rowIdx);
if (gridViewCtl != null)
{
$(selectedIndex).val(rowIdx);
return gridViewCtl.rows[rowIdx];
}
return null;
}
This is what happens: When The page first loads, the hidden field is undefined, which it should be. When I click on a row and then click the 'select' button which then calls this:
GridView.Attributes.Add("disabled", "true");
The gridview becomes disabled (along with the select button) and another gridview comes up (which should happen depending on what is seleted in the first gridview). So now, here is the problem. When I click on a row in the gridview (I'm only talking about the initial gridview, not the secondary one which comes up, that's not an issue here), and click select, everything gets greyed out and most of the time, the selected row will highlight when the page loads (the other times for some reason it defaults to row #2). Then, say you click on row 4 then click on row 1 and then click select, for some reason row 4 will remain highlighted and row 4's data will then populate the second gridview, like you never clicked row 1. But if I click row 4 then click row 1 then click row 1 again, does it save. Does anyone know why that happens?
Also, I'm pretty much trying to disable the first gridview when select is hit so I do
GridView.Attributes.Add("disabled", "true");
rather than
GridView.Enabled = false;
If a user re-clicks the search button (another button located previously on the page which makes this gridview become visible), I would like the secondary gridview to become hidden, and the primary gridview (this one in question) to become re-enabled. But doing
GridView.Attributes.Add("disabled", "false");
when the search button is fired only disables the gridview, which is very weird. Now I know that the disabled field is not supported by any other browser except IE, and i only use it because I need to check if the gridview is disabled so a user cant click on another row after they've made their selection (which happens if I dont do the following:
if (document.getElementById(gridViewCtlId).disabled == false)
So could anyone let me know of another way of accomplishing that task? Thanks again in advance.
Some bits of info on disabled:
Browsers won't send any disabled control's value to the server. This is by definition.
Disabled field is supported by other browsers, but it uses a different model. Note list of supported browsers: http://www.w3schools.com/tags/att_input_disabled.asp (also how it is defined disabled='disabled').
Also see how it compares to the read-only: http://www.w3.org/TR/html401/interact/forms.html#h-17.12.2
Also note according to the standard its support its limited to certain elements. This is important, as you are applying it at an unsupported html element, which is also a likely cause of it not working in other browsers in your scenario. You can disable the supported control by using an script, getting the controls to apply it like $get("someClientID").getElementsByTagName("input");

Resources