diff --git a/README.md b/README.md index 19bc05f..f3d3e85 100644 --- a/README.md +++ b/README.md @@ -97,6 +97,9 @@ This is the first release with JavaScript and jQuery included. As a reference, h ### v[1.2.0] Asynchronously edit and delete jobs +[View pull request](../../pull/6). +[Download release](https://github.com/codeunion/linkedout-example/archive/v1.2.0.zip). + - [X] User can edit jobs on the résumé show page - [X] User can delete jobs on the résumé show page - [X] Editing jobs occurs asynchronously @@ -104,13 +107,19 @@ This is the first release with JavaScript and jQuery included. As a reference, h - [X] Edited jobs are updated on the page without refresh - [X] Deleted jobs are removed from the page without refresh -### v[1.4.0] Asynchronously edit and delete skills +### v[1.3.0] Asynchronously delete skills + +- [X] User can delete skills on the résumé show page +- [X] Deleting skills occurs asynchronously +- [X] Deleted skills are removed from the page without refresh + +This release also makes improvements to the underlying architecture of the application, including the following changes: + +- [X] Use [RESTful routing](http://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services) conventions +- [X] Logging in `skills.js` +- [X] Format of HTML and JavaScript more consistent -- [ ] User can edit skills on the résumé show page -- [ ] User can delete skills on the résumé show page -- [ ] Editing and deleting skills occurs asynchronously -- [ ] Edited skills are updated on the page without refresh -- [ ] Deleted skills are removed from the page without refresh +In addition, the `/resumes/edit` page has been changed to `/users/edit` and now contains only a form to edit the user profile. This change removes redundancy in editing other resources. ### v[1.5.0] Display education information diff --git a/linkedout.rb b/linkedout.rb index 83027ab..f6aee07 100644 --- a/linkedout.rb +++ b/linkedout.rb @@ -25,14 +25,11 @@ def default_user erb :'resumes/show' end -get "/resumes/edit" do - @jobs = default_user.jobs - @skills = default_user.skills - - erb :'resumes/edit' +get "/users/edit" do + erb :'users/edit' end -put "/users/edit" do +put "/users" do user_attrs = params[:user] default_user.update(user_attrs) @@ -58,11 +55,10 @@ def default_user end end -put "/jobs/edit" do +put "/jobs/:job_id" do + job_id = params[:job_id] job_attrs = params[:job] - job_id = job_attrs.delete("id") - job = Job.get(job_id) job.update(job_attrs) @@ -73,11 +69,10 @@ def default_user end end -delete "/jobs" do +delete "/jobs/:job_id" do + job_id = params[:job_id] job_attrs = params[:job] - job_id = job_attrs.delete("id") - job = Job.get(job_id) job.destroy @@ -96,19 +91,25 @@ def default_user skill.save if request.xhr? # this will return true when handling an AJAX request - partial :'partials/skill', :locals => { :skill => skill } + html = "
  • " + html += partial :'partials/skill', :locals => { :skill => skill } + html += "
  • " + html else redirect "/" end end -put "/skills/edit" do +delete "/skills/:skill_id" do + skill_id = params[:skill_id] skill_attrs = params[:skill] - skill_id = skill_attrs.delete("id") - skill = Skill.get(skill_id) - skill.update(skill_attrs) + skill.destroy - redirect "/" + if request.xhr? + skill_id + else + redirect "/" + end end diff --git a/public/js/jobs.js b/public/js/jobs.js index ed3ed93..e98ffd3 100644 --- a/public/js/jobs.js +++ b/public/js/jobs.js @@ -11,12 +11,16 @@ var createJobOnSubmit = function() { evt.preventDefault(); var $newJobForm = $(this); + + // Use the destination path defined in the form's 'action' + // attribute, i.e. `/jobs` + var actionPath = $newJobForm.attr('action'); var newJobFormData = $(this).serialize(); - log("Sending POST request to /jobs"); + log("Sending POST request to " + actionPath); - $.post("/jobs", newJobFormData, function(jobHTML) { - log("Received response from POST request to /jobs"); + $.post(actionPath, newJobFormData, function(jobHTML) { + log("Received response from POST request to " + actionPath); log("Adding new job element to list"); $newJobForm.parent('li').before(jobHTML); @@ -48,21 +52,25 @@ var updateJobOnSubmit = function() { evt.preventDefault(); var $editJobForm = $(this); + + // Use the destination path defined in the form's 'action' + // attribute, i.e. `/jobs/:job_id` + var actionPath = $editJobForm.attr('action'); var editJobFormData = $editJobForm.serialize(); // The job item to update is the the element of class // 'job' in the same containing element (in this case,
  • ) var $jobElem = $editJobForm.siblings('.job'); - log("Sending PUT request to /jobs/edit"); + log("Sending PUT request to " + actionPath); - // Send async PUT request to /jobs/edit + // Send async PUT request to /jobs/:job_id $.ajax({ - url: '/jobs/edit', + url: actionPath, type: 'PUT', data: editJobFormData }).done(function(responseData) { - log("Received response from PUT request to /jobs/edit"); + log("Received response from PUT request to " + actionPath); // This function will execute when the response comes // back from the server // @@ -87,23 +95,27 @@ var deleteJobOnSubmit = function() { evt.preventDefault(); var $deleteJobForm = $(this); + + // Use the destination path defined in the form's 'action' + // attribute, i.e. `/jobs/:job_id` + var actionPath = $deleteJobForm.attr('action'); var deleteJobFormData = $deleteJobForm.serialize(); // Grab the containing
  • element so that we can remove // it when the delete request completes var $jobContainerElem = $(this).closest('li'); - log("Sending DELETE request to /jobs"); + log("Sending DELETE request to " + actionPath); // Sending a DELETE request requires using the jQuery // .ajax() function and configuring the url, type, // data, and complete options $.ajax({ - url: '/jobs', + url: actionPath, type: 'DELETE', data: deleteJobFormData }).done(function() { - log("Received response from DELETE request to /jobs"); + log("Received response from DELETE request to " + actionPath); log("Removing deleted job element"); $jobContainerElem.remove(); diff --git a/public/js/skills.js b/public/js/skills.js index e388e05..1bb7904 100644 --- a/public/js/skills.js +++ b/public/js/skills.js @@ -1,19 +1,30 @@ var createNewSkillsOnSubmit = function() { - // What's this with the dollar sign in the variable? - // It doesn't change how the variable works, it is just an - // indicator to say "this variable contains a jQuery object" - var $newSkillForm = getNewSkillForm(); + // Bind to the "submit" event of the new skill form using + // event delegation + $('.skills').on('submit', 'form[name="new_skill"]', function(evt) { + log("New skill form submitted"); - // Bind to the "submit" event of the new skill form - $newSkillForm.submit(function (evt) { // Prevent the submit from sending an HTTP POST request // Instead, we'll handle the request with AJAX evt.preventDefault(); + // What's this with the dollar sign in the variable? + // It doesn't change how the variable works, it is just an + // indicator to say "this variable contains a jQuery object" + // + // In this context, `this` points to the form which has just + // been submitted, which is what we want. + var $newSkillForm = $(this); + + // Use the destination path defined in the form's 'action' + // attribute, i.e. `/skills` + var actionPath = $newSkillForm.attr('action'); + // Grab the form data and serialize it as a string in // standard URL-encoded notation - var skillFormData = $(this).serialize(); + var newSkillFormData = $newSkillForm.serialize(); + log("Sending POST request to " + actionPath); // Send a POST request asynchronously to the "/skills" // route on the server, passing the serialized form data // in the request body. @@ -22,33 +33,66 @@ var createNewSkillsOnSubmit = function() { // this will send a request to the server to save this // skill to the database // - // The insertNewSkillIntoDOM function (argument 3) will + // The anonymous function passed as the third argument will // be executed when the browser receives a response from // the server. It is passed the response body, which in // this case is a snippet of HTML representing the newly- // created skill. - $.post("/skills", skillFormData, insertNewSkillIntoDOM); + $.post(actionPath, newSkillFormData, function(newSkillHTML) { + log("Received response from POST request to " + actionPath); + + log("Adding new skill element to list"); + // Add the new skill to the list, just before the form's + // parent 'li' element + $newSkillForm.parent('li').before(newSkillHTML); + + log("Resetting new skill form") + // Reset the form so that new skills can be added + $newSkillForm.get(0).reset(); + }); }); }; -var getNewSkillForm = function() { - // Select the new skill form using its name attribute - return $('form[name="new_skill"]'); -}; +var deleteSkillOnSubmit = function() { + $('.skills').on('submit', 'form[name="delete_skill"]', function(evt) { + log("Edit skill form submitted"); + + evt.preventDefault(); + + var $deleteSkillForm = $(this); + + // Use the destination path defined in the form's 'action' + // attribute, i.e. `/skills/:skill_id` + var actionPath = $deleteSkillForm.attr('action'); + var deleteSkillFormData = $deleteSkillForm.serialize(); + + // Grab the containing
  • element so that we can remove + // it when the delete request completes + var $skillContainerElem = $(this).closest('li'); -var insertNewSkillIntoDOM = function(newSkillHTML) { - var $newSkillForm = getNewSkillForm(); + log("Sending DELETE request to " + actionPath); - // Add the new skill to the list, just before the form's - // parent 'li' element - $newSkillForm.parent('li').before(newSkillHTML); + // Sending a DELETE request requires using the jQuery + // .ajax() function and configuring the url, type, + // data, and complete options + $.ajax({ + url: actionPath, + type: 'DELETE', + data: deleteSkillFormData + }).done(function() { + log("Received response from DELETE request to " + actionPath); - // Reset the form so that new skills can be added - $newSkillForm.get(0).reset(); + log("Removing deleted skill element"); + $skillContainerElem.remove(); + }); + }); }; + // Wait to execute all code until the document is ready // (i.e. all of the DOM nodes have been loaded) $(document).ready(function() { createNewSkillsOnSubmit(); + + deleteSkillOnSubmit(); }); diff --git a/views/partials/job.erb b/views/partials/job.erb index 8e2ecd4..d054ee3 100644 --- a/views/partials/job.erb +++ b/views/partials/job.erb @@ -1,7 +1,6 @@
    -
    + -
    Edit diff --git a/views/partials/job_edit.erb b/views/partials/job_edit.erb index 9b2ecd4..c5ac33c 100644 --- a/views/partials/job_edit.erb +++ b/views/partials/job_edit.erb @@ -1,12 +1,10 @@ <% if hidden %> - +<%= skill.name %> diff --git a/views/partials/skill_edit.erb b/views/partials/skill_edit.erb deleted file mode 100644 index f42ead9..0000000 --- a/views/partials/skill_edit.erb +++ /dev/null @@ -1,12 +0,0 @@ -<% skill = skill_edit %> -
  • -
    - - - - - - - -
    -
  • diff --git a/views/partials/skill_new.erb b/views/partials/skill_new.erb index 1d9104e..810d108 100644 --- a/views/partials/skill_new.erb +++ b/views/partials/skill_new.erb @@ -1,7 +1,5 @@ -
  • -
    - + + - -
    -
  • + + diff --git a/views/resumes/edit.erb b/views/resumes/edit.erb deleted file mode 100644 index d94594c..0000000 --- a/views/resumes/edit.erb +++ /dev/null @@ -1,28 +0,0 @@ -
    - - - <%= partial :'partials/profile_form', :locals => { :user => default_user } %> - -
    -
    -

    Jobs

    -
      - <% @jobs.each do |job| %> -
    • - <%= partial :'partials/job_edit', :locals => { :job => job, :hidden => false } %> -
    • - <% end %> -
    -
    - -
    -

    Skills

    -
      - <%= partial :'partials/skill_edit', collection: @skills %> -
    -
    -
    -
    diff --git a/views/resumes/show.erb b/views/resumes/show.erb index 7daf827..5c7a2d8 100644 --- a/views/resumes/show.erb +++ b/views/resumes/show.erb @@ -1,6 +1,6 @@
    @@ -26,9 +26,15 @@

    Skills

    diff --git a/views/users/edit.erb b/views/users/edit.erb new file mode 100644 index 0000000..9080e3e --- /dev/null +++ b/views/users/edit.erb @@ -0,0 +1,8 @@ +
    + + + <%= partial :'partials/profile_form', :locals => { :user => default_user } %> +