If you were to write a web application from scratch, would you just use the new forms way? i.e. just no reason not to - a higher/better abstraction is always a good thing.
@cjmurphy if you can model it as a form, i cant see a reason not to, but thatβs a reasonably sized if also better is subjective, and higher isnt necessarily better (not that i disagree wrt untangled forms)
although the more we use it, the more we can justify work towards improving it especially if we get an db adapter layer for the commit, plug-n-play with server persistance is going to be pretty nice
which btw is something im tinkering with
If the application has some idea of a user committing work, then it ought to be modelled as a form?? The whole web idea of what a form is is something that I don't fully understand I don't think. I wouldn't know whether to use a form or not if was writing a 'raw' web application. In the old days (pre-web, pre-Java, pre-Delphi, even-pre-Windows) there was a brief period when a huge number of applications used to be Oracle Forms applications. I think of a 'form' in that context - something ubiquitous to be used for programming applications, now SPAs. It was quite a productive way of programming. Ranting a bit π
it does feel like it could get really productive
and it feels validating (pun intended) that at one point developing with forms felt productive
The form support is a good match any time you have one or more persisted entities you'd like to allow a user to edit. But it is centered on the idea that the data will be presented in form entry fields
With management between a pristine and dirty state
I guess my thinking is that's there's no difference between a form entry field and any other field - always use form entry fields even when only displaying. The difference doesn't make much sense - its a 'web thing'.
But the presentation is customizable and also easily generated...so you could end up with forms-based dev on certain types of apps
Also easy to make click-to-edit things
@adambros: some of the reasons was productive: no mouse, no layout problems, no CSS (of course). There was field and record (i.e. record on the screen) block and whole form validation events (known as triggers). But not just validation, also triggers for leaving fields and that sort of thing - your whole app logic existed in well thought out triggers. So when the mouse came along these triggers could have (??) abstracted out (i.e. application programmer s/not have to care) things like whether the user is using the mouse or the keyboard to visit fields.
So an idea... just of the top of my head. Would it be a good idea to have a mechanism where if an ident could not be resolved (the entity is not longer in the table), the ident would just be deleted. So if an ident that doesn't have an entry in a table is encountered, it gets removed.
@urbank you could just walk through the app db and drop idents you encounter, kinda like how the tempid resolution function works.
I think adding in a global GC type utility for cleaning up old idents is a bit overkill though, at least for our case. Especially considering the create function for an entity usually lays out all the paths a new ident gets placed, it's trivial to make that a constant def and just do the reverse in the delete mutation.
@gardnervickers Right, I suppose it's usually overkill.
Something else. Take the calendar component in untangled-ui. It has a representation in the app-db. So that works great in a case like this
https://untangled-web.github.io/untangled-ui/guide.html#!/untangled.ui.calendar_cards
But what if start and end date can actually be edited from multiple parts of the UI (kind of silly for this example, but you get the point)? Then :start-date and :end-date can no longer be just idents pointing to calendar components, correct?
Because they aren't solely represented by the calendar components, but just edited by them
@urbank The model won't really support that. The query engine would have to be changed to side-effect, and that isn't what you want as overhead while trying to process a UI refresh. Your mutations should keep state clean...in fact, you should more be focused on removing and adding idents. The tables can be easily GC'd based on a graph analysis...if you run into a case where it really matters. That is where your space is consumed anyhow. Dangling idents should be considered errors in your program logic, since you've basically created a "bad pointer" in the graph.
and your calendar example makes no sense to me. It is normalized data. Of course you can point to a calendar from more than one place. That is intentional and desired.
and control it from multiple places
but I would never expect you to delete a calendar component from app state
So, separate your logic from UI bits that should probably remain in app state from "entities" that have a persisted lifetime on a server. Those are the ones you care about...and yes, Untangled makes you do a bit more "view creation" in the sense of creating multiple pointers if you have multiple views.
This is an intentional trade-off. If you want to purge an item from the DB, then you app logic needs to be well-constructed around that entity...but that is all local reasoning (in the sense that you only have to reason about it in the places it is used). For example, your navigation mutations should be dealing with data lifecycle as you move from place to place in the UI. It is your data.
@tony.kay Yes, I have gotten hung up on the view creation a couple of times, but when I think about it the trade-off does make sense. For one, as you've previously stated, computing everything on every render has the potential of tanking the performance, at which point you basically have to do what untangled has you do anyway.
perhaps I don't get your point. I cannot use the same instance for both start and end...that's true
and if I reuse one somewhere else, it'll still have the last state, which may be desirable
if it isn't desirable, then I use a diff instance
So I wasn't talking about the calendar being pointed to from different parts of the app...
But rather that the calendar is only one view on some date
So the calendar isn't being pointed to from different places in the app, but the value that the calendar is editing is being edited by other components
if that makes sense
when I say "if used from somewhere else" I am thinking in terms of any UI that makes a read/write interface to that instance in the db. Could either be a component (that shared calendar's ident) or a component that uses a link query to "pull" the component from a table entry [{[:calendar/by-id :end-date] ['*]}]
I guess I'm not seeing the problem you're trying to point out
Could be because it's not actually a problem, and I'm just missing something π I'll try to rephrase
So let's say the app-db stores a bunch of Person
s
Or just one Person
to make it simpler
this Person
has a :person/date-of-birth
field
Then there are two components PersonViewAlpha
and PersonViewBeta
They both display the Person
entity from the database, but one uses CalendarAlpha
and the other uses CalendarBeta
Both CalendarAlpha
and CalendarBeta
conceptually modify the same value :person/date-of-birth
But they also each have a representation in the app-db in separate tables. Person
also has a separate representation. If there were only one Calendar
for the Person
the value of :person/date-of-birth
could just be an ident pointing to that Calendar's data in the table
As is though, as far as I can see, updating :date-of-birth of a person involves also updating the value of CalendarAlpha
and CalendarBeta
.
Does this make any sense?
No...why would you have different representations of the same person in two different tables? Share the ident.
two views of the same data is most of the reason for the existence of a graph db format
date-of-birth for person A is a singular fact. It should never ever be a duplicate thing in your database
nor should any of the other facts about person A.
The idents are how you create views of that same data in different places in the UI
BUT
I think I do see your point: I've got a calendar with state, and you're wanting to overlay the entity on person
No...don't do that π
calendar has a callback for telling you when the date changes. It's internal date should not be used for a bday in an entity
you should hook up a callback to calendar, get the value when it is selected, and then transact it into person
the two should not be complected
Calendar should be treated as an opaque component that provides a date through a callback, not that represents a date in a person entity
That's a great questions, and it should be clearer in the docs
I didn't think about that possible misunderstanding
Also, the forms support makes it even more interesting...because you might choose to render a date as a calendar in an opaque way
for the form field
but that form field would need to update the date field in another entity
such a custom date field support would require you to either use component local state, or manually compose in the calendar widget for use by the calendar rendering...though the form support for updating could be linked behind the scenes....you'd still have to make the user add a query for the calendar on the form that was allowing that custom editor for the date. Composition can get interesting in this way when you're trying to keep things clean
I definitely need to make a video or something about composition when you have a container that is acting sort of like a pass-through for opaque children that are doing something more complex. Another good example is something like a tabbed interface, where you might want a component that controls tabs as a stand-alone component, but which cannot possibly know (in advance) the queries of the component that will appear in the tabs. This is another case where you would not model it in Untangled in that way (the tabs would not be a parent in the UI). Instead, the tab bar itself would be a sibling component that has a callback to tell you what tab is selected, and your "router" component would act on that to switch between your UI elements in the "pane".
Looking forward to the the video or something π
not sure when I'll have time, but hopefully that is clear. Feel free to ask questions
Aha, I see now about the calendar... it's suppose to modify the entity by the callback. However I'm still unclear on how it solves the particular issue with two views of the Person. For example
So one calendar per :person/date-of-birth
is now solved... you just use the callback, and the opaque data of the calendar is always in sync with Person
But then you add another component that also edits :person/date-of-birth
,
now when this component modifies the :person/date-of-birth
via callback, don't you also have to update the calendar (the first component which was editing Person
)?
otherwise, when you switch back to the calendar, it will display the previous value of :person/date-of-birth
Yes, you're understanding correctly
and yes, if you choose to use a different component that will need the date, then you'll have to push the date into it, which kinda sucks from a normalization standpoint.
for that matter, when you "click" to edit the date, that is the point at which the calendar would need to have its date set.
since the calendar has "what date is being edited" and is different from the storage of the bday itself.
But this IS what you want, since someone fiddling with the calendar (but not hitting save, cancel...for example) should not have changed the bday
and now we're back to why form support exists
you want separation of state: the state that is current, and the state that is pristine...and an ability to control the lifecycle of the edit itself
Being able to decide: as they edit fields, I send to the server...or, once the whole form is changed and they click save. Both valid...and you need to know what it was (to undo) and what it will be (to commit)
this implies a copy
without form support, you're in this circumstance of managing the copy through minutiae
which is exactly the problem you have in mutable javascript or any other way of doing it. Editing makes you want to have copies π
Right, makes sense. I'll have to think about it a bit. You've been most helpful, as always π
welcome
hello people, I just published a new article on how to write an intermediate/advanced parser for Om.next, with the stuff I have learned so far in this new world, I hope it can help some of you: https://medium.com/@wilkerlucio/implementing-custom-om-next-parsers-f20ca6db1664
Hi, Iβm new with om.next and untangled.. I was following the tutorial on https://github.com/untangled-web/untangled-todomvc I get to get it all up and running until the point of sending a support request, but when I go to the support.html with the id provided on console I get this error Uncaught Error: :support-request is not ISeqable
Iβm running it JVM_OPTS="-Ddev -Dtest -Dsupport" lein run -m clojure.main script/figwheel.clj
trying different configs but I keep getting this error
@jonystorm whatβs the url you are hitting? and can you give the full stacktrace?
The url for my list is <http://localhost:3000/dev.html?list=MyList>
the url for support is <http://localhost:3000/support.html?id=1001>
core.cljs:1120 Uncaught Error: :support-request is not ISeqable
at Object.cljs$core$seq [as seq] (core.cljs:1120)
at Object.cljs$core$first [as first] (core.cljs:1129)
at cljs$core$ffirst (core.cljs:1640)
at untangled$client$impl$data_fetch$data_query_key (data_fetch.cljs?rel=1491257248840:285)
at untangled$client$impl$data_fetch$data_path (data_fetch.cljs?rel=1491257248840:291)
at data_fetch.cljs?rel=1491257248840:48
at data_fetch.cljs?rel=1491257248840:52
at core.cljs:4265
at Function.cljs.core.swap_BANG_.cljs$core$IFn$_invoke$arity$2 (core.cljs:4265)
at cljs$core$swap_BANG_ (core.cljs:4258)
This is all the error I have@jonystorm wrong console
look at the server console for the support request ID
not the browser console
we need to make that bold/underlined/caps in the docs π
Make sure you don't restart the server between making and using the request. The implementation caches them in RAM for the demo
that error is a bad error...it basically means it didn't find the request you supplied
we should fix that error message
oh, ok. Yeah, that should work
on the develop branch or master? The develop branch has had some recent code cleanup, and isn't officially released. I doubt it is broken, but if you can't get it to work, you might try the master branch