feature (form): form pages

This commit is contained in:
Mickael KERJEAN
2019-01-28 01:09:03 +11:00
parent 9e6ab92201
commit aaf33996c8
10 changed files with 135 additions and 32 deletions

View File

@ -2,7 +2,7 @@ import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Input, Textarea, Select, Enabler } from './'; import { Input, Textarea, Select, Enabler } from './';
import { FormObjToJSON, bcrypt_password, format } from '../helpers/'; import { FormObjToJSON, bcrypt_password, format, autocomplete } from '../helpers/';
import "./formbuilder.scss"; import "./formbuilder.scss";
@ -122,7 +122,34 @@ const FormElement = (props) => {
} }
props.onChange(value); props.onChange(value);
}; };
$input = ( <Input onChange={(e) => onTextChange(e.target.value)} {...id} name={struct.label} type="text" value={struct.value || ""} placeholder={struct.placeholder} readOnly={struct.readonly}/> );
const list_id = struct.datalist ? "list_"+Math.random() : null;
$input = ( <Input list={list_id} onChange={(e) => onTextChange(e.target.value)} {...id} name={struct.label} type="text" value={struct.value || ""} placeholder={struct.placeholder} readOnly={struct.readonly}/> );
if(list_id != null){
const filtered = function(multi, datalist, currentValue){
if(multi !== true || currentValue == null) return datalist;
return autocomplete(
currentValue
.split(",")
.map((t) => t.trim())
.filter((t) => t),
datalist
);
};
$input = (
<span>
{ $input }
<datalist id={list_id}>
{
filtered(struct.multi, struct.datalist, struct.value).map((item,i) => {
return ( <option key={i} value={item} /> );
})
}
</datalist>
</span>
);
}
break; break;
case "number": case "number":
const onNumberChange = (value) => { const onNumberChange = (value) => {

View File

@ -7,6 +7,7 @@
margin-bottom: 10px; margin-bottom: 10px;
opacity: 0.25; opacity: 0.25;
font-size: 0.95em; font-size: 0.95em;
line-height: 0.95em;
} }
input::placeholder, textarea::placeholder{ input::placeholder, textarea::placeholder{

View File

@ -15,3 +15,13 @@ export function copyToClipboard (str){
document.execCommand("copy"); document.execCommand("copy");
$input.remove(); $input.remove();
} }
export function format(str = ""){
if(str.length === 0) return str;
return str.split("_")
.map((word, index) => {
if(index != 0) return word;
return word[0].toUpperCase() + word.substring(1);
})
.join(" ");
}

View File

@ -52,3 +52,48 @@ export function createFormBackend(backend_available, backend_data){
obj[backend_data.type] = template; obj[backend_data.type] = template;
return obj; return obj;
} }
/*
* return a new list of autocompletion candidates considering the current input
*/
export function autocomplete(values, list) {
if(values.length === 0) return list;
let candidates_input = [],
candidates_output = [];
for(let i=0; i<list.length; i++){
const last_value = values[values.length - 1];
if(list[i].indexOf(last_value) === 0){
let tmp = JSON.parse(JSON.stringify(values))
tmp[values.length - 1] = list[i];
if(list[i] === last_value){
candidates_input = [tmp];
} else {
candidates_input.push(tmp)
}
continue
}
if(values.indexOf(list[i]) === -1){
candidates_output.push(list[i]);
}
}
if(candidates_input.length === 0){
candidates_input = [values]
}
candidates_output = [""].concat(candidates_output);
if(candidates_input.length > 1) {
return candidates_input.map((candidate) => {
return candidate.join(", ");
});
}
return candidates_output.map((candidate, idx) => {
return candidates_input[0]
.concat(candidate)
.join(", ")
.replace(/\,\s?$/, "");
});
}

View File

