!7 abstract sclass DynConvo > DynModule { L posts_raw = new L; // underlying list transient L posts; // list with notify S userInput; transient JChatConvo view; start { posts = dm_synchroList(this, posts_raw); } Pt minimumSize() { ret pt(300, 300); } O[] menuItems() { ret litarray("Clear", rEnter clearPosts); } visualize { print("Have posts: " + posts); view = swing(() -> new JChatConvo); view.usePostsList(posts); ret centerAndSouth( jscroll_trackWidth(view), withMargin(bottomPart())); } JComponent bottomPart() { ret dm_textFieldAndSubmit userInput('sendInput, buttonText := "Send"); } void sendInput { S s = trim(userInput); if (nempty(s)) { setField(userInput := ""); sendInput2(s); } } void sendInput2(S s) { if "clear" ret with clearPosts(); addPost(htmlEncode2(s), false); vmBus_send chatUserInput(module(), s); } // API void addPost(S html, bool right) swing { JChatConvo.Post post = new(html, right); posts.add(post); programLog("New post: " + struct(post)); view?.updateAndScrollDown(); } void clearPosts swing { posts.clear(); programLog("Cleared posts"); view?.updateAndScrollDown(); } void reply(S s) { if (empty(s = trim(s))) ret; addPost(htmlEncode2(s), true); } S lastInput() { for (Post p : lazyReversed(posts)) if (!p.right) ret p.html; null; } }