I'm working on a web app in Google closure where the structure is something like this:
App
+ Control Pane
| + Search Box
| + Search Button
+ Result Pane
+ Results
+ Next Page Link
The actual component structure is quite a bit more complex. The important point is that there are many different components all over the component tree that can initiate certain actions. In this example, hitting enter in Search Box, pressing Search Button, or hitting Next Page all require a query to be made.
This is simple enough to handle. Any child anywhere in the component tree can do
this.dispatchEvent(App.EventType.ACTION, ...)
App will be able to listen to it when the event propagates upwards. The problem is the other direction: When App receives data from its query, it must push it to all children.
It seems very brittle for App to try to push directly to Search box and Results, as their location in the component tree is subject to change. What I'd like to do is fire an App.EventType.DATA_RECEIVED event and have all children (and sub-children, etc.) hear it.
The only way to do this in google closure that I've been able to find is to make a global public singleton instance of App and use as the source for all App.EventType.DATA_RECEIVED events, or to plumb App through to all children and subchildren.
Both of these are messy and brittle in their own way.
Is there a simple way in closure to dispatch events that bubble downwards?
It's not a very satisfactory answer, but it's what I settled on:
There's no good way to communicate such things down the component tree. Even closure itself bumps into this problem, passing opt_domHelper down the tree to every subcomponent.
My suggestion is to subclass goog.ui.Component for your app and create a myapp.Environment class, which contains both opt_domHelper and other environment variables, such as one event listener designated as the application's event channel.
It's not a good solution, per se, but it's the least of all possible evils. And if you've already been dutifully passing opt_domHelper everywhere, then it's no worse a problem: that plumbing becomes more extensible, and the opt_domHelper itself is hidden from implementors (who now pass around environment instead).
Related
I'm trying to implement a solution to report detailed user activities to Google Analytics. Since the application is a single page application I know that I can capture the page change events from router, but this is only the basic part of the solution which is only for reporting seen pages.
Main point is since this is a large scale application containing 500+ pages/components and I don't want to create a logger service requires to change every page by adding it, I need to figure out a way to determine a centralized or application wide place to catch and detect button click events and maybe the pages they are fired from. Then I hope to be able to report detailed user activities on related pages to analytics like X button clicked from Y page.
For a central place I know there are interceptors for HTTP events, and router for page changes. Besides that I know maybe I can use change detection with hooks, but I'm not sure about should I have to work with hooking into application lifecycle level.
How can I implement a mechanism to catch and detect component elements events with related pages in Angular? Are there any best practices or abstractions that framework provides that may I utilize?
Note: I've learned that Google Tag Manager does the job specifically for the analytics purposes, but my question remain same which how to implement it with Angular.
Custom event API is one option, but you have to despatch that event in all clicks.
So using HostListner we can achieve this.
Add a listener in the main component ie in app.componnet
#HostListener('window:click', ['$event'])
onClickEvent(event: MouseEvent) {
var target = event.target || event.srcElement;
var id = target['id']; //this will be the id of the target that you clicked
this.clickedValue = id;
}
And follow a naming pattern while you adding the id of each item. ie add the component name as the prefix of id. After prod build, the component name will be minified to one-letter name, it loses its original name.
For eg: if you have component mychild.component
<div>
<h1 id="mychild-h1">Test</h1>
</div>
Here, id is myChild-h1 and you can pick the components name from it. Now you have the id and name of the component that you clicked and create a service to log that info.
Attaching one sample code for check
CheckThis
I have an application that displays a google map with places autocomplete controller added to it just like this example from google
when an address is searched and selected, or the map bounds are changed I call algolia search, which has an event of onResult, that is fired when it received a response.
I am trying to turn this all into VUE js components. I have managed to get a google maps component and an autocomplete component.
I load the autocomplete first and then have the mounted section of the google maps attach it as a controller.
Where I start to fall down is the interoperability between the components.
I.E on place change which is an autocomplete event. I need to recentre the map and make the search.
But if they are two different components I can't get a reference to the google map.
when I bring the agolia search in to play, that also needs reference to the map when the event fires to pass the marker to it.
I started trying to use a simple view store, but this seems like I am tightly coupling the components.
Have I missed something or are simple stores and global event buses the way to go?
TL;DR;
Vuex may solve your problem, but need to see more code to know what's going on
There are multiple ways of achieving this, but I'll only list two.
Global State Management (Vuex), and props/listeners
Global State Management (Vuex)
If you know you'll only have one instance of each of the components (one map, one autocomplete) this is easy, fast, and reliable solution. The two components do not need to know about each other, they both deal with the global store. The autocomplete will update the data in the store, and the map will be notified whenever the variables it subscribes to change, and update accordingly.
The downside is...
Using vuex makes it harder to reuse and components.. Once you have more than one instance (ie. two autocompletes and two maps) then you may run into some issues, so you'll need to add additional complexity.
Props/Emit
If the two components have a direct connection, either siblings or parent-child relation, using this interaction (IMHO) is preferred.
The autocomplete component can have an #change or even a v-model set up, that parent component would link to the map component using a prop.
It seems like you may be doing it this way, which is not wrong, but without seeing any code, it's hard to make an assessment.
My question is about the communication between components in the Maquette Javascript framework.
Imagine I have a Menu sub-component used in an Application component: the Application instance would like to know when a menu item is selected in the Menu instance in order to swap the main content displayed in the application (for example). In other words, I need a way to communicate between a child component and its parent component. How can this be achieved in Maquette?
Sure, I can pass a callback owned by the application instance to the menu instance, which will be called when an item is selected. But I'm a bit reluctant to do that because the "selection" event is just a "rendering-side" aspect of the menu component, so I would prefer the event not to leak into my Menu API, but to stay inside the render function/method instead.
So, I would like to deal with events at the "rendering-side". But I guess it means I have to send a CustomEvent from the Menu's render function and register a CustomEvent handler inside the Application's render function, right? Is this use-case supported in maquette? Are there other alternatives to CustomEvent for my use-case?
Thanks!
PS: question reposted here from ticket #71 in order to get more answers.
What we usually do is pass callbacks to components that get invoked when items get clicked. A Menu could be constructed with a callback menuItemClicked(menuItem: MenuItem) for example. Imho this does not leak any application-specific knowledge into the menu/menuitem. The creator of the menu could do anything inside the callback like routing or changing a variable.
An event system is also certainly possible. I do not recommend using the DOM hierarchy to bubble the events though. I can imagine the Application creating the menu using menu = createMenu(...) and afterwards calling menu.addEventListener('itemClicked', navigate) or something.
I am trying to make a tabbed windowing system within a webpage using om-bootstrap's "pills" navigation by adding tabs when links get clicked and removing tabs when an X button on the tabs is clicked.
I need to know how to add and remove data from the global state/store and create a macro that can be used to declare a tab app component and make it remove itself when it is no longer alive.
What is the best way to reference the global state? How can I make a component remove/unmount itself when it gets closed?
Since removal of a subcomponent affects its owner, you should let the owner (i. e. the "tab system") know that this tab needs to be closed/destroyed/obliterated.
I've digged through todomvc example (live) assuming your process of destroying tab panes is pretty much the same as destruction of TODO items there. I see nothing ocnflicting so far. Here are my findings:
A channel is used.... When application starts (IWillMount), a (chan) (from core.async) is written into application state at :comm key.
...for event handling.... Events from the channel are handled in the loop following that code, in go-form, asynchronously with the block it appears in (with <! being a "kinda blocking" operation). Well, you may know it, I didn't, still learning what is CLJS all about.
...that is passed to all child items' init states.... So it becomes a way for children to send events to the root. I'm starting to like this.
...so they can send events to their parent! This is done in put! calls with the comm channel, fetched in the linked line. Events put there are handled by the loop defined in (2), which delegates them to appropriate functions depending on type (accompanying keyword).
I'm nowhere near a ClojureScript pro, but I'm learning. So if the above doesn't make sense, this is normal and means I didn't understand something. If that turns out to be the case, putting me back on track would be much appreciated.
I'm new to Backbone.js and am having trouble figuring out the proper architecture for a model-view relationship.
I have a view that holds an input box and a model that is supposed to take the contents of that input box and send it to the server.
My issue is that I don't always have a discreet DOM event that triggers a request for the view to update the model data, such as input.change. Sometimes the code itself needs to ask the model to send updates to the server.
I've thought of three solutions to this problem so far, I'm not sure if any if them are any good though:
Update the model on the input element's keypress event
Once the view is initialized with the model, have the view update/add a function to the model called 'get_input_value()' that returns the value of the input box
Whenever the application needs to request the model to update the server, first call a function in the view that updates all of the information that the user has typed into the view to the model.
Please bear in mind that this is a simplified example. The view contains child views as well, all of which hold a number of elements that the user can manipulate, the model needs to be updated with all of this information so that it can update the server.
Any help and input is appreciated! Thanks so much!
Edit :::
Base on machineghost's response, I now see that I did not articulate this problem correctly:
There is a DOM event, but the problem is that it doesn't necessarily originate from inside the view that uses the model. It may originate from the Router or another view and be triggered by a global event handler. Additionally, there is not a 1:1 View-Model relationship. This model is used by multiple views who express the model in different ways. So in this case, it seems like the command to update the server should not go through a View, but to the model itself. If that is the case, the model must be able to say "Sync me with my views!".
But I don't know how to do this without breaking the rules and thus creating other problems with architecture...
Ok this is kind of a subjective question, so forgive me if this just seems like me spouting off my two cents. And before I even answer your question, I have to admit I'm a bit skeptical that you:
don't always have a discreet DOM event
because pretty much anything the user can do triggers an event that you can watch for. For instance, if you want to wait until a user changes a text input there's change, but also (as you noted) the various key* events, plus there's blur (which is commonly used for this sort of thing). Between the 3(+) you should always be able to respond appropriately to the user's actions. It would only be if (say) you had to save the text input's contents every 3 seconds that it would truly be independent of DOM events.
So, without knowing your particulars, I just have to point out that something smells fishy there. But anyhow, as for your actual question, here's my take on your ideas:
Update the model on the input element's keypress event
This certainly would work, but just be sure to use the view to do the actual event handling/model setting; hooking up the onKeyPress handler in the model would be a bad idea
Overall this approach seems pretty standard, and fits the Backbone paradigm.
Once the view is initialized with the model, have the view update/add a function to the model called 'get_input_value()' that returns the value of the input box
I don't quite get how this helps your problem, plus it seems to put the concerns in the wrong place: the model should (ideally) have nothing to do with the DOM.
Whenever the application needs to request the model to update the server, first call a function in the view that updates all of the information that the user has typed into the view to the model.
Is the save happening every 5 minutes or something? If not, then it's presumably happening in response to the user's actions, and you should use an event handler to respond.
However, if you truly do need to make the sync independent of user actions, I'd recommend using a custom event to manage things. In other words, in your model's sync method put something like this.trigger('preSync'). Then, every view which uses that model can bind some sort of updateMyModelValue method, ie. this.model.on('preSync', this.updateMyModelValue, this);.
This way, your model code is never directly interacting with the DOM at all; instead, it just worries about the stuff it's supposed to worry about (the data) and the views pay attention for when they need to update that data from the DOM.
Hope that helps.
* EDIT (in response to your editing of your question) *
If that is the case, the model must be able to say "Sync me with my views!".
The general Backbone way for a model to say ... well, pretty much anything to its views is through events.
(Technically you could maintain a list of a model's views in the model itself, and then iterate through that list to tell the views to do things. Backbone is even un-opinionated enough to let you do that. However, from a maintainability standpoint that seems like a terrible approach to me.)
My example of a "presync" event (above) demonstrates how you'd use this technique; comment back if any of it is unclear.
Similarly, if you have an issue of:
View A catches an event
View B needs to do something in response to that event
You basically have two options:
1) You can tightly couple the two views. Let's say have a table view that creates row views, but needs to respond to events that happen in those rows. You can pass the table itself as an option to the row when you create it (new Row({table:this})), and then when those rows need to tell their table "an event happened" they can just do this.options.table.informThatAnEventHappened(). This is a great approach if the two views are inherently related, like a table and its rows. If not, a better approach is:
2) You can use events to communicate between the views. Let's say you have a title div at the top of the page, which needs to be updated whenever a "title" text input changes ... but that text input is way down the page and doesn't conceptually have much to do with the page's title (apart from setting it). The common point between these two elements (and their views) is the data, the text of the title itself.
Now imagine that titleDivView and titleSettingInputView both share a pageTitle model. When titleSettingInputView calls this.model.set('titleText', 'newTitle'), the titleDivView can listen for this.model.on('change:titleText', ...), and re-render itself appropriately in response. In this way two totally un-connected, de-coupled views can interact with each other, without creating a tangled web of inter-related code.
And of course, if there isn't a nice convenient "change:title" event to bind to, you can always make your own, as with the custom "presync" event I described above.