Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue3 form components #639

Merged
merged 38 commits into from
May 4, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4d9e7af
feat(WIP): create form components
sgfost Mar 31, 2023
636b53d
feat(WIP): add datepicker form field component
sgfost Mar 31, 2023
3f38ed3
refactor: be more dry about form field setup
sgfost Mar 31, 2023
c9a4847
feat(WIP): add tagger form component
sgfost Apr 3, 2023
2982e39
fix: correct query url, value types for form tagger
sgfost Apr 3, 2023
806e714
chore: move form components to correct dir
sgfost Apr 4, 2023
d02d6d0
feat(experimental): create 'form builder' API
sgfost Apr 5, 2023
8a5b536
refactor: remove FormContext, experimental form API
sgfost Apr 5, 2023
477b925
feat: replace codebase search component
sgfost Apr 6, 2023
96b03ec
feat: port over requests apis
sgfost Apr 6, 2023
5604488
chore: format with prettier
sgfost Apr 6, 2023
dffff15
fix: turn off eslint no-unused-vars
sgfost Apr 7, 2023
4a81ed0
refactor: add type param to generic useField
sgfost Apr 7, 2023
223d068
test: remove helloworld test component, add search component test
sgfost Apr 7, 2023
46c7d63
docs: add documentation for composables (forms/api)
sgfost Apr 12, 2023
d25f15a
style: add back eslint no-unused-vars
sgfost Apr 12, 2023
f773359
feat: give 3rd party form fields bootstrap/comses styling
sgfost Apr 12, 2023
6f0b082
feat: add clear button to multiselect tagger
sgfost Apr 12, 2023
72a02ad
refactor: clean up some form components
sgfost Apr 12, 2023
58173dd
refactor: renaming, use yup.InferType for form typing
sgfost Apr 13, 2023
3fe1b54
feat: add textarea form component
sgfost Apr 13, 2023
0940642
feat: automatically convert ISO dates in useAxios
sgfost Apr 13, 2023
8c936c0
feat: add form field loading placeholder
sgfost Apr 14, 2023
6342812
refactor: refine datepicker component
sgfost Apr 14, 2023
b0d6320
feat(WIP): add event edit form
sgfost Apr 14, 2023
f08f738
refactor: update form components with placeholder inject, required name
sgfost Apr 14, 2023
e0fe7ff
feat: add success/error handling to useAxios/events form
sgfost Apr 17, 2023
894f22c
feat: add better error handling
sgfost Apr 18, 2023
bda2f20
feat: add summarize button to event edit form
sgfost Apr 19, 2023
fc5dcc2
feat: add placeholder markdown component
sgfost Apr 19, 2023
3a5e826
feat: add text item (list entry) component
sgfost Apr 19, 2023
9eb8a7d
feat: add profile edit form
sgfost Apr 19, 2023
73d9e35
feat: add ROR lookup typeahead component
sgfost Apr 20, 2023
780f3a2
feat: add org items/multiple affiliations component
sgfost Apr 20, 2023
db8c099
refactor: form/field component renaming
sgfost May 4, 2023
2860522
fix: revert prop type sharing
sgfost May 4, 2023
338f8df
refactor: clean up axios composable
sgfost May 4, 2023
937e6e6
test: add unit tests for useAxios + util functions
sgfost May 4, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions django/core/jinja2/core/events/edit.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
{% block introduction %}<h1>Submit an Event</h1>{% endblock %}

{% block content %}
<div id="app"></div>
<div id="event-form"></div>
{% endblock %}

{% block js %}
{{ render_bundle('events', attrs='defer') }}
{% endblock %}
{{ vite_asset('apps/event_edit.ts') }}
{% endblock %}
11 changes: 7 additions & 4 deletions django/core/jinja2/core/member_profiles/edit.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
{% block introduction %}<h1>Edit Profile</h1>{% endblock %}

{% block content %}
<div class="modal" tabindex="-1" role="dialog" id="rightsAndResponsibilities">
<div class="modal" tabindex="-1" role="dialog" id="membership-modal">
<div class="modal-dialog modal-xl modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">CoMSES Net Membership</h5>
Expand All @@ -15,9 +16,11 @@
</div>
</div>
</div>
<div id="app" data-user-pk="{{ object.user.id }}" data-connections-url="{{ url('socialaccount_connections') }}"></div>
</div>
<div id="profile-form" data-user-id="{{ object.user.id }}"
data-connections-url="{{ url('socialaccount_connections') }}"></div>
{% endblock %}

