Skip to content
This repository has been archived by the owner on Nov 4, 2023. It is now read-only.

feat: onLayout hook triggered on items being added/remove #703

Merged
merged 1 commit into from
Apr 27, 2021

Conversation

rchl
Copy link
Collaborator

@rchl rchl commented Apr 25, 2021

When implementing some custom, automatic layout that needs to
recalculate positions and/or sizes on items being added or removed
dynamically a hook like this one might be useful.

This is actually for my own custom layout that I'm using to position
and size items automatically but it could be useful for others too.

When implementing some custom, automatic layout that needs to
recalculate positions and/or sizes on items being added or removed
dynamically a hook like this one might be useful.
@rchl rchl force-pushed the feat/layout-hook branch from 8b65182 to 492a233 Compare April 25, 2021 18:59
@timota
Copy link
Contributor

timota commented Apr 25, 2021

Looks interesting. Can you provide examples how it can be used, lets say for calculation layout and sizes? Mostly looking what do you meant when item added/removed.

Thanks

@rchl
Copy link
Collaborator Author

rchl commented Apr 26, 2021

Here is something I'm using but I'm not fully satisfied with it.

I would define this layout in the config file:

const AVAILABLE_WIDTH = window.innerWidth;

const AUTO_LAYOUT = function (page, group) {
   const items = group.items;
   const options = group.itemsLayoutOptions || {};

   options.isHorizontal = typeof(options.isHorizontal) === 'boolean' ?
      options.isHorizontal : true;
   options.mainAxisLimit = typeof(options.mainAxisLimit) === 'number' ?
      options.mainAxisLimit : 0;
   options.scaleToFit = typeof(options.scaleToFit) === 'boolean' ?
      options.scaleToFit : false;

   const TILE_SIZE = page.tileSize || CONFIG.tileSize;
   const TILE_MARGIN = page.tileMargin || CONFIG.tileMargin;

   const SIZE_MAIN_AXIS = options.isHorizontal ? 'width' : 'height';
   const SIZE_CROSS_AXIS = options.isHorizontal ? 'height' : 'width';

   let mainAxisIndex = 0;
   let crossAxisIndex = 0;

   // Items grouped by row/column.
   const groupedItems = [];

   for (const item of items) {
      if (!item[SIZE_MAIN_AXIS]) {
         item[SIZE_MAIN_AXIS] = 1;
      }

      if (options.mainAxisLimit
          && (mainAxisIndex + item[SIZE_MAIN_AXIS]) > options.mainAxisLimit) {
         mainAxisIndex = 0;
         crossAxisIndex += 1;
      }

      let crossAxisContainer = groupedItems[crossAxisIndex];

      if (!crossAxisContainer) {
         crossAxisContainer = [];
         groupedItems.push(crossAxisContainer);
      }

      crossAxisContainer.push(item);

      if (!item[SIZE_CROSS_AXIS]) {
         item[SIZE_CROSS_AXIS] = 1;
      }

      mainAxisIndex += item[SIZE_MAIN_AXIS];
   }

   const calculateScaleFactor = items => {
      if (!options.scaleToFit) {
         return 1;
      }

      const itemsWidth = items.reduce((accum, item) => {
         return accum + item[SIZE_MAIN_AXIS];
      }, 0);

      // How many full tiles fit within available space?
      const fitCount = Math.floor(AVAILABLE_WIDTH / TILE_SIZE);
      // How much space would margins take for visible amount of tiles.
      const tileMargins = (fitCount - 1) * TILE_MARGIN;
      // How much to scale tiles to make them fill the screen.
      return (AVAILABLE_WIDTH - tileMargins) / (itemsWidth * TILE_SIZE);
   };

   let mainAxisPos = 0;
   let crossAxisPos = 0;
   let biggestCrossAxisSize = 0;

   for (const mainAxisContainer of groupedItems) {
      const scaleFactor = calculateScaleFactor(mainAxisContainer);

      for (const [mainAxisIndex, item] of mainAxisContainer.entries()) {
         if (mainAxisIndex === 0) {
            mainAxisPos = 0;
            crossAxisPos += biggestCrossAxisSize;
            biggestCrossAxisSize = 0;
         }

         item.position = options.isHorizontal ? [mainAxisPos, crossAxisPos] : [
            crossAxisPos, mainAxisPos,
         ];

         item.width = item.width * scaleFactor;
         item.height = item.height * scaleFactor;

         mainAxisPos += item[SIZE_MAIN_AXIS];

         biggestCrossAxisSize = Math.max(biggestCrossAxisSize, item[
            SIZE_CROSS_AXIS]);
      }
   }

   return items;
}

And then in chosen groups I'd add options like:

               onLayout: AUTO_LAYOUT,
               itemsLayoutOptions: {
                  isHorizontal: true,
                  mainAxisLimit: 4.5,
                  scaleToFit: true,
               },

This allows me to not have to set width/height/position on tiles and let them be automatically layed-out.
I only use it on mobile.

@rchl
Copy link
Collaborator Author

rchl commented Apr 26, 2021

Mostly looking what do you meant when item added/removed.

As for that, I run a code from onReady hook to populate groups with items. For example to automatically gather all sensors on the same page.

@alphasixtyfive alphasixtyfive merged commit 94a1846 into master Apr 27, 2021
@rchl rchl deleted the feat/layout-hook branch April 27, 2021 12:27
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants