Recording options selected from dropdown menu
Your indentation is a little funky. I can understand not indenting after $(function () { (though I would do so myself), but the change handler's body really should be indented
You've got everything in one function, though it'd probably be more readable to separate things. For instance, addRecord, removeRecord (with further specialized functions for adding/removing a row, and adding/removing an option.
Use jQuery to build the HTML; don't type it out as a long string. Doing so is error-prone
I don't know how modalMsg works, but I imagine that the result you receive in the callback can have at least 2 different values. What happens if the user says "No, I don't want to delete that resource"? It looks like it gets removed anyway...
Moreover, the wording of that modal seems a bit severe considering you can just add the resource back in a moment later. The word "Delete" can scare the pants of people.
I have to wonder why your ajax call is a POST. I'm guessing you're updating something server-side when a record is added to the table, but in that case, shouldn't there also be a POST or DELETE call when the user removes something from the table?
You don't have any error handling, so if the ajax call should fail for any reason, the option is already gone, and the record isn't in the table either.
It'd be easy to add a little bit of caching, just in case someone changes their mind twice (adds, removes, adds again)
The hidden input should be in a td, rather than floating around inside a tr.
There's no need to create unique class names for every row. The idea of class names is that they can be applied to several elements. If you want to uniquely identify something, use an ID attribute - it's what they're for. However, in this case, we don't even need that, as we can use closures instead.
Rather than add and remove the no-resources row and the "No more contacts available" alert, it'd be easier to have these in the HTML already, and simply show/hide them as needed. The less HTML you have to build in JS, the better.
There's the issue of sorting the records, too. Not so much in the table, but rather in the dropdown. If I were to select the 3rd option out of, say, 10, and then change my mind, I'd expect it to behave like "undo": That's the option is back in the dropdown, as the 3rd option. But tackling this is a bit beyond the scope of this review.


I'd do something like this (assuming that the 1st option - the do-nothing option - has its value attribute set to an empty string). I haven't looked at error handling because I don't know how you'd prefer to do that.

Haven't tested this, but the general idea should be apparent

$(function () {
  var dropdown = $("#addResource"),
      table = $("#resourcesTable tbody"),
      noResourcesRow = table.find(".no-resources"),
      noContactsAlert = dropdown.parent().find(".alert"),
      url = "/admin/contacts/json",
      recordsCache = {};

  function fetchRecord(id) {
    if(recordsCache[id]) {
      addTableRow(records[id]); // use cache if possible
    } else {
      $.post(url, {id: id}, function (record) {
        recordsCache[id] = record; // add to cache
        addTableRow(record);
      });
    }
  }

  function addTableRow(record) {
    var row = $("&lt;tr&gt;&lt;/tr&gt;").addClass("resource").data("record", record),
        removeLink = $("&lt;a&gt;&lt;/a&gt;", {
          "href": "#",
          "class": "remove-resource",
          "title": "Remove resource"
        }).append($("&lt;i&gt;&lt;/i&gt;", { "class": "icon-trash" }));

    row.append($("&lt;td&gt;&lt;/td&gt;").text(record.name)).append($("&lt;input&gt;&lt;/input&gt;", {
      type: "hidden",
      value: record.id
    }));

    // this could be done with a loop too
    row.append($("&lt;td&gt;&lt;/td&gt;").text(record.phone));
    row.append($("&lt;td&gt;&lt;/td&gt;").text(record.email));
    row.append($("&lt;td&gt;&lt;/td&gt;").text(record.rate));

    row.append($("&lt;td&gt;&lt;/td&gt;").append(removeLink));

    removeLink.on("click", function (event) {
      event.preventDefault();
      modalMsg("Are you sure you wish to remove " + record.name + " as a resource?", "", function (result) {
        // FIXME: Add proper checking of the result
        if(...) {
          removeTableRow();
        }
      });
    });

    table.append(row);
  }

  function removeTableRow(row) {
    var record = row.data("record");
    row.fadeOut(function () {
      row.remove();
      if(!table.children.length) {
        noResourcesRow.show(); // or fadeIn() maybe
      }
      addOption(record.id, record.name); // add the option back in
    }
  }

  function addOption(id, name) {
    dropdown.append($("&lt;option&gt;&lt;/option&gt;", { value: id }).text(name));
    noContactsAlert.hide();
    dropdown.show();
  }

  function removeOption(option) {
    option.remove();
    if(dropdown.children.length === 1) { // includes the do-nothing option
      dropdown.hide();
      noContactsAlert.show();
    }
  }

  dropdown.on("change", function (event) {
    if(!this.value) {
      return; // ignore event if the do-nothing option is selected
    }

    fetchRecord(this.value);
    removeOption(dropdown.children(":selected"));
    this.value = ""; // set the do-nothing option
  });
});