Beefy Boxes and Bandwidth Generously Provided by pair Networks
Do you know where your variables are?

comment on

( #3333=superdoc: print w/replies, xml ) Need Help??

(I reserve the right to make *sensible* silent updates or corrections in code and formatting for a day or two; until I remove this line. No one wants a post that has “update: …” repeated 20 times and if I have to proof this for an hour right now, I won’t post it.) :P

This code will give the site a more modern layout and appearance. You can thank tobyink for that part.

Fair warning

This code was just written for me. It’s not bombproof, it has no automatic tests, and including external files is a security risk if the source/host decides to exploit it or is itself hacked. I only address problems as they arise so there may be edge cases galore that I don’t know. I did not clean-up or check anything and I wrote 95% of it like 2 years ago; generally speaking, I cannot remember code I wrote last week.

I may not explain much of any of it,

As it stands, you will have to have a webserver. I am using plackup with https support. If you have your own host or something, you can definitely use that. Without supporting https this stuff won’t work unless you unblock the sources with your browser (a PITA and not a good idea).

This code does a skosh of dynamic handling of the Chatterbox.

Nodelet settings

In your Free Nodelet Settings you will need to add several lines.

<script src="//" integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo= +" crossorigin="anonymous"></script> <script src="// +n.js"></script> <script src="// +ht.min.js"></script> <script src="//"></script> <script src="//localhost:8282/pm/he.js"></script> <script src="//localhost:8282/pm/pm.2.0.js"></script> <script src="//localhost:8282/pm/pm.2.0.cb.js"></script> <link rel="stylesheet" href="//localhost:8282/pm/pm.2.0.css" type="tex +t/css" />

The jQuery and Bootstrap are from, I think, reliable CDNs. he.js is a library for handling characters and HTML entities; hence “he.” The main formatting script, pm2.js, is from tobyink’s The next JS and CSS are mine and you’ll have to serve them yourself from somewhere. Toby’s code needs Bootstrap. My code needs jQuery.


If you don’t have it installed already via node or something, just take the he.js file to serve:


"use strict"; if ( typeof $().emulateTransitionEnd !== 'function') { throw("Bootstrap doesn’t seem to be available…"); } function colorReps() { $("div.reputation").each(function(){ $(this).text().match(/\s\d/) ? $(this).css({color:"teal", opacity:0.66, fontSize:"85%"}) +: $(this).css({color:"#DF3969", opcaity:0.66 , fontSize:"85% +"}); }); } function submitVote () { // Need to unbind and/or include a spinner. console.log("submitVote…"); let $clicked = $(this); $; // Right? $clicked.closest("div").animate({opacity:0},999) .children().prop("disabled",true); let $vote = $clicked.parent("label").find("button"); let $form = $vote.closest("form"); let voteName = $vote.attr("name"); let voteNodeId = voteName.replace(/vote__/,""); let nodeId = $form.find("input[name='node_id']").val(); let formData = { node_id: nodeId ,vc: $form.find("input[name='vc']").val() ,op: $form.find("input[name='op']").val() ,sexisgreat: $form.find("input[name='sexisgreat']").val() }; formData[voteName] = $vote.val(); $.ajax({ method: $form.attr("method") ,url: $form.attr("action") ,data: formData }) .fail(function(jqXHR, textStatus, errorThrown){ console.log(jqXHR); if ( textStatus === "abort" ) return; $clicked.closest("div").stop().animate({ opacity: 1 }) +; $vote.closest("div.reputation").text([textStatus, erro +rThrown || "unknown"].join(": ")); }) .done(function( html, textStatus, jqXHR ) { console.log("here in top of done with node " + nodeId + " +and vote " + voteNodeId); console.log("div.reputation with node " + nodeId + " and v +ote " + voteNodeId); let $html = $($.parseHTML(html)); let $rep = $html.find("#rep-" + voteNodeId); console.log("received html find " + $rep.html()); $rep.css({opacity:0}); $clicked.closest("div").replaceWith($rep); $rep.animate({ opacity: 1 }, 666); let $xp = $("h1:contains(XP Nodelet)") .filter(function(){ return $(this).children().length = +== 0 }) .parent() .find("div"); // Can't use the same selector because the DOM isn't the s +ame pre-transform. let $newXp = $html.find("#XP_Nodelet td"); // Account for out of votes for day. if ( $newXp.length ) { $xp.fadeOut(100, function(){ $(this).html( $newXp.html +() ).fadeIn(100) }); } else { $xp.closest("section").fadeOut(100, function(){ $(this +).remove() }); // Remove all vote buttons too. Untested, might work. $("div.reputation").find("button").closest("div.reputa +tion") .fadeOut(100, function(){ $(this).remove() }); } colorReps(); }); } jQuery(function($) { $("body").hide().fadeIn(100); colorReps(); $("body").css({ color:"#00000a" }); // not being carried over anyw +here... let $stripe = $('<div style="font-size:1.5rem; text-align:left; po +sition:fixed; bottom:16rem; left:-11rem; transform:rotate(-90deg); li +ne-height:1rem; color:teal">Your Mother’s pm.2.0.js is loading…</div> +'); $("body").append($stripe); // Block bottom page/viewed controls. $("#nodethreads-foot form").submit(function(){ return false }); // Block voting <input type="hidden" name="op" value="vote"> $("input[name='op'][value='vote']").closest("form").submit(functio +n(){ return false }); $("input[name='node']").css({width:"100%"}); let $postBody = $("textarea[name='note_doctext']"); $postBody.css({width:"100%", fontFamily:"monospace"}); if ( $postBody.length ) { let $stash = $('<div style="display:hidden"/>'); $postBody.parent().append($stash); $ "post", $postBody.val().replace(/\s+/g,"") ); window.onbeforeunload = function() { if ( $("submit['op']").val() == "op" ) return null; if ( $("submit['op']").val() == "create" ) return null; return $"post") == $postBody.val().replace(/\s+/g," +") ? null : "Unsaved…"; }; } let $voteForm = $("form").find('input[name="op"][value="vote"]').p +arent("form"); $voteForm.find("input[name='sexisgreat']").remove(); // Ajax -> no + vote button. // DOM elements. let voteMap = { "1": '<button title="++" style="border:0; backgrou +nd-color:inherit; font-size:1.75rem; padding:2px 0; color:teal; "><sp +an class="glyphicon glyphicon-thumbs-up"></span></button>', "-1": '<button title="--" style="border:0; border-r +adius:100%; background-color:inherit; font-size:1.75rem; padding:2px +0; color:#DF3969"><span class="glyphicon glyphicon-thumbs-down"></spa +n></button>' }; $voteForm.find('input[type="radio"]').each(function (i, v) { let $label = $(v).parent("label"); let $button = $(voteMap[$(v).val()]); $button.attr("name",$(v).attr("name")); $button.attr("value",$(v).val()); $label.html($button); $button.on("click", submitVote); }); let $viewedForm = $("input[name='pageloadtime']").closest("form"); $viewedForm.submit(function(evt){ evt.preventDefault() }); $viewedForm.find("input[type='submit']").on("click", function(evt) +{ console.log(evt); console.log($(; console.log(; let $submit = $(; let formData = $viewedForm.serializeArray(); formData.push({ name:$submit.attr("name"), value:$submit.val() + }); $("body").children().fadeOut(100); console.log( $viewedForm.attr("method") + " " + $ +rialize() ); console.log(formData); $.ajax({ method: $viewedForm.attr("method") ,url: $viewedForm.attr("action") ,data: formData }) .fail(function(jqXHR, textStatus, errorThrown){ if ( textStatus === "abort" ) return; let err = [textStatus, errorThrown || "unknown"].join(": " +); let $err = $('<div style="position:absolute; top:20%; left +:50%; margin-left:-25%;"></div>'); $err.text(err).fadeIn().delay(2000).fadeOut(); // Should r +emove, wrote this once already… $("body").children().fadeIn(100); }) .done(function( html, textStatus, jqXHR ) { let newDoc ="text/html", "replace"); // html = html.replace(/<body /, '<body style="animatio +n: fadein 2s" '); html = html.replace(/<body /, '<body style="display:non +e" '); console.log(html); newDoc.write(html); newDoc.close(); }); return false; }); $stripe.fadeOut(100, function(){ $(this).text("Your Mother is load +ed with tobyink’s help").fadeIn() }); }); /* Features * Warning/confirm if leaving page with unsubmitted changes. Too aggr +essive right now. * Ajax voting on nodes. * Ajax "Check Time" interception and reload to keep page GET. To do * Abort ajax on page leave; maybe +28/109483 */


Code to try to make the Chatterbox more dynamic. It has bugs, including an initial double display, that haven’t bugged me enough to fix yet. If you use/edit it, please be careful not to put a bunch of load on the server with bad timers, etc.

// What XML generators are currently available on PerlMonks? https://p // PerlMonks Related Scripts // REQUIRES he.js "use strict"; // throw("Not ready to be incurring load from regular visits"); jQuery(function($) { let $currentMsg = $(".cb_msg"); setTimeout(function(){ $currentMsg.fadeOut(function(){ $(this).remov +e() }) }, 500000); // <span class="cb_author"><a href="?node_id=248054" title="Posted : +59:27 (1:19 before next)">Your Mother</a></span> $("table.cb_table").css({margin:"1ex 0", fontSize:"1.4rem"}); // Dup +. let $CBform = $("input[type='submit'][name='message_send']").closest +("form"); $CBform.find("input[name='message']").css({width:"100%",border:"1px +solid gray",borderRadius:"2px"}); $CBform.find("input[name='message_send']").hide(); $CBform.parent().find("hr, input[type='checkbox']").remove(); $CBform.find("label, span.msg, span.cb_more_messages").delay(15000). +slideUp(1000, function(){ $CBform.parent().find("br").remove() }); //span.cb_more_messages $CBform.parent().css({padding:0}); // Needs some click animation- $("input[type='submit']").css({border:"1px solid gray",borderRadius: +"2px",margin:"2px 3px"}); $("i.cb_quiet").wrap('<div style="margin:1em 0 1ex 0"></div>'); let intID; let chatURI = document.location.origin + "/bare/"; // .cb_table // Timer, interval, like gmail; check frequently if there are a lot +of current messages. // Less and less frequently when there is none. // getCB // displayCB function getCB ( arg ) { if ( ! arg ) arg = { timeout: 5000 }; if ( arg.timeout < 500000 ) arg.timeout += 10000; // maybe 10% of +current instead? let queryData = { "node_id":207304 // hardcode node… ,"xmlstyle":"modern" } // Get the parsed->rendere +d messages. $.ajax({ method: "GET" ,url: chatURI ,data: queryData ,dataType: "xml" }) .fail(function(jqXHR, textStatus, errorThrown){ console.log(jqXHR); console.log([textStatus, errorThrown || "unknown"].join(": + ")); console.log('setTimeout( getCB, ' + arg.timeout + ', arg ) +;'); intID = setTimeout( getCB, arg.timeout, arg ); }) .done(function( xml, textStatus, jqXHR ) { console.log("Received…", xml); if ( ! xml ) { console.log('setTimeout( getCB, ' + arg.timeout + ', a +rg );'); intID = setTimeout( getCB, arg.timeout, arg ); return false; } // Getting null sometimes. let messages = xml.getElementsByTagName("message"); if ( ! messages.length ) { console.log('setTimeout( getCB, ' + arg.timeout + ', a +rg );'); intID = setTimeout( getCB, arg.timeout, arg ); return false; } let $CBtable = $("table.cb_table"); if ( ! $CBtable.length ) { // No messages in the page curr +ently… $CBtable = $('<table class="cb_table" border="0" width +="100%" cellspacing="0" cellpadding="2"></table>'); // <i class="cb_quiet">and all is quiet...</i> // Always there by class? <i class="cb_quiet">and all +is quiet...</i> $("i.cb_quiet").replaceWith($CBtable); } $CBtable.css({margin:"1ex 0", fontSize:"1.4rem"}); // Dup… for (var i = 0; i < messages.length; i++ ) { let message = { author: messages[i].getElementsByTagName("author") +[0].textContent ,user: messages[i].getElementsByTagName("author_use +r")[0].textContent ,epoch: messages[i].getElementsByTagName("createdep +och")[0].textContent ,id: messages[i].getElementsByTagName("message_id") +[0].textContent ,text: messages[i].getElementsByTagName("parsed")[0 +].textContent }; // Skip duplicates! let renderedMessage = ` <tr class="cb_msg msg_id_${}"> <td><!-- class -> odd-row/even-row, but CSS is better --> <span class="chat"> <span class="chatfrom_${message.user}"> <span class='cb_sq_br'>[</span><span class='cb_author'><a hr +ef="?node_id=${message.user}">${}</a></span><span class +='cb_sq_br'>]</span><span class='cb_sep'>:</span> <span class='content'>${message.text}</span> </span> </span> </td> </tr>`; setTimeout(function(){ $(".msg_id_" + +Out() }, 500000); console.log(renderedMessage); $CBtable.append(renderedMessage); // I think the direc +tion is settable… } intID = setTimeout( getCB, 5000 ); }); } getCB(); // console.log( "node_id from CB form -> " + $CBform.find("input[nam +e='node_id']").val() ); $CBform.submit(function(evt){ evt.preventDefault(); sendMessage(); }); function sendMessage () { let formData = { node_id: $CBform.find("input[name='node_id']").val() ,op: $CBform.find("input[name='op']").val() // "message" ,message: he.encode( $CBform.find("input[name='message']").v +al() ) ,message_send: $CBform.find("input[name='message_send']").va +l() // "talk" }; console.log(formData); $.ajax({ method: $CBform.attr("method") ,url: $CBform.attr("action") ,data: formData }) .fail(function(jqXHR, textStatus, errorThrown){ console.log(jqXHR); console.log(".fail -> " + [textStatus, errorThrown || +"unknown"].join(": ")); }) .done(function( html, textStatus, jqXHR ) { $CBform.find("input[name='message']").val(""); clearTimeout(intID); getCB({ timeout: 10000 }); }); } $(document).on("visibilitychange", function(){ if ( document.hidden ) { console.log("Tab is hidden, ceasing ajax CB"); clearTimeout(intID); } else { getCB({ timeout: 60000 }); console.log("Tab is visible, restarting ajax CB"); } }); });


The only purpose here is to do a sort of anti-spinner. When you click I've checked all of these on the Recently Active Threads page, it blanks the page so you know something happened and shows it when the ajax has loaded it in the background. I probably should have just done it in JS but I thought I’d be doing more CSS so started a file…

body { opacity: 0 !important; transition: opacity 0.5s; }

If you don’t have a webserver already, this is how I run mine on :8282; you’ll need Plack::App::Directory. Don’t ask me how to do your own certs. I do it less than annually and it’s always a RTFM situation.

plackup -p 8282 -MPlack::App::Directory -e \ 'Plack::App::Directory->new({root => q{/path/to/the/files}})->to_app +' \ --enable-ssl \ --ssl-key-file /path/to/certs/localhost.key \ --ssl-cert-file /path/to/certs/localhost.cert

In reply to Ajax voting and modern formatting on PerlMonks by Your Mother

Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":

  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?

What's my password?
Create A New User
Domain Nodelet?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others pondering the Monastery: (8)
As of 2023-10-03 11:02 GMT
Find Nodes?
    Voting Booth?

    No recent polls found