These are chat archives for mithriljs/mithril.js

13th
Aug 2017
theRefugee
@theRefugee
Aug 13 2017 07:28
Looking for DnD touch. It would be nice, if anyone can offer a working example. Haven't found anything online. The examples on codepen don't work. Not sure how to adjust RubaXa to work with mithril.
theRefugee
@theRefugee
Aug 13 2017 08:59
@nicholasday search didn't spit this one out. thanks. I'll check it out
JD Williams
@jdwil
Aug 13 2017 13:40
I have a database running in a web worker (using alasql). Sending a query to the worker returns a promise. In my es6 components, I fetch data in the constructor and show a loading graphic until the promise is fulfilled. This is all working fine. But, when I receive push messages from my socket server in the web worker, I'll update the database and I want to refresh the page content. I can call m.redraw() easy enough, but this won't update the data because it was loaded in the constructor. Anyone know the most elegant way to tackle this?
James Forbes
@JAForbes
Aug 13 2017 13:53

can you elaborate what you mean by "it was loaded in the constructor", what was loaded in the constructor. And do you mean the oninit hook of the component, or some other constructor?

My hunch is, plug the socket from your worker into a stream in your component, reference the stream in your view, but yeah might need to see some code / get more context

JD Williams
@jdwil
Aug 13 2017 14:08
Thanks @JAForbes . I mean when the component is constructed, it loads data from a database and stores it in the component's state. So when a redraw is run it will have the same data. I can reload the data onupdate() but that triggers needless queries. streams seem interesting. I'll have to figure out how to use them with the web worker and alasql. Thanks for the suggestion!
pakx
@pakx
Aug 13 2017 15:02
@jdwil , the following may not help since it sounds like it would be a large-scale change to your codebase, but if you use a model-first approach this would be easy -- details here
JD Williams
@jdwil
Aug 13 2017 15:38
@pakx thanks, that's a good thought as well. I'm trying to get the MVP for this product out the door quickly, but that may be a good idea for a refactor. Even going that route I think I'd still have to solve the problem of getting the models' state updated when the db changes. As I think about it more it seems less like a mithril issue and more of a personal problem :)
Ben Chauvette
@bdchauvette
Aug 13 2017 15:52
@jdwil I haven't worked with alasql before, but I've been playing around with webworkers a bit lately. I'll put up an example in a bit, after I have some lunch :smile:
pakx
@pakx
Aug 13 2017 15:56
@jdwil , I'm curious -- w/ a model-first approach where is the continued problem? What I see is something like this: a) webworker prepares new data b) then invokes an actions method c) which updates model and calls redraw. Is there a subtlety I'm missing?
JD Williams
@jdwil
Aug 13 2017 15:58
@pakx I think maybe the subtlety here is that the update is not instigated from the UI (mithril) side. The web worker is connected to the server via web sockets, so the server can push data up to the clients. Right now, if they push some data up, the user would have to browse away from the page and back to see it. I'd rather have it "just show up"
I could see a solution where each component on the page registers a stream with the web worker, along with a query that feeds that stream. When a db update is received, the worker could loop over the streams, re-run the queries, and write the new data to them.
pakx
@pakx
Aug 13 2017 16:29

@jdwil , hmm, as far as I can tell from your incoming data flow, with a model-first approach such as described at the earlier link this would not be a problem at all. In that approach UI-events, route-changes, server-push, et al are considered "outside input", which are fed to actions, which updates model.

Your per-component business should work, too; different approach.

JD Williams
@jdwil
Aug 13 2017 16:40
In that case I'll do some more reading on that approach. I really appreciate all the feedback you guys!
Ben Chauvette
@bdchauvette
Aug 13 2017 16:46
@jdwil here's a quick example of triggering redraws using workers & streams
Ben Chauvette
@bdchauvette
Aug 13 2017 16:57
oops, looks like the link is broken :eyes:
here's the meat of it:
const workerBlob = new Blob([`
  let i = 0;
  setInterval(() => postMessage(++i), 500);
`]);

const workerUrl = window.URL.createObjectURL(workerBlob);
const worker = new Worker(workerUrl);

class Ticker {
  constructor() {
    this.ticker = m.stream();
    this.ticker.map(() => m.redraw());

    worker.onmessage = ({ data }) => this.ticker(data);
  }

  view() {
    return m('.ticker', this.ticker());
  }
}

