Sublime Forum

Sunday JS

#1

Here’s a pointless JavaScript bookmarklet to play with on a Sunday. If you select some text on a web page and click the bookmarklet it will add a comment area under the text :smiley:

javascript:(function(){var d=document;var wrap=d.createElement('span');wrap.style.position='relative';var cmt=d.createElement('textarea');cmt.style.backgroundColor='yellow';cmt.style.top='1.5em';cmt.style.position='absolute';cmt.style.zIndex='99';cmt.style.height='4em';if(window.getSelection){var sel=window.getSelection();if(sel.rangeCount){var rng=sel.getRangeAt(0).cloneRange();rng.surroundContents(wrap);sel.removeAllRanges();sel.addRange(rng);wrap.insertBefore(cmt,wrap.firstChild);}}return false;})()

It won’t work for IE < 9, although I could modify it so that it does.

The comment(s) will disappear if you refresh the page. Pretty pointless - although, if you’re reading a detailed page it could be handy to jot a few notes down as you progress, I suppose :question:

If you want instructions to set this up come back to me. Andy.

0 Likes

#2

Neat! If you wanted to extend “Sunday JS” into Monday JS, here’s an idea for ya… use HTML5 storage to store your notes so they persist on refresh. I’m not sure how well this would work with a bookmarklet, for example, on refresh the data would be in localStorage, but you’d have to click the bookmarklet to retrieve it. But might be worth exploring. :smile:

Here’s a sample with working code (view source):
html5demos.com/storage

0 Likes

#3

@SeanWcom

Thank you: I need a “project” to help me study :smile: . I keep meaning to look at HTML5 in more detail, but have lacked motivation to study it “cold”.

Andy.

0 Likes

#4

Yes, if I give each comment a class-name of ‘cmt’ I can save them in localStorage:

javascript: (function(){ var d=document; var els=d.getElementsByClassName('cmt'); for(var i=0;i<els.length;i++){ localStorage'cmt'+i]=els*.value; } return false; } )()
I am able to then retrieve them later when I revisit the page:

javascript: (function(){ for(var i=0;i<localStorage.length;i++){ window.alert(localStorage[localStorage.key(i)]); } return false; } )()
Other than alerts I could append them to the body (bottom of the page) as paragraphs, or perhaps as another textarea that can be copied from.

However, I would ideally like to re-instate them as comments - at their original positions?! This is tricky… How could I determine their original position?

I suppose I could store the word they were attached to and later find the first occurrence of this word… I suppose :question:. Suggestions welcome, Andy.*

0 Likes

#5

Couple ideas off the top of my head… the first one, which would really only work on a fixed-width page, would be to store the comment’s x/y position on the page (or the original text’s position). But in a fluid width page, that location isn’t guaranteed. Not the best idea unless you’re looking for a quick fix.

The second idea, which is better than the first, would be to create a DOM query string for that text when you are saving the note. Something like this:

var elements = $("*:contains('the original highlighted string')");

That will return an array of elements that contain that string. You’d have to account for multiple results. Maybe by getting that string’s parent elements and creating a big selector all the way from body down… something that might ultimately look like “body div.content div.entry p:contains(‘asdf’)”. You might still have multiple results in that last paragraph element, but once you get down to the raw text, that’s the best you can do.

So you’d eventually have that string that you could store in localStorage and reuse as your selector for that particular note.

There’s obviously a lot of missing links here, but that might get an idea or two rolling in your head. :smile:

EDIT: I use jQuery in just about everything I do, and sadly for me, I’ve learned most of my Javascript knowledge by using jQuery. So, those selectors are certainly possible w/o the use of jQuery - but you’re on your own there. :smile: … although you can have your bookmarklet include external scripts (i.e., check for jquery already being loaded, if not yet loaded, add it to the page).

EDIT #2: Meant to post this originally and forgot. I’ve had it bookmarked for awhile and thought you might find it useful: dbushell.com/2012/05/22/javascript-bookmarklets/

0 Likes

#6

