Use custom function to create elements in d3.js - javascript

I want to use d3.js to interact with marked text in CodeMirror. However, to mark text in CodeMirror, you call markText(from,to, {class: 'my-class'}) and it creates a span around the text with class my-class.
I wish to mark many pieces of text based on data and then add some event handling and such to them. Thus, I would like to do something like:
var box = d3.selectAll('box').data(myData);
box.enter().each(function(d) {cm.markText(from(d), to(d), {class: 'box'});})
box.on('mouseover', function(d) {...});
box.exit().remove();
However, d3's enter selections only support append, insert, and select. I tried faking the effect with select already (e.g. box.enter().select(arbitraryElt).each(...)) but that didn't work.
Of course, I could just loop through my data, mark the text, and then use d3 to apply the other attributes I want, but that sort of defeats the point of using d3.
Is it possible to do this with d3 in an elegant way?
EDIT:
I realized I completely misunderstood how markText works, and thus the context of my question is invalid. However, I'm still curious if it's possible to create elements using an arbitrary function instead of append.

You can use .call for custom functions, so that would be .call(box).

You can also use insert(), but not arbitrary functions. The problem is that the elements in the enter() selection don't really exist yet (i.e. there're no corresponding DOM elements). As there are no DOM elements, you can't operate on them.
More information in the documentation. You could of course implement your own custom function for handling the enter() elements, but that would require quite some knowledge on how d3 works internally.

Related

Triggering change on input