const $root = document.getElementById('root');
m.mount($root, Ticker);
it's basically what @JAForbes said: create a stream in your constructor / oninit, then pipe the output of your worker into the stream. as long as your view function pulls its value from the stream, everything will be kept in sync by the stream mapping function :+1:
Boaz Blake
@boazblake
Aug 13 2017 19:18
i have a question about view rendering after href link.
I am using the app from the mithril tutorial
but I wanted to add an add functionality so i am pointing it a mongo db instance
everything is working ok excpet
i wish to re use the view for the add and edit
everything seems to route ok eg from the collections view and from another view I created
but when I try to go from the edit to the add view
mithril doesnt seem to recognize the change
the link is in the layout component
if I add an onupdate method to the view it gets called repeatedly
Boaz Blake
@boazblake
Aug 13 2017 19:24
i guess I need the a tag in the layout to refresh the page
how do I get a link to refresh the page?
Arthur Clemens
@ArthurClemens
Aug 13 2017 19:37
@boazblake It doesn’t sound right that you need to refresh the page
Boaz Blake
@boazblake
Aug 13 2017 19:39
i would not think so since I am using mithril routing - but i think because I have the logic to determine if the view should call the edit or add method in the oninit
then only when the first time i route to the add it works
or when I go to edit for the first time it works
but edit to add
i still have the names in the state
and the id
it does not reset the state
Arthur Clemens
@ArthurClemens
Aug 13 2017 19:40
why not put the add/edit decision in the view?
Boaz Blake
@boazblake
Aug 13 2017 19:41
i have this in my view
  oninit: vnode =>
    vnode.attrs.id
      ? Item.edit(vnode.attrs.id)
      : Item.add()
  ,
I assume this only runs when I navigate to it from another view
Arthur Clemens
@ArthurClemens
Aug 13 2017 19:42
I mean in view: vnode =>
I wouldn’t call “oninit” the view
it is comparable to a constructor
Boaz Blake
@boazblake
Aug 13 2017 19:44
I have the oninit in my view componenent
I have a view method too
its probably messy but here it is
import m from "mithril"
import Item from "./component.js"

export const view = {
  oninit: vnode =>
    vnode.attrs.id
      ? Item.edit(vnode.attrs.id)
      : Item.add()
  ,

  view: function() {
    return Item.state.currentItem
      ? m("form", {
        onsubmit: function(e) {
          e.preventDefault()
          Item.save()
        }
      }, [  m('formField',
              m("label.label", "First name"),
              m("input.input[type=text][placeholder=First name][required=true]", {
                oninput: m.withAttr("value", value => Item.state.updatedItem.firstName = value),
                value: Item.state.updatedItem.firstName})),

          m("label.label", "Last name"),
          m("input.input[placeholder=Last name][required=true]", {
            oninput: m.withAttr("value", value => Item.state.updatedItem.lastName = value),
            value: Item.state.updatedItem.lastName}),

          m("label.label", "image"),
          m("img", {src:Item.state.currentItem.image}),

          m("button.button[type=submit]",{class: "c-button button-brand"},"Save"),

          Item.state.edit
            ? m("button.button",{ class:"c-button c-button--error", onclick:m.withAttr("userId", Item.deleteItem )
              , userId:Item.state.updatedItem._id}, "X")
            : ""
            ,
          ])

        : "LOADING"
  }
}

module.exports = view
and in my compoenent.js
import m from "mithril"
import { clone } from "ramda"
import { initializeTask , addTask, findTask, editTask, removeTask, saveTask} from "./model.js"
import { log } from "utilities"

export const Item = {
  state: {
    currentItem:{},
    updatedItem:{},
  },
  data: {},
  errors:{},

  edit:id => {
    Item.state.edit = true

    const onError = e => console.log("E",e)
    const onSuccess = data => {
      Item.state.currentItem = data
      Item.state.updatedItem = clone(Item.state.currentItem)
    }

    return findTask(id).fork(onError, onSuccess)
  },

  add: _ => {
    Item.reset()
  },

  save: () => {
    const onError = e => log("e")(e)

    const onSuccess = item => {
      Item.state.currentItem = item
      Item.state.updatedItem = clone(Item.state.currentItem)
    }

    saveTask(Item.state.edit)(Item.state.updatedItem).fork(onError, onSuccess)
  },

  deleteItem:(id) => {
    const onError = e => log('e')(e)
    const onSuccess = s => log('s')(s)
    log("id")(id)
    id
      ? removeTask(id).fork(onError, onSuccess)
      : console.log("USER IS NOT IN Db ") //TOAST THIS
  },

  reset:() => {
    Item.data = {}
    Item.state =
      { edit: false
      , currentItem:
          { firstName: ""
          , lastName: ""
          , image: "http://www.telegraph.co.uk/content/dam/men/2016/05/24/Untitled-1-large_trans_NvBQzQNjv4BqqVzuuqpFlyLIwiB6NTmJwfSVWeZ_vEN7c6bHu2jJnT8.jpg"
          , id: ""
          }
      , updatedItem: {}
      }
    Item.errors = {}
    console.log('Item was reset', Item.state.edit)
    m.redraw()
  },
}

module.exports = Item
in the layout.js
i have this a tag
, m("a[href='/new']", {oncreate: m.route.link, onupdate:  m.route.link, class:"c-nav__item"}, "ADD")
Arthur Clemens
@ArthurClemens
Aug 13 2017 19:47
When is Item set to the edit state?
Boaz Blake
@boazblake
Aug 13 2017 19:48
im still unclear about mithril binding - so the updatedItem and currentItem may not be needed
in my collections view
I have this
, { oncreate: m.route.link, href:/edit/${item._id}, key: item._id}
i am mapping over the items
import m from "mithril"
import Collection from "./component.js"

