Thursday, April 3, 2014

Methods to add multi-line CSS content

I'm sure most of you know that you can add content before or after an element using css; And you can add a line break within that content (spec):

HTML
<div id="test1">Hello World</div>

CSS
#test1:after {
    content : ' of foo \A barred';
    white-space: pre; /* this line is essential */
}

Sadly, this same method doesn't work if you get your content from an attribute:

HTML
<div id="test1" data-extra=" of bar \A food">Hello World</div>

CSS
#test1:after {
    content : attr(data-extra);
    white-space: pre;
}

Example: So after some discussion with the developers of Firefox, I learned that the following alternatives do work; make sure to set the css white-space to pre.

I was hoping that these methods would work consistently and I wouldn't have to remember, so I made this blog post to remind me :)

Here is a condensed list:

content sourceCarriage ReturnExample
inline string
\A
content: "line1 \A line2"
javascript
\n
.setAttribute('data-extra', " line1 \n line2");
data-attributeinline carriage return
data-extra="line1
	line2"
&#10;
data-extra="line1 &#10 line2"
&#xA; (hex)
data-extra="line1 &#xA line2"

Here is a full example:

Thursday, January 30, 2014

Add Anchors to jQuery UI Accordion Headers

If you want jQuery UI's accordion (version 1.10.0+) to be able to open the desired section based on the window hash, you are stuck with using a hash that looks something like this "#ui-accordion-accordion-header-1", and well, the panel won't open. It requires some extra coding.

With this code you can add a link within the header which opens the panel and updates the hash. It also opens the panel based on the hash after the accordion initializes.
I originally answered this code on Stackoverflow, but thought it would be nice to have a write up with demo.
var hashId = 0,
    $accordion = $('#accordion');
if (window.location.hash) {
    $accordion.children('h3').each(function (i) {
        var txt = this.textContent.toLowerCase().replace(/\s+/g, '_');
        if ( txt === window.location.hash.slice(1) ) {
            hashId = i;
        }
    });
}
This above first block of code sets a default hash id (zero-based index of the currently open header), then looks within each accordion header's text and checks if it matches the current window hash. The line with this.textContent (may not work in older browsers, so change it to $(this).text() as needed), gets the header text and replaces any spaces, tabs, etc that don't work within an element ID. If the header text is really long, contains punctuation like commas, exclamation points, etc, then you may need to truncate the text with something like this:
var txt = $(this).text()
    .toLowerCase()
    .replace(/[^a-z\s]/g,'')
    .replace(/\s+/g, '_')
    .substring(0,10)
This code replaces any text that isn't a letter in the alphabet, or a space, replaces spaces with an underscore, then saves the first 10 characters.
Then it compares it to the window hash. The .slice(1) removes the hash ("#") before comparing it to the accordion header text.
The hash id is then set using the header index.

Now we can initialize the accordion...
$accordion.accordion({
    active: hashId,
    animate: false,
    heightStyle: 'content',
    collapsible: true,
    create: function (event, ui) {
        $accordion.children('h3').each(function (i) {
            // set id here because jQuery UI sets them as "ui-accordion-#-header-#"
            this.id = this.textContent.toLowerCase().replace(/\s+/g, '_');
            // add the anchor
            $(this).before('');
        });
        $accordion.find('.accordion-link').click(function () {
            // the active option requires a numeric value (not a string, e.g. "1")
            $accordion.accordion( "option", "active", $(this).data('index') );

            // uncomment out the return false below to prevent the header jump
            // return false;
        });
    }
});

Set the active option to initialize the accordion with the hash tag matching section open, it uses a zero-based index.

The animate option is set to false because when the user clicks on a link, the page jumps to the section... then the animation opens the panel. It sometimes animates so the entire page scrolls up and the section header is no longer at the top of the page.

The heightStyle and collapsible options are set to my personal preference, change them as desired.

Now the create option needs to contain code to add the links and make them clickable. It cycles through all of the section headers, replaces the id to match the header text - use the code that was used to match the text in the first block of code, so the code will compare the text parsed in the same way - then add the link before the header. If the link is added after the header, the accordion messes up because it is set to look for the element (a <div>) immediately following the header. The link contains a "data-index" attribute which contains the header zero-based index used to set the active accordion panel.

And finally the code to make the link clickable. After setting the "active" accordion option, you can chose to return false, or not. If not included, the selected header will jump to the top of the browser page and if included, the page will not scroll.

Lastly, you'll need to include some css to position the link image within the section header:
.ui-accordion {
    position: relative;
}
.ui-accordion .accordion-link {
    position: absolute;
    right: 2%;
    margin-top: 16px; /* adjust as needed to vertically center the icon */
    z-index: 1;
    width: 12px; /* approx 12x12 link icon */
    height: 12px;
    background: url(http://i57.tinypic.com/fyfns4.png) center center no-repeat;
}
Here is a demo of what it looks like: or, try this full screen version of the same demo

Tuesday, April 2, 2013

Regex Replacement String Math

I needed a simple method to do math within a regex replacement. Basically, a simple way for someone using a plugin to choose between a zero or one based index.

Yes, I could have just added an additional replacement like this:
var string = '&index0={index}&index1={index+1}'
  .replace(/\{index\}/, index)
  .replace(/\{index\+1\}/, index + 1);


That works! But what you if for some unknown reason needed the index to start at 2, 3 or even 10? Easy, go back in and modify the code.

Or, just use some regex math that allows you to set any number. Try this demo, and change the last input string to use "{index+10}" or even "{index-5}":

The code isn't really that complicated:
var string = '&index0={index}&index1={index+1}'
  .replace(/\{index([-+]\d+)?\}/g, function(fullstring, match){
    return index + (match ? parseInt(match, 10) : 0);
  });
It uses a replace function to take the index and add the first regex matching string obtained from the match argument.

But before that, we need to use a ternary operator to check if there is a match. If there isn't one, then add zero to the index.

We then need to parse the matched string into a numerical value so we can add it to the index. Then return the result.

I hope someone finds this useful :)