Welcome!

AJAX & REA Authors: Lee Novak, Brad Abrams, Alin Irimie, Jonny Defh, RealWire News Distribution

Related Topics: AJAX & REA

AJAX & REA: Article

Real-World AJAX Book Preview: Designing the Server API

Real-World AJAX Book Preview: Designing the Server API

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs for the special pre-order price, click here for more information. Aimed at everyone from enterprise developers to self-taught scripters, Real-World AJAX: Secrets of the Masters is the perfect book for anyone who wants to start developing AJAX applications.

Designing the Server API
At this point, we'll look at the functionality that we need on the server to serve our client. We'll create an API that matches our major service needs, which are:

  • Display the client UI
  • Return a list of users
  • Return messages between the user and each buddy
  • Return a list of buddies with their current online status
  • Send a new message from the user to a buddy
  • Update the user's online status
We'll design our API to take advantage of some of the features of REST (Representational State Transfer) style interfaces; in particular, we'll use URLs to represent much of the API.

Displaying the Client UI
Our server is going to live at im.php (in the same directory with the other files: im.html, im.css, and im.js), and we'll have the default call to http://server/im.php return the contents of im.html. In this way, the entire interaction with our application will be contained in http://server/im.php.

Returning a List of Users
Because user management is a feature we're only minimally building out in our application, we'll meet our requirement to return a list of users in a simple way that is highly specific to our HTML. We'll just have the server return each user name wrapped in an HTML <option> that can be slotted directly into <select id="user"> in our HTML (see Listing 7).

In our PHP code, we have a function that just writes out the HTML we need:

Listing 12.10a

function printEveryoneAsHTMLOptions($people) {
    $out = "<option>***select a user***</option>\n";
    foreach ($people as $name) {
      $out .= "<option>" . $name ."</option>\n";
    }
    print $out;
}

XML, HTML, and JSON
In creating our IM server and API before creating the AJAX that uses it, we realized that it was pretty easy on the server-side to provide results in HTML, XML, and JSON formats.

We knew the HTML results would allow us to simplify the development of the AJAX code (which, when using HTML rather than XML, some people call AHAH for "Asychronous HTML and HTTP"). Working with an API that returned HTML results, our JavaScript would be able to directly display them and wouldn't need to parse XML and reformat to match our user interface.

