From 20174015995833509d5e47dd0eef29d361401a75 Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 08:14:14 -0500 Subject: [PATCH 1/9] Add link to PR and release download for v1.2.0 --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 19bc05f..8842bbe 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 From b02eac8f6416069eb1b30620bce9f2dbafa5f435 Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 08:27:02 -0500 Subject: [PATCH 2/9] Implement RESTful routing conventions RESTful routes are a way to implement a conventional and standardized design to server routes that work with resources. In this case, the resources we're working with are `jobs` and `skills`, so the routes that affect those resources should follow RESTful conventions. There are two significant changes in this commit: 1. When working with singular resources, add an identifier to the URL (e.g. DELETE /jobs/21 means "delete job resource with id 21) 2. Let the HTTP verb define the route (e.g. PUT /jobs/21 means "edit job resource with id 21, no need for an extraneous /jobs/edit in the route) Check out http://en.wikipedia.org/wiki/Representational_state_transfer#Applied_to_web_services for more information --- linkedout.rb | 17 +++++++---------- public/js/jobs.js | 32 ++++++++++++++++++++++---------- views/partials/job.erb | 3 +-- views/partials/job_edit.erb | 6 ++---- views/partials/skill_edit.erb | 4 +--- 5 files changed, 33 insertions(+), 29 deletions(-) diff --git a/linkedout.rb b/linkedout.rb index 83027ab..31552be 100644 --- a/linkedout.rb +++ b/linkedout.rb @@ -32,7 +32,7 @@ def default_user erb :'resumes/edit' end -put "/users/edit" do +put "/users" do user_attrs = params[:user] default_user.update(user_attrs) @@ -58,11 +58,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 +72,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 @@ -102,11 +100,10 @@ def default_user end end -put "/skills/edit" do +put "/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) 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/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 %> - -
  • + + diff --git a/views/resumes/show.erb b/views/resumes/show.erb index 7daf827..d66ea20 100644 --- a/views/resumes/show.erb +++ b/views/resumes/show.erb @@ -26,9 +26,15 @@

    Skills

      - <%= partial :'partials/skill', collection: @skills %> + <% @skills.each do |skill| %> +
    • + <%= partial :'partials/skill', :locals => { :skill => skill } %> +
    • + <% end %> - <%= partial :'partials/skill_new' %> +
    • + <%= partial :'partials/skill_new' %> +
    From 16c0fc18e819b2d6642b8280ef8d58a18b660a68 Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 08:44:56 -0500 Subject: [PATCH 4/9] Asynchronously delete skills MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a delete button to skills on the résumé show page so that user can remove skills as needed --- linkedout.rb | 14 ++++++++++++++ public/js/skills.js | 38 ++++++++++++++++++++++++++++++++++++++ views/partials/skill.erb | 4 ++++ 3 files changed, 56 insertions(+) diff --git a/linkedout.rb b/linkedout.rb index 1f0107e..10aa90a 100644 --- a/linkedout.rb +++ b/linkedout.rb @@ -112,3 +112,17 @@ def default_user redirect "/" end + +delete "/skills/:skill_id" do + skill_id = params[:skill_id] + skill_attrs = params[:skill] + + skill = Skill.get(skill_id) + skill.destroy + + if request.xhr? + skill_id + else + redirect "/" + end +end diff --git a/public/js/skills.js b/public/js/skills.js index e388e05..3ac11be 100644 --- a/public/js/skills.js +++ b/public/js/skills.js @@ -47,8 +47,46 @@ var insertNewSkillIntoDOM = function(newSkillHTML) { $newSkillForm.get(0).reset(); }; +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'); + + 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: actionPath, + type: 'DELETE', + data: deleteSkillFormData + }).done(function() { + log("Received response from DELETE request to " + actionPath); + + 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/skill.erb b/views/partials/skill.erb index 1771007..eb54fb8 100644 --- a/views/partials/skill.erb +++ b/views/partials/skill.erb @@ -1 +1,5 @@ +
    + + +
    <%= skill.name %> From 8ecfe69252d4e19cce54bec3958c774c81ee801d Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 08:50:13 -0500 Subject: [PATCH 5/9] Remove features from /resumes/edit & rename to /users/edit Since jobs can be edited on the main page, and skills can be created and deleted, making them editable on a separate page seems redundant. So instead there will just be a separate edit page for the user profile information. Now the app has two primary pages: - "/" which points to "/resumes/show" - "/users/edit" This commit also removes the "put /skills/:skill_id" route, as it is no longer in use anywhere. --- linkedout.rb | 17 ++--------------- views/partials/skill_edit.erb | 10 ---------- views/resumes/edit.erb | 28 ---------------------------- views/resumes/show.erb | 2 +- views/users/edit.erb | 8 ++++++++ 5 files changed, 11 insertions(+), 54 deletions(-) delete mode 100644 views/partials/skill_edit.erb delete mode 100644 views/resumes/edit.erb create mode 100644 views/users/edit.erb diff --git a/linkedout.rb b/linkedout.rb index 10aa90a..f6aee07 100644 --- a/linkedout.rb +++ b/linkedout.rb @@ -25,11 +25,8 @@ 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" do @@ -103,16 +100,6 @@ def default_user end end -put "/skills/:skill_id" do - skill_id = params[:skill_id] - skill_attrs = params[:skill] - - skill = Skill.get(skill_id) - skill.update(skill_attrs) - - redirect "/" -end - delete "/skills/:skill_id" do skill_id = params[:skill_id] skill_attrs = params[:skill] diff --git a/views/partials/skill_edit.erb b/views/partials/skill_edit.erb deleted file mode 100644 index 456022b..0000000 --- a/views/partials/skill_edit.erb +++ /dev/null @@ -1,10 +0,0 @@ -<% skill = skill_edit %> -
  • -
    - - - - - -
    -
  • 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 d66ea20..5c7a2d8 100644 --- a/views/resumes/show.erb +++ b/views/resumes/show.erb @@ -1,6 +1,6 @@
    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 } %> +
    From 63f226129260932516200737f8cfbdae67591322 Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 08:57:40 -0500 Subject: [PATCH 6/9] Update skills.js to follow patterns in jobs.js Reduce smaller functions, add extra comments. --- public/js/skills.js | 51 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/public/js/skills.js b/public/js/skills.js index 3ac11be..8c705ae 100644 --- a/public/js/skills.js +++ b/public/js/skills.js @@ -1,18 +1,26 @@ 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 - $newSkillForm.submit(function (evt) { + // Bind to the "submit" event of the new skill form using + // event delegation + $('.skills').on('submit', 'form[name="new_skill"]', 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(); // Send a POST request asynchronously to the "/skills" // route on the server, passing the serialized form data @@ -22,29 +30,20 @@ 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) { + // Add the new skill to the list, just before the form's + // parent 'li' element + $newSkillForm.parent('li').before(newSkillHTML); -var getNewSkillForm = function() { - // Select the new skill form using its name attribute - return $('form[name="new_skill"]'); -}; - -var insertNewSkillIntoDOM = function(newSkillHTML) { - var $newSkillForm = getNewSkillForm(); - - // Add the new skill to the list, just before the form's - // parent 'li' element - $newSkillForm.parent('li').before(newSkillHTML); - - // Reset the form so that new skills can be added - $newSkillForm.get(0).reset(); + // Reset the form so that new skills can be added + $newSkillForm.get(0).reset(); + }); + }); }; var deleteSkillOnSubmit = function() { From 4d25eaf5f36e2d8515f937e2a7408c3aa30ccbad Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 09:00:47 -0500 Subject: [PATCH 7/9] Add logging to skills.js --- public/js/skills.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/public/js/skills.js b/public/js/skills.js index 8c705ae..1bb7904 100644 --- a/public/js/skills.js +++ b/public/js/skills.js @@ -2,6 +2,8 @@ var createNewSkillsOnSubmit = function() { // 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"); + // Prevent the submit from sending an HTTP POST request // Instead, we'll handle the request with AJAX evt.preventDefault(); @@ -22,6 +24,7 @@ var createNewSkillsOnSubmit = function() { // standard URL-encoded notation 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. @@ -36,10 +39,14 @@ var createNewSkillsOnSubmit = function() { // this case is a snippet of HTML representing the newly- // created skill. $.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(); }); From 3b3e88e65ba6311d3c46b3331f8e25d94963442d Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 09:06:15 -0500 Subject: [PATCH 8/9] Modify release 1.4.0 => 1.3.0 with diff requirements --- README.md | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 8842bbe..f3da90f 100644 --- a/README.md +++ b/README.md @@ -107,13 +107,17 @@ 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 -- [ ] 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 +- [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 conventions +- [X] Logging in `skills.js` +- [X] Format of HTML and JavaScript more consistent ### v[1.5.0] Display education information From cc385a73f067da88eaf073d4d03a80533e3fe27b Mon Sep 17 00:00:00 2001 From: Tanner Welsh Date: Sun, 2 Nov 2014 09:11:15 -0500 Subject: [PATCH 9/9] Slight change to desc of v1.3.0 --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f3da90f..f3d3e85 100644 --- a/README.md +++ b/README.md @@ -115,10 +115,12 @@ This is the first release with JavaScript and jQuery included. As a reference, h This release also makes improvements to the underlying architecture of the application, including the following changes: -- [X] Use RESTful routing conventions +- [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 +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 - [ ] Résumé show page displays a users education history