Angular-gridster2: Grid responsiveness for different device sizes (xl, lg, md, sm, xs)

Created on 24 Jan 2018  路  3Comments  路  Source: tiberiuzuld/angular-gridster2

Making this issue so people stop asking about it every month or so.

Yes you can with CSS Grid and @media-queries
You can increase the option mobileBreakpoint: 640 to what size you desire.
You can override the css gridster.mobile with your own and you can define your own columns.
You will not be able to drag and resize.

There is no way for the grid to work with drag and resize when you move around the widgets depending on the size of the grid.

If you don't need drag and resize then don't use this library use CSS Grid only.

enhancement wontfix

Most helpful comment

This is how I was able to achieve a dynamic columns solution in my dashboard component.
If anyone needs more info let me know. I have pretty good results with this. sometimes I do still see collisions happen but not often. Maybe I need to change the algorithm but haven't touch it since it was working rather well.

Overview Dynamic cols

  1. When the window is resized calculate the column width;
  2. Give the widgets that violate the column width a tmpPosition (the gridster auto calculated position)
  3. When the user resizes the screen larger and the widget no longer violates column width. Then delete tmpPosition and use its original position.
angular-gridster2": "3.15.0",

Widget Structure

{
  gridItem: {
    cols: 2,
    id: '-L6Nl9-e1CxJQzIXbl2O',
    position: {
      desktop: {
        x: 6,
        y: 0
      },
      phone: {
        x: -1,
        y: -1
      },
      tablet: {
        x: 0,
        y: 6
      },
      widescreen: {
        x: -1,
        y: -1
      }
    },
    rows: 2,
    x: 6,
    y: 0
  }
}
  constructor(private store: Store<AppStore>) {
    // the window Observable is throttled, maybe we should use debounce or delay
    // could play around with this some more.
    this.windowSub = Observable.fromEvent(window, 'resize')
      .throttleTime(200)
      // .delay(200)
      // .debounceTime(700)
      .subscribe(event => {
        this.computeColWidth();
      });
  }

  /** computeColWidth - compute gridsters columns based on minColWidth
   *  This function will utilized the fixed columnWidth and compare it against gridsters
   *  it will detect which screensize based on a mobile browser check
   *  then autoposition the values if need be without collision
   **/
  private computeColWidth() {
    // check the inner width compute the columns based on our minimum col width
    const width = window.innerWidth;
    const minColWidth = this.options.fixedColWidth;
    const gridColWidth = this.gridster.curColWidth;
    const cols = this.gridster.columns;
    const columns = Math.floor(width / minColWidth);

    // enter a mobile check. Tablet users may want a separate orientation
    // maybe set mobile breakpoint to number | function
    if (width >= 1024 && !this.mobileCheck()) {
      this.screenSize = 'desktop';
    }
    if (width < 1024 && this.mobileCheck()) {
      this.screenSize = 'tablet';
    }

    // now we override gridsters columns
    if (columns !== this.gridster.columns) {
      this.options.maxCols = columns;
      this.options.minCols = columns;
      if (this.options.api) this.options.api.optionsChanged();

      // Now go through our dashboard and delete any tmpPositions
      // original positions is first detected to preserve anti collision
      this.dashboard.map((item: DashterItem) => {
        if (
          item.gridItem.position['tmpPos'] && // delete tmp position if real pos fits
          !(
            item.gridItem.cols + item.gridItem.position[this.screenSize].x >=
            columns
          )
        ) {
          delete item.gridItem.position['tmpPos'];
        }
      });

      // when the user changes the column sizes we will manually add a tmpPosition
      // tmpPosition is based upon gridster's getNextPossiblePosition
      this.gridster.grid.forEach((item: GridsterItemComponent, i) => {
        const idx = findIndex(this.dashboard, ['id', item.item.id]);
        if (item.$item.cols + item.item.position[this.screenSize].x >=columns) {
          // this is meant to correctly auto position item. Not
          this.options.api.getNextPossiblePosition(item.$item);
          this.dashboard[idx].gridItem.position['tmpPos'] = item.$item;
        }
      });
    }
  }

