OiO.lk Community platform!

Oio.lk is an excellent forum for developers, providing a wide range of resources, discussions, and support for those in the developer community. Join oio.lk today to connect with like-minded professionals, share insights, and stay updated on the latest trends and technologies in the development field.
  You need to log in or register to access the solved answers to this problem.
  • You have reached the maximum number of guest views allowed
  • Please register below to remove this limitation

Inserting components in Angular in a table-like format (replicating Jira)

  • Thread starter Thread starter Chelcea Claudiu
  • Start date Start date
C

Chelcea Claudiu

Guest
I'm trying to replicate the Jira functionality where I create a "gadget" and that gadget can be inserted in a board, resized, moved, deleted.

(For non-knowers of why this would be useful, think of the "Gantt" gadget. There is literally a gadget you can add to your personal dashboard to take all your tasks from the database and put them in a Gantt format; Or deadline tracker; Tasks counter; Sprint burn chart; etc...)

I've been struggling for days and I almost gave up, so I'll want to ask for your help.

HTML:

  • very simple, it's just a list and a slider; basically people come to the page, press the plus button, a slider pops-up with a list of items (gadgets; people should be able to click on them to insert the gadget)

CSS:

  • very simple, some slider formatting

TS:

  • also simple, just playing with the slider


Code:
import {
  Component,
  OnDestroy
} from '@angular/core';
import {
  Subscription
} from 'rxjs';
import {
  SliderStateService
} from './slider-state.service'; // Update path accordingly
import {
  CommonModule
} from '@angular/common';
import {
  ComponentFactoryResolver,
  ViewContainerRef,
  ViewChild
} from '@angular/core';

/* Gadgets */
import {
  ReadingNowTrackerComponent
} from './gadgets/ReadingNow Tracker/ReadingNowTracker.component'; // Make sure the path is correct
/* Gadgets */

@Component({
  selector: 'app-dashboard-1',
  templateUrl: './dashboard-1.component.html',
  styleUrls: ['./dashboard-1.component.scss'],
  standalone: true,
  imports: [CommonModule]
})
export class Dashboard1Component implements OnDestroy {

  sliderOpen = false;
  enlargedSlider = false;
  private subscription: Subscription;
  @ViewChild('dynamicInsert', {
    read: ViewContainerRef
  }) dynamicInsert: ViewContainerRef;

  constructor(
    private sliderStateService: SliderStateService,
    private componentFactoryResolver: ComponentFactoryResolver) {
    // Subscribe to the enlargedSlider$ observable
    this.subscription = this.sliderStateService.enlargedSlider$.subscribe(enlarged => {
      this.enlargedSlider = enlarged;
      // Adjust slider position when enlarging
      if (enlarged) {
        this.adjustSliderPosition();
      } else {
        this.resetSliderPosition();
      }
    });
  }

  ngOnInit(): void {}

  ngOnDestroy(): void {
    // Unsubscribe from the subscription to prevent memory leaks
    this.subscription.unsubscribe();
  }

  toggleSlider() {
    this.sliderOpen = !this.sliderOpen;
  }

  toggleEnlargedSlider() {
    // Toggle the enlarged state through the service
    this.sliderStateService.toggleEnlargedSlider();
  }

  private adjustSliderPosition() {
    document.querySelector('.slider').setAttribute('style', 'left: 0; right: 0;');
  }

  private resetSliderPosition() {
    document.querySelector('.slider').removeAttribute('style');
  }
}

Code:
.container {
  position: fixed;
  min-height: 100vh;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 5;
  /* Below the slider to allow the slider to overlay */
  display: flex;
  flex-direction: column;
  /* Stack children vertically */
  justify-content: space-between;
  /* Adjust based on your layout needs */
}

.slider {
  position: fixed;
  bottom: 0;
  left: 6.5%;
  /* Offset from the left */
  right: 0%;
  /* Offset from the right */
  height: 20vh;
  /* 20% of the viewport height */
  background-color: #e1b035e7;
  /* A professional bluish color */
  transition: transform 0.3s ease-in-out, left 0.5s ease-in-out, right 0.5s ease-in-out;
  /* Combined transitions */
  transform: translateY(100%);
  /* Initially hidden */
  z-index: 6;
  /* Above the container to overlay the button when open */
  overflow-x: auto;
  /* Enable horizontal scrolling */
  overflow-y: hidden;
  /* Prevent vertical scrolling */
  white-space: nowrap;
  /* Keep the slider items in one line */
  padding: 10px 0;
  /* Padding at the top and bottom for aesthetics */
  box-sizing: border-box;
  /* Include padding in the height calculation */
}

.slider-open {
  transform: translateY(0);
  /* Move the slider up to show it */
}

.toggle-btn {
  position: fixed;
  /* Adjusted from 'relative' to 'fixed' to maintain your original design */
  bottom: 4px;
  /* Adjust the bottom position for initial state */
  left: 50%;
  transform: translateX(-50%);
  width: 50px;
  height: 50px;
  border-radius: 50%;
  background-color: #d16b0bc4;
  /* Matching blue color for consistency */
  color: white;
  font-size: 24px;
  line-height: 50px;
  /* Center the plus sign */
  text-align: center;
  border: none;
  cursor: pointer;
  z-index: 7;
  /* Above the slider to not be overlapped */
  transition: bottom 0.3s ease-in-out, left 0.3s ease;
  /* Added left transition here */
  overflow: hidden;
  /* Hide the parts of the pseudo-element that overflow the button */
  /* Pseudo-element for hover effect */
  &::before {
    content: '';
    position: absolute;
    top: 0;
    left: -100%;
    /* Start from the left, outside the button */
    width: 100%;
    height: 100%;
    background-color: #4CAF50;
    /* The color of the slide-in effect */
    transition: left 0.3s ease;
    /* Smooth transition for the slide-in effect */
    z-index: -1;
    /* Place the pseudo-element below the button text */
  }
  &:hover::before {
    left: 0;
    /* On hover, slide in the background from left to right */
  }
}

.slider-open~.toggle-btn {
  bottom: calc(20vh + 4px);
  /* Move button above the slider */
}

.slider-list {
  display: flex;
  /* Align items horizontally */
  padding-left: 10px;
  /* Adjusts padding for the inner content */
  padding-right: 0px;
  /* Adjusts padding for the inner content */
  margin: 0;
  /* Removes default margin */
  list-style-type: none;
  /* Removes default list styling */
  flex-wrap: nowrap;
  /* Prevents items from wrapping to a new line */
  overflow-x: auto;
  /* Enable horizontal scrolling */
}

.slider-item {
  display: flex;
  /* Keep items in a horizontal line */
  align-items: center;
  /* Align items vertically in the center */
  margin-right: 10px;
  /* Space between each item */
  border: 4px solid black;
  /* Border styling */
  padding: 5px;
  /* Padding inside each item */
  border-radius: 10px;
  /* Rounded corners */
  background: rgba(253, 196, 111, 0.982);
  /* Background color */
  box-sizing: border-box;
  /* Include padding and border in the element's size */
  flex-shrink: 0;
  /* Prevent shrinking */
  width: 350px;
  /* Fixed width for each slider item */
  height: calc(20vh - 20px);
  /* Fixed height based on the slider height, adjusting for padding */
  position: relative;
  overflow: hidden;
  cursor: pointer;
}

.slider-item::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background-color: #4CAF50;
  /* Adjust the color as needed */
  transition: left 0.3s ease;
  z-index: 0;
}

.slider-item:hover::before {
  left: 0;
}

.slider-item * {
  position: relative;
  z-index: 1;
}

.slider-item:hover {
  transition: transform 0.2s ease-in-out;
}

.slider-image {
  width: 100px;
  /* Fixed width for all images */
  height: 100px;
  /* Fixed height for all images */
  object-fit: cover;
  /* Cover the area, image may be cropped */
}

.slider-content {
  display: flex;
  flex-direction: column;
  /* Stack title and description vertically */
  justify-content: center;
  /* Centers the text content vertically */
  overflow: hidden;
  /* Hide overflowed text */
  margin-left: 10px;
  /* Space between image and text content */
  width: calc(250px - 10px);
  /* Adjusted width for text content, considering padding */
}

.slider-title {
  font-weight: bold;
  /* Make the title text bold */
  font-size: 1em;
  /* Slightly larger font size for the title */
  overflow: hidden;
  /* Hide overflowed title text */
  text-overflow: ellipsis;
  /* Add an ellipsis for overflowed text */
  white-space: wrap;
  /* Prevent text wrapping to keep title in a single line */
}

.slider-description {
  font-size: 0.8em;
  /* Smaller font size for the description */
  overflow: hidden;
  /* Hide overflowed description text */
  text-overflow: ellipsis;
  /* Add an ellipsis for overflowed text */
  white-space: wrap;
  /* Prevent text wrapping to keep description in a single line */
}

