// $Id: drupal.js,v 1.29.2.2 2008/08/13 18:12:23 drumm Exp $

var Drupal = Drupal || {};

/**
 * Set the variable that indicates if JavaScript behaviors should be applied
 */
Drupal.jsEnabled = document.getElementsByTagName && document.createElement && document.createTextNode && document.documentElement && document.getElementById;

/**
 * Extends the current object with the parameter. Works recursively.
 */
Drupal.extend = function(obj) {
  for (var i in obj) {
    if (this[i]) {
      Drupal.extend.apply(this[i], [obj[i]]);
    }
    else {
      this[i] = obj[i];
    }
  }
};

/**
 * Redirects a button's form submission to a hidden iframe and displays the result
 * in a given wrapper. The iframe should contain a call to
 * window.parent.iframeHandler() after submission.
 */
Drupal.redirectFormButton = function (uri, button, handler) {
  // Trap the button
  button.onmouseover = button.onfocus = function() {
    button.onclick = function() {
      // Create target iframe
      Drupal.createIframe();

      // Prepare variables for use in anonymous function.
      var button = this;
      var action = button.form.action;
      var target = button.form.target;

      // Redirect form submission to iframe
      this.form.action = uri;
      this.form.target = 'redirect-target';

      handler.onsubmit();

      // Set iframe handler for later
      window.iframeHandler = function () {
        var iframe = $('#redirect-target').get(0);
        // Restore form submission
        button.form.action = action;
        button.form.target = target;

        // Get response from iframe body
        try {
          response = (iframe.contentWindow || iframe.contentDocument || iframe).document.body.innerHTML;
          // Firefox 1.0.x hack: Remove (corrupted) control characters
          response = response.replace(/[\f\n\r\t]/g, ' ');
          if (window.opera) {
            // Opera-hack: it returns innerHTML sanitized.
            response = response.replace(/&quot;/g, '"');
          }
        }
        catch (e) {
          response = null;
        }

        response = Drupal.parseJson(response);
        // Check response code
        if (response.status == 0) {
          handler.onerror(response.data);
          return;
        }
        handler.oncomplete(response.data);

        return true;
      }

      return true;
    }
  }
  button.onmouseout = button.onblur = function() {
    button.onclick = null;
  }
};

/**
 * Retrieves the absolute position of an element on the screen
 */
Drupal.absolutePosition = function (el) {
  var sLeft = 0, sTop = 0;
  var isDiv = /^div$/i.test(el.tagName);
  if (isDiv && el.scrollLeft) {
    sLeft = el.scrollLeft;
  }
  if (isDiv && el.scrollTop) {
    sTop = el.scrollTop;
  }
  var r = { x: el.offsetLeft - sLeft, y: el.offsetTop - sTop };
  if (el.offsetParent) {
    var tmp = Drupal.absolutePosition(el.offsetParent);
    r.x += tmp.x;
    r.y += tmp.y;
  }
  return r;
};

/**
 * Return the dimensions of an element on the screen
 */
Drupal.dimensions = function (el) {
  return { width: el.offsetWidth, height: el.offsetHeight };
};

/**
 *  Returns the position of the mouse cursor based on the event object passed
 */
Drupal.mousePosition = function(e) {
  return { x: e.clientX + document.documentElement.scrollLeft, y: e.clientY + document.documentElement.scrollTop };
};

/**
 * Parse a JSON response.
 *
 * The result is either the JSON object, or an object with 'status' 0 and 'data' an error message.
 */
Drupal.parseJson = function (data) {
  if ((data.substring(0, 1) != '{') && (data.substring(0, 1) != '[')) {
    return { status: 0, data: data.length ? data : 'Unspecified error' };
  }
  return eval('(' + data + ');');
};

/**
 * Create an invisible iframe for form submissions.
 */
Drupal.createIframe = function () {
  if ($('#redirect-holder').size()) {
    return;
  }
  // Note: some browsers require the literal name/id attributes on the tag,
  // some want them set through JS. We do both.
  window.iframeHandler = function () {};
  var div = document.createElement('div');
  div.id = 'redirect-holder';
  $(div).html('<iframe name="redirect-target" id="redirect-target" class="redirect" onload="window.iframeHandler();"></iframe>');
  var iframe = div.firstChild;
  $(iframe)
    .attr({
      name: 'redirect-target',
      id: 'redirect-target'
    })
    .css({
      position: 'absolute',
      height: '1px',
      width: '1px',
      visibility: 'hidden'
    });
  $('body').append(div);
};

