How do I get the values of all the fields of a form? - smalltalk

I have a HTML form like this in a client side Amber solution
<form id="myForm1">
Creator: <input type="text" name="creator" />
<br>
Title: <input type="text" name="title" />
<br>
Description: <input type="text" name="description" />
<br>
Doctype: <input type="text" name="doctype" />
<br>
Tags: <input type="text" name="tags" />
</form>
Question
How do I iterate through all of the fields of the form in order to put the content of the fields into a Amber dictionary with the field name as key and the text content as value?
New version of the question after answer by Stephen-Eggermont and MKroenert
How do I get the values of all the fields of the form in order to put them into an Amber dictionary with the field name as key and the text content as value?
Or is there an idiomatic way to create a form and retrieve the values?
Note: The form may be constructed with Amber code if this makes things more readable.
References
https://github.com/amber-smalltalk/amber/wiki/FAQ#how-do-i-get-the-text-value-of-an-input-field
http://api.jquery.com/each/
Edit after answer: FileIn code
The answer provided by MKroenert works fine
Below is his code I tested. It may be filed in directly in a workspace
Widget subclass: #AmberFormExample
instanceVariableNames: 'dictionary inputs'
package: 'TodoList'!
!AmberFormExample methodsFor: 'not yet classified'!
collectValues
inputs do: [ :each |
dictionary at: (each asJQuery attr: 'name')
put: (each asJQuery val).
].
Transcript show: dictionary printString
!
initialize
dictionary := Dictionary new.
inputs := Array new.
!
renderInput: inputName on: html
html p: [
html label with: inputName.
inputs add: (html input id: inputName;
name: inputName;
yourself)]
!
renderOn: html
inputs removeAll.
html form id: 'myForm1'; with: [
#('Creator' 'Title' 'Description' 'Doctype' 'Tags') do: [ :each |
self renderInput: each on: html]].
html button
with: 'Collect Inputfield Values';
onClick: [
self collectValues.
]
! !

I reused the code from this SO question and rewrote it in Amber to address the first part of your question.
Here is how you iterate over all input fields:
(('#myForm1 *' asJQuery)
filter: ':input')
each: [ :thisArg :index |
console log: thisArg ] currySelf
This Amber recipe is required to get access to the JavaScript this.
Printing both name and value of the input fields to the JavaScript console can be done like this:
(('#myForm1 *' asJQuery)
filter: ':input')
each: [ :thisArg :index |
console log: (thisArg asJQuery attr: 'name').
console log: (thisArg asJQuery val)] currySelf
Putting the values into a dictionary:
| dict |
dict := Dictionary new.
(('#myForm1 *' asJQuery)
filter: ':input')
each: [ :thisArg :index |
dict at: (thisArg asJQuery attr: 'name')
put: (thisArg asJQuery val)] currySelf
As for the second part of your question, there is the Web package in Amber which contains Classes for generating HTML pages.
What you do is to create a subclass of Widget and implement the renderOn: html method.
The object passed in as html parameter is of type HTMLCanvas and can be used to create an HTML form like this:
renderOn: html
html form with: [
html input id: 'creator'.
html input id: 'title'.]
Here is a complete example.
Take it as a starting point and be aware that it may not be the most efficient way of doing things
Widget subclass: #AmberFormExample
instanceVariableNames: 'dictionary inputs'
package: 'Examples'
AmberFormExample>>initialize
dictionary := Dictionary new.
inputs := Array new.
AmberFormExample>>renderOn: html
inputs removeAll.
html form id: 'myForm1'; with: [
#('Creator' 'Title' 'Description' 'Doctype' 'Tags') do: [ :each |
self renderInput: each on: html]].
html button
with: 'Collect Inputfield Values';
onClick: [
self collectValues.
]
AmberFormExample>>renderInput: inputName on: html
html p: [
html label with: inputName.
inputs add: (html input id: inputName;
name: inputName;
yourself)]
AmberFormExample>>collectValues
inputs do: [ :each |
dictionary at: (each asJQuery attr: 'name')
put: (each asJQuery val).
].
After implementing this class in a running Amber instance the following code can be used to execute it:
AmberFormExample new appendToJQuery: 'body' asJQuery

