Skip to content

Commit

Permalink
Merge pull request zorchenhimer#133 from wyrid/feat/form_field_maxlen…
Browse files Browse the repository at this point in the history
…gth_indicator

Add maxlength indicator to Add Movie inputs
  • Loading branch information
CptPie authored Oct 17, 2022
2 parents 2ff8cae + 7f634b3 commit 8f1f812
Show file tree
Hide file tree
Showing 3 changed files with 241 additions and 15 deletions.
28 changes: 28 additions & 0 deletions web/static/css/site.css
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,34 @@ i:hover + .errorPopup {
flex-direction: column;
}

.movieInput textarea,
.movieInput input {
width: 100%;
box-sizing: border-box;
}

.movieInput .movieHeader {
display: flex;
flex-direction: row;
}

.movieInput .movieHeader label {
flex-grow: 1;
}

.maxlength_indicator {
text-align: right;
padding-left: 10px;
}

.maxlength_indicator .warning {
color: #FFF87B;
}

.maxlength_indicator .limit {
color: #FF8C8C;
}

#headTitle {
display: flex;
width: 50%;
Expand Down
191 changes: 191 additions & 0 deletions web/static/js/maxlength_indicator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
/*
maxlength_indicator.js
Apply class "maxlength_indicator" to any div / element to insert the text
indicating the limit of a form element. The data-name attribute must match the
name attribute of the input being tracked.
Attributes:
data-name(required):
The name of the form input whose maxlength is being tracked.
data-type(required):
Used to indicate how the maxlength is being tracked. Current allowed
values:
data-type="length"
Normal input fields that have a maxlength limit
data-type="link"
Tracks the maxlength of each individual link, as long as it is one link
per line.
data-link-length(required when data-type="link"):
The maxlength of each individual link.
Examples:
Normal input field:
<form>
<div class="maxlength_indicator"
data-type="length"
data-name="MyInput">
</div>
<textarea name="MyInput" maxlength="200"></textarea>
</form>
Textarea with links limited to 500 char each:
<form>
<div class="maxlength_indicator"
data-type="links"
data-name="MyLinks"
data-link-length="500">
</div>
<textarea name="MyLinks"></textarea>
</form>
*/

let limits = {};
/*
Warning class is applied to the indicator when the field's length is this many
letters or less away from the field's limit.
*/
const char_limit_warning = 10;

/* Update current limit to equal field length. */
function updateFieldLength(element) {
const length = element.value.length;
limits[element.name]['current'] = length;
}

/* Update the array storing the current length of each array. */
function updateLinksLength(element) {
const links = element.value.split('\n');
/*
Set the array length equal to the number of links. This will remove any
links still in the array that were deleted / are no longer present in
the form field.
*/
limits[element.name]['current'].length = links.length;
for(let i = 0; i < links.length; ++i) {
limits[element.name]['current'][i] = links[i].length;
}
}

/* Update innerHTML of the maxlength_indicator element. */
function updateIndicator(element, type=null) {
const {current, max} = limits[element.getAttribute('data-name')];
if(type === 'links') {
let values = current.slice();
for(let i = 0; i < values.length; ++i) {
values[i] = applyLimitClassDiv(values[i], max);
}
element.innerHTML = `${values.join('')}`;

} else {
element.innerHTML = applyLimitClassDiv(current, max);
}
}

/*
Returns a div for each limit in the format of "current / max" and applies
CSS classes based on how close it is to the limit.
*/
function applyLimitClassDiv(current, max) {
const remaining = max - current;
let elClass = '';

if(remaining <= 0) {
elClass = 'limit';
} else if(remaining <= char_limit_warning) {
elClass = 'warning';
}

return `<div class="${elClass}">${current} / ${max}</div>`;
}

