Initial commit
This commit is contained in:
		
						commit
						c672ec1666
					
				
					 3 changed files with 295 additions and 0 deletions
				
			
		
							
								
								
									
										110
									
								
								serializer.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								serializer.js
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,110 @@ | ||||||
|  | class SerializerField { | ||||||
|  | 	/** | ||||||
|  | 	 * constructor | ||||||
|  | 	 * | ||||||
|  | 	 * @param {HTMLElement} f Input field (input|select|textarea) | ||||||
|  | 	 * @returns {SerializerField} | ||||||
|  | 	 */ | ||||||
|  | 	constructor(f){ | ||||||
|  | 		this.field = f; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * serialize | ||||||
|  | 	 * | ||||||
|  | 	 * @returns {(String|Number)} | ||||||
|  | 	 */ | ||||||
|  | 	serialize(){ | ||||||
|  | 		let type = this.field.getAttribute("type"); | ||||||
|  | 		if(type == "number" || type == "range"){ | ||||||
|  | 			return Number(this.field.value); | ||||||
|  | 		} else if(type == "date" || type == "datetime-local" || type == "month" || type == "week") { | ||||||
|  | 			return new Date(this.field.value); | ||||||
|  | 		} else { | ||||||
|  | 			return this.field.value; | ||||||
|  | 		}	 | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | class Serializer { | ||||||
|  | 	/** | ||||||
|  | 	 * constructor | ||||||
|  | 	 * | ||||||
|  | 	 * @param {HTMLElement} element | ||||||
|  | 	 * @returns {Serializer} | ||||||
|  | 	 */ | ||||||
|  | 	constructor(element){ | ||||||
|  | 		let fields = element.querySelectorAll(`:scope > input:not([type="submit"]), :scope > select, :scope > textarea, :scope > group`); | ||||||
|  | 
 | ||||||
|  | 		this._fields = new Map(); | ||||||
|  | 
 | ||||||
|  | 		for(let f of fields){ | ||||||
|  | 			this.addField(f); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * addField | ||||||
|  | 	 * | ||||||
|  | 	 * @param {HTMLElement} f | ||||||
|  | 	 */ | ||||||
|  | 	addField(f){ | ||||||
|  | 		let fieldName = f.getAttribute("name"); | ||||||
|  | 		let isArray = fieldName.match(/^\[(\d*)\]/) | ||||||
|  | 
 | ||||||
|  | 		let field; | ||||||
|  | 		if(f.tagName == "GROUP"){ | ||||||
|  | 			field = new Serializer(f) | ||||||
|  | 		} else { | ||||||
|  | 			field = new  SerializerField(f); | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if(isArray){ | ||||||
|  | 			fieldName = fieldName.replace(/^\[(\d*)\]/, ''); | ||||||
|  | 			if(!this._fields.has(fieldName)){ | ||||||
|  | 				this._fields.set(fieldName, []) | ||||||
|  | 			} | ||||||
|  | 			if(isArray[1] != ""){ | ||||||
|  | 				this._fields.get(fieldName)[isArray[1]] = field | ||||||
|  | 			} else { | ||||||
|  | 				this._fields.get(fieldName).push(field) | ||||||
|  | 			} | ||||||
|  | 		} else { | ||||||
|  | 			this._fields.set(fieldName, field) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/** | ||||||
|  | 	 * serialize | ||||||
|  | 	 * | ||||||
|  | 	 * @returns {Object} | ||||||
|  | 	 */ | ||||||
|  | 	serialize(){ | ||||||
|  | 		let json = {}; | ||||||
|  | 		for(let [k, f] of this._fields){ | ||||||
|  | 			if(Array.isArray(f)){ | ||||||
|  | 				if(k != ""){ | ||||||
|  | 					json[k] = []; | ||||||
|  | 				} else { | ||||||
|  | 					json = []; | ||||||
|  | 				} | ||||||
|  | 				for(let key in f){ | ||||||
|  | 					if(f[key]){ | ||||||
|  | 						if(k == ""){ | ||||||
|  | 							json[key] = f[key].serialize() | ||||||
|  | 							continue; | ||||||
|  | 						} | ||||||
|  | 						json[k][key] = f[key].serialize() | ||||||
|  | 					} | ||||||
|  | 				} | ||||||
|  | 			} else { | ||||||
|  | 				if(k == ""){ | ||||||
|  | 					json = f.serialize() | ||||||
|  | 					continue; | ||||||
|  | 				} | ||||||
|  | 				json[k] = f.serialize() | ||||||
|  | 			}	 | ||||||
|  | 		} | ||||||
|  | 		return json; | ||||||
|  | 	} | ||||||
|  | } | ||||||
							
								
								
									
										133
									
								
								tests/main.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								tests/main.go
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,133 @@ | ||||||
|  | package main | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"encoding/json" | ||||||
|  | 	"fmt" | ||||||
|  | 	"html/template" | ||||||
|  | 	"io/ioutil" | ||||||
|  | 	"net/http" | ||||||
|  | 	"net/http/httptest" | ||||||
|  | 	"os" | ||||||
|  | 	"os/exec" | ||||||
|  | 	"reflect" | ||||||
|  | 	"text/tabwriter" | ||||||
|  | 	"time" | ||||||
|  | 
 | ||||||
|  | 	"github.com/fatih/color" | ||||||
|  | 	"github.com/julienschmidt/httprouter" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | var templates *template.Template | ||||||
|  | var tw *tabwriter.Writer | ||||||
|  | var testData = []*Person{ | ||||||
|  | 	&Person{ | ||||||
|  | 		Name:      "Foo", | ||||||
|  | 		Surname:   "Bar", | ||||||
|  | 		Age:       57, | ||||||
|  | 		BirthDate: time.Date(1960, 1, 1, 0, 0, 0, 0, time.UTC), | ||||||
|  | 	}, | ||||||
|  | 	nil, | ||||||
|  | 	&Person{ | ||||||
|  | 		Name:      "Crow", | ||||||
|  | 		Surname:   "CrowCrow", | ||||||
|  | 		Age:       22, | ||||||
|  | 		BirthDate: time.Date(1995, 1, 5, 0, 0, 0, 0, time.UTC), | ||||||
|  | 		Languages: []Language{ | ||||||
|  | 			Language{ | ||||||
|  | 				Language:   "Dutch", | ||||||
|  | 				SkillLevel: 9.99, | ||||||
|  | 			}, | ||||||
|  | 			Language{ | ||||||
|  | 				Language:   "English", | ||||||
|  | 				SkillLevel: 8.4, | ||||||
|  | 			}, | ||||||
|  | 			Language{ | ||||||
|  | 				Language:   "Marokaans", | ||||||
|  | 				SkillLevel: -9000.9, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func main() { | ||||||
|  | 	router := httprouter.New() | ||||||
|  | 
 | ||||||
|  | 	router.GET("/", getTest) | ||||||
|  | 	router.POST("/", postTest) | ||||||
|  | 
 | ||||||
|  | 	router.ServeFiles("/static/*filepath", http.Dir("../")) | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
|  | 	templates, err = template.ParseGlob(`templates/*.gohtml`) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	tw = tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) | ||||||
|  | 
 | ||||||
|  | 	server := httptest.NewServer(router) | ||||||
|  | 
 | ||||||
|  | 	exec.Command("chromium", "--headless", "--disable-gpu", server.URL).Run() | ||||||
|  | 	// TODO: Fix closing FF | ||||||
|  | 	exec.Command("firefox", "-headless", server.URL).Run() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Person ... | ||||||
|  | type Person struct { | ||||||
|  | 	Name      string     `json:"name"` | ||||||
|  | 	Surname   string     `json:"surname"` | ||||||
|  | 	Age       int        `json:"age"` | ||||||
|  | 	BirthDate time.Time  `json:"birth_date"` | ||||||
|  | 	Languages []Language `json:"languages"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Languages ... | ||||||
|  | type Language struct { | ||||||
|  | 	Language   string  `json:"language"` | ||||||
|  | 	SkillLevel float64 `json:"skill_level"` | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func getTest(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||||||
|  | 	templates.ExecuteTemplate(w, `test.gohtml`, testData) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func postTest(w http.ResponseWriter, r *http.Request, _ httprouter.Params) { | ||||||
|  | 	body, _ := ioutil.ReadAll(r.Body) | ||||||
|  | 
 | ||||||
|  | 	var p []*Person | ||||||
|  | 
 | ||||||
|  | 	err := json.Unmarshal(body, &p) | ||||||
|  | 	if err != nil { | ||||||
|  | 		fmt.Println(err) | ||||||
|  | 		fmt.Println("Person could not be decoded") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for k := range p { | ||||||
|  | 		if p[k] == nil && testData[k] == nil { | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  | 		isEq(*p[k], *testData[k]) | ||||||
|  | 	} | ||||||
|  | 	tw.Flush() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func isEq(p1, p2 Person) { | ||||||
|  | 	assert(p1.Name, p2.Name, "Name") | ||||||
|  | 	assert(p1.Surname, p2.Surname, "Surname") | ||||||
|  | 	assert(p1.Age, p2.Age, "Age") | ||||||
|  | 	assert(p1.BirthDate, p2.BirthDate, "BirthDate") | ||||||
|  | 	for k := range p1.Languages { | ||||||
|  | 		assert(p1.Languages[k].Language, p2.Languages[k].Language, "Languages.Language") | ||||||
|  | 		assert(p1.Languages[k].SkillLevel, p2.Languages[k].SkillLevel, "Languages.SkillLevel") | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func assert(v, v2 interface{}, ident string) { | ||||||
|  | 	if !reflect.DeepEqual(v, v2) { | ||||||
|  | 		fmt.Fprint(tw, color.RedString("FAIL\t")) | ||||||
|  | 	} else { | ||||||
|  | 		fmt.Fprint(tw, color.GreenString("PASS\t")) | ||||||
|  | 	} | ||||||
|  | 	fmt.Fprintf(tw, "%s\t%s\n", color.BlueString(ident), color.YellowString(fmt.Sprintf("'%v'\t'%v'", v, v2))) | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								tests/templates/test.gohtml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								tests/templates/test.gohtml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | ||||||
|  | <!DOCTYPE html> | ||||||
|  | <html lang="en"> | ||||||
|  | 	<head> | ||||||
|  | 		<meta charset="UTF-8"> | ||||||
|  | 		<title>test</title> | ||||||
|  | 		<style> | ||||||
|  | 			form > group{ | ||||||
|  | 				margin: 10px 0; | ||||||
|  | 				display: block; | ||||||
|  | 			} | ||||||
|  | 			group * { | ||||||
|  | 				display: block; | ||||||
|  | 			} | ||||||
|  | 		</style> | ||||||
|  | 		<script src="/static/serializer.js"></script> | ||||||
|  | 	</head> | ||||||
|  | 	<body> | ||||||
|  | 		<form id="testform"> | ||||||
|  | 			{{ range $k, $p := . }} | ||||||
|  | 				{{ if $p }} | ||||||
|  | 					<group name="[{{ $k }}]"> | ||||||
|  | 						<input name="name" type="text" value="{{ $p.Name }}"> | ||||||
|  | 						<input name="surname" type="text" value="{{ $p.Surname }}"> | ||||||
|  | 						<input name="age" type="number" value="{{ $p.Age }}"> | ||||||
|  | 						<input name="birth_date" type="date" value="{{ $p.BirthDate.Format "2006-01-02" }}"> | ||||||
|  | 						{{ range $p.Languages }} | ||||||
|  | 							<group name="[]languages"> | ||||||
|  | 								<input name="language" type="text" value="{{ .Language }}"> | ||||||
|  | 								<input name="skill_level" type="number" value="{{ .SkillLevel }}"> | ||||||
|  | 							</group> | ||||||
|  | 						{{ end }} | ||||||
|  | 					</group> | ||||||
|  | 				{{ end }} | ||||||
|  | 			{{ end }} | ||||||
|  | 		</form> | ||||||
|  | 		<script> | ||||||
|  | 			let f = document.getElementById("testform"), | ||||||
|  | 				s = new Serializer(f); | ||||||
|  | 
 | ||||||
|  | 			fetch("/", { | ||||||
|  | 				method: 'post', | ||||||
|  | 				headers: { | ||||||
|  | 					'Accept': 'application/json', | ||||||
|  | 					'Content-Type': 'application/json' | ||||||
|  | 				}, | ||||||
|  | 				body: JSON.stringify(s.serialize()) | ||||||
|  | 			}).then(() => { | ||||||
|  | 				window.close() | ||||||
|  | 			}) | ||||||
|  | 		</script> | ||||||
|  | 	</body> | ||||||
|  | </html> | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue