Xamarin - Updating listview items as data is received - xamarin

I have a Listview that requires two API calls, one for all the text data, and one for image data for each item (via byte array). I first get all the text data and show the list, with a placeholder image. I then do a loop over all the items in the list view, and for each item I retrieve its image. I'd like the Listview to update after each image is retrieved, so that slowly the images will populate down the list, 1 by 1. However, the Listview only updates as Listview items are shown for the first time. For example, I'll load 20 items, with 7 being shown (the rest need to be scrolled to). If I wait a few seconds and scroll, the 8th+ items will show the updated images. Once all 20 items have retrieved their image, the entire list will update and show all the images.
I've tried setting the ItemSource (Results) of the listview to null after each image is retrieved, which calls OnPropertyChanged(), however the list still doesn't update for each item. Here's the code:
public async Task GetImagesForResults()
{
using (var scope = App.Container.BeginLifetimeScope())
{
using (new Busy(this))
{
var peopleService = scope.Resolve<IPersonService>();
_incompleteResults = Results;
if (_incompleteResults != null && _incompleteResults.Count > 0)
{
foreach(PersonViewModel p in _incompleteResults)
{
if (StopLoading)
{
break;
}
IsBusy = false;
var peopleImage = await peopleService.GetPersonImage(p.Email);
if ((peopleImage.Error == null) && (peopleImage.Response != null))
{
p.Picture = peopleImage.Response;
p.PictureImageSource = ImageSource.FromStream(() => new MemoryStream(peopleImage.Response));
p.ShowInitials = false;
Results = null;
Results = _incompleteResults;
}
}
}
}
}
}
Thanks for the help.

Related

How do I update model in Master detail razor page with AJAX results

I have a parent table with rows.
When they select a row, an AJAX call fires that returns the child details.
I have multiple text boxes showing child properties
<div class="row">
#Html.CheckBoxFor(m => m.Child.Property)
#Html.LabelFor(m => m.Child.Property)
</div>
but I can't see how to update the text boxes with the child I get back in AJAX results.
The best I've been able to do is manually updating each field from the 'complete' method. But I've got about 30 more fields to add and it feels like the wrong approach.
How do I bind the edit boxes to the returned model without using partials and without refreshing the entire page?
I added Child as a property in the #model, and the TextFor appears to bind properly. But of course
#Model.Child = child
does not. So they never show any data.
This question was based on a misunderstanding on my part. At first I deleted the question when I realized my mistake. I'm reinstating it because I think it is an easy mistake for a noobie to fall into and once you do, the answer is hard to sort out.
The problem is that #model no longer exists once the page is rendered. There is no data binding going on behind the scenes as I thought there was.
Your options are
populating the elements manually. (this will need editing to fit your particular elements)
function DisplayMergeSection(data) {
for (var key of Object.keys(data)) {
DisplayElement(data, key, "#Clients_");
}
}
function DisplayElement(data, key, prefix) {
return;
var val = data[key];
var valString = data[key + "String"];
var element = $(prefix + key)[0];
if (element && element.type === 'text') {
if ((val || '').toString().indexOf("Date(") > -1) {
var dtStart = new Date(parseInt(val.substr(6)));
element.value = moment(dtStart).format('MM/DD/YYYY');
} else {
element.value = val;
}
} else if (element && element.type === 'checkbox') {
element.checked = val;
} else if (element && element.type === 'select-one') {
element.value = valString;
} else if (element && element.nodeName === 'DIV') {
if ((val || '').toString().indexOf("Date(") > -1) {
var dtStart = new Date(parseInt(val.substr(6)));
element.innerText = moment(dtStart).format('MM/DD/YYYY');
} else {
element.innerText = val;
}
}
}
create a bunch of observables with knockout and then set up data binding. This is a lot cleaner.
https://knockoutjs.com/documentation/json-data.html
set up a mapping with the knockout plugin.
https://knockoutjs.com/documentation/plugins-mapping.html

How can I replace an image in Google Documents?

I'm trying to insert images into Google Docs (other GSuite apps later) from an Add-On. I've succeeded in fetching the image and inserting it when getCursor() returns a valid Position. When there is a selection (instead of a Cursor), I can succeed if it's text that's selected by walking up to the Parent of the selected text and inserting the image at the start of the paragraph (not perfect, but OK).
UPDATE: It seems that I was using a deprecated method (getSelectedElements()), but that didn't fix the issue. It seems the issue is only with wrapped images as well (I didn't realize that the type of the object changed when you changed it to a wrapped text).
However, when an wrapped-text Image (presumably a PositionedImage) is highlighted (with the rotate and resize handles visible in blue), both getSelection() and getCursor() return null. This is a problem as I would like to be able to get that image and replace it with the one I'm inserting.
Here's my code... any help would be great.
var response = UrlFetchApp.fetch(imageTokenURL);
var selection = DocumentApp.getActiveDocument().getSelection();
if (selection)
{
Logger.log("Got Selection");
var replaced = false;
var elements = selection.getRangeElements();
if (elements.length === 1
&& elements[0].getElement().getType() === DocumentApp.ElementType.INLINE_IMAGE)
{
//replace the URL -- this never happens
}
//otherwise, we take the first element and work from there:
var firstElem = elements[0].getElement();
Logger.log("First Element Type = " + firstElem.getType());
if (firstElem.getType() == DocumentApp.ElementType.PARAGRAPH)
{
var newImage = firstElem.asParagraph().insertInlineImage(0, response);
newImage.setHeight(200);
newImage.setWidth(200);
}
else if (firstElem.getType() == DocumentApp.ElementType.TEXT)
{
var p = firstElem.getParent();
if (p.getType() == DocumentApp.ElementType.PARAGRAPH)
{
var index = p.asParagraph().getChildIndex(firstElem);
var newImage = p.asParagraph().insertInlineImage(index, response);
newImage.setHeight(200);
newImage.setWidth(200);
}
}
} else {
Logger.log("Checking Cursor");
var cursor = DocumentApp.getActiveDocument().getCursor();
if (cursor)
{
Logger.log("Got Cursor: " + cursor);
var newImage = cursor.insertInlineImage(response);
var p = cursor.getElement();
var size=200;
newImage.setHeight(size);
newImage.setWidth(size);
}
}
You are using the deprecated 'getSelectedElements()' method of the Range class. You may notice it's crossed out in the autocomplete selection box.
Instead, use the 'getRangeElements()' method. After selecting the image in the doc, the code below worked for me:
var range = doc.getSelection();
var element = range.getRangeElements()[0].getElement();
Logger.log(element.getType() == DocumentApp.ElementType.INLINE_IMAGE); //logs 'true'

Adding External filters for Kendo UI Grid

What i want to achieve is:
Have a Master Grid. Clicking on a row of this grid, i want to filter the rows of the ChildGrid.
What i have done so far:
function updateChildGridRows(field, operator, value) {
// get the kendoGrid element.
var gridData = $("#childGrid").data("kendoGrid");
var filterField = field;
var filterValue = value;
// get currently applied filters from the Grid.
var currFilterObj = gridData.dataSource.filter();
// if the oject we obtained above is null/undefined, set this to an empty array
var currentFilters = currFilterObj ? currFilterObj.filters : [];
// iterate over current filters array. if a filter for "filterField" is already
// defined, remove it from the array
// once an entry is removed, we stop looking at the rest of the array.
if (currentFilters && currentFilters.length > 0) {
for (var i = 0; i < currentFilters.length; i++) {
if (currentFilters[i].field == filterField) {
currentFilters.splice(i, 1);
break;
}
}
}
if (filterValue != "") {
currentFilters.push({
field: filterField,
operator: "eq",
value: filterValue
});
}
gridData.dataSource.filter({
filters: currentFilters
}); }
I got this code from the following jsfiddle:
http://jsfiddle.net/randombw/27hTK/
I have attached the MasterGrid's Change event to MasterGridSelectionChange() method. From there i am calling my filter method.
But when i click on the MasterGrid's row, all of the rows in my ChildGrid are getting removed.
One thing i can understand is, if i give wrong column name in the filter list, all the rows will be removed. But even though i have given correct ColumnName, my rows are getting deleted.
Sorry for the long post.
Please help me with this issue, as i am stuck with this for almost 4 days!
Thanks.
You can define ClientDetailTemplateId for the master grid and ToClientTemplate() for the Child grid
read Grid Hierarchy for a example

Update MultiselectList selected items