Gridster config

  // This will be moved to ngrx store for initial state
  options: GridsterConfig = {
    gridType: 'verticalFixed',
    compactType: 'none',
    mobileBreakpoint: 750,
    margin: 8,
    outerMargin: true,
    scrollSpeed: 10,
    itemInitCallback: (i, c) => this.itemInitCallback(i, c),
    defaultItemCols: 2,
    defaultItemRows: 2,
    fixedColWidth: 180,
    fixedRowHeight: 180,
    displayGrid: 'onDrag&Resize',
    keepFixedWidthInMobile: false,
    keepFixedHeightInMobile: true,
    disablePushOnResize: false,
    draggable: {
      enabled: true,
      ignoreContent: true, // if true drag will start only from elements from `dragHandleClass`
      dragHandleClass: 'drag-handle', // drag event only from this class. If `ignoreContent` is true.
      stop: (p, q, r) => this.updateGridItems() // callback when dragging an item stops.  Accepts Promise return to cancel/approve drag.
    },
    resizable: {
      enabled: false
    },
    pushItems: true,
    swap: true, // allow items to switch position if drop on top of another
    disablePushOnDrag: false, // disable push on drag
    pushDirections: { north: true, east: false, south: false, west: false }, // control the directions items are pushed
    pushResizeItems: false, // on resize of item will shrink adjacent items
    disableWindowResize: true, // disable the window on resize listener. This will stop grid to recalculate on window resize.
    disableWarnings: true,
    scrollToNewItems: true // scroll to new items placed in a scrollable view
  };

All 3 comments

This is how I was able to achieve a dynamic columns solution in my dashboard component.
If anyone needs more info let me know. I have pretty good results with this. sometimes I do still see collisions happen but not often. Maybe I need to change the algorithm but haven't touch it since it was working rather well.

Overview Dynamic cols

  1. When the window is resized calculate the column width;
  2. Give the widgets that violate the column width a tmpPosition (the gridster auto calculated position)
  3. When the user resizes the screen larger and the widget no longer violates column width. Then delete tmpPosition and use its original position.
angular-gridster2": "3.15.0",

Widget Structure

{
  gridItem: {
    cols: 2,
    id: '-L6Nl9-e1CxJQzIXbl2O',
    position: {
      desktop: {
        x: 6,
        y: 0
      },
      phone: {
        x: -1,
        y: -1
      },
      tablet: {
        x: 0,
        y: 6
      },
      widescreen: {
        x: -1,
        y: -1
      }
    },
    rows: 2,
    x: 6,
    y: 0
  }
}
  constructor(private store: Store<AppStore>) {
    // the window Observable is throttled, maybe we should use debounce or delay
    // could play around with this some more.
    this.windowSub = Observable.fromEvent(window, 'resize')
      .throttleTime(200)
      // .delay(200)
      // .debounceTime(700)
      .subscribe(event => {
        this.computeColWidth();
      });
  }

  /** computeColWidth - compute gridsters columns based on minColWidth
   *  This function will utilized the fixed columnWidth and compare it against gridsters
   *  it will detect which screensize based on a mobile browser check
   *  then autoposition the values if need be without collision
   **/
  private computeColWidth() {
    // check the inner width compute the columns based on our minimum col width
    const width = window.innerWidth;
    const minColWidth = this.options.fixedColWidth;
    const gridColWidth = this.gridster.curColWidth;
    const cols = this.gridster.columns;
    const columns = Math.floor(width / minColWidth);

    // enter a mobile check. Tablet users may want a separate orientation
    // maybe set mobile breakpoint to number | function
    if (width >= 1024 && !this.mobileCheck()) {
      this.screenSize = 'desktop';
    }
    if (width < 1024 && this.mobileCheck()) {
      this.screenSize = 'tablet';
    }

    // now we override gridsters columns
    if (columns !== this.gridster.columns) {
      this.options.maxCols = columns;
      this.options.minCols = columns;
      if (this.options.api) this.options.api.optionsChanged();

      // Now go through our dashboard and delete any tmpPositions
      // original positions is first detected to preserve anti collision
      this.dashboard.map((item: DashterItem) => {
        if (
          item.gridItem.position['tmpPos'] && // delete tmp position if real pos fits
          !(
            item.gridItem.cols + item.gridItem.position[this.screenSize].x >=
            columns
          )
        ) {
          delete item.gridItem.position['tmpPos'];
        }
      });

      // when the user changes the column sizes we will manually add a tmpPosition
      // tmpPosition is based upon gridster's getNextPossiblePosition
      this.gridster.grid.forEach((item: GridsterItemComponent, i) => {
        const idx = findIndex(this.dashboard, ['id', item.item.id]);
        if (item.$item.cols + item.item.position[this.screenSize].x >=columns) {
          // this is meant to correctly auto position item. Not
          this.options.api.getNextPossiblePosition(item.$item);
          this.dashboard[idx].gridItem.position['tmpPos'] = item.$item;
        }
      });
    }
  }

