By default a dojo.dnd.Source container allows you to hold Ctrl to duplicate/copy a dragged item rather than just move it.
I know you can set singular=true to stop multiple items being dragged but how do I stop copying? Duplicating items makes no sense in the context of my items (I am making a draggable list for reordering pages on a website menu).
Thanks
I'm unsure if there's a nicer way, but I've always accomplished this by clobbering the copyState method on the Source instance to always return false.
If you've got several Sources on the page, you could also elect to dojo.declare a subclass with the method overridden, or dojo.extend dojo.dnd.Source itself to clobber the method in all instances.
Or second option
dojo.addOnLoad(function(){
//Disable the key events Ctrl and Shift
dojo.extend( dojo.dnd.Source, { copyState: function( keyPressed, self ){
return false; }}
);
//Create the dnd source object for data point column bar
columnBar = new dojo.dnd.Source("viewColumnBar",{ singular: true });
});
Thanks to Ken Franquiero, I managed to solve this problem. For others in the same boat, here's my code:
/**
* Extend dojo.dnd.Source to prevent copying
*/
dojo.require( 'dojo.dnd.Source' );
dojo.addOnLoad( function() {
dojo.declare
(
'EditPosition',
dojo.dnd.Source,
{
copyState: function( keyPressed, self )
{
return false;
}
}
);
oEditPosition = new EditPosition
(
'position_container',
{
withHandles: 'true'
}
);
} );
HTML:
<div id="position_container">
<div class="dojoDndItem">
<div class="dojoDndHandle drag_icon drag_handle"></div> <strong>Short Paragraphs</strong>
</div>
<div class="dojoDndItem">
<div class="drag_icon fixed_handle"></div> About Us
</div>
<div class="dojoDndItem">
<div class="drag_icon fixed_handle"></div> Team Members
</div>
</div>
Related
So from the backend I get a array of objects that look kind of like this
ItemsToAdd
{
Page: MemberPage
Feature: Search
Text: "Something to explain said feature"
}
So i match these values to enums in the frontend and then on for example the memberpage i do this check
private get itemsForPageFeatures(): ItemsToAdd[] {
return this.items.filter(
(f) =>
f.page== Pages.MemberPage &&
f.feature != null
);
}
What we get from the backend will change a lot over time and is only the same for weeks at most. So I would like to avoid to have to add the components in the template as it will become dead code fast and will become a huge thing to have to just go around and delete dead code. So preferably i would like to add it using a function and then for example for the search feature i would have a ref on the parent like
<SearchBox :ref="Features.Search" />
and in code just add elements where the ItemsToAdd objects Feature property match the ref
is this possible in Vue? things like appendChild and so on doesn't work in Vue but that is the closest thing i can think of to kind of what I want. This function would basically just loop through the itemsForPageFeatures and add the features belonging to the page it is run on.
For another example how the template looks
<template>
<div class="container-fluid mt-3">
<div
class="d-flex flex-row justify-content-between flex-wrap align-items-center"
>
<div class="d-align-self-end">
<SearchBox :ref="Features.Search" />
</div>
</div>
<MessagesFilter
:ref="Features.MessagesFilter"
/>
<DataChart
:ref="Features.DataChart"
/>
So say we got an answer from backend where it contains an object that has a feature property DataChart and another one with Search so now i would want components to be added under the DataChart component and the SearchBox component but not the messagesFilter one as we didnt get that from the backend. But then next week we change in backend so we no longer want to display the Search feature component under searchbox. so we only get the object with DataChart so then it should only render the DataChart one. So the solution would have to work without having to make changes to the frontend everytime we change what we want to display as the backend will only be database configs that dont require releases.
Closest i can come up with is this function that does not work for Vue as appendChild doesnt work there but to help with kind of what i imagine. So the component to be generated is known and will always be the same type of component. It is where it is to be placed that is the dynamic part.
private showTextBoxes() {
this.itemsForPageFeatures.forEach((element) => {
let el = this.$createElement(NewMinorFeatureTextBox, {
props: {
item: element,
},
});
var ref = `${element.feature}`
this.$refs.ref.appendChild(el);
});
}
You can use dynamic components for it. use it like this:
<component v-for="item in itemsForPageFeatures" :is="getComponent(item.Feature)" :key="item.Feature"/>
also inside your script:
export default {
data() {
return {
items: [
{
Page: "MemberPage",
Feature: "Search",
Text: "Something to explain said feature"
}
]
};
},
computed: {
itemsForPageFeatures() {
return this.items.filter(
f =>
f.Page === "MemberPage" &&
f.Feature != null
);
}
},
methods: {
getComponent(feature) {
switch (feature) {
case "Search":
return "search-box";
default:
return "";
}
}
}
};
I have a property on my class:
class Control {
#bindable households;
get people() {
return households
.map(household => househould.people)
.reduce((g1, g2) => g1.concat(g2), []);
}
}
Which I use to compute a collection of all people[] within all households which is then rendered here:
<ul>
<li repeat.for="person of people">
${person.firstName} ${person.lastName} - ${person.phone}
</li>
</ul>
I need the list to update whenever people are added to a household, OR if any of the rendered properties, firstName, lastName, phone, for any element in the computed collection is updated. How can I do this in Aurelia? If I use #computedFrom() it will not detect changes to elements of the array, and since the list of people in all households is dynamic, I cannot just create an observer for each element without creating a system for managing when observers should be subscribed / unsubscribed.
Use Dirty Checking
Leave off #computedFrom() and you'll achieve the desired behavior.
export class App {
#bindable households;
get people() {
const households = this.households || []; // Make sure househoulds is defined.
return households.reduce((people, household) => people.concat(household.people), []);
}
}
https://gist.run/?id=040775f06aba5e955afd362ee60863aa
Right as I was about to give up on being able to Google for a solution, Aurelia Signaling came to the rescue. This code ended up working for me:
<ul>
<li repeat.for="person of people">
<!-- May work without this rendering method,
this is just closer to what my actual code is doing. -->
${renderPersonInfo(person) & signal: 'example-signal'}
</li>
</ul>
And the class:
import {BindingSignaler} from 'aurelia-templating-resources';
#inject(BindingSignaler)
class Control {
#bindable households;
constructor(bindingSignaler) {
this.bindingSignaler = bindingSignaler;
//Obviously, you can have this trigger off any event
setInterval(() => this.bindingSignaler.signal('example-signal'), 1000);
}
get people() {
return households
.map(household => househould.people)
.reduce((g1, g2) => g1.concat(g2), []);
}
}
You must avoid dirty-checking as far as possible, signals are the perfect option for your scenario. Just bear in mind that if you want to use computedFrom on an array you can do so by watching its length property for instance rather than dirtyChecking, sth like the following #computedFrom("myArray.length")
I am using materialize chips in a typical "Publish Post" form where i use the chips for the tags field. All good.
Now, when i do the same in the almost identical "Edit Post" screen there is a difference, i call from the database the tags the post already has and i insert them in the element where the chips are inserted.
I solved already a couple of that idea's problems but when i want to delete a chip clicking the close icon it doesn't work, either if it's a pre-existing tag or it is a newly generated tag.
This behavior doesn't happen if I don't populate the chip's container with pre-existing tag/chips. So I have 2 options:
I keep trying to make the close icon work populating by myself the chip's container or
I use the addChip method, so probably if I let the plugin generate those pre-existing tag/chips all is going to be fixed.
My problem with the second option is I have no idea how could I make that method work in my code.
Option 1
<div id="chips1" class="chips chips-placeholder input-field">
#foreach($tags as $tag)
<div class="chip" data-tag="{{$tag}}" tabindex="0">{{$tag}}
<i class="material-icons close">close</i>
</div>
#endforeach
</div>
Option 2 (without the foreach populating the div)
<div id="chips1" class="chips chips-placeholder input-field">
</div>
and the JS
/* I initialize the chips with a onChipAdd method
and onDelete method to keep the variable updated */
var val = [];
$('#chips1').chips({
placeholder: 'Etiquetas...',
secondaryPlaceholder: '+Etiqueta',
onChipAdd: function () {
val = this.chipsData;
console.log(val);
},
onChipDelete: function() {
val = this.chipsData;
console.log(val);
}
});
/* ***************************************** */
/* Here populate the hidden input that is going to deliver the
data with the pre-existing chips of Option 1 */
var chipsArr = [];
var chipis = $('#chips1 div.chip');
for (let index = 0; index < chipis.length; index++) {
chipsArr.push($(chipis[index]).data('tag'));
}
$('#chips-values').val(JSON.stringify(chipsArr));
/* ***************************************** */
/* Here i push to the array the newly added tags with the val variable */
$('#publishSubmit').on('click', function (e) {
e.preventDefault();
for (let index = 0; index < val.length; index++) {
chipsArr.push(val[index].tag);
}
$('#chips-values').val(JSON.stringify(chipsArr));
console.log($('#chips-values').val());
$('form#postPublish').submit();
});
I know your question is already a while ago, but maybe it will help you nevertheless. I come across the exact same problem and solved it as follows:
I created the chip container as follows:
<div id="chips" class="chips-placeholder"></div>
<div id="chips_inputcontainer"></div>
Then I created hidden inputs foreach existing chip in the database inside of the "chips_inputcontainer".
For example like this:
<div id="chips_inputcontainer">
<?php foreach($chips as $chip): ?>
<input type='hidden' name='chip[previous][<?php echo $chip['id'] ?>]' value='<?php echo $chip['text'] ?>'>
<?php endforeach; ?>
</div>
At the end, I initalized the chip input with the following JavaScript Snipped:
<script>
$('#chips').chips({
placeholder: 'Enter a tag',
secondaryPlaceholder: '+Tag',
onChipAdd: function(e, chip){
$("#chips_inputcontainer").append("<input type='hidden' name='chip[new][]' value='" + chip.innerHTML.substr(0, chip.innerHTML.indexOf("<i")) + "'>");
},
onChipDelete: function(e, chip){
$('#chips_inputcontainer input[value="' + chip.innerHTML.substr(0, chip.innerHTML.indexOf("<i")) + '"]').val('**deleted**');
},
data: [
<?php foreach($chips as $chip): ?>
{tag: '<?php echo $chip['text'] ?>'},
<?php endforeach; ?>
],
});
</script>
This snippet creates every time when a new chip is added a hidden input with the necessary data.
And every time, when a chip is deleted, the value of the hidden input field is set to **deleted**
And so I know:
which tags are new to the database
which ones are existing ones
which id do the existing ones have in the database
which ones are deleted
I hope, this will help you.
I have been working on a website that has to function on both desktop and tablets. Part of the website is having three columns and being able to drag orders from column to column. Sometimes on drop, the user has to answer a few questions or change some of the data of that specific order. This happens in a pop-up window that is triggered by an #drop function (for example #drop="approved()". The method approved() then checks the status of the dropped order and shows the pop-up window).
When I am on desktop, everything works just fine. But when I switch to iPad Pro in the developer tools, nothing happens. I implemented Vue Draggable, which says to work with touch devices. In their examples I can't find anything about touch events or adding new handles for touch, so I don't know what to do now.
The dragging works just fine with touch devices, it's just the #drop function that doesn't trigger.
The dropzone (it includes a component that contains the draggables and a lot of if-statements):
<div class="col-md-4 border" #dragover.prevent #drop="approved()">
<Wachtrij class="fullHeight" :data2="opdrachtenData2"></Wachtrij>
</div>
The method:
export default {
methods: {
...
approved() {
console.log("Function approved() is being executed.")
if (this.draggingOrder.status === 5) {
this.popupGekeurd = true;
}
else if (this.draggingOrder.status === 6) {
this.popupTochGoed = true;
}
else if ([40, 52, 42,41,49,55,54].indexOf(this.draggingOrder.status) !== -1) {
this.back = true;
}
},
...
}
}
The problem seems to be that you are using native events, while the touch implementation does not (always?) use these events. It is intended that you use a draggable component with one of the events outlined in the documentation. In your case the start and end events look promising. This event has a few properties (docs), some of them being to and from.
Let's assume that we have the following code:
<draggable v-for="(zone, index) in zones" v-model="zones[index]" :class="['dropzone', `zone-${index}`]" :key="`dropzone-${index}`" :options="options" #start="start" #end="end">
<div v-for="item in zones[index]" class="dropitem" :key="`dropitem-${item.id}`">
{{ item.title }}
</div>
</draggable>
This creates a few zones, each filled with their own items. Each array item of zones is changed based on where you move each item. You can then use start to have information on when you start moving an item, and end to have information on when you stop moving an item, and where that item came from and where it ended up. The following methods show off what you can do with that in this case:
methods: {
start (event) {
console.log('start', event);
},
end (event) {
console.log('end', event);
const { from, to } = event;
if (to.className.match(/\bzone-2\b/)) {
console.log('Zone 2 has something added!')
}
if (from.className.match(/\bzone-0\b/)) {
console.log('Zone 0 had something removed!');
}
}
}
We make our dropzones with a class zone-0, zone-1 or zone-2 in this case, so we can use the class name to determine which dropzone we ended up in.
An alternative way to determine which zone was changed is to simply use a watcher. Since zones changes based on where you move items, you can simply watch a particular dropzone for changes and do things based on that.
watch: {
'zones.1': {
handler (oldZone, newZone) {
if (Array.isArray(oldZone) && Array.isArray(newZone) && oldZone.length !== newZone.length) {
console.log('Zone 1 was changed from', oldZone, 'to', newZone);
}
}
}
}
A full example can be found on codesandbox.
I use the control MicrosoftNSJS.Advertising.AdControl in the ItemTemplate of a ListView.
I would like to bind some datas to the following data-win-options properties : ApplicationId and AdUnitId
The source datas are correctly set and are visible in my item template, I can display them with an h2 + a classic data-win-bind on innerText property
Ads are displayed correctly if I put directly static IDs in html code but these IDs need to be loaded from a config file...
Is it possible ? Thanks
If it's not possible, can I modify directly the item template in the JS code before to be injected in the listview ?
Come to find out this is possible (I was trying to do something similar)
The syntax for the control properties must be prefixed with winControl.
Example (I'm setting the application id here but binding the html element's className and the ad control's adUnitId)
<div id="adItemTemplate" data-win-control="WinJS.Binding.Template">
<div data-win-bind="className:css; winControl.adUnitId: adUnitId"
data-win-control="MicrosoftNSJS.Advertising.AdControl"
data-win-options="{ applicationId: 'd25517cb-12d4-4699-8bdc-52040c712cab'}">
</div>
</div>
I finally found a way to perform this without real binding, by using the itemTemplateSelector function like this :
function itemTemplateSelector(itemPromise)
{
return itemPromise.then(function (item)
{
if (item.type == "ad")
{
var template = _$(".adTemplate").winControl.render(item, null);
// Access to the AdControl through the DOM
var adControl = template._value.childNodes[1].childNodes[1].winControl;
// Set options that are specified in the item
WinJS.UI.setOptions(adControl, { applicationId: item.AdAppId, adUnitId: item.AdUnitId });
return template;
}
else
{
return _$(".itemTemplate").winControl.render(item, null);
}
}
}
I had this problem in ratings:
<div data-win-control="WinJS.UI.Rating" data-win-options="{averageRating: 3.4, onchange: basics.changeRating}"></div>
I bind it via winControl:
<div data-win-control="WinJS.UI.Rating" data-win-bind="winControl.averageRating: myrating" data-win-options="{onchange: basics.changeRating}"></div>
It worked fine.
<div data-win-bind="this['data-list_item_index']:id WinJS.Binding.setAttribute" >