There is a lot of duplication in your form. You might want to take a look at HTMLCanvas and how it is used in IDE.
You could add a method renderField: aFieldName on: aCanvasand reuse that 5 times. Did you take a look at Seaside?
The end result should be something like
renderOn: html
html form with: [
#('Creator' 'Title' 'Description' 'Doctype' 'Tags') do: [:each |
self renderField: each on: html]

Related

How do you preselect the options when loading the q-select?

I'm new to vue and quasar so i haven't done this yet. I'm building the ability to edit a record and I want to be able to pre select the options for a q-select element. The pre selected options are what the user saved when they first created the record. Currently I have:
<q-select
v-model="products"
label="Products *"
:options="products"
option-label="title"
option-value="id"
multiple
use-chips
:rules="[val => val || 'A product is required']"
/>
Chris, You just need to set the model value in your case products and it will work.
Example -
model: 'Twitter',
options: [
'Google', 'Facebook', 'Twitter', 'Apple', 'Oracle'
]
codepen - https://codepen.io/Pratik__007/pen/QWNBxWz

How do I get a table to display images

I am building a table in Seaside (Using VisualAge Smalltalk). I would like to have each table row have an image as well as text data. This what the method looks like:
where PSIni getImageUrl returns the main part of the url. The final url looks like
http://www.totallyobjects.com/images/pennstadt//15.jpg
Can anyone point me as to how to get this done please?
makeData: html usingMethod: aMethod
|tableData headingList methodList|
tableData := self perform: aMethod.
headingList := self headingList.
methodList := self methodList.
html table: [
html tableRow: [
headingList do: [ :each | html
tableHeading: each
]
].
tableData do: [ :one |
html tableRow: [
methodList do: [ :each |
each = #image ifTrue: [
html tableData: (self showImageFor: one id using: html)
] ifFalse: [
html tableData: (one perform: each)
]
]
]
]
]
(self showImageFor: one id using: html) obviously doesn't work. This method looks like this:
showImageFor: anID using: html
html image width: 200; url: PSIni getImageURL, '/', anID printString, '.jpg'
where PSIni getImageUrl returns the main part of the url.
The final url looks like:
http://www.totallyobjects.com/images/pennstadt/15.jpg
and should result in that image appearing in one element of a table row.
You need to nest the brushes:
html tableData: [ (self showImageFor: one id using: html) ]
It's explained in depth in the book: http://book.seaside.st/book/fundamentals/rendering-components

how to properly parse paired html tags?

