Giter VIP home page Giter VIP logo

om's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

om's Issues

ITransact protocol

Currently we've hard coded how a cursor can update the application state.

renderToString

renderToString of components should be very useful for server-side rendering.

synthesized components

om.core/join to synthesize components (or whatever it becomes) will likely be the tool of choice for less trivial applications. In this case we want the equality check to ignore the actual synthesized value - we only want to check the original values that compose it.

feeling part of it

get-node comment is a little bit outdated since owner was taken from most lifecycle protocols : Note the life cycle protocol methods all pass the owner as an argument, ie. IRender."

root function: path variable is not used since to-cursor is called with two parameters arity

wrap-form-element: (this-as this
(.transferPropsTo this
;; NOTE: if switch to macro we remove a closure allocation
(ctor #js {:value (aget (.-state this) "value")
:onChange (aget this "onChange")}))))}))

Is onChange within ctor necessary since it's already a property ?

set-state! on already deleted node

Hi David,

I do the following:

(defn on-mark [node e {:keys [id]}]
  (let [state (om/get-state node [:marked])]
    (om/set-state! node [:marked]
      ((if (state id) disj conj) state id))))

(defn on-delete [items e item]
  (om/update! items #(vec (remove #{(om/value item)} %))))

(defn item-component [{:keys [id text] :as item} node items]
  (reify
    om/IInitState
    (init-state [_]
      {:marked #{}})
    om/IRender
    (render [_]
      (let [marked (om/get-state node [:marked])]
        (dom/li #js {:onClick (om/bind (partial on-mark node) item)
                     :style #js {:background (if (marked id) "red" "white")}}
          (dom/a #js {:href "javascript:void(0);"
                      :onClick (om/bind (partial on-delete items) item)}
            "[x]")
          " "
          text)))))

;; (def app-state (atom {:items []}))
;; (om/build-all item-component (:items app)
;;           {:key :id :opts (:items app)})

I create a couple of items and then delete them all. After deleting the last one I get the following error:

Uncaught Error: No protocol method ICloneable.-clone defined for type null:

This fixes the problem:

(defn on-delete [items e item]
  (om/update! items #(vec (remove #{(om/value item)} %)))
  (.stopPropagation e))

full source

CLJS 2131 Not Available on Clojars

Currently local install of OM with lein install will not work

Could not find artifact org.clojure:clojurescript:jar:0.0-2131 in central (http://repo1.maven.org/maven2/)

Switching back to 2127 works, though.

Get actual data by cursor?

Maybe i missed something, but i guess i need something like

(defn actualize [cursor]
  (let [m (meta cursor)
        path (:om.core/path m)]
    (if (empty? path)
      @(:om.core/state m)
      (get-in @(:om.core/state m) path))))

to get actual piece of state by cursor for now. If so, maybe there is a sense of including it in lib, cuz it is actually opposite to update! and quite usefull.

Hiccup style templating?

This is awesome.
Have you looked at Pump?
It allows using hiccup for the html, rather than calling, say (dom/div ...).
Would you consider taking a PR making that syntax available?

Question: How would you "reset" a nested tree?

Let's say you have state-representation like so:

(def app-state (atom {:tree 
                      {:tag :root
                       :content [{:tag :node
                                  :content []}
                                 {:tag :node
                                  :content []}]}
                      :file-name "sample.xml"}))

In this example it would represent a xml-file.
If you load another XML-file, how would you "reset" (?) the :tree part correctly?

I tried some things, but most of them (like an (om/update! state assoc :tree new-tree-representation)) failed. The nested elements throw an Uncaught Error: Cannot build Om component from non-cursor exception.

Would love to hear your thoughts on this! Thanks in advance.

Another thing that is on my mind and I'd like to share: Thank you very much for your effort! It's a lot of fun to play with OM ๐Ÿ‘

don't pass props to component constructor function

The React model means that people will accidentally refer to it from various life cycle protocols and it will not be what they expect. We should not pass it in. All life cycle implementers can access Om props via om.core/get-props. Only IRender implementations would receive props directly as it always guaranteed to be consistent.

dom elements select/option does not work as expected?

I cannot get dom select/option elements to work as I expect. Probably me, but I don't know if its related to the om/dom/wrap-form-element function to preserve form values on state change.

My code below does not produce the text part of the option element.

(defn sel [data]
  (om/component
    (dom/select nil
      (dom/option #js {:value "one"} "ONE")
      (dom/option #js {:value "two"} "TWO"))))

(om/root base sel js/document.body)

produces:

<html>
<head></head>
<body>
<select data-reactid=".r[2l15d]">
<option value="one" data-reactid=".r[2l15d].[0]"></option>
<option value="two" data-reactid=".r[2l15d].[1]"></option>
</select>
</body>
</html>

State on IDidUpdate\WillUpdate

I've found strange behaviour of states on IDidUpdate and IWillUpdate.
Did: prev-state is always same as current state.
Will: when i'm printing whole next-state object to console i'm seeing state that should be (realy will-state) but when i'm accessing it through (.-__om_state next-state) i recieve same state as current, same way as Did.
Maybe there is another "true" way to accessing this states...

set-state! doesn't invalidate properly when nested

This works as you'd think:

(defn toggle [cursor owner opts]
  (om/component
    (dom/div #js {:onClick #(om/set-state! owner :toggle
                                           (not (om/get-state owner :toggle)))}
             (if (om/get-state owner :toggle)
               "B"
               "A"))))

(om/root {} toggle js/document.body)

But this does not:

(defn two-toggles [cursor owner opts]
  (om/component
    (dom/div nil
      (om/build toggle cursor)
      (om/build toggle cursor))))

(om/root {} two-toggles js/document.body)

om.core/build-raw need works

build-raw is not enough if a user wants to create a component with local state which does not need to update the application state.

unable to render changed value in a vector

i have a vector of random chars which gets rendered into an ul list. i set up a timer which will transact! the head of the vector every 2 seconds to a random char. a span is also setup to display the head of the vector. when i display the page, only the span text gets updated not the li item. am i doing something wrong?

(ns om-sandbox.test-list
  (:require [om.core :as om :include-macros true]
            [om.dom :as dom :include-macros true]
            [sablono.core :as html :refer [html] :include-macros true]
            [purnam.types :as types])
  (:use-macros [purnam.js :only [obj arr !> ?> ! ? def.n]]))

(enable-console-print!)

(defn rand-value []
  (let [possible-values "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"]
    (rand-nth possible-values)))

(defn gen-list [n]
  (vec (repeatedly n rand-value)))

(defn test-list-item [item owner]
  (om/component
   (html [:li item])))

(defn test-list [items owner]
  (reify
    om/IWillMount
    (will-mount [_]
                (js/setInterval
                 #(om/transact! items [0] (constantly (rand-value)))
                 1000))
    om/IRender
    (render [_]
     (html [:div
            [:span (get items 0)]
            [:ul
             (map
              #(om/build test-list-item %1 {:react-key %2})
              items
              (range))]]))))

(def items (gen-list 5))

(om/root items test-list (.getElementById js/document "content"))

hooks into React life cycle methods

Pure instead of taking a function should take an instance that implements life cycle protocols as well as something like IChildren for producing the children lazily.

(defn some-component [data path]
  (reify
    IMount
    (-will-mount [...] ...)
    (-did-mount [...] ...)
    IUnmount
    (-will-unmount [...] ...)
    (-did-unmount [...]) ...)
    IUpdate
    ...
    IChildren
    ...))

Because many components don't need all this boilerplate we should probably provide a component macro:

(defn some-component [data path]
  (component
    (dom/div ...)))

Which expands into a reify that just implements IChildren.

Avoid om.core.join using IEquiv to compare cursors in shouldComponentUpdate

I was taking a look at the counters example and I noticed the usage of om.core.join. This function seems like trouble to me because it makes the component using it depend on the entire structure of the app state. A more composable way of putting components together would be to make sure that each component only knows about the data it needs to read and potentially update, without regard for the structure of anything else.

I created a fork with an example of one way to get around this problem: https://github.com/rads/om/compare/counters

Instead of using join, I simply attached the cursor for :shared to each counter. According to the current implementation, using assoc on an existing cursor gives you a new cursor. The counters continue to update when they need to, updating when either the :shared key or the individual counter changes.

The only problem with doing this naively is that since we're creating a new cursor every time we build the child component, the shouldComponentUpdate for the child always returns true. We can get rid of this inefficiency by using the IEquiv protocol to compare inside of shouldComponentUpdate, instead of the existing identical? check. This means that even if the map is not the same as the previous one, it will still return false if the values of the child cursors have not changed.

With this change to shouldComponentUpdate, it's also possible to create entirely new cursors on-the-fly to pass in to child components. Even if a new cursor is created every time, it is considered equivalent to the previous one as long as the cursors it contains -- the ones that come from the app state -- have not changed.

The benefit of this is that the structure of your app data does not have to match up with the structure of your components. For example, if your app data looks like {:states {:editing 1}, :todos [{:id 1, ...}, {:id 2, ...}]}, but you want to reuse a component that expects a data format of {:editing [{:id 1, ...}]}, you could pass in something like (om/to-cursor {:editing (filter #(= (:id %) (get-in app [:states :editing])) (:todos app)}) and it would only update when either :editing or :todos has changed on the existing app state. This would make it much easier to share components across projects.

Edit: Fixed typo and error in example at the end.

Need more info on how to structure application state

I've been trying out Om with a UI for a music player. I've had a hard time understanding where certain pieces of state should go. My app state looks like this:

{:search-results [{:id 1, :title "First Song", ...}
                  {:id 2, :title "Second Song", ...}]
 :player {:playing? true
          :current-song {:id 1, :title "First Song", ...}}}

The :search-results key represents to the songs that show up in the UI. The user can click on a song to change the current song being played, which is when the :player key gets updated.

The confusing part is that the :player key does not map to a single UI component, since there are multiple parts of the UI that need to change when a new song starts playing. For example, when the :playing? key changes, the player's controls need to change a button to show "Play" or "Pause". At the same time, the search results need to show an icon next to the current song in the results to show the song that is currently selected.

There is a single :player key for both the search results and the controls components. An alternative would be to create a :controls key, get rid of the :player key, and then duplicate the player state for each component. This would match up better with the idea of representing UI components as EDN, but it seems like a bad idea to me to duplicate the application state.

It seems like with issue #19 there will be another place to put the state, but I'm not sure how that would help. In plain React, there is one place to put state: encapsulated inside an component. There is no global app state, which means components can only share information using props, and only from parent to child. There's no decision to be made about whether the state is global or transient.

In summary:

  • How do you deal with shared state among multiple components?
  • What is the need for having both a global application state and individual component states?
  • If a component has its own state, but needs to react to shared state as well, how do you express that when we are currently only allowed to set one path per component?

Thanks for the great work!

OM mounts/unmounts only once.

Hi David,

I noticed that when I switch child components in a parent component
the will-mount fn is called only once and will-unmount is never
called. Looks like a bug to me.

I have a repository that shows this behaviour over
here. There is a
JavaScript implementation in index-react.html and an OM version
index-om.html that shows the different behaviour in the JavaScript
console.

Roman

Google Groups

Any chance of a mailing list, I have some questions to ask that are too long for twitter, and don't really count as bug/issue.

Thanks

data representation independence

A very awesome idea from @asolove, just need to consider how to achieve it.

A user might have a piece of data from the app state that doesn't conform to the representation required by a widget. There should be a way to make the data conform with very little work.

examples showing the usage

Hi David,

Thanks for putting this out. Can you also post any examples that you have tried out. It could just be a simple project which uses 'om'. If you could also upload that on github, it would be great.

Thanks,
Murtaza

Example in README doesn't (always) work

It seems that the example in the README is (sometimes) running before the DOM is ready. When I run it in Chrome, I get the error:

Uncaught Error: Invariant Violation: prepareEnvironmentForDOM(...): Target container is not a DOM element. in the browser console. Some debugging revealed that js/document.body was returning nil.

But when I wrap (om/root {} widget js/document.body) in a function and call that function in a script tag at the bottom of the HTML page, it works.

expose a state api

We don't want to use React's setState because it has subtree re-rendering semantics we have little use for. Still, users need a way store transient state in a component without having to put it into the application state, which is more like the pure model data w/o application specific attributes.

This also means Pure will need to do an identity check on our special state property. We need to be careful to put setState changes into some pending state so that everyone gets a chance to read a consistent picture. When the re-render begins we should merge pending state into the previous one.

TodoMVC bug

Open your benchmark page
Run Benchmark 1
Toggle 1st tick - it marks the 1st todo
Delete items 2, 3, 4
Try to click the tick for the 2nd item โ€”ย 5th item is actually toggled

sortable example issues

Sortable is still janky on item drop. The code relies too much on peculiarities of the DOM. Items in the the sortable should not them selves be draggable. Clicking on an item in the sortable should construct a single draggable component that is not actually in the list itself.

Support absolute paths

Currently om/build is given a relative path. It's useful to allow supplying an arbitrary path to build UIs that do not correspond precisely to the shape of the given data. We would allow us to do joins.

Expose cursor path

This is useful when doing a custom state succession mechanism, instead of om.core/update! on deep cursors.

For example:

(def ^:dynamic *ui-events-chan* (chan))

(defn post-event [type cursor param]
  (let [path (.-path cursor)]
    (put! *ui-events-chan* [type path param])))

(declare handle-event)
(defn run-event-loop [root-cursor]
  (go-loop []
    (om/update! root-cursor handle-event (<! *ui-events-chan*))
    (recur)))

(defn handle-event [app [type path param :as e]]
  (case type
    ; ... 
    ))

Pending state doesn't propagate before first render

A simple way to see this bug is to modify TodoMVC to have one todo in the list by default:

(def app-state (atom {:showing :all :todos [{:id (guid)
                                             :title "Do a thing"
                                             :completed false}]}))

Then click the X next to the todo. I get this message in the console:

Error: No protocol method WritePort.put! defined for type null:

This is because when the todo is rendered, (get-state owner :comm) is nil. As the React lifecycle docs note, componentWillUpdate is not called for the initial render. Since componentWillUpdate isn't called, the value for :comm doesn't get moved from __om_pending_state to __om_state, either.

When the initial todo list is empty, TodoMVC doesn't exhibit any problems. The change of state triggers a second render (so there are actually two renders when the page is loaded), and in this second render, the todo-app, main, and footer components all re-render, now with the correct comm value.

However, if we have a todo item from the beginning, the second render does not re-render the todo item, because its state hasn't changed. As such, the todo item maintains a stale value of comm as nil in its event handlers.

The situation is summed up by the console log you get if you log the value of comm when each component renders:

05:36:02.684 "Rendering todo-app, comm is" ""
05:36:02.686 "Rendering main, comm is" ""
05:36:02.689 "Rendering a todo item, comm is" ""
05:36:02.691 "Rendering footer, comm is" ""
05:36:02.707 "Rendering todo-app, comm is" "[object Object]"
05:36:02.708 "Rendering main, comm is" "[object Object]"
05:36:02.710 "Rendering footer, comm is" "[object Object]"

I'm not sure what the best fix is here. Maybe special-case set-state! if it's called on a component that has not yet ever been rendered?

TodoMVC example: editing middle of existing todo moves cursor to end of input

In the TodoMVC example linked in the readme, there's a nasty input bug - when you edit an existing todo, if your cursor is anywhere before the end of the input, it will jump to the end after you enter a character.

This is, of course, because the input is getting completely reset on render. I'm currently implementing a widget with an input field in React, the solution I'm going with is:

  1. Store the cursor position in the state tree (the current value is update!ed from the onChange handler)
  2. Use the did-update hook to call .setSelectionRange cursor-pos cursor-pos to reset back to the original position

This works, but isn't ideal, and is certainly some nasty boilerplate to be attaching to every input. Maybe there could be a special om/input component that could handle resetting the cursor on every change?

Clarify name

I have wondered, and others have asked me, about both the pronunciation and meaning of the name "Om":

  • Is it "DOM" without the D and pronounced "ahm"?
  • Is it the Sanskrit mantra and pronounced "ohm"?
  • Something else, or just nothing at all?

It is very important you clear this up so that we can proceed with creating thematically-named projects based on Om.

render being called twice

I'm noticing some weird behavior when setting state in will-mount and trying to use it in render. Logging the value in render shows it first being nil, then being set to what I expect.

My assumption from reading the docs would be that will-mount would be called before the first render, then you can set the state on owner, and then read that state in the first render call.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.