if items overflow apply scroll dynamically - javascript

https://jsfiddle.net/Ldv10oz5/2/
How would I set div to scroll dynamically if list bigger than div?
<div style="border: 1px solid black; width: 150px; height: 75px;">
<div data-bind="foreach: {data:teams}">
<div>
<span data-bind="text: name"></span>
</div>
</div>
</div>
function Team(name) {
var self = this;
self.name = name;
}
function AppViewModel() {
var self = this;
self.teams = ko.observableArray([
new Team('red'),
new Team('blue'),
new Team('yellow'),
new Team('green'),
new Team('orange'),
]);
}
var vm = new AppViewModel();
ko.applyBindings(vm);

Try to add to parent div (this with border) css:
overflow: auto;

Related

Draggabilly: dragged elements in wrong position

i want to create a page with two areas. On the left is an "asset"-area, on the right the "content"-area. I have multiple elements in the "asset"-area. If i drag an element, it should be droppable in the right area.
I'm using the "draggabilly" library for simplicity. If i drag and drop an element, a copy of the original element should be created. This is fine. But if i drop an element, the element doesn't appear on the position of the mouse. Instead, it appears on other positions on the page.
I've got the following code:
HTML
<h2>Assets</h2>
<div id="tool-box">
<!--<img src="https://via.placeholder.com/100x100" draggable="true" ondragstart="drag(event)">-->
<!--<div id="clone-container"></div>-->
<img src="https://via.placeholder.com/100x100" class="draggable">
<div class="draggable"></div>
<div class="draggable"></div>
<div class="draggable"></div>
<div class="draggable"></div>
</div>
</div>
<!-- Main Panel -->
<div>
<h2>Editor</h2>
<div id="box-content">
<!--<div id="test1" class="content" ondrop="drop(event)" ondragover="allowDrop(event)""></div>-->
<div class=" content">b</div>
<div class="content">c</div>
<div class="content">d</div>
</div>
JS
const draggableElems = document.querySelectorAll('.draggable');
const cloneContainer = document.querySelector("#box-content");
draggableElems.forEach(element => {
const draggable = new Draggabilly(element);
draggable.on('dragStart', function() {
const clone = element.cloneNode(true);
clone.classList.add('clone');
clone.style.position = "absolute";
clone.style.left = `${element.offsetLeft}px`;
clone.style.top = `${element.offsetTop}px`;
document.body.appendChild(clone);
draggable.element = clone;
const draggableClone = new Draggabilly(clone);
});
draggable.on('dragMove', function(event) {
draggable.element.style.left = `${pointer.pageX}px`;
draggable.element.style.top = `${pointer.pageY}px`;
});
CSS
body { font-family: sans-serif; }
.draggable {
width: 140px;
height: 140px;
background: #000FFF;
border-radius: 10px;
cursor: move;
}
.draggable.is-pointer-down {
background: #09F;
}
.draggable.is-dragging { opacity: 0.7; }
I tried several code for the position, but none works as expected.
Do you have any clue, why the elements appear on the wrong position and how to fix it?
Thanks!

changing the content of a div

I would like to change the content of a div. I have three divs:
<div
class="box1"
style="height: 200px; width: 200px; background-color: red"
>
A
</div>
<br />
<div
class="box2"
style="height: 200px; width: 200px; background-color: blue"
>
<label for="">tex</label>
<input type="text" />
</div>
<br />
<div
class="box3"
style="height: 200px; width: 200px; background-color: yellow"
>
C
</div>
when the page is ready the 2 and 3rd box displays none:
function hideElementBoxOnLoad() {
let box1 = document.querySelector(".box1");
let box2 = document.querySelector(".box2");
let box3 = document.querySelector(".box3");
box2.style.display = "none";
box3.style.display = "none";
}
$(document).ready(hideElementBoxOnLoad);
I want a click that toggles the content of box2 and box3 into box1 and then back to box1 content:
function changeContent() {
let chang = true;
let box1 = document.querySelector(".box1");
let box2 = document.querySelector(".box2");
let box3 = document.querySelector(".box3");
let box2Content = box2.textContent;
let box3Content = box3.textContent;
if (chang) {
box1.textContent = box2Content;
chang = !chang;
if ((box1.textContent === box2Content)) {
box1.textContent = box3Content;
}
}
}
let btn = document.getElementById("btn");
btn.addEventListener("click", changeContent);
So far it worked but it does not display the content of box2 only box3. what did i do wrong and what better way can i toggle with a boolean.
See below
Instead of trying to swap content between each div just use JS to go through the array of them and swap an active class between them;
var boxes = document.getElementsByClassName('box');
var change = document.getElementById('change');
var counter = 0;
change.addEventListener('click', function(){
boxes[counter].classList.remove('active');
boxes[counter].nextElementSibling.classList.add('active');
counter++;
if(counter === boxes.length) {
counter = 0;
boxes[0].classList.add('active');
}
});
.box {
display: none;
width: 100px;
height: 100px;
background-color: gray;
}
.box.active {
display:block
}
<div class="box active">A</div>
<div class="box">B</div>
<div class="box">C</div>
<button id="change">Change Content</button>
im not completely sure if i understood ur question.
but below u can see and even test with the snippet button.
the button now add what ever content in in the yellow box, and whats in the input field of the blue box into the red box. listing them downwards.
if you want to replace the content completely.
just change the logic to box1.innerHTML += spacer+box3.innerHTML+spacer+input.value
this is the most simple way to do it thats easy to understand just by reading the code i think.
hope this helps!
function changeContent() {
//the button
const btn = document.getElementById("btn");
//the boxes
const box1 = document.getElementById("box1");
const box2 = document.getElementById("box2");
const box3 = document.getElementById("box3");
//a spacer
const spacer = "<br>";
//the input field
const input = document.getElementById("input");
//logic
box1.innerHTML += spacer+box3.innerHTML+spacer+input.value
}
div{
border-radius: 5px;
text-align: center;
font-family: sans-serif;
}
#box1{
min-height: 200px;
width: 200px;
padding: 5px;
background-color: rgb(255, 73, 73);
}
#box2 {
height: 200px;
width: 200px;
padding: 5px;
background-color: rgb(0, 195, 255);
}
#box3 {
height: 200px;
width: 200px;
padding: 5px;
background-color: yellow;
}
button{
padding: 3px;
margin-top: 10px;
}
<div id="box1">
<p>contetnt A</p>
</div>
<br />
<div id="box2" >
<label for="">tex</label>
<input id="input" type="text" />
<button id="btn" onclick="changeContent()">click me</button>
</div>
<br />
<div id="box3">
contetnt C
</div>
List of bugs :-
You had declared the var chang locally instead of globally, which make it true whenever you runs the function.
You are directly writing value from one tag to another, which causing the data loss, when you run your function second time.
For example :- When you click the button first time, the data is swapped, but for the second click, the data first div is lost and cannot be brought back...
Solution :- Store the data in an array in document.ready event handler and extract data from the array to update you html tags.
function hideElementBoxOnLoad() {
let box1 = document.querySelector(".box1");
let box2 = document.querySelector(".box2");
let box3 = document.querySelector(".box3");
box2.style.display = "none";
box3.style.display = "none";
content = [box1.textContent, box2.textContent, box3.textContent];
let btn = document.getElementById("btn");
btn.addEventListener("click", changeContent);
}
var content = [];
window.onload = (hideElementBoxOnLoad);
var index = 0;
function changeContent() {
let chang = true;
let box1 = document.querySelector(".box1");
/* let box2 = document.querySelector(".box2");
let box3 = document.querySelector(".box3");
let box2Content = box2.textContent;
let box3Content = box3.textContent;
if (chang) {
box1.textContent = box2Content;
chang = !chang;
if ((box1.textContent === box2Content)) {
box1.textContent = box3Content;
}
}
*/
function cycle(n, x = 0, y = content.length - 1, a = 1) {
n += a;
if (n > y) return x;
if (n < x) return y;
return n;
}
index = cycle(index);
box1.textContent = content[index];
}
<div class="box1" style="height: 200px; width: 200px; background-color: red">
A
</div>
<br />
<div class="box2" style="height: 200px; width: 200px; background-color: blue">
<label for="">tex</label>
<input type="text" />
</div>
<br />
<div class="box3" style="height: 200px; width: 200px; background-color: yellow">
C
</div>
<button id="btn"> CLICK ME </button>
Explaination
Here I first stored the tags textContent in a array content, in the starting of the code.
Then, inside the button click handler, a simple cycle function to cycle on the values stored inside the content array.

