I use FlowRow in the accompanist project to auto wrap my text items to next line. It works as intended. However, when I have a large dataset (which I already load with paging), I don't find an api like LazyColumn to load and build the items as needed, if I loop through the pager flow, it tries to load to build everything at once. Any adice please?
lazyPagingItems = pager.flow.collectAsLazyPagingItems()
FlowRow(
) {
val items = lazyPagingItems
for (index in 1..items.itemCount-1) {
Text(
text = word,
maxLines = 1
)
}
}
Little late to the party. But it seems you could use LazyVerticalGrid or LazyHorizontalGrid in adaptive mode like below.
LazyVerticalGrid(
columns = GridCells.Adaptive(/* item min size */)
) {
// Items
}
Related
I tried to develop a simple custom layout just like the documentation
#Composable
fun MyBasicColumn(
modifier: Modifier = Modifier,
content: #Composable () -> Unit
) {
Layout(
modifier = modifier,
content = content
) { measurables, constraints ->
// Don't constrain child views further, measure them with given constraints
// List of measured children
val placeables = measurables.map { measurable ->
// Measure each children
measurable.measure(constraints)
}
// Set the size of the layout as big as it can
layout(constraints.maxWidth, constraints.maxHeight) {
// Track the y co-ord we have placed children up to
var yPosition = 0
// Place children in the parent layout
placeables.forEach { placeable ->
// Position item on the screen
placeable.placeRelative(x = 0, y = yPosition)
// Record the y co-ord placed up to
yPosition += placeable.height
}
}
}
}
it works fine when I know exact number of items
but what about lazy items?
there is nothing in documentation about how can I develop a LazyCustomLayout
You don't exactly have to know how many items are in the Layout, since even for dynamic lists, there's always a 'current number of items' which can be computed. Let's say you download a list of texts from a server, and then intend to use this Layout to render those. Even in that case, while the server may vary the length of the list, i.e., the list is dynamic in size, you would presumably have a LiveData object keeping track of the list items. From there, you can easily use the collectAsState() method inside a Composable, or the observeAsState() method tied to a LifecycleOwner to convert it into the Compose-compatible MutableState<T> variable. Hence, whenever the LiveData notifies a new value (addition, or deletion), the MutableState<T> variable will also be updated to reflect those values. This, you can use inside the said Layout, which is also a Composable and hence, will update along-side the server-values, in real-time.
The thing is, no matter how you get your list, in order to show it on-screen, or use it anywhere in your app, you would always have a list object, which can be exploited using Compose's declarative and reactive nature.
I am trying to create a widget which displays the current BTC price, but the displayed widget is not updated.
First i create and register the widget with
mytextwidget = {
widget = wibox.widget.textbox,
}
btcbox = {
{
mytextwidget,
halign = "center",
layout = wibox.container.place,
},
forced_width = 100,
layout = wibox.layout.stack,
}
vicious.register(mytextwidget,vicious.contrib.btc,"$1",2,"eur")
I changed the btc widgets code, such that the indices of the returned table are numeric instead of the string "{price}", because i was not able to register it that way(maybe someone can tell me how to change the format string so that it works). I then add it to the default wibox with
s.mywibox:setup {
layout = wibox.layout.align.horizontal,
{ -- Left widgets
layout = wibox.layout.fixed.horizontal,
mylauncher,
s.mytaglist,
s.mypromptbox,
},
s.mytasklist,
-- Middle widget
{ -- Right widgets
layout = wibox.layout.fixed.horizontal,
mykeyboardlayout,
wibox.widget.systray(),
mytextclock,
batbox,
btcbox,
s.mylayoutbox,
}
}
As you can see there is also the batbox, which is setup the same way, except for registering to the vicious.widget.bat widget and this one works fine. I am new to awesomewm and especially Lua but i tried to reverse engineer through the vicious library and the text of the widget actually gets updated. I created a naughty notification which is called right after
widget.text = fmtd_data
in the init.lua of the vicious library, to display the widgets text and it is actually showing the correct result. Hopefully someone can explain it to me why this change is not displayed, thanks in advance!
Random guess without much time to try things out:
mytextwidget = {
widget = wibox.widget.textbox,
}
replace the above with
mytextwidget = wibox.widget.textbox()
Why am I suggesting this? Well, it's what is done on https://vicious.readthedocs.io/en/latest/examples.html#date-widget.
I'm using pdfmake to generate a document that contains a number of sub-documents, e.g. a PDF containing invoices; each invoice is a logical document inside the PDF document.
I have a requirement that in the footer or header of each page I show "Page of " where both those numbers are relative to a single invoice, and not the overall document.
The header and footer callbacks look good, but only specify the page number and count relative to the entire document, and the pageBreakBefore callback doesn't generate anything like the documented information, and in any case I can't figure out how I could use it here.
This doesn't seem like a unique requirement, so hopefully I'm just missing something obvious.
Thanks!
I believe that pdkmake's header and footer function's arguments can only contain the global pageCount.
There is however a way to reproduce the behavior that you want, if you handle manually the pageBreaks :
const realPageIndexes = [];
let currSubPdfIdx = 0;
let currPageCountForSubPdf = 0;
const getPageBreak = (newSubPdfIdx: number) => {
if (currSubPdfIdx !== newSubPdfIdx) {
currSubPdfIdx = newSubPdfIdx;
currPageCountForSubPdf = 0;
} else {
currPageCountForSubPdf += 1;
}
realPageIndexes.push(currPageCountForSubPdf);
return {text: '', pageBreak: 'after'}; // return elem causing the pageBreak
}
The footer function, filling the page numbers, is called once the document definition generation is done.
If you handle every overflow by yourself, by calling getPageBreak(currentSubPdfIndex) at the end of each page, you will end up knowing which sub-Pdf is displayed in each page :
I display part of the 1rst subPdf in page 1:
I display the end of 1rst subPdf in page 2
I display 2nd subPdf in page3
I display 3rd subPdf in page4 ....
.
let subPdfIdx = 0;
pdfContent.push(subPdf1FirstPart)
pdfContent.push(getPageBreak(subPdfIdx))
pdfContent.push(subPdf1SecondPart)
pdfContent.push(getPageBreak(subPdfIdx))
subPdfIdx++;
pdfContent.push(subPdf2)
pdfContent.push(getPageBreak(subPdfIdx))
subPdfIdx++;
pdfContent.push(subPdf3)
pdfContent.push(getPageBreak(subPdfIdx))
realPageIndexes[] then looks like :
[ 1, 1, 2, 3 ];
The only step left is to tell the footer function to use the page counts we just created instead of the global page count :
const documentDefinition = {
content: [YOUR CONTENT],
footer: (currPage, allPages) => 'subPdf index is ' + realPageIndexes[currPage];
}
Please note that this method will fail if you don't handle overflows correctly:
if for example a paragraph is too big to fit a page, and you call getPageBreak() after that, pdfmake will automatically create a new page for the end of the paragraph (page which is untracked in our realPageIndexes) and then add the page caused by your getPageBreak() right after the end of the text. So just make sure not to overflow the page :)
I have an unordered 'source' list that can contain up to around 1,000 list items. I want to be able to drag the items from the source list into a connected 'destination' list. I have everything working great until my source list gets filtered. I'm using the jquery quicksearch plugin to filter (search) my source list. The filter is accomplished by setting 'display:none;' on items that don't match the search.
When 1..n items in my source list are hidden, the drag operation is not fluid when initiated. Meaning, I click on the item I want to drag, move my mouse around the screen, but the item I'm dragging does not appear under my cursor until about a full second after I've initiated the drag.
For diagnosis, I've slimmed down my use case to just one list that I want to sort. I've completely eliminated the use of quicksearch by just hard coding half of my list items as hidden. I'm still able to reproduce the 'non-fluid' behavior. My example is here:
http://pastebin.com/g0mVE6sc
If I remove the overflow style from the list in my example, the performance is a little better, but still slower than I'd hope to see.
Does anyone have any suggestions for me before I start considering other options?
Thanks in advance.
As you can see on this jsferf example, calculating outerWidth()/outerHeight() (this is what the plugin does - see below) for hidden elements (with display none) is terribly slower than for visible elements, wether it is achieved by a style attribute or a class.
The only way I have found to bypass this and still achieve the same result is to set the height for the elements to hide to zero, instead of working with the display property, whether using the style atttibute or a class:
<li style="height: 0;">b</li>
<li class="hidden">b</li>
.hidden { height: 0 }
DEMO (with class) - DEMO (with style attr)
What's happenning with sortable when dragging an element ?
When starting dragging, the plugin refreshes the list of all items and recalculates positions of all elements. The plugin actually gets outerWidth and outerHeight:
_mouseStart: function(event, overrideHandle, noActivation) {
...
//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
this.refreshPositions();
...
}
refreshPositions: function(fast) {
...
for (var i = this.items.length - 1; i >= 0; i--) {
var item = this.items[i];
...
if (!fast) {
item.width = t.outerWidth();
item.height = t.outerHeight();
}
var p = t.offset();
item.left = p.left;
item.top = p.top;
};
...
return this;
},
If you still want to use display:none, this is a simple fix to the jQuery UI source specified in Didier's answer:
if (!fast) {
if(item.item.css('display') === 'none') {
item.width = 0;
item.height = 0;
}
else {
item.width = t.outerWidth();
item.height = t.outerHeight();
}
}
This is my very first post on stackoverflow, so do let me know if I messed something up.
I was also having a similar problem, but with hidden drop containers instead of sortable items. Here is my solution applying Jordan's answer to both sortable items and their containers and simply replacing the relvent method.
$.ui.sortable.prototype.refreshPositions = function(fast) {
//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
if(this.offsetParent && this.helper) {
this.offset.parent = this._getParentOffset();
}
for (var i = this.items.length - 1; i >= 0; i--){
var item = this.items[i];
//We ignore calculating positions of all connected containers when we're not over them
if(item.instance != this.currentContainer && this.currentContainer && item.item[0] != this.currentItem[0])
continue;
var t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
if (!fast) {
/********** MODIFICATION ***********/
if(item.item.css('display') === 'none') {
item.width = 0;
item.height = 0;
} else {
item.width = t.outerWidth();
item.height = t.outerHeight();
}
/********** END MODIFICATION ***********/
}
var p = t.offset();
item.left = p.left;
item.top = p.top;
};
if(this.options.custom && this.options.custom.refreshContainers) {
this.options.custom.refreshContainers.call(this);
} else {
for (var i = this.containers.length - 1; i >= 0; i--){
/********** MODIFICATION ***********/
if (this.containers[i].element.css('display') == 'none') {
this.containers[i].containerCache.left = 0;
this.containers[i].containerCache.top = 0;
this.containers[i].containerCache.width = 0;
this.containers[i].containerCache.height = 0;
} else {
var p = this.containers[i].element.offset();
this.containers[i].containerCache.left = p.left;
this.containers[i].containerCache.top = p.top;
this.containers[i].containerCache.width = this.containers[i].element.outerWidth();
this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
}
/********** END MODIFICATION ***********/
};
}
return this;
};
I came across with the same problem...
I've searched for a solution, but it seems there is no solution to the jquery problem, only some workaround...
I didn't found either a solution, just another workaround.
In my case I just created a general method to to a search in a sortable list, where on keyup, the code goes and do a find on every element in the list and was hiding it by fadeout if didn't match the value.
This was working very well, but when you have hundreds of items in a list, the of hidden gets big enough to trigger the slow effect on the drag&drop.
My solution was to reorder the list, bringing to the top the matched items..
Just remove and appendTo again...
This way I don't have problems with the hidden elements :)
Sorry this was no solution, but just another workaround..
Regards
More recently I came accross with this issue again... and found that my workaround was not the best solution anymore. Since the issue is the height... I've just create a CSS class with
.hidden {display: block; line-height:0; height: 0; overflow: hidden; padding: 0; margin: 0; }
and instead of setting the element hidden with just add this class and remove it to show/hide the element..
Regards,
AP
I got the same issue today with sortable + draggable table rows.
The solution was simple : use visibility:collapse instead of display:none and removing the jQuery hide/show function for the hidden lines did the trick. It's a lot faster now.
Hope I will help.
I have a table of many rows in a JQuery UI accordion.
I dynamically append the table this way:
var resJson = JSON.parse(connector.process(JSON.stringify(reqJson)));
for ( var i in resJson.entryArrayM) {
// test if entry has already been displayed
if ($("#resultTr_" + resJson.entryArrayM[i].id) == null)
continue;
$("#resultTable > tbody:last").append(listEntry.buildEntryRow(resJson.entryArrayM[i]));
}
Firstly I check if a row of the same tr id already exists. If not, I would append to the last row of the table.
It works. But the problem is: every time a row is appended, the accordion would scroll to the first row of the table. Since the table is remarkably long, it makes users inconvenient to scroll down again and again to watch newly-added rows. So how to avoid this?
First of all, just do one append rather than appending every time through the loop:
var resJson = JSON.parse(connector.process(JSON.stringify(reqJson)));
var seen = { };
var rows = [ ];
var trId = null;
for(var i in resJson.entryArrayM) {
// test if entry has already been displayed
var trId = 'resultTr_' + resJson.entryArrayM[i].id;
if($('#' + trId).length != 0
|| seen[trId])
continue;
rows.push(listEntry.buildEntryRow(resJson.entryArrayM[i]));
seen[trId] = true;
}
$("#resultTable > tbody:last").append(rows.join(''));
Also note that I corrected your existence test, $(x) returns an empty object when x doesn't match anything, not null. Not only is this a lot more efficient but you'll only have one scroll position change to deal with.
Solving your scrolling issue is fairly simple: find out what element is scrolling, store its scrollTop before your append, and reset its scrollTop after the append:
var $el = $('#whatever-is-scrolling');
var scrollTop = $el[0].scrollTop;
$("#resultTable > tbody:last").append(rows.join('')); // As above.
$el[0].scrollTop = scrollTop;
There might be a slight visible flicker but hopefully that will be lost in the noise of altering the table.
You could also try setting the table-layout CSS property of the <table> to fixed. That will keep the table from trying to resize its width or the width of its columns and that might stop the scrolling behavior that you're seeing. The downside is that you'll have to handle the column sizing yourself. But, you could try setting table-layout:fixed immediately before your append operation to minimize the hassle.