Code:
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.0/angular.min.js"></script>
<div class="container">
  <div class="slider" [ngClass]="{'slider-open': sliderOpen}">
    <ul class="slider-list">
      <!-- Repeat for more image-text pairs -->
      <li class="slider-item">
        <img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
        <div class="slider-content">
          <div class="slider-title">NowReading Tracker</div>
          <div class="slider-description">Shows your current book and progress, keeping your reading journey clearly in view.</div>
        </div>
      </li>
      <li class="slider-item">
        <img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
        <div class="slider-content">
          <div class="slider-title">Deadline Dash</div>
          <div class="slider-description">Highlights your most urgent task, ensuring top priorities are never overlooked.</div>
        </div>
      </li>
      <li class="slider-item">
        <img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
        <div class="slider-content">
          <div class="slider-title">Note Nudge</div>
          <div class="slider-description">Brings your key notes to the forefront, making important information readily accessible.</div>
        </div>
      </li>
      <li class="slider-item">
        <img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
        <div class="slider-content">
          <div class="slider-title">Budget Beacon</div>
          <div class="slider-description">Displays current budget status and upcoming expenses, guiding your financial decisions.</div>
        </div>
      </li>
      <li class="slider-item">
        <img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
        <div class="slider-content">
          <div class="slider-title">Invest Insight</div>
          <div class="slider-description">Provides a snapshot of investment trends and metrics, simplifying financial oversight.</div>
        </div>
      </li>
      <li class="slider-item">
        <img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
        <div class="slider-content">
          <div class="slider-title">QuickPath Picker</div>
          <div class="slider-description">Instantly select the best route, optimizing your journey with a tap.</div>
        </div>
      </li>
    </ul>
  </div>
  <!-- Plus (ADD) symbol -->
  <button (click)="toggleSlider()" class="toggle-btn">&#43;</button>
</div>

What I'm trying to achieve is the following:

Similar to Jira gadgets, I want to insert gadgets in the screen.

Say the screen is 10x10 matrix, so I want when I press one item, e.g. the book item from the slider, to insert the component in the first empty spot in the matrix. Also from there I should be able to resize it (from 1x1 to maybe 2x1 for example), move, and delete.

This is why I have this here: "import { ReadingNowTrackerComponent } from './gadgets/ReadingNow Tracker/ReadingNowTracker.component';"; I was trying to put this component in the "matrix" and sometimes it overflowed, sometimes it wouldn't place at all, many many issues I've encountered.

Please help me by taking my code and helping me get the result I intended.

Thank you!
<p>I'm trying to replicate the Jira functionality where I create a "gadget" and that gadget can be inserted in a board, resized, moved, deleted.</p>
<p>(For non-knowers of why this would be useful, think of the "Gantt" gadget. There is literally a gadget you can add to your personal dashboard to take all your tasks from the database and put them in a Gantt format; Or deadline tracker; Tasks counter; Sprint burn chart; etc...)</p>
<p>I've been struggling for days and I almost gave up, so I'll want to ask for your help.</p>
<p>HTML:</p>
<ul>
<li>very simple, it's just a list and a slider; basically people come to the page, press the plus button, a slider pops-up with a list of items (gadgets; people should be able to click on them to insert the gadget)</li>
</ul>
<p>CSS:</p>
<ul>
<li>very simple, some slider formatting</li>
</ul>
<p>TS:</p>
<ul>
<li>also simple, just playing with the slider</li>
</ul>
<p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
<div class="snippet-code">
<pre class="snippet-code-js lang-js prettyprint-override"><code>import {
Component,
OnDestroy
} from '@angular/core';
import {
Subscription
} from 'rxjs';
import {
SliderStateService
} from './slider-state.service'; // Update path accordingly
import {
CommonModule
} from '@angular/common';
import {
ComponentFactoryResolver,
ViewContainerRef,
ViewChild
} from '@angular/core';

/* Gadgets */
import {
ReadingNowTrackerComponent
} from './gadgets/ReadingNow Tracker/ReadingNowTracker.component'; // Make sure the path is correct
/* Gadgets */