I'm using MultiselectList from Controls.Toolkit. I use it as a favourite selector. I have a list with items, I select the favourites and the next time I open the selection bar I would like to see my favourites already selected. When IsSelectionEnabledChanged event occurs, if IsSelectionEnabled is true (the selection bar is opened) I try to add my favourites to the list's SelectedItems. Here is a code snippet:
private void multiSelectList_IsSelectionEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (multiSelectList.IsSelectionEnabled)
{
foreach (var favourite in FavouritesList)
{
multiSelectList.SelectedItems.Add(multiSelectList.Items.Where(i => ((MyModel)i).id == favourite.id).FirstOrDefault());
}
}
}
I have tested this solution and I found out that the layout does not update the entire list that's why I'm not seeing the items as selected (but they are). Not even the actual visible items in the list. After scrolling for a bit and scrolling back, the selection appears! I've tried to use multiSelectList.UpdateLayout() method programatically but it did not solve it.
I wonder if it is a visualization problem or a CheckBox binding problem (the selection uses CheckBox on the side).
The SelectedItems is just a List<object>, it doesn’t raise any events when you update it.
To update your items manually, you could do something like following instead (untested code):
private void multiSelectList_IsSelectionEnabledChanged( object sender, DependencyPropertyChangedEventArgs e )
{
if( !multiSelectList.IsSelectionEnabled )
return;
var dictSelected = FavouritesList.ToDictionary( f => f.id, f => true );
for( int i = 0; i < multiSelectList.Items.Count; i++ )
{
MyModel m = (MyModel)multiSelectList.Items[ i ];
if( !dictSelected.ContainsKey( m.id ) )
continue; // Not selected
MultiselectItem item = (MultiselectItem)multiSelectList.ItemContainerGenerator.ContainerFromIndex( i );
if( null != item )
item.IsSelected = true; // This should add the item into the SelectedItems collection.
else
multiSelectList.SelectedItems.Add( m ); // The item is virtualized and has no visual representation yet.
}
}

Assigning functions to multiple slickgrids

Please help to solve this very annoying problem. I am using a for loop to iterate over an array of data and create multiple grids. It is working well but the filter function is not binding properly (it only binds to the LAST grid created) Here is the code:
// this function iterates over the data to build the grids
function buildTables() {
// "domain" contains the dataset array
for (i = 0; i < domains.length; i++) {
var dataView;
dataView = new Slick.Data.DataView();
var d = domains[i];
grid = new Slick.Grid('#' + d.name, dataView, d.columns, grids.options);
var data = d.data;
// create a column filter collection for each grid - this works fine
var columnFilters = columnFilters[d.name];
// this seems to be working just fine
// Chrome console confirms it is is processed when rendering the filters
grid.onHeaderRowCellRendered.subscribe(function (e, args) {
$(args.node).empty();
$("<input type='text'>")
.data("columnId", args.column.id)
.val(columnFilters[args.column.id])
.appendTo(args.node);
});
// respond to changes in filter inputs
$(grid.getHeaderRow()).delegate(":input", "change keyup", function (e) {
var columnID = $(this).data("columnId");
if (columnID != null) {
// this works fine - when the user enters text into the input - it
// adds the filter term to the filter obj appropriately
// I have tested this extensively and it works appropriately on
// all grids (ie each grid has a distinct columnFilters object
var gridID = $(this).parents('.grid').attr('id');
columnFilters[gridID][columnID] = $.trim($(this).val());
dataView.refresh();
}
});
//##### FAIL #####
// this is where things seem to go wrong
// The row item always provides data from the LAST grid populated!!
// For example, if I have three grids, and I enter a filter term for
// grids 1 or 2 or 3 the row item below always belongs to grid 3!!
function filter(row) {
var gridID = $(this).parents('.grid').attr('id');
for (var columnId in grids.columnFilters[gridID]) {
if (columnId !== undefined && columnFilters[columnId] !== "") {
var header = grid.getColumns()[grid.getColumnIndex(columnId)];
//console.log(header.name);
}
}
return true;
}
grid.init();
dataView.beginUpdate();
dataView.setItems(data);
dataView.setFilter(filter); // does it matter than I only have one dataView instance?
dataView.endUpdate();
grid.invalidate();
grid.render();
In summary, each function seems to be binding appropriately to each grid except for the filter function. When I enter a filter term into ANY grid, it returns the rows from the last grid only.
I have spent several hours trying to find the fault but have to admit defeat. Any help would be most appreciated.
yes, it matters that you have only one instance of dataView. and also sooner or later you will come up to the fact that one variable for all grids is also a bad idea
so add a var dataView to your loop, it should solve the problem

Resources