DEV Community

sandr0-p
sandr0-p

Posted on • Originally published at code-lens.hashnode.dev

Structuring Your Space: Designing a Visual Grid

In the first article of the “Drag. Drop. Engage.” series, we laid the groundwork by Creating a Basic Draggable Directive in Angular. This foundational piece enabled users to move elements around the page, setting the stage for a more dynamic and customisable user experience. However, for a truly interactive and organised dashboard, simply being able to drag elements isn’t enough.

We’ll take things further in this article by building a visual grid layout. This grid will act as the structured space where draggable elements can be positioned, providing visual guidance and functional structure. By the end of this article, you’ll clearly understand how to create an SVG grid, setting the stage for more complex interactions and precision placement in your Angular dashboard.


To begin, we create the grid directive, which we will use to render the grid.

ng g directive grid

Enter fullscreen mode Exit fullscreen mode

There are many different ways in which we can visualise a grid. In this example, we use SVG patterns. The SVG is constructed in four steps:

  1. We draw a path from the lower left corner to the upper left corner, continue to the upper right corner. This gives the grid line.
  2. We create a pattern element and add the path to it.

  3. We create a rect and set the pattern as it’s filling.

  4. Lastly, we create the SVG and add the rect and pattern to it

/**
 * Creates an SVG element with a grid pattern
 * @param cellSize Size of the grid cells in pixels
 * @param borderColor CSS color of the grid border
 * @param borderWidth Width of the grid border in pixels
 * @returns SVG element with a grid pattern
 */
private createGrid(cellSize: number, borderColor: string = '#c2c2c2', borderWidth: number = 1): SVGElement {

  let path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); // Create a path element
  path.setAttribute('d', `M 0 ${cellSize} L 0 0 ${cellSize} 0`); // Set the path data
  path.setAttribute('fill', 'none'); // Set the fill to none
  path.setAttribute('stroke', borderColor); // Set the stroke color
  path.setAttribute('stroke-width', borderWidth.toString()); // Set the stroke width

  let pattern = document.createElementNS('http://www.w3.org/2000/svg', 'pattern'); // Create a pattern element
  pattern.setAttribute('id', 'grid'); // Set the id to 'grid'
  pattern.setAttribute('width', cellSize.toString()); // Set the width to the size
  pattern.setAttribute('height', cellSize.toString()); // Set the height to the size
  pattern.setAttribute('patternUnits', 'userSpaceOnUse'); // Set the pattern units to 'userSpaceOnUse'
  pattern.appendChild(path); // Append the path to the pattern

  let grid = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); // Create a rect element
  grid.setAttribute('width', '100%'); // Set the width to 100%
  grid.setAttribute('height', '100%'); // Set the height to 100%
  grid.setAttribute('fill', 'url(#grid)'); // Set the fill to the grid pattern

  let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); // Create an SVG element
  svg.setAttribute('width', '100%'); // Set the width
  svg.setAttribute('height', '100%'); // Set the height
  svg.appendChild(pattern); // Append the pattern to the SVG
  svg.appendChild(grid); // Append the grid to the SVG

  return svg; // Return the SVG element
}

Enter fullscreen mode Exit fullscreen mode

With the functionality out of the way, we can initialise the directive. We are using the ngAfterViewInit lifecycle hook to ensure our HTML element is ready for use. We also introduce three @Input parameters with default values to make the functionality reusable.

@Input('cellSize') cellSize: number = 50; // Size of the grid cells in pixels
@Input('borderColor') borderColor: string = '#c2c2c2'; // CSS color of the grid border
@Input('borderWidth') borderWidth: number = 1; // Width of the grid border in

/**
 * AfterViewInit lifecycle hook
 * Creates a grid with the specified cell size and appends it to the container
 */
public ngAfterViewInit(): void {
  let grid = this.createGrid(this.cellSize, this.borderColor, this.borderWidth); // Create a grid with
  this._container.nativeElement.appendChild(grid); // Append the grid to the container
}

Enter fullscreen mode Exit fullscreen mode

To render the grid, we add the grid directive to our HTML. I have added a main element and attached ngGrid to it.

💡 I added a Bootstrap header to the page to make it more visually appealing. It also gives us an area where dropping isn’t allowed. The custom CSS can be found in below StackBlitz.

<div class="page-grid">
  <nav class="navbar bg-body-tertiary">
    <div class="container-fluid">
      <a class="navbar-brand" href="#">
        <img src="angular.svg" alt="Angular Drag and Drop" width="30" height="24" class="d-inline-block align-text-top">
        Angular Drag and Drop
      </a>
    </div>
  </nav>

  <main ngGrid>
    <div class="card col-2 position-absolute" ngDraggable>
      <div class="card-header">
        Drag and Drop
      </div>
      <div class="card-body">
      </div>
    </div>
  </main>
</div>

Enter fullscreen mode Exit fullscreen mode

And here is the result. The draggable card from part one and the grid we just created.


With your visual grid now in place, you've added a critical layer of structure to your drag-and-drop dashboard, making it easier for users to position elements accurately and maintain a well-organised layout.

We'll introduce grid-snapping and restrictions in the next article of the “Drag. Drop. Engage.” series. This enhancement will ensure that interactions remain intuitive and controlled, providing users with a seamless and polished experience. Stay tuned as we continue to refine and enhance your Angular drag-and-drop dashboard!

Top comments (1)

Collapse
 
wyattdave profile image
david wyatt

Very cool, thanks for sharing