@Component({
selector: 'app-dashboard-1',
templateUrl: './dashboard-1.component.html',
styleUrls: ['./dashboard-1.component.scss'],
standalone: true,
imports: [CommonModule]
})
export class Dashboard1Component implements OnDestroy {

sliderOpen = false;
enlargedSlider = false;
private subscription: Subscription;
@ViewChild('dynamicInsert', {
read: ViewContainerRef
}) dynamicInsert: ViewContainerRef;

constructor(
private sliderStateService: SliderStateService,
private componentFactoryResolver: ComponentFactoryResolver) {
// Subscribe to the enlargedSlider$ observable
this.subscription = this.sliderStateService.enlargedSlider$.subscribe(enlarged => {
this.enlargedSlider = enlarged;
// Adjust slider position when enlarging
if (enlarged) {
this.adjustSliderPosition();
} else {
this.resetSliderPosition();
}
});
}

ngOnInit(): void {}

ngOnDestroy(): void {
// Unsubscribe from the subscription to prevent memory leaks
this.subscription.unsubscribe();
}

toggleSlider() {
this.sliderOpen = !this.sliderOpen;
}

toggleEnlargedSlider() {
// Toggle the enlarged state through the service
this.sliderStateService.toggleEnlargedSlider();
}

private adjustSliderPosition() {
document.querySelector('.slider').setAttribute('style', 'left: 0; right: 0;');
}

private resetSliderPosition() {
document.querySelector('.slider').removeAttribute('style');
}
}</code></pre>
<pre class="snippet-code-css lang-css prettyprint-override"><code>.container {
position: fixed;
min-height: 100vh;
bottom: 0;
left: 0;
width: 100%;
z-index: 5;
/* Below the slider to allow the slider to overlay */
display: flex;
flex-direction: column;
/* Stack children vertically */
justify-content: space-between;
/* Adjust based on your layout needs */
}

.slider {
position: fixed;
bottom: 0;
left: 6.5%;
/* Offset from the left */
right: 0%;
/* Offset from the right */
height: 20vh;
/* 20% of the viewport height */
background-color: #e1b035e7;
/* A professional bluish color */
transition: transform 0.3s ease-in-out, left 0.5s ease-in-out, right 0.5s ease-in-out;
/* Combined transitions */
transform: translateY(100%);
/* Initially hidden */
z-index: 6;
/* Above the container to overlay the button when open */
overflow-x: auto;
/* Enable horizontal scrolling */
overflow-y: hidden;
/* Prevent vertical scrolling */
white-space: nowrap;
/* Keep the slider items in one line */
padding: 10px 0;
/* Padding at the top and bottom for aesthetics */
box-sizing: border-box;
/* Include padding in the height calculation */
}

.slider-open {
transform: translateY(0);
/* Move the slider up to show it */
}

.toggle-btn {
position: fixed;
/* Adjusted from 'relative' to 'fixed' to maintain your original design */
bottom: 4px;
/* Adjust the bottom position for initial state */
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 50px;
border-radius: 50%;
background-color: #d16b0bc4;
/* Matching blue color for consistency */
color: white;
font-size: 24px;
line-height: 50px;
/* Center the plus sign */
text-align: center;
border: none;
cursor: pointer;
z-index: 7;
/* Above the slider to not be overlapped */
transition: bottom 0.3s ease-in-out, left 0.3s ease;
/* Added left transition here */
overflow: hidden;
/* Hide the parts of the pseudo-element that overflow the button */
/* Pseudo-element for hover effect */
&::before {
content: '';
position: absolute;
top: 0;
left: -100%;
/* Start from the left, outside the button */
width: 100%;
height: 100%;
background-color: #4CAF50;
/* The color of the slide-in effect */
transition: left 0.3s ease;
/* Smooth transition for the slide-in effect */
z-index: -1;
/* Place the pseudo-element below the button text */
}
&:hover::before {
left: 0;
/* On hover, slide in the background from left to right */
}
}

.slider-open~.toggle-btn {
bottom: calc(20vh + 4px);
/* Move button above the slider */
}

.slider-list {
display: flex;
/* Align items horizontally */
padding-left: 10px;
/* Adjusts padding for the inner content */
padding-right: 0px;
/* Adjusts padding for the inner content */
margin: 0;
/* Removes default margin */
list-style-type: none;
/* Removes default list styling */
flex-wrap: nowrap;
/* Prevents items from wrapping to a new line */
overflow-x: auto;
/* Enable horizontal scrolling */
}

.slider-item {
display: flex;
/* Keep items in a horizontal line */
align-items: center;
/* Align items vertically in the center */
margin-right: 10px;
/* Space between each item */
border: 4px solid black;
/* Border styling */
padding: 5px;
/* Padding inside each item */
border-radius: 10px;
/* Rounded corners */
background: rgba(253, 196, 111, 0.982);
/* Background color */
box-sizing: border-box;
/* Include padding and border in the element's size */
flex-shrink: 0;
/* Prevent shrinking */
width: 350px;
/* Fixed width for each slider item */
height: calc(20vh - 20px);
/* Fixed height based on the slider height, adjusting for padding */
position: relative;
overflow: hidden;
cursor: pointer;
}

