Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p>Getting into JQuery makes things really easy. Just practice 'til it sticks!<br> If you're new to JQuery, it's worth looking through the <a href="http://api.jquery.com/" rel="nofollow">API docs</a>. It really helps you get started.</p> <p>Anyway. After reading your question, I decided to challenge myself and speed-create a <a href="http://jsfiddle.net/jfALF/" rel="nofollow">small plugin per your request</a>, mostly for funsies, but also because I've been there. </p> <p><sub></sub></p> <p>Clocked it at ~2 hrs (including tea break).<br> It may be a tad inefficient, but it works. That's all that matters, right?<br> <sub>...I hope I'm not coming across as a smug bastard that you want to punch in the face; I'm actually terrible and need the practice >.&lt;</sub></p> <p>Hopefully you'll find some use for it, or at least learn <em>something</em> from it.<br> Let me explain my approach for building stuff * <em>ahem</em> *</p> <hr> <p>It is generally a good idea to have an outline or an idea of what needs to be done before making, well, anything. Writing comments that outline some basic things you need to do really help and speeds the process.</p> <p>My outlines for making the plugin were something like</p> <ul> <li>Settings for class names, input size, prefixes and delimiters for input names</li> <li>Template functions that build and return elements (easier to handle)</li> <li>Update the form inputs with new ids and names after something is removed or added</li> <li>Make sure it's relatively easy to parse on the server side once you submit the form</li> <li>Have some plugin functions to make it easier to add/edit/remove stuff</li> <li>Double -- quintuple -- check that everything works as expected (code self-QA; good practice)</li> </ul> <hr> <h2>Code | <a href="http://jsfiddle.net/jfALF/" rel="nofollow">JSFiddle</a></h2> <p>Here is the plugin code, I named it <code>GroupManager</code><br> <sub>(don't judge me, I'm terrible with names)</sub></p> <p><strong>Javascript</strong></p> <pre class="lang-js prettyprint-override"><code>(function($){ $.fn.GroupManager = function(opts) { //Self reference, for when you're deep in the darkness and need a light var _self = this; //Defaults var def = { sDelimeter: "_", sGroupPrefix: "g", sFirstNamePrefix: "fn", sLastNamePrefix: "ln", iInputSize: 25, clContainer: "group_container", clTable: "person_table", clTitle: "group_title", clTitleInput: "group_title_input", clDelBtn: "del_btn", sDelGroupTitle: "Remove group", sDelPersonTitle: "Remove person from this group", sAddPersonTitle: "Add a person to group", clAddBtn: "add_btn", clFirstName: "fname", clLastName: "lname", clPersonMenu: "menu" }; //Extend option properties to defaults $.extend(def, opts); //Template to create a new group function tmpl_group_table(grp_name){ var container = $("&lt;div&gt;"), group_ttl = $("&lt;div&gt;"), group_field = $("&lt;fieldset&gt;"), group_table = $("&lt;table&gt;"), group_title_input = $("&lt;input&gt;"); container.addClass(def.clContainer); //Add the group title label &amp; input group_title_input.addClass(def.clTitleInput); //If a group name was provided, set its value as well if(grp_name) group_title_input.val(grp_name); group_ttl.addClass(def.clTitle); group_ttl.append("&lt;label&gt;Group - &lt;/label&gt;"); group_ttl.append(group_title_input); //Set up the table group_table.addClass(def.clTable); group_table.append("&lt;caption&gt;People in this group:&lt;/caption&gt;"); group_table.append("&lt;thead&gt;&lt;tr&gt;&lt;th&gt;First name&lt;/th&gt;&lt;th&gt;Last Name&lt;/th&gt;&lt;th&gt;&amp;nbsp;&lt;/th&gt;&lt;tr&gt;&lt;/thead&gt;"); group_table.append("&lt;tbody&gt;&lt;/tbody&gt;"); group_table.append(tmpl_person_row(false)); //Link elements group_field.append(group_table); container.append(group_ttl); container.append(group_field); return container; } //Function that creates a new person row in table function tmpl_person_row(btn){ var row = $("&lt;tr&gt;"), col1 = $("&lt;td&gt;"), i1 = $("&lt;input&gt;"), col2 = $("&lt;td&gt;"), i2 = $("&lt;input&gt;"), col3 = $("&lt;td&gt;"); //Set up input for first name i1.addClass(def.clFirstName); i1.attr("size", def.iInputSize); col1.append(i1); //Set up input for last name i2.addClass(def.clLastName); i2.attr("size", def.iInputSize); col2.append(i2); //Make a delete button, if specified if(btn){ var del_btn = $("&lt;div&gt;"); del_btn.text("x"); del_btn.addClass(def.clDelBtn); del_btn.attr("title", def.sDelPersonTitle); del_btn.click(function(){ var grp = $(this).parents("."+def.clContainer); $(this).closest("tr").remove(); reassign(grp); }); col3.append(del_btn); }else{ col3.html("&amp;nbsp;"); } col3.addClass(def.clPersonMenu); //Link elements to row row.append([col1, col2, col3]); return row; } //Function that reassigns input ids and names, usually called after something was removed function reassign(group){ var ind = group.index() + 1, id = def.sGroupPrefix + def.sDelimeter + ind, title_input = group.find("."+def.clTitleInput), title_label = title_input.siblings("label"), p_table = group.find("." + def.clTable); title_label.attr("for", id); title_input.attr({id: id, name: id}); p_table.find("tbody tr").each(function(i, e){ var fname_id = def.sFirstNamePrefix + def.sDelimeter + ind + def.sDelimeter + (i+1), lname_id = def.sLastNamePrefix + def.sDelimeter + ind + def.sDelimeter + (i+1); $(e).find("."+def.clFirstName).attr({id: fname_id, name: fname_id}); $(e).find("."+def.clLastName).attr({id: lname_id, name: lname_id}); }); } //Add a person to the specified group (0-based index) this.addPerson = function(group_no, first_name, last_name){ var grp = _self.find("."+def.clContainer).eq(group_no), persons = grp.find("."+def.clTable).find("tbody"), row = tmpl_person_row(true); if(first_name) $("."+def.clFirstName, row).val(first_name); if(last_name) $("."+def.clLastName, row).val(last_name); persons.append(row); reassign(grp); }; //Set a person's first and last name this.setPerson = function(group_no, person_no, first_name, last_name){ var grp = _self.find("."+def.clContainer).eq(group_no), row = grp.find("tbody tr").eq(person_no); if(first_name) $("."+def.clFirstName, row).val(first_name); if(last_name) $("."+def.clLastName, row).val(last_name); }; //Add a bunch of people to the specified group, assuming an array with arrays that hold two strings this.addPeople = function(group_no, arr){ var grp = _self.find("."+def.clContainer).eq(group_no), rows = grp.find("tbody tr"); if(arr.length &gt; rows.length){ //Add more rows until it matches array length for(var i = 0; i &lt; (arr.length - rows.length); i++){ _self.addPerson(group_no); } reassign(grp); rows = grp.find("tbody tr"); } for(var j = 0; j &lt; arr.length; j++){ var row = rows.eq(j); $("."+def.clFirstName, row).val(arr[j][0]); $("."+def.clLastName, row).val(arr[j][4]); } }; //Add a group to the form. Can provide title and an array with people to populate it from the start this.addGroup = function(title, arr){ var grp = tmpl_group_table(title); _self.append(grp); if(arr){ _self.addPeople(grp.index(), arr); } //Add a remove group button if it's not the first group if(grp.index() !== 0){ var del_btn = $("&lt;div&gt;"); del_btn.text("x"); del_btn.addClass(def.clDelBtn); del_btn.attr("title", def.sDelGroupTitle); grp.find("."+def.clTitle).append(del_btn); del_btn.click(function(){ $(this).parents("."+def.clContainer).remove(); _self.refresh(); }); } //Add an "add people"-button var add_people_btn = $("&lt;div&gt;"); add_people_btn.text("+"); add_people_btn.addClass(def.clAddBtn); add_people_btn.attr("title", def.sAddPersonTitle); add_people_btn.click(function(){ _self.addPerson(grp.index()); }); grp.find("."+def.clTable+" caption").append(add_people_btn); reassign(grp); }; //Remove a group this.removeGroup = function(group_no){ var grp = _self.find("."+def.clContainer).eq((group_no)); grp.remove(); _self.refresh(); }; //Refresh form this.refresh = function(){ _self.find("."+def.clContainer).each(function(){ reassign($(this));}); }; //Add an empty group _self.addGroup(); return this; }; }(jQuery)); $(function(){ //Create an instance for the empty form var gm = $("#someform").GroupManager(); //Add groups filled with a title and people var unknowns = [ ["John", "Doe"], ["Mary", "Jane"], ["Tom", "Harry"] ]; var randoms = [ ["Looney", "McFloo"], ["Private", "Detective"], ["Tom", "Jones"], ["Rick", "James"] ]; gm.addGroup("Unknowns", unknowns); gm.addGroup("Randoms", randoms); //Attach click handlers for the main menu $("#add_group").click(function(){ gm.addGroup();}); $("#save_form").click(function(e){ e.preventDefault(); console.log($("#someform").serialize());}); }); </code></pre> <p><strong>HTML</strong></p> <pre class="lang-html prettyprint-override"><code>&lt;div id='menu'&gt; &lt;button id='add_group' type="button"&gt;Add group&lt;/button&gt; &lt;button id='save_form' type="button"&gt;Save&lt;/button&gt; &lt;/div&gt; &lt;form id="someform" method="post" action="#"&gt;&lt;/form&gt; </code></pre> <p><strong>CSS</strong></p> <pre class="lang-css prettyprint-override"><code>body{ margin: 0; padding: 0 20px; font-family: Arial, Helvetica, sans-serif;} #menu{ margin-bottom: 20px; } #save_form{ margin-left: 25px; } .group_container{ margin-bottom: 20px; border: 1px solid gray; float: left; clear: both; } .group_title{ text-align: left; border-bottom: 1px solid lightgray; margin: 0 10px; padding-left: 10px; } .group_title_input{ width: 150px; border: 0; text-align: left; text-overflow: ellipsis; font-weight: bold; } .del_btn, .add_btn{ color: red; cursor: pointer; text-align: center; display: inline-block; border: 1px solid transparent; height: 16px; width: 16px; line-height: 16px; float: right; } .add_btn{ color: green; float: right; font-size: 1.25em; line-height: 1.25em; } .del_btn:hover, .add_btn:hover{ border-color: lightgray; } .del_btn:active, .add_btn:active{ color: white; background-color: black; } .person_table caption{ margin-bottom: 10px; font-size: 0.75em; text-align: left; font-style: italic; } .person_table input{ text-align: left; font-size: 0.833em; } .group_container{ font-size: 14px; } .person_table{ border-collapse: collapse; } .person_table thead th:not(:nth-child(3)){ font-size: 0.833em; border-bottom: 1px solid black; font-weight: normal; text-align: center; } .person_table tbody td:nth-child(3){ width: 30px; } .group_container fieldset{ border: 0px; } .groups, .persons{ border: 1px solid gray; padding: 4px; } .fname, .lname{ width: 100px; } </code></pre>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload