Modeling condition, nested forms with elm-simple-forms - elm

I have a form that starts with a select. Depending on what was selected the form then expands to a common main bit and a details section that depends on the selection.
I started modeling with a separate details section
type ProductDetails
= Book Book.Model
| Brochure Brochure.Model
| Card Card.Model
type alias Model =
{ form : Form CustomError Estimate
, details : ProductDetails -- a Form CustomerError CardModel / BookModel / ....
, msg : String
}
but this is becoming quite convoluted to handle in e.g. view.
The alternative would seem to be conditionally to add the details into the main form model - e.g.
type alias Estimate =
{ customer : String
, project : String
, product : String
, details : ProductDetails
}
Before I get started I’d welcome experience from others on what has worked well

If I understand correctly, you have separate modules for Book, Brochure and Card? I don't quite understand what is the purpose of your Model but I would structure it like this:
import Book
import Brochure
import Card
type Products
= Book
| Brochure
| Card
type Msg
= Details Products
type alias Model =
{ selectedProduct : Product
}
update : Msg -> Model -> Model
update msg model =
case msg of
Details prd ->
Model prd
view : Model -> Html
view model =
model.selectedProduct.view
So as you can see, now you define all available products and then you say that Msg can be Details, which would show details, and it's function would be to set selectedProduct value in Model to selected Product. You can implement selecting with:
button [ onClick (Details Book) ] [ text "Book" ]
for example to select a book. Later on you want to make it dynamic and the first instinct should be to be able to call the view function of selected Product.
In other case you could define view which would require some fields, which every Product's model would contain and then you could use them to write some information on site.
Please note that code above isn't meant to work, it's just to represent the idea.

I'm not familiar with elm-simple-forms, but this seems like a good representation of your form:
type ProductType
= TBook
| TBrochure
| TCard
type Product
= Book Book.Model
| Brochure Brochure.Model
| Card Card.Model
type alias Model =
{ product : Maybe Product
, customer : String
, project : String
}
type Msg
= SelectProductType ProductType
init : Model
init =
{ product = Nothing
, customer = ""
, project = ""
}
update : Msg -> Model -> Model
update msg model =
case msg of
SelectProductType product ->
{model | product =
case product of
TBook -> Book Book.init
TBrochure -> Brochure Brochure.init
TCard -> Card Card.init
}
view : Model -> Html Msg
view model =
case model.product of
Nothing ->
myProductTypeSelect
Just product ->
withCommonFormInputs
<| case product of
Book submodel -> Book.view submodel
Brochure submodel -> Brochure.view submodel
Card submodel -> Card.view submodel
The Maybe gives you a nice way to choose between the first form (just the select) and the second form (customer details + selected product type details).
The Book.view etc. give you Html which you can add to the common case:
withCommonFormInputs : Model -> Html Msg -> Html Msg
withCommonFormInputs model productInputs =
div
[]
[ input [] [] -- customer
, input [] [] -- project
, productInputs -- product (Book, Brochure, Card) subform
]

I ended up using a Dict of the various fields and changed the fields when the product changed. Trying to model each product more explicitly create more boiler plate than I needed.

Related

How to create a record for field using many2one in Odoo 13?

I have 3 models. I used One2many field o put the tree view of model 3 in form view of model 1 and 2.
1/ bao_hiem.py with one2many field:
lstd_baohiem = fields.One2many('lich.su', 'name')
thamchieu = fields.Char('Tham chiếu')
2/ dieu_chinh.py with one2many field:
lstd_dieuchinh = fields.One2many('lich.su', 'name')
thamchieu = fields.Char('Tham chiếu')
And 3/ lich_su.py with many2one field:
name = fields.Many2one('bao.hiem')
name_id = fields.Many2one('bao.hiem')
thamchieu = fields.Char('Tham chiếu')
I want to pass the values (eg: 'name', 'thamchieu') from dieu_chinh.py to lich_su.py and auto record with all those values via a button. So that, in the user's form view of model 1 and 2 can show the recorded value also, called the activity history of 1 user.
The code for the button like this:
def chapthuan(self):
giatri_lstd = self.env['lich.su']
gt = {
'name' : self.name.id,
'thamchieu' : self.thamchieu,
'thoigian' : self.thoigian
}
list_lstd_dieuchinh=[]
for line in self.lstd_dieuchinh :
art = {}
art['name'] = line.name.id
art['lstd_dieuchinh'] = line.lstd_dieuchinh
art['thamchieu'] = line.thamchieu
art['thoigian'] = line.thoigian
list_lstd_dieuchinh.append((0, 0, art))
gt.update({ # add the list of command to gt
'thamchieu': list_lstd_dieuchinh
})
giatri_lstd = giatri_lstd.create(gt)
return True
After click the button 'chapthuan', it can pass the value and create the record with the field 'name', 'date'. But with the field: 'thamchieu' which has relation many2one with model2 is still not worked.
I must select manually in the form view of the user.
So how to create a record with many2one field?
Please help!
Thank you
https://www.odoo.com/documentation/13.0/reference/orm.html#create-update
You are trying to create records by (0, 0, {values}) to name_id by the way of x2many field.
But name_id is Many2one field. That's your programming error.
But the error log you showed happened when you are trying to remove a record that already links to another record. So what I said above is not the main problem... Anyway, I'm a little confused with your code, it looks not so good

The Elm way of transforming flags to model

I have the following types in my app:
type Page
= Welcome
| Cards
type alias Flags =
{ recipientName : String
, products : List Product
}
type alias Product =
{ id : Int
, name : String
, price : Float
, liked : Maybe Bool
}
type alias Model =
{ recipientName : String
, currentPage : Page
, products : List Product
}
I am passing an array of products as flags. Here's what my init looks like:
init : Flags -> ( Model, Cmd Msg )
init flags =
let
{ recipientName, products } =
flags
in
Model recipientName Welcome products
|> withNoCmd
The challenge I'm facing is that the products in this array only have id, name, and price attributes. So, given the Flags definition, every time I extend Product with a new attribute (such as liked), the array of products passed as flags will need to have that attribute as well. For now, I just render them as empty, but this doesn't feel right, so I was wondering what is the Elm way™ of receiving flags and transforming them into the model? Thank you!
It sounds like your Product is already defined as an input (or the environment) of your app:
type alias Product =
{ id : Int
, name : String
, price : Float
}
and you are augmenting this with info that relates the Recipient to the Products. I'd suggest splitting this out into its own type that can grow as your app grows, eg:
type alias Opinion =
{ liked : Maybe Bool
, review : String
, preferredColor : Color
}
then you can tie these together in your Model:
type alias Model =
{ recipientName : String
, currentPage : Page
, products : List (Product, Opinion)
}
or, depending on how the application works, you might end up wanting to look up the recipient's opinion by product.id:
...
, products : List Product
, opinions : Dict Int Opinion
The point is that if you keep the original Product unchanged, you can build a small library of functions that work on Product, both for inventory (where no recipient is involved) and for the customer. Maybe you can re-use the Opinion type for customer metrics.
If these two types are likely to evolve, keeping them separate can help ensure you don't end up with messy and bug-attracting interdependencies.

Convert SQL to Active Record Query matching on IN