{% block js %}
{{ render_bundle('profiles', attrs='defer') }}
{% endblock %}
{{ vite_asset('apps/profile_edit.ts') }}
{% endblock %}
6 changes: 2 additions & 4 deletions django/library/jinja2/library/codebases/list.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,6 @@
{% include "library/includes/archive_model_help.jinja" %}
{{ pagination_block }}
<div id='sortby'></div>
{# django vite test #}
<div id="app"></div>
{{ vite_asset('apps/helloworld.ts') }}
{% endblock %}

{% block content %}
Expand All @@ -36,9 +33,10 @@
{% endblock %}

{% block sidebar %}
<div id="sidebar"></div>
<div id="search"></div>
{% endblock %}

{% block js %}
{{ render_bundle('codebase_list', attrs='defer') }}
{{ vite_asset('apps/codebase_list.ts') }}
{% endblock %}
15 changes: 0 additions & 15 deletions e2e/cypress/e2e/helloworld.spec.ts

This file was deleted.

6 changes: 6 additions & 0 deletions frontend-vue3/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ module.exports = {
'@vue/eslint-config-typescript',
'@vue/eslint-config-prettier/skip-formatting'
],
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
{ 'varsIgnorePattern': "props" }
],
},
parserOptions: {
ecmaVersion: 'latest'
}
Expand Down
15 changes: 13 additions & 2 deletions frontend-vue3/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,28 @@
"dependencies": {
"@fortawesome/fontawesome-free": "^5.12.0",
"@popperjs/core": "^2.11.6",
"bootstrap": "5.0.2",
"@vorms/core": "^1.1.0",
"@vorms/resolvers": "^1.1.0",
"@vuepic/vue-datepicker": "^4.2.3",
"@vueuse/core": "^9.13.0",
"axios": "^1.3.5",
"bootstrap": "5.2.3",
"pinia": "^2.0.32",
"query-string": "^8.1.0",
"sass": "^1.58.3",
"sortablejs": "^1.15.0",
"sortablejs-vue3": "^1.2.9",
"vue": "^3.2.47",
"vue-multiselect": "^3.0.0-beta.1",
"vue-router": "^4.1.6"
"vue-router": "^4.1.6",
"yup": "^1.0.2"
},
"devDependencies": {
"@rushstack/eslint-patch": "^1.2.0",
"@types/jsdom": "^21.1.0",
"@types/node": "^18.14.2",
"@types/sortablejs": "^1.15.1",
"@types/yup": "^0.32.0",
"@vitejs/plugin-vue": "^4.0.0",
"@vue/eslint-config-prettier": "^7.1.0",
"@vue/eslint-config-typescript": "^11.0.2",
Expand Down
6 changes: 6 additions & 0 deletions frontend-vue3/src/apps/codebase_list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import "vite/modulepreload-polyfill";

import { createApp } from "vue";
import CodebaseListSidebar from "@/components/CodebaseListSidebar.vue";

createApp(CodebaseListSidebar).mount("#search");
8 changes: 8 additions & 0 deletions frontend-vue3/src/apps/event_edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import "vite/modulepreload-polyfill";

import { createApp } from "vue";
import EventEditForm from "@/components/EventEditForm.vue";
import { extractIdFromPath } from "./util";

const props = { eventId: extractIdFromPath(window.location.pathname, "events") };
createApp(EventEditForm, props).mount("#event-form");
6 changes: 6 additions & 0 deletions frontend-vue3/src/apps/formdemo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import "vite/modulepreload-polyfill";

import { createApp } from "vue";
import FormDemo from "@/components/FormDemo.vue";

createApp(FormDemo).mount("#app");
4 changes: 2 additions & 2 deletions frontend-vue3/src/apps/helloworld.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import "vite/modulepreload-polyfill";

import { createApp } from "vue";
import HelloWorld from "@/components/HelloWorld.vue";
import FormDemo from "@/components/FormDemo.vue";

createApp(HelloWorld).mount("#app");
createApp(FormDemo).mount("#app");
8 changes: 8 additions & 0 deletions frontend-vue3/src/apps/profile_edit.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import "vite/modulepreload-polyfill";

import { createApp } from "vue";
import ProfileEditForm from "@/components/ProfileEditForm.vue";
import { extractDataParams } from "./util";

const props = extractDataParams("profile-form", ["userId", "connectionsUrl"]);
createApp(ProfileEditForm, props).mount("#profile-form");
49 changes: 49 additions & 0 deletions frontend-vue3/src/apps/util.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
type ExtractedAttributes = {
[key: string]: string | object;
};

export function extractDataParams(elementId: string, names: string[]): ExtractedAttributes {
/**
* Extract parameters from data attributes
*
* @param elementId The id of the element
* @param names The names of the attributes to extract and return (camelCase)
*/
const el = document.getElementById(elementId);
if (el) {
const attributes = names.reduce<ExtractedAttributes>((acc, name) => {
const hyphenatedName = name.replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
const attrValue = el.getAttribute(`data-${hyphenatedName}`);

if (attrValue) {
try {
const parsedValue = JSON.parse(attrValue);
acc[name] = parsedValue;
} catch (error) {
acc[name] = attrValue;
}
}
return acc;
}, {});
return attributes;
} else {
return {};
}
}

export function extractIdFromPath(
path: string,
prefix: string,
suffix: string = "edit"
): number | undefined {
/**
* Extract resource id from path
*
* @param path The path to extract the id (usually window.location.pathname)
* @param prefix The prefix of the path (e.g. "codebases", "events")
* @param suffix="edit" The suffix of the path (e.g. "edit")
*/
const re = new RegExp(`/${prefix}/([0-9]+)/${suffix}/`);
const match = path.match(re);
return match ? parseInt(match[1]) : undefined;
}
74 changes: 74 additions & 0 deletions frontend-vue3/src/components/CodebaseListSidebar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<template>
<ListSidebar
create-label="Archive a model"
create-url="/codebases/add/"
search-label="Search"
:search-url="query"
>
<template #form>
<form @submit="handleSubmit">
<TextField class="mb-3" name="keywords" label="Keywords" @keyup.enter="search" />
<DatepickerField class="mb-3" name="startDate" label="Published After" />
<DatepickerField class="mb-3" name="endDate" label="Published Before" />
<TaggerField class="mb-3" name="tags" label="Tags" type="Codebase" />
<SelectField
class="mb-3"
name="peerReviewStatus"
label="Peer Review Status"
:options="peerReviewOptions"
/>
</form>
</template>
</ListSidebar>
</template>

<script setup lang="ts">
import * as yup from "yup";
import { computed } from "vue";
import ListSidebar from "@/components/ListSidebar.vue";
import TextField from "@/components/form/TextField.vue";
import SelectField from "@/components/form/SelectField.vue";
import DatepickerField from "@/components/form/DatepickerField.vue";
import TaggerField from "@/components/form/TaggerField.vue";
import { useForm } from "@/composables/form";
import { useCodebaseAPI } from "@/composables/api/codebase";

const peerReviewOptions = [
{ value: "reviewed", label: "Reviewed" },
{ value: "not_reviewed", label: "Not Reviewed" },
{ value: "", label: "Any" },
];

const schema = yup.object({
keywords: yup.string(),
startDate: yup.date(),
endDate: yup.date(),
tags: yup.array().of(yup.object().shape({ name: yup.string().required() })),
peerReviewStatus: yup.string(),
});
type SearchFields = yup.InferType<typeof schema>;

const { handleSubmit, values } = useForm<SearchFields>({
schema,
initialValues: { peerReviewStatus: "" },
onSubmit: () => {
window.location.href = query.value;
},
});

const { searchUrl } = useCodebaseAPI();

const query = computed(() => {
return searchUrl({
query: values.keywords,
published_after: values.startDate?.toISOString(),
published_before: values.endDate?.toISOString(),
tags: values.tags?.map(tag => tag.name),
peer_review_status: values.peerReviewStatus,
});
});

function search() {
window.location.href = query.value;
}
</script>
Loading