Can not push new data to knockoutjs?

I'm using knockoutjs to bind data and this is the first time I use knockoutjs, I have a list data show to table, when user scroll down to bottom, it's will be load more new data. Here is my code:
HTML:
<div data-bind="template: { name: 'product-template', foreach: listProduct }" id="data-list"></div>
<script type="text/html" id="product-template">
<table>
<tr>
<td data-bind="text:name"></td>
<td data-bind="text:description"></td>
</tr>
</table>
</script>
and JS here:
var product = {
get : function(pageNumber){
var self = this;
self.listProduct = ko.observableArray([]);
request.product(pageNumber, function(resp){
//response list data of product
//example: {"data":[{"name":"sony","desciption":"this is sony"},{"name": "toshiba","description": "this is toshiba"}]};
if(pageNumber>1){
self.listProduct.push(resp.data);
}else{
self.listProduct(resp.data);
}
})
}
}
and then I call function like this:
ko.applyBindings(new product.get(1), document.getElementById("data-list"));// it's success to bind data
and bind more data when I call scroll down to bottom event:
ko.applyBindings(new product.get(2), document.getElementById("data-list"));// I got error: Error You cannot apply bindings multiple times to the same element
There is any something wrong? thanks.
I think what you want to do is something like infinite scroll, so see the example, i think this will simplify what you doing.
var viewModel = {
items: ko.observableArray([]),
//this function always will be called when scroll event was trigered
scrolled: function(data, event) {
var elem = event.target;
if (elem.scrollTop > (elem.scrollHeight - elem.offsetHeight - 200)) {
getItems(20);
}
},
//you can use this like your page
maxId: 0
};
function getItems(cnt) {
//here you do the requst for the data
//create fake data to pass to echo service
for (var i = 0; i < cnt; i++) {
var id = viewModel.maxId++;
viewModel.items.push({
id: id,
name: "Name" + id
});
}
}
ko.applyBindings(viewModel);
getItems(20);
#main { height: 500px; width: 500px; overflow: scroll; }
#main div { background-color: #eee; margin: 5px; height: 100px; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
<span data-bind="text: items().length"></span>
</div>
<div id="main" data-bind="foreach: items, event: { scroll: scrolled }">
<div data-bind="text: name"></div>
</div>