/*
Once content is loaded, find all divs with a class of maxlength_indicator and
the corresponding input element that has a name matching the data-name, and
apply event listeners to track the length of the input's content.
*/
document.addEventListener('DOMContentLoaded', function() {
const limitEls = document.querySelectorAll('.maxlength_indicator');

limitEls.forEach(limitEl => {
const type = limitEl.getAttribute('data-type');
const name = limitEl.getAttribute('data-name');

if(type === null) {
console.error('Maxlength indicator missing data-type on element.')
console.error(limitEl);
}
if(name === null) {
console.error('Maxlength indicator missing data-name on element.')
console.error(limitEl);
}

/* Find the first form element that has a name that matches data-name. */
const limitInput = document.querySelector(`form [name="${name}"]`);
if(limitInput === null) console.error('Unable to find matching form element with name = ' + name);

/* Handle regular input elements with a maxlength. */
if(type === 'length') {
const limit = limitInput.getAttribute('maxlength')

if(limit === null) {
console.error('Element missing maxlength property.');
console.error(limitInput);
}

limits[name] = {
current: 0,
max: limit
};

limitInput.addEventListener('input', () => {
updateFieldLength(limitInput);
updateIndicator(limitEl);
});
/*
Call this once to update the indicators immediately to reflect any text
that is already in a field, such as when a page is refreshed and the
input still contains text.
*/
updateFieldLength(limitInput);
updateIndicator(limitEl);

/*
Handle input elements that limit the lengths of indiviual lines (such as
links).
*/
} else if(type === 'link'){
const limit = limitEl.getAttribute('data-link-length')

if(limit === null) {
console.error('Field limit indicator missing data-link-length property.');
console.error(limitInput);
}

limits[name] = {
current: [],
max: limit
};

limitInput.addEventListener('input', () => {
updateLinksLength(limitInput);
updateIndicator(limitEl, 'links');
});
/*
Call this once to update the indicators immediately to reflect any text
that is already in a field, such as when a page is refreshed and the
input still contains text.
*/
updateLinksLength(limitInput);
updateIndicator(limitEl, 'links');
}
});
});
37 changes: 22 additions & 15 deletions web/templates/add-movie.html
Original file line number Diff line number Diff line change
@@ -1,51 +1,56 @@
{{define "header"}}{{end}}
{{define "header"}}
<script type="text/javascript" src="/static/js/maxlength_indicator.js"></script>
{{end}}

{{define "body"}}
<form method="POST" action="/add" enctype="multipart/form-data">
{{if .HasError}}<div class="errorMessage">The input contained errors, see the highlighted fields for more information.</div>{{end}}
<div id="addMovieForm">
{{if .FormfillEnabled}}
<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "Title")}}{{if (index .Fields "Title").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "Title").Error}}</div>{{end}}{{end}}
<label class="TitleLabel" for="Title">Movie Title (max. {{.MaxTitleLength}} characters)</label>
<label for="Title">Movie Title (max. {{.MaxTitleLength}} characters)</label>
<div class="maxlength_indicator" data-type="length" data-name="Title"></div>
</div>
<div>
<textarea name="Title" id="Title" style="width:400px">{{if (index .Fields "Title")}}{{if (index .Fields "Title").Value}}{{ (index .Fields "Title").Value}}{{end}}{{end}}</textarea>
<textarea name="Title" id="Title" maxlength="{{.MaxTitleLength}}">{{if (index .Fields "Title")}}{{if (index .Fields "Title").Value}}{{ (index .Fields "Title").Value}}{{end}}{{end}}</textarea>
</div>
</div>

<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "Description")}}{{if (index .Fields "Description").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "Description").Error}}</div>{{end}}{{end}}
<label for="Description">Description (max. {{.MaxDescriptionLength}} characters)</label>
<div class="maxlength_indicator" data-type="length" data-name="Description"></div>
</div>
<div>
<textarea name="Description" id="Description" style="width:400px">{{if (index .Fields "Description")}}{{if (index .Fields "Description").Value}}{{( index .Fields "Description").Value}}{{end}}{{end}}</textarea>
<textarea name="Description" id="Description" maxlength="{{.MaxDescriptionLength}}">{{if (index .Fields "Description")}}{{if (index .Fields "Description").Value}}{{( index .Fields "Description").Value}}{{end}}{{end}}</textarea>
</div>
</div>

<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "Links")}}{{if (index .Fields "Links").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "Links").Error}}</div>{{end}}{{end}}
<label for="Links">Referencelinks (max. {{.MaxLinkLength}} characters per Link)</label>
<div class="maxlength_indicator" data-type="link" data-name="Links" data-link-length={{.MaxLinkLength}}></div>
</div>
<div>
<textarea name="Links" id="Links" style="width:400px">{{if (index .Fields "Links")}}{{if (index .Fields "Links").Value}}{{ (index .Fields "Links").Value}}{{end}}{{end}}</textarea>
<textarea name="Links" id="Links">{{if (index .Fields "Links")}}{{if (index .Fields "Links").Value}}{{ (index .Fields "Links").Value}}{{end}}{{end}}</textarea>
</div>
</div>
<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "PosterFile")}}{{if (index .Fields "PosterFile").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "PosterFile").Error}}</div>{{end}}{{end}}
<label for="PosterFile">Poster Image</label>
</div>
<div>
<input type="file" name="PosterFile" id="PosterFile" accept="image/*"style="width:400px"/>
<input type="file" name="PosterFile" id="PosterFile" accept="image/*"/>
</div>
</div>
{{ if .AutofillEnabled }}
<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "AutofillBox")}}{{if (index .Fields "AutofillBox").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "AutofillBox").Error}}</div>{{end}}{{end}}
<label for="AutofillBox">Autofill Data with the provided Link</label>
</div>
Expand All @@ -59,23 +64,25 @@
{{if not .FormfillEnabled}}
<input type="hidden" name="AutofillBox" value="on" />
<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "Links")}}{{if (index .Fields "Links").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "Links").Error}}</div>{{end}}{{end}}
<label for="Links">Enter IMDB or MyAnimeList link for a movie to add (max. {{.MaxLinkLength}} characters per Link):</label>
<div class="maxlength_indicator" data-type="link" data-name="Links" data-link-length={{.MaxLinkLength}}></div>
</div>
<div>
<textarea name="Links" id="Links" style="width:400px">{{if (index .Fields "Links")}}{{if (index .Fields "Links").Value}}{{(index .Fields "Links").Value}}{{end}}{{end}}</textarea>
<textarea name="Links" id="Links">{{if (index .Fields "Links")}}{{if (index .Fields "Links").Value}}{{(index .Fields "Links").Value}}{{end}}{{end}}</textarea>
</div>
</div>
{{end}}

<div class="movieInput">
<div>
<div class="movieHeader">
{{if (index .Fields "Remarks")}}{{if (index .Fields "Remarks").Error}}<i class='fas fa-exclamation-triangle warningIcon'></i><div class="errorPopup">{{(index .Fields "Remarks").Error}}</div>{{end}}{{end}}
<label class="RemarksLabel" for="Remarks">Enter your remarks here (max. {{.MaxRemarksLength}} characters):</label>
<div class="maxlength_indicator" data-type="length" data-name="Remarks"></div>
</div>
<div>
<textarea name="Remarks" id="Remarks" style="width:400px" maxlength="{{.MaxRemarksLength}}">{{ if (index .Fields "Remarks")}}{{if (index .Fields "Remarks").Value}}{{(index .Fields "Remarks").Value}}{{end}}{{end}}</textarea>
<textarea name="Remarks" id="Remarks" maxlength="{{.MaxRemarksLength}}">{{ if (index .Fields "Remarks")}}{{if (index .Fields "Remarks").Value}}{{(index .Fields "Remarks").Value}}{{end}}{{end}}</textarea>
</div>
</div>
<div class="movieInput">
Expand Down

0 comments on commit 8f1f812

Please sign in to comment.