.slider-item::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background-color: #4CAF50;
/* Adjust the color as needed */
transition: left 0.3s ease;
z-index: 0;
}

.slider-item:hover::before {
left: 0;
}

.slider-item * {
position: relative;
z-index: 1;
}

.slider-item:hover {
transition: transform 0.2s ease-in-out;
}

.slider-image {
width: 100px;
/* Fixed width for all images */
height: 100px;
/* Fixed height for all images */
object-fit: cover;
/* Cover the area, image may be cropped */
}

.slider-content {
display: flex;
flex-direction: column;
/* Stack title and description vertically */
justify-content: center;
/* Centers the text content vertically */
overflow: hidden;
/* Hide overflowed text */
margin-left: 10px;
/* Space between image and text content */
width: calc(250px - 10px);
/* Adjusted width for text content, considering padding */
}

.slider-title {
font-weight: bold;
/* Make the title text bold */
font-size: 1em;
/* Slightly larger font size for the title */
overflow: hidden;
/* Hide overflowed title text */
text-overflow: ellipsis;
/* Add an ellipsis for overflowed text */
white-space: wrap;
/* Prevent text wrapping to keep title in a single line */
}

.slider-description {
font-size: 0.8em;
/* Smaller font size for the description */
overflow: hidden;
/* Hide overflowed description text */
text-overflow: ellipsis;
/* Add an ellipsis for overflowed text */
white-space: wrap;
/* Prevent text wrapping to keep description in a single line */
}</code></pre>
<pre class="snippet-code-html lang-html prettyprint-override"><code><script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.0/angular.min.js"></script>
<div class="container">
<div class="slider" [ngClass]="{'slider-open': sliderOpen}">
<ul class="slider-list">
<!-- Repeat for more image-text pairs -->
<li class="slider-item">
<img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
<div class="slider-content">
<div class="slider-title">NowReading Tracker</div>
<div class="slider-description">Shows your current book and progress, keeping your reading journey clearly in view.</div>
</div>
</li>
<li class="slider-item">
<img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
<div class="slider-content">
<div class="slider-title">Deadline Dash</div>
<div class="slider-description">Highlights your most urgent task, ensuring top priorities are never overlooked.</div>
</div>
</li>
<li class="slider-item">
<img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
<div class="slider-content">
<div class="slider-title">Note Nudge</div>
<div class="slider-description">Brings your key notes to the forefront, making important information readily accessible.</div>
</div>
</li>
<li class="slider-item">
<img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
<div class="slider-content">
<div class="slider-title">Budget Beacon</div>
<div class="slider-description">Displays current budget status and upcoming expenses, guiding your financial decisions.</div>
</div>
</li>
<li class="slider-item">
<img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
<div class="slider-content">
<div class="slider-title">Invest Insight</div>
<div class="slider-description">Provides a snapshot of investment trends and metrics, simplifying financial oversight.</div>
</div>
</li>
<li class="slider-item">
<img src="../../../../../assets/img/clients/4.jpg" alt="Description" class="slider-image">
<div class="slider-content">
<div class="slider-title">QuickPath Picker</div>
<div class="slider-description">Instantly select the best route, optimizing your journey with a tap.</div>
</div>
</li>
</ul>
</div>
<!-- Plus (ADD) symbol -->
<button (click)="toggleSlider()" class="toggle-btn">&#43;</button>
</div></code></pre>
</div>
</div>
</p>
<p>What I'm trying to achieve is the following:</p>
<p>Similar to Jira gadgets, I want to insert gadgets in the screen.</p>
<p>Say the screen is 10x10 matrix, so I want when I press one item, e.g. the book item from the slider, to insert the component in the first empty spot in the matrix. Also from there I should be able to resize it (from 1x1 to maybe 2x1 for example), move, and delete.</p>
<p>This is why I have this here:
"import { ReadingNowTrackerComponent } from './gadgets/ReadingNow Tracker/ReadingNowTracker.component';"; I was trying to put this component in the "matrix" and sometimes it overflowed, sometimes it wouldn't place at all, many many issues I've encountered.</p>
<p>Please help me by taking my code and helping me get the result I intended.</p>
<p>Thank you!</p>
Continue reading...
 
Top