const view = {
  oninit: Collection.load(),
  view:() => {
    return Collection.data.list[0]
      ? m(".item.list", Collection.data.list.map( item =>
        m("a.item-list-item"
          , { oncreate: m.route.link, href: `/edit/${item._id}`, key: item._id}
          , item.firstName + " " + item.lastName
        )
      ))
      : m("img", {src:'http://www.emptymag.com/wp-content/uploads/2016/06/EMPTY-masthead.png'})
  }
}

module.exports = view
Scotty Simpson
@CreaturesInUnitards
Aug 13 2017 19:52
@ACXgit excellent — thanks for letting me know! Sorry this took so long. I was camping with my family with no connectivity. It was like being in hell 😁
Arthur Clemens
@ArthurClemens
Aug 13 2017 19:53
If I understand correctly, the list-item edit link will show a the edit view. Why would you change the Item model? It seems unnecessary.
In the view function you can check the current route, then decide how to draw the page.
Boaz Blake
@boazblake
Aug 13 2017 19:53
how so?
what do you mean by changing the Item model?
Arthur Clemens
@ArthurClemens
Aug 13 2017 19:55
This is changing the Item model:
oninit: vnode =>
    vnode.attrs.id
      ? Item.edit(vnode.attrs.id)
      : Item.add()
and that seems unnecessary
I would opt for the simpler solution: get the current route, if it is an edit route, show the edit view, otherwise the add view.
This logic in the view function
Boaz Blake
@boazblake
Aug 13 2017 20:03
@ArthurClemens ok I think I get it ... thank you !!
Arthur Clemens
@ArthurClemens
Aug 13 2017 20:06
great. In the meantime I have created this flems: https://goo.gl/7rYndp
Boaz Blake
@boazblake
Aug 13 2017 20:12
thanks for the flems - it seems like you are showing different divs in the view depending on the url?
what if you wanted to show the same elements ... but have them populated depending on the vnode?
when I move the function I had into the view
I get repeated xhr calls to my db
it gets stuck in some type of loop
although i do see the delete button now disapearing when I click the link to new
Boaz Blake
@boazblake
Aug 13 2017 20:27
it seem like those xhr calls are all 304 warnings
pakx
@pakx
Aug 13 2017 20:42
@theRefugee , RubaXa/Sortable looks interesting, and it looks like it should be usable similarly to Dragula. I'll add it to the list of samples ... soon.
spacejack
@spacejack
Aug 13 2017 21:48
Looking into MithrilJS/mithril.js#1932 a bit... does not seem like an easy fix.
As far as I can tell, the attrs you send into hyperscript are never cloned, so keeping a clone of a style object would be tricky.
spacejack
@spacejack
Aug 13 2017 21:54
Not cloning attrs can result in some surprising behaviour as well...
I suppose a clone of every attrs object would add quite a bit of GC
I wonder what Inferno does
Boaz Blake
@boazblake
Aug 13 2017 22:22
I managed to stop the request loop by adding an if block, but this doesnt feel right
  edit:id => {
    Item.state.edit = true
    if (Item.state.currentItem._id === undefined) {
      const onError = e => console.log("E",e)
      const onSuccess = data => {
        Item.state.currentItem = data
        Item.state.updatedItem = clone(Item.state.currentItem)
      }

      findTask(id).fork(onError, onSuccess)
    }
  },
it now all works
except the values on the screen dont get updated
even though the console shows that the variables the inputs are bound to have been updated
Boaz Blake
@boazblake
Aug 13 2017 22:27
nm now it doesnt update state at all
pakx
@pakx
Aug 13 2017 22:46

@boazblake , IMO you were on the right track in your approach to using the same "view" for both "create" and "update" -- set a flag someplace (you did it in oninit I believe?), then when it comes time to render the view, render a create-view or an update-view based on that flag.

As you found, setting that flag in oninit means it works the way we want only the first time around -- when the component is initialized. Thereafter, since there's no further "initialization", we can't switch between create and update modes. You could get around this by putting the same flag-setting call in onupdate (as you noted), or in view (as arthurclemens did).

Thing is, in either case, and especially in case of setting it in view, we'll want to make sure that we don't do in that view-creation process something that itself causes a view-render. (For example if in the middle of rendering a view we go out and fetch data using m.request, the view usually would be re-rendered when that call resolves.) Ideally we'd want all display data pre-fetched and ready to be rendered by the time view is called. Hope that helps.

Arthur Clemens
@ArthurClemens
Aug 13 2017 22:59
A pragmatic approach is to put an updating call (that would result in a new view render) in a setTimeout with duration 0.
That will result in the call being processed in the next tick.
pakx
@pakx
Aug 13 2017 23:16
( @arthurclemens, are you sure setTimeout(..., 0) occurs at the subsequent tick? I thought it put it at the end of the current job queue, rather than the next tick. )
Boaz Blake
@boazblake
Aug 13 2017 23:18
@ArthurClemens @pakx I have tried your suggestions but it doesnt seem like the state gets updated unless I force a redraw or refresh and they both have unwanted side effects
Arthur Clemens
@ArthurClemens
Aug 13 2017 23:19
With Mithril you will need to call m.redraw when your data is updated. There is no magic binding.
@pakx Not sure about the tick.