I'm trying to make a generic modal that takes as parameters a text to show and an event to send on the onClick of the accept button.
There is a cancel button, which shares the hiding functionality with the accept button.
So I tried to send 2 events on the onClick of the accept. One for the accept logic and one for the hiding logic. And that's not working, just the first event is working.
I tried another strategy, the hidden event might have an event
OnHideModal (Maybe Msg) so the modal merges both events before the accept and the OnHide hides the modal and sends the internal event to continue the processing.
This seems to me too heavy, because if you have 3 or more logic events all your events must have another Maybe Msg. And I have difficulties making a Cmd Msg with the internal events.
It seems peoples advise is to make a HideAndLogic event but that would lead to a lot of copying and pasting of the hiding code, one for each logic.
Plus the logic would be tied with the hiding logic which would make it harder to understand. And if I want to send an event for the logic without hiding anything I would have to make another event that copies half the logic of the first. That goes against basic programming good practice, so I think some people maybe have found a better way.
A way that allows these separate logics to be really separated even in progenitor events.
Using Elm 0.18
I think you are making this too complicated.
You need:
viewModal : msg -> msg -> Html msg
viewModal acceptMsg cancelMsg =
div [] [ ... button [onclick acceptMsg] ...
... button [onclick cancelMsg] ...
]
Then, your main model needs a shouldShowModal : Bool, or some other way of deciding if it should show the modal.
The modal will trigger a Msg which you will handle in your event loop.
Related
TL/DR: React components have two kinds of code:
rendering code that draws the component, which depends on certain props that affect the component's visual appearance (call them "visual props"), and
event-handling code, e.g., onclick handlers, which depends on certain props that don't affect the component's visual appearance (call them "event props").
When event props change, they cause the component to re-render, even though its appearance doesn't change. The only thing changing is its future event-handling behavior.
What's best practice for removing event props to avoid unnecessary re-renders, while still allowing intelligent event handling?
Longer version
My question is subtly different from this question about how to give handlers to dumb React components; see below for explanation.
I have an application with many React components (hundreds to thousands of SVG elements; it's a CAD application).
There are many "edit modes" in this application (imagine a drawing program like Inkscape): depending on the edit mode, you might want a left-click to select an object, or drag to draw a selection outline rectangle, or do any number of different edits to the component that was clicked, depending on the edit mode.
In my original architecture, every one of these components had the current edit mode as a prop. Each component would use the mode prop to decide what to do in response to events such as clicks: different sorts of Redux actions are dispatched in response to clicks depending on the current mode. This means that every time the user switches the edit mode, every component gets re-rendered, even though none of them change visually. In a large design, it takes several seconds to re-render.
I've altered it to improve performance. Now, each component is dumber: none of them know the edit mode. But this means they don't know what to do in response to a click. In some cases, I solved this by having each dispatch a "dumber" action that says essentially "I was clicked". Middleware intercepts this action, looks up the edit mode in the Redux store, and dispatches an appropriate smart action based on the edit mode. In other cases, I simply let the component dispatch the original action (e.g., Select), even if that action may not be valid for the current edit mode, and similarly rely on the middleware to intercept and stop the action if it is invalid for the current edit mode.
This solution feels inelegant. Now, many more actions get dispatched, even though most of them are thrown away. It's also nothing like what I find in introductions/tutorials to middleware, which mostly talk about how it's good for async stuff (I don't need any of this to be asynchronous since these actions generally are not talking to the network or files) and side-effects such as logging (no side-effects here; I simply want a user interaction to trigger a normal Redux action to be dispatched).
I feel as though a better solution would be to access the Redux store as a global variable within event handling code. I know this is emphatically not safe to do with rendering code, since it breaks the rule "React views should be a deterministic function of their props and state". But it feels safer to do with event-handling code.
I realize it's common with "very dumb" React components to pass click handlers in as a prop (e.g., this stackoverflow answer), but I don't see this as a solution. If handler has the edit mode encoded in it as a bound value, then the handler itself needs to change when the edit mode changes, which, since the handler is a prop, requires re-rendering the component. So I think this issue I'm describing is orthogonal to whether the handler is passed into the component as a prop, or written specifically for the component.
So to summarize, there's three options I see:
Pass all data required for intelligent event handling as props. (causes unnecessary re-renders)
Have React components dispatch actions "promiscuously", and rely on middleware (which has access to the Redux store) to stop and/or transform the action if necessary. (As I implemented it, is harder to understand, and puts lots of unrelated application logic in one place, where it feels like it doesn't belong. Also makes for a messier Redux history of actions, making it harder to debug using Redux DevTools, and is not a pattern I've seen in any documentation/tutorial on Redux middleware.)
Allow event handler code (unlike rendering code) to access the Redux store as a global variable, to make intelligent decisions about what action to dispatch. (Seems okay, but scares me to use global variables in this way, and I'm worried that it could cause a problem I'm not seeing.)
Is there a fourth option I'm missing?
I have an idea for how to solve this in a way that feels close to the Redux spirit. (Though I still lean towards accessing global variables in event handlers to solve the problem.)
Redux has some notion of "action creators", which is a function that returns an action object. This always seemed like an unnecessary layer of abstraction to me. But perhaps a similar idea can be used here. (I use Dart, not Javascript, so the code below is Dart; hopefully the answer makes sense.)
The idea is to have a new type of action in called ActionCreator<A extends Action> (subtype of Action). An ActionCreator<A> is an object with a method of type
A create(AppState state)
In other words, it takes the whole AppState and returns an Action. This lets it do the necessary data lookups. As an object, it can contain fields that describe data gathered from the code (usually View event handler code) that instantiated it. For example, it could reference a Selectable to select. create() returns either null or some special value to indicate that the action should be thrown away.
For example, if we have a click handler, we'd dispatch an ActionCreator
class Select {
final Item item_clicked;
Select(this.item_clicked);
}
class ClickedAction implements ActionCreator<Select> {
final Item item_clicked;
ClickedAction(this.item_clicked);
Select create(AppState state) =>
state.ui_state.select_mode_is_on ? Select(this.item_clicked) : null;
}
// ...
onClick = (event) {
props.dispatch(ClickedAction(props.item));
}
And in middleware, once we have access to the full state, this can be turned into a concrete action, but only if it's legal. But the nice thing is that the next piece of code is generic and handles any such ActionCreator, so I wouldn't have to remember to keep editing this code whenever I create a new Action that needs to be "conditionally dispatched".
action_creator_middleware(Store<AppState> store, action, NextDispatcher next) {
if (action is ActionCreator) {
var maybe_action = action.create(store.state);
if (maybe_action != null) {
dispatch(maybe_action);
}
} else {
next(action);
}
}
The disadvantage of this is that it's still dispatching many more actions than we really need; most will get thrown away. It's a "cleaner" implementation of what I need, but I still think that for asynchronous event handlers, access the Redux store as a global variable is probably perfectly fine. I don't see in that any of the problems one would expect if the view code went outside of its React props and accessed global variables.
From https://docs.ckeditor.com/ckeditor4/docs/#!/api/CKEDITOR.editor-event-change
If it is important not to get the change event fired too often, you should compare the previous and the current editor content inside the event listener. It is not recommended to do that on every change event.
I don't understand the above ... is there possibly a typo?
Is it trying to say that a call such as checkDirty on every change event could result in poor performance?
Would using an interval timer be a good approach to limit the number of checkDirty calls made.
For example, do not call checkDirty unless at least 1 second had passed from its last invocation?
The use case I have is enabling or disabling a save button based on the editor content differing from its last saved state.
I deal with this situation by always having the save button enabled and:
1) Every time a user presses the save button, I alert him that the contents have been saved and I clear the dirty flag:
CKEDITOR.instances.editor1.resetDirty();
2) I register an onbeforeunload event handler to alert him if he tries to exit the webpage having unsaved data:
window.onbeforeunload = function() {
if (CKEDITOR.instances.editor1.checkDirty()) {
return 'You will lose the changes made in the editor if you don't save them.';
}
}
I have an app that has several different types of form elements which all post data to the server with jQuery AJAX.
What I want to do is:
Show a loader during AJAX transmission
Prevent the user from submitting twice+ (clicking a lot)
This is easy to do on a one off basis for every type of form on the site (comments, file upload, etc). But I'm curious to learn if that is a more global way to handle this?
Something that's smart enough to say:
If a form is submitting to the server and waiting for a response, ignore all submits
Show a DISABLED class on the submitted / clicked item
Show a loading class on the class="spinner" which is closest to the submit item clicked
What do you think? Good idea? Done before?
Take a look at the jQuery Global Ajax Event Handlers.
In a nutshell, you can set events which occur on each and every AJAX request, hence the name Global Event Handlers. There are a few different events, I'll use ajaxStart() and ajaxComplete() in my code sample below.
The idea is that we show the loading, disable the form & button on the ajaxStart() event, then reenable the form and hide the loading element inside the ajaxComplete() event.
var $form = $("form");
$form.ajaxStart(function() {
// show loading
$("#loading", this).show();
// Add class of disabled to form element
$(this).addClass("disabled");
// Disable button
$("input[type=submit]", this).attr("disabled", true);
});
And the AJAX complete event
$form.ajaxComplete(function() {
// hide loading
$("#loading", this).hide();
// Remove disabled class
$(this).removeClass("disabled");
// Re-enable button
$("input[type=submit]", this).removeAttr("disabled");
});
You might need to attach to the ajaxError event as well in case an AJAX call fails since you might need to clean up some of the elements. Test it out and see what happens on a failed AJAX request.
P.S. If you're calling $.ajax or similar ($.getJSON), you can still set these events via $.ajaxStart and $.ajaxComplete since the AJAX isn't attached to any element. You'll need to rearrange the code a little though since you won't have access to $(this).
I believe you have to do 2 for sure and 3 to improve usability of your app. It is better to keep backend dumb but if you have a security issue you should handle that too.
I've been working on Chrome Extension for a website for the past couple of days. It's coming along really nicely but I've encountered a problem that you might be able to help with.
Here's an outline of what the extension does (this functionality is complete):
A user can enter their username and password into the extensions popup - and verify their user account for the particular website
When a user browses http://twitter.com a content script is dynamically included that manipulates the DOM to include an extra button next to each tweet displayed.
When a user clicks this button they are presented with a dialog box
I've made a lot of progress but here is my problem:
When a user visits Twitter the content script is activated and all tweets on the page get my new button - but if the user then clicks 'More...' and dynamically loads the next 20 tweets... these new additions to the page DOM do not get affected by the content script (because it is already loaded).
I could add an event listener to the 'More...' button so it then triggers the original content script again (and adds the new button) but i would have to predict the length of twitter's ajax request response.
I can't tap into their Ajax request that pulls in more tweets and call my addCurateButton() function once the request is complete.
What do you think is the best solution? (if there is one)
What you want to do is to re-execute your content-script every time the DOM is changed. Luckily there is an event for that. Have a look at the mutation event called DOMNodeInserted.
Rewrite your content script so that it attaches an event listener to the body of the DOM for the DOMNodeInserted event. See the example below:
var isActive = false;
/* Your function that injects your buttons */
var inject = function() {
if (isActive) {
console.log('INFO: Injection already active');
return;
}
try {
isActive = true;
//inject your buttons here
//for the sake of the example I just put an alert here.
alert("Hello. The DOM just changed.");
} catch(e) {
console.error("ERROR: " + e.toString());
} finally {
isActive = false;
}
};
document.body.addEventListener("DOMNodeInserted", inject, false);
The last line will add the event listener. When a page loads the event is triggered quite often so you should define a boolean (e.g. var isActive), that you initialize to false. Whenever the inject function is run check whether isActive == true and then abort the injection to not execute it too often at the same time.
Interacting with Ajax is probably the hardest thing to coax a content script to do, but I think you’re on the right track. There are a couple different approaches I’ve taken to solving this problem. In your case, though, I think a combination of the two approaches (which I’ll explain last) would be best.
Attach event listeners to the DOM to detect relevant changes. This solution is what you’ve suggested and introduces the race condition.
Continuously inspect the DOM for changes from inside a loop (preferably one executed with setInterval). This solution would be effective, but relatively inefficient.
The best-of-both-worlds approach would be to initiate the inspection loop only after the more button is pressed. This solution would both avoid the timing issue and be efficient.
You can attach an event-handler on the button, or link that is used for fetching more results. Then attach a function to it such that whenever the button is clicked, your extension removes all the buttons from DOM and starts over inserting them, or check weather your button exists in that particular class of DOM element or not and attach a button if it doesn't.
In grails, I use the mechanism below in order to implement what I'd call a conditional server-side-triggered dialog: When a form is submitted, data must first be processed by a controller. Based on the outcome, there must either be a) a modal Yes/No confirmation in front of the "old" screen or b) a redirect to a new controller/view replacing the "old" screen (no confirmation required).
So here's my current approach:
In the originating view, I have a <g:formRemote name="requestForm" url="[controller:'test', action:'testRequest']", update:"dummyRegion"> and a
<span id="dummyRegion"> which is hidden by CSS
When submitting the form, the test controller checks if a confirmation is necessary and if so, renders a template with a yui-based dialog including Yes No buttons in front of the old screen (which works fine because the dialog "comes from" the dummyRegion, not overwriting the page). When Yes is pressed, the right other controller & action is called and the old screen is replaced, if No is pressed, the dialog is cancelled and the "old" screen is shown again without the dialog. Works well until here.
When submitting the form and test controller sees that NO confirmation is necessary, I would usually directly redirect to the right other controller & action. But the problem is that the corresponding view of that controller does not appear because it is rendered in the invisble dummyRegion as well. So I currently use a GSP template including a javascript redirect which I render instead. However a javascript redirect is often not allowed by the browser and I think it's not a clean solution.
So (finally ;-) my question is: How do I get a controller redirect to cause the corresponding view to "break out" of my AJAX dummyRegion, replacing the whole screen again?
Or: Do you have a better approach for what I have in mind? But please note that I cannot check on the client side whether the confirmation is necessary, there needs to be a server call! Also I'd like to avoid that the whole page has to be refreshed just for the confirmation dialog to pop up (which would also be possible without AJAX).
Thanks for any hints!
I know, it's not an "integrated" solution, but have you considered to do this "manually" with some JS library of your choice (my personal choice would be jQuery, but any other of the established libraries should do the trick)? This way you wouldn't depend on any update "region", but could do whatever you want (such as updating any DOM element) in the response handler of the AJAX request.
Just a thought. My personal experience is that the "built-in" AJAX/JS stuff in Grails often lacks some flexibility and I've always been better off just doing everything in plain jQuery.
This sounds like a good use-case for using web flows. If you want to show Form A, do some kind of check, and then either move onto NextScreen or show a Dialog that later redirects to NextScreen, then you could accomplish this with a flow:
def shoppingCartFlow = {
showFormA {
on("submit") {
if(needToShowDialog())return
}.to "showNextScreen"
on("return").to "showDialog"
}
showDialog {
on("submit").to "showNextScreen"
}
showNextScreen {
redirect(controller:"nextController", action:"nextAction")
}
}
Then you create a showDialog.gsp that pops up the dialog.
--EDIT--
But, you want an Ajax response to the first form submit, which WebFlow does not support. This tutorial, though, will teach you how to Ajaxify your web flow.