I seem to be in a minority, having studied JS before jQuery. I recommend it :smile:. It took no time at all to learn jQuery, and I’m able to make use of pure JS when appropriate.

I can append a textarea to the body to list the comments:

javascript: (function(){ var d=document; var newel=d.createElement('textarea'),str='Comments:\n'; newel.id='thecmts'; for(var i=0;i<localStorage.length;i++){ str=str+(localStorage[localStorage.key(i)])+'\n'; } newel.value=str; d.body.appendChild(newel); return false; } )()
[Annoyingly, the page I was using to test had no body tag, Doh!]

A variation on your query idea (querySelector() in JS) is to navigate backwards when adding the comment. That is, to navigate to the parent, parent,… element. But I would also have to count sibling elements. That is, if the comment is within a paragraph, then I would need to determine which paragraph number it is.

I’ll think a bit further… there may be a way to get the comment “in the vicinity” of its original location. Otherwise, all this navigating is likely to get quite involved, and it’s not likely to be 100% foolproof. Andy.

Just occurred to me. There should be a simpler way to navigate up to the parent, then body, and count the siblings (to the parent that is not the body).

Added: Thank you for that link :wink:

0 Likes

#7

[quote=“agibsonsw”]I seem to be in a minority, having studied JS before jQuery. I recommend it :smile:. It took no time at all to learn jQuery, and I’m able to make use of pure JS when appropriate.
[/quote]

I think it should be mandatory for people to learn JS before jQuery. Too many answers to JS questions on the web start with “Using jQuery you can just…”. I always find it refreshing to see answers that are posed as “you can do it this way…or with jQuery you can…”; it really shows the person’s competence with the language when you see they are not exclusively tied to the jQuery framework. Don’t get me wrong though, I like jQuery :smile:.

0 Likes

#8

@facelessuser Quite agree :sunglasses:. Too many do not bother with JS at all. And, as you indicate, too many searches that I make *assume *jQuery.

Current challenge:
I can navigate all the way backwards to the body, noting the type of element *just prior *to the body. Assuming this is a, for example, ‘p’ paragraph, can anyone suggest a simple way to determine which (index) the current paragraph is? That is, what position it is in, in relation to its siblings?

Added: a messy way to do this would be give the para an id, then loop through all the paras checking for this id :astonished:

Added more: I suppose I could give the current parent an id, loop through ALL elements - getElementsByTagName(’*’) :question: then note the index for this item. Then I wouldn’t have to bother navigating around too much/ at all…

0 Likes

#9

Think I’m just about there :smiley:

When I ‘add comment’ I’ll attach the selected word as a property to the comment-element;
When I ‘save comments’ I’ll also store the selected word, but use the elements-index (within the document) as part of the localStorage key:

javascript: (function(){ var d=document; var all=d.getElementsByTagName('*'); for(var i=0;i<all.length;i++){ if(all*.className=='cmt'){ localStorage'cmt'+i]=all*.value; // also store the attached word } } return false; } )();
When I ‘append comments’ I can extract the index number (from the storage-key) and also use querySelector to find the attached word (within the element):

javascript: (function(){ for(var i=0;i<localStorage.length;i++){ x=parseInt((localStorage.key(i)).substr(3)); window.alert(x); // locate this xth element and find the attached word within it! } return false; } )();

All I need to do is create the code… and then discover a reason why I’m doing this :laughing:**

0 Likes

#10

Almost there, but not quite - if anyone’s willing to assist :wink: just for the fun of it :wink:

I’m giving the wrapper element the classname ‘wrap’. But I need to adjust the stored index-numbers because I’ve added (temporarily) new elements to the page. Here’s the whole code (3 bookmarklets) I have so far:

[code]javascript:
(function(){
var d=document;
var wrap=d.createElement(‘span’);
wrap.style.position=‘relative’;
wrap.className=‘wrap’;
var cmt=d.createElement(‘textarea’);
cmt.style.backgroundColor=‘yellow’;
cmt.style.top=‘1.5em’;
cmt.style.position=‘absolute’;
cmt.style.zIndex=‘99’;
cmt.className=‘cmt’;
cmt.style.height=‘4em’;
if(window.getSelection){
var sel=window.getSelection();
if(sel.rangeCount){
var rng=sel.getRangeAt(0).cloneRange();
rng.surroundContents(wrap);
sel.removeAllRanges();
sel.addRange(rng);
wrap.insertBefore(cmt,wrap.firstChild);
cmt.thetext=rng;
}
}
return false;
}
)();

javascript:
(function(){
var d=document;
var all=d.getElementsByTagName(’’),j=0;
for(var i=0;i<all.length;i++){
if(all
.className==‘wrap’){
localStorage’cmt’+(i-j)]=all*.value;
localStorage’wrd’+(i-j)]=all*.thetext;
j+=2;
}
}
return false;
}
)();

javascript:
(function(){
var d=document;
for(var i=localStorage.length-1;i>=0;i–){
var skey=localStorage.key(i);
x=parseInt((skey).substr(3));
if(skey.substr(0,3)==‘cmt’){
alert(x+’ ‘+localStorage’wrd’+x]+’\n’+localStorage’cmt’+x]);
found=d.getElementsByTagName(’*’)[x];
found.style.color=‘red’;
}
}
return false;
}
)();
[/code]
The third section isn’t quite working as it’s not applying red colouring to the correct (parent) element.

When I’m searching to decide where to insert the previously saved comments (the 3rd code section) I’ll need to search from the bottom upwards, as I will be inserting new nodes.

I can’t use querySelector() to find the previously stored word, because it returns an element. That is, it will return the *parent ***that contains the word - which I already have anyway. I might have to extract the text from the parent and just do a JS string search. It will then be a little messy as I’ll have to rebuild the whole text-content and re-insert it.

If I get this working I suppose it may have some uses. You can comment web-pages and, when you revisit the page later, you’ll be able to reinstate the comments. I should even be able to reduce it to two (or possibly one) bookmarklet(s).*

0 Likes

#11

Subtracting 1 from x in the third section (shown here) seems good:

javascript: (function(){ var d=document; for(var i=localStorage.length-1;i>=0;i--){ var skey=localStorage.key(i); x=parseInt((skey).substr(3)); if(skey.substr(0,3)=='cmt'){ alert(x+' '+localStorage'wrd'+x]+'\n'+localStorage'cmt'+x]); found=d.getElementsByTagName('*')[x-1]; found.style.color='red'; } } return false; } )();
I’ll have to test this a little and then, if it’s working(?), concentrate on attaching the comment to the correct word. But any input, suggestions, are welcome. Andy.

0 Likes

#12

Not to derail the actual conversation in this thread, but had to chime in and defend myself (I know you weren’t calling me out, but I still feel compelled…). :laughing:

You are right - learning Javascript first would be the best way, so I feel that I should clarify - I didn’t just dive into jQuery before knowing javascript. Quite the opposite. I knew plenty of javascript but had always scoffed at it’s potential. Shortly after “Web 2.0” became all the rage, I figured I’d better learn this “ajax” stuff. That’s when I started with jQuery.

I’m a developer. I write code for a living. I know a lot of different languages and writing code is more than just a job. It’s my passion. I love to learn new things, I just happened to learn this side of things in an odd way. Hehe… So, these days when I sit down to work on a project I usually get a POC in place using jQuery - then I work backwards to remove it. I don’t like dependencies. It’s a pet peeve of mine. I realize that’s bass ackwards, but it works for me. :smile:

That said - carry on. :smile:

0 Likes

#13

@SeanWcom
You are right, I wasn’t calling you out; I was just chiming in on @agibsonw’s remark. I taught myself JavaScript (I do firmware not web development), and I remember looking around for examples of ways to do certain things, and I saw a lot of “Just use jQuery”. I found that frustrating because that wasn’t teaching me about the techniques used. I kind of like doing things the hard way at first so that I understand better what I am learning. So that was just me saying “me too!”.

In school, we had a crash course in Assembly (I am an EE major), and everyone was given a choice to do the final project in C or Assembly, I was the only one (dumb enough?) who opted to do the whole thing in Assembly. I hope that paints a better picture of my personality and why I say what I do :smile:.

0 Likes

#14

Assembly! Yikes! Yeah, you’re way out of my league! Hahah… I originally went to school as a Comp Sci major and started learning C/C++ and gave up. That was before I’d ever written a line of code in my life. Looking back, I wish I had stuck with it. These days I’m a fairly competent C# dev, but really wish my roots were in C++… but with two small kids at home, the likelihood of me learning it… nil. Lol!

0 Likes

#15

Well, I did get through the Assembly project, but I won’t say I am an expert…far from it; I am just stubborn and very determined. I never actually knew much C/C++ when I started doing firmware; just enough to be dangerous. Companies are like that with engineers, I guess they just figure we will learn it one way or the other. Once you learn a couple languages, you start to see they are all pretty much the same, and learning new languages comes quicker and quicker. But yeah, unless your job requires you to learn it, it is tough to learn it at home with kids. I also have two kids, and they are exhausting; great, but exhausting :smile:.

0 Likes

#16

I’m making some progress with this but would appreciate advice or suggestions :smile: . I appreciate it’s not the most important “feature” ever created, but it’s keeping me amused :wink:

The bookmarklet code is below, in three parts: ‘Add a comment’, ‘Save the comments’ and ‘Show the comments’.

  1. I think I need to sort the localStorage by key before showing/inserting the (previously saved) comments. This is because it currently doesn’t insert them in the correct/reverse order, and then the “position” to insert the next comment is out-of-step with the element-indexing. How can I sort the localStorage by key?

  2. I’m using innerHTML to re-insert the comments. It works (almost!) but I would prefer to use ‘range’ objects - this might prove more robust. Is this doable?

  3. localStorage seems to be “per site/domain” rather than per page. Is there a setting to make it per page? If not, I might have to store the page/location as part of the key(s). Alternatively, and easier, can I store another dictionary in localStorage? In which case I could use the location as the main dictionary-key (having stripped out the domain, bookmarks, query string, and encodeURIComponent-ed it)?

[code]javascript:
(function(){
var d=document;
var wrap=d.createElement(‘span’);
wrap.style.position=‘relative’;
wrap.className=‘wrap’;
var cmt=d.createElement(‘textarea’);
cmt.style.backgroundColor=‘yellow’;
cmt.style.top=‘1.5em’;
cmt.style.position=‘absolute’;
cmt.style.zIndex=‘99’;
cmt.className=‘cmt’;
cmt.style.height=‘4em’;
if(window.getSelection){
var sel=window.getSelection();
if(sel.rangeCount){
var rng=sel.getRangeAt(0).cloneRange();
rng.surroundContents(wrap);
sel.removeAllRanges();
sel.addRange(rng);
wrap.insertBefore(cmt,wrap.firstChild);
cmt.thetext=rng;
}
}
return false;
}
)();

javascript:
(function(){
var d=document;
localStorage.clear();
var all=d.getElementsByTagName(’’),j=0;
for(var i=0;i<all.length;i++){
if(all
.className==‘wrap’){
localStorage’cmt’+(i-j)]=all*.value;
localStorage’wrd’+(i-j)]=all*.thetext;
j+=2;
}
}
return false;
}
)();

javascript:
(function(){
var d=document;
for(var i=localStorage.length-1;i>=0;i–){
var skey=localStorage.key(i);
var x=parseInt((skey).substr(3));
if(skey.substr(0,3)==‘cmt’){
var found=d.getElementsByTagName(’’)[x-1];
var fndtext=found.innerHTML;
var newtext="";
newtext += “<textarea style=“position:absolute;background-color:yellow;zindex:99;top:1.5em;height:4em;” class=‘cmt’>”;
newtext += localStorage’cmt’+x]+"
";
fndtext=fndtext.replace(localStorage’wrd’+x],newtext+localStorage’wrd’+x]);
found.innerHTML=fndtext;
}
}
return false;
}
)();[/code]
**

0 Likes