the question is about parsing an html stream obtained by load/markup in a way you can get html tags constituent parts, i.e. when you find
<div id="one">my text</div>
you should end with something like <div id="one">, {my text} and </div> in the same container, something like
[<div id="one"> {my text} </div>]
or even better
[<div> [id {one}] {my text} </div>]
the parsing problem is matching paired html tags, in html a tag may be an empty tag with maybe attributes but without content and thus without an ending tag or a normal tag maybe with attributes and content and so an ending tag, but both types of tag are just a tag
I mean when you find a sequence like <p>some words</p> you have a P tag just the same you get whit a sequence like <p /> just a P tag, in first case you have associated text and ending tag and in the latter you don't, that's all
In other words, html attributes and content are properties of tag element in html, so representing this in json you will get someting like:
tag: { name: "div" attributes: { id: "one } content: "my text" }
this means you have to identify content of a tag in order to assign it to properly tag, which in terms of rebol parse means identifing matching tags (opening tag and ending tag)
In rebol you can easy parse an html sequence like:
<div id="yo">yeah!</div><br/>
with the rule:
[ some [ tag! string! tag! | tag! ]]
but with this rule you will match the html
<div id="yo">yeah!</div><br/>
and also
<div id="yo">yeah!</p><br/>
as being the same
so you need a way to match the same opening tag when appearing in ending position
sadly rebol tags cannot (AFAIK) be parametrized with tag name, so you cannot say something like:
[ some [ set t1 tag! set s string! set t2 tag!#t1/1 | tag! ] ]
the t1/1 notation is due to a (bad) feature of rebol including all tag atributes at same level of tag name (another bad feature is not reckognizing matching tags as being the same tag)
Of course you can achieve the goal using code such as:
tags: copy []
html: {<div id="yo">yeah!</p><br/>}
parse html [ some [ set t1 tag! set s string! set t2 tag! (tag: first make block! t1 if none <> find t2 tag [append/only tags reduce [t1 s] ]) | tag! (append/only tags reduce [t1])]]
but the idea is to use a more elegant and naive approach using parse dialect only
There's a way to parse pairs of items in rebol parse dialect, simply using a word to store the expected pair:
parse ["a" "a"] [some [set s string! s ]]
parse ["a" "a" "b" "b"] [some [set s string! s ]]
But this doesn't work well with tags due to tags carry attributes and special ending marks (/) and thus it's not easy to find the ending pair from initial one:
parse [<p> "some text" </p>] [some [ set t tag! set s string! t ]
parse [<div id="d1"> "some text" </div>] [some [ set t tag! set s string! t ]
don't work cause </p> is not equal to <p> and neither </div> is equal to <div id="d1">
Again you can fix it with code:
parse load/markup "<p>preug</p>something<br />" [
some [
set t tag! (
b: copy t remove/part find b " " tail b
insert b "/"
)
set s string!
b (print [t s b])
|
tag!
|
string!
]
]
but this is not simple and zen code anymore, so question's still alive ;-)

Smalltalk / Seaside - A more elegant way to use radio buttons?

So I want to do something like:
html unorderedList: [
group := html radioGroup.
self employeeNames do: [ :eachEmp |
html listItem: [
group radioButton
selected: <set tmpVar = empKey>
callback: [ self <dependent on button pushed> ].
html text: eachEmp ] ] ] ]
What I would like this to do is generate a list of employees, with radio buttons on each line. Then, depending on the radio button that's selected and which button is pushed, we do some action.
So a visual representation of what I want is something like:
|Post Timecard| --- |Timesheet History| --- |Print Paycheck| --- |Delete|
Employee 1 ..... data .... data .... data
Employee 2 ..... data .... data .... data
Employee 3 ..... data .... data .... data
Employee 4 ..... data .... data .... data
Employee 5 ..... data .... data .... data
So they could for example, select Employee 3 and then push |Delete| - this would trigger a delete for Employee 3.
I think I can handle what to do for the selected: portion of my code, but I've no clue how to handle the callback? Is there a way to dynamically choose what to call?
Since you are using a radio group anyway you'll only be able to select a single entry from the list of employees. Now, if you use a form element to wrap the controls, your action anchors could simply trigger a submit of the form:
formName := 'employee-form'.
html form
name: formName;
with: [
html unorderedList: [
group := html radioGroup.
self employeeNames do: [ :eachEmp |
html listItem: [
group radioButton
selected: nil "<set tmpVar = empKey>";
callback: [ :value | self "<dependent on button pushed>" ];
with: eachEmp ] ] ] ].
html anchor
submitFormNamed: formName;
callback: [ :value | self handleDelete ]
with: 'delete'
The callback of the anchor will be evaluated after all of the callbacks of the form elements.
Side note: notice the use of with: to set the anchor and radio button texts. You should almost always use with: as the last message to html.

the keyAttr of data-dojo-props doesn't work

here is my html file :
...
<span data-dojo-id="staffStore" data-dojo-type="dojo.data.ItemFileReadStore" data-dojo-props='data:../../staff.json'></span>
<input data-dojo-type="dijit.form.ComboBox"
data-dojo-props="store: staffStore,
keyAttr: 'id',
searchAttr: 'staff_name',
autoComplete: true,
id: 'staff_name',
name:'staff_name',
value: '' "/>
...
and the json data goes as follows:
{
identifier: "id";,
label: "id",
items: [{id: 982483700, staff_name: "guanyu";},{id: 582057769, staff_name: "zhangfei";},{id: 166802994, staff_name: "zhaoyun";}]
}
here is my problem:
when i use post method i have got 'staff_name' in the searchAttr: 'staff_name' passed to the background-appication ,but i want to have the 'id' in the keyAttr: 'id' passed to background-application.in a word,i have passed made a wrong post action.can someone help me get out of this problem?
Use dijit/form/FilteringSelect not dijit/form/ComboBox.
You can enter any text into a ComboBox therefore it cannot return id which is the reason it returns text (or label or searchAttr). FilteringSelect allows only to chose one of options and therefore it can return id, which it does.
See it in action and experiment at jsFiddle: http://jsfiddle.net/phusick/QzQ38/