* Project Description
I am creating a responsive tournament Brackets tree web Page.
* Problem
My Problem is that I want to connect each Bracket "each bracket is a div of its own" to the next one with a decorated line, in general I want two connect two divs with a line that I can decorate.
- Example
Example1
Example2
* what I tried
I tried using the CanvasRenderingContext2D but I didn't have much luck in it, or at least didn't know how to fully utilize it
* What I want/ What's expected
I want to be able to connect the two brackets/divs with a line that can be bent and moved "by the developer not the user" to design it.
p.s: This only needs to be shown on screens larger than "xs" so mobile devices is not a concern.
* My Code
Stackblitz Project
- Used Libraries and versions
Angular V:5.1
Flex-Layout "angular" V: 2.00 beta 12
Angular Material V: 5.01
<div style="position:absolute; left:0; top:0; width: 100px; height: 50px; background:red;"></div>
<div style="position:absolute; left:250px; top:150px; width: 100px; height: 50px; background: blue;"></div>
<svg style="position:absolute; left:0; top:0;" width="300" height="300">
<line x1="100" y1="50" x2="250" y2="150" stroke="black"/>
</svg>
Example:
In app.component.html file:
<div class="cont">
<div *ngFor="let item of arr_item; index as i" class="block d-flex align-items-center justify-content-between">
<div class="d-flex flex-column" #left>
<div *ngFor="let val of item.left" class="box box-left">
{{val}}
</div>
</div>
<div class="d-flex flex-column align-items-end" #right>
<div *ngFor="let val of item.right" class="box box-right">
{{val}}
</div>
</div>
</div>
<svg style="position: absolute; z-index: -1; width:100%; height:100%; top: 0; left: 0;">
<polyline *ngFor="let item of polylines"
[attr.points]="item.points"
[attr.fill]="item.fill"
[attr.stroke]="item.stroke"
[attr.stroke-width]="item.strokeWidth"
/>
</svg>
</div>
In app.component.css file:
.cont {
position: relative;
}
.box {
padding: 10px;
border: 1px solid #292929;
width: fit-content;
margin: 10px;
max-width: 200px;
}
.block {
margin: 10px;
}
In app.component.ts file:
import {
AfterViewInit,
Component,
ElementRef,
OnInit,
QueryList,
ViewChildren
} from "#angular/core";
export class Polyline {
points: string;
fill: string;
stroke: string;
strokeWidth: number;
constructor(
points?: string,
fill?: string,
stroke?: string,
strokeWidth?: number
) {
this.points = points || "";
this.fill = fill || "none";
this.stroke = stroke || "blue";
this.strokeWidth = strokeWidth || 3;
}
}
#Component({
selector: "my-app",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent implements OnInit, AfterViewInit {
arr_item = [
{
left: ["Lorem Ipsum", "ABC"],
right: ["Hello World", "Hello"]
},
{
left: [
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
"Lorem Ipsum is simply dummy text"
],
right: [""]
},
{
left: [
"",
"Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s"
],
right: ["Rainbow"]
}
];
#ViewChildren("left", { read: ElementRef }) left: QueryList<ElementRef>;
#ViewChildren("right", { read: ElementRef }) right: QueryList<ElementRef>;
polylines: Polyline[] = [];
ngAfterViewInit() {
this.drawLines();
}
ngOnInit(): void {
window.addEventListener("resize", () => {
this.drawLines();
});
}
getPos_line(ele) {
var className = ele.getAttribute("class");
if (className.indexOf("left") !== -1) {
return {
x: Math.round(ele.offsetLeft + ele.offsetWidth),
y: Math.round(ele.offsetTop + ele.offsetHeight / 2)
};
} else {
return {
x: Math.round(ele.offsetLeft),
y: Math.round(ele.offsetTop + ele.offsetHeight / 2)
};
}
}
getMaxX_Left(arr: any) {
if (arr.length >= 1) {
var maxX = this.getPos_line(arr[0]).x;
for (var i = 0; i < arr.length; i++) {
var posLine = this.getPos_line(arr[i]);
if (posLine.x > maxX) {
maxX = posLine.x;
}
}
return maxX;
} else {
return null;
}
}
getMinX_Right(arr: any) {
if (arr.length >= 1) {
var minX = this.getPos_line(arr[0]).x;
for (var i = 0; i < arr.length; i++) {
var posLine = this.getPos_line(arr[i]);
if (posLine.x < minX) {
minX = posLine.x;
}
}
return minX;
} else {
return null;
}
}
getCenY(left, right) {
if (left.offsetHeight >= right.offsetHeight) {
return Math.round(left.offsetTop + left.offsetHeight / 2);
} else {
return Math.round(right.offsetTop + right.offsetHeight / 2);
}
}
drawLines() {
this.polylines = [];
for (var i = 0; i < this.arr_item.length; i++) {
var polylines_temp: Polyline[] = [];
var left_children = this.left.get(i).nativeElement.children;
var right_children = this.right.get(i).nativeElement.children;
var left_childrenLen = left_children.length;
var right_childrenLen = right_children.length;
var maxX_left = this.getMaxX_Left(left_children);
var minX_right = this.getMinX_Right(right_children);
var cenY = this.getCenY(
this.left.get(i).nativeElement,
this.right.get(i).nativeElement
);
var cenX = Math.round((maxX_left + minX_right) / 2);
var space = 0;
if (left_childrenLen > 1 && right_childrenLen > 1) {
space = 10;
}
for (var j = 0; j < left_childrenLen; j++) {
var posLine = this.getPos_line(left_children[j]);
polylines_temp.push(
new Polyline(`
${posLine.x},${posLine.y}
${cenX - space},${posLine.y}
${cenX - space},${cenY}
${cenX},${cenY}
`)
);
}
for (var j = 0; j < right_childrenLen; j++) {
var posLine = this.getPos_line(right_children[j]);
polylines_temp.push(
new Polyline(`
${cenX},${cenY}
${cenX + space},${cenY}
${cenX + space},${posLine.y}
${posLine.x},${posLine.y}
`)
);
}
this.polylines = this.polylines.concat(polylines_temp);
}
}
}
Link to Stackblitz
clearcanv() {
while (this.svg1.lastChild) {
this.svg1.removeChild(this.svg1.lastChild);
}
this.lines = [];
console.log(this.lines.length);}
drawcanv() {
var body = document.body,
html = document.documentElement;
var pheight = Math.max(
body.scrollHeight,
body.offsetHeight,
html.clientHeight,
html.scrollHeight,
html.offsetHeight
);
this.svg1.setAttribute("id", "svg");
var j2 = 0;
for (let i = 0; i < this.stages.length; i++) {
j2 = 0;
for (let j = 0; j < this.stages[i].Hist.length; j += 2) {
//Get Position of elements
var element1A = document.getElementById(i.toString() + j.toString());
var element1B = document.getElementById(
i.toString() + (j + 1).toString()
);
var element2 = document.getElementById(
(i + 1).toString() + j2.toString()
);
const x1A = element1A.offsetLeft + element1A.clientWidth;
const x1B = element1B.offsetLeft + element1B.clientWidth;
const x2 = element2.offsetLeft;
const y1A =
element1A.offsetTop + element1A.getBoundingClientRect().height / 2;
const y1B =
element1B.offsetTop + element1B.getBoundingClientRect().height / 2;
const y2 =
element2.offsetTop + element2.getBoundingClientRect().height / 2;
const halfwayx = Math.abs(x1A + x2) / 2;
const halfwayy = Math.abs(y1A + y1B) / 2;
//=======================================================================================
this.DrawLine(x1A, halfwayx, y1A, y1A, pheight);
this.DrawLine(x1B, halfwayx, y1B, y1B, pheight);
this.DrawLine(halfwayx, halfwayx, y1A, halfwayy, pheight);
this.DrawLine(halfwayx, halfwayx, y1B, halfwayy, pheight);
j2++;
}
}}
DrawLine(xs: number, xd: number, ys: number, yd: number, PageHeight: Number) {
//creating a line and adding design
this.lines.push(
document.createElementNS("http://www.w3.org/2000/svg", "line")
);
this.lines[this.lines.length - 1].setAttribute("stroke-width", "1px");
this.lines[this.lines.length - 1].setAttribute("stroke", "#000000");
this.lines[this.lines.length - 1].setAttribute(
"marker-end",
"url(#triangle)"
);
//=======================================================================================
//appending to and styling the svg
this.svg1.appendChild(this.lines[this.lines.length - 1]);
document.body.appendChild(this.svg1);
this.svg1.style.position = "absolute";
this.svg1.style.top = "0";
this.svg1.style.left = "0";
this.svg1.style.width = "100%";
this.svg1.style.height = PageHeight.toString();
this.svg1.style.zIndex = "-2";
//==================================================================s=====================
//Drawing the line.
this.lines[this.lines.length - 1].setAttribute("x1", `${xs}`);
this.lines[this.lines.length - 1].setAttribute("x2", `${xd}`);
this.lines[this.lines.length - 1].setAttribute("y1", `${ys}`);
this.lines[this.lines.length - 1].setAttribute("y2", `${yd}`);
//=======================================================================================}
Related
So far i only can achieve to this point. I am using example code from w3school
I trying to figure out to get year in the same position like in the picture but no luck and also tried to insert image background for circle but only manage to color fill
/* The actual timeline (the vertical ruler) */
.timeline {
position: relative;
max-width: 1200px;
margin: 0 auto;
}
/* The actual timeline (the vertical ruler) */
.timeline::after {
content: ' ';
position: absolute;
width: 6px;
background-color: #ffc200;
top: 0;
bottom: 0;
left: 50%;
margin-left: -5px;
}
/* Container around content */
.contaiment {
padding: 10px 40px;
position: relative;
background-color: inherit;
width: 50%;
}
/* The circles on the timeline */
.contaiment::after {
content: '';
position: absolute;
width: 25px;
height: 25px;
right: -11px;
background-color: #ffc200;
border: 4px solid #ffc200;
top: 15px;
border-radius: 50%;
z-index: 1;
}
/* Place the container to the left */
.left {
left: 0;
}
/* Place the container to the right */
.right {
left: 50%;
}
/* Add arrows to the left container (pointing right) */
/* .left::before {
content: " ";
height: 0;
position: absolute;
top: 22px;
width: 0;
z-index: 1;
right: 30px;
border: medium solid white;
border-width: 10px 0 10px 10px;
border-color: transparent transparent transparent white;
} */
/* Add arrows to the right container (pointing left) */
.right::before {
content: " ";
height: 0;
position: absolute;
top: 22px;
width: 0;
z-index: 1;
left: 30px;
border: medium solid white;
border-width: 10px 10px 10px 0;
border-color: transparent white transparent transparent;
}
/* Fix the circle for containers on the right side */
.right::after {
left: -16px;
}
/* The actual content */
.content {
padding: 20px 30px;
background-color: white;
position: relative;
border-radius: 6px;
}
<div class="timeline">
<div class="contaiment left">
<div class="content">
<p>2017
<p>
<p>Lorem ipsum..</p>
</div>
</div>
<div class="contaiment right">
<div class="content">
<p class="">2017
<p>
<p>Lorem ipsum..</p>
</div>
</div>
<div class="contaiment left">
<div class="content">
<p class="">2017
<p>
<p>Lorem ipsum..</p>
</div>
</div>
</div>
I know you want to do something with only HTML and CSS, but take a look at this JQuery library. It's very easy to use.
https://github.com/musclesoft/jquery-connections/wiki/API
You just need to set the 2 elements you want to connect.
$("#div1, #div2").connections();
$("#div2, #div3").connections();
$("#div3, #div4").connections();
<!doctype html>
<html lang="en">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap CSS -->
<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>
<title>Hello, world!</title>
</head>
<body>
<p style="width: 50px;height: 50px;border: 1px solid black;" id="div1">TEST</p>
<p style="width: 50px;height: 50px;margin-left: 50px;border: 1px solid black;" id="div2">TEST</p>
<p style="width: 50px;height: 50px;margin-left: 200px;border: 1px solid black;" id="div3">TEST</p>
<p style="width: 50px;height: 50px;margin-left: 300px;border: 1px solid black;" id="div4">TEST</p>
<!-- Script retrieved from https://raw.githubusercontent.com/musclesoft/jquery-connections/gh-pages/jquery.connections.js -->
<script>
(function($) {
$.fn.connections = function(options) {
if (options === "update") {
return processConnections(update, this);
} else if (options === "remove") {
return processConnections(destroy, this);
} else {
options = $.extend(
true,
{
borderClasses: {},
class: "connection",
css: {},
from: this,
tag: "connection",
to: this,
within: ":root"
},
options
);
connect(options);
return this;
}
};
$.event.special.connections = {
teardown: function(namespaces) {
processConnections(destroy, $(this));
}
};
var connect = function(options) {
var borderClasses = options.borderClasses;
var tag = options.tag;
var end1 = $(options.from);
var end2 = $(options.to);
var within = $(options.within);
delete options.borderClasses;
delete options.tag;
delete options.from;
delete options.to;
delete options.within;
within.each(function() {
var container = this;
var done = new Array();
end1.each(function() {
var node = this;
done.push(this);
end2.not(done).each(function() {
createConnection(
container,
[node, this],
tag,
borderClasses,
options
);
});
});
});
};
var createConnection = function(
container,
nodes,
tag,
borderClasses,
options
) {
var css = $.extend({ position: "absolute" }, options.css);
var connection = $("<" + tag + "/>", options).css(css);
connection.appendTo(container);
var border_w = (connection.outerWidth() - connection.innerWidth()) / 2;
var border_h = (connection.outerHeight() - connection.innerHeight()) / 2;
if (border_w <= 0 && border_h <= 0) {
border_w = border_h = 1;
}
var data = {
borderClasses: borderClasses,
border_h: border_h,
border_w: border_w,
node_from: $(nodes[0]),
node_to: $(nodes[1]),
nodes_dom: nodes,
css: css
};
if ("none" === connection.css("border-top-style")) {
data.css.borderStyle = "solid";
}
$.data(connection.get(0), "connection", data);
$.data(connection.get(0), "connections", [connection.get(0)]);
for (var i = 0; i < 2; i++) {
var connections = connection.add($.data(nodes[i], "connections")).get();
$.data(nodes[i], "connections", connections);
if (connections.length == 1) {
$(nodes[i]).on("connections.connections", false);
}
}
update(connection.get(0));
};
var destroy = function(connection) {
var nodes = $.data(connection, "connection").nodes_dom;
for (var i = 0; i < 2; i++) {
var connections = $($.data(nodes[i], "connections"))
.not(connection)
.get();
$.data(nodes[i], "connections", connections);
}
$(connection).remove();
};
var getState = function(data) {
data.rect_from = data.nodes_dom[0].getBoundingClientRect();
data.rect_to = data.nodes_dom[1].getBoundingClientRect();
var cached = data.cache;
data.cache = [
data.rect_from.top,
data.rect_from.right,
data.rect_from.bottom,
data.rect_from.left,
data.rect_to.top,
data.rect_to.right,
data.rect_to.bottom,
data.rect_to.left
];
data.hidden =
0 === (data.cache[0] | data.cache[1] | data.cache[2] | data.cache[3]) ||
0 === (data.cache[4] | data.cache[5] | data.cache[6] | data.cache[7]);
data.unmodified = true;
if (cached === undefined) {
return (data.unmodified = false);
}
for (var i = 0; i < 8; i++) {
if (cached[i] !== data.cache[i]) {
return (data.unmodified = false);
}
}
};
var update = function(connection) {
var data = $.data(connection, "connection");
getState(data);
if (data.unmodified) {
return;
}
var border_h = data.border_h;
var border_w = data.border_w;
var from = data.rect_from;
var to = data.rect_to;
var b = (from.bottom + from.top) / 2;
var r = (to.left + to.right) / 2;
var t = (to.bottom + to.top) / 2;
var l = (from.left + from.right) / 2;
var h = ["right", "left"];
if (l > r) {
h = ["left", "right"];
var x = Math.max(r - border_w / 2, Math.min(from.right, to.right));
r = l + border_w / 2;
l = x;
} else {
l -= border_w / 2;
r = Math.min(r + border_w / 2, Math.max(from.left, to.left));
}
var v = ["bottom", "top"];
if (t > b) {
v = ["top", "bottom"];
var x = Math.max(b - border_h / 2, Math.min(from.bottom, to.bottom));
b = t + border_h / 2;
t = x;
} else {
b = Math.min(b, Math.max(from.top, to.top));
t -= border_h / 2;
}
var width = r - l;
var height = b - t;
if (width < border_w) {
t = Math.max(t, Math.min(from.bottom, to.bottom));
b = Math.min(b, Math.max(from.top, to.top));
l = Math.max(from.left, to.left);
r = Math.min(from.right, to.right);
r = l = (l + r - border_w) / 2;
}
if (height < border_h) {
l = Math.max(l, Math.min(from.right, to.right));
r = Math.min(r, Math.max(from.left, to.left));
t = Math.max(from.top, to.top);
b = Math.min(from.bottom, to.bottom);
b = t = (t + b - border_h) / 2;
}
width = r - l;
height = b - t;
width <= 0 && (border_h = 0);
height <= 0 && (border_w = 0);
var style =
"border-" +
v[0] +
"-" +
h[0] +
"-radius: 0;" +
"border-" +
v[0] +
"-" +
h[1] +
"-radius: 0;" +
"border-" +
v[1] +
"-" +
h[0] +
"-radius: 0;";
(border_h <= 0 || border_w <= 0) &&
(style += "border-" + v[1] + "-" + h[1] + "-radius: 0;");
if (data.hidden) {
style += "display: none;";
} else {
data.css["border-" + v[0] + "-width"] = 0;
data.css["border-" + h[0] + "-width"] = 0;
data.css["border-" + v[1] + "-width"] = border_h;
data.css["border-" + h[1] + "-width"] = border_w;
var current_rect = connection.getBoundingClientRect();
data.css.left = connection.offsetLeft + l - current_rect.left;
data.css.top = connection.offsetTop + t - current_rect.top;
data.css.width = width - border_w;
data.css.height = height - border_h;
}
var bc = data.borderClasses;
$(connection)
.removeClass(bc[v[0]])
.removeClass(bc[h[0]])
.addClass(bc[v[1]])
.addClass(bc[h[1]])
.attr("style", style)
.css(data.css);
};
var processConnections = function(method, elements) {
return elements.each(function() {
var connections = $.data(this, "connections");
if (connections instanceof Array) {
for (var i = 0, len = connections.length; i < len; i++) {
method(connections[i]);
}
}
});
};
})(jQuery);
</script>
<script defer type="text/javascript">
$("#div1, #div2").connections();
$("#div2, #div3").connections();
$("#div3, #div4").connections();
</script>
</body>
</html>
i want to put dynamically components like div and textArea into a div with innerHTML but it doesn't work
imaginarium is the parent component where i'd like to show the draggable component
imaginarium.html
<div *ngIf="shape == true">
<div>
<table>
<tr>
<td><button (click)='update()'>box</button></td>
</tr>
<tr>
<td><button (click)='update()'>editeur de texte</button></td>
</tr>
</table>
</div>
<div class="box-container" [innerHTML]="trustedURL"></div>
</div>
i give you all the necessary code to test yourself
imaginarium.scss
.box-container {
position: absolute;
width: 80%;
height: 90%;
outline: 1px solid black;
transform: translate3d(-100%, -100%);
}
imaginarium.ts
import { DomSanitizer } from '#angular/platform-browser';
trustedURL : any;
constructor(
public sanitizer: DomSanitizer
) {
}
update(){
this.trustedURL = this.sanitizer.bypassSecurityTrustHtml(`<app-resizable-draggable [width]='100' [height]='150' [left]='100' [top]='100' ></app-resizable-draggable>`);
}
this is this child component
resizable-draggable.html
<ul #menu (mouseleave)="resetMenu()"></ul>
<div #box class="resizable-draggable"
[style.width.px]="width"
[style.height.px]="height"
[style.transform]="'translate3d('+ left + 'px,' + top + 'px,' + '0px)'"
[class.active]="status === 1 || status === 2"
(mousedown)="setStatus($event, 2)"
(window:mouseup)="setStatus($event, 0)"
(contextmenu)="Menu()"
>
<div class="resize-action" (mousedown)="setStatus($event, 1)"></div>
</div>
with CSS
resizable-draggable.scss
.resizable-draggable {
outline: 1px solid black;
&.active {
outline-style: solid;
background-color: #80ff800d;
}
&:hover {
cursor: all-scroll;
}
}
.resize-action {
position: absolute;
left: 100%;
top: 100%;
transform: translate3d(-50%, -50%, 0) rotateZ(45deg);
border-style: solid;
border-width: 8px;
border-color: transparent transparent transparent #1100ff;
&:hover, &:active {
cursor: nwse-resize;
}
}
and the .ts
resizable-draggable.ts
import { Component, OnInit, Input, ViewChild, ElementRef, AfterViewInit, HostListener } from '#angular/core';
const enum Status {
OFF = 0,
RESIZE = 1,
MOVE = 2
}
#Component({
selector: 'app-resizable-draggable',
templateUrl: './resizable-draggable.component.html',
styleUrls: ['./resizable-draggable.component.scss']
})
export class ResizableDraggableComponent implements OnInit, AfterViewInit {
#Input('width') public width!: number;
#Input('height') public height!: number;
#Input('left') public left!: number;
#Input('top') public top!: number;
#ViewChild("box") public box!: ElementRef;
#ViewChild('menu', {static: false}) menuChild! : ElementRef;
private boxPosition!: { left: number, top: number };
private containerPos!: { left: number, top: number, right: number, bottom: number };
public mouse!: {x: number, y: number}
public status: Status = Status.OFF;
private mouseClick!: {x: number, y: number, left: number, top: number}
ngOnInit() {}
ngAfterViewInit(){
this.loadBox();
this.loadContainer();
}
private loadBox(){
const {left, top} = this.box.nativeElement.getBoundingClientRect();
this.boxPosition = {left, top};
}
private loadContainer(){
const left = this.boxPosition.left - this.left;
const top = this.boxPosition.top - this.top;
const right = left + window.screen.width -395;
const bottom = top + window.screen.height- 240;
this.containerPos = { left, top, right, bottom };
}
setStatus(event: MouseEvent, status: number){
if(status === 1) event.stopPropagation();
else if(status === 2) this.mouseClick = { x: event.clientX, y: event.clientY, left: this.left, top: this.top };
else this.loadBox();
this.status = status;
}
#HostListener('window:mousemove', ['$event'])
onMouseMove(event: MouseEvent){
this.mouse = { x: event.clientX, y: event.clientY };
if(this.status === Status.RESIZE) this.resize();
else if(this.status === Status.MOVE) this.move();
}
private resize(){
if(this.resizeCondMeet()){
this.width = Number(this.mouse.x > this.boxPosition.left) ? this.mouse.x - this.boxPosition.left : 0;
this.height = Number(this.mouse.y > this.boxPosition.top) ? this.mouse.y - this.boxPosition.top : 0;
}
}
private resizeCondMeet(){
return (this.mouse.x < this.containerPos.right && this.mouse.y < this.containerPos.bottom);
}
private move(){
if(this.moveCondMeet()){
this.left = this.mouseClick.left + (this.mouse.x - this.mouseClick.x);
this.top = this.mouseClick.top + (this.mouse.y - this.mouseClick.y);
}
}
private moveCondMeet(){
const offsetLeft = this.mouseClick.x - this.boxPosition.left;
const offsetRight = this.width - offsetLeft;
const offsetTop = this.mouseClick.y - this.boxPosition.top;
const offsetBottom = this.height - offsetTop;
return (
this.mouse.x > this.containerPos.left + offsetLeft &&
this.mouse.x < this.containerPos.right - offsetRight &&
this.mouse.y > this.containerPos.top + offsetTop &&
this.mouse.y < this.containerPos.bottom - offsetBottom
);
}
Menu(){
this.menuChild.nativeElement.innerHTML = `
<li><button>Actions</button></li>
<li><button>Actions</button></li>
<li><button>Actions</button></li>
<li><button>Actions</button></li>
<li><button>Actions</button></li>
`;
return false;
}
resetMenu(){
this.menuChild.nativeElement.innerHTML = ``;
}
}
have you got an idea to show the component ?
thanks
I've searched and I found something helpful :
https://dzone.com/articles/how-to-dynamically-create-a-component-in-angular
it's permit to add angular components into html
I'm pretty new to express and socket.io and I'm trying to achieve a little website:
What is it supposed to do:
You can connect to the website and enter a username
You have to select a column where you want to write (it's stored in var column)
Once on the page with the four column, you can see your username at the top of your column and start doing things there.
The other users see you in the correct column.
What it is not doing:
Actually the three points above are working quite well, my issue is with the last point :
The other users see you in the correct column.
My code is somehow not displaying every user in the correct column, in fact, it's displaying them in the same column as you are
Here is the code
$(document).ready(function () {
var socket = io();
var username = prompt("premier utilisateur : ", "nom");
var column = prompt("colonne ", "1,2,3 ou 4");
var gdhb = "";
socket.emit("new user entered his name");
socket.emit("nomUser", username);
if (column === "1") { column = ".one"; gdhb = ".dir1" }
if (column === "2") { column = ".two"; gdhb = ".dir2" }
if (column === "3") { column = ".three"; gdhb = ".dir3" }
if (column === "4") { column = ".four"; gdhb = ".dir4" }
socket.emit("user chose a column");
socket.emit("columnUser", column);
$(column).append($("<p class='username'>" + username + "</p>"))
$(document.body).click(function (b) {
var verbes = [
"appuie",
"bouscule",
"pousse"
];
var adverbes = [
"puis",
"ensuite",
"pour finir",
"alors"
];
var verbe = verbes[Math.floor(Math.random() * verbes.length)];
var adverbe = adverbes[Math.floor(Math.random() * adverbes.length)];
var verbadv = verbe + " " + adverbe;
console.log(verbadv);
socket.emit("verbadverbe");
socket.emit("verbadv", verbadv);
var div = $("<div />", {
"class": "document"
})
.css({
"left": b.pageX + 'px',
"top": b.pageY + 'px'
})
.append($("<p>" + verbadv + "</p>"))
.appendTo(column);
});
$(document.body).contextmenu(function (rc) {
var div = $("<div />", {
"class": "document"
})
.css({
"left": rc.pageX + 'px',
"top": rc.pageY + 'px'
})
.append($("<p>recule</p>"))
.appendTo(column);
});
var direction = "";
var oldx = 0;
var oldy = 0;
mousemovemethod = function (e) {
if (e.pageX > oldx && e.pageY == oldy) {
direction = "gauche";
}
else if (e.pageX == oldx && e.pageY > oldy) {
direction = "bas";
}
else if (e.pageX == oldx && e.pageY < oldy) {
direction = "haut";
}
else if (e.pageX < oldx && e.pageY == oldy) {
direction = "droite";
}
$(gdhb).append($("<p class='direction' id='direction'>" + direction + "</p>"))
$(".direction").prev().remove();
oldx = e.pageX;
oldy = e.pageY;
}
document.addEventListener('mousemove', mousemovemethod);
socket.on("columnUser", function (column) {
socket.on("nomUser", function (username) {
$(column).append($("<p class='username'>" + username + "</p>"));
socket.on("verbadv", function (verbadv) {
var div = $("<div />", {
"class": "document"
})
.append($("<p>" + verbadv + "</p>"))
.appendTo(column);
});
});
});
});
and the index.js :
const path = require('path');
const http = require('http');
const express = require('express');
const socketio = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketio(server);
app.use(express.static(path.join(__dirname, 'public')));
io.on('connection', (socket) => {
console.log('Nouvel utilisateur')
socket.on("nomUser", (username) => {
console.log(username);
io.emit("nomUser", username);
});
socket.on("verbadv", (verbadv) => {
console.log(verbadv);
io.emit("verbadv", verbadv);
});
socket.on("columnUser", (column) => {
console.log(column);
io.emit("columnUser", column);
});
});
server.listen(3000, () => {
console.log('listen on 3000');
})
Also if it's needed to understand better, here is the css
body {
font-family: sans-serif;
font-size: 1.3rem;
margin: 0;
background-color: DarkSlateGray;
}
.wrapper {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 0px;
grid-auto-rows: minmax(100vh, auto);
height: 100vh;
}
.one,
.two,
.three,
.four {
-ms-overflow-style: none; /* Internet Explorer 10+ */
scrollbar-width: none; /* Firefox */
position: relative;
overflow: scroll;
height: 100%;
background-color: tan;
}
.one {
grid-column: 1 / 2;
}
.two {
grid-column: 2 / 3;
}
.three {
grid-column: 3 / 4;
}
.four {
grid-column: 4 / 4;
}
.one::-webkit-scrollbar,
.two::-webkit-scrollbar,
.three::-webkit-scrollbar,
.four::-webkit-scrollbar {
display: none; /* Safari and Chrome */
}
.note {
text-align: center;
width: 100px;
height: 30px;
}
.note p{
filter: drop-shadow(0 0 0.75rem black);
}
.document{
text-align: center;
}
.document p{
padding: 0;
margin: 0;
}
.username{
text-align: center;
padding: 0;
margin: 0;
}
.direction{
position: fixed;
bottom : 0;
width: 25vw;
text-align: center;
}
Thanks a lot for the precious help.
i've solved your problem with sockets. See at my solution.
client.js
function columnIndexIsValid(index, columnsQuantity) {
return index >= 0 && index <= columnsQuantity;
}
function fullNameIsValid(fullName) {
return typeof fullName === 'string' && fullName.length > 2;
}
function reloadPage() {
window.location.reload();
}
function rand(min, max) {
return Math.floor(min + Math.random() * (max - 1 - min));
}
function getRandomColour(colours = []) {
const colour = colours[rand(0, colours.length)];
return `#${colour}`;
}
function getUserHtml(user) {
return `<div class="column__users-list__item" data-item-id="${user.id}">${user.fullName}</div>`;
}
function getDrawnUsersNodes() {
return $('.column__item');
}
function canIRenderUsers(usersQuantity) {
const $renderedUsersQuantity = getDrawnUsersNodes().length;
return $renderedUsersQuantity < usersQuantity;
}
function renderUserHtmlToNode($node, html) {
$node.html($node.html() + html);
}
function getColumnUsersList(columnNode) {
const $column = $(columnNode);
return $column.find('.column__users-list');
}
function removeDrawnUserById(userId) {
$(`[data-item-id=${userId}]`).remove();
}
class DrawnUsers {
constructor() {
this.users = new Map();
}
getUserById(id) {
return this.users.get(id);
}
add(id, state) {
this.users.set(id, state);
}
removeById(id) {
this.users.delete(id);
}
exists(id) {
return this.users.has(id);
}
}
class Storage {
static setItem(key, value) {
localStorage.setItem(key, value);
}
static getItem(key) {
return localStorage.getItem(key) || null;
}
}
function generateUserId() {
return `user-${rand(rand(0, 10000), rand(20000, 50000))}`;
}
class UserState {
constructor() {
this.state = {};
}
get() {
return {
data: this.state,
};
}
set fullName(fullName) {
this.state.fullName = fullName;
}
get fullName() {
return this.state.fullName;
}
set id(id) {
this.state.id = id;
}
get id() {
return this.state.id;
}
set columnIndex(columnIndex) {
this.state.columnIndex = columnIndex - 1;
}
get columnIndex() {
return this.state.columnIndex;
}
}
$(document).ready(function () {
const drawnUsers = new DrawnUsers();
const colours = ['F2994A', 'F2C94C', '6FCF97', '2F80ED', '56CCF2', 'DFA2F5'];
const $columns = $('.column');
const $container = $('.container');
const userState = new UserState();
$columns.each(function () {
const $self = $(this);
$self.css({ 'background-color': getRandomColour(colours) });
});
userState.fullName = prompt('Type your fullName');
userState.columnIndex = +prompt('Type your column number');
if (
!fullNameIsValid(userState.fullName) ||
!columnIndexIsValid(userState.columnIndex, $columns.length)
) {
return reloadPage();
}
$container.addClass('active');
const socket = io('ws://localhost:3000');
socket.on('connect', () => {
const generatedUserId = generateUserId();
userState.id = Storage.getItem('userId') || generatedUserId;
Storage.setItem('userId', userState.id);
socket.emit('connected', userState.get());
socket.emit('addUser', userState.get());
socket.on('updateCurrentUsers', ({ data }) => {
const { users } = data;
if (!users || !canIRenderUsers(users.length)) {
return;
}
users.forEach((user) => {
const $column = $columns[user.columnIndex];
if ($column) {
if (!drawnUsers.exists(user.id)) {
drawnUsers.add(user.id);
renderUserHtmlToNode(
getColumnUsersList($column),
getUserHtml(user)
);
}
}
});
});
socket.on('newUser', ({ data }) => {
console.log('[debug] newUser: ', data);
const $column = $columns[data.columnIndex];
if (!$column) {
return;
}
if (drawnUsers.exists(data.id)) {
drawnUsers.removeById(data.id);
removeDrawnUserById(data.id);
} else {
drawnUsers.add(data.id);
renderUserHtmlToNode(getColumnUsersList($column), getUserHtml(data));
}
});
socket.on('disconnect', () => {
socket.open();
});
});
});
server.js
const path = require('path');
const http = require('http');
const express = require('express');
const socketIo = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
app.use(express.static(path.join(__dirname, 'public')));
io.on('connection', (socket) => {
console.log('[debug] Client was connected successfully to server');
socket.on('connected', (user) => {
console.log('connected user data', user.data);
socket.data = user.data;
const users = Array.from(io.sockets.sockets.values()).map(
({ data }) => data
);
console.log('users', users);
socket.emit('updateCurrentUsers', {
data: {
users,
},
});
});
socket.on('addUser', ({ data }) => {
socket.data.columnIndex = data.columnIndex;
socket.broadcast.emit('newUser', {
data,
});
const users = Array.from(io.sockets.sockets.values()).map(
({ data }) => data
);
socket.broadcast.emit('updateCurrentUsers', {
data: {
users,
},
});
});
});
server.listen(3000, () => {
console.log('listen on 3000');
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="./index.css">
<title>Users Columns</title>
</head>
<body>
<div class="container">
<div class="column">
<span class="column__index">#1</span>
<div class="column__users-list"></div>
</div>
<div class="column">
<span class="column__index">#2</span>
<div class="column__users-list"></div>
</div>
<div class="column">
<span class="column__index">#3</span>
<div class="column__users-list"></div>
</div>
<div class="column">
<span class="column__index">#4</span>
<div class="column__users-list"></div>
</div>
</div>
<script
src="https://code.jquery.com/jquery-3.5.1.js"
integrity="sha256-QWo7LDvxbWT2tbbQ97B53yJnYU3WhH/C8ycbRAkjPDc="
crossorigin="anonymous"></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.0.1/socket.io.js"
integrity="sha512-vGcPDqyonHb0c11UofnOKdSAt5zYRpKI4ow+v6hat4i96b7nHSn8PQyk0sT5L9RECyksp+SztCPP6bqeeGaRKg=="
crossorigin="anonymous"></script>
<script src="./client.js"></script>
</body>
</html>
index.css
html, body {
box-sizing: border-box;
}
body {
margin: 0;
padding: 0;
}
.container {
display: none;
}
.container.active {
display: flex;
flex-direction: row;
height: 100vh;
}
.container .column {
width: calc(100% / 4);
text-align: center;
padding: 20px 0;
}
.container .column .column__users-list {
display: flex;
flex-direction: column;
}
.container .column .column__index {
color: #FFF;
font-size: 36px;
letter-spacing: 8px;
}
hey) Let's look on that case more attentive, you have created a variable what contains a column index. right? This variable does store inside var variable. That is global variable for your script. I think you need to store each user column info inside each socket. If you don't know, each socket contains some info inside itself like id and other what can be used in your project if you'd like. But for this case you need to do so:
Ask new user what column he would like to choose.
Write to his socket data/info his column index.
When you do broadcast (It's when you send an object of data to each socket in room all each socket at all) just take this column index and draw this user in correct position. But make sure what your var column has a correct value. I can advice you to use const/let in javascript :)
I've a table inside a div inside the main.
all of this have width = 100% but when i make the window smaller just the div ant the main gets smaller but the table dosent resize. all other elements below the table change position and size and starts lay over it. the table has 25 records and if the window is full-size everything matches perfect.
The div im talkin about has the id home
The html:
<DOCTYPE! html>
<html>
<head>
<meta charset="UTF-8">
<title>Admin Panel | Please Login</title>
<link href='../css/admin.css' type='text/css' rel='stylesheet'>
<meta http-equiv="refresh" content="60">
</head>
<body>
<main>
<div id='container'>
<img src="../img/ipw_quer_rgb.jpg" alt="IPW Logo" id="logo" width="15%">
<header>
<ul id='menu'>
<div id="links">
<li>Home</li>
<li>Schüler verwalten</li>
<li>History</li>
</div>
</ul>
</header>
<div id="home">
<h2 id="date"></h2>
<table id="table">
</table>
</div>
<div id="dropdown" class="dropdown">
<select id="select">
<option value="Nothing">Nichts ausgewählt</option>
<option value="Extern">Extern</option>
<option value="Termin">Termin</option>
<option value="Schule">Schule</option>
</select>
</div>
<div id="legend" class="legend">
<svg width="10" height="10">
<rect x="0" y="0" width="10" height="10" style="fill:#D3D3D3;" />
</svg>
<a>Extern</a>
<br>
<svg width="10" height="10">
<rect x="0" y="0" width="10" height="10" style="fill:#FFAEB9;" />
</svg>
<a>Termin</a>
<br>
<svg width="10" height="10">
<rect x="0" y="0" width="10" height="10" style="fill:#FFFF00;" />
</svg>
<a>Schule</a>
<br>
<svg width="10" height="10">
<rect x="0" y="0" width="10" height="10" style="fill:#00FF00;" />
</svg>
<a>Visiert</a>
<br>
<button id="edit">Editieren</button>
</div>
</div>
</main>
the css:
*{
margin: 0;
padding: 0;
font-family: monospace;
box-sizing: border-box;
}
main{
height: 100%;
width: 100%;
}
label{
font: 13px Helvetica, Arial, sans-serif;
}
html, body{
background-color: #006975;
overflow-y:auto;
height: 100%;
width: 100%;
padding: 0;
margin: 0;
}
h3{
margin-right:50%;
}
table{
width:100%;
height:calc(100% -50px);
}
ul {
width:100%;
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: #006975;
}
li {
width:25%;
float: left;
}
h1{
text-align:center;
}
li a {
display: block;
color: white;
font-size: 120%;
text-align: center;
padding: 14px 16px;
border-left: 2px solid #fff;
border-right: 2px solid #fff;
text-decoration: none;
}
/* Change the link color to #111 (black) on hover */
li a:hover {
color: #006975;
background-color: #fff;
}
button:hover{
color:#fff;
background-color:#84afb8;
}
#container{
position:absolute;
margin: 4% 4% 4% 4%;
padding: 2%;
width: 90%;
height: 90%;
background-color: #fff;
}
#home{
height:60%;
}
#dropdown{
width:100%;
height:2%;
visibility:hidden;
}
.cell {
height: 4%;
width:10%;
text-align: center;
background-color: #D3D3D3;
}
.cell.on {
height: 4%;
width: 10%;
background-color: #00FF00;
}
.cell.les {
height: 4%;
width: 10%;
background-color: #FFFF00;
}
.cell.term {
height: 4%;
width: 10%;
background-color: #FFAEB9;
}
.cell.ext{
height: 4%;
width: 10%;
background-color: #D3D3D3;
}
.cell.spacer {
height: 4%;
width: 10%;
background-color:white;
}
.name {
border: 1px solid black;
}
if you also need the javascript please ask
EDIT:
javascript:
getDataUser('logGLAUSB');
var names = ["Zebra","Benj", "Nico", "Timon","Miro", "Leo"];
var longpresstimer = null;
getData();
window.addEventListener('click', function(){
});
window.addEventListener('load', function () {
var clickcount = 0;
var singleClickTimer;
document.getElementById('table').addEventListener('click', function (event) {
clickcount++;
if(clickcount==1){
if(event.target.tagName != "INPUT" && event.target.classList != 'cell spacer'){
singleClickTimer = setTimeout(function() {
clickcount = 0;
var cell = event.target;
var selected = getSelected();
if(selected == 3){
cell.classList.remove("ext");
cell.classList.remove("term");
cell.classList.remove("les");
cell.classList.add("on");
}else{
cell.classList.remove("ext");
cell.classList.remove("term");
cell.classList.remove("les");
cell.classList.remove("on");
switch(selected){
case 0: cell.classList.add("ext"); break;
case 1: cell.classList.add("les"); break;
case 2: cell.classList.add("term"); break;
}
}
var x = "get";
x += getString(event.target.parentNode.cells[0].childNodes[0].innerHTML);
getData(x);
}, 300);
}
}else if (clickcount == 2){
if(event.target.classList != "name"){
clearTimeout(singleClickTimer);
clickcount = 0;
toInput(event.target);
}
}
});
});
document.getElementById("edit").addEventListener('click', function(){
var legend = document.getElementById("legend");
var dropdown = document.getElementById('dropdown');
var select = document.getElementById('select');
legend.style.visibility = 'hidden';
dropdown.style.visibility = 'visible';
var button = document.createElement('button');
button.innerHTML= "Fertig";
dropdown.appendChild(button);
button.onclick = function(){
legend.style.visibility = 'visible';
dropdown.style.visibility = 'hidden';
dropdown.removeChild(button);
select.value = "Nothing";
}
});
function reset(){
var rows = Array.from(document.getElementsByClassName('row'));
var table = document.getElementById('table');
rows.forEach(function (row){
var cells = Array.from(document.getElementsByClassName('cell'));
for(var i = 0;i< cells.length;i++){
var cell = cells[i]
cell.classList.remove('on');
cell.classList.remove('les');
cell.classList.remove('term');
cell.classList.remove('ext');
}
});
var x = "rep";
x += getResetString();
getData(x);
}
function clearSelection() {
if(document.selection && document.selection.empty) {
document.selection.empty();
} else if(window.getSelection) {
var sel = window.getSelection();
sel.removeAllRanges();
}
}
function getData(str) {
var requestURL = "http://adopraesenz.ipwin.ch/data/students.php?q=" +str;
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(this.readyState === 4 && this.status === 200){
loadJson(request);
}
};
request.open("GET", requestURL, true);
request.send();
}
function getDataHistory(str) {
var requestURL = "http://adopraesenz.ipwin.ch/data/history.php?q=" +str;
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(this.readyState === 4 && this.status === 200){
if(request.responseText != ""){
loadDate(request);
}
}
};
request.open("GET", requestURL, true);
request.send();
}
function getDataUser(str){
var requestURL = "http://adopraesenz.ipwin.ch/data/login.php?q=" +str;
var request = new XMLHttpRequest();
request.onreadystatechange = function() {
if(this.readyState === 4 && this.status === 200){
if(request.responseText != ""){
loadDate(request);
} }
};
request.open("GET", requestURL, true);
request.send();
}
function getSelected(cell) {
var value = document.getElementById("select").value;
switch(value){
case "Extern": return 0; break;
case "Schule": return 1; break;
case "Termin": return 2; break;
default: return 3;
}
}
function loadDate(request){
var newDate = new Date();
newDate.setDate(newDate.getDate() -1);
newDate.setHours(0,0,0,0);
var days = request.responseText.split(".");
var oldDate = new Date(days[1]+"."+days[0]+"."+days[2]);
if(newDate > oldDate){
var date = new Date();
date.setDate(date.getDate() - 1);
var dd = date.getDate();
var mm = date.getMonth() + 1;
var yyyy = date.getFullYear();
if(dd < 10) {
dd = '0' +dd;
}
if(mm < 10) {
mm = '0' +mm;
}
var yesterday = dd+"."+mm+"."+yyyy;
getDataHistory('add' + yesterday);
reset();
}
newDate = new Date().toLocaleDateString('de-CH', {
weekday: 'long',
day: '2-digit',
month: '2-digit',
year: 'numeric'
});
document.getElementById('date').innerHTML = newDate;
}
getDataHistory('new');
function loadJson(request){
createTable(request.responseText);
}
function createHeader(array){
var header = document.createElement("thead");
var hRow = document.createElement('tr');
hRow.classList.add('header');
for(var i = 0; i < array.length; i++){
var div = document.createElement('div');
var th = document.createElement('th');
div.innerHTML = array[i];
th.appendChild(div);
hRow.appendChild(th);
}
header.appendChild(hRow);
return header;
}
function createTable(json){
var obj = JSON.parse(json);
var oldBody = document.getElementsByTagName('tbody')[0];
console.log(oldBody);
var oldHeader = document.getElementsByTagName('thead')[0];
var body = document.createElement('tbody');
var header = createHeader(["Name","09:00 – 09:45","10:15 – 11:00","11:00 – 11:45"," ","14:00 – 14:45","15:00 - 15:45","16:00 – 16:45"]);
for (var j = 0; j < obj.length; j++) {
var row = addRow(obj[j],body);
row.classList.add('row');
}
console.log(body);
replaceTable(body, oldBody, header ,oldHeader);
if(obj.length > 25){
var view = document.getElementById('home');
view.setAttribute("style", "overflow-y:scroll");
}
}
function toInput(cell){
var input = document.createElement('input');
setTimeout(function() { input.focus(); }, 200);
cell.appendChild(input);
window.addEventListener('keypress', function(e){
if(e.keyCode == '13'){
var text = input.value;
if(input.parentNode != null){
input.parentNode.removeChild(input);
}
cell.innerHTML = text;
getData("get"+getString(cell.parentNode.cells[0].childNodes[0].innerHTML));
}
}, false);
}
function replaceTable(body, oldBody, header, oldHeader){
if(typeof oldHeader == 'undefined'){
table.appendChild(header);
}else if(oldHeader.parentNode == table){
table.replaceChild(header, oldHeader);
}else{
table.appendChild(header);
}
if(typeof oldBody == 'undefined'){
table.appendChild(body);
}else if(oldBody.parentNode == table){
table.removeChild(oldBody);
table.appendChild(body);
//table.replaceChild(body, oldBody);
}else{
table.appendChild(body);
}
}
function addRow(val,body) {
var rest = val.split(";");
var tr = document.createElement('tr');
for( var i = 0; i < 8; i++){
if(i==0){
var name = rest[0];
addCell(tr, null,name);
}else{
var value = rest[i];
addCell(tr, value, name);
}
}
body.appendChild(tr);
return tr;
}
function addCell(tr, val, name) {
var name;
var cell = document.createElement('td');
var value = "get";
if(val == null){
var input = document.createElement('label');
cell.classList.add("name")
input.innerHTML = name;
input.readOnly = true;
cell.appendChild(input);
}else{
cell = document.createElement('td');
cell.classList.add('cell');
var content = val.split(":");
switch(content[0]){
case '0': cell.classList.add('ext'); break;
case '1': cell.classList.add('les'); break;
case '2': cell.classList.add('term'); break;
case '3': cell.classList.add('on'); break;
case '4': cell.classList.add('spacer'); break;
}
if(val.length > 1){
cell.innerHTML = content[1];
}
}
tr.appendChild(cell);
}
window.onclick = function(event) {
if (!event.target.matches('.dropA')) {
var dropdowns = document.getElementsByClassName("dropdown-content");
var i;
for (i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
function getString(name){
var x = "";
var names = document.getElementsByClassName('name');
var values = document.getElementsByClassName('cell');
for(var i = 0;i<names.length;i++){
if(names[i].childNodes[0].innerHTML == name){
x+= names[i].childNodes[0].innerHTML + ";";
for(var j = (7 * i); j < (7 * i) + 7 ; j++){
switch(""+values[j].classList){
case 'cell': x += "0"; break;
case 'cell ext': x += "0"; break;
case 'cell les': x += "1"; break;
case 'cell term': x += "2"; break;
case 'cell on': x += "3"; break;
case 'cell spacer': x += "4"; break;
}
if(values[j].innerHTML != "" && values[j].innerHTML != null){
x+= ":" + values[j].innerHTML
}
x += ";";
}
}
}
return x;
}
function getResetString(){
var names = document.getElementsByClassName('name');
var x = "";
for(var i = 0; i < names.length; i++){
x += names[i].value +";";
for (var j = 0; j < 7 ; j++){
if(j == 3){
x += "4";
}else{
x += "0";
}
x += ";";
}
if(i < names.length-1){
x+="|";
}
}
return x;
}
it seems a tiny problem. on your css, you have to add an space on the calc statement:
height:calc(100% - 50px);
I was able to make it work in here
https://codesandbox.io/s/vibrant-http-ty85k
You can add display: table; to #container
and add:
#legend {
display: table-row;
}
This should work, but I think you should refactor (simplify) the whole page and styles.
So essentially I wrote a table with resizeable columns as an angular component that worked as intended as a standalone component with no external CSS.
However I moved it into a project I was making using the base template from:
https://github.com/akveo/ngx-admin
After I moved in here, the widths which were defined at ngOnInit where unevenly spread, and when I tried to move them they some not move.
As I said the table works perfectly fine on my own vanilla angular app, however when I move it to ngx-admin it messes up once data is added.
Picture (before):
Picture (after):
HTML:
<div class='container'>
<table #table>
<tr>
<th *ngFor="let column of columns; let i = index" [ngClass]="'col-header'" [attr.id]="'col-header-' + i">
<button>
<mat-icon (mousedown)="clicked($event)" class="icon">compare_arrows</mat-icon>
</button>
<div (overflow)="overflow()">
{{ column.title }}
</div>
</th>
<th id="col-header-actions" *ngIf="actionModules.length !== 0" [colSpan]="actionModules.length" class="col-header">
{{ settings.actions.columnTitle }}
</th>
</tr>
<tr *ngFor="let datum of dataShowing">
<td *ngFor="let column of columns; let i = index" [ngClass]="'col-' + i">
<div (overflow)="overflow()" [ngClass]="'col-div-' + i">
{{ datum[column.name] }}
</div>
</td>
<td *ngIf="settings.actions.edit">
<button class="action-icon">
<mat-icon (mousedown)="edit(datum)" class="icon">mode_edit</mat-icon>
</button>
</td>
<td *ngIf="settings.actions.delete">
<button class="action-icon">
<mat-icon (mousedown)="delete(datum)" class="icon">delete</mat-icon>
</button>
</td>
</tr>
<tr *ngIf="!hasData">
<td [colSpan]="columns.length + 1" >
<div id="no-data">
No Data
</div>
</td>
</tr>
</table>
</div>
CSS:
table {
font-family: arial, sans-serif;
border-collapse: collapse;
width: 100%;
}
td, th {
border: 1px solid #e2e2e2a6;
text-align: left;
padding: 10px;
position: relative;
overflow: hidden;
text-overflow: ellipsis;
}
td {
height: 1.25em;
}
tr {
padding: 5px;
&:nth-child(odd) {
background-color: #ffffff;
}
&:nth-child(even) {
background-color: #f5f5f5;
}
&:not(:nth-child(1)):hover {
background-color: #eaf5ff;
}
}
th button{
float: right;
background-color: #ffffff;
}
button{
z-index: 1;
position: relative;
display: block;
background: transparent;
border: none;
&:hover {
border: none;
}
&:action {
border: none;
}
}
th div {
position: absolute;
height: 1em;
overflow: hidden;
word-break: break-all;
float: left;
white-space: nowrap;
text-overflow: ellipsis;
transform: translateY(30%);
}
td div {
position: absolute;
height: 1em;
overflow: hidden;
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
top: 0.5em;
width: auto;
transform: translateY(30%);
}
mat-icon {
color: #b9dfff;
}
.container {
width: 100%;
height: 800px;
}
#no-data {
width: 100%;
}
.action-icon {
float: none;
margin: auto;
}
#col-header-actions {
text-align: center;
}
TS:
import { Component, OnInit, Input, ElementRef, Renderer, ViewChild, HostListener} from '#angular/core';
import { AfterViewInit, DoCheck } from '#angular/core/src/metadata/lifecycle_hooks';
#Component({
selector: 'smart-tabl',
templateUrl: './smart-tabl.component.html',
styleUrls: ['./smart-tabl.component.scss']
})
export class SmartTablComponent implements OnInit, AfterViewInit, DoCheck {
#Input() settings: any;
#Input() data: any;
columns: any;
dataShowing: any;
#ViewChild('table') table: ElementRef;
lastMouseX: number;
lastMouseY: number;
curDragX: number;
curDragY: number;
dragging: boolean;
originalColumnWidth: number;
adjacentColumnWidth: number;
adjacentColumn: any;
column: any;
hasData: boolean;
actionModules: any;
constructor(private el:ElementRef) {
}
ngOnInit() {
this. actionModules = [];
let settings = this.settings;
let columns = [];
Object.keys(settings.columns).forEach((key) => {
settings.columns[key].name = key;
columns.push(settings.columns[key]);
});
this.columns = columns;
this.hasData = (this.columns.length !== 0);
this.dataShowing = this.data;
if (!this.settings.actions.hasOwnProperty('delete')) {
this.settings.actions.delete = true;
this.actionModules.push("delete");
}
if (!this.settings.actions.hasOwnProperty('edit')) {
this.settings.actions.edit = true;
this.actionModules.push("edit");
}
}
ngAfterViewInit() {
let headers: any = document.querySelectorAll(".col-header");
for (let i = 0; i < headers.length; i++) {
if (headers[i].attributes.id.nodeValue === "col-header-actions") {
headers[i].style.width = (this.actionModules.length * 5) + "%";
} else {
headers[i].style.width = ((100 - (this.actionModules.length * 5))/(this.columns.length))+"%";
let divs: any = document.querySelectorAll(".col-div-" + i);
for (let j = 0; j < divs.length; j++) {
divs[j].style.width = ((headers[i].clientWidth)*0.9) + "px";
}
}
}
}
ngDoCheck() {
if (this.data.length === 0){
this.hasData = false;
} else {
this.hasData = true;
}
}
#HostListener('mouseup') released(event) {
this.dragging = false;
}
add(item) {
this.data.push(item);
}
clicked(event) {
this.lastMouseX = event.clientX;
this.lastMouseY = event.clientY;
this.dragging = true;
this.column = event.target.parentNode.parentNode;
this.originalColumnWidth = parseInt(this.column.style.width, 10);
let id = this.column.attributes.id.nodeValue;
let idNum = parseInt(id[id.length - 1]) + 1
let query = "col-header-" + (idNum)
if (idNum === this.columns.length) {
query = "col-header-actions";
}
this.adjacentColumn = document.getElementById(query);
this.adjacentColumnWidth = parseInt(this.adjacentColumn.style.width, 10);
}
#HostListener('mousemove', ['$event']) mouseMoved(event) {
this.curDragX = event.clientX;
this.curDragY = event.clientY;
let percentageChange = ((this.lastMouseX-this.curDragX)/parseInt(this.table.nativeElement.clientWidth, 10))*100;
if (this.dragging &&
this.originalColumnWidth - (percentageChange) > 6 &&
this.originalColumnWidth - (percentageChange) < 100 &&
this.adjacentColumnWidth + (percentageChange) > 6 &&
this.adjacentColumnWidth + (percentageChange) < 100 ) {
this.column.lastElementChild.style.width = ((this.column.clientWidth - this.column.firstElementChild.clientWidth)*0.8) + "px";
this.column.style.width = (this.originalColumnWidth - (percentageChange) + "%");
let id = this.column.attributes.id.nodeValue;
//change adjacent columns
this.adjacentColumn.style.width = (this.adjacentColumnWidth + (percentageChange) + "%");
let adjacentId = this.adjacentColumn.attributes.id.nodeValue;
let adjDivs: any = document.querySelectorAll(".col-div-" + adjacentId[adjacentId.length - 1]);
for (let i = 0; i < adjDivs.length; ++i) {
adjDivs[i].style.width = (this.adjacentColumn.clientWidth) + "px";
}
//change inner div sizing
let divs: any = document.querySelectorAll(".col-div-" + id[id.length - 1]);
for (let i = 0; i < divs.length; ++i) {
divs[i].style.width = (this.column.clientWidth) + "px";
}
}
}
filter() {
this.dataShowing = this.data;
}
edit(item) {
console.log(item);
}
delete(item) {
const index = this.data.indexOf(item);
this.data.splice(index, 1);
console.log(this.data.length);
}
}
turns out the ngx-admin already had css for col-(column number), and it was messing up mind, so i changed my naming and it worked fine.