Adding custom remove method to Raphael JS Graffle Connection - javascript

I'm using the custom connection method (Raphael.fn.connection) added in the example found at: raphaeljs.com/graffle.html
My example is here: http://jsfiddle.net/WwT2L/ (scroll in the display window to see the effect)
Essentially, I've linked the graffle connection to the bubble so it stays with it as it scales. I'm hoping that I can have the connection switch to the next bubble as the user scrolls past a certain point.
To do this, I was thinking I would remove the connection and add another one, but as the connection method is not a native Raphael element, it doesn't have the built in remove method, and I'm having trouble adding the remove method to the prototype.
I've found some info about adding custom methods at this google group discussion
and I've tried:
this.connections[0] = this.r.connection(this.bubbles[0], this.unitConnector, "#fff", "#fff").__proto__.remove = function() {alert('working custom method');};
which seems to add a method to this instance of connection but I'm not sure what to have the method do and it seems like there should be a better way.

To recap... when we create a connection, we often use the following:
connections.push(
r.connection(r.getById(firstObjectId), r.getById(secondObjectId), '#fff')
);
What we're doing here is pushing (adding) a Raphael.connections object into a connections[] array, based on their Raphael object id's
To add a method/function to Raphael, one might use:
Raphael.fn.fnName = function (){ /* Your code here */ }
This creates a function in our Raphael namespace for use with our Raphael objects.
Below is the code i've created which does exactly what you require. I couldn't find a good resource out there for Raphael, but will surely be creating one soon, as I have done a lot of development with it.
Raphael.fn.removeConnection = function (firstObjectId, secondObjectId) {
for (var i = 0; i < connections.length; i++) {
if (connections[i].from.id == firstObjectId) {
if (connections[i].to.id == secondObjectId) {
connections[i].line.remove();
connections.splice(i, 1);
}
}
else if (connections[i].from.id == secondObjectId) {
if (connections[i].to.id == firstObjectId) {
connections[i].line.remove();
connections.splice(i, 1);
}
}
}
};
Just like in the create connections, two id's are provided. We must find these ID's in the array of connections we've pushed each connection set to. If you only have one connection, there is no need for array traversing, though this is a case less encountered.
We have two possible scenarios here - excluding the case of having found no connection for simplicity sake. It either finds that:
the connection objects from.id corresponds to the first provided paramenter firstObjectId. Then, the to corresponds to the second provided paramenter secondObjectId.
the connection objects from.id corresponds to the first provided paramenter secondObjectId. Then, the to corresponds to the second provided paramenter firstObjectId.
This method of checking covers all our bases, so no matter how the connection is interacted with (in my case the user clicks two objects to connect them, and delete their connection)
Once we've confirmed we have the two correct objects, we then remove the line from the DOM, using connections[i].line.remove(); as just removing the connection object from the array will leave it on the map.
Finally, we remove the specified connection object from the array, and the splice method leave us with an un-holy array (no holes in our array, that is ;) ) using connections.splice(i, 1);
Then,

this is what i am using to remove connections from connections array used with graffle example and so far i am having no issue with it. the question may be old but i stumbled upon on it searching the related solution, so when i had no luck i created mine and want to share with rest of the world.
//checks if the current object has any relation with any other object
//then remove all the to and from connections related to current object
for(var i =0 ; i<connections.length; i++){
if(connections[i].from.id == objectId || connections[i].to.id ==objectId ){
connections[i].line.remove();
}
}
//finds out which connections to remove from array and updates connections array
connections = $.grep(connections, function(el){
return el.line.paper != null;
})
the splice method was having issues with my case as if object has more than one connections (to, from) with multiple objects and every time i was using splice the main connections array length was changing as well as value of i was increasing, so i used jQuery grep method to update array based on removed lines. i hope this will help others too.

function removeShape(shape) {
//CONNECTIONS is my global structure.
var connections = [];
while (CONNECTIONS.length) {
var connection = CONNECTIONS.pop();
if (connection.from.id == shape.id || connection.to.id == shape.id)
connection.line.remove();
else
connections.push(connection);
}
shape.remove();
CONNECTIONS = connections;
}

Related

Rewrite javascript functions for IE

I have some functions that either kill the page or fail silently in IE. I can't figure out how to rewrite them. I'd prefer not to have to add a bunch of plugins but I do have jQuery.
The variables in question are arrays of objects. How would you write the following?
// 1. Get only the newly added user / group
var new_students = new_enrollee_list.filter(function( new_enrollee ){
return ! current_enrollee_list.some(function( current_enrollee ){
return new_enrollee.id === current_enrollee.id && new_enrollee.type === current_enrollee.type;
});
});
// 2. Remove students from current list
current_enrollee_list.splice(0, current_enrollee_list.length, ...new_enrollee_list);
For the spread syntax you should be able to workaround by making one array from all the arguments and using Function.apply:
So this
current_enrollee_list.splice(0, current_enrollee_list.length, ...new_enrollee_list);
becomes
current_enrollee_list.splice.apply(current_enrollee_list, [0, current_enrollee_list.length].concat(new_enrollee_list));
Since you are using IE9 some and filter should work fine.

