adding to :effects in :before function in re-frame interceptors - interceptor

I have this working piece of code:
(rf/reg-fx :mr-effect (fn [] (js/console.log "I AM MR EFFECT.")))
(def mr-interceptor
(rf/->interceptor
:id :mr-interceptor
:after (fn [context] (assoc-in context [:effects :mr-effect] "some_arg"))))
So, when I attach mr-interceptor to an event handler, then all is good and I can see "I AM MR EFFECT." printed out.
But, as soon as I change :after to :before, like this:
(def mr-interceptor
(rf/->interceptor
:id :mr-interceptor
:before (fn [context] (assoc-in context [:effects :mr-effect] "some_arg"))))
Then the desired side effect does not happen. So, my reasoning is that :before cannot register such side effects to be triggered. But... am I right? Can :before change :effects in any way that's meaningful?

Your conclusion is correct. Event handlers are called in the last interceptor in the chain (it's added automatically by re-frame) and that interceptor uses simple (assoc ctx :effects ...). So it will overwrite whatever you put in :effects during any previous step.

Related

how to use selectRootElement angular 2 Renderer method

what is the use of selectRootElement , when I tried to use this Renderer function
I am getting only root element but it's child not visible on UI .
May I get one example to understand this renderer method ?
Thanks in advance !
what's wrong with using selectRootElement if I only want to grab the element? It returns the element without its children and nothing changes in the view! Well, you can still use it of course, but you will be defeating its purpose and misusing it just like people do with DynamicComponentLoader#loadAsRoot and subscribing manually to EventEmitter.
See this https://stackoverflow.com/a/36059595/6554634 and here see the question also

jQuery like event maps for Backbone listenTo?

I have a view which needs to listenTo the change of a model attribute. When that change event fires, I have to call two different callback functions. I know this can be easily accomplished using .on like
model.on({
'change:title' : updateTitle,
'change:title' : updateSummary
});
But when using listenTo I have to write this in multiple lines
this.listenTo(model, 'change:title', updateTitle);
this.listenTo(model, 'change:title', updateSummary);
Is there a way to avoid having to re-write the listenTo statement multiple times?
Or does this issue mean that my code isn't structured properly and I should rethink my approach?
A simple alternative is that I call updateSummary from updateTitle, but I was just wondering if this can be accomplished using object.listenTo
does this issue mean that my code isn't structured properly and I should rethink my approach?
I don't see any problem having two (or more) listeners registered to the same event. That's a core point of decoupling. You could have different (decoupled) Views that update their contents when the same event is triggered.
I don't know the rest of your code, but I suppose your updateSummary listens to other change events apart from the change:title:
this.listenTo(model, 'change:summary', updateSummary);
In this case you are registering the same listener (updateSummary) to different events (change:title and change:summary). And that's ok, with listenTo you are sort of connecting different view updates to different model events in a decoupled way.
Is there a way to avoid having to re-write the listenTo statement multiple times?
Maybe an overkill, but you could use the functions that underscore provides to have something like this:
_.each([updateTitle, updateSummary], _.partial(this.listenTo, model, 'change:title'), this);
A simple alternative is that I call updateSummary from updateTitle, but I was just wondering if this can be accomplished using object.listenTo
I'm afraid that listenTo only accepts a callback function, not an array of functions.
If you are still worried about having listenTo on the same event multiple times, you can create a more general function, let's say update, that updates both title and summary, and register only this more general update function:
this.listenTo(model, 'change:title', update);
or if you don't want another function, you can use an anonymous one:
this.listenTo(model, 'change:title', function() {updateTitle(); updateSummary();});
As #muistooshort already mentioned in the comment :
var objParam={
'change:title' : updateTitle,
'change:title' : updateSummary
};
model.on(objParam);
See carefully, objParam has 2 keys with the same name, this will give error in strict mode, otherwise will just override the previous value of the key & hence, console.log(objParam) prints
{ 'change:title' : updateSummary }
and this is what passed as argument to the .on call.

Extjs mask() with message

I am able to mask() then unmask() a chart before and after it's rendered with : chart.mask() then chart.unmask().
But I want to add a loading message while it's masked, how can I do it? I'm using Extjs 4.1 with MVC.
Ext.Component (from which chart inherits) and Ext.dom.Element both have a mask method. But Ext.dom.Element's one accepts a msg argument.
That means you can achieve what you want with this kind of code:
chart.el.mask('My message');
Now there are a couple more things to take into account.
The most important is to ensure that the element is defined before calling its mask method. In effect, due to possible load delays or render deferrings, it might happen that some events or methods are called on components that have been destroyed or not yet rendered.
The second thing is that with this method, you won't have the loading CSS class by default. You can pass it as the second argument to mask if you want it; the class is 'x-mask-loading'.
With all that, the code should rather look like this:
var maskEl = chart.getMaskTarget() // this one's marked as private... maybe we shouldn't use it
|| chart.getEl();
if (maskEl) {
maskEl.mask("Loading...", Ext.baseCSSPrefix + 'mask-loading');
}
Unmask similarly. The check that the element still exists will be even more important there, because the chance that the component has been destroyed (e.g. window or tab closed) before the load is done is even greater.

Better structure to watch window level DOM events in Marionette?

In a CompositeView, I implemented infinite scrolling like this
List.Foo extends Marionette.CompositeView
initialize: (collection) ->
#page = 1
$(window).on('scroll', #loadMore)
loadMore: =>
if _nearBottom
#page++
App.vent.trigger('list:foo:near_bottom', #page)
_nearBottom =>
$(window).scrollTop > $(document).height - $(window.height) - 200
# Then I have the controller to process the event "list:foo:near_bottom",
# to ask for adding one more page of data in collection.
The code basically works as expected. But I can't find it satisfactory as I think this ComposteView watches some DOM events outside of its scope, aka, the window level DOM events.
I thought to use a layout to watch such events and broadcast it, but my top level layout seems still not broad enough to cover window/document :)
My question is, what would be a better structure to watch these kinds of window/document level DOM event in Marionette? Thanks!
This question has not been answered for a long time, and I changed implementation in that project so I didn't touch it.
Nguyen's comment provided very nice point and reminds me to review this question.
I also have new understanding similar to Nguyen's point.
Something has to be global, we can't avoid it.
These things include but not limited to:
Route
Page scroll
Page load
Window resize
Global key stroke
...
Backbone has Routes to take care of routing events. The others things are not so important and so popular but they still need to be treated similar to routing.
A better approach would be, in my opinion: Watching the global DOM events at global level, send App event which don't care whoever may be interested in it.
If I re-do this feature, I will do something like this(pseudo code)
# App
App.on "initialize:after", ->
#startHistory()
#navigate('somePath', trigger: true) # Normal steps
App.module('WindowWatcher').start()
# WindowWatcher module
ExampleProject.module "WindowWatcher", (WindowWatcher, App, Backbone, Marionette, $, _) ->
class Watcher
constructor: ->
#watchPageScroll
watchPageScroll: ->
$(window).on('scroll', #_checkScroll)
_checkScroll: ->
if #_nearBottom:
App.vent.trigger(scroll:bottom)
_nearBottom:
$(window).scrollTop > $(document).height - $(window.height) - 200
WindowWatcher.on 'start' ->
new Watcher()
Then List.Foo controller will watch the App event scroll:bottom as he like, and supply next page.
There may be other parts interested in this event, for example in Footer view popping a button saying you are at bottom, or another notification saying if you want to see more you need to sign up, etc. They can also listen to the the App vent without need to manage window level DOM, thanks to the beauty of Marionette.
Important update
If you watch App vents directly inside controller but not at module level, make sure the controller will stop listen to this vent otherwise the listeners will increase in App.vents which is a memory leak.
# FooController
onClose: ->
App.vent.off 'scroll:bottom'

Backbone.js 'swallows' click event if another event triggers a re-render

What I want to achieve is that on form changes, the whole view should be re-rendered. This is to provide a preview of the data just edited, and to hide certain elements in the form when check boxes are ticked.
When the user edits the field and clicks on the button without leaving the filed first two events are fired at the same time: change, click. The change handler first updates the model, which triggers a re-render of the form. When it's the click events turn, nothing happens. I guess it has to do with the re-render because when I comment out the
#model.on 'change', #render, #
Both event handlers are executed as it should be.
Maybe the click handler is not executed because the click target has been removed from dom and a new button has been added? How would I fix this? I was thinking the code I wrote was 'idiomatic' Backbone.js, but I'm still learning :-)
Here is a simplified version of my code showing the problem:
jsbin
Let us add a few things so that we can see what's going on. First we'll mark the Save button with a unique ID:
render: ->
id = "b#{Math.floor(Math.random() * 1000)}"
console.log('button id = ', id)
#...
And then we can see which button was hit:
save: ->
console.log('pressed = ', #$('button').attr('id'))
#...
We'll also add a global click handler to watch the <button> outside of the Backbone stuff:
$(document).on('click', 'button', ->
console.log('global click = ', #id)
)
Live version: http://jsbin.com/oviruz/6/edit
Play around with that version a bit and you might see what is going on:
Change the content of the <input>.
Try to click Save.
As soon as the <input> loses focus, the change event is triggered.
That event calls fieldChanged which does #model.set(...).
The #model.set call triggers Backbone's events, in particular, the #model.on(...) from the view's initialize.
The Backbone event sends us into render which does a #$el.html(...) which replaces both the <input> and the <button>.
The html call kills all the DOM elements inside the view's el. But, and this is a big but, the browser needs to get control again before this process finishes.
Now we're back into the event queue to deal with the click on Save. But the <button> we're clicking is a zombie as the browser's work queue looks like this: deal with the click event, replace the DOM elements from 3.4. Here the work from 3.4 isn't complete so the <button> that you're clicking is half in the DOM and half dead and won't respond to any events.
You have two event queues fighting each other; your Backbone events are changing the DOM behind the browser's back and, since JavaScript is single threaded, the browser is losing and getting confused.
If you delay the #$el.html call long enough to let the browser catch up:
set_html = =>
#$el.html """
<input type="text" id="text" value="#{#model.get('foo')}"/>
<button class="save" id="#{id}">Save</button>
"""
setTimeout(set_html, 1000) # Go higher if necessary.
You'll get the behavior you're expecting. But that's an awful, horrific, nasty, and shameful kludge.
Messing around with the DOM while you're still processing events on those DOM elements is fraught with danger and is little more than a complicated way to hurt yourself.
If you want to validate the field when it changes and bind the view's render to "change" events on the model, then I think you'll have to do the validation by hand and use a silent set call:
fieldChanged: (e) ->
field = #$(e.currentTarget)
#model.set({ foo: field.val() }, { silent: true })
// #model.validate(#model.attributes) and do something with the return value
If you do a #model.save() in the Save button's callback, the silent changes will be validated en mass and sent to the server. Something like this: http://jsbin.com/oviruz/7/edit
Or you skip the #model.set inside fieldChanged and just use #model.validate:
fieldChanged: (e) ->
val = #$(e.currentTarget).val()
// #model.validate(foo: val) and do something with the return value
and leave all the setting stuff for save:
save: ->
#model.save(foo: #$('#text').val())
Something like this: http://jsbin.com/oviruz/8/edit
You can add a little delay before update model in fieldChange, you can replace change event with keyup. There might be many workarounds, but probably best was would be not to re-render whole view on model change.

Resources