I have this Vue view to display jobs. There are 3 tabs showing jobs:
My Jobs
Jobs Applied
All jobs
When I click on the apply button on all jobs then the Apply to Job window shows and I can enter content. If however I am viewing a job and I click on Apply then I get an error in the console and the apply to job window doesn’t show. The error in the console is this:
Uncaught (in promise) DOMException: String contains an invalid character
How do I even begin to find this error?
Here is the code for the entire page:
<template>
<div class="page" :class="{ wide: store.wide }" id="jobsPage">
<div class="pageWrap" id="listingsWrap">
<div class="pageHeader flex alignC fixed top55" :class="{ wide: store.wide }">
<div class="col col33 pageTitle flex alignC">
<h1 class="uc mr5">Jobs</h1>
<div class="infoButton">
<div id="infoBtn" @click="showInfo"><h4>?</h4></div>
</div>
<div class="infoContainer">
<div class="infoBlock absolute" v-if="info">
<h3 class="infoTitle">Perfect for Recruiters | Perfect for Job Seekers</h3>
<p>
We have spent a large amount of time making sure it's as easy as possible for recruiters to post jobs
and for job seekers to apply to jobs. Recuiters with an Elite Membership get access to our API and they
are able to easily post jobs in bulk. Job seekers can easily apply with their profiles and they can even
generate a PDF document that is their CV.
</p>
</div>
</div>
</div>
<div class="col col33 pageTitle">
<h2 class="addTitle uc center greenC">{{ title }}</h2>
</div>
<div class="col col33 listingAdd right">
<div class="flex alignC justE">
<button v-if="!adding" :class="{ mr5: viewing }" :disabled="disabled" @click="addJob">Add</button>
<button v-if="viewing" class="delBtn" :class="{ mr5: viewing }" @click="editJob(jobToView.id)">Edit</button>
<button class="cancelBtn" v-if="adding || viewing" @click="closeAdd">Cancel</button>
</div>
</div>
</div>
<div class="pageContent">
<div class="jobsWrap p0-10" v-if="!adding && !viewing">
<div class="jobTabs flex mb10">
<div
class="col col33 tab blueBG white p10 center br5 pointer"
@click="switchTab(1)"
:class="{ active: tab == 1 }"
>
<h4 class="fs20">Jobs You Added</h4>
</div>
<div
class="col col33 tab greenBG white p10 center br5 pointer"
@click="switchTab(2)"
:class="{ active: tab == 2 }"
>
<h4 class="fs20">Jobs You Applied To</h4>
</div>
<div
class="col col33 tab lightblueBG greenC p10 center br5 pointer"
@click="switchTab(3)"
:class="{ active: tab == 3 }"
>
<h4 class="fs20">All Jobs</h4>
</div>
</div>
<div class="sloaderWrap" v-if="!ready">
<div class="sloader"></div>
</div>
<div class="jobContentWrap" v-if="ready && !viewing">
<div class="jobTabContent" v-if="tab == 1">
<div class="postedJobs flex" v-if="postedJobs.length > 0">
<div class="col25 job p10" v-for="(job, index) in postedJobs" :key="job.id">
<div class="jobWrap p10">
<div class="jobImage">
<img
:src="job.image"
class="jobImg pointer"
:alt="job.title + ''s image'"
@click="viewJob(job.id)"
/>
</div>
<div class="jobTitle">
<h3 class="center fs20 greenC">{{ truncateText(job.title, 50) }}</h3>
</div>
<div class="jobEditBtns flex around p20-0">
<button class="smallBtn p0-10" @click="viewJob(job.id)">View</button>
<button class="smallBtn saveBtn p0-15" @click="editJob(job.id)">Edit</button>
<button class="smallBtn delBtn p0-5" @click="deleteJob(job.id)">Delete</button>
<button class="smallBtn" @click="applyJob(job.id)" v-if="job.user != account.id">Apply</button>
</div>
</div>
</div>
</div>
<div class="noPostedJobs" v-else>
<h2 class="noTitle center">You have not added any jobs yet. <span @click="addJob">Add One</span></h2>
</div>
</div>
<div class="jobTabContent pp2-0" v-if="tab == 2">
<div class="appliedJobs" v-if="appliedJobs.length > 0"></div>
<div class="noAppliedJobs" v-else>
<h2 class="noTitle center">You have not applied to any jobs</h2>
</div>
</div>
<div class="jobTabContent pp2-0" v-if="tab == 3">
<div class="allJobs" v-if="filteredJobs.length > 0">
<div class="jobsListWrap flex">
<div class="col col30 jobFilters pr50">
<h3 class="mb20">Filter Jobs</h3>
<div class="sectorFilter mb25">
<div class="filterHeader flex between alignE mb15 pointer" @click="slideBlock(1)">
<h4 class="label m0">Sectors</h4>
<div class="arrow pointer" :class="{ down: showFilters.includes(1) }">
<svg
width="10"
height="10"
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
fill="#008db9"
>
<polygon points="0,0 10,0 5,10" />
</svg>
</div>
</div>
<div class="filtersWrap" :class="{ expanded: openBlock == 1 }">
<select name="" class="w100 mr10" @change="getCities" v-if="showFilters.includes(1)">
<option value="" selected disabled>Sectors</option>
<option :value="sector.id" v-for="sector in sectors" :key="sector.id">
{{ province.name }}
</option>
</select>
</div>
</div>
<div class="functionFilter mb25">
<div class="filterHeader flex between alignE mb15 pointer" @click="slideBlock(2)">
<h4 class="label m0">Functions</h4>
<div class="arrow pointer" :class="{ down: showFilters.includes(2) }">
<svg
width="10"
height="10"
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
fill="#008db9"
>
<polygon points="0,0 10,0 5,10" />
</svg>
</div>
</div>
<div class="filtersWrap" :class="{ expanded: openBlock == 2 }">
<select name="" class="w100 mr10" @change="getCities" v-if="showFilters.includes(2)">
<option value="" selected disabled>Functions</option>
<option :value="province.id" v-for="province in provinces" :key="province.id">
{{ province.name }}
</option>
</select>
</div>
</div>
<div class="environmentFilter mb25"></div>
<div class="locationFilter mb25">
<div class="filterHeader flex between alignE mb15 pointer" @click="slideBlock(3)">
<h4 class="label m0">Locations</h4>
<div class="arrow pointer" :class="{ down: showFilters.includes(3) }">
<svg
width="10"
height="10"
viewBox="0 0 10 10"
xmlns="http://www.w3.org/2000/svg"
fill="#008db9"
>
<polygon points="0,0 10,0 5,10" />
</svg>
</div>
</div>
<div class="filtersWrap" :class="{ expanded: openBlock == 3 }">
<div class="selectWrap flex alignC mb10" v-if="showFilters.includes(3)">
<select name="" class="w100 mr10" @change="getCities">
<option value="" selected disabled>Province</option>
<option :value="province.id" v-for="province in provinces" :key="province.id">
{{ province.name }}
</option>
</select>
<button type="button" class="minBtn">Go</button>
</div>
<div class="selectWrap flex alignC mb10" v-if="showFilters.includes(3)" @change="getAreas">
<select name="" class="w100 mr10">
<option value="" selected disabled>City</option>
<option :value="city.id" v-for="city in cities" :key="city.id">
{{ city.name }}
</option>
</select>
<button type="button" class="minBtn">Go</button>
</div>
<div class="selectWrap flex mb10" v-if="showFilters.includes(3)">
<select name="" class="w100 mr10">
<option value="" selected disabled>Area</option>
<option :value="area.id" v-for="area in areas" :key="area.id">
{{ area.name }}
</option>
</select>
<button type="button" class="minBtn">Go</button>
</div>
</div>
</div>
<div class="eduFilter"></div>
</div>
<div class="col col70 allJobsList">
<div class="listJob mb10 flex" v-for="job in filteredJobs" :key="job.id">
<div class="col col30 jobLeft pr50">
<img
:src="job.image"
class="jobListImg pointer"
:alt="job.title + ''s image'"
@click="viewJob(job.id)"
/>
</div>
<div class="col col70 jobRight">
<h2 class="jobTitle fs20 link fw700 pointer mb10" @click="viewJob(job.id)">{{ job.title }}</h2>
<p class="mb10">
{{
job.description.length > 150 ? job.description.substring(0, 150) + "..." : job.description
}}
</p>
<h4 class="jobLabel darkC mb10">
<span class="blueC mr10">Sector:</span> {{ job.sector.name }}
</h4>
<h4 class="jobLabel darkC mb10">
<span class="blueC mr10">Function:</span> {{ job.function.name }}
</h4>
<div class="applyBtn">
<button class="smallBtn" @click="applyToJob(job.id)">Apply</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="noAppliedJobs" v-else>
<h2 class="noJobsTitle center">There are no jobs currently</h2>
</div>
</div>
</div>
</div>
<div class="viewJob w50 auto p40-0" v-if="viewing">
<div class="jobTitle">
<h2 class="fs26 orangeC center fw700 mb15">{{ jobToView.title }}</h2>
</div>
<div class="datePosted flex justC alignC mb15">
<h4 class="label m0 mr10">Posted:</h4>
{{ convertDateTime(jobToView.created_at) }}
</div>
<div class="jobImage mb20 center">
<img :src="jobToView.image" class="h300 contain jobFullImg" :alt="jobToView + ''s image'" />
</div>
<div class="jobLocation mb15">
<h4 class="label">Job Location</h4>
{{ jobToView.area.city.province.name }} // {{ jobToView.area.city.name }} / {{ jobToView.area.name }}
</div>
<div class="jobDemographics mb15">
<h4 class="label">Demographics</h4>
<p class="cap">{{ jobToView.ethnicity }}</p>
</div>
<div class="workEnvironment mb15">
<h4 class="label">Equity</h4>
<p class="cap">{{ jobToView.work_environment }}</p>
</div>
<div class="expRequired mb15">
<h4 class="label">Experience Required</h4>
<p class="cap">{{ jobToView.experience_required }}</p>
</div>
<div class="eduRequired mb15">
<h4 class="label">Education Required</h4>
<p class="cap">{{ jobToView.education_required }}</p>
</div>
<div class="workSchedule mb15">
<h4 class="label">Work Schedule</h4>
<p class="cap">{{ jobToView.work_schedule }}</p>
</div>
<div class="jobDescription mb15">
<h4 class="label">Job Description</h4>
<p class="cap">{{ jobToView.description }}</p>
</div>
<div class="applyThisJob">
<h4 class="fs18 mb10">Please Note:</h4>
<p class="mb15">
When you apply you for a job you might be asked to complete an exam to test your knowledge. You must
finish the exam and cannot come back to finish it later, so we suggest making time to apply and to
complete the exam thanks.
</p>
<div class="jobExams mb15">
<h4 class="fs20 greenC mb10">Exams</h4>
<p class="mb15">Exams to complete {{ jobToView.exams.length }}</p>
<div class="exams" v-if="applyingToJob">
<div class="exam mb10" v-for="exam in jobToView.exams">
<h4 class="mb5">Duration</h4>
<p class="mb10">{{ exam.length }} {{ exam.time_format }}</p>
<h4 class="mb5">Questions</h4>
<p class="mb10">{{ exam.questions.length }} Questions</p>
</div>
<button type="button" class="smallBtn" @click="" applyExam(exam.id)>Start Exam</button>
</div>
</div>
<div class="buttonsWrap flex justC">
{{ jobToView }}
<button class="mr5 saveBtn" @click="applyToJob(jobToView.id)">Apply</button>
<button class="ml5 cancelBtn" @click="viewing = false">Cancel</button>
</div>
</div>
</div>
</div>
</div>
<!-- COMPONENTS -->
<JobApplication v-if="applyingToJob" :job="jobToApplyTo" @save="addApplication" @cancel="stopApplying" />
<AddJob v-if="adding" @closeform="closeAdd" :action="action" :job="jobToEdit" @save="saveJob" @cancel="cancelAdd" />
</div>
</template>
<script setup>
// page title
document.title = "Jobs in South Africa - Sesha";
// imports
import { ref, onBeforeMount } from "vue";
import AddJob from "@/components/AddJob.vue";
import JobApplication from "@/components/JobApplication.vue";
import { useSeshaStore } from "@/stores/sesha";
import axios from "axios";
// store
const store = useSeshaStore();
// account
const account = JSON.parse(localStorage.getItem("account"));
// refs
// state
const info = ref(false);
const accepted = ref(false);
const adding = ref(false);
const viewing = ref(false);
const ready = ref(false);
const disabled = ref(false);
const doingExam = ref(false);
const applyingToJob = ref(false);
// data
const title = ref("");
const action = ref("");
const jobToEdit = ref({});
const jobToView = ref({
area: {
id: 0,
city: {
id: 0,
name: "",
province: {
id: 0,
name: "",
},
},
},
});
const examToComplete = ref({});
const tab = ref(1);
const appliedJobs = ref([]);
const postedJobs = ref([]);
const allJobs = ref([]);
const filteredJobs = ref([]);
const showFilters = ref([]);
const openBlock = ref(0);
const jobToApplyTo = ref({});
// functions
const showMessage = (message) => {
store.messages.push(message);
setTimeout(() => (store.messages = []), 3000);
};
const showInfo = () => {
if (info.value) {
info.value = false;
} else {
info.value = true;
setTimeout(() => {
document.addEventListener("click", closeInfo);
}, 200);
}
};
const closeInfo = (event) => {
let button = document.getElementById("infoBtn");
if (!button.contains(event.target)) {
info.value = false;
document.removeEventListener("click", closeInfo);
}
};
const switchTab = async (num) => {
tab.value = num;
if (num == 1) {
if (account.membership == "premium" || account.membership == "vip" || account.membership == "professional") {
title.value = "My Jobs";
} else {
title.value = "My Job";
}
} else if (num == 2) {
title.value = "Jobs Applied To";
} else {
title.value = "All Jobs";
try {
const response = await axios.get("/api/v1/jobs/all/");
if (response.status == 200) {
allJobs.value = response.data;
filteredJobs.value = response.data;
}
} catch {}
}
};
const convertDateTime = (datetimeString) => {
const dateTime = new Date(datetimeString);
const monthNames = [
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
];
const formattedDate = `${dateTime.getDate()} ${monthNames[dateTime.getMonth()]} ${dateTime.getFullYear()}`;
const hours = dateTime.getHours().toString().padStart(2, "0");
const minutes = dateTime.getMinutes().toString().padStart(2, "0");
const formattedTime = `${hours}:${minutes}`;
return `${formattedDate} ${formattedTime}`;
};
const slideBlock = (num) => {
const index = showFilters.value.indexOf(num);
if (index > -1) {
showFilters.value.splice(index, 1);
} else {
showFilters.value.push(num);
}
};
const getUserJobs = async () => {
const response = await axios.get(`/api/v1/jobs/user/${account.id}/`);
if (response.status == 200) {
postedJobs.value = response.data;
ready.value = true;
if (postedJobs.value.length > 0) {
tab.value = 1;
}
}
};
const addJob = () => {
const limits = {
basic: store.basicJobs,
premium: store.premiumJobs,
professional: store.profJobs,
vip: store.vipJobs,
};
const limit = limits[account.membership];
if (postedJobs.value.length >= limit) {
const jobText = limit === 1 ? "job" : "jobs";
showMessage(`Your membership limits you to ${limit} ${jobText}. Upgrade to post more jobs`);
disabled.value = true;
} else {
adding.value = true;
title.value = "Adding A Job";
action.value = "add";
}
};
const cancelAdd = () => {
adding.value = false;
title.value = "My Jobs";
};
const saveJob = (data) => {
if (action.value === "edit") {
const jobIndex = postedJobs.value.findIndex((job) => job.id === data.id);
if (jobIndex !== -1) {
postedJobs.value[jobIndex] = data;
}
adding.value = false;
} else {
postedJobs.value.push(data);
}
};
const editJob = (id) => {
jobToEdit.value = postedJobs.value.find((e) => e.id == id);
adding.value = true;
viewing.value = false;
title.value = "Editing A Job";
action.value = "edit";
};
const viewJob = (id) => {
jobToView.value = postedJobs.value.find((e) => e.id == id);
viewing.value = true;
adding.value = false;
title.value = "Viewing A Job";
};
const applyToJob = (id) => {
applyingToJob.value = true;
jobToApplyTo.value = allJobs.value.filter((e) => e.id == id);
store.overlay = true;
};
const addApplication = (data) => {
const jobIndex = allJobs.value.findIndex((job) => job.id === data.job);
if (jobIndex !== -1) {
if (!allJobs.value[jobIndex].applications) {
allJobs.value[jobIndex].applications = [];
}
allJobs.value[jobIndex].applications.push(data);
showMessage("Application was successful, Good Luck");
} else {
showMessage(`Job with ID ${data.job} not found`);
}
applyingToJob.value = false;
store.overlay = false;
};
const stopApplying = () => {
applyingToJob.value = false;
store.overlay = false;
};
const closeAdd = () => {
title.value = "Jobs";
adding.value = false;
viewing.value = false;
};
// utilities
const truncateText = (text, length) => {
return text.length > length ? text.substring(0, length) + "..." : text;
};
// hooks
onBeforeMount(() => {
if (account.membership == "premium" || account.membership == "vip" || account.membership == "professional") {
title.value = "My Jobs";
} else {
title.value = "My Job";
}
getUserJobs();
});
</script>
Where do I start to find this error and what is considered an illegal character for a string?
The error in the concole links to this error in the compressed Vue code:
var xlinkNS = "http://www.w3.org/1999/xlink";
function patchAttr(el, key, value, isSVG, instance) {
if (isSVG && key.startsWith("xlink:")) {
if (value == null) {
el.removeAttributeNS(xlinkNS, key.slice(6, key.length));
} else {
el.setAttributeNS(xlinkNS, key, value);
}
} else {
const isBoolean2 = isSpecialBooleanAttr(key);
if (value == null || isBoolean2 && !includeBooleanAttr(value)) {
el.removeAttribute(key);
} else {
el.setAttribute(key, isBoolean2 ? "" : value);
}
}
}
So from the looks of it it seems it’s gotta do with a SVG but I don’t see any invalid characters.
Any help is appreciated thanks