Javascript: Duplicate div in html, replace hardcoded text within, and append to another div

How can I able to duplicate a div in html and replace a hardcoded text within the div. After its duplicated I want to append them to a another div. I have my code below however its not working for me. Please help.
HTML
<div id="studentInfo" style="border:1px solid black;">
<ul>
<li>Raven</li>
<li>James</li>
</ul>
</div>
<button id="addBtn">Add Student</button>
<div id="block" style="width: 100px; height: 100px; border:1px solid black;">
<p style="text-align: center; font-weight: bold;">Achievers</p>
</div>
Javascript
$('#addBtn').click(function(){
var firstname = 'Stack';
var lastname = 'overflow';
var studentDiv = $('#studentInfo').clone(true);
var children = studentDiv.children('ul li');
studentDiv.find(children).val(firstname);
studentDiv.find(children).val(lastname);
$('#block').append(studentDiv).html();
});
1st With li use .text() not .val()
2nd Use :nth-child() selector to determine the selected li you need
try this code
$('#addBtn').click(function(){
var firstname = 'Stack';
var lastname = 'overflow';
var studentDiv = $('#studentInfo').clone(true);
studentDiv.find('ul > li:nth-child(1)').text(firstname);
studentDiv.find('ul > li:nth-child(2)').text(lastname);
$('#block').append(studentDiv);
});
Working Demo
$('#addBtn').click(function() {
var firstname = 'Stack';
var lastname = 'overflow';
var studentDiv = $('#studentInfo').clone(true);
var children = studentDiv.find('ul li');
children.eq(0).html(firstname);
children.eq(1).html(lastname);
$('#block').append(studentDiv);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="studentInfo" style="border:1px solid black;">
<ul>
<li>Raven</li>
<li>James</li>
</ul>
</div>
<button id="addBtn">Add Student</button>
<div id="block" style="width: 100px; height: 100px; border:1px solid black;">
<p style="text-align: center; font-weight: bold;">Achievers</p>
</div>

Knockoutjs: Invoking function of parent component from child component

Problem:
I'm trying to build a dashboard of widgets. Each widget will have a delete button on its header. When clicked on this button, corresponding widget have to disappear.
How I designed:
I have two knockout components.
my-widget-list:
VO will have an observableArray of widget objects.
my-widget:
VO will have details to display within the widget.
Note: For simplicity, I'm replacing the widget object with just numbers.
ko.components.register('my-widget-list', {
viewModel : function(params) {
var self = this;
self.values = ko.observableArray([10,20,30,40,50]);
self.deleteWidget = function(obj)
{
self.values.remove(obj);
}
},
template: {element: 'my-widget-list-template'}
});
ko.components.register('my-widget', {
viewModel : function(params) {
var self = this;
self.value = params.value;
},
template: {element: 'my-widget-template'}
});
ko.applyBindings({});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<my-widget-list></my-widget-list>
<script id="my-widget-list-template" type="text/html">
<div data-bind="foreach:values">
<my-widget params="value: $data"></my-widget><br>
</div>
</script>
<script id="my-widget-template" type="text/html">
<span data-bind="text: value"></span>
<button data-bind="click: $parent.deleteWidget">Delete</button>
</script>
Now, I want to invoke my-widget-list's deleteWidget function when the button is clicked.
I have thought about
Passing the parent view model reference into the child
Passing the parent function in the params attribute of the child component as a callback
But I wish to know from experts what's the best way to achieve this.
JsFiddle Link
Thanks in advance
You can pass in the parent as a param to the child:
ko.components.register('my-widget-list', {
viewModel : function(params) {
var self = this;
self.values = ko.observableArray([10,20,30,40,50]);
self.deleteWidget = function(obj) {
self.values.remove(obj);
}
},
template: {element: 'my-widget-list-template'}
});
ko.components.register('my-widget', {
viewModel : function(params) {
var self = this;
self.value = params.value;
self.remove = function () {
params.parent.deleteWidget(self.value);
};
},
template: {element: 'my-widget-template'}
});
ko.applyBindings({});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<my-widget-list></my-widget-list>
<script id="my-widget-list-template" type="text/html">
<div data-bind="foreach:values">
<my-widget params="value: $data, parent: $parent"></my-widget><br>
</div>
</script>
<script id="my-widget-template" type="text/html">
<span data-bind="text: value"></span>
<button data-bind="click: remove">Delete</button>
</script>
But I'm not sure if that is a good idea, as it needlessly couples the child to the parent.
I'd recommend implementing the "remove" button in the parent, i.e. in <my-widget-list>, this way the widget can exist without a widget-list (or in a differently structured one) while the widget-list is in control of its children.
Compare window managers: They work the same way. The window manager draws the frame and the minimize/maximize/close buttons, while the window contents is drawn by the respective child process. That logic makes sense in your scenario as well.
Alternative implementation with removeWidget control in the parent:
ko.components.register('my-widget-list', {
viewModel : function(params) {
var self = this;
self.values = ko.observableArray([10,20,30,40,50]);
self.deleteWidget = function(obj) {
self.values.remove(obj);
}
},
template: {element: 'my-widget-list-template'}
});
ko.components.register('my-widget', {
viewModel : function(params) {
var self = this;
self.value = params.value;
},
template: {element: 'my-widget-template'}
});
ko.applyBindings({});
.widget-container {
position: relative;
display: inline-block;
padding: 10px 5px 5px 5px;
margin: 0 5px 5px 0;
border: 1px solid silver;
border-radius: 2px;
min-width: 40px;
}
.widget-buttons {
position: absolute;
top: 2px;
right: 2px;
}
.widget-buttons > button {
font-size: 2px;
padding: 0;
height: 15px;
width: 15px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<my-widget-list></my-widget-list>
<script id="my-widget-list-template" type="text/html">
<div class="widget-list" data-bind="foreach:values">
<div class="widget-container">
<div class="widget-buttons">
<button data-bind="click: $parent.deleteWidget">X</button>
</div>
<my-widget params="value: $data"></my-widget>
</div>
</div>
</script>
<script id="my-widget-template" type="text/html">
<div class="widget">
<span data-bind="text: value"></span>
</div>
</script>

Categories