Dojo extending dojo.dnd.Source, move not happening. Ideas? - javascript

NOTICE: THIS IS SOLVED, I WILL PUBLISH THE SOLUTION HERE ASAP.
Hey all,
Ok... I have a simple dojo page with the bare essentials. Three UL's with some LI's in them. The idea si to allow drag-n-drop among them but if any UL goes empty due to the last item being dragged out, I will put up a message to the user to gie them some instructions.
In order to do that, I wanted to extend the dojo.dnd.Source dijit and add some intelligence. It seemed easy enough. To keep things simple (I am loading Dojo from a CDN) I am simply declating my extension as opposed to doing full on module load. The declaration function is here...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDndDrop: function(source, nodes, copy){
console.debug('onDndDrop!');
if(this == source){
// reordering items
console.debug('moving items from us');
// DO SOMETHING HERE
}else{
// moving items to us
console.debug('moving items to us');
// DO SOMETHING HERE
}
console.debug('this = ' + this );
console.debug('source = ' + source );
console.debug('nodes = ' + nodes);
console.debug('copy = ' + copy);
return dojo.dnd.Source.prototype.onDndDrop.call(this, source, nodes, copy);
}
});
}
I have a init function to use this to decorate the lists...
dojo.addOnLoad(function(){
declare_mockupSmartDndUl();
if(dojo.byId('list1')){
//new mockup.SmartDndUl(dojo.byId('list1'));
new dojo.dnd.Source(dojo.byId('list1'));
}
if(dojo.byId('list2')){
new mockup.SmartDndUl(dojo.byId('list2'));
//new dojo.dnd.Source(dojo.byId('list2'));
}
if(dojo.byId('list3')){
new mockup.SmartDndUl(dojo.byId('list3'));
//new dojo.dnd.Source(dojo.byId('list3'));
}
});
It is fine as far as it goes, you will notice I left "list1" as a standard dojo dnd source for testing.
The problem is this - list1 will happily accept items from lists 2 & 3 who will move or copy as apprriate. However lists 2 & 3 refuce to accept items from list1. It is as if the DND operation is being cancelled, but the debugger does show the dojo.dnd.Source.prototype.onDndDrop.call happening, and the paramaters do look ok to me.
Now, the documentation here is really weak, so the example I took some of this from may be way out of date (I am using 1.4).
Can anyone fill me in on what might be the issue with my extension dijit?
Thanks!

If you use Dojo XD loader (used with CDNs), all dojo.require() are asynchronous. Yet declare_mockupSmartDndUl() assumes that as soon as it requires dojo.dnd.Source it is available. Generally it is not guaranteed.
Another nitpicking: dojo.dnd.Source is not a widget/dijit, while it is scriptable and can be used with the Dojo Markup, it doesn't implement any Dijit's interfaces.
Now the problem — the method you are overriding has following definition in 1.4:
onDndDrop: function(source, nodes, copy, target){
// summary:
// topic event processor for /dnd/drop, called to finish the DnD operation
// source: Object
// the source which provides items
// nodes: Array
// the list of transferred items
// copy: Boolean
// copy items, if true, move items otherwise
// target: Object
// the target which accepts items
if(this == target){
// this one is for us => move nodes!
this.onDrop(source, nodes, copy);
}
this.onDndCancel();
},
Notice that it has 4 arguments, not 3. As you can see if you do not pass the 4th argument, onDrop is never going to be called by the parent method.
Fix these two problems and most probably you'll get what you want.

In the end, I hit the Dojo IRC (great folks!) and we ended up (so far) with this...
function declare_mockupSmartDndUl(){
dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
markupFactory: function(params, node){
//params._skipStartup = true;
return new mockup.SmartDndUl(node, params);
},
onDropExternal: function(source, nodes, copy){
console.debug('onDropExternal called...');
// dojo.destroy(this.getAllNodes().query(".dndInstructions"));
this.inherited(arguments);
var x = source.getAllNodes().length;
if( x == 0 ){
newnode = document.createElement('li');
newnode.innerHTML = "You can drag stuff here!";
dojo.addClass(newnode,"dndInstructions");
source.node.appendChild(newnode);
}
return true;
// return dojo.dnd.Source.prototype.onDropExternal.call(this, source, nodes, copy);
}
});
}
And you can see where I am heading, I put in a message when the source is empty (client specs, ug!) and I need to find a way to kill it when something gets dragged in (since it is not, by definition, empty any more ona incomming drag!). That part isnt workign so well.
Anyway, the magic was not to use the onDnd_____ functions, but the higher level one and then call this.inherited(arguments) to fire off the built in functionality.
Thanks!