However, we also saw how one could want to introduce other features in the client interface (some of which we'll discuss at the end of the chapter) that would not be easy to do with HTML. We designed our API to return results in either HTML, XML, or JSON.

We also included an extra method that returns "everything" for a user - the status of all of their buddies and all of their messages to and from each buddy.

The Server API (Application Programing Interface)
Here's an overview of the API and samples of the results returned from the server:

Note: in this description, {from} and {to} are placeholders for actual user names in the sending or receiving role, e.g.:

    im.php/html/John/buddies

This displays all of John's buddies as HTML. All users (from and to) must be valid as determined by the validFrom() function in im.php. All statuses must be valid as determined by the validStatus() function in im.php.

Listing 12.10b

API Calls:

ACTION METHOD URL
display client UI GET im.php
userlist GET im.php/userlist
{display all data} GET im.php/xml/{from}
im.php/html/{from}
im.php/json/{from}
buddies GET im.php/html/{from}/buddies
im.php/xml/{from}/buddies
im.php/json/{from}/buddies
msgs GET im.php/html/{from}/msgs/{to}
im.php/xml/{from}/msgs/{to}
im.php/json/{from}/msgs/{to}
sendmsg POST im.php/sendmsg
parameters: msg= text
from= user
to= user
updatestatus POST im.php/updatestatus/
parameters: status= validStatus(status)
status_msg= text
from= user

Also: add the parameter ?test=true to any URL to get result returned with the text/plain MIME type.

Buddy Results
In Listings 12.10c, 12.10d, and 12.10e, we see the server response to requests for John's buddy statuses in HTML, XML, and JSON, respectively.

Listing 12.10c

request: im.php/html/John/buddies

<div id="for_John">
   <div class="budlisting" id="buddy_Paul"><div class="offline"> </div> <p>Paul</p></
div>
    <div class="budlisting" id="buddy_George"><div class="offline"> </div> <p>George</
p></div>
    <div class="budlisting" id="buddy_Ringo"><div class="offline"> </div> <p>Ringo</
p></div>
    <div class="budlisting" id="buddy_Clarence"><div class="offline"> </div>
<p>Clarence</p></div>
</div>

The HTML results are designed to fit into the HTML of our IM client, and we'll also hook event listeners off of the ID attributes so that clicking on a buddy will open a chat window to that buddy (see Listing 12.19).

Listing 12.10d

request: im.php/xml/John/buddies

<?xml version="1.0" encoding="utf-8"?>
<imdata for="John">
    <buddies count="4" timestamp="1146708256">
      <buddy status="Offline:: Logged Out">Paul</buddy>
      <buddy status="Offline:: Logged Out">George</buddy>
      <buddy status="Offline:: Logged Out">Ringo</buddy>
      <buddy status="Offline:: Logged Out">Clarence</buddy>
    </buddies>
</imdata>

Listing 12.10e

request: im.php/json/John/buddies

{
    "buddies": {
      "count":4,"timestamp":1146708256, "list": [
      {"status":"Offline:: Logged Out","name":"Paul"},
      {"status":"Offline:: Logged Out","name":"George"},
      {"status":"Offline:: Logged Out","name":"Ringo"},
      {"status":"Offline:: Logged Out","name":"Clarence"}
      ]},
"endcap":null}

One thing to note is that the XML and JSON versions include timestamp and count values. These are to aid in parsing on the client side, and the timestamp represents the last time the buddy status was updated on the server. (The JSON results also have an "endcap" value that we throw in there to make different combinations of JSON results easier to generate in the server code.)

Message Results
In Listings 12.10f, 12.10g, and 12.10h, we see the server response to requests for John's message exchange with Paul in HTML, XML, and JSON, respectively.

Listing 12.10f

request: /im.php/html/John/msgs/Paul

<div id="for_John">
    <div id="msgs_Paul">
      <p>[14:33:09] Paul: howdy</p>
      <p>[14:33:15] John: yo!</p>
    </div>
</div>

Again, the HTML results are designed to fit into the HTML of our IM client.

Listing 12.10g

request: /im.php/xml/John/msgs/Paul
<?xml version="1.0" encoding="utf-8"?>
<imdata for="John">
    <msgs buddy="Paul" timestamp="1146699445" count="34">
      <msg timestamp="1146691989">Paul: howdy</msg>
      <msg timestamp="1146691995">John: yo!</msg>
    </msgs>
</imdata>

Listing 12.10h

request: /im.php/json/John/msgs/Paul

{
    "msgs": {
      "Paul": {"timestamp":1146699445,"count":34,"list": [
        {"timestamp":1146691989,
        "msg":'Paul: howdy'},
        {"timestamp":1146691995,
        "msg":'John: yo!'}
      ]}
    },
"endcap":null}

Again, note that the XML and JSON versions include timestamp and count values. The HTML version includes a formatted time for each message (which matches the needs of our client), but the XML and JSON versions leave the timestamp unformatted to allow more flexibility for other clients.

Overview of the IM Server Code
We'll quickly cover some highlights of the PHP code for our IM server. The code is shown in Listing 12.10i.

As mentioned above, im.php lives in the same directory as the other im.html, im.css, and im.js files. The IM messages and status need to be stored on the server in order to be available to different users, so our PHP code stores these messages in "state" files.

While a more robust approach would be to use a database like MySQL, we again took a simple approach that works well for a small number of users. Similarly, our "user database" is little more than an array of our five, uniquely named, users: John, Paul, George, Ringo, and Clarence.

By default, im.php writes "state" files in the same directory it's stored in, so this directory must be writable by the Web server.

If you have PHP set up on a Web server, you should be able to simply copy the im.* and prototype. js files into a directory, make that directory world writable (e.g., on UNIX and Mac, chmod 777 the directory; on Windows, grant everyone write access to the directory), and then browse to im.php and see all of this code in action.

The files written by im.php store the timestamps of the most recent update to status (statusTS) and new message (messageTS), one file with current status of each user (users), and then each pair of users gets their own file to store their conversations.

In order to create unique IDs for each possible "couple," we do a little trick of creating IDs with the names appended to each other in alphabetical order. For example, all conversations between Ringo and Clarence (regardless of who starts them) live under the ID ClarenceRingo and are stored in a file with the same name.

Also, we chose to store the conversations indefinitely; new messages just keep getting added to the files. If you ever want to "clean slate," you can erase the "state" files at any time.

Finally, near the top of im.php, you'll see there's a $storageDir = ""; statement. You can modify this with a full path to another location for the "state" files. Just be sure to include a final trailing slash on the path.

Listing 12.10i

<?php

/* im.php is a standalone IM client application created
    by Jay Fienberg for the Real World Ajax book.

To run this script, copy this file (im.php) with im.html, im.js, and im.css into a PHP-enabled directory on your Web server.

This directory should be writeable by your Web server, because this script saves files, and needs permission to write them.

If you want or need to put the storage files elsewhere, enter the path in the $storageDir variable, below, e.g.:

change         $storageDir = ""; to

$storageDir = "/path/to/store/files/";

Be sure to include the trailing slash!

*/

$storageDir = "";

/* setting test=true in the request causes the results to be
   returned with the content/type text/plain for debugging
*/
$test = false;
$test = $_REQUEST[‘test'];

/* DATABASE: our imitation user database starts with an array
   see the Users function below, which saves user status,
   and the getBuddyList function below, which automatically
   makes everyonea buddy of everyone else,
   and the Msgs function below, which saves messages
*/
$everyone = array("John", "Paul", "George", "Ringo", "Clarence");

/* btw, you can change the user list right here, including
   adding more people, e.g., uncomment the next line.
   Note: the IM client only supports 4 windows right now
*/
//$everyone = array("Yoko", "Linda", "Pattie", "Barbara", "Ida Mae");

//extract parameter values from POST
$from = $_POST[‘from'];
$to = $_POST[‘to'];
$msg = $_POST[‘msg'];
$status = $_POST[‘status'];
$status_msg = $_POST[‘status_msg'];

//extract actions from the URL
$parts = array();
$f = array_pop(explode("/", __FILE__));
$s = explode("/", $_SERVER["PHP_SELF"]);
$idx = array_search($f, $s);
if ($idx < count($s)) $parts = array_slice($s, $idx+1);
$type = count($parts);
if ($parts[0]=="html" || $parts[0]=="xml" || $parts[0] == "json") {
   $mode = array_shift($parts);
   $type--;
}

//route to relevant processing and response function
switch ($type) {
   case 0: //default: show IM client user interface
     include_once("im.html");
     exit;

   case 1: //userlist, sendmsg, updatestatus, or default all data
     if ($parts[0]=="userlist") {
       printEveryoneAsHTMLOptions($everyone);
     } elseif ($parts[0]=="updatestatus") {
       Users(‘set', $from, validStatus($status) . ": " . cleanMsg($status_msg));
     } elseif ($parts[0]=="sendmsg" && validUser($from) && validUser($to)) {
       Msgs(‘append', uniqueKey($from,$to), $from . ": ". cleanMsg($msg));

     } elseif (validUser($parts[0])) { //get all data
       startResponse($parts[0], $mode);
       printBuddies($parts[0], $mode);
       printMsgs($parts[0], $mode);
       endResponse($mode);
     } else {
       returnError("400", $parts[0]);
     }
     break;

   case 2: //get buddies
     if (validUser($parts[0]) && $parts[1]=="buddies") {
       startResponse($parts[0], $mode);
       printBuddies($parts[0], $mode);
       endResponse($mode);
     } else {
       returnError("400", $parts[0]);
     }
     break;

   case 3: //get messages
     if (validUser($parts[0]) && $parts[1]=="msgs" && validUser($parts[2])) {
       startResponse($parts[0], $mode);
       printMsgs($parts[0], $mode, $parts[2]);
       endResponse($mode);
     } else {
       returnError("400", $parts[0]);
     }
       break;

   default: //debug only
     print_r($parts);
     break;

}

function getBuddyList($user, $DB) {
   //imitation buddylist - returns everyone but the user (i.e., $user is $from)
   $a_user = array($user);
   return array_diff($DB, $a_user);
}

function startResponse($user, $mode="html") {
   global $test;
   if ($test==true || $mode=="json")
     header("Content-type: text/plain");
   elseif ($mode=="xml")
     header("Content-type: text/xml");

   if ($mode=="xml") {
     print ‘<?xml version="1.0" encoding="utf-8"?>'."\n";
     print ‘<imdata for="' . $user . ‘">' . "\n";
   } elseif ($mode=="html") {
     print ‘<div id="for_' . $user . ‘">' . "\n";
   } else if ($mode=="json") {
     print ‘{‘;
   }
}

function endResponse($mode="html") {
   if ($mode=="xml") print "</imdata>";
   elseif ($mode=="html") print "</div>";
   elseif ($mode=="json") print "\n" . ‘"endcap":null}';
}

function printEveryoneAsHTMLOptions($people) {
   $out = "<option>***select a user***</option>\n";
   foreach ($people as $name) {
     $out .= "<option>" . $name ."</option>\n";
   }
   print $out;
}

function printBuddies($user, $mode="html") {
   global $everyone;
   $buds = getBuddyList($user, $everyone);
   $xml = "";
   $html = "";
   $json = "\n\t" . ‘"buddies": {‘ ."\n";

   $xml .= "\t" . ‘<buddies count="' . count($buds) . ‘"';
   $json .= "\t\t" . ‘"count":' . count($buds) . ‘,';
   $cnt = 0;
   foreach ($buds as $name) {
     $bud_stat = Users(‘get', $name);
     if ($cnt==0) {
       $xml .= ‘ timestamp="' . $bud_stat[0] . ‘">' ."\n";
       $json .= ‘"timestamp":'. $bud_stat[0] . ‘, "list": [‘ ;
       $cnt++;
     }
     $xml .= "\t\t<buddy status=\"" . $bud_stat[1] . "\">" . $name . "</buddy>\n";

   $html .= "\t" . ‘<div class="budlisting" id="buddy_' . $name . ‘"><div class="'.
strtolower(simpleStatus($bud_stat[1])) . ‘"> </div> <p>' . $name . "</p></div>\n";

   $json .= "\n\t\t" . ‘{"status":"' . $bud_stat[1] . ‘","name":"' . $name . ‘"},';

   }
   $xml .= "\t</buddies>\n";
   $json = rtrim($json, ‘,') . "\n\t\t]},";

   if ($mode=="html") print $html;
   elseif ($mode=="xml") print $xml;
   elseif ($mode=="json") print $json;
}

function printMsgs($user, $mode="html", $to=null) {
   global $everyone;
   $buds = getBuddyList($user, $everyone);
   $xml = "";
   $html = "";
   $json = "";

   $json .= "\n\t" . ‘"msgs": {‘;

   foreach ($buds as $nameforkey) {
     $key = uniqueKey($nameforkey, $user);
     $keymsgs = Msgs(‘get', $key);
     $msgcnt = count($keymsgs[1]);

     if ($to==null || ($to!==null && $to==$nameforkey)) {

       $json .= "\n\t\t" . ‘"' . $nameforkey . ‘": {‘;
       $json .= ‘"timestamp":' . $keymsgs[0] . ‘,';
       $json .= ‘"count":'. $msgcnt . ‘,';
       $json .= ‘"list": [‘;

       if ($msgcnt == 0) {
         $xml .= "\t" . ‘<msgs buddy="' . $nameforkey . ‘" timestamp="' . $keymsgs[0]
. ‘" count="0" />' . "\n";

         $html .= "\t" .'<div id="msgs_' . $nameforkey . ‘"></div>' ."\n";

       } else {
         $xml .= "\t" . ‘<msgs buddy="' . $nameforkey . ‘" timestamp="' . $keymsgs[0]
. ‘" count="' . $msgcnt .'">' . "\n";

         $html .= "\t" . ‘<div id="msgs_' . $nameforkey . ‘">' ."\n";

         foreach ($keymsgs[1] as $im) {
           $xml .= "\t\t". ‘<msg timestamp="' . $im[0] . ‘">' . $im[1] . "</msg>\n";
           $html .= "\t\t<p>" . "[" . date("H:i:s",$im[0]) . "] " . $im[1] . "</p>\n";

           $json .= "\n\t\t\t\t{" . ‘"timestamp":' . $im[0] . ‘,';
           $json .= "\n\t\t\t\t" . ‘"msg":\'' . $im[1] . ‘\'},';
         }
         $xml .= "\t</msgs>\n";
         $html .= "\t</div>\n";
         $json = rtrim($json, ‘,') . "\n\t\t]},";
       }      }    }

   $json = rtrim($json, ‘,') . "\n\t},";

   if ($mode=="html") print $html;
   elseif ($mode=="xml") print $xml;
   elseif ($mode=="json") print $json;
}

function cleanMsg($msg) {
   $msg = str_replace("[amp]", "&", $msg);
   $msg = str_replace(‘"', """, $msg);
   $msg = strip_tags($msg);
   return $msg;
}

function validStatus($status) {
   if (!($status=="Offline"
     || $status=="Away"
     || $status=="Available"))
     $status = "Offline";
   return $status . ‘:';
}

function simpleStatus($status) {
   $comp = explode(":", $status);
   return $comp[0];
}

function validUser($name) {
   global $everyone;
   return in_array($name, $everyone);
}

function returnError($type, $data) {
   HTTPStatus($type);
   print "Error " . $type . " with ";
   print_r($data);
}

//simple message storage database
function Msgs($verb, $noun="", $val="") {
   $msgarray = array();
   $msgTS = 0;

   $test1 = unserialize(getFileContents($noun));
   if (!empty($test1)) $msgarray = $test1;

   $test2 = unserialize(getFileContents("msgTS"));
   if (!empty($test2)) $msgTS = $test2;

   switch ($verb) {
     case ‘get':
       return array($msgTS, $msgarray);
       break;
     case ‘append':
       $t = time();
       $msgarray[] = array($t, $val);
       if ($msgTS < $t) $msgTS = $t;
       setFileContents($noun, serialize($msgarray));
       setFileContents("msgTS", serialize($t));
       return $t;
       break;
     case ‘delete':
       $msgarray = null;
       $t = time();
       if ($msgTS < $t) $msgTS = $t;
       setFileContents($noun, serialize($msgarray));
       setFileContents("msgTS", serialize($t));
       return $t;
       break;
     }
   }

   //simple user and status storage database
   function Users($verb, $noun="", $val="") {
     $defaultStatus = "Offline:";
     $statusarray = array();
     $statusTS = 0;

     $test1 = unserialize(getFileContents("users"));
     if (!empty($test1)) $statusarray = $test1;

     $test2 = unserialize(getFileContents("statusTS"));
     if (!empty($test2)) $statusTS = $test2;

   switch ($verb) {
     case ‘get':
       if (!isset($statusarray[$noun])) $statusarray[$noun] = $defaultStatus;
       return array($statusTS, $statusarray[$noun]);
       break;
     case ‘set':
       $statusarray[$noun] = $val;
       $t = time();
       if ($statusTS < $t) $statusTS = $t;
       setFileContents("users", serialize($statusarray));
       setFileContents("statusTS", serialize($t));

       return $t;
       break;
     case ‘delete':
       if (isset($statusarray[$noun])) $statusarray[$noun] = null;
       $t = time();
       if ($statusTS < $t) $statusTS = $t;
       setFileContents("users", serialize($statusarray));
       setFileContents("statusTS", serialize($t));

       return $t;
       break;
   }
}

function uniqueKey($n1, $n2) {
   /* this is a simple way to generate a unique key for
   a conversation between two people in this app -
   each key is just the two names put together in alpha order
   */
   return ($n1 < $n2) ? $n1 . $n2 : $n2 . $n1;
}

function getFileContents($key) {
   $contents = "";
   $handle = fopen($storageDir . $key, ‘r');
   if ($handle && filesize($key) > 0 ) {
     $contents = fread($handle, filesize($key));
     fclose($handle);
   } elseif (!$handle) {
     setFileContents($key, ‘');
   }
     return $contents;
   }

   function setFileContents($key, $content) {
     $handle = fopen($storageDir . $key, ‘w');
     $contents = fwrite($handle, $content);
     fclose($handle);
     return $contents;
   }

   function HTTPStatus($num) {

     static $http = array (
       100 => "HTTP/1.1 100 Continue",
       101 => "HTTP/1.1 101 Switching Protocols",
       200 => "HTTP/1.1 200 OK",
       201 => "HTTP/1.1 201 Created",
       202 => "HTTP/1.1 202 Accepted",
       203 => "HTTP/1.1 203 Non-Authoritative Information",
       204 => "HTTP/1.1 204 No Content",
       205 => "HTTP/1.1 205 Reset Content",
       206 => "HTTP/1.1 206 Partial Content",
       300 => "HTTP/1.1 300 Multiple Choices",
       301 => "HTTP/1.1 301 Moved Permanently",
       302 => "HTTP/1.1 302 Found",
       303 => "HTTP/1.1 303 See Other",
       304 => "HTTP/1.1 304 Not Modified",
       305 => "HTTP/1.1 305 Use Proxy",
       307 => "HTTP/1.1 307 Temporary Redirect",
       400 => "HTTP/1.1 400 Bad Request",
       401 => "HTTP/1.1 401 Unauthorized",
       402 => "HTTP/1.1 402 Payment Required",
       403 => "HTTP/1.1 403 Forbidden",
       404 => "HTTP/1.1 404 Not Found",
       405 => "HTTP/1.1 405 Method Not Allowed",
       406 => "HTTP/1.1 406 Not Acceptable",
       407 => "HTTP/1.1 407 Proxy Authentication Required",
       408 => "HTTP/1.1 408 Request Time-out",
       409 => "HTTP/1.1 409 Conflict",
       410 => "HTTP/1.1 410 Gone",
       411 => "HTTP/1.1 411 Length Required",
       412 => "HTTP/1.1 412 Precondition Failed",
       413 => "HTTP/1.1 413 Request Entity Too Large",
       414 => "HTTP/1.1 414 Request-URI Too Large",
       415 => "HTTP/1.1 415 Unsupported Media Type",
       416 => "HTTP/1.1 416 Requested range not satisfiable",
       417 => "HTTP/1.1 417 Expectation Failed",
       500 => "HTTP/1.1 500 Internal Server Error",
       501 => "HTTP/1.1 501 Not Implemented",
       502 => "HTTP/1.1 502 Bad Gateway",
       503 => "HTTP/1.1 503 Service Unavailable",
       504 => "HTTP/1.1 504 Gateway Time-out"
     );

     header($http[$num]);

   }
   ?>

Building Out the AJAX
In order to highlight the JavaScript/AJAX parts of our applicaiton, we'll present the JavaScript as if it's being written to connect preexisting HTML/CSS and PHP server code, e.g., everything presented earlier in this chapter.

We did, in fact, create the JavaScript after having developed much of the user interface and the server-side API. But, it would be misleading to suggest that there wasn't adjustments going on between the HTML, CSS, JavaScript, and PHP throughout this application's development. For example, as we developed our class structure in the JavaScript, we added new features to the HTML structure, new style rules to the CSS, and made adjustments to result format returned by the PHP.

This content is reprinted from Real-World AJAX: Secrets of the Masters published by SYS-CON Books. To order the entire book now along with companion DVDs, click here to order.

More Stories By James Benson

Jim Benson, AICP, is the COO of Gray Hill Solutions in Seattle. Gray Hill creates tools for government and industry to harness and utilize real-time data. Jim has always driven applications for his clients to store and provide information in easily extensible ways. Web 2.0 has therefore been a natural environment for him. He is also involved with the Cooperation Commons and the Institute for the Future's Future Commons to study human cooperation and envision the future of cooperation. Jim's tags: Gray Hill Solutions (www.grayhillsolutions.com), Jim's Blog (http://ourfounder.typepad.com), Cooperation Commons (www.cooperationcommons.org), Institute for the Future (www.iftf.org).

More Stories By Jay Fienberg

Jay Fienberg is co-founder of Juxtaprose where he designs information architecture and user experience for websites and information systems.

He specializes in design for content and information-rich websites and web-based social and collaboration systems. His current preferred CMS is ExpressionEngine, though he also works with Wordpress and Joomla, and still gets called upon to make SharePoint do interesting CMS-like things for enterprise intranets.

Since the early 1990s, Jay also has designed and developed hypertext, database, and content management systems and worked in a wide range of programming languages including XML, SQL, SGML, Python, PHP, Javascript, Java, HTML, CSS and APL.

For anything more official, please visit jayfienberg.com.

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.