Merge branch 'camille/style-new-form-elements' into 'master'
Stylise les éléments de formulaire multi-entrées See merge request klub-dev-ens/annuaire!9
This commit is contained in:
commit
e99becd89c
9 changed files with 605 additions and 63 deletions
|
@ -207,6 +207,50 @@ body {
|
|||
font-family: "Source Code Pro", sans-serif;
|
||||
}
|
||||
|
||||
.content button,
|
||||
.content input[type=button],
|
||||
.content input[type=submit] {
|
||||
padding: 10px;
|
||||
background-color: #724162;
|
||||
border: none;
|
||||
font-size: 1.2rem;
|
||||
color: #FFFFFF;
|
||||
box-shadow: 2px 2px 0 rgba(31, 14, 25, 0.3);
|
||||
transition: 25ms ease-in-out;
|
||||
}
|
||||
|
||||
.content button:hover,
|
||||
.content input[type=button]:hover,
|
||||
.content input[type=submit]:hover {
|
||||
background-color: #4e2d43;
|
||||
}
|
||||
|
||||
#content-edit-profile form .form-entry > .errorlist.nonfield, #content-edit-profile form .form-sub-entry.erroneous {
|
||||
padding: 10px;
|
||||
background-color: #973232;
|
||||
}
|
||||
|
||||
#content-edit-profile form .form-entry > .errorlist.nonfield, #content-edit-profile form .form-sub-entry .errorlist {
|
||||
margin: 0;
|
||||
padding: 0 0 10px 0;
|
||||
list-style: none;
|
||||
}
|
||||
#content-edit-profile form .form-entry > .errorlist.nonfield li::before, #content-edit-profile form .form-sub-entry .errorlist li::before {
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
margin: -2px 0.5ex 0 0;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
#content-edit-profile form .form-entry > .errorlist.nonfield li:not(:last-child), #content-edit-profile form .form-sub-entry .errorlist li:not(:last-child) {
|
||||
margin: 0 0 0.5ex 0;
|
||||
}
|
||||
|
||||
#content-area {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
@ -228,21 +272,6 @@ body {
|
|||
border: none;
|
||||
border-radius: 0;
|
||||
}
|
||||
.content input[type=button],
|
||||
.content input[type=submit] {
|
||||
padding: 10px;
|
||||
background-color: #724162;
|
||||
border: none;
|
||||
font-size: 1.2rem;
|
||||
color: #FFFFFF;
|
||||
box-shadow: 2px 2px 0 rgba(31, 14, 25, 0.3);
|
||||
transition: 25ms ease-in-out;
|
||||
}
|
||||
.content input[type=button]:hover,
|
||||
.content input[type=submit]:hover {
|
||||
background-color: #4e2d43;
|
||||
}
|
||||
|
||||
#content-home form {
|
||||
display: grid;
|
||||
width: 400px;
|
||||
|
@ -326,6 +355,24 @@ body {
|
|||
background-color: #3b1e31;
|
||||
box-shadow: 2px 2px 0 rgba(31, 14, 25, 0.3);
|
||||
}
|
||||
#content-view-profile .infos > *.multi-entry ul.value {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
}
|
||||
#content-view-profile .infos > *.multi-entry ul.value li {
|
||||
padding: 0;
|
||||
}
|
||||
#content-view-profile .infos > *.multi-entry ul.value li .type {
|
||||
display: inline-block;
|
||||
margin: 0 1em 0 0;
|
||||
color: #DFDFDF;
|
||||
font-style: italic;
|
||||
}
|
||||
#content-view-profile .infos > *.multi-entry ul.value li .value {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
#content-view-profile .free-text {
|
||||
grid-area: free-text;
|
||||
padding: 10px;
|
||||
|
@ -363,6 +410,62 @@ body {
|
|||
#content-edit-profile form .form-entry input, #content-edit-profile form .form-entry select {
|
||||
width: 100%;
|
||||
}
|
||||
#content-edit-profile form .form-entry > .errorlist.nonfield {
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: 2fr 3fr auto;
|
||||
grid-template-areas: "errors errors errors" "type-input value-input remove-button";
|
||||
column-gap: 10px;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry.hidden {
|
||||
display: none;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry input:nth-child(1) {
|
||||
grid-area: type-input;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry input:nth-child(2) {
|
||||
grid-area: value-input;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry .remove-button {
|
||||
grid-area: remove-button;
|
||||
min-height: 30px;
|
||||
min-width: 30px;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
|
||||
background-size: 80%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry .remove-button:hover,
|
||||
#content-edit-profile form .form-sub-entry .remove-button:active {
|
||||
background-color: #973232;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry input[type=checkbox] {
|
||||
display: none;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry.erroneous input:nth-child(2) {
|
||||
grid-area: type-input;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry.erroneous input:nth-child(3) {
|
||||
grid-area: value-input;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry.erroneous .remove-button:hover,
|
||||
#content-edit-profile form .form-sub-entry.erroneous .remove-button:active {
|
||||
background-color: #7d2727;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry .errorlist {
|
||||
grid-area: errors;
|
||||
}
|
||||
#content-edit-profile form .form-sub-entry-template {
|
||||
display: none;
|
||||
}
|
||||
#content-edit-profile form .add-sub-entry-button {
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
#content-edit-profile form .form-entry.checkbox > * {
|
||||
display: inline;
|
||||
width: auto;
|
||||
|
|
242
fiches/static/fiches/js/forms.js
Normal file
242
fiches/static/fiches/js/forms.js
Normal file
|
@ -0,0 +1,242 @@
|
|||
"use strict";
|
||||
|
||||
// Sub-entry of a multi-entry form entry
|
||||
// (e.g. one phone number among several phone numbers grouped in a single entry)
|
||||
class SubEntry {
|
||||
constructor(subEntryElement, index, parentFormEntry) {
|
||||
this.parentFormEntry = parentFormEntry;
|
||||
this.index = index;
|
||||
|
||||
this.subEntryElement = subEntryElement;
|
||||
this.removeButtonElement = subEntryElement.querySelector(".remove-button");
|
||||
this.deletionCheckboxElement = subEntryElement.querySelector("input[id$='DELETE']");
|
||||
|
||||
this.addPlaceholderAttributes();
|
||||
this.startHandlingSubEntryRemoveButtonClicks();
|
||||
}
|
||||
|
||||
addPlaceholderAttributes() {
|
||||
// Add the type placeholder to the first input element
|
||||
// and the value placeholder to the second one
|
||||
const inputElements = this.subEntryElement.querySelectorAll("input");
|
||||
inputElements[0].setAttribute("placeholder", this.parentFormEntry.placeholders.type);
|
||||
inputElements[1].setAttribute("placeholder", this.parentFormEntry.placeholders.value);
|
||||
}
|
||||
|
||||
hide() {
|
||||
this.subEntryElement.classList.add("hidden");
|
||||
}
|
||||
|
||||
display() {
|
||||
this.subEntryElement.classList.remove("hidden");
|
||||
}
|
||||
|
||||
markForDeletionAndHide() {
|
||||
// Check Django's deletion checkbox
|
||||
this.deletionCheckboxElement.checked = true;
|
||||
|
||||
this.hide();
|
||||
}
|
||||
|
||||
reset() {
|
||||
// Clear all input fields
|
||||
for (let inputElement of this.subEntryElement.querySelectorAll("input")) {
|
||||
inputElement.value = "";
|
||||
}
|
||||
|
||||
// Uncheck Django's deletion checkbox
|
||||
this.deletionCheckboxElement.checked = false;
|
||||
}
|
||||
|
||||
resetAndDisplay() {
|
||||
this.reset();
|
||||
this.display();
|
||||
}
|
||||
|
||||
reindex(newIndex) {
|
||||
// Replace -<old index>- by -<new index>- in the values of the
|
||||
// id and name attributes of every child of this sub-entry's element
|
||||
const attributesToUpdate = ["id", "name"];
|
||||
|
||||
for (let childElement of this.subEntryElement.childNodes) {
|
||||
for (let attribute of attributesToUpdate) {
|
||||
if (!childElement.hasAttribute(attribute)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const newValue = childElement
|
||||
.getAttribute(attribute)
|
||||
.replace(`-${this.index}-`, `-${newIndex}-`);
|
||||
childElement.setAttribute(attribute, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
this.index = newIndex;
|
||||
}
|
||||
|
||||
startHandlingSubEntryRemoveButtonClicks() {
|
||||
this.removeButtonElement.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
this.parentFormEntry.removeSubEntry(this);
|
||||
});
|
||||
}
|
||||
|
||||
static fromTemplate(templateElement, index, parentFormEntry) {
|
||||
// Clone the template, update its class,
|
||||
// and replace __prefix__ by the index of the new sub-entry
|
||||
const clonedTemplateElement = templateElement.cloneNode(true);
|
||||
clonedTemplateElement.innerHTML = clonedTemplateElement.innerHTML
|
||||
.replace(/__prefix__/g, index.toString());
|
||||
clonedTemplateElement.className = "form-sub-entry";
|
||||
|
||||
return new SubEntry(clonedTemplateElement, index, parentFormEntry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Form entry which can contain 0+ sub-entries
|
||||
// (e.g. a phone number entry which can contain several numbers)
|
||||
class MultiEntryFormEntry {
|
||||
constructor(formEntryElement) {
|
||||
this.formEntryElement = formEntryElement;
|
||||
this.subEntryTemplateElement = formEntryElement.querySelector(".form-sub-entry-template");
|
||||
this.newSubEntryButtonElement = formEntryElement.querySelector(".add-sub-entry-button");
|
||||
this.totalFormsElement = [...formEntryElement.querySelectorAll("input")]
|
||||
.find(element => element.name && element.name.endsWith("TOTAL_FORMS"));
|
||||
this.maxNumFormsElement = [...formEntryElement.querySelectorAll("input")]
|
||||
.find(element => element.name && element.name.endsWith("MAX_NUM_FORMS"));
|
||||
|
||||
this.placeholders = {
|
||||
type: this.formEntryElement.getAttribute("data-type-placeholder"),
|
||||
value: this.formEntryElement.getAttribute("data-value-placeholder")
|
||||
};
|
||||
|
||||
this.maxNbSubEntries = parseInt(this.maxNumFormsElement.value);
|
||||
this.initialNbSubEntries = 0;
|
||||
this.subEntries = [];
|
||||
this.createInitialSubEntries();
|
||||
|
||||
this.startHandlingNewSubEntryButtonClicks();
|
||||
}
|
||||
|
||||
get nbSubEntries() {
|
||||
return this.subEntries
|
||||
.filter(subEntry =>
|
||||
!subEntry.subEntryElement.classList.contains("hidden")
|
||||
)
|
||||
.length;
|
||||
}
|
||||
|
||||
get hasHiddenSubEntries() {
|
||||
return this.subEntries
|
||||
.findIndex(subEntry =>
|
||||
subEntry.subEntryElement.classList.contains("hidden")
|
||||
) >= 0;
|
||||
}
|
||||
|
||||
createInitialSubEntries() {
|
||||
const subEntryElements = this.formEntryElement.querySelectorAll(".form-sub-entry");
|
||||
this.initialNbSubEntries = subEntryElements.length;
|
||||
|
||||
// This loop assumes sub-entries elements are
|
||||
// ordered with no gap according to indices which start at 0
|
||||
for (let [index, element] of [...subEntryElements].entries()) {
|
||||
this.subEntries.push(
|
||||
new SubEntry(element, index, this)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
createNewSubEntry() {
|
||||
if (this.nbSubEntries === this.maxNbSubEntries) {
|
||||
console.log(`The max. number of sub-entries has been reached (${this.maxNbSubEntries}).`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If there are hidden sub-entries,
|
||||
// it means one of the initial sub-entry elements should be reset and displayed
|
||||
if (this.hasHiddenSubEntries) {
|
||||
// Reset and display the first hidden sub-entry
|
||||
const existingSubEntry = this.subEntries.find(
|
||||
subEntry => subEntry.subEntryElement.classList.contains("hidden")
|
||||
);
|
||||
|
||||
existingSubEntry.resetAndDisplay();
|
||||
}
|
||||
|
||||
// Otherwise, it means a new sub-entry (element) must be created
|
||||
// and appended to the form entry element
|
||||
else {
|
||||
const newSubEntryIndex = this.nbSubEntries;
|
||||
const newSubEntry = SubEntry.fromTemplate(
|
||||
this.subEntryTemplateElement,
|
||||
newSubEntryIndex,
|
||||
this
|
||||
);
|
||||
|
||||
this.subEntries.push(newSubEntry);
|
||||
this.formEntryElement.insertBefore(
|
||||
newSubEntry.subEntryElement,
|
||||
this.newSubEntryButtonElement
|
||||
);
|
||||
|
||||
// Increment Django's TOTAL_FORMS hidden form input
|
||||
this.totalFormsElement.value = (parseInt(this.totalFormsElement.value) + 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
removeSubEntry(subEntry) {
|
||||
// If the index of the sub-entry to remove is below the initial number of sub-entries,
|
||||
// it means one of the initial sub entry elements should be marked for deletion and hidden
|
||||
const removedSubEntryIndex = subEntry.index;
|
||||
if (removedSubEntryIndex < this.initialNbSubEntries) {
|
||||
subEntry.markForDeletionAndHide();
|
||||
}
|
||||
|
||||
// Otherwise, delete the sub-entry (and remove its element from the DOM)
|
||||
// and reindex other user-created sub-entries if need be
|
||||
else {
|
||||
subEntry.subEntryElement.remove();
|
||||
|
||||
this.subEntries.splice(subEntry.index, 1);
|
||||
for (let index = this.removeSubEntryIndex; index < this.subEntries.length; index++) {
|
||||
this.subEntries[index].reindex(index);
|
||||
}
|
||||
|
||||
// Decrement Django's TOTAL_FORMS hidden form input
|
||||
this.totalFormsElement.value = (parseInt(this.totalFormsElement.value) - 1).toString();
|
||||
}
|
||||
}
|
||||
|
||||
startHandlingNewSubEntryButtonClicks() {
|
||||
const buttonElement = this.formEntryElement.querySelector(".add-sub-entry-button");
|
||||
buttonElement.addEventListener("click", event => {
|
||||
event.preventDefault();
|
||||
this.createNewSubEntry();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
// Setup the script by creating one instance of MultiEntryFormEntry
|
||||
// for each form entry configured to contain several sub-entries
|
||||
document.addEventListener("DOMContentLoaded", event => {
|
||||
const multiEntryFormEntryElements = document.querySelectorAll(
|
||||
"#content-edit-profile .form-entry.multi-entry"
|
||||
);
|
||||
|
||||
for (let element of multiEntryFormEntryElements) {
|
||||
const formEntry = new MultiEntryFormEntry(element);
|
||||
}
|
||||
});
|
||||
|
15
fiches/static/fiches/scss/_buttons.scss
Normal file
15
fiches/static/fiches/scss/_buttons.scss
Normal file
|
@ -0,0 +1,15 @@
|
|||
@use "colors";
|
||||
|
||||
%button {
|
||||
padding: 10px;
|
||||
background-color: colors.$page-button-background;
|
||||
border: none;
|
||||
font-size: 1.2rem;
|
||||
color: colors.$page-button-text;
|
||||
box-shadow: 2px 2px 0 colors.$shadow;
|
||||
transition: 25ms ease-in-out;
|
||||
}
|
||||
|
||||
%button:hover {
|
||||
background-color: colors.$page-button-background-hover;
|
||||
}
|
|
@ -1,4 +1,6 @@
|
|||
@use "colors";
|
||||
@use "buttons";
|
||||
@use "errors";
|
||||
|
||||
#content-area {
|
||||
color: colors.$content-text;
|
||||
|
@ -23,20 +25,10 @@
|
|||
border-radius: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input[type="button"],
|
||||
input[type="submit"] {
|
||||
padding: 10px;
|
||||
background-color: colors.$page-button-background;
|
||||
border: none;
|
||||
font-size: 1.2rem;
|
||||
color: colors.$page-button-text;
|
||||
box-shadow: 2px 2px 0 colors.$shadow;
|
||||
transition: 25ms ease-in-out;
|
||||
}
|
||||
|
||||
input[type="button"]:hover,
|
||||
input[type="submit"]:hover {
|
||||
background-color: colors.$page-button-background-hover;
|
||||
@extend %button;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,6 +143,28 @@
|
|||
padding: 10px;
|
||||
background-color: colors.$content-frame-background;
|
||||
box-shadow: 2px 2px 0 colors.$shadow;
|
||||
|
||||
&.multi-entry ul.value {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style-type: none;
|
||||
|
||||
li {
|
||||
padding: 0;
|
||||
|
||||
.type {
|
||||
display: inline-block;
|
||||
margin: 0 1em 0 0;
|
||||
color: colors.$page-text-secondary;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.value {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +211,83 @@
|
|||
input, select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
> .errorlist.nonfield {
|
||||
@extend %error-list-container;
|
||||
@extend %error-list;
|
||||
padding: 10px;
|
||||
margin: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.form-sub-entry {
|
||||
display: grid;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-columns: 2fr 3fr auto;
|
||||
grid-template-areas:
|
||||
"errors errors errors"
|
||||
"type-input value-input remove-button";
|
||||
column-gap: 10px;
|
||||
margin: 0 0 10px 0;
|
||||
|
||||
&.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
// Since the different labels and inputs are not obvious to identifiate using CSS selectors,
|
||||
// they are selected one after the other using their natural order in the DOM
|
||||
// TODO: make this more robust by giving proper class names to each sub-entry element
|
||||
input:nth-child(1) { grid-area: type-input; }
|
||||
input:nth-child(2) { grid-area: value-input; }
|
||||
|
||||
.remove-button {
|
||||
grid-area: remove-button;
|
||||
min-height: 30px;
|
||||
min-width: 30px;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"/></svg>');
|
||||
background-size: 80%;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.remove-button:hover,
|
||||
.remove-button:active {
|
||||
background-color: #973232;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.erroneous {
|
||||
@extend %error-list-container;
|
||||
|
||||
// TODO: this is not robust and should be replaced by better selectors
|
||||
// In case of error, increment the child indices
|
||||
// to take the additional list of errors (.errorlist)
|
||||
// prepended by Django to the sub-entry
|
||||
input:nth-child(2) { grid-area: type-input; }
|
||||
input:nth-child(3) { grid-area: value-input; }
|
||||
|
||||
// Use a darker red color when the sub-entry background is already red
|
||||
.remove-button:hover,
|
||||
.remove-button:active {
|
||||
background-color: #7d2727;
|
||||
}
|
||||
}
|
||||
|
||||
.errorlist {
|
||||
@extend %error-list;
|
||||
grid-area: errors;
|
||||
}
|
||||
}
|
||||
|
||||
.form-sub-entry-template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.add-sub-entry-button {
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
|
||||
.form-entry.checkbox {
|
||||
|
|
28
fiches/static/fiches/scss/_errors.scss
Normal file
28
fiches/static/fiches/scss/_errors.scss
Normal file
|
@ -0,0 +1,28 @@
|
|||
%error-list-container {
|
||||
padding: 10px;
|
||||
background-color: #973232;
|
||||
}
|
||||
|
||||
%error-list {
|
||||
margin: 0;
|
||||
padding: 0 0 10px 0;
|
||||
list-style: none;
|
||||
|
||||
li::before {
|
||||
content: " ";
|
||||
display: inline-block;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
// A small top-margin is used to vertically center the icon with the text
|
||||
margin: -2px 0.5ex 0 0;
|
||||
background-image: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="white" width="18px" height="18px"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>');
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
li:not(:last-child) {
|
||||
margin: 0 0 0.5ex 0;
|
||||
}
|
||||
}
|
|
@ -9,7 +9,6 @@
|
|||
<title>
|
||||
{% block title_onglet %}{% trans "Annuaire des élèves de l'ENS" %}{% endblock %}
|
||||
</title>
|
||||
<link rel="stylesheet" type="text/css" href="view_fiche_files/style.css">
|
||||
<link rel="stylesheet" type="text/css" href="{% static "fiches/css/normalize.css" %}" />
|
||||
<link rel="stylesheet" type="text/css" href="{% static "fiches/css/annuaire.css" %}" />
|
||||
</head>
|
||||
|
@ -75,5 +74,5 @@
|
|||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
{% block extra_js %}{% endblock %}
|
||||
</html>
|
||||
|
|
|
@ -21,60 +21,76 @@
|
|||
|
||||
<div class="infos">
|
||||
{% if profile.pronoun %}
|
||||
<p class="pronouns">
|
||||
<div class="pronouns">
|
||||
<span class="label">{% trans "Pronom(s) utilisé(s)" %}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{{ profile.pronoun }}</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.department.exists %}
|
||||
<p class="department">
|
||||
<div class="department">
|
||||
<span class="label">{% trans "Département" %}{{ profile.department.count|pluralize }}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{% for dep in profile.department.all %}{{ dep }}{% if not forloop.last %}, {% endif %}{% endfor %}</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.birth_date %}
|
||||
<p class="birthdate">
|
||||
<div class="birthdate">
|
||||
<span class="label">{% trans "Date de naissance" %}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{{ profile.birth_date }}</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.thurne %}
|
||||
<p class="room">
|
||||
<div class="room">
|
||||
<span class="label">{% trans "Thurne" %}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{{ profile.thurne }}</span>
|
||||
</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.phone_set.exists %}
|
||||
<p class="phone">
|
||||
<div class="phone multi-entry">
|
||||
<span class="label">{% trans "Téléphone" %}{{ profile.phone_set.count|pluralize }}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{% for p in profile.phone_set.all %}{{ p }}{% if not forloop.last %},<br>{% endif %}{% endfor %}</span>
|
||||
</p>
|
||||
<ul class="value">
|
||||
{% for p in profile.phone_set.all %}
|
||||
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.number }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.social_set.exists %}
|
||||
<p class="social">
|
||||
<div class="social multi-entry">
|
||||
<span class="label">{{ profile.social_set.count|pluralize:_("Réseau social,Réseaux sociaux") }}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{% for p in profile.social_set.all %}{{ p }}{% if not forloop.last %},<br>{% endif %}{% endfor %}</span>
|
||||
</p>
|
||||
<ul class="value">
|
||||
{% for p in profile.social_set.all %}
|
||||
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.content }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.mail_set.exists %}
|
||||
<p class="mail">
|
||||
<div class="mail multi-entry">
|
||||
<span class="label">{{ profile.mail_set.count|pluralize:_("Mail,Mails") }}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{% for p in profile.mail_set.all %}{{ p }}{% if not forloop.last %},<br>{% endif %}{% endfor %}</span>
|
||||
</p>
|
||||
<ul class="value">
|
||||
{% for p in profile.mail_set.all %}
|
||||
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.mail }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if profile.address_set.exists %}
|
||||
<p class="address">
|
||||
<div class="address multi-entry">
|
||||
<span class="label">{{ profile.address_set.count|pluralize:_("Adresse,Adresses") }}</span>
|
||||
<span class="separator"></span>
|
||||
<span class="value">{% for p in profile.address_set.all %}{{ p }}{% if not forloop.last %},<br>{% endif %}{% endfor %}</span>
|
||||
</p>
|
||||
<ul class="value">
|
||||
{% for p in profile.address_set.all %}
|
||||
<li><span class="type">{{ p.name }}</span><span class="value">{{ p.content }}</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{% extends "fiches/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
|
||||
{% block content %}
|
||||
|
||||
|
||||
<div id="content-edit-profile" class="content">
|
||||
<h2>{% trans "Modifier ma page d'annuaire" %}</h2>
|
||||
{{ form.errors }}
|
||||
{{ form.non_field_errors }}
|
||||
<form method="post" action="" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<div class="form-entry">
|
||||
|
@ -63,21 +63,41 @@
|
|||
<label for="id_thurne">{% trans "Thurne :" %}</label>
|
||||
{{ form.thurne }}
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<div
|
||||
class="form-entry multi-entry"
|
||||
data-type-placeholder="{% trans "Personnel" %}"
|
||||
data-value-placeholder="{% trans "0612345678" %}"
|
||||
>
|
||||
<label for="id_phone">{% trans "Numéro(s) de téléphone :" %}</label>
|
||||
{{ phone_form }}
|
||||
{% trans "Ajouter un numéro" as add_number %}
|
||||
{% include "fiches/multientry.html" with formset=phone_form new_entry_text=add_number %}
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label for="id_social">{% trans "Réseaux sociaux :" %}</label>
|
||||
{{ social_form }}
|
||||
<div
|
||||
class="form-entry multi-entry"
|
||||
data-type-placeholder="{% trans "InstaTok" %}"
|
||||
data-value-placeholder="{% trans "mon_profil_instatok" %}"
|
||||
>
|
||||
<label for="id_social">{% trans "Réseaux sociaux :" %}</label>
|
||||
{% trans "Ajouter un réseau social" as add_social %}
|
||||
{% include "fiches/multientry.html" with formset=social_form new_entry_text=add_social %}
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label for="id_mail">{% trans "Mail(s):" %}</label>
|
||||
{{ mail_form }}
|
||||
<div
|
||||
class="form-entry multi-entry"
|
||||
data-type-placeholder="{% trans "Professionelle" %}"
|
||||
data-value-placeholder="{% trans "moi@ens.fr" %}"
|
||||
>
|
||||
<label for="id_mail">{% trans "Mail(s):" %}</label>
|
||||
{% trans "Ajouter un email" as add_mail %}
|
||||
{% include "fiches/multientry.html" with formset=mail_form new_entry_text=add_mail %}
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label for="id_address">{% trans "Adresse(s):" %}</label>
|
||||
{{ address_form }}
|
||||
<div
|
||||
class="form-entry multi-entry"
|
||||
data-type-placeholder="{% trans "Bureau" %}"
|
||||
data-value-placeholder="{% trans "45 rue d'Ulm" %}"
|
||||
>
|
||||
<label for="id_address">{% trans "Adresse(s):" %}</label>
|
||||
{% trans "Ajouter une adresse" as add_address %}
|
||||
{% include "fiches/multientry.html" with formset=address_form new_entry_text=add_address %}
|
||||
</div>
|
||||
<div class="form-entry">
|
||||
<label for="id_text_field">{% trans "Champ libre :" %}</label>
|
||||
|
@ -97,5 +117,8 @@
|
|||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block extra_js %}
|
||||
<script type="text/javascript" src="{% static "fiches/js/forms.js" %}"></script>
|
||||
{% endblock %}
|
||||
|
|
25
fiches/templates/fiches/multientry.html
Normal file
25
fiches/templates/fiches/multientry.html
Normal file
|
@ -0,0 +1,25 @@
|
|||
{{ formset.non_field_errors }}
|
||||
{{ formset.management_form }}
|
||||
{% for form in formset %}
|
||||
{{ form.non_field_errors }}
|
||||
<div class="form-sub-entry {% if form.errors %}erroneous{% endif %}">
|
||||
{% for field in form.visible_fields %}
|
||||
{{field.errors}}
|
||||
{{field}}
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{field}}
|
||||
{% endfor %}
|
||||
<button type=button" class="remove-button"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="form-sub-entry-template">
|
||||
{% for field in form.visible_fields %}
|
||||
{{field}}
|
||||
{% endfor %}
|
||||
{% for field in form.hidden_fields %}
|
||||
{{field}}
|
||||
{% endfor %}
|
||||
<button type="button" class="remove-button"></button>
|
||||
</div>
|
||||
<button type="button" class="add-sub-entry-button">{{ new_entry_text }}</button>
|
Loading…
Reference in a new issue