I have a list of nodes (divs) that can be re-arranged via drag-and-drop. Sample code is listed below. Normally, I want to highlight the hovered node with one class (for simplicity, let's say blue background, ":hover" pseudo class), but when a node is dragged I want to highlight with a different class (red, "dragged" class).
I'm having two problems with this:
In dragstart() I apply "dragged" class to the node, but it doesn't take the effect immediately. I can change the background color by modifying style.backgroundColor directly, but I was hoping there's a more straightforward css solution to redraw a node after adding a class.
Hover state gets messed up once I change the order of nodes. As you can see, if you drag node from top to bottom hovered state follows that node (masking "dragged" class), but if you drag from bottom to top hovered state jumps to the original index (index of the node that received mousedown) and now "dragged" class shows up.
I tried various things but to no avail. As far as I can tell there's no way to remove or suspend :hover pseudo class, and I can't figure out if there's a way to force hover on a certain node (I tried simple things like setting focus to make the node active).
The second problem is the real issue, but I would appreciate if anyone can comment on how to resolve both issues. Thanks in advance.
<html>
<head>
<style>
.node {
border: 1px solid gray;
height: 26px;
width:300px;"
}
.node:hover {
background-color: #4444ff;
}
.dragged {
background-color: #ff4444;
}
</style>
</head>
<body>
<div id='cont' style="width: 300px;"></div>
</body>
<script>
(function(){
var html = '';
for (var i = 1; i < 11; i++) {
html += '<div id="' + i + '"draggable="true" ondragstart="dragstart(event)" ondragenter="dragenter(event)" ondragend="dragend(event)" class="node">item' + i + '</div>';
}
document.getElementById('cont').innerHTML = html;
})();
function dragstart(event) {
event.target.className += ' dragged';
event.dataTransfer.setData('text/html', event.target.id);
}
function dragenter(event) {
var sourceId = event.dataTransfer.getData('text/html');
var targetId = event.target.id;
if (targetId === sourceId) {
return true;
}
var sourceNode = document.getElementById(sourceId);
var targetNode = document.getElementById(targetId);
var sourceIndex = Array.prototype.indexOf.call(sourceNode.parentNode.childNodes, sourceNode);
var targetIndex = Array.prototype.indexOf.call(targetNode.parentNode.childNodes, targetNode);
if (targetIndex > sourceIndex) {
targetNode.parentNode.insertBefore(targetNode, sourceNode);
} else {
targetNode.parentNode.insertBefore(sourceNode, targetNode);
}
}
function dragend(event) {
event.target.className = event.target.className.replace(' dragged', '');
}
</script>
</html>
First issue :
In the CSS, add !important at the end of your .dragged's background-color.
Second issue :
Apparently, Chrome have an odd behaviour with event.dataTransfer.setData()
I just tested by replacing setData('text/html'... by setData('Text'...
and it actually does work in my Chrome (36 osx).
Of course you'll have to change the getData() parameters to "Text" too
Edit from comments
Due to Chrome restrictions about dataTransfer.setData() I wasn't able to make a working fiddle for last code.
But it does work for me in a standalone page.
The easiest solution would be to use a global variable that will store your element.
var draggedItem;
function dragstart(event) {
event.target.className += ' dragged';
draggedItem = event.target;
}
function dragenter(event) {
var sourceId = draggedItem.id;
var targetId = event.target.id;
...
Here is a working fiddle : http://jsfiddle.net/2Kgvh/
This was turning into a css and cross-browser nightmare, so I resorted to jQuery and its Sortable Widget implementation.
Related
How to deselect a word after making it clickable
What I want to do is add (I'm assuming) another JS so when they click on the word again, it'll appear back to normal without the black background?
Rather than setting inline values for your code, just create a CSS class to handle this, then toggle as necessary.
element.onclick = () => {
element.classList.toggle('highlighted');
}
And the class ...
.highlighted {
background-color: black;
color: white;
}
A messier solution would be to add an if/else statement to your onclick function.
element.onclick = () => {
if (element.style.background == '#000') {
element.style.background = '#fff';
}
else {
element.style.background = '#000';
}
}
The .split() function actually removes your spaces, so just make sure you fix that when appending everything back together as well.
inputValue.split(" ").forEach(word => {
const element = document.createElement("span");
element.innerHTML = word + ' ';
...
I've mocked up the class solution at codepen.io.
The new predictive type feature Smart Compose of Gmail is quite interesting.
Let's say we want to implement such a functionality ourselves:
User enters beginning of text, e.g. How and in gray behind it appears are you?.
User hits TAB and the word tomorrow is set.
Example:
Can a textarea with Javascript be used to achieve this?
And if not, how could this be implemented otherwise?
My previous answer got deleted, so here's a better attempt at explaining how I've somewhat replicated Smart Compose. My answer only focuses on the pertinent aspects. See https://github.com/jkhaui/predictable for the code.
We are using vanilla js and contenteditable in our solution (just like Gmail does). I bootstrap my example with create-react-app and Medium-Editor, but neither React nor Medium-Editor are necessary.
We have a database of "suggestions" which can be an array of words or phrases. For our purposes, in my example, I use a static array containing 50,000+ common English phrases. But you can easily see how this could be substituted for a dynamic data-source - such as how Gmail uses its neural network API to offer suggestions based on the current context of users' emails: https://ai.googleblog.com/2018/05/smart-compose-using-neural-networks-to.html
Smart Compose uses JavaScript to insert a <span></span> element immediately after the word you are writing when it detects a phrase to suggest. The span element contains only the characters of the suggestion that have not been typed.
E.g. Say you've written "Hi, how a" and a suggestion appears. Let's say the entire suggestion is "how are you going today". In this case, the suggestion is rendered as "re you going today" within the span. If you continue typing the characters in the placeholder - such as "Hi, how are you goi" - then the text content of the span changes dynamically - such that "ng today" is now the text within the span.
My solution works slightly differently but achieves the same visual effect. The difference is I can't figure out how to insert an inline span adjacent to the user's current text and dynamically mutate the span's content in response to the user's input.
So, Instead, I've opted for an overlay element containing the suggestion. The trick is now to position the overlay container exactly over the last word being typed (where the suggestion will be rendered). This provides the same visual effect of an inline typeahead suggestion.
We achieve correct positioning of the overlay by calculating the top + left coordinates for the last word being typed. Then, using JavaScript, we couple the top + left CSS attributes of the overlay container so that they always match the coordinates of the last word. The tricky part is getting these coordinates in the first place. The general steps are:
Call window.getSelection().anchorNode.data.length which retrieves the current text node the user is writing in and returns its length, which is necessary to calculate the offset of the last word within its parent element (explained in the following steps).
For simplicity's sake, only continue if the caret is at the end of the text.
Get the parent node of the current text node we're in. Then get the length of the parent node's text content.
The parent node's text length - the current text node's (i.e the last word's) text length = the offset position of the last text node within its contenteditable parent.
Now we have the offset of the last word, we can use the various range methods to insert a span element immediately preceding the last word: https://developer.mozilla.org/en-US/docs/Web/API/Range
Let's call this span element a shadowNode. Mentally, you can now picture the DOM as follows: we have the user's text content, and we have a shadowNode placed at the position of the last word.
Finally, we call getBoundingClientRect on the shadowNode which returns specific metadata, including the top + left coordinates we're after.
Apply the top + left coordinates to the suggestions overlay container and add the appropriate event handlers/listeners to render the suggestion when Tab is pressed.
Visit this link for documentation https://linkkaro.com/autocomplete.html .
May be you need to make few adjustment in CSS ( padding and width ).
I hope it will help.[![
$(document).ready(function(){
//dummy random output. You can use api
var example = {
1:"dummy text 1",
2:"dummy text 2"
};
function randomobj(obj) {
var objkeys = Object.keys(obj)
return objkeys[Math.floor(Math.random() * objkeys.length)]
}
var autocomplete = document.querySelectorAll("#autocomplete");
var mainInput = document.querySelectorAll("#mainInput");
var foundName = '';
var predicted = '';
var apibusy= false;
var mlresponsebusy = false;
$('#mainInput').keyup(function(e) {
//check if null value send
if (mainInput[0].value == '') {
autocomplete[0].textContent = '';
return;
}
//check if space key press
if (e.keyCode == 32) {
CallMLDataSetAPI(e);
scrolltobototm();
return;
}
//check if Backspace key press
if (e.key == 'Backspace'){
autocomplete[0].textContent = '';
predicted = '';
apibusy = true;
return;
}
//check if ArrowRight or Tab key press
if(e.key != 'ArrowRight'){
if (autocomplete[0].textContent != '' && predicted){
var first_character = predicted.charAt(0);
if(e.key == first_character){
var s1 = predicted;
var s2 = s1.substr(1);
predicted = s2;
apibusy = true;
}else{
autocomplete[0].textContent = '';
apibusy= false;
}
}else{
autocomplete[0].textContent = '';
apibusy= false;
}
return;
}else{
if(predicted){
if (apibusy == true){
apibusy= false;
}
if (apibusy== false){
mainInput[0].value = foundName;
autocomplete[0].textContent = '';
}
}else{
return;
}
}
function CallMLDataSetAPI(event) {
//call api and get response
var response = {
"predicted": example[randomobj(example)]
};
if(response.predicted != ''){
predicted = response.predicted;
var new_text = event.target.value + response.predicted;
autocomplete[0].textContent = new_text;
foundName = new_text
}else{
predicted = '';
var new_text1 = event.target.value + predicted;
autocomplete[0].textContent = new_text1;
foundName = new_text1
}
};
});
$('#mainInput').keypress(function(e) {
var sc = 0;
$('#mainInput').each(function () {
this.setAttribute('style', 'height:' + (0) + 'px;overflow-y:hidden;');
this.setAttribute('style', 'height:' + (this.scrollHeight+3) + 'px;overflow-y:hidden;');
sc = this.scrollHeight;
});
$('#autocomplete').each(function () {
if (sc <=400){
this.setAttribute('style', 'height:' + (0) + 'px;overflow-y:hidden;');
this.setAttribute('style', 'height:' + (sc+2) + 'px;overflow-y:hidden;');
}
}).on('input', function () {
this.style.height = 0;
this.style.height = (sc+2) + 'px';
});
});
function scrolltobototm() {
var target = document.getElementById('autocomplete');
var target1 = document.getElementById('mainInput');
setInterval(function(){
target.scrollTop = target1.scrollHeight;
}, 1000);
};
$( "#mainInput" ).keydown(function(e) {
if (e.keyCode === 9) {
e.preventDefault();
presstabkey();
}
});
function presstabkey() {
if(predicted){
if (apibusy == true){
apibusy= false;
}
if (apibusy== false){
mainInput[0].value = foundName;
autocomplete[0].textContent = '';
}
}else{
return;
}
};
});
#autocomplete { opacity: 0.6; background: transparent; position: absolute; box-sizing: border-box; cursor: text; pointer-events: none; color: black; width: 421px;border:none;} .vc_textarea{ padding: 10px; min-height: 100px; resize: none; } #mainInput{ background: transparent; color: black; opacity: 1; width: 400px; } #autocomplete{ opacity: 0.6; background: transparent;padding: 11px 11px 11px 11px; }
<script src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<textarea id="autocomplete" type="text" class="vc_textarea"></textarea>
<textarea id="mainInput" type="text" name="comments" placeholder="Write some text" class="vc_textarea"></textarea>
]1]1
Is it somehow possible to get a style property from a css class that is not used anywhere?
I'd like to read for example the color property that I want to apply to an animation with jquery ui but I want to avoid duplicating them again in the js code.
Let's say I have this:
.default-style {
color: red;
}
.disabled-style {
color: gray;
}
.current-style {}
<span class="current-style">Hello world!</span>
Now I would like to set the .default-style color to the .current-style and then animate the color from the .default-style to the .disabled-style and back on click but I don't know how to get them without creating a hidden element.
var currentColor = ""; // I'm stuck here. Get color from css class?
$("span.abc").animate({ color: currentColor });
You can cheat by creating an element, applying the class, adding the element to the document, getting its color, then removing it. If this is all done in one code block, the user will never see the element:
var div = $("<div>").addClass("default-style").appendTo(document.body);
var color = div.css("color");
div.remove();
Alternately, you can loop through document.styleSheets in the document, and loop through the rules of each stylesheet looking for the one that uses that simple class selector, then look at the styles that rule defines.
Gratuitous snippet: ;-)
var div = $("<div>").addClass("default-style").appendTo(document.body);
var color = div.css("color");
div.remove();
$("<p>The color is: " + color + " (the color of this paragraph)</p>").css("color", color).appendTo(document.body);
.default-style {
color: red;
}
.disabled-style {
color: gray;
}
.current-style {}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<span class="current-style">Hello world!</span>
Side note: jQuery's animate function doesn't animate colors natively, you need to add a plugin to do it (jQuery UI bundles one, but if you're not using jQuery UI, you can just use one of the plugins that does this, such as this one, directly).
Correct Way ! Without cheating the document
var currentColor;
var styleSheets = document.styleSheets;
for(var j=0; !currentColor && j<styleSheets.length; j++)
{
var styleSheet = styleSheets[j];
var cssprops = styleSheet.cssRules || styleSheet.rules; // .rules is for older IE
for (var i = 0; i < cssprops.length; i++) {
if(cssprops[i].selectorText == '.default-style');
currentColor = cssprops[i].style.getPropertyCSSValue('color').cssText;
}
}
$("span.abc").animate({ color: currentColor });
Reference From https://developer.mozilla.org/en-US/docs/Web/API/document.styleSheets
I need to change each item's color in a list after a reorder or removing one item, now I am using jquery's css method like below
$('li').css('background-color', color);
It works, but terribly slow, and sometimes the page will render the color incorrectly, even on Chrome, which is supposed to be fast. The list doesn't have many items, below 10, usually 5 - 7. So this performance is not acceptable. So I want to know if there is a better, faster way in CSS3, or HTML5. If not, if there is an walkaround or some kind of jquery solution?
The code for refreshing list items' color is as below. The index can be decided by a function and the color can decide color by it. The major issue I think is that changing background color trigger reflow or maybe rerendering.
function refreshListItemColor(liElements, colorGetter, indexGetter) {
colorGetter = colorGetter || (function (color) {
return color;
});
indexGetter = indexGetter || (function (liElement, index) {
return index;
});
liElements.each(function (index, liElement) {
index = indexGetter(liElement, index);
var data = ko.dataFor(liElement);
var indexColor = colorForIndex(index);
indexColor = colorGetter(indexColor, data);
if (indexColor !== $(liElement).css('background-color')) {
$(liElement).css('background-color', indexColor);
}
});
}
Update: using element.style['background-color'] won't do. The issue still remains. Another possible explanation for the lagging is that every list item itself has about 10 child elements, making change list item's color particularly expensive.
Update2: I'll try to ask a related question: is there a way to change the color of the background of the parent node without triggering a rerender of children elements?
Update3: I tried to add delay for each color change operation, like below
var delay = 100, step = 100;
liElements.each(function (index, liElement) {
index = indexGetter(liElement, index);
var data = ko.dataFor(liElement);
var indexColor = colorForIndex(index);
indexColor = colorGetter(indexColor, data);
if (indexColor !== $(liElement).css('background-color')) {
setTimeout(function () {
liElement.style['background-color'] = indexColor;
}, delay);
delay += step;
}
});
It seems can alleviate this issue a lot. I guess this will not solve the problem, but will reduce the impact to an acceptable level.
Could you use attribute selectors in your stylesheet?
[data-row="1"][data-col="3"]{
background-color: blue;
}
I noticed that If you want to select a whole row or column you have to use !important
[data-col="3"]{
background-color: blue !important;
}
(edit)Adding styles dynamically
Create a empty style tag with a div
<style type="text/css" id="dynamicstyle"></style>
and just append to it like any other tag
$("#dynamicstyle").append('[data-row="0"]{background-color:red !important;}');
for your case you can check whenever an element is added and add a row style since in theory the user could pile up all of the elements.
$(function () {
var maxRows = 0;
$("ul").bind("DOMSubtreeModified", updateStyleSheet);
function updateStyleSheet() {
var childCount = $("ul").children().length;
if (maxRows < childCount) {
maxRows = childCount;
var newRule = [
'[data-row="',
maxRows,
'"]{background-color:', ((maxRows % 2) ? "red" : "blue"),
' !important;}'].join('')
$("#dynamicstyle").append(newRule);
}
}
});
http://jsfiddle.net/PgAJT/126/
FizzBuzz rows http://jsfiddle.net/PgAJT/127/
Remove your "if", which may force browser to redraw/recompile/reflow latest CSS value.
if (indexColor !== $(liElement).css('background-color')) {
Yes, read are slow and as they will block write-combine.
Presumably, the colour is determined by the position of the element in the list.
Use nth-child or nth-of-type selectors in your stylesheet.
Hi i have just tried wat u need just check it..
http://jsbin.com/awUWAMeN/7/edit
function change()
{
var colors = ['green', 'red', 'purple'];
alert(colors)
$('.sd-list li').each(function(i) {
var index = $(this).index();
$(this).css('background-color', colors[index]);
});
}
I've created a simple test with 10 list items, each with 12 children and setting the background colour for every item each time Gridster's draggable.stop event fires. The change is pretty much instantaneous in IE11 and Chrome.
To me, this suggests it isn't the CSS rendering that's slow, but maybe the calculations determining which colours are for which elements.
This is the JavaScript I was using:
var colors = ['#000', '#001', '#002', '#003', '#004', '#005', '#006', '#007', '#008', '#009', '#00a', '#00b'];
$('.gridster ul').gridster({
widget_margins: [10, 10],
widget_base_dimensions: [120, 120],
draggable: {
stop: function (e, ui, $widget) {
refreshListItemColor();
}
}
});
function refreshListItemColor() {
var sortedElements = [];
$('ul > li:not(.preview-holder').each(function () {
sortedElements.push([this, this.getAttribute('data-col'), this.getAttribute('data-row')]);
});
sortedElements.sort(function (a, b) {
return a[1] - b[1] || a[2] - b[2];
});
for (var i = sortedElements.length - 1; i >= 0; i--) {
sortedElements[i][0].style.backgroundColor = colors[i];
}
}
How are you determining which colours to set on each list item?
I've find it fast to create a class with the css attributes you want and then add that class to the dom element you want the css attribute applied to. CSS rules appear without refresh.
css:
.bg-green{
background:green;
}
js:
$("#someDomId").toggleClass('bg-green','add');
A cool way of dealing with lists is to index the id of each list element as you create/alter it:
Create list:
for (i=0;i=m;i++){
var listElement = "<li id='"+i+">Some Content</div>";
$('ul').append(listElement);
}
Then instead of iterating through a dom element (which is expensive) you can run another for loop and alter each list element by selecting it's id.
for (i=0;i=m;i++){
$("#"+i).toggleClass('bg-green','add');
}
I have this script, which I thought was relatively simple. It basically makes a tree-layout of an iframe's contents. When parts of the tree are hovered, their corresponding iframe elements are 'selected'. But it isn't working, for the life of me. Here is some quick half-pseudo code:
function traverseTree(c,o){
//c = the tree subset to put more stuff in
//o = the element to traverse
var treeNode = D.createElement('div');
c.appendChild(treeNode);
treeNode.innerHTML = o.tagName;
treeNode['refNode'] = o;
treeNode.addEventListener('mouseover',check,false);
loop(o.children){
traverseTree(treeNode,o.child);
}
}
function check(e){
alert(e.target.refNode);
}
The problem occurs with e.target.refNode. It only gives an element reference with the first node (or the HTML tag). The rest are undefined. But, when I check treeNode.refNode right after setting it, it is always right.
EDIT:
So, I made a quick test:
<html>
<head>
<title>Test</title>
<script>
window.onload = function(){
createTree(document.getElementById('tree'),
document.getElementById('page').contentWindow.document);
}
function createTree(c,o){
//get list of children
var children = o.childNodes;
//loop through them and display them
for (var i=0;i<children.length;i++){
if (typeof(children[i].tagName) !== 'undefined'){
//Create a tree node
var node = document.createElement('div');
if (children[i].childNodes.length > 0){
node.style.borderLeft = '1px dotted black';
node.style.marginLeft = '15px';
}
c.appendChild(node);
//Reference to the actual node
node.refNode = children[i];
//Add events
node.addEventListener('mouseover',selectNode,false);
//Display what type of tag it is
node.innerHTML += "<"+children[i].tagName.toLowerCase()+">";
//Add in its child nodes
createTree(node,children[i]);
//ending tag... CRASHES THE PROGRAM
node.innerHTML += "</"+children[i].tagName.toLowerCase()+">";
}
}
}
function selectNode(e){
document.getElementById('info').innerHTML = e.target.refNode;
}
</script>
</head>
<body>
<iframe id='page' src='some_page.html'></iframe>
<div id='info' style='border-bottom:1px solid red;margin-bottom:10px;'></div>
<div id='tree'></div>
</body>
</html>
and I figured out that adding innerHTML after appending the child tree nodes was taking away the children's refNode property. So, that's where the problem is occurring.
So, I guess the solution would just be to change .innerHtml to .appendChild(document.createTextNode(...)). My script is only for local use, and only built for FF3+, so other than that, there should be no problems.
You cannot store objects in attributes of elements. What you see if you check the attribute is the string-representation of the assigned node. If you would use jquery, you could implement that with jQuery.data()