I have written some code that changes an input quantity on a magento 1.9 ecommerce website.
jQuery("input.qty").val("10");
The problem is the javascript that triggers the total to update doesn't fire. I have found the code responsible and it looks like this:
(function() {
var qtyFields = $('super-product-list').select('input.qty');
qtyFields.each(function(el) {
el.observe("change", updateGroupedPrice);
});
Event.observe(window, "load", updateGroupedPrice);
function updateGroupedPrice() {
//do stuff
}
})();
I think this is using prototype.js but I tried to isolate it in a codepen but couldn't get it working.
I have tried to trigger the change event like so:
jQuery("input.qty").trigger("change")
But it does not work. I also ran through a load of other events but in the dev tools it shows the code listening on "change".
Does anyone know why I can't trigger the change?
Since the page is using Prototype.js, you ought to keep using that to trigger your change. If you introduce jQuery into this, you're a) loading another complete duplicate of what Prototype already does, and b) asking for a lot of trouble isolating the fact that $() is a method in both libraries.
Your jQuery is a little fishy to me, too. You're setting the value of one picker (I imagine) and yet you are addressing it with a classname, so potentially there is more than one select.qty in the page, and all of them will change to value 10, firing off (potentially) multiple callback functions.
The Prototype code you see here is setting up a "listener" for changes on what you would address in jQuery as$(#super-product-list input.qty) inputs.
jQuery always treats $() as returning an array of objects, and thus all of its methods act on the array, even if it only contains one member. Prototype has two different methods for accessing elements in the DOM: $('some_id'), which always returns one element (or none, if no match), and $$('some css selector'), which always returns an array (of zero or more matching elements). You would write (or use native) callback methods differently, depending on which accessor you used to gather the element(s).
If you want to change one of these inputs, you will need to isolate it before you set its value.
Let's say there are three select pickers with the classname qty in your #super-product-list element. You want to change the third one to 10:
$('super-product-list').select('input.qty').last().setValue('10');
Or, much smarter than this, you add an ID to the third one, and then your code is much shorter:
$('quantity_3').setValue('10');
In either case, this will send the "change" event from your select, and the updateGroupedPrice method will observe that and do whatever you have coded it to do.
You won't need to (and should not ever) trigger the change event -- that's a "native" event, and the browser owns it. jQuery's trigger() (which is fire() in Prototype, is used exclusively for "synthetic events", like you see in Bootstrap: show.bs.modal, hide.bs.modal, etc. You can spot these by the punctuation in their names; usually dots or colons to namespace the events and avoid collisions with other code.
Finally, if you really, really, really wanted to change every single #super-product-list select.qty element on the whole page to '10', you would do this in Prototype.js:
$$('#super-product-list select.qty').invoke('setValue', 10);

How do I find the React DOM node that represents its actual html element?

Here's my problem, in case you're interested: I want to find the text selected by the user and insert a tag at the start and end of that selection (to highlight the text).
The best approach to find the selection, as quoted by the top minds of StackOverflow, is to use window.getSelection().
This, in combination with getRangeAt(0), turns up a list of things, where startContainer and endContainer look especially promising. startContainer.parentNode points directly to the p tag I started the selection in. Great!
However, how do I actually know which element this represents in the React DOM? How do I manipulate the right thing?
window.addEventListener("mousedown", function(e){console.log(e)});
using this event listener (or better use mouseup), you can find all the data about the target, example: if you copy/paste this piece of code into the console and see the log, you'll find all info about this event including the target, timestamp, shift key, control key, x,y, which mouse button, and all relevant information. if you combine this with what you described earlier you will get the results you need
EDIT:
As Mox had indirectly mentioned... When React re-renders, all the of the new nodes would be over-written and the highlighting would be lost.
This option as it is won't work well with React.
I'm going to leave this answer here since it is an option if the highlighted text is not being re-rendered often.
I believe the [surroundContents][1] method will be of great use to you.
According to the documentation, you can create a new node. Setup the class attribute on that new node. This class should enable the highlighting you want.
Then you can pass the new node into the surroundContents method to place the content selected by the range into your new node.
Here is an example from the documentation:
var range = document.createRange();
var newNode = document.createElement("p");
range.selectNode(document.getElementsByTagName("div").item(0));
range.surroundContents(newNode);
I believe you already have a range object, so you would just need to create a new node to wrap the highlighted text and use the surroundContents method.
I do see 1 major concern with this method.
If a user highlights text from inside a container and outside the same container, a situation like following may occur:
<p>
some text
<span class="highlighting">
some more text
</p>
some other text
</span>
I do not know how surroundContents will handle this, so this may be become a problem for you.

Applying a javascript function on a div

I want to put a variable on a div and to be applied and inherited by all the dojo widgets under this div.
Is this feasible ?
For example
<div shaper="Contextual">
<textarea ..../>
<select multiple data-dojo-type="dijit/form/MultiSelect">
....
</div>
I want the functionality supported by the shaper to be applied to all the widgets included in the div.
p.s.: "shaper" is a custom module created to do numeric shaping for Arabic numbers.
It's possible, but not out of the box.
You can write something like this:
require(["dojo/query", "dojo/domReady!"], function(query) {
query("[shaper]").forEach(function(shaper) {
});
});
This will query all elements with a shaper attribute and loop over it. Inside the loop, you will have to retrieve the value of the shaper attribute (for example Contextual), you can do that with the getAttribute() function, for example:
var shaperModule = shaper.getAttribute("shaper");
Now you have the name of the module to load, so you can write something like this inside the loop:
require([shaperModule], function(shaperModule) {
});
This will use AMD to retrieve the Contextual module. Now all that's left is to include the shaper functionality into all widgets inside your <div>.
First of all, with dijit/registry::findWidgets() you can retrieve all widgets inside a specific DOM node, you can use this to retrieve your dijit/form/MultiSelect widget in this case:
registry.findWidgets(shaper);
Then you can loop over the array of widgets that are found and use dojo/_base/lang::mixin() to extend an object with the contents of another object, for example:
registry.findWidgets(shaper).forEach(function(widget) {
lang.mixin(widget, shaperModule);
});
For example: http://jsfiddle.net/zLv7cvzt/
Though this might not work entirely (what if the module does not exist or what about widgets inside widgets, which dijit/registry::byId() does not detect), it does give you an idea of how to achieve it.
To answer your second question, if it's feasible or not, I would say that it depends. If you extend a widget with another widget like this, it could really end up with really weird things, because all widgets extend from dijit/_WidgetBase, which provides the widget lifecycle, you could mix both widgets their lifecycle.
Also, if you end up doing this and you get an error, it will be really hard to debug this if you're not familiar with the custom code.

jQuery Tools -> Tooltip destroy method?

I am using Flowplayer's jQuery Tools framework (specifically the tooltips plugin) in a table, in addition to jQuery UI calendar.
Each row of the table has the ability to insert a row above and below it.
When doing this I am cloning the clicked object (events and objects) and inserting it directly above or below.
After adding a new row, I refresh the table, generating new id's for my elements, reinitializing the datepicker, and attempting to reinitialize the tooltip.
I am searching for a way to destroy it altogether from the instance and reapply it.
I am looking for something similar to the datepicker('destroy') method.
$j($editRow).find('input.date').datepicker('destroy').datepicker({dateFormat: 'mm-dd-yy', defaultDate : defaultDateStr});
I have already attempted to :
to unbind the mouseover and focus events : when reinvoking tooltip, it automatically goes for the object it was made from.
hide the tooltip DOM element, remove the tooltip object from the target, and reapply it. The same thing happens as (1)
Is there way I can create a destroy method myself?
I tried kwicher's method, and could not get it to work. Although I was trying to alter the minified code, and I'm not entirely sure I found the right place to make the change.
I did, however, get this to work. ValidationFailed is a class I am applying to all the input fields that are also having tooltips applied to them.
$('.validationFailed').each(function (index) {
$(this).removeData('tooltip');
});
$('.tooltip').remove();
I tried this several different ways, and this was the only combination that allowed me to add additional tool tips post-removal, without preventing the new tooltips from working properly.
As best I can tell, the tooltip class removal handles getting rid of the actual tooltip divs, whose events are also wired up directly. The .removeData allows the tooltip to be re-bound in the future.
Building on your ideas and Tom's answer, I found that three things are necessary:
remove the 'tooltip' data from the tooltip target element
unbind event listeners from the tooltip target element
remove the element (assuming you're using this tooltip approach to allow for arbitrary HTML in your tip)
So, this simple function should do the trick. Just pass it a jQuery object containing the target elements of the tooltips you want to destroy.
function destroyTooltips($targets) {
$targets.removeData('tooltip').unbind().next('div.tooltip').remove();
}
If you still need it you can do as follows:
- in the tooltip implementation file add the following function
destroy: function(e) {
tip.detach();
}
somewhere in :
$.extend(self, {
...
I have after the last native function.
Then when you want to remove the tip, fire:
$(.tip).data('tooltip').destroy();
It should do the trick
K
if($element.data('ui-tooltip')) {//if tooltip has been initialized
$element.tooltip('destroy');
}
Maybe it's too late... ;-)
But here you can find all methods of 'tooltip': http://www.w3schools.com/bootstrap/bootstrap_ref_js_tooltip.asp
I leave the tip for someone who could pass by, having the same problem: dealing with different 'tooltip' actions/objs.

What is the fastest way to get a dom element?

I'm performance-tuning my code, and am surprised to find that the bottleneck is not dom node insert, but selection.
This is fast:
var row = jquery(rowHTML).appendTo(oThis.parentTable);
but the subsequent getting of an element inside "row" is slow:
var checkbox = jquery(".checkbox input", row);
I need to get the checkbox in every row so I can attach an event handler to it. Selecting the checkbox is ALMOST 10X AS SLOW as inserting the entire parent row.
What am I doing wrong here?
DOM manipulation uses native functions to perform simple operations. Browser vendors optimize these. You are building the row from HTML. Internally jQuery is using .innerHTML to build the collection which then patches into the browser's mega-fast parser.
Selection is slow in comparison because JS code needs to loop through the DOM repeatedly. Newer browsers have native selection handling which provides dramatic speedups to selector based JS. As time moves on this will be less of a problem.
Here is how the query in question, $(".checkbox input", row), breaks down:
row.getElementsByTagName('*');
for-loop through every element returned (all elements within the row) and test elements[i].className with /(\s|^)checkbox(\s|$)/.
for-loop every element still remaining and collect matched[i].getElementsByTagName('input');
unique the final collection.
This is different for jQuery 1.3 as it's engine moves through the selector the other way around, beginning with getting all input elements and then testing the parent elements.
Rremember that the JS selector engines implement a lot more of the CSS selector spec than is actually usable with CSS (or implemented by current browsers). Exploiting this, and knowledge of the engines, we can optimize selector can be optimized in a few different ways:
If you know what element type the .checkbox is:
$("td.checkbox input", row);
It is faster for filter first for type and then for the class for only those matches. This doesn't apply for a very small subset of elements, but that is almost never the case in praxis.
The single class test is the slowest of the common selectors people actually use.
Simpler selection:
$("input[type=checkbox]", row);
One loop is faster than two loops. This only finds input elements and then directly filters them by type attribute. Since sub/child-elements are never used, unique may also be skipped (and smart engines will try to do this because unique is slow).
A more direct selector:
$("td:first.checkbox input", row);
A more complex selector may actually be faster if it is more direct (YMMV).
If possible, move the search context up to the table level:
By this I mean that instead of looping through the rows, and searching for the checkbox in every one, leave them alone until after the loop and then select them all at a time:
$("tr td:first.checkbox input", table);
The point of this is to eliminate the overhead of firing the selector engine up repeatedly, but instead do everything in one haul. This is presented here for completeness rather than something that I think would return massive speedups.
Don't select:
Build the row from bits, assigning events as you go.
var row = $( '<tr></tr>' );
var cell = $( '<td class="checkbox"></td>' ).appendTo( row );
$( '<input type="checkbox" name="..."/>' ).appendTo( cell ).click(/* ... */);
This may be impossible for reasons of Ajax or other templates out of your control. Additionally, the speed may not be worth turning your code into this sort of mess, but sometimes this may make sense.
Or, if none of these work for you, or return too performance gain, it may be time to rethink the method entirely. You can assign an event listener higher up the tree and grab the events there, instead of per-element instance:
$('table').change(function(e){
// you may want a faster check...
if ( $(e.target).is('input[type=checkbox]') ) {
// do some stuff ...
}
});
This way you don't do anything unless, and until, the user actually requests it. Fastest. :-)
var checkbox = jquery(".checkbox input", row);
This is traversing the entire dom tree to find the checkbox. You could possibly speed it up by changing the selector to an ID which can use the browsers native getElementById functionality.
var checkbox = jquery("#checkbox input", row);
You could also use your row as a starting point for the DOM search like the following example. Now your not parsing through the entire DOM tree again to find the matched element.
var row = jquery(rowHTML).appendTo(oThis.parentTable);
row.children(".checkbox input");
Use event delegation and add a single handler to a parent element and not the checkboxes themselves.
jQuery supports this via the live() function.
Try putting a class name on the input field itself. That may prove to be faster.
The reason for that is your code goes through all .checkbox classes, tries to find the input child of that element and returns that. I think that action might be your culprit.
By simply just looking for all elements with the class the input field has, you might see some speedup.
Try using Sly, it has an emphasis on performance.
If you're looking for performance, jQuery selectors are very slow. In the example there, it has to scan the full DOM tree and check CSS classes and so on to find the relevant nodes.
It is significantly faster to use native DOM methods. There are some interesting library performance comparisons here:
http://ajaxian.com/archives/taskspeed-more-benchmarks-for-the-libraries-and-browsers
The fastest way to get the DOM element is to use pure JavaScript and calling it by ID.
var element = document.getElementById('element);

Categories

Resources