onkeydown, and auto complete

I was wondering if anyone could help me solve this issue or point me towards the right direction.
In my project we have a filed that needs to be autofilled, at this moment I use onblur which works wonders as it only does it so once you leave the focus. However, due to recent changes, it needs to only do so when there is only one unique item in the map which it matches the input.
I have a large array defined as following:
var myArray = [
[content, content],
[content, content],
...
]
Later in my code I associate it with a map, at least this is what most stackoverflow questions I looked at referred to it as follows:
var myMap = {};
for(0 to myArray.length) {
var a = myArray[i][0];
var b = myArray[i][1];
myMap[a] = b;
}
Now, finally I iterate over this array as follows:
for (var key in map) {
if (map.hasOwnProperty(key)) {
if (map[key].toLowerCase().indexOf(location.toLowerCase()) >= 0)
the above is the line of code I am struggling to figure out how to change. At this moment, while using on blur, if I type in the letter 'A' for example, and leave the focus area it will automatically fill it in with a certain name. However, in the array there are many other objects that begin with, or contain A. How can I change it so that the onkeydown event will keep going until it finally filters it down to to only possible key-value pair? I tried looking at MDN's documentation for filtering, but I do not think that will work for my purposes, or at least I am too inexperienced with JS.
If the indexOf the first and last are nonnegative and equal, there is just one. You could do this with an && and boolean short circuit evaluation, but that will run very far right off the screen, so I am showing your code with one more nested if (up to you to add the end of the block). But we also need to see if there are matches on multiple keys.
var matchCount=0;
for (var key in map) {
if (map.hasOwnProperty(key)) {
if (map[key].toLowerCase().indexOf(location.toLowerCase()) >= 0){
if (map[key].toLowerCase().indexOf(location.toLowerCase()) == map[key].toLowerCase().lastIndexOf(location.toLowerCase())) {
matchCount++;
then outside your for loop:
if (matchCount==1){ //do your stuff

Efficiently tracking and updating an array of DOM elements with Javascript / jQuery?

Inside of a module I'm writing (its kind of a slider / timeline interface component) I've got a method that updates the controls which are a set of clickable elemetns along the bottom that are updated on click and when the user scrolls.
I'm doing the following to attach classes to the items up until the active one. While the approach I'm using works, its feels very inefficient as I'm looping over a set of DOM elements each time.
updateTimeLine : function(pos, cb) {
var p = pos;
var timeline = $('.timer').toArray();
if (p > 15)
p = 15;
$.each(timeline, function(index,value) {
var that = $(this);
if (index >= p) {
if (that.children('span').hasClass('active'))
that.children('span').removeClass('active');
} else {
that.children('span').addClass('active');
}
});
if (cb && typeof(cb) === "function") {
cb();
}
return this;
},
Is there a better way to do this? If so, how?
Is this a good use case for something like the observer pattern? which I don't fully get, having not spent any time with it yet, so if it is, I'd really like to know how to apply this pattern properly.
Observer patterns notify subscribed objects by looping through and invoking listeners on each subscriber when a relevant change occurs. Because of that, you'd probably end up using $.each anyways. I think what you have is equally efficient.
If you feel bad about iterating over the dom each time, consider this: there exists no such algorithm that can update each dom element without iterating through them. Caching the DOM array theoretically would improve performance, but my money says the browser's already doing that. Try it yourself on this jsperf...

Protractor variable scope with promises

Background
I'm working on an Angular app which uses ng-repeat to make a table. One of the users found that the table sometimes contains duplicate entries, which I confirmed visually, then promptly wrote a Protractor test for.
The Test
Variable Scoping Issues
While writing the test, I noticed that the scope wasn't behaving in a way that I understood.
Naturally, the for-loop on line 61 has access to linkStorage (line 38), since it is in a higher scope. It logs that all of the objects have been successfully added to the object via the for-loop in the promise on line 47.
However, when I move the confirmation loop outside of the promise, say, before the expect block...
...linkStorage is an empty object.
Looping over the object finds no nested key-value pairs; it is truely empty.
Question (tl;dr)
Why is the linkStorage object populated inside the then statement, but not before the expectation?
Asynchronousity Strikes Again
The first example works is due to asynchronousity. Because the .getAttribute method is non-blocking, the code continues to run past it while it works. Therefore, the console loop is reached before the object has been populated; it's empty.
If you give the asynchronous code some time to run, maybe one second:
...linkStorage is populated.
Complete Solution
Chain multiple promises together to ensure code runs at the correct time.
it('should not have duplicates within the match grid', function() {
// Already on job A, with match grid shown.
var duplicate = false;
var linkStorage = {};
// Save unique links
var uniqueUserLinks = element.all(by.css('div.row table tbody tr td a'));
// get an array of href attributes
uniqueUserLinks.getAttribute('href')
.then(function(hrefs) {
// add the links to the linkStorage object
for (var i = 0; i < hrefs.length; i++) {
// if the link is already there
if( linkStorage[ hrefs[i] ] ) {
// update its counter
linkStorage[hrefs[i]] += 1
duplicate = true;
// there's already one duplicate, which will fail the test
break;
} else {
// create a link and start a counter
linkStorage[hrefs[i]] = 1;
}
};
}).then(function() {
// confirm links have been added to storage
for(var link in linkStorage) {
console.log('link:', link );
console.log('number:', linkStorage[link] );
}
}).then(function() {
expect(duplicate).toBe(false);
});
});

Iterate Through Nested JavaScript Objects - Dirty?

I have some JavaScript that I wrote in a pinch, but I think it could be optimized greatly by someone smarter than me. This code runs on relatively small objects, but it runs a fair amount of times, so its worth getting right:
/**
* Determine the maximum quantity we can show (ever) for these size/color combos
*
* #return int=settings.limitedStockThreshold
*/
function getMaxDefaultQuantity() {
var max_default_quantity = 1;
if (inventory && inventory.sizes) {
sizecolor_combo_loop:
for (var key in inventory.sizes) {
if (inventory.sizes[key].combos) {
for (var key2 in inventory.sizes[key].combos) {
var sizecolor_combo = inventory.sizes[key].combos[key2];
if (isBackorderable(sizecolor_combo)) {
//if even one is backorderable, we can break out
max_default_quantity = settings.limitedStockThreshold;
break sizecolor_combo_loop;
} else {
//not backorderable, get largest quantity (sizecolor_combo or max_default_quantity)
var qoh = parseInt(sizecolor_combo.quantityOnHand || 1);
if (qoh > max_default_quantity) {
max_default_quantity = qoh;
};
};
};
};
};
};
return Math.min(max_default_quantity, settings.limitedStockThreshold);
};
First, inventory is a object returned via JSON. It has a property inventory.sizes that contain all of the available sizes for a product. Each size has a property inventory.sizes.combos which maps to all of the available colors for a size. Each combo also has a property quantityOnHand that tells the quantity available for that specific combo. (the JSON structure returned cannot be modified)
What the code does is loop through each size, then each size's combos. It then checks if the size-color combo is backorderable (via another method). If it any combo is backorderable, we can stop because the default quantity is defined elsewhere. If the combo isn't backorderable, the max_default_quantity is the largest quantityOnHand we find (with a maximum of settings.limitedStockThreshold).
I really don't like the nested for loops and my handling of the math and default values feels overly complicated.
Also, this whole function is wrapped in a much larger jQuery object if that helps clean it up.
Have you considered using map-reduce? See a live example of a functional approach.
This particular example uses underscore.js so we can keep it on a elegant level without having to implement the details.
function doStuff(inventory) {
var max = settings.limitedStockThreshold;
if (!(inventory && inventory.sizes)) return;
var quantity = _(inventory.sizes).chain()
.filter(function(value) {
return value.combos;
})
.map(function(value) {
return _(value.combos).chain()
.map(function(value) {
return isBackorderable(value) ? max : value.quantityOnHand;
})
.max().value();
})
.max().value();
return Math.min(quantity, max);
}
As for an explanation:
We take the inventory.sizes set and remove any that don't contain combos. We then map each size to the maximum quantity of it's colour. We do this mapping each combo to either its quantity or the maximum quantity if backordable. We then take a max of that set.
Finally we take a max of set of maxQuantities per size.
We're still effectily doing a double for loop since we take two .max on the set but it doesn't look as dirty.
There are also a couple of if checks that you had in place that are still there.
[Edit]
I'm pretty sure the above code can be optimized a lot more. but it's a different way of looking at it.
Unfortunately, JavaScript doesn't have much in the way of elegant collection processing capabilities if you have to support older browsers, so without the help of additional libraries, a nested loop like the one you've written is the way to go. You could consider having the values precomputed server-side instead, perhaps cached, and including it in the JSON to avoid having to run the same computations again and again.

Categories

Resources