b2evolution PHP Cross Reference Blogging Systems

Source: /rsc/js/blog_widgets.js - 615 lines - 20195 bytes - Summary - Text - Print

Description: Server communication functions - widgets javascript interface This file is part of the evoCore framework - {@link http://evocore.net/} See also {@link http://sourceforge.net/projects/evocms/}.

   1  /**
   2   * Server communication functions - widgets javascript interface
   3   * This file is part of the evoCore framework - {@link http://evocore.net/}
   4   * See also {@link http://sourceforge.net/projects/evocms/}.
   5   * @author yabs - http://innervisions.org.uk/
   6   * @version $Id: blog_widgets.js 6556 2014-04-28 07:28:58Z attila $
   7   */
   8  
   9  
  10  /**
  11   * @TODO yabs > finish docs
  12   */
  13  
  14  
  15  /**
  16   * @internal strings various <img> tags
  17   * these will be set during Init()
  18   */
  19  var edit_icon_tag = ''; // edit icon image tag
  20  var delete_icon_tag = ''; // delete icon image tag
  21  
  22  /**
  23   * @internal string current_widgets
  24   * holds the list of current widgets
  25   */
  26  var current_widgets = '';
  27  
  28  /**
  29   * @internal object reorder_widgets_queue
  30   * re-order requests timer object
  31   */
  32  var reorder_widgets_queue;
  33  
  34  /**
  35   * @internal integer reorder_delay
  36   * time, in milliseconds, to buffer requests for
  37   * Does not work when set to 0 or even 20 !!
  38   */
  39  var reorder_delay = 200;
  40  
  41  /**
  42   * @internal integer reorder_delay_remaining
  43   * time in milliseconds before the request is sent
  44   */
  45  var reorder_delay_remaining = 0;
  46  
  47  /**
  48   * @internal string current_widgets
  49   * crumb to be added to urls
  50   */
  51  var crumb_url = '';
  52  
  53  
  54  /**
  55   * Init()
  56   *
  57   * Activates the new interface if javascript enabled
  58   */
  59  jQuery(document).ready( function()
  60  {
  61      // grab some constants -- fp> TODO: this is flawed. Fails when starting with an empty blog having ZERO widgets. Init that in .php
  62      edit_icon_tag = jQuery( '.edit_icon_hook' ).find( 'a' ).html();// grab the edit icon
  63      delete_icon_tag = jQuery( '.delete_icon_hook' ).find( 'a' ).html();// grab the delete icon
  64      //get crumb url from delete url and then add it in toggleWidget
  65      crumb_url = jQuery( '.delete_icon_hook' ).find( 'a' ).attr('href');
  66      crumb_url = crumb_url.match(/crumb_.*?$/);
  67      // Modify the current widgets screen
  68      // remove the "no widgets yet" placeholder:
  69      jQuery( ".new_widget" ).parent().parent().remove();
  70      // get rid of the odd/even classes and add our own class:
  71      jQuery( ".odd" ).addClass( "widget_row" ).removeClass( ".odd" );
  72      jQuery( ".even" ).addClass( "widget_row" ).removeClass( ".even" );
  73  
  74      // make container title droppable -- fp> This works but gives no visual feedback. It would actually be cool to drop 'after' the current line in which case dropping on the title would make sense
  75      jQuery( '.fieldset_title' ).each( function(){
  76          jQuery( this ).droppable(
  77          {
  78              accept: ".draggable_widget", // classname of objects that can be dropped
  79              hoverClass: "droppable-hover", // classname when object is over this one
  80              greedy: true, // stops propogation if over more than one
  81              tolerance : "pointer", // droppable active when cursor over
  82              delay: 1000,
  83              drop: function(ev, ui)
  84              {    // function called when object dropped
  85                  jQuery( ".fade_me" ).removeClass( "fade_me" ); // remove any existing fades
  86                  jQuery( '.available_widgets' ).removeClass( 'available_widgets_active' ); // close any open windows
  87  
  88                  jQuery( ui.draggable ).appendTo( jQuery( '#container_' + jQuery( this ).find( '.container_name' ).html().replace( ' ', '_' ) ) ); // add the dragged widget to this container
  89                  jQuery( ui.draggable ).addClass( "fade_me server_update" ); // add fade class
  90                  jQuery( ui.draggable ).droppable( "enable" );    // enable dropping if disabled
  91                  doFade( ".fade_me" ); // fade the widget
  92                  colourWidgets();
  93                  sendWidgetOrder(); // send the new order to the server
  94              }
  95          });
  96      });
  97  
  98      // grab the widget ID out of the "delete" url and add as ID to parent row:
  99      jQuery( '.widget_row td:nth-child(5)' ).each( function()
 100      {
 101          var widget_id = jQuery( this ).find( 'a' ).attr( "href" );
 102          widget_id = widget_id.match(/wi_ID=([0-9]+)/)[1] // extract ID
 103          jQuery( this ).parent().attr( "id", "wi_ID_"+widget_id ); // add ID to parent row
 104      });
 105  
 106      // Convert the tables:
 107      var the_widgets = new Array();
 108      jQuery( ".grouped" ).each( function()
 109      { // grab each container
 110          var container = jQuery( this ).attr( "id" );
 111          the_widgets[ container ] = new Array();
 112          jQuery( "#"+container+" .widget_row" ).each( function()
 113          { // grab each widget in container
 114              var widget = jQuery( this ).attr( "id" );
 115              the_widgets[ container ][ widget ] = new Array();
 116              the_widgets[ container ][ widget ]["name"] = jQuery( "#"+widget ).find('.widget_name' ).html();
 117              the_widgets[ container ][ widget ]["class"] = jQuery( this ).attr( "className" );
 118              the_widgets[ container ][ widget ]["enabled"] = jQuery( '#' + widget + ' .widget_is_enabled' ).size();
 119          } );
 120      });
 121  
 122      // create new container for each current container
 123      for( container in the_widgets )
 124      {    // loop through each container
 125          var is_droppable = !jQuery( '#'+container ).hasClass( "no-drop" );
 126          newContainer = jQuery( "<ul id=\"container_"+container+"\" class=\"widget_container\"></ul>" );
 127          if( !is_droppable )
 128          {    // container doesn't exist in skin
 129              jQuery( newContainer ).addClass( 'no-drop' );
 130          }
 131          jQuery( "#"+container ).replaceWith( newContainer );// replace table with new container
 132  
 133          // create widget entry for each widget in each container
 134          for( widget in the_widgets[container] )
 135          {    // loop through all widgets in this container
 136              createWidget( widget, container, 0, the_widgets[container][widget]["name"], the_widgets[container][widget]["class"], the_widgets[container][widget]["enabled"] );
 137          }
 138      }
 139  
 140      // disable dropping on empty containers:
 141      jQuery( '.no-drop .draggable_widget').droppable( "disable" );
 142      jQuery( '.draggable_widget' ).bind( 'mousedown', function()
 143      { // hide any available widgets panes
 144          if( !jQuery( this ).hasClass( 'new_widget' ) )
 145          {    // we're dragging a current widget, close any open "available widgets" screens
 146              jQuery( '.available_widgets_active' ).removeClass( 'available_widgets_active' );
 147          }
 148      });
 149  
 150      colourWidgets(); // add odd/even classes to widgets
 151  
 152      convertAvailableList(); // converts available widgets list to something we can work with
 153  
 154      current_widgets = getWidgetOrder(); // save current widget order
 155  
 156      doFade( ".fadeout-ffff00" );// highlight any changed widgets
 157  });
 158  
 159  
 160  /**
 161   * Makes the selector drag and drop .. because I'm lazy ;)
 162   *
 163   * @param mixed selector DOM ID or class or object
 164   */
 165  function makeDragnDrop( selector )
 166  {
 167      makeDraggable( selector );
 168      makeDroppable( selector );
 169  }
 170  
 171  
 172  /**
 173   * Makes an element / group of elements draggable
 174   *
 175   * @param mixed selector : the object to make draggable
 176   */
 177  function makeDraggable( selector )
 178  {
 179      jQuery( selector ).draggable(
 180      {
 181          helper: "clone", // use a copy of the image
 182          scroll: true, // scroll the window during dragging
 183          scrollSensitivity: 100, // distance from edge before scoll occurs
 184          zIndex: 999, // z-index whilst dragging
 185          opacity: .8, // opacity whilst dragging
 186          cursor: "move" // change the cursor whilst dragging
 187      }).addClass( "draggable_widget" ); // add our css class
 188  }
 189  
 190  /**
 191   * Makes an element / group of elements droppable
 192   *
 193   * @param mixed selector : the object to make droppable
 194   */
 195  function makeDroppable( selector )
 196  {
 197      jQuery( selector ).droppable(
 198      {
 199          accept: ".draggable_widget", // classname of objects that can be dropped
 200          hoverClass: "droppable-hover", // classname when object is over this one
 201          greedy: true, // stops propogation if over more than one
 202          tolerance : "pointer", // droppable active when cursor over
 203          delay: 1000,
 204          drop: function(ev, ui)
 205          {    // function called when object dropped
 206              jQuery( ".fade_me" ).removeClass( "fade_me" ); // remove any existing fades
 207              jQuery( '.available_widgets' ).removeClass( 'available_widgets_active' ); // close any open windows
 208              if( !jQuery( this ).hasClass( "available_widgets" ) )
 209              {    // we're not deleting it
 210                  if( jQuery( ui.draggable ).hasClass( "new_widget" ) )
 211                  {    // this is a new widget, we need to treat it diffently
 212                      addNewWidget( ui.draggable, this ); // add as new widget
 213                  }
 214                  else
 215                  {    // this is an existing widget, just move it
 216                      jQuery( ui.draggable ).insertBefore( this ); // add the dragged widget before this widget
 217                      jQuery( ui.draggable ).addClass( "fade_me server_update" ); // add fade class
 218                      jQuery( ui.draggable ).droppable( "enable" );    // enable dropping if disabled
 219                  }
 220              }
 221              else
 222              { // we might be deleting the widget
 223                  if(  !jQuery( ui.draggable ).hasClass( "new_widget" ) )
 224                  { // we're deleting it
 225                      jQuery( ui.draggable ).remove();
 226                  }
 227              }
 228              doFade( ".fade_me" ); // fade the widget
 229              colourWidgets();
 230              sendWidgetOrder();// send the new order to the server
 231          }
 232      });
 233  }
 234  
 235  /**
 236   * Fades the relevant object
 237   */
 238  function doFade( selector )
 239  {
 240      evoFadeSuccess( selector );
 241  }
 242  
 243  
 244  /**
 245   * Send the current widget containers and order to the server
 246   *
 247   * Successive calls within the buffer time resets the countdown
 248   * this reduces the number of server calls made
 249   */
 250  function sendWidgetOrder()
 251  {
 252      if( reorder_delay_remaining < 1 )
 253      {
 254          jQuery( '#server_messages' ).html( '<div class="log_container"><div class="log_error"></div></div>' );
 255      }
 256      // reset the clock
 257      reorder_delay_remaining = reorder_delay;
 258      bufferedServerCall();
 259  }
 260  
 261  /**
 262   * Callback funtion for sendWidgetOrder()
 263   *
 264   * Highlights the updated widgets and resets their odd/even style
 265   */
 266  function sendWidgetOrderCallback( server_response )
 267  {
 268      // alert( server_response+' vs '+blog );
 269      doFade( '.server_updating' ); // highlight updated widgets
 270      jQuery( '.server_updating' ).removeClass( 'server_updating' ); // remove "needs updating"
 271      colourWidgets(); // redo widget odd/even colours
 272  }
 273  
 274  /**
 275   * Buffered server call
 276   *
 277   * Waits until delay period is over and then sends new order to the server
 278   * only sends if the current widget order has changed since last update
 279   */
 280  function bufferedServerCall()
 281  {
 282      var new_widget_order = getWidgetOrder();
 283      if( new_widget_order != current_widgets )
 284      {    // widget order has changed, we need to update
 285          jQuery( '#server_messages' ).html( '<div class="log_container"><div class="log_message">'+T_( 'Saving changes' )+'</div></a>' ); // inform user
 286  
 287          current_widgets = new_widget_order; // store current order
 288          //add crumbs here
 289          new_widget_order += '&' + crumb_url;
 290          jQuery( '.pending_update' ).removeClass( 'pending_update' ).addClass( 'server_updating' ); // change class to "updating"
 291  
 292          SendAdminRequest( 'widgets', 're-order', new_widget_order, false ); // send current order to server
 293      }
 294      else
 295      {    // widget order either hasn't changed or has been changed back to original order
 296          jQuery( '#server_messages' ).html( '<div class="log_container"><div class="log_message">'
 297                          +T_( 'Widget order unchanged' )+'</div></a>' ); // inform user
 298          jQuery( '.pending_update' ).removeClass( 'pending_update' ); // remove "needs updating"
 299          colourWidgets(); // redo widget colours
 300      }
 301  }
 302  
 303  
 304  /**
 305   * Gets the current widget order
 306   *
 307   * @return string widget order
 308   */
 309  function getWidgetOrder()
 310  {
 311      // need to get every container, then every widget in container and send the lot to the server
 312      var containers = new Array()
 313      jQuery( '.widget_container' ).each(function()
 314      {
 315          var container_name = jQuery( this ).attr('id');
 316          containers[ container_name ] = '';
 317          jQuery( '#'+container_name+' .draggable_widget' ).each( function(){
 318              if( jQuery( this ).attr( 'id' ) && jQuery( this ).attr( 'id' ) != 'undefined' )
 319              {    // this is a widget
 320                   containers[container_name] += jQuery( this ).attr( 'id' ) + ', ';
 321              }
 322          });
 323      });
 324  
 325      var query_string = '';
 326      var containers_list = '';
 327      for( container in containers )
 328      {
 329          query_string += container+'='+containers[container]+'&';
 330          containers_list += container+',';
 331      }
 332  
 333      var r = 'blog='+blog+'&'+query_string+'container_list='+containers_list;
 334  
 335      // console.log( r );
 336  
 337      return r;
 338  }
 339  
 340  
 341  /**
 342   * Redo odd / even classes
 343   */
 344  function colourWidgets()
 345  {
 346          jQuery( ".draggable_widget" ).removeClass( "odd" ); // remove any odd classes
 347          jQuery( ".draggable_widget" ).removeClass( "even" ); // remove any even classes
 348          var pos = false; // will be used as a toggle for odd / even rows
 349          jQuery( "#current_widgets .draggable_widget" ).each( function(){
 350                  pos = !pos; // toggle
 351                  jQuery(this).addClass( ( pos ? "even" : "odd" ) ); // add relevant new odd/even class
 352          });
 353  }
 354  
 355  /**
 356   * Delete widget
 357   */
 358  function deleteWidget( widget )
 359  {
 360      jQuery( '#wi_ID_'+widget.substr( 6, widget.length ) ).animate({
 361              backgroundColor: "#f88"
 362          },"fast", function(){
 363              jQuery( this ).remove(); // remove the widget
 364              colourWidgets(); // redo widget colours
 365              sendWidgetOrder(); // update the server
 366          });
 367      return false;
 368  }
 369  
 370  /**
 371   * Request edit screen from server...
 372   */
 373  function editWidget( widget )
 374  {
 375      jQuery( '#server_messages' ).html( '' );
 376      msg = "wi_ID="+widget.substr( 6, widget.length );
 377      SendAdminRequest( "widgets", "edit", msg, true );
 378      return false;
 379  }
 380  
 381  /*
 382   * This is called when we get the response from the server:
 383   */
 384  function widgetSettings( the_html )
 385  {
 386      // add placeholder for widgets settings form:
 387      jQuery( 'body' ).append( '<div id="screen_mask" onclick="closeWidgetSettings()"></div><div id="widget_settings"></div>' );
 388      // var evobar_height = jQuery( '#evo_toolbar' ).height();
 389      // jQuery( '#screen_mask' ).css({ top: evobar_height });
 390      jQuery( '#screen_mask' ).fadeTo(1,0.5).fadeIn(200);
 391      jQuery( '#widget_settings' ).html( the_html ).addClass( 'widget_settings_active' );
 392      jQuery( '#widget_settings' ).prepend( jQuery( '#server_messages' ) );
 393      AttachServerRequest( 'form' ); // send form via hidden iframe
 394      jQuery( '#widget_settings > form > span > a' ).bind( 'click', closeWidgetSettings );
 395  
 396      // Close widget Settings if Escape key is pressed:
 397      var keycode_esc = 27;
 398      jQuery(document).keyup(function(e)
 399      {
 400          if( e.keyCode == keycode_esc )
 401          {
 402              closeWidgetSettings();
 403          }
 404      });
 405  }
 406  
 407  function widgetSettingsCallback( the_widget, the_name )
 408  {
 409      jQuery( '#wi_ID_'+the_widget+' .widget_name' ).html( the_name );
 410  }
 411  
 412  function closeWidgetSettings()
 413  {
 414      jQuery( '#widget_settings' ).hide(); // removeClass( 'widget_settings_active' );
 415      jQuery( '#server_messages' ).insertBefore( '.available_widgets' );
 416      jQuery( '#widget_settings' ).remove();
 417      jQuery( '#screen_mask' ).remove();
 418      return false;
 419  }
 420  
 421  
 422  function T_( native_string )
 423  {
 424      if( typeof( T_arr[ native_string ] ) == "undefined" )
 425      { // we don't have a translation
 426          return native_string;
 427      }
 428      else
 429      {    // we have a translation
 430          return T_arr[ native_string ];
 431      }
 432  }
 433  
 434  /**
 435   * Converts the widget available list to something we can work with
 436   */
 437  function convertAvailableList()
 438  {
 439      // Open list on click, not on hover!
 440      jQuery( ".fieldset_title_bg > span > a" ).attr( 'href', '#' ).bind( 'click', function(e)
 441      {
 442          // add placeholder for widgets settings form:
 443          jQuery( 'body' ).append( '<div id="screen_mask" onclick="closeAvailableWidgets()"></div>' );
 444          jQuery( '#screen_mask' ).fadeTo(1,0.5).fadeIn(200);
 445          offset = jQuery( this ).offset();
 446          var y = offset.top;
 447          // can't dislay any lower than this!:
 448          // var max_y = jQuery( window ).height() - jQuery( '.available_widgets' ).height(); // this doesn't work when window is scrolled :(
 449          var max_y = jQuery( document ).height() - 10 - jQuery( '.available_widgets' ).height();
 450          if( max_y < 20 ) { max_y = 20 };
 451          if( y > max_y ) { y = max_y };
 452          jQuery( '.available_widgets' ).addClass( 'available_widgets_active' ).attr( 'id', 'available_'+jQuery( this ).attr( "id" ) );
 453  
 454          // cancel default href action:
 455          return false;
 456      });
 457  
 458      // Close action:
 459      jQuery( '.available_widgets_toolbar > a' ).bind( 'click', function(e)
 460      {
 461          closeAvailableWidgets();
 462          // cancel default href action:
 463          return false;
 464      });
 465  
 466      // Close Overlay if Escape key is pressed:
 467      var keycode_esc = 27;
 468      jQuery(document).keyup(function(e)
 469      {
 470          if( e.keyCode == keycode_esc )
 471          {
 472              closeAvailableWidgets();
 473              return false;
 474          }
 475      });
 476  
 477      jQuery( ".available_widgets li" ).each( function()
 478      { // shuffle things around
 479          jQuery( this ).addClass( "new_widget" ); // add hook for detecting new widgets
 480  
 481          var the_link = jQuery( this ).children( 'a' ).attr( 'href' ); // grab the url
 482          the_link = the_link.substr( the_link.indexOf( '&type' ) + 1, the_link.length );
 483  
 484          // replace href with JS addnewwidget action:
 485          jQuery( this ).children( 'a' ).attr( 'href', '#' ).bind( 'click', function(){
 486              addNewWidget( this, the_link );
 487              // cancel default href action:
 488              return false;
 489          });
 490      });
 491  }
 492  
 493  /**
 494   * Close available widgets overlay
 495   */
 496  function closeAvailableWidgets()
 497  {
 498      jQuery('.available_widgets').removeClass( 'available_widgets_active' );
 499      jQuery( '#screen_mask' ).remove();
 500  }
 501  
 502  
 503  /**
 504   * Adds a new widget to a container
 505   */
 506  function addNewWidget( widget_list_item, admin_call )
 507  {
 508      closeAvailableWidgets()
 509  
 510      var widget_id = jQuery( widget_list_item ).attr( "id" );
 511      jQuery( widget_list_item ).attr( "id", widget_id );
 512  
 513      var widget_name = jQuery( widget_list_item ).html();
 514      var destination = jQuery( '.available_widgets' ).attr( 'id' );
 515      destination = destination.substr( 18, destination.length ).replace( '_', ' ' );
 516  
 517      SendAdminRequest( 'widgets', 'create', admin_call+"&blog="+blog+"&container="+destination, true );
 518  }
 519  
 520  
 521  /**
 522   * Adds a new widget to a container
 523   *
 524   * @param integer wi_ID Id of the new widget
 525   * @param string container Container to add widget to
 526   * @param intger wi_order ( unused atm ) Order of the widget on the server
 527   * @param string wi_name Name of the new widget
 528   */
 529  function addNewWidgetCallback( wi_ID, container, wi_order, wi_name )
 530  {
 531      jQuery( '.fade_me' ).removeClass( 'fade_me' ); // kill any active fades
 532      createWidget( 'wi_ID_'+wi_ID, container.replace( ' ', '_' ),wi_order, '<strong>'+wi_name+'</strong>', '', 1 );
 533      doFade( '#wi_ID_'+wi_ID );
 534      if( reorder_delay_remaining > 0 )
 535      {    // send outstanding updates
 536          reorder_delay_remaining = 0;
 537      }
 538      else
 539      {    // no outstanding updates, store current order
 540          current_widgets = getWidgetOrder(); // store current order
 541      }
 542  }
 543  
 544  /**
 545   * Create a new widget in a container
 546   *
 547   * @param integer wi_ID Id of the new widget
 548   * @param string container Container to add widget to
 549   * @param integer wi_order ( unused atm ) Order of the widget on the server
 550   * @param string wi_name Name of the new widget
 551   * @param boolean wi_enabled Is the widget enabled?
 552   */
 553  function createWidget( wi_ID, container, wi_order, wi_name, wi_class, wi_enabled )
 554  {
 555      //    window.alert( wi_ID + ' : ' + container + ' : ' + wi_name + ' : ' +wi_class );
 556      var newWidget = jQuery( '<li id="'+wi_ID+'" class="draggable_widget"><a class="widget_name" href="#" onclick="return editWidget( \''+wi_ID+'\' );">'+wi_name+'</a></li>' );
 557      if( wi_class )
 558      { // add class
 559          jQuery( newWidget ).addClass( wi_class );
 560      }
 561  
 562      // Add state indicator:
 563      jQuery( newWidget ).prepend( jQuery( '<span class="widget_state">'+( wi_enabled ? enabled_icon_tag : disabled_icon_tag )+'</span>' ) );
 564  
 565      // Add action icons:
 566      var actionIcons = jQuery( '<span class="widget_actions"><a href="#" class="toggle_action" onclick="return toggleWidget( \''+wi_ID+'\', \''+crumb_url+'\' );">'
 567                  +( wi_enabled ? deactivate_icon_tag : activate_icon_tag )+'</a><a href="#" onclick="return editWidget( \''+wi_ID+'\' );">'
 568                  +edit_icon_tag+'</a><a href="#" onclick="return deleteWidget( \''+wi_ID+'\' );">'
 569                  +delete_icon_tag+'</a></span>' );
 570      jQuery( newWidget ).prepend( actionIcons ); // add widget action icons
 571  
 572      jQuery( '#container_'+container ).append( newWidget );    // add widget to container
 573  
 574      makeDragnDrop( '#'+wi_ID );
 575      colourWidgets();    // recolour the widgets
 576  }
 577  
 578  /**
 579   * Toggle the widget state.
 580   *
 581   * @param string Widget ID.
 582   */
 583  function toggleWidget( wi_ID )
 584  {
 585       //console.log( 'Toggling widget #' + wi_ID.substr( 6 ) );
 586      SendAdminRequest( 'widgets', 'toggle', 'wi_ID=' + wi_ID.substr( 6 ) + '&' + crumb_url, true );
 587      return false;
 588  }
 589  
 590  /**
 591   * Callback for toggling a widget.
 592   *
 593   * @param integer Widget ID
 594   * @param integer new widget state
 595   */
 596  function doToggle( wi_ID, wi_enabled )
 597  {
 598      //console.log( 'Setting state of widget #' + wi_ID + ' to ' + ( wi_enabled ? 'enabled' : 'disabled' ) );
 599  
 600      jQuery( '#wi_ID_' + wi_ID + ' .widget_state' ).html( wi_enabled ? enabled_icon_tag : disabled_icon_tag );
 601      jQuery( '#wi_ID_' + wi_ID + ' .toggle_action' ).html( wi_enabled ? deactivate_icon_tag : activate_icon_tag );
 602  }
 603  
 604  /**
 605   * replicates PHP's str_repeat() function
 606   *
 607   * @param string data string to repeat
 608   * @param integer multiplier number of repeats required
 609   *
 610   * @return the multiplied string
 611   */
 612  function str_repeat( data, multiplier )
 613  {
 614      return new Array( multiplier + 1 ).join( data );
 615  }

title

Description

title

Description

title

Description

title

title

Body