Gridster config

  // This will be moved to ngrx store for initial state
  options: GridsterConfig = {
    gridType: 'verticalFixed',
    compactType: 'none',
    mobileBreakpoint: 750,
    margin: 8,
    outerMargin: true,
    scrollSpeed: 10,
    itemInitCallback: (i, c) => this.itemInitCallback(i, c),
    defaultItemCols: 2,
    defaultItemRows: 2,
    fixedColWidth: 180,
    fixedRowHeight: 180,
    displayGrid: 'onDrag&Resize',
    keepFixedWidthInMobile: false,
    keepFixedHeightInMobile: true,
    disablePushOnResize: false,
    draggable: {
      enabled: true,
      ignoreContent: true, // if true drag will start only from elements from `dragHandleClass`
      dragHandleClass: 'drag-handle', // drag event only from this class. If `ignoreContent` is true.
      stop: (p, q, r) => this.updateGridItems() // callback when dragging an item stops.  Accepts Promise return to cancel/approve drag.
    },
    resizable: {
      enabled: false
    },
    pushItems: true,
    swap: true, // allow items to switch position if drop on top of another
    disablePushOnDrag: false, // disable push on drag
    pushDirections: { north: true, east: false, south: false, west: false }, // control the directions items are pushed
    pushResizeItems: false, // on resize of item will shrink adjacent items
    disableWindowResize: true, // disable the window on resize listener. This will stop grid to recalculate on window resize.
    disableWarnings: true,
    scrollToNewItems: true // scroll to new items placed in a scrollable view
  };

The code above doesnt work for me because my widgets are added dynamically and I cannot hard-code the x/y coordinates like above.

A simpler way I've found for responsive tiles without hard-coded positions:

 private computeColWidth() {
         const width = this.gridster.el.clientWidth;
         const columns = Math.floor(width / this.options.fixedColWidth);

         if (columns !== this.options.maxCols) {
            this.options.maxCols = columns;
            this.options.minCols = columns;

            this.options.api.optionsChanged();

            this.gridster.grid.forEach((comp: GridsterItemComponent) => {
              this.gridster.autoPositionItem(comp);
          });
       }
}

@parliament718 @smoore2386 don't suppose either of you have a demo of what you managed to create?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

marco-martins picture marco-martins  路  4Comments

leandro-ali-elel picture leandro-ali-elel  路  4Comments

dhaniksahni picture dhaniksahni  路  4Comments

Vojislav-Vukovic picture Vojislav-Vukovic  路  4Comments

JohnxAss picture JohnxAss  路  5Comments