/**
 * Delete the invisible iframe
 */
Drupal.deleteIframe = function () {
  $('#redirect-holder').remove();
};

/**
 * Freeze the current body height (as minimum height). Used to prevent
 * unnecessary upwards scrolling when doing DOM manipulations.
 */
Drupal.freezeHeight = function () {
  Drupal.unfreezeHeight();
  var div = document.createElement('div');
  $(div).css({
    position: 'absolute',
    top: '0px',
    left: '0px',
    width: '1px',
    height: $('body').css('height')
  }).attr('id', 'freeze-height');
  $('body').append(div);
};

/**
 * Unfreeze the body height
 */
Drupal.unfreezeHeight = function () {
  $('#freeze-height').remove();
};

/**
 * Wrapper to address the mod_rewrite url encoding bug
 * (equivalent of drupal_urlencode() in PHP).
 */
Drupal.encodeURIComponent = function (item, uri) {
  uri = uri || location.href;
  item = encodeURIComponent(item).replace(/%2F/g, '/');
  return (uri.indexOf('?q=') != -1) ? item : item.replace(/%26/g, '%2526').replace(/%23/g, '%2523').replace(/\/\//g, '/%252F');
};

// Global Killswitch on the <html> element
if (Drupal.jsEnabled) {
  $(document.documentElement).addClass('js');
}


// Validate all required fields before submission.
// All fields are tested for validity when they lose focus, so
// don't worry about non-required fields.
//
// If any of the fields possess the "error" class, we know
// there's something wrong. Abort the submission.

var DriggValidateBeforeSubmit = function () {
	$(".error").removeClass("error");
	$(".messages.error").remove();
	DriggValidateBodyFull( true );
	DriggValidateTitleFull( true );

        if( ! drigg_allow_empty_url){
	  DriggValidateURL( $("#edit-url"), "Story's URL", 'sURL', true);
        }

	if ( $(".error").length > 0 ) {
		return false;
	}
	return true;
}

// cname: The error "class name"
// error: The error message to display
//
// Generate an error message in a standard error message box.
// The error has an error name (cname) and an error message.
// The cname is assigned as the class name to the error message.
// In that way, everything that can generate an error has a distinct
// error message location. That message is generated, cleared, or
// modified based on the cname.

function DriggAddError( cname, error ) {
	var msgs = $(".messages.error ul");

	// If the error display area doesn't exist, create it.
	// Arbitrarily, it is created after the div with class "help"
	if ( msgs.length == 0 ) {
		$(".messages.error").remove();
		$("form#node-form").before('<div class="messages error"><ul></ul></div>' );
		msgs = $(".messages.error ul");
	}
	// Remove any current messages with a class of <cname>
	msgs.find("."+cname).remove();

	// Create a new error item identified by <cname>
	msgs.append('<li class="' + cname + '">' + error + '</li>');
}


// cname: The "error class" identifier
//
// Remove the error message identified by <cname>.
// If there are no more error messages, remove the error
// message box.

function DriggRemoveError( cname ) {
	var msgs = $(".messages.error ul");

	msgs.find("." + cname).remove();
	if( msgs.children().length == 0 ) {
		$(".messages.error").remove();
	}
}

// Validate the "Story description" text area. Do this by
// calling DriggValidateBodyFull. The "false" indicates 
// this is not a full check-- don't generate an error if
// the field is empty.

var DriggValidateBody = function() {
	DriggValidateBodyFull( false );
}

// check: Full check flag
//
// Validate the "Story description" text area. If <check> is true,
// generate an error if the field is empty. Otherwise, only generate
// an error if the field contains characters, but does not contain
// the minimum required number of characters.
//
// Returns "true" if the field validates, "false" otherwise.

var DriggValidateBodyFull = function( check ) {
	var body = $("#edit-body");

	if ( check || body.val().length > 0 )
	{
		check = true;
	}
	else {
		DriggRemoveError("bodyErr");
		body.removeClass("error");
	}

	if ( check ) {
		if ( drigg_body_minimum_length != 0 && $("#edit-body").val().length < drigg_body_minimum_length ) {
			DriggAddError("bodyErr", "The description must be at least " + 
				drigg_body_minimum_length + " characters long.");
			$("#edit-body").addClass("error");
			return false;
		} else if ( drigg_body_maximum_length != 0 && $("#edit-body").val().length > drigg_body_maximum_length ) {
			DriggAddError("bodyErr", "The description cannot be more than " + 
				drigg_body_maximum_length + " characters long.");
			$("#edit-body").addClass("error");
			return false;
		} else {
			DriggRemoveError("bodyErr");
			$("#edit-body").removeClass("error");
			return true;
		}
	}
	else {
		return false;
	}
}

// Validate the "Story's title" text input. Do this by
// calling DriggValidateTitleFull. The "false" indicates 
// this is not a full check-- don't generate an error if
// the field is empty.

var DriggValidateTitle = function( ) {
	DriggValidateTitleFull( false );
}

// check: Full check flag
//
// Validate the "Story's title" text input. If <check> is true,
// generate an error if the field is empty. Otherwise, only generate
// an error if the field contains characters, but does not contain
// the minimum required number of characters.
//
// Returns "true" if the field validates, "false" otherwise.

var DriggValidateTitleFull = function( check ) {
	var title = $("#edit-title");
	if ( check || title.val().length > 0 )
	{
		check = true;
	}
	else {
		DriggRemoveError("titleErr");
		title.removeClass("error");
	}
	if ( check )
	{
		if ( drigg_title_minimum_length != 0 && $("#edit-title").val().length < drigg_title_minimum_length ) {
			DriggAddError("titleErr", "The title must be at least "
					+ drigg_title_minimum_length + " characters.");
			$("#edit-title").addClass("error");
			return false;
		} else if ( drigg_title_maximum_length != 0 && $("#edit-title").val().length > drigg_title_maximum_length ) {
			DriggAddError("titleErr", "The title cannot be longer than "
					+ drigg_title_maximum_length + " characters.");
			$("#edit-title").addClass("error");
			return false;
		} else {
			DriggRemoveError("titleErr");
			$("#edit-title").removeClass("error");
			return true;
		}
	}
	else {
		return false;
	}
}

// Validate the "Trackback URL" text input. Do this by
// calling DriggValidateURL. (SEE DriggValidateURL for
// a complete description of the arguments.) 

var DriggValidatePingURL = function() {
	DriggValidateURL( this, 'Trackback URL', 'tbURL', false);
}

// Validate the "Story's URL" text input. Do this by
// calling DriggValidateURL. (SEE DriggValidateURL for
// a complete description of the arguments.) 

var DriggValidateStoryURL = function() {

        var entry = $(this);
        var url = entry.val();

        // If an empty URL is allowed, then don't check and take the error out
        if( drigg_allow_empty_url &&  url.length == 0 ) {
          entry.removeClass("error");
          DriggRemoveError('sURL');
          return;
        }

        DriggValidateURL( this, "Story's URL", 'sURL', false);
}

// elem: The text input to validate
// prefix: A string to display in the error box
// errtype: A unique identifier (<cname> in DriggCreateError
//          and DriggRemoveError) 
// check: Full check flag
//
// Validates the text contained in <elem> to make sure the URL
// is valid. This is done via the server, so we pass it in
// with AJAX.
//
// If the entry is not a valid URL, generate an error of class
// <errtype>. The error message begins with "prefix," which
// should help distinguish the error for the user.
//
// If <check> is false, don't bother reporting if the field is
// empty. This helps make things a little nicer when navigating
// the form.

var DriggValidateURL = function(elem, prefix, errtype, check) {
	var entry = $(elem);
	var args = {};
	args.operation = 'validate_url';
	args.url = entry.val();

        if(drigg_editing_node){
          args.editing_node=drigg_editing_node;
        }

	if ( args.url == '' )
	{
		if ( !/required/.test(entry[0].className) ) {
			check = false;
		}
		DriggRemoveError(errtype);
		entry.removeClass("error");
	}
	else {
		check = true;
	}

	if ( check ) {
		if ( args.url == '' ) {
			DriggAddError(errtype, prefix + ' is required');
			entry.addClass("error");
	
			return false;
		}

		$.ajax({
			data     : args,
                	url      : Drupal.settings.drigg.base_path+"drigg/ajax/handle",
			type     : 'get',
			datatype : 'html',
			success  : function (result) {
				if ( result == 'success' ) {
					entry.removeClass("error");
					DriggRemoveError(errtype);
				}
				else {
					DriggAddError(errtype, prefix + ': ' + result);
					entry.addClass("error");
				}
			},
			error: function( xhr, msg ) {
				entry.addClass("error");
			},
			complete: function( xhr, stat ) {
			}	
		});
	
	}
}

// This is executed on document load.
//
// If this appears to be a drigg node form, wire up the text
// entries for validation.

$(function(){
	if ( $("#edit-drigg-node-form").length > 0 ) {
		$("#edit-trackback-ping-url").blur(DriggValidatePingURL);
                //if(! drigg_editing_node ){
			$("#edit-url").blur(DriggValidateStoryURL);
                //}
		$("#edit-title").blur(DriggValidateTitle);
                $("#edit-title").blur(DriggValidateBody);
		$("#node-form").submit(DriggValidateBeforeSubmit);
	}
});




///////////////////////////////////////////////////////////////////////////////
// This is called when a form is submitted.
var KarmaSendForm = function() {
  var form = $(this);
  var args = {};
  var destination = form.find("input[@name=js_destination]").val();
        var karma_url=Drupal.settings.extra_voting_forms.base_path + 'extra_voting_forms/handle';

  // If 'destination' is defined, we need to load the destination page 
  // rather than submit the vote.
  // This is used to redirect to a login page, for example.
  if (  destination ) {
    // top.location = Drupal.settings.extra_voting_forms.base_path + Drupal.settings.extra_voting_forms.login_page + '?destination=' + destination;
    top.location = '/' + Drupal.settings.extra_voting_forms.login_page + '?destination=' + destination;
    return false;
  }

  // The class "tmpClicked" is a simple state variable. It is added as soon 
  // as a label is clicked, and removed only once the vote has succeeded or 
  // failed. In this way, pending votes are tracked.

  // If an element exists with both the "karma_clicked" and the "tmpClicked", 
  // then the user has cast the same vote again. Ignore it. 
  else if ( form.find('.tmpClicked').length > 0 ) {
    var tmpclicked = $(form.find('.tmpClicked')[0] );
    tmpclicked.removeClass('tmpClicked');
    if ( karma_clicked_indicator( tmpclicked ) == 'clicked' ) {
      return false;
    }
    else {
      tmpclicked.addClass('tmpClicked');
    }
  }
  // Process the vote.
  // Marshall all AJAX arguments

  args.karma_vote   = form.find(":selected").val() || form.find(":checked").val();
  args.oid     = form.find("input[@name=oid]").val();
  args.otype     = form.find("input[@name=otype]").val();
  args.form_type     = 'ajax';
  form.addClass("karma_sending");

  // Send the request  
  $.ajax({
    data    : args,
    url    : karma_url,
    type    : 'get',
    dataType: 'json',
    success  : function(json){
      var ts;
      for(var x in json) {args[x] = json[x];}
      form.removeClass("karma_sending");

      // If there's an error, remove "tmpClicked", as the request is no 
      // longer pending. 
      if (json.error) {
        form.find(".tmpClicked").removeClass("tmpClicked");
        alert(json.error);
      } else {

        // On success, disable the appropriate forms (SEE KarmaStripForm 
        // for more details).
        // Also, ensure only the currently-clicked button has the state 
        // "karma_clicked". Remove "tmpClicked", as the request is 
        // no longer pending.
        var tmpclicked = form.find(".tmpClicked");
        form.addClass("karma_success");
        form.find("label").each( function() { karma_clicked( $(this), "not_clicked"); } )
        tmpclicked.each( function() { { karma_clicked( $(this), "clicked")  } } );
        KarmaStripForm(form,args);
      }
    },
    error : function(xhr,msg){
      // If there's an error, remove "tmpClicked", as the request is no longer pending. 

      form.find(".tmpClicked").removeClass("tmpClicked");
      if (xhr.status==404||!msg) {
        alert("Karma Error: Communication (0)");
      } else {
        alert("Karma Error: "+msg);
      }
    },
    complete: function(xhr,stat) {
      (function(form,args){
        window.setTimeout(function(){
          form.removeClass("karma_sending");
        },2000);
      })(form,args);
    }
  });
  return false;
}


///////////////////////////////////////////////////////////////////////////////
// Given a form, disable the appropriate related forms.
var KarmaStripForm  = function(form,args) {

  // If the site is set to allow only a single vote, disable the target form.

  if (Drupal.settings.extra_voting_forms.only_one_vote) {
    // Disable the drop-down select elements by setting their "disabled" attribute
    form.find("select").attr("disabled","disabled");

    // For our radio-button based forms, the label is the clickable element. Select all labels in the target form.
    form.find("label")
      .each(function() { karma_clickable( $(this), 'not_clickable' ); } )

      // Finally, remove the 'click' event, so the label no longer responds to user input.
      .unbind('click');
  }

  // Find all forms associated with this node. Disable all other forms. This has the effect of
  // disabling *all* forms in an only_one_vote environment, or disabling all forms but the target
  // form in other environments.

  var fid = form.attr("id");

  // Modify the forms *only* if there is a valid karma score (in args.karma_aggregate)
  if ( args.karma_aggregate != "Undefined") {
    for ( var prefix in ( { 'w': 1, 'x': 1, 'y': 1, 'z': 1} ) ) {
      var did = prefix + args.otype + args.oid;

      // Update all the karma scores for this node
      $("#" + did + " span.karma_score").text(args.karma_aggregate);
      var did = prefix + args.oid;
      if ( did != fid ) {

        // Disable all drop-down select elements
        $("#" + did + " *").attr("disabled", "true");

        // Disable all radiobutton-based forms, via their clickable labels.
        $("#" + did + " label")
           .each(function() { karma_clickable( $(this), 'not_clickable' ); } )
          // Finally, remove the 'click' event, so the label no longer responds to user input.
          .unbind('click');
      }
    }
  }
}

///////////////////////////////////////////////////////////////////////////////
// Add click events to the appropriate form elements. This occurs at document load time.
var KarmaRenderForm = function() {
  var form   = $(this);
  var submit = form.find("input[@type=submit]");

  // Skip this form if it's disabled.
  if (! submit.attr("disabled")) {

    // Remove checks on radiobuttons.
    form.find(":checked").attr("checked", false );

    // Add a simple "submit" event when a drop-down is selected.
    form.find("select").change(function(){form.submit();});

    // For radiobutton-based forms, add a slightly more complex click event
    // to the *label* of the radiobutton. The radiobuttons themselves are
    // not visible, so the *label* becomes the active element.

    form.find("label").click(function(){

      // If this radiobutton is not checked, do nothing. Basically, there are *two* click
      // events on each label. The first is generated when the label is clicked, but before
      // the radiobutton has been checked. The second is when the radiobutton is checked, which
      // occurs immediately after the first click event propogates.
      //
      // NOTE: "checked" here means "received the checkmark," and does not refer to validiation.

      form.find("#"+$(this).attr("for")).attr("checked", true );
      if($('.karma_sending').length > 0) {
  
        return;
      }
      // Add the "tmpClicked" class, to provide a state variable indicating a vote
      // submission is in progress for this label.

      $(this).addClass("tmpClicked");

      // Submit the form. This calls "KarmaSendForm".
      form.submit();
    });
  }
}

///////////////////////////////////////////////////////////////////////////////
function karma_up_down_indicator( element ) {
  if ( element.attr('class').substring(0,8) == "karma_up" ) {
    return "karma_up";
  }
  else {
    return "karma_down";
  }
}

///////////////////////////////////////////////////////////////////////////////
function karma_clickable_indicator( element ) {
  if ( element.attr('class').substr(-13) == "not_clickable" ) {
    return "not_clickable";
  }
  else {
    return "clickable";
  }
}

///////////////////////////////////////////////////////////////////////////////
function karma_clicked_indicator( element ) {
  if ( element.attr('class').substring(0,16) == "karma_up_clicked" ||
       element.attr('class').substring(0,18) == "karma_down_clicked" ) {

    return "clicked";
  }
  else {
    return "not_clicked";
  }
}

///////////////////////////////////////////////////////////////////////////////
function karma_clicked( element, clicked ) {
  var up_down = karma_up_down_indicator( element );
  var clickable = karma_clickable_indicator( element );
  element.removeAttr('class').attr( 'class', up_down + '_' + clicked + '_' + clickable );
  
}

///////////////////////////////////////////////////////////////////////////////
function karma_clickable( element, clickable ) {
  var up_down = karma_up_down_indicator( element );
  var clicked = karma_clicked_indicator( element );
  element.removeAttr('class').addClass( up_down + '_' + clicked + '_' + clickable );
}

// This is activated when the document is loaded.
$(function(){

  // Modify all karma_forms (even the promoted ones).
  $("form.karma_form,form.karma_form_promoted")

    // Call "KarmaSendForm" when the form is submitted. KarmaSendForm handles
    // all the AJAX magic.
    .submit(KarmaSendForm)

    // Send every form to "KarmaRenderForm" (which attaches click events to the form elements)
    .each(KarmaRenderForm);

  //$(".karma_clicked.karma_down").each(karma_hide_comment);
});


// $Id: collapse-fix.js,v 1.1 2007/04/26 15:14:38 jjeff Exp $
// redefining toggleFieldset for compatibility with later versions of JQuery
Drupal.toggleFieldset = function(fieldset) {
  if ($(fieldset).is('.collapsed')) {
    var content = $('> div', fieldset).hide();
    $(fieldset).removeClass('collapsed');
    content.slideDown( {
    duration: 300, // THE FIX
      complete: function() {
        // Make sure we open to height auto
        $(this).css('height', 'auto');
        Drupal.collapseScrollIntoView(this.parentNode);
        this.parentNode.animating = false;
      },
      step: function() {
         // Scroll the fieldset into view
        Drupal.collapseScrollIntoView(this.parentNode);
      }
    });
    if (typeof Drupal.textareaAttach != 'undefined') {
      // Initialize resizable textareas that are now revealed
      Drupal.textareaAttach(null, fieldset);
    }
  }
  else {
    var content = $('> div', fieldset).slideUp('medium', function() {
      $(this.parentNode).addClass('collapsed');
      this.parentNode.animating = false;
    });
  }
}


// UPGRADE: The following attribute helpers should now be used as:
// .attr("title") or .attr("title","new title")
jQuery.each(["id","title","name","href","src","rel"], function(i,n){
  jQuery.fn[ n ] = function(h) {
    return h == undefined ?
      this.length ? this[0][n] : null :
      this.attr( n, h );
  };
});

// UPGRADE: The following css helpers should now be used as:
// .css("top") or .css("top","30px")
jQuery.each("top,left,position,float,overflow,color,background".split(","), function(i,n){
  jQuery.fn[ n ] = function(h) {
    return h == undefined ?
      ( this.length ? jQuery.css( this[0], n ) : null ) :
      this.css( n, h );
  };
});

// UPGRADE: The following event helpers should now be used as such:
// .oneblur(fn) -> .one("blur",fn)
// .unblur(fn) -> .unbind("blur",fn)
var e = ("blur,focus,load,resize,scroll,unload,click,dblclick," +
  "mousedown,mouseup,mousemove,mouseover,mouseout,change,reset,select," + 
  "submit,keydown,keypress,keyup,error").split(",");

// Go through all the event names, but make sure that
// it is enclosed properly
for ( var i = 0; i < e.length; i++ ) new function(){
      
  var o = e[i];
    
  // Handle event unbinding
  jQuery.fn["un"+o] = function(f){ return this.unbind(o, f); };
    
  // Finally, handle events that only fire once
  jQuery.fn["one"+o] = function(f){
    // save cloned reference to this
    var element = jQuery(this);
    var handler = function() {
      // unbind itself when executed
      element.unbind(o, handler);
      element = null;
      // apply original handler with the same arguments
      return f.apply(this, arguments);
    };
    return this.bind(o, handler);
  };
      
};

// UPGRADE: .ancestors() was removed in favor of .parents()
jQuery.fn.ancestors = jQuery.fn.parents;

// UPGRADE: The CSS selector :nth-child() now starts at 1, instead of 0
jQuery.expr[":"]["nth-child"] = "jQuery.nth(a.parentNode.firstChild,parseInt(m[3])+1,'nextSibling')==a";

// UPGRADE: .filter(["div", "span"]) now becomes .filter("div, span")
jQuery.fn._filter = jQuery.fn.filter;
jQuery.fn.filter = function(arr){
  return this._filter( arr.constructor == Array ? arr.join(",") : arr );
};