Fork me on GitHub

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 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


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 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 Persons


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


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'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 πŸ™‚


hello people, I just published a new article on how to write an intermediate/advanced parser for, with the stuff I have learned so far in this new world, I hope it can help some of you:


Hi, I’m new with and untangled.. I was following the tutorial on 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.cljtrying 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 the url for support is

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 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