How would I convert this sort of SQL into Active Record Syntax.
I've struggled mainly resolving the IN with the other elements.
SELECT \"accounts\".* FROM account_categories, accounts WHERE \"accounts\".\"worksheet_id\" = 5 AND (account_categories.name IN ('Savings','Deposit') AND account_categories.id = accounts.account_category_id) ORDER BY \"accounts\".\"id\" ASC"
worksheet_id will vary, won't always be 5.
I want to use this in a scope in the Account model.
Similar like this
scope :savings, -> { from('account_categories, accounts').where("account_categories.name = ? AND account_categories.id = zen_accounts.account_category_id", 'Savings') }
but testing for both Savings & Deposit something like this:
scope :savings_and_deposit, -> { from('account_categories, accounts').where("account_categories.name = ? AND account_categories.id = zen_accounts.account_category_id", ['Savings','Deposit]) }
Try this code:
scope :savings, -> (worksheet_id) { filtered_categories(worksheet_id, ['Savings']) }
scope :savings_and_deposit, -> (worksheet_id) { filtered_categories(worksheet_id, ['Savings', 'Deposit']) }
scope :filtered_categories, -> (worksheet_id, category_names) do
joins(:account_categories).
where(worksheet_id: worksheet_id).
where(account_categories: {name: category_names}).
order(id: :asc)
end
This code supposes what Account model already has relation account_categories, otherwise replace joins(:account_categories) with joins("JOIN account_categories ON account_categories.id = accounts.account_category_id")

Elm 0.18: How to route messages with Sub.map based on their content?

I have dynamically created Javascript components that communicate via ports with Elm model. They send data through a port of form port sendData : ((ComponentId, String) ...
On the elm side there is a ChildComponent module that represents their models:
type alias ComponentId =
Int
port sendData : ((ComponentId, String) -> msg) -> Sub msg
type Msg
= ProcessData String
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [
sendData ProcessData
]
In the Parent I have:
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
ChildMessage componentId msg -> ...
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch
[ Sub.map (ChildMessage componentId) (ChildComponent.subscriptions model.child) ]
Obviously this results in
Cannot find variable `componentId`
310| Sub.map (ChildMessage componentId) (ChildComponent.subscriptions (getChildModel (componentId model))) ]
^^^^^^^^^^^
How do I "extract" componentId from data coming from port? Or perhaps what I am doing is completely wrong?
Update
I have isolated the problem (with a few additions suggested by #ChadGilbert) into a project https://github.com/lguminski/ElmMultipleComponentsSubscription
When using Sub.map, you need a function that accepts a child model as its parameter. You could instead define ChildMessage as
-- in Parent.elm
type Model =
...
| ChildMessage Child.Model
Your parent subscriptions function will need to be updated:
-- in Parent.elm
subscriptions model =
Sub.batch
[ Sub.map ChildMessage (ChildComponent.subscriptions model.child) ]
When it comes to handling the data from the port in the child, you will need a Msg that has as its first parameter the same type defined in your port, mainly (ComponentId, String). Consider adding something like this to your child, where the new RouteData message can then call the ProcessData update case when needed, or ignore it otherwise.
-- in Child.elm
type Msg
= ProcessData String
| RouteData (ComponentId, String)
subscriptions : Model -> Sub Msg
subscriptions model =
Sub.batch [
sendData RouteData
]
update msg model =
case msg of
RouteData (componentId, val) ->
if componentId == "my-component-id" then
update (ProcessData val) model
else
model ! []
ProcessData val -> ...
Update
Based on your additional detail, I would propose that you move the port and child Msg routing out of the Child module and into the Parent. You could then add the following update cases to the Parent:
ChildMessage componentId childMessage ->
let
childUpdates =
List.map (\c -> if c.id == componentId then updateChild c else c ! []) model.children
updateChild child =
let
(updatedChildModel, cmd) =
Child.update childMessage child
in
updatedChildModel ! [ Cmd.map (ChildMessage child.id) cmd ]
in
{ model | children = List.map Tuple.first childUpdates }
! (List.map Tuple.second childUpdates)
RouteData (componentId, val) ->
update (ChildMessage componentId (Child.ProcessData val)) model
I have created a pull request for your repository here

displaying data related to a contact

I don't really know how to deal with the next issue :
So, I've a webbased application developped with ASP.NET MVC3, which is used to remember when an event is relayed to some people.
I've 3 tables
Contact
id_contact
name
firstname
...
event
id_event
...
transmission
FK_id_event
FK_id_firstname
mailed (boolean)
phoned (boolean)
For each event, I need to list all the contacts that are related to this event. And for each contact, I need to display 2 checkboxes that have to be checked if the contact has been called or mailed.
'
' GET: /Event/Details/5
Function Details(id As Integer) As ViewResult
Dim event As event = db.event.Single(Function(o) o.idOpportunite = id)
Dim contacts = (From a In db.contact, b In db.transmission
Where a.id_Contact = b.FK_id_contact And b.FK_id_event = id
Select a)
Dim transmission = (From a In contacts, b In db.transmission
Where a.id_Contact = b.FK_trans_cont
Select b)
Dim model = New EventDetails With {
.event= event,
.Contacts = contacts,
.TransOpp = transopp
}
Return View(model)
End Function
I don't know if the "transmission" part of the code is good or not.
Here in the view, this is were I display the contacts
#For Each contact In Model.contacts
#<tr>
<td>
#Html.ActionLink(contact.name + " " + contact.firstname , "Details", New With {.id = contact.idContact})
</td>
<td>
#Html.Raw(contact.phone)
</td>
<td>
#*Html.DisplayFor(Function(modelItem) currentItem.mail)*#
<a href=mailto:#contact.mail>#Html.Raw(contact.mail)</a>
</td>
<td>
***My checkboxes should be here***
</td>
</tr>
Next
So, my question is, what should I do to display those checkboxes?
(sorry if I'm not understandable, I'm not a native english speaker. Don't hesitate to edit my english mistakes (or the title which is not a great one)).
With the help of Yasser, I've done this :
#code
Dim mail As Boolean = (From a In Model.Event
Where a.FK_id_contact = contact.idContact And a.FK_id_event = Model.Opportunite.idOpportunite
Select a.mailed)
End Code
However, I get an error :
Value of type 'System.Collections.Generic.IEnumerable(Of Boolean)' cannot be converted to 'Boolean'.
Here is something that should help
#{
bool isMailed = // set values;
bool isPhoned = // set values
}
#Html.CheckBox("mailed ", isMailed );
#Html.CheckBox("phoned", isPhoned );
In your ContactViewModel you can have two properties
bool isMailed ;
bool isPhoned ;
Then you can query the database from your controller before you bind the viewmodel the view and set those parameters. For example if you are showing data for contact id = 1 and event id = 2, you can query the database table transmissions and find whether you have called or mailed before and update the variable in ContactViewModel.
then in your view you can bind the values to the checkbox as follows
#Html.CheckBox("mailed ", contact.isMailed );
#Html.CheckBox("phoned", contact.isPhoned );
if you want to update the mailed or phoned in the database you can do it using the above ViewModel by submitting data to the controller. from controller you can find what is the Event_Id, Contact_Id and mailed or phoned , then you can update the database accordingly