1/*! 2* Clamp.js 0.5.1 3* 4* Copyright 2011-2013, Joseph Schmitt http://joe.sh 5* Released under the WTFPL license 6* http://sam.zoy.org/wtfpl/ 7*/ 8 9(function(){ 10 /** 11 * Clamps a text node. 12 * @param {HTMLElement} element. Element containing the text node to clamp. 13 * @param {Object} options. Options to pass to the clamper. 14 */ 15 function clamp(element, options) { 16 options = options || {}; 17 18 var self = this, 19 win = window, 20 opt = { 21 clamp: options.clamp || 2, 22 useNativeClamp: typeof(options.useNativeClamp) != 'undefined' ? options.useNativeClamp : true, 23 splitOnChars: options.splitOnChars || ['.', '-', 'â', 'â', ' '], //Split on sentences (periods), hypens, en-dashes, em-dashes, and words (spaces). 24 animate: options.animate || false, 25 truncationChar: options.truncationChar || 'â¦', 26 truncationHTML: options.truncationHTML 27 }, 28 29 sty = element.style, 30 originalText = element.innerHTML, 31 32 supportsNativeClamp = typeof(element.style.webkitLineClamp) != 'undefined', 33 clampValue = opt.clamp, 34 isCSSValue = clampValue.indexOf && (clampValue.indexOf('px') > -1 || clampValue.indexOf('em') > -1), 35 truncationHTMLContainer; 36 37 if (opt.truncationHTML) { 38 truncationHTMLContainer = document.createElement('span'); 39 truncationHTMLContainer.innerHTML = opt.truncationHTML; 40 } 41 42 43// UTILITY FUNCTIONS __________________________________________________________ 44 45 /** 46 * Return the current style for an element. 47 * @param {HTMLElement} elem The element to compute. 48 * @param {string} prop The style property. 49 * @returns {number} 50 */ 51 function computeStyle(elem, prop) { 52 if (!win.getComputedStyle) { 53 win.getComputedStyle = function(el, pseudo) { 54 this.el = el; 55 this.getPropertyValue = function(prop) { 56 var re = /(\-([a-z]){1})/g; 57 if (prop == 'float') prop = 'styleFloat'; 58 if (re.test(prop)) { 59 prop = prop.replace(re, function () { 60 return arguments[2].toUpperCase(); 61 }); 62 } 63 return el.currentStyle && el.currentStyle[prop] ? el.currentStyle[prop] : null; 64 } 65 return this; 66 } 67 } 68 69 return win.getComputedStyle(elem, null).getPropertyValue(prop); 70 } 71 72 /** 73 * Returns the maximum number of lines of text that should be rendered based 74 * on the current height of the element and the line-height of the text. 75 */ 76 function getMaxLines(height) { 77 var availHeight = height || element.clientHeight, 78 lineHeight = getLineHeight(element); 79 80 return Math.max(Math.floor(availHeight/lineHeight), 0); 81 } 82 83 /** 84 * Returns the maximum height a given element should have based on the line- 85 * height of the text and the given clamp value. 86 */ 87 function getMaxHeight(clmp) { 88 var lineHeight = getLineHeight(element); 89 return lineHeight * clmp; 90 } 91 92 /** 93 * Returns the line-height of an element as an integer. 94 */ 95 function getLineHeight(elem) { 96 var lh = computeStyle(elem, 'line-height'); 97 if (lh == 'normal') { 98 // Normal line heights vary from browser to browser. The spec recommends 99 // a value between 1.0 and 1.2 of the font size. Using 1.1 to split the diff. 100 lh = parseInt(computeStyle(elem, 'font-size')) * 1.2; 101 } 102 return parseInt(lh); 103 } 104 105 106// MEAT AND POTATOES (MMMM, POTATOES...) ______________________________________ 107 var splitOnChars = opt.splitOnChars.slice(0), 108 splitChar = splitOnChars[0], 109 chunks, 110 lastChunk; 111 112 /** 113 * Gets an element's last child. That may be another node or a node's contents. 114 */ 115 function getLastChild(elem) { 116 //Current element has children, need to go deeper and get last child as a text node 117 if (elem.lastChild.children && elem.lastChild.children.length > 0) { 118 return getLastChild(Array.prototype.slice.call(elem.children).pop()); 119 } 120 //This is the absolute last child, a text node, but something's wrong with it. Remove it and keep trying 121 else if (!elem.lastChild || !elem.lastChild.nodeValue || elem.lastChild.nodeValue == '' || elem.lastChild.nodeValue == opt.truncationChar) { 122 elem.lastChild.parentNode.removeChild(elem.lastChild); 123 return getLastChild(element); 124 } 125 //This is the last child we want, return it 126 else { 127 return elem.lastChild; 128 } 129 } 130 131 /** 132 * Removes one character at a time from the text until its width or 133 * height is beneath the passed-in max param. 134 */ 135 function truncate(target, maxHeight) { 136 if (!maxHeight) {return;} 137 138 /** 139 * Resets global variables. 140 */ 141 function reset() { 142 splitOnChars = opt.splitOnChars.slice(0); 143 splitChar = splitOnChars[0]; 144 chunks = null; 145 lastChunk = null; 146 } 147 148 var nodeValue = target.nodeValue.replace(opt.truncationChar, ''); 149 150 //Grab the next chunks 151 if (!chunks) { 152 //If there are more characters to try, grab the next one 153 if (splitOnChars.length > 0) { 154 splitChar = splitOnChars.shift(); 155 } 156 //No characters to chunk by. Go character-by-character 157 else { 158 splitChar = ''; 159 } 160 161 chunks = nodeValue.split(splitChar); 162 } 163 164 //If there are chunks left to remove, remove the last one and see if 165 // the nodeValue fits. 166 if (chunks.length > 1) { 167 // console.log('chunks', chunks); 168 lastChunk = chunks.pop(); 169 // console.log('lastChunk', lastChunk); 170 applyEllipsis(target, chunks.join(splitChar)); 171 } 172 //No more chunks can be removed using this character 173 else { 174 chunks = null; 175 } 176 177 //Insert the custom HTML before the truncation character 178 if (truncationHTMLContainer) { 179 target.nodeValue = target.nodeValue.replace(opt.truncationChar, ''); 180 element.innerHTML = target.nodeValue + ' ' + truncationHTMLContainer.innerHTML + opt.truncationChar; 181 } 182 183 //Search produced valid chunks 184 if (chunks) { 185 //It fits 186 if (element.clientHeight <= maxHeight) { 187 //There's still more characters to try splitting on, not quite done yet 188 if (splitOnChars.length >= 0 && splitChar != '') { 189 applyEllipsis(target, chunks.join(splitChar) + splitChar + lastChunk); 190 chunks = null; 191 } 192 //Finished! 193 else { 194 return element.innerHTML; 195 } 196 } 197 } 198 //No valid chunks produced 199 else { 200 //No valid chunks even when splitting by letter, time to move 201 //on to the next node 202 if (splitChar == '') { 203 applyEllipsis(target, ''); 204 target = getLastChild(element); 205 206 reset(); 207 } 208 } 209
210 //If you get here it means still too big, let's keep truncating 211 if (opt.animate) { 212 setTimeout(function() { 213 truncate(target, maxHeight); 214 }, opt.animate === true ? 10 : opt.animate); 215 } 216 else { 217 return truncate(target, maxHeight); 218 } 219 } 220 221 function applyEllipsis(elem, str) { 222 elem.nodeValue = str + opt.truncationChar; 223 } 224 225 226// CONSTRUCTOR ________________________________________________________________ 227 228 if (clampValue == 'auto') { 229 clampValue = getMaxLines(); 230 } 231 else if (isCSSValue) { 232 clampValue = getMaxLines(parseInt(clampValue)); 233 } 234 235 var clampedText; 236 if (supportsNativeClamp && opt.useNativeClamp) { 237 sty.overflow = 'hidden'; 238 sty.textOverflow = 'ellipsis'; 239 sty.webkitBoxOrient = 'vertical'; 240 sty.display = '-webkit-box'; 241 sty.webkitLineClamp = clampValue; 242 243 if (isCSSValue) { 244 sty.height = opt.clamp + 'px'; 245 } 246 } 247 else { 248 var height = getMaxHeight(clampValue); 249 if (height <= element.clientHeight) { 250 clampedText = truncate(getLastChild(element), height); 251 } 252 } 253 254 return { 255 'original': originalText, 256 'clamped': clampedText 257 } 258 } 259 260 window.$clamp = clamp; 261})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.