I am having problem with drag and drop, the problem is that the array automatically rearranges when i move the element but not swap.
after I drag the first element to an empty cell, the array fills the first element by itself, not standing still like my butt.
Here is the picture description :
I tried the :swap="true" attribute but it didn't work.
Here is my code:
setDataLayout() {
for (var i = 1; i <= 100; i++) {
const item = { idLayout: i, name: "" };
this.listLayout.push(item);
}
},
<draggable
v-model="listLayout"
:swap="true"
group="student"
class="layout-center-container-line"
>
<div
v-for="(element, index) in listLayout"
:key="index"
:class="
element.name != ''
? 'layout-center-container-box-item'
: 'layout-center-container-box-shadown'
"
>
{{ element.name }}
</div>
</draggable>
thanks for the help !
Related
I am making a to do list. Each list item includes a remove button when created.
But I cannot access these remove buttons in my script, because it is not included in my DOM, although I used '.appendChild'. Can anyone help?
const buttonSubmit = document.querySelector('#button-submit');
const form = document.querySelector('form');
const icons = document.querySelector('#icons');
let toDoList = document.querySelector('#todolist');
const input = document.querySelector('#formtext');
form.addEventListener('submit', (e) => {
e.preventDefault();
const newListItem = document.createElement('li');
newListItem.innerHTML = '<span>' + input.value + '</span>' +
'<span id="icons">' +
'<button id="check" class="buttonlist">' + '<img src="checked.png" alt="">' + '</button>' +
'<button id="remove" class="buttonlist">' + '<img src="remove.png" alt="">' + '</button>' +
'<button id="edit" class="buttonlist">' + '<img src="edit.png" alt="">' + '</button>' + '</span>';
toDoList.appendChild(newListItem);
form.reset();
})
const buttonCheck = document.querySelector('#check');
const buttonEdit = document.querySelector('#edit');
const buttonRemove = document.getElementById('remove');
buttonRemove.addEventListener('click', function() {
alert('remove list item');
})
<div class="container-box">
<h1>To Do List</h1>
<br>
<form action="">
<input type="text" id="formtext" name="formtext">
<button id="button-submit">Add Item</button>
</form>
<br><br><br><br>
<!-- Dynamic list here -->
<ul id="todolist"> </ul>
</div>
Problems
ids must be unique, every time you add a task to the list -- after the first one everything is invalid HTML. When directed to an id the browser will find the first id then stop and ignore the duplicate ids. Use class and/or name attributes for any replicated tags.
The reason why the remove button doesn't work is because the reference to the button was defined when it didn't exist.
Figure I
// After page is loaded...
const buttonRemove = document.getElementById('remove');
// Console will tell you buttonRemove is null
// User has not entered any data nor has user clicked the add button
Moreover, even if that was fixed by referencing the button after it was created, binding it as shown on Figure II will only work for the first button only.
Figure II
buttonRemove.addEventListener('click', function() {
alert('remove list item'); // Don't use alert() use console.log()
})
Solution
Reference tags after they are created. In the OP (Original Post), the contents of the <li> is rendered htmlString which makes referencing newly created tags problematic plus binding to dynamically created tags individually should be avoided if it's feasible and practical in which in most cases it is.
To handle events for an unknown amount of dynamically created tags, bind the event to a static ancestor tag, which in the OP is <ul> or any of it's parent tags (even <body>, document, and window but it's best to be as close as possible). Then make it so the event handler controls which tags respond and how. See Appendix located at the very end of this answer for more details.
There are two examples:
Example A - revised OP code
Example B - a todo list using HTMLFormElement interface, see Appendix
Both examples have commented step-by-step details
Example A
// Reference <form>, <ul>, and <input>
const form = document.querySelector('form');
const list = document.querySelector('ul');
const input = document.querySelector('#text');
// Bind <form> to submit event
form.addEventListener('submit', (e) => {
// Stop default behavior of <form> during submit ecent
e.preventDefault();
// Create <li> and <output>
const item = document.createElement('li');
const out = document.createElement('output');
// Assign value of <input> to the value of <output>
out.value = input.value;
// Add <output> to <li> -- <li> to <ul>
item.append(out);
list.append(item);
/*
Run a for loop 3 times -- on each iteration...
...create an <input> and assign type as "button"...
...buttons [name] and [value] is determined by current index...
...add button to <li>
*/
for (let i = 0; i < 3; i++) {
let btn = document.createElement('input');
btn.type = 'button';
let cmd = i === 0 ? 'done' : i === 1 ? 'edit' : i === 2 ? 'remove' : false;
btn.name = cmd;
btn.value = cmd;
item.append(btn);
}
// Reset <form>
form.reset();
});
// Bind <ul> to click event
list.addEventListener('click', manageList);
// Event handler always passes event object by default
function manageList(e) {
// Reference the tag user clicked
const clk = e.target;
// If user clicked a remove button remove it's parent tag
if (clk.name === 'remove') {
clk.parentElement.remove();
}
if (clk.name === 'edit') {
console.log('EDIT');
}
if (clk.name === 'done') {
console.log('DONE');
}
}
li {
display: flex;
align-items: center
}
[type='button'] {
text-transform: capitalize
}
<form>
<input id="text" name="text" type="text">
<button>Add Item</button>
</form>
<br>
<ul></ul>
Example B
// Bind <form> to click event
document.forms.todo.onclick = taskList;
// Event handler akways passes the event object
function taskList(e) {
// Reference the tag user clicked
const clk = e.target;
// Reference all form controls
const IO = this.elements;
/*
If the user clicked the add button...
...reference the <ul>...
...create <li> and <output>...
...add text from <input> to <output>...
...add <output> to <li>...
...Run a for loop 3 times -- on each iteration...
...create an <button> and assign type as "button"...
...buttons [name] and text is determined by current index...
...add button to <li>...
...add <li> to <ul>...
...clear <input>
*/
if (clk.name == 'add') {
const list = IO.list.firstElementChild;
const item = document.createElement('li');
const text = document.createElement('output');
text.value = IO.data.value;
item.append(text);
for (let i = 0; i < 3; i++) {
let btn = document.createElement('button');
btn.type = 'button';
let cmd = i === 0 ? 'done' : i === 1 ? 'edit' : i === 2 ? 'remove' : false;
btn.name = cmd;
btn.textContent = cmd;
item.append(btn);
}
list.append(item);
IO.data.value = '';
}
/*
If the user clicked a remove button...
...find the <li> ancestor of remove button and remove
it thereby removing the <output> and itself as well
*/
if (clk.name === 'remove') {
clk.closest('li').remove();
}
if (clk.name === 'done') {
console.log('DONE');
}
if (clk.name === 'edit') {
console.log('EDIT');
}
}
<form id='todo'>
<input id='data' required><button name='add' type='button'>Add</button>
<fieldset id='list'>
<ul></ul>
</fieldset>
</form>
Appendix
Events
Event delegation
HTMLFormElement
HTMLFormControlsCollection
Form Controls
I have used svelte-infinite-loading, and it worked fine at first,
but as the list got very long, my web app started using substantial amounts of memory, using as much as 2gb.
So, I needed to virtualize this infinite list.
I used svelte-tiny-virtual-list as recommended by svelte-infinite-loading's author:
<script>
....
function onInfinite({ detail }) {
const skip = items !== undefined ? items.length : 0;
fetchItems(skip).then((data) => {
if (data.length === 0) {
items = [];
detail.complete();
return;
}
if (items === undefined) items = data;
else items = [...items, ...data];
detail.loaded();
});
}
onMount(() => {
fetchItems(0).then((data) => {
Items = data;
});
});
</script>
{#if items !== undefined}
{#if items.length === 0}
<p><i>No items found</i></p>
{:else}
<VirtualList
itemCount={items.length}
itemSize={200}
height="100%">
<div slot="item" let:index>
<Item
item={items[index]} />
</div>
<div slot="footer">
<InfiniteLoading on:infinite={onInfinite} />
</div>
</VirtualList>
{/if}
{/if}
The problem comes when the page loads:
The first few items are fetched and displayed correctly, but then the page grows to abnormal lengths, then the list disappears and I get the following error:
InfiniteLoading.svelte:103 executed the callback function more than 10 times for a short time, it looks like searched a wrong scroll wrapper that doest not has fixed height or maximum height, please check it.
What have I done wrong?
A VirtualList creates items until the height of the list exceed the height of the parent. It then fakes a scrollbar to select which items it should render.
Apparently, you have placed the VirtualList in a container without height/max-height and it can't determine how many items it should create.
You have to set a max-height or a height on the parent element.
I have a list which is sortable with drag and drop. And it works
https://codepen.io/destroy90210/pen/rGEodB
Now I wanted to include the feature to sort the list by name, date and position.
So if i see the list sorted by name or date i block the drag an drop functionality. Only if position is selected, from the dropdown, the items are dragable, but now my drag and drop doesn't work any more. The items jump back to the old position...
https://codepen.io/destroy90210/pen/yzdQxK
<div id="main">
<select class="dd" v-model="orderBy" #change="sortedData">
<option value='created'>created</option>
<option value='abc'>Abc</option>
<option value='position'>Position</option>
</select>
<draggable :list="data" class="dragArea" #change="changeOrder" :options="{draggable:'.card--dragable'}">
<div :class="{'card--dragable': isDragable}" class="card" v-for="item in sortedData"><span class="card__label">{{item.name}}</span></div>
</draggable>
</div>
new Vue({
el: "#main",
data: {
data: data,
orderBy: 'position',
isDragable: true,
},
computed:{
sortedData(){
this.isDragable = false;
if (this.orderBy === 'abc') {
return this.data.sort((a, b) => { return a.name.localeCompare(b.name); });
} else if (this.orderBy === 'created') {
return this.data.sort((a, b) => { return a.id > b.id; });
}
this.isDragable = true;
return this.data.sort((a, b) => { return a.position > b.position; });
},
},
methods:{
changeOrder(e){
const oldIndex = e.moved.oldIndex;
const newIndex = e.moved.newIndex;
let i = Math.min(oldIndex, newIndex);
const max = Math.max(oldIndex, newIndex) + 1;
for (i; i < max; i += 1) {
this.data[i].position = i;
}
}
}
});
Fix: https://codepen.io/destroy90210/pen/jGgNBN
sortedData() is a computed element in your codepen, this means it will execute when its dependencies update (in your case when changeOrder() gets executed on a drag/drop action).
Use a method instead so it only executes when your select is updated by the following change event:
<select class="dd" v-model="orderBy" #change="sortedData">
This means we can fix the issue by moving sortedData() from computed to methods.
Now it wont update on a drop anymore.
Documentation about computed vs methods.
I have been changing a form based on REACT and this is something I am a newb with (been using it already for 4 months but just segments of it, sometimes actual progress with the programming is based on pure luck and every time on advices of good people found here).
Currently I have a task of re-developing a form of this look:
What I need to achieve is Calibration radios' behavior based on Type's selection: if argument calibration is set to 0 (zero) then disable option 'Accredited' and check second option automatically.
Edited: 19 Oct 2017
This creates the drop down, and the DD works great:
createSuggestInput(name) {
const { id, value, labels } = this.props;
const _t = this.props.intl.formatMessage;
var options = [
{ value: 'one', label: 'One', calibration: '0' },
{ value: 'two', label: 'Two ', calibration: '1' },
{ value: 'three', label: 'Three', calibration: '0' },
{ value: 'four', label: 'Four', calibration: '1' },
];
return <Select.Creatable
name = {`${id}_${name}`}
value = {this.state.brandSelect}
placeholder = {_t(translations.txtSuggest)}
options = {options}
onChange = {this._onChange.bind(this)}
label = {labels[name]}
key = {`${id}_${name}`}
promptTextCreator = { (label) => _t(translations.txtCreate) + ' ' + label + _t(translations.txtCreateEnter) }
/>;
}
When selected option's calibration value is ZERO, I need to update set of Calibration radio buttons, by disabling the option "Accredited" and at the same time checking the second option, "Not Accredited".
createRadioCalibration(name) {
const { id, value, labels } = this.props;
const _t = this.props.intl.formatMessage;
const ACCREDITATION_TYPES = [
[CALIBRATION_ACCREDITED, _t(messages.calibrationAccredited)],
[CALIBRATION_NOT_ACCREDITED, _t(messages.calibrationNotAccredited)]
];
return <FormChoiceGroup
type = "radio"
values = {ACCREDITATION_TYPES.map(mapValueArray)}
key = {`${id}_${name}`}
name = {`${id}_${name}`}
value = {value[name]}
handleChange = {this.handleFieldChangeFn(name)}
/>;
}
These two are rendered as follows:
render () {
const FIELDS = {
[CALIBRATION]: this.createRadioCalibration(CALIBRATION),
[TYPE]: this.createSuggestInput(TYPE),
};
return (
<div className="repair-form-device repair-form-device-field-row">
<div className="repair-form-device-id">
{id + 1}
</div>
<div className="clearfix repair-form-device-content">
<div className="">
{ FIELDS[TYPE] }
</div>
<div className="">
<label>{_t(messages.repair)}</label>
{ FIELDS[CALIBRATION] }
</div>
.....
And lastly the _onChange function:
_onChange(tool) {
const { id } = this.props;
this.setState({
brandSelect: tool
});
}
As I stated previously, I am stuck with the main task, which is manipulating the Calibration radio buttons.
I believe I can update its status inside the _onChange function, but everything I tested so far lead me nowhere.
Your patience is much appreciated!
I have this ionic tag already populated and with all items unchecked:
<ion-checkbox ng-repeat="categoria in listaCategorias"
ng-model="categoria.checked"
ng-checked="categoria.checked"
ng-change="recuperarServicos(categoria)">
{{ categoria.nmCategoria }}
</ion-checkbox>
And here my controller code that has a list of 'categoria ids':
//here I have the ids recovered from database that I split into an array of ids
var idsCategoria = $scope.meuanuncio.idsCategoria.trim().split(',');
if($scope.listaCategorias.length > 0)
{
//for each item in my listaCategorias (used in ng-repeat)
for (var i = 0; i < $scope.listaCategorias.length; i++) {
var item = $scope.listaCategorias[i];
//I compare id from each item with my list recovered from database
if(idsCategoria.indexOf($scope.listaCategorias[i].idCategoria) != -1)
{
//If the item id exist in database list, I check the item
item.checked = true;
// Below there are other ways that I tried to use
// $scope.listaCategorias[i].Selected = true;
// $scope.listaCategorias[i].checked = true;
$scope.listaCategorias[0].checked = true;
}
}
};
But I canĀ“t do my ion-checkbox item checked.
What am I doing wrong ?
Thanks.
ng-model="categoria.checked"
looks fine, don't think you need the ng-checked though.
var item = $scope.listaCategorias[i];
item.checked = true;
Nope, the item gets lost through the loop. I see you were trying with:
$scope.listaCategorias[i].checked = true;
Did you get an error or something? Because this looks like the way to do it.
Maybe try looping on a div around the ion-checkbox? aka
<div ng-repeat="categoria in listaCategorias">
<ion-checkbox ng-model="categoria.checked"
ng-change="recuperarServicos(categoria)">
{{ categoria.nmCategoria }}
</ion-checkbox>
</div>
try this :
<div ng-repeat="categoria in listaCategorias track by $index">
<ion-item class="item item-checkbox">
<label class="checkbox">
<input type="checkbox" ng-model="categoria.checked" ng-change="recuperarServicos(categoria)">
</label>
{{categoria.nmCategoria}}
</ion-item>
</div>
Controller:
$scope.recuperarServicos = function(categoria){
if(categoria.selected && ($scope.selectedItems.indexOf(categoria.name) < 0)){
$scope.selectedItems.push(categoria.name);
}else{
$scope.selectedItems.splice($scope.selectedItems.indexOf(categoria.name), 1);
}
};
hope this helps you..in someway..!
My problem was when I attribute my array of items to the $scope.listaCategorias.
I was doing that:
$scope.listaCategorias = listaCategorias;
But I need to do that:
$scope.listaCategorias.push.apply($scope.listaCategorias, listaCategorias);
I was building an array with the checked attribute inside, but when I associate my built list, I was associating the first one, which has not the checked attribute setted.
Let me show my code now.
My view :
<div ng-repeat="item in listaCategorias track by $index">
<ion-item class="item item-checkbox">
<label class="checkbox">
<input type="checkbox" ng-model="item.checked" ng-checked="item.checked" ng-change="recuperarServicos(item)">
</label>
{{ item.nmCategoria }}
</ion-item>
</div>
My controller:
//here I get all my 'categorias' from datatable
listaCategorias = appFactory.recuperarCategorias();
//If list is not null go ahead
if(listaCategorias != null) {
//split into an array all my 'categoria' ids
var idsCategoria = $scope.meuanuncio.idsCategoria.split(',');
//if list has items go ahead
if(listaCategorias.length > 0) {
for (var i = 0; i < listaCategorias.length; i++) {
//if 'categoria' id exists in datatable list set true, else false
if(idsCategoria.indexOf(listaCategorias[i].idCategoria) != -1) {
listaCategorias[i].checked = true;
}
else {
listaCategorias[i].checked = false;
}
}
};
//Here is the point !!! I need to load my $scope variable this way to build all my items correctly
$scope.listaCategorias.push.apply($scope.listaCategorias, listaCategorias);
}