From 13f688496aa41a5a060df4b80b9a0dc4c6d26b9a Mon Sep 17 00:00:00 2001 From: "Daniel J. Summers" Date: Fri, 13 Jan 2023 09:17:35 -0500 Subject: [PATCH] WIP on skill add/remove --- src/JobsJobsJobs/Server/ViewModels.fs | 6 +- src/JobsJobsJobs/Server/Views/Profile.fs | 18 ++++-- src/JobsJobsJobs/Server/wwwroot/script.js | 70 ++++++++++++++++++++--- 3 files changed, 81 insertions(+), 13 deletions(-) diff --git a/src/JobsJobsJobs/Server/ViewModels.fs b/src/JobsJobsJobs/Server/ViewModels.fs index f710a5e..d789dc0 100644 --- a/src/JobsJobsJobs/Server/ViewModels.fs +++ b/src/JobsJobsJobs/Server/ViewModels.fs @@ -60,7 +60,11 @@ module EditProfileViewModel = FullTime = false Biography = "" Experience = None - Skills = [||] + Skills = [| + { Id = "1"; Description = "test 1"; Notes = None } + { Id = "3"; Description = "test 2"; Notes = Some "noted" } + { Id = "4"; Description = "asfasdfa"; Notes = None } + |] } /// Create an instance of this form from the given profile diff --git a/src/JobsJobsJobs/Server/Views/Profile.fs b/src/JobsJobsJobs/Server/Views/Profile.fs index 43ae76f..3f6f7ca 100644 --- a/src/JobsJobsJobs/Server/Views/Profile.fs +++ b/src/JobsJobsJobs/Server/Views/Profile.fs @@ -7,12 +7,13 @@ open Giraffe.ViewEngine open Giraffe.ViewEngine.Htmx open JobsJobsJobs.ViewModels +/// Render the skill edit template and existing skills let skillEdit (skills : SkillForm array) = let mapToInputs (idx : int) (skill : SkillForm) = - div [ _class "row pb-3" ] [ + div [ _id $"skillRow{skill.Id}"; _class "row pb-3" ] [ div [ _class "col-2 col-md-1 align-self-center" ] [ button [ _class "btn btn-sm btn-outline-danger rounded-pill"; _title "Delete" - _onclick $"jjj.removeSkill('{skill.Id}')" ] [ + _onclick $"jjj.profile.removeSkill('{skill.Id}')" ] [ rawText " − " ] ] @@ -23,7 +24,8 @@ let skillEdit (skills : SkillForm array) = _maxlength "200"; _value skill.Description; _required ] label [ _class "jjj-label"; _for $"skillDesc{skill.Id}" ] [ rawText "Skill" ] ] - div [ _class "form-text" ] [ rawText "A skill (language, design technique, process, etc.)" ] + if idx < 1 then + div [ _class "form-text" ] [ rawText "A skill (language, design technique, process, etc.)" ] ] div [ _class "col-12 col-md-5" ] [ div [ _class "form-floating" ] [ @@ -33,7 +35,8 @@ let skillEdit (skills : SkillForm array) = _value (defaultArg skill.Notes "") ] label [ _class "jjj-label"; _for $"skillNotes{skill.Id}" ] [ rawText "Notes" ] ] - div [ _class "form-text" ] [ rawText "A further description of the skill" ] + if idx < 1 then + div [ _class "form-text" ] [ rawText "A further description of the skill" ] ] ] template [ _id "newSkill" ] [ mapToInputs -1 { Id = ""; Description = ""; Notes = None } ] @@ -97,7 +100,7 @@ let edit (m : EditProfileViewModel) continents isNew csrf = hr [] h4 [ _class "pb-2" ] [ rawText "Skills   " - button [ _class "btn btn-sm btn-outline-primary rounded-pill"; _onclick "jjj.addSkill" ] [ + button [ _class "btn btn-sm btn-outline-primary rounded-pill"; _onclick "jjj.profile.addSkill()" ] [ rawText "Add a Skill" ] ] @@ -140,4 +143,9 @@ let edit (m : EditProfileViewModel) continents isNew csrf = rawText "(If you want to delete your profile, or your entire account, " a [ _href "/so-long/options" ] [ rawText "see your deletion options here" ]; rawText ".)" ] + script [] [ + rawText """addEventListener("DOMContentLoaded", function () {""" + rawText $" jjj.profile.nextIndex = {m.Skills.Length} " + rawText "})" + ] ] diff --git a/src/JobsJobsJobs/Server/wwwroot/script.js b/src/JobsJobsJobs/Server/wwwroot/script.js index 9b17741..7b50c10 100644 --- a/src/JobsJobsJobs/Server/wwwroot/script.js +++ b/src/JobsJobsJobs/Server/wwwroot/script.js @@ -41,17 +41,73 @@ this.jjj = { /** * The time zone of the current browser * @type {string} - **/ - timeZone: undefined, + */ + timeZone: undefined, - /** - * Derive the time zone from the current browser - */ - deriveTimeZone () { + /** + * Derive the time zone from the current browser + */ + deriveTimeZone () { try { this.timeZone = (new Intl.DateTimeFormat()).resolvedOptions().timeZone } catch (_) { } - } + }, + + /** + * Script for profile pages + */ + profile: { + + /** + * The next index for a newly-added skill + * @type {number} + */ + nextIndex: 0, + + /** + * Add a skill to the profile form + */ + addSkill() { + const newId = `new${this.nextIndex}` + + /** @type {HTMLTemplateElement} */ + const newSkillTemplate = document.getElementById("newSkill") + /** @type {HTMLDivElement} */ + const newSkill = newSkillTemplate.content.firstElementChild.cloneNode(true) + newSkill.setAttribute("id", `skillRow${newId}`) + + const cols = newSkill.children + // Button column + cols[0].querySelector("button").setAttribute("onclick", `jjj.profile.removeSkill('${newId}')`) + // Skill column + const skillField = cols[1].querySelector("input") + skillField.setAttribute("id", `skillDesc${newId}`) + skillField.setAttribute("name", `Skills[${this.nextIndex}].Description`) + cols[1].querySelector("label").setAttribute("for", `skillDesc${newId}`) + if (this.nextIndex > 0) cols[1].querySelector("div.form-text").remove() + // Notes column + const notesField = cols[2].querySelector("input") + notesField.setAttribute("id", `skillNotes${newId}`) + notesField.setAttribute("name", `Skills[${this.nextIndex}].Notes`) + cols[2].querySelector("label").setAttribute("for", `skillNotes${newId}`) + if (this.nextIndex > 0) cols[2].querySelector("div.form-text").remove() + + // Add the row + const skills = document.querySelectorAll("div[id^=skillRow]") + const sibling = skills.length > 0 ? skills[skills.length - 1] : newSkillTemplate + sibling.insertAdjacentElement('afterend', newSkill) + + this.nextIndex++ + }, + + /** + * Remove a skill row from the profile form + * @param {string} id The ID of the skill row to remove + */ + removeSkill(id) { + document.getElementById(`skillRow${id}`).remove() + } + } } htmx.on("htmx:configRequest", function (evt) {