dojo.require("dojo.dnd.Source");
dojo.provide("mockup.SmartDndUl");
dojo.declare("mockup.SmartDndUl", dojo.dnd.Source, {
Dojo require statement and declare statement are next to next. I think that will cause dependencies problem.
the dojo require statement should go outside onload block and the declare statement should be in onload block.

Related

Creating .json file and storing data in javascript -- using vis.js

In my project I need to save the data to .txt or .xml or .json file. I could not find any answer from vis.js website/issues blog. It might be simple, do not know. Really helpful if anyone help me out with example code. Thank you so much in advance.
function saveData(data,callback) {
data.id = document.getElementById('node-id').value;
data.label = document.getElementById('node-label').value;
clearPopUp();
callback(data);
}
If I understand you correctly, you are looking for a way to save data and options of a graph. In my graph editor adaptation for TiddlyWiki Classic I use the following method to extract data (the full implementation can be found in the repo, see config.macros.graph.saveDataAndOptions, here's a simplified one):
config.macros.graph.saveDataAndOptions = function(network,newOptions) {
newOptions = newOptions || {};
// get nodes and edges
var nodes = network.body.data.nodes._data; // contains id, label, x,y, custom per-node options and doesn't contain options from options.nodes; presumably contains option values set when network was created, not current ones (it is so for x,y)
// no suitable getter unfortunately
var edges = network.body.data.edges._data; // map; for edges to/from? certain node use network.getConnectedNodes(id)
// network.body.data.edges._data is a hash of { id: , from: , to: }
// get node positions, options
var positions = network.getPositions(), // map
options = // get options stored previously
// merge newOptions into options
for(var nodeId in nodes) {
// nodes[nodeId].x is the initial value, positions[nodeId].x is the current one
if(positions[nodeId]) { // undefined for hidden
nodes[nodeId].x = positions[nodeId].x;
nodes[nodeId].y = positions[nodeId].y;
}
storedNode = copyObjectProperties(nodes[nodeId]);
storedNodes.push(storedNode);
}
//# do whatever you need with storedNodes, edges and options
// (pack them with JSON.stringify, store to a file etc)
};
However, while this works ok for storing data, this only helps to save options passed for storing explicitly which can be not very nice for some cases. I use this method in manipulation helpers and on dragEnd (network.on("dragEnd",this.saveToTiddlerAfterDragging), config.macros.graph.saveToTiddlerAfterDragging = function(stuff) { config.macros.graph.saveDataAndOptions(this,{ physics: false }); };). I haven't recieved any better suggestions, though.
If you need to get data and options reactively and setting such helper to handle certain edit events can't solve your problem, then I suggest wrapping nodes, edges and options as vis.DataSet and save those when needed. This is related too.
To answer the question about events/other ways to use such methods. Here's how I use them:
I save data after drag&drop moving of nodes, this is done using an event handler. Namely, I introduced
config.macros.graph.saveToTiddlerAfterDragging = function(stuff) {
config.macros.graph.saveDataAndOptions(this,{ physics: false });
};
(when drag&drop is used, physics should be switched off, otherwise coordinates won't be preserved anyway) and then I use
network.on("dragEnd",this.saveToTiddlerAfterDragging);
so that changes are saved.
As for saving after adding/editing a node/edge, I apply saving not by an event (although it's nice thinking, and you should try events of DataSet, since there's no special graph events for that). What I do is I add an elaborated hijack to the manipulation methods. Take a look at the source I've linked after the
var mSettings = options.manipulation;
line: for each manipulation method, like options.manipulation.addNode I hijack it so that its callback is hijacked to call config.macros.graph.saveDataAndOptions in the end. Here's a simplified version of what I'm doing:
var nonSaving_addNode = options.manipulation.addNode;
options.manipulation.addNode = function(data,callback) {
// hijack callback to add saving
arguments[1] = function() {
callback.apply(this,arguments); // preserve initial action
config.macros.graph.saveDataAndOptions(network); // add saving
};
nonSaving_addNode.apply(this,arguments);
}
The thing is, addNode is actually called when the add node button is clicked; though, I'm using a customized one to create a popup and apply changes once user is happy with the label they chose.

Vis.js network: how to add a node on click inside the canvas?

Manipulation methods of vis.js only include addNodeMode(), but not something like addNode(). I wonder if there's some nice way to create a node on click. May be by manipulating the data instead of network itself?
Of'course, one may go
network.on('click',function(params){
if((params.nodes.length == 0) && (params.edges.length == 0)) {
network.addNodeMode(); // doesn't add, one more click needed
//# generate click in the same place. Use params.pointer.canvas
// or params.pointer.DOM to set appropriate coordinates
}
})
but then we have also to prevent infinit loops since we generate a click event in a click handler..
Ok, here's my current implementation:
...
data = ...
nodes = new vis.DataSet(data.nodes); // make nodes manipulatable
data = { nodes:nodes, edges:edges };
...
var network = new vis.Network(container, data, options);
network.on('click',function(params){
if((params.nodes.length == 0) && (params.edges.length == 0)) {
var updatedIds = nodes.add([{
label:'new',
x:params.pointer.canvas.x,
y:params.pointer.canvas.y
}]);
network.selectNodes([updatedIds[0]]);
network.editNode();
}
})
It's not perfect since it actually creates a node and starts editing it, so if we cancel editing, the node stays. It also creates unwanted shadows of nodes. But it's already a working prototype which is enough to start with.
You can add nodes dynamically by using the update method of the vis.DataSet class. See this documentation page for details: https://visjs.github.io/vis-data/data/dataset.html

Javascript: Linked List: Unable to delete object reference

I'm doing a Linked List data structure. The prototype includes a method to pop (delete) the last item from the list which I'm attempting to do by finding the last object, and then setting it to null. It does not seem to work. What does work is setting the reference (the 'pointer') in the previous object to null. I'm still a relative JS OOP newbie, can't get my brain to understand why. The code:
function LinkedList() {
this._rootNode = null;
this._length = 0;
}
LinkedList.prototype = {
push: function(data) {
var newNode = {
data: data,
nextNode: null
};
// initialize this._rootNode or subsequent .nextNode with newNode
this._length++;
},
pop: function() {
var selectedNode, perviousNode;
if ( this._rootNode ) {
if ( this._length > 1 ) {
selectedNode = this._rootNode;
while ( selectedNode.nextNode ) {
previousNode = selectedNode; // <-- shouldn't need this?
selectedNode = selectedNode.nextNode;
}
selectedNode = null; // <-- doesn't delete it
// previousNode.nextNode = null; // <-- works (but feels unnecessary?)
} else {
this._rootNode = null;
}
this._length--;
}
},
// more methods..
};
/* --- Main Prorgam --- */
var list = new LinkedList();
list.push('AAA');
list.push('BBB');
list.pop();
console.log(list._rootNode.nextNode.data); <-- 'BBB' still there
Would appreciate some insight, and any other tips on improving the function. Thanks!
I guess you realize that your push method doesn't work, but you haven't asked about that one.
If you are doing some kind of school project that requires you to write a linked list like this, then by all means, continue. Your issue is that selectedNode is not really "the node itself", it's a reference to it, and you're just setting that reference to null while the previous item's nextNode pointer still refers to it, so you haven't actually removed it from your list. You would actually do so by un-commenting the line setting that pointer to null, which means you also have to leave in the line saving the reference to the previous node.
previousNode.nextNode = null;
You actually don't want to delete the node entirely with pop(), you want to return it. Once you remove the reference to the popped node in your calling function though, it will be the last reference and the object will be made available for garbage collection. This is (to my knowledge) how all traditional OOP languages handle linked lists at the basic level.
Which brings me to my next point, that most OOP languages you'll use these days don't actually require you to work on the basic level. Most of them have libraries that will implement linked lists for you and Javascript in particular essentially implements a linked list-style data structure in its array syntax. To the point where ([1,2,3,4]).pop() evaluates to 4 and ([1,2,3,4]).push(5) evaluates to [1,2,3,4,5]. If you actually need to USE a linked list in a real project, just don't.

JS vs DOM timing: .remove() element visually happens, but travesal still includes it

The short description of the functionality that we are trying to achieve: we have a list of source objects on the left, a person can drag new items from the list to a list on the right, items thus get added to the list on the right; they can also remove items from the list on the right. The list on the right then gets saved whenever it is changed. (I don't think the specifics of how/where it is being saved matter...)
I am having a problem with a bit of timing in the JavaScript vs. DOM elements realm of things. Items that are already on the list on the right can be removed. We have some code that fires on a 'remove/delete' type icon/button on a DOM element, that is supposed to remove the element from the DOM visually and permanently (i.e. it doesn't need to be brought back with a 'show'). This visual change should then also show up in the JSON object that is built when the JS traverses the DOM tree to build the new updated list.
However, this chunk of JS code that runs immediately after this .remove() is called, the element that should have just been removed still shows up in the JSON object. This is not good.
Here are what I believe to be the relevant bits of code operating here. This lives in a web browser; much of this is in the document.ready() function. A given list can also have subsections, hence the sub-list parts and loops.
The on-click definition:
$('body').on('click', '.removeLine', function() {
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
var List=$(this).closest('article'); //Another parent object, containing all the
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
}
);
sendList(List); // This builds and stores the list based on the DOM elements
});
And then later on, this function definition:
function sendList(List) {
var ListArray=[],
subListArray=[],
itemsArray = [],
subListName = "";
var ListTitle = encodeText(List.find('.title').html());
// loop through the subLists
List.find('.subList').each(
function(index, element) {
subListName=($(this).find('header > .title').html()); // Get sublist Title
subListID=($(this).attr('id')); // Get subList ID
// loop through the line items
itemsArray=[];
$(this).find('.itemSearchResult').each(
function(index, element) {
// Build item Array
if( $(this).attr('data-itemid')!= item ) {
itemArray.push( $(this).attr('data-itemid'));
}
}
);
// Build SubList Array with items Array
subListArray.push(
{
"subListName": subListName,
"subListID" : subListID,
"items" : itemsArray
}
);
}
); <!-- end SubList Loop -->
// Complete List Array with subListArray
ListArray ={
"ListName": ListTitle,
"ListID": List.attr('id'),
"subLists": subListArray
};
// Send New List to DataLists Object - the local version of storage
updateDataLists(ListArray);
// Update remote storage
window.location= URLstring + "&$Type=List" + "&$JSON=" + JSON.stringify(ListArray) + "&$objectID=" + ListArray.ListID;
};
It seems to be the interaction of the 'parent.remove()' step and then the call to 'sendList()' that get their wires crossed. Visually, the object on screen looks right, but if we check the data being sent to the storage, it comes through WITH the object that was visually removed.
Thanks,
J
PS. As you can probably tell, we are new at the Javascript thing, so our code may not be terribly efficient or proper. But...it works! (Well, except for this issue. And we have run into this issue a few times. We have a workaround for it, but I would rather understand what is going on here. Learn the deeper workings of JS so we don't create these problems in the first place.)
There's a few things going on here, but I'm going to explain it by approaching it from an asynchronous programming perspective.
You are calling sendList before the element gets removed from the DOM. Your element doesn't get removed from the DOM until after your fadeOut callback gets executed (which takes 300ms).
Your sendList function gets called immediately after you begin the fadeOut, but your program doesn't wait to call sendList until your fadeOut is finished - that's what the callback is for.
So I would approach it by calling sendList in the callback, after your DOM element has been removed like this:
$('body').on('click', '.removeLine', function() {
var el = $(this); //maintain a reference to $(this) to use in the callback
var parent=$(this).parent().parent().parent(); //The button is a few DIVs shy of the outer container
parent.fadeOut( 300,
function() {
parent.slideUp(300);
parent.remove();
sendList(el.closest('article'));
}
);
});

Sencha Touch 2.2 List not reusing list-items

I have a list linked to a store filled with Facebook friends. It contains around 350 records.
There is a searchfield at the top of the list which triggers the following function on keyup:
filterList: function (value) {
// console.time(value);
if (value === null) return;
var searchRegExp = new RegExp(value, 'g' + 'i'),
all = Ext.getStore('Friends'),
recent = Ext.getStore('Recent'),
myFilter;
all.clearFilter();
recent.clearFilter();
// console.log(value, searchRegExp);
myFilter = function (record) {
return searchRegExp.test(record.get('name'));
}
all.filter(myFilter);
recent.filter(myFilter);
// console.timeEnd(value);
},
Now, this used to work fine with ST2.1.1 but since I upgraded the app to ST2.2. It's really slow. It even makes Safari freeze and crash on iOS...
This is what the logs give :
t /t/gi Friends.js:147
t: 457ms Friends.js:155
ti /ti/gi Friends.js:147
ti: 6329ms Friends.js:155
tit /tit/gi Friends.js:147
tit: 7389ms Friends.js:155
tito /tito/gi Friends.js:147
tito: 7137ms
Does anyone know why it behaves like this now, or does anyone have a better filter method.
Update
Calling clearFilter with a true paramater seems to speed up things, but it's not as fast as before yet.
Update
It actually has nothing to do with filtering the store.
It has to do with rendering list-items. Sencha apparently create a list-item for every record I have in the store instead of just creating a couple of list-items and reusing them
Could there be an obvious reason it's behaving this way ?
Do you have the "infinite" config on your list set to true?
http://docs.sencha.com/touch/2.2.0/#!/api/Ext.dataview.List-cfg-infinite
You probably don't want the list rendering 300+ rows at once, so setting that flag will reduce the amount of DOM that gets generated.

Categories