1 /** 2 * CutterClasses is the config singleton for classes used in Cutter 3 * @class CutterClasses 4 * @constructor 5 * @author Tomas Corral Casas 6 * @version 1.0 7 * @type Object 8 */ 9 var CutterClasses = function() 10 { 11 this.more = "more"; 12 }; 13 /** 14 * CutterTexts is the config singleton for texts used in Cutter 15 * @class CutterTexts 16 * @constructor 17 * @author Tomas Corral Casas 18 * @version 1.0 19 * @type Object 20 */ 21 var CutterTexts = function() 22 { 23 this.more = "View more"; 24 }; 25 /** 26 * Cutter is a class that allows HTML code to cut a number of words contained in the nodes, keeping intact the HTML markup. 27 * @author Tomas Corral Casas 28 * @version 1.0 29 * @class Cutter 30 * @constructor 31 * @type Object 32 */ 33 var Cutter = function() 34 { 35 /** 36 * oApplyTo is the Dom object where we want to use the cutter. 37 * @member Cutter.prototype 38 * @author Tomas Corral Casas 39 * @version 1.0 40 * @type Object 41 */ 42 this.oApplyTo = null; 43 /** 44 * oBackupApplyTo is the clone from Dom object to get it when showing the content. 45 * @member Cutter.prototype 46 * @author Tomas Corral Casas 47 * @version 1.0 48 * @type Object 49 */ 50 this.oBackupApplyTo = null; 51 /** 52 * oTarget is the Dom object where to put the cutted code 53 * @member Cutter.prototype 54 * @author Tomas Corral Casas 55 * @version 1.0 56 * @type Object 57 */ 58 this.oTarget = null; 59 /** 60 * oClasses is the config singleton for classes used in Cutter 61 * @member Cutter.prototype 62 * @author Tomas Corral Casas 63 * @version 1.0 64 * @type Object 65 */ 66 this.oClasses = new CutterClasses(); 67 /** 68 * oTexts is the config singleton for texts used in Cutter 69 * @member Cutter.prototype 70 * @author Tomas Corral Casas 71 * @version 1.0 72 * @type Object 73 */ 74 this.oTexts = new CutterTexts(); 75 /** 76 * nWords is the number of words to Cut 77 * @member Cutter.prototype 78 * @author Tomas Corral Casas 79 * @version 1.0 80 * @type Number 81 */ 82 this.nWords = 0; 83 /** 84 * nWordsCounter is the counter of words when finding them in code 85 * @member Cutter.prototype 86 * @author Tomas Corral Casas 87 * @version 1.0 88 * @type Number 89 */ 90 this.nWordsCounter = 0; 91 /** 92 * oViewMore is a reference to the "see more" link. 93 * @member Cutter.prototype 94 * @author Tomas Corral Casas 95 * @version 1.0 96 * @type Object 97 */ 98 this.oViewMore = null; 99 /** 100 * oSerialized is the JSON object where Cutter serializes all the DOM objects inside the oApplyTo Dom element 101 * @member Cutter.prototype 102 * @author Tomas Corral Casas 103 * @version 1.0 104 * @type Object 105 */ 106 this.oSerialized = {}; 107 /** 108 * oDocumentFragment is the DocumentFragment where the Dom elements are inserted before insert on Target 109 * @member Cutter.prototype 110 * @author Tomas Corral Casas 111 * @version 1.0 112 * @type Object 113 */ 114 this.oDocumentFragment = document.createDocumentFragment(); 115 /** 116 * bTest is a property to now if you want to test the Cutter class 117 * This is used to change the type of id for each element. 118 * false by default. 119 * @member Cutter.prototype 120 * @author Tomas Corral Casas 121 * @version 1.0 122 * @type Boolean 123 */ 124 this.bTest = false; 125 /** 126 * nIdTest is the property for testing that will save the order for id. 127 * 0 by default. 128 * @member Cutter.prototype 129 * @author Tomas Corral Casas 130 * @version 1.0 131 * @type Number 132 */ 133 this.nIdTest = 0; 134 /** 135 * bNeedViewMore is a property that will check it it's necessary to add the link to view more or not 136 * It checks if its necessary if for some reason the content is cutted. 137 * false by default. 138 * @member Cutter.prototype 139 * @author Tomas Corral Casas 140 * @version 1.0 141 * @type Boolean 142 */ 143 this.bNeedViewMore = false; 144 /** 145 * bNotViewMore is a property that could be setted by the user to add or not the link to view more content if needed. 146 * false by default. 147 * @member Cutter.prototype 148 * @author Tomas Corral Casas 149 * @version 1.0 150 * @type Boolean 151 */ 152 this.bNotViewMore = false; 153 }; 154 /** 155 * applyTo is the method that sets the Dom object where to apply the cutter 156 * @member Cutter.prototype 157 * @author Tomas Corral Casas 158 * @version 1.0 159 * @param {object} oApplyTo This is the Dom element 160 * @return the instance of the Cutter 161 * @type Object 162 */ 163 Cutter.prototype.applyTo = function(oApplyTo) 164 { 165 if(!oApplyTo) 166 { 167 return this; 168 } 169 this.oApplyTo = oApplyTo; 170 this.oBackupApplyTo = oApplyTo.cloneNode(true); 171 return this; 172 }; 173 /** 174 * setTarget is the method that sets the Dom object where to put the cutted code 175 * @member Cutter.prototype 176 * @author Tomas Corral Casas 177 * @version 1.0 178 * @param {object} oTarget This is the Dom element 179 * @return the instance of the Cutter 180 * @type Object 181 */ 182 Cutter.prototype.setTarget = function(oTarget) 183 { 184 if(!oTarget) 185 { 186 return this; 187 } 188 this.oTarget = oTarget; 189 return this; 190 }; 191 /** 192 * setClasses is the method that sets the config singleton of Classes used in Cutter 193 * @member Cutter.prototype 194 * @author Tomas Corral Casas 195 * @version 1.0 196 * @param {object} oClasses This is the singleton config 197 * @return the instance of the Cutter 198 * @type Object 199 */ 200 Cutter.prototype.setClasses = function(oClasses) 201 { 202 if(!oClasses) 203 { 204 return this; 205 } 206 this.oClasses = oClasses; 207 return this; 208 }; 209 /** 210 * setTexts is the method that sets the config singleton of Texts used in Cutter 211 * @member Cutter.prototype 212 * @author Tomas Corral Casas 213 * @version 1.0 214 * @param {object} oTexts This is the singleton config 215 * @return the instance of the Cutter 216 * @type Object 217 */ 218 Cutter.prototype.setTexts = function(oTexts) 219 { 220 if(!oTexts) 221 { 222 return this; 223 } 224 this.oTexts = oTexts; 225 return this; 226 }; 227 /** 228 * setWords is the method used to set the max number of words before cut the code. 229 * @member Cutter.prototype 230 * @author Tomas Corral Casas 231 * @version 1.0 232 * @param {number} nWords This is the number of words to see. 233 * @return the instance of the Cutter 234 * @type Object 235 */ 236 Cutter.prototype.setWords = function(nWords) 237 { 238 if(!nWords) 239 { 240 return this; 241 } 242 this.nWords = nWords - 1; 243 return this; 244 }; 245 /** 246 * _trim is an utilities method used to keep out all the spaces before or after the sentence 247 * @member Cutter.prototype 248 * @author Tomas Corral Casas 249 * @version 1.0 250 * @param {string} sString This is the text to be trimmed 251 * @private 252 * @return the trimmed string 253 * @type String 254 */ 255 Cutter.prototype._trim = function(sString) 256 { 257 return sString.replace(/^\s+/g, '').replace(/\s+$/g, ''); 258 }; 259 /** 260 * _countWords is an utilities method used to count the words in a String 261 * @member Cutter.prototype 262 * @author Tomas Corral Casas 263 * @version 1.0 264 * @param {string} sText The text where to know how many words are. 265 * @private 266 * @return the number of words in the string 267 * @type Number 268 */ 269 Cutter.prototype._countWords = function(sText) 270 { 271 return this._trim(sText).split(" ").length; 272 }; 273 /** 274 * _getOnlyNumberOfWords is an utilities method used to get a number of words from the string 275 * @member Cutter.prototype 276 * @author Tomas Corral Casas 277 * @version 1.0 278 * @param {string} sString The text from which to extract the words 279 * @param {number} nWords The number of words to get 280 * @private 281 * @return the number of words in the string 282 * @type Number 283 */ 284 Cutter.prototype._getOnlyNumberOfWords = function(sString, nWords) 285 { 286 return this._trim(sString).split(" ").splice(0, nWords).join(" "); 287 }; 288 /** 289 * createViewMore is the method that creates the link to see all the content again 290 * @member Cutter.prototype 291 * @author Tomas Corral Casas 292 * @version 1.0 293 * @private 294 */ 295 Cutter.prototype.createViewMore = function() 296 { 297 var oLink = document.createElement("a"); 298 oLink.className = this.oClasses.more; 299 oLink.title = this.oTexts.more; 300 oLink.innerHTML = this.oTexts.more; 301 this.oViewMore = oLink; 302 }; 303 /** 304 * _getFirstElementOfObject is an utilities method used to get the first element in a JSON object 305 * @member Cutter.prototype 306 * @author Tomas Corral Casas 307 * @version 1.0 308 * @param {object} oObject The JSON object from which to obtain the first element 309 * @private 310 * @return the first element in the JSON object 311 * @type Object 312 */ 313 Cutter.prototype._getFirstElementOfObject = function(oObject) 314 { 315 var oFirstElement = undefined; 316 for (var sKey in oObject) 317 { 318 if (oObject.hasOwnProperty(sKey)) 319 { 320 oFirstElement = oObject[sKey]; 321 break; 322 } 323 } 324 return oFirstElement; 325 }; 326 /** 327 * deserializeObject is an utilities method used to deserialize a JSON object in a Dom element 328 * @member Cutter.prototype 329 * @author Tomas Corral Casas 330 * @version 1.0 331 * @param {object} oSerialized The JSON object from which to obtain the Dom element information 332 * @param {object} oParent The Dom element where to add the new Dom element 333 * @private 334 */ 335 Cutter.prototype.deserializeObject = function(oSerialized, oParent) 336 { 337 var oDom = undefined; 338 if (oSerialized.nodeType == 1) 339 { 340 oDom = document.createElement(oSerialized.tagName); 341 if (typeof oSerialized.attributes != "undefined") 342 { 343 if(typeof jQuery !== "undefined") 344 { 345 $(oDom).attr(oSerialized.attributes); 346 }else 347 { 348 for(var sKey in oSerialized.attributes) 349 { 350 if(oSerialized.attributes.hasOwnProperty(sKey)) 351 { 352 oDom.setAttribute(sKey, oSerialized.attributes[sKey]); 353 } 354 } 355 } 356 } 357 oParent.appendChild(oDom); 358 } 359 else 360 if (oSerialized.nodeType == 3) 361 { 362 if (typeof oSerialized.textContent != "undefined") 363 { 364 oDom = document.createTextNode(oSerialized.textContent); 365 } 366 else 367 { 368 if (oSerialized.data) 369 { 370 oDom = document.createTextNode(oSerialized.data); 371 } 372 else 373 { 374 oDom = document.createTextNode(oSerialized.innerText); 375 } 376 } 377 378 oParent.appendChild(oDom); 379 } 380 if (typeof oSerialized.childNodes != "undefined") 381 { 382 this.loopOnDeserialize(oSerialized.childNodes, oDom); 383 } 384 }; 385 /** 386 * loopOnDeserialize is an utilities method used to loop over all the serialized elements 387 * @member Cutter.prototype 388 * @author Tomas Corral Casas 389 * @version 1.0 390 * @param {object} oSerializedElements The JSON object on which to run the loop 391 * @private 392 */ 393 Cutter.prototype.loopOnDeserialize = function(oSerializedElements, oParent) 394 { 395 for (var sKey in oSerializedElements) 396 { 397 if (oSerializedElements.hasOwnProperty(sKey)) 398 { 399 this.deserializeObject(oSerializedElements[sKey], oParent); 400 } 401 } 402 }; 403 /** 404 * deserializeSerializedObject is an utilities method used to deserialize all the Dom elements that where serialized and is where the cut is applied. 405 * This method is the core of the class. 406 * @member Cutter.prototype 407 * @author Tomas Corral Casas 408 * @version 1.0 409 * @param {object} oSerialized The JSON object to deserialize 410 * @param {object} oParent The DOM object where to put the new finished code. 411 * @private 412 */ 413 Cutter.prototype.deserializeSerializedObject = function(oSerialized, oParent) 414 { 415 var bLoopOnChilds = false; 416 if (typeof oSerialized == "undefined") 417 { 418 oSerialized = this._getFirstElementOfObject(this.oSerialized); 419 this.oDocumentFragment = document.createDocumentFragment(); 420 bLoopOnChilds = true; 421 } 422 if (typeof oParent == "undefined") 423 { 424 var oLayer = document.createElement("div"); 425 this.oDocumentFragment.appendChild(oLayer); 426 oParent = oLayer; 427 } 428 429 this.deserializeObject(oSerialized, oParent); 430 431 if (typeof oSerialized.childNodes != "undefined") 432 { 433 this.loopOnDeserialize(oSerialized.childNodes, oParent); 434 } 435 }; 436 /** 437 * serializeDomObject is an utilities method used to serialize all the Dom elements in a JSON object 438 * This method is the brain of the class. 439 * @member Cutter.prototype 440 * @author Tomas Corral Casas 441 * @version 1.0 442 * @param {object} oDom The Dom element to serialize 443 * @param {object} oSerializeObject The JSON object where to serialize the Dom element 444 * @private 445 */ 446 Cutter.prototype.serializeDomObject = function(oDom, oSerializeObject) 447 { 448 var sId = Math.random() * 15412457562; 449 if(this.bTest) 450 { 451 sId = "__" + (this.nIdTest++) + "__"; 452 } 453 if (this.nWordsCounter < this.nWords) 454 { 455 var oSerialized = {}; 456 oSerialized.nodeType = oDom.nodeType; 457 if (typeof oDom.tagName != "undefined") 458 { 459 oSerialized.tagName = oDom.tagName.toLowerCase(); 460 } 461 var aAttributes = oDom.attributes; 462 if (aAttributes) 463 { 464 oSerialized.attributes = {}; 465 var oAttribute = undefined; 466 467 for (var nAttribute = 0, nLenAttributes = aAttributes.length; nAttribute < nLenAttributes; nAttribute++) 468 { 469 oAttribute = aAttributes[nAttribute]; 470 if(oAttribute.nodeValue) 471 { 472 oSerialized.attributes[oAttribute.name] = oAttribute.value; 473 } 474 } 475 } 476 if (oSerialized.nodeType == 3) 477 { 478 var nLastWordsCounter = this.nWordsCounter; 479 if (typeof oDom.textContent != "undefined") 480 { 481 this.nWordsCounter += this._countWords(this._trim(oDom.textContent)); 482 } 483 else 484 { 485 if (oDom.data) 486 { 487 this.nWordsCounter += this._countWords(this._trim(oDom.data)); 488 } 489 else 490 { 491 this.nWordsCounter += this._countWords(this._trim(oDom.innerText)); 492 } 493 494 } 495 496 497 if (this.nWordsCounter < this.nWords) 498 { 499 if (typeof oDom.textContent != "undefined") 500 { 501 oSerialized.textContent = oDom.textContent; 502 } 503 else 504 { 505 if (oDom.data) 506 { 507 oSerialized.innerText = oDom.data; 508 } 509 else 510 { 511 oSerialized.innerText = oDom.innerText; 512 } 513 } 514 515 } 516 else 517 { 518 this.bNeedViewMore = true; 519 if (nLastWordsCounter < this.nWords && this.nWordsCounter > this.nWords) 520 { 521 if (typeof oDom.textContent != "undefined") 522 { 523 oSerialized.textContent = this._getOnlyNumberOfWords(oDom.textContent, (this.nWords - nLastWordsCounter)); 524 } 525 else 526 { 527 if (oDom.data) 528 { 529 oSerialized.innerText = this._getOnlyNumberOfWords(oDom.data, (this.nWords - nLastWordsCounter)); 530 } 531 else 532 { 533 oSerialized.innerText = this._getOnlyNumberOfWords(oDom.innerText, (this.nWords - nLastWordsCounter)); 534 } 535 } 536 537 } 538 else 539 { 540 if (document.body.textContent) 541 { 542 oSerialized.textContent = ""; 543 } 544 else 545 { 546 oSerialized.innerText = ""; 547 } 548 } 549 } 550 551 } 552 if (oDom.hasChildNodes()) 553 { 554 oSerialized.childNodes = {}; 555 for (var nChild = 0, nLenChilds = oDom.childNodes.length; nChild < nLenChilds; nChild++) 556 { 557 this.serializeDomObject(oDom.childNodes[nChild], oSerialized.childNodes); 558 } 559 } 560 561 if (typeof oSerializeObject == "undefined") 562 { 563 this.oSerialized[sId] = oSerialized; 564 } 565 else 566 { 567 oSerializeObject[sId] = oSerialized; 568 } 569 } 570 }; 571 /** 572 * addEvent is the wrapper method to add a new event to the element 573 * @member Cutter.prototype 574 * @author Tomas Corral Casas 575 * @version 1.0 576 * @private 577 * @param {DOM} oElement 578 * @param {String} sType 579 * @param {Function} fpCallback 580 */ 581 Cutter.prototype.addEvent = function(oElement, sType, fpCallback) 582 { 583 if(oElement.addEventListener) 584 { 585 oElement.addEventListener(sType, fpCallback, false); 586 } 587 else if(oElement.attachEvent) 588 { 589 oElement.attachEvent("on" + sType, fpCallback); 590 } 591 }; 592 /** 593 * setBehaviour is the method that applies the behaviour to the "see more" link to get the full content again when makink click on it 594 * @member Cutter.prototype 595 * @author Tomas Corral Casas 596 * @version 1.0 597 * @private 598 */ 599 Cutter.prototype.setBehaviour = function() 600 { 601 var self = this; 602 this.addEvent(this.oViewMore, "click", function() 603 { 604 self.showAll(); 605 }); 606 }; 607 /** 608 * showAll is the method that put the initial content to the target Dom element 609 * @member Cutter.prototype 610 * @author Tomas Corral Casas 611 * @version 1.0 612 * @private 613 */ 614 Cutter.prototype.showAll = function() 615 { 616 var oTarget = this.oTarget; 617 var oParent = oTarget.parentNode; 618 oParent.insertBefore(this.oBackupApplyTo, oTarget); 619 oParent.removeChild(oTarget); 620 }; 621 /** 622 * init is the method that makes all the work. 623 * Serialize the Dom. 624 * Deserialize the JSON object to Dom elements only with the words that were wanted 625 * Remove the firstChild in oDocumentFragment because this node is the oApplyTo content. 626 * Clean the content on oTarget. 627 * Create the "see more" link 628 * Append the "see more" link to the oDocumentFragment. 629 * Insert the oDocumentFragment content in the oTarget element 630 * At least the behaviour is applied to the link to make possible to get the original content before the cut. 631 * @member Cutter.prototype 632 * @author Tomas Corral Casas 633 * @version 1.0 634 */ 635 Cutter.prototype.init = function() 636 { 637 this.serializeDomObject(this.oApplyTo); 638 this.deserializeSerializedObject(); 639 var oElement = this.oDocumentFragment.childNodes[0]; 640 oElement.removeChild(this.oDocumentFragment.childNodes[0].childNodes[0]); 641 642 this.oTarget.innerHTML = ""; 643 this.createViewMore(); 644 var oFinalContent = this.oDocumentFragment.childNodes[0].childNodes[this.oDocumentFragment.childNodes[0].childNodes.length-1]; 645 if(oFinalContent.nodeType !== 3) 646 { 647 oFinalContent.appendChild(document.createTextNode("...")); 648 } 649 650 if(this.bNeedViewMore && !this.bNotViewMore) 651 { 652 oElement.appendChild(this.oViewMore); 653 this.setBehaviour(); 654 } 655 this.oTarget.appendChild(this.oDocumentFragment); 656 }; 657 /** 658 * run is the static method to make API simple. 659 * @member Cutter 660 * @author Tomas Corral Casas 661 * @version 1.0 662 * @param {DOM} oApplyTo 663 * @param {DOM} oTarget 664 * @param {Number} nWords 665 * @param {Object} oTexts 666 * @param {Object} oClasses 667 */ 668 Cutter.run = function(oApplyTo, oTarget, nWords, oTexts, oClasses) 669 { 670 var oCutter = new Cutter(); 671 oCutter 672 .applyTo(oApplyTo) 673 .setTarget(oTarget) 674 .setWords(nWords); 675 if(typeof oTexts !== "undefined") 676 { 677 oCutter.setTexts(oTexts); 678 } 679 if(typeof oClasses !== "undefined") 680 { 681 oCutter.setClasses(oClasses); 682 } 683 oCutter.init(); 684 } 685