@ -11,8 +11,7 @@ export { invalidate, http_get, http_post, http_delete, http_options } from './aj
export { prompt, alert, confirm } from './popup'; export { prompt, alert, confirm } from './popup';
export { notify } from './notify'; export { notify } from './notify';
export { gid, randomString } from './random'; export { gid, randomString } from './random';
export { leftPad, copyToClipboard } from './common'; export { leftPad, format, copyToClipboard } from './common';
export { getMimeType } from './mimetype'; export { getMimeType } from './mimetype';
export { settings_get, settings_put } from './settings'; export { settings_get, settings_put } from './settings';
export { FormObjToJSON, createFormBackend } from './form'; export { FormObjToJSON, createFormBackend, autocomplete } from './form';
export { format } from './text';

View File

@ -1,10 +0,0 @@
export function format(str = ""){
if(str.length === 0) return str;
return str.split("_")
.map((word, index) => {
if(index != 0) return word;
return word[0].toUpperCase() + word.substring(1);
})
.join(" ");
}

View File

@ -341,6 +341,7 @@ const DateTime = (props) => {
const FileSize = (props) => { const FileSize = (props) => {
function displaySize(bytes){ function displaySize(bytes){
if(bytes === -1) return "";
if(Number.isNaN(bytes) || bytes === undefined){ if(Number.isNaN(bytes) || bytes === undefined){
return ""; return "";
}else if(bytes < 1024){ }else if(bytes < 1024){

View File

@ -7,10 +7,7 @@ export class FormViewer extends React.Component {
constructor(props){ constructor(props){
super(props); super(props);
this.state = { this.state = {
form: { form: {}
"test": {label: "test", type: "text", "value": null, default: "polo", placeholder: "test"},
"something": {label: "test", type: "text", "value": null, default: "polo", placeholder: "test"}
}
}; };
} }
@ -23,13 +20,12 @@ export class FormViewer extends React.Component {
} }
render(){ render(){
console.log(this.state.form);
return ( return (
<div className="component_formviewer"> <div className="component_formviewer">
<MenuBar title={this.props.filename} download={this.props.data} /> <MenuBar title={this.props.filename} download={this.props.data} />
<div className="formviewer_container"> <div className="formviewer_container">
<Container> <Container>
<form className="sticky"> <form className="sticky box">
<FormBuilder form={this.state.form} onChange={this.onChange.bind(this)} render={ ($input, props, struct, onChange) => { <FormBuilder form={this.state.form} onChange={this.onChange.bind(this)} render={ ($input, props, struct, onChange) => {
return ( return (
<label className={"no-select"}> <label className={"no-select"}>
@ -41,6 +37,12 @@ export class FormViewer extends React.Component {
{ $input } { $input }
</div> </div>
</div> </div>
<div>
<span className="nothing"></span>
<div style={{width: '100%'}}>
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
</div>
</div>
</label> </label>
); );
}}/> }}/>

View File

@ -13,18 +13,43 @@
flex: 1; flex: 1;
width: 100%; width: 100%;
.formbuilder{ .box{
label.no-select > div { padding: 25px 20px;
display: flex; border-radius: 2px;
line-height: 30px;
> span {
display: inline-block; .formbuilder{
width: 150px; > div {
max-width: 150px; margin-bottom: 10px;
text-align: right; @media screen and (max-width: 470px) { margin-bottom: 20px; }
padding-right: 15px;
color: var(--emphasis);
label.no-select > div {
display: flex;
line-height: 30px;
@media screen and (max-width: 470px) {
display: block;
line-height: inherit;
span.nothing{ position: absolute; }
}
> span {
display: inline-block;
width: 160px;
max-width: 160px;
text-align: right;
padding-right: 15px;
color: var(--emphasis);
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
@media screen and (max-width: 470px) {
text-align: left;
font-weight: bold;
}
}
}
} }
} }
} }

View File

@ -42,6 +42,9 @@ type FormElement struct {
ReadOnly bool `json:"readonly"` ReadOnly bool `json:"readonly"`
Default interface{} `json:"default"` Default interface{} `json:"default"`
Value interface{} `json:"value"` Value interface{} `json:"value"`
MultiValue bool `json:"multi,omitempty"`
Datalist []string `json:"datalist,omitempty"`
Order int `json:"-"`
} }
func init() { func init() {