I am building a dashboard using mithril components. The dashboard consist of generic widget components which nest more specific components such as twitter widgets, sales widgets etc. The generic widget is agnostic of the nested component.
Now I have a requirement to compliment the generic widget from the nested component. Eg, the generic widget has a toolbar with common operations with delete & refresh, I want to add inner component specific operations such as change date range in case of sale widget, change account in case of twitter widget.
Straight forward approach to this is decompose the inner component is to 2 subcompoents a toolbar and the content, eg sales_toolbar_component & sales_content_component. Both these subcomponnets needs the same data loaded via ajax, hence it will lead to duplication. An approach is to follow Hybrid architecture (http://mithril.js.org/components.html#hybrid-architecture) where a top level component will do the ajax calls and pass data at constructor of each sub component. However in this case the parent component is generic is unaware of child data requirements
Another approach is for one sub-component to do the ajax call and notify the sibling via observer pattern, this seems quite a bit of internal coupling/dependency
Another consideration was to create a component that can have multiple views & for the parent to render each view as required using the same controller instance.Something like:
//create a component whose controller and view functions receive some arguments
var component = m.component(MyComponent, {name: "world"}, "this is a test")
var ctrl = new component.controller() // logs "world", "this is a test"
m.component(MyComponent.toolbar(ctrl));
m.component(MyComponent.content(ctrl));
None of these seems complete, is there a reference pattern I can consider?
The memoization pattern might suit you. Memoization involves wrapping a function - in this case the AJAX request - such that the first call with any given input triggers the underlying function, but its return value is stored in an internal cache; subsequent calls with the same input retrieve the cached value without touching the underlying function. Here's a simple implementation1 and a memoized AJAX requesting function that wraps Mithril's m.request:
function memoize( fn ){
var cache = {}
return function memoized( input ){
if( !( input in cache ) )
cache[ input ] = fn( input )
return cache[ input ]
}
}
var get = memoize( function( url ){
return m.request( { method : 'GET', url : url } )
} )
This way whichever components executes first will make the request; the next component (which will execute immediately afterwards) will retrieve the same value.
Regarding the idea of having a multi-view component, this isn't really any different in practical terms from your original proposal. Consider the following code:
var Wrapper = {
controller : function(){
// AJAX or whatnot
},
view : function( ctrl ){
return m( '.Wrapper',
m( Toolbar, ctrl )
m( Content, ctrl )
)
}
}
m( Wrapper, { name : 'world' }, 'This is a test' )
Here, I used the reference Wrapper instead of MyComponent, and the Toolbar and Content are just components as originally proposed. The wrapper isn't generic, but neither was MyComponent. I find that trying to reduce individual units of Mithril code to components where possible - even if you'd rather have less components in an ideal world - generally makes code much easier to maintain, because whereas you may end up with many context-specific modules instead of a few highly configurable generic modules, all of these context-specific modules are generic in the way they're called, which is much more useful in terms of code predictability.
Having said that, it would be possible to refine your idea of a pattern for passing one controller to multiple views. Again, I would reduce this pattern to component form, so that we can deal with the complications internally but expose an interface that's consistent across Mithril:
var MultiView = {
controller : function( controller ){
return new controller()
},
view : function( ctrl ){
var views = [].slice.call( arguments, 2 )
return m( 'div',
view.map( function( view ){
return view( ctrl )
} )
)
}
}
m( MultiView, function controller(){ /* AJAX or whatnot */ }, Toolbar.view, Content.view )
1 This memoization function will work for any function which accepts a single string argument, which is perfect for AJAX requests. If you ever want a more comprehensive memoization solution, check out funes.
Related
I'm trying to use native web components for one of my UI project and for this project, I'm not using any frameworks or libraries like Polymer etc. I would like to know is there any best way or other way to communicate between two web components like we do in angularjs/angular (like the message bus concept).
Currently in UI web-components, I'm using dispatchevent for publishing data and for receiving data, I'm using addeventlistener.
For example, there are 2 web-components, ChatForm and ChatHistory.
// chatform webcomponent on submit text, publish chattext data
this.dispatchEvent(new CustomEvent('chatText', {detail: chattext}));
// chathistory webcomponent, receive chattext data and append it to chat list
this.chatFormEle.addEventListener('chatText', (v) => {console.log(v.detail);});
Please let me know what other ways work for this purpose. Any good library like postaljs etc. that can easily integrate with native UI web components.
If you look at Web Components as being like built in components like <div> and <audio> then you can answer your own question. The components do not talk to each other.
Once you start allowing components to talk directly to each other then you don't really have components you have a system that is tied together and you can not use Component A without Component B. This is tied too tightly together.
Instead, inside the parent code that owns the two components, you add code that allows you to receive events from component A and call functions or set parameters in Component B, and the other way around.
Having said that there are two exceptions to this rule with built in components:
The <label> tag: It uses the for attribute to take in an ID of another component and, if set and valid, then it passes focus on to the other component when you click on the <label>
The <form> tag: This looks for form elements that are children to gather the data needed to post the form.
But both of these are still not TIED to anything. The <label> is told the recipient of the focus event and only passes it along if the ID is set and valid or to the first form element as a child. And the <form> element does not care what child elements exist or how many it just goes through all of its descendants finding elements that are form elements and grabs their value property.
But as a general rule you should avoid having one sibling component talk directly to another sibling. The methods of cross communications in the two examples above are probably the only exceptions.
Instead your parent code should listen for events and call functions or set properties.
Yes, you can wrap that functionality in an new, parent, component, but please save yourself a ton of grief and avoid spaghetti code.
As a general rule I never allow siblings elements to talk to each other and the only way they can talk to their parents is through events. Parents can talk directly to their children through attributes, properties and functions. But it should be avoided in all other conditions.
Working example
In your parent code (html/css) you should subscribe to events emited by <chat-form> and send event data to <chat-history> by execute its methods (add in below example)
// WEB COMPONENT 1: chat-form
customElements.define('chat-form', class extends HTMLElement {
connectedCallback() {
this.innerHTML = `Form<br><input id="msg" value="abc"/>
<button id="btn">send</button>`;
btn.onclick = () => {
// alternative to below code
// use this.onsend() or non recommended eval(this.getAttribute('onsend'))
this.dispatchEvent(new CustomEvent('send',{detail: {message: msg.value} }))
msg.value = '';
}
}
})
// WEB COMPONENT 2: chat-history
customElements.define('chat-history', class extends HTMLElement {
add(msg) {
let s = ""
this.messages = [...(this.messages || []), msg];
for (let m of this.messages) s += `<li>${m}</li>`
this.innerHTML = `<div><br>History<ul>${s}</ul></div>`
}
})
// -----------------
// PARENT CODE
// (e.g. in index.html which use above two WebComponents)
// Parent must just subscribe chat-form send event, and when
// receive message then it shoud give it to chat-history add method
// -----------------
myChatForm.addEventListener('send', e => {
myChatHistory.add(e.detail.message)
});
body {background: white}
<h3>Hello!</h3>
<chat-form id="myChatForm"></chat-form>
<div>Type something</div>
<chat-history id="myChatHistory"></chat-history>
+1 for both other answers, Events are the best because then Components are loosly
coupled
Also see: https://pm.dartus.fr/blog/a-complete-guide-on-shadow-dom-and-event-propagation/
Note that in the detail of a Custom Event you can send anything you want.
Event driven function execution:
So I use (psuedo code):
Elements that define a Solitaire/Freecell game:
-> game Element
-> pile Element
-> slot Element
-> card element
-> pile Element
-> slot Element
-> empty
When a card (dragged by the user) needs to be moved to another pile,
it sends an Event (bubbling up the DOM to the game element)
//triggered by .dragend Event
card.say(___FINDSLOT___, {
id,
reply: slot => card.move(slot)
});
Note: reply is a function definition
Because all piles where told to listen for ___FINDSLOT___ Events at the game element ...
pile.on(game, ___FINDSLOT___, evt => {
let foundslot = pile.free(evt.detail.id);
if (foundslot.length) evt.detail.reply(foundslot[0]);
});
Only the one pile matching the evt.detail.id responds:
!!! by executing the function card sent in evt.detail.reply
And getting technical: The function executes in pile scope!
(the above code is pseudo code!)
Why?!
Might seem complex;
The important part is that the pile element is NOT coupled to the .move() method in the card element.
The only coupling is the name of the Event: ___FINDSLOT___ !!!
That means card is always in control, and the same Event(Name) can be used for:
Where can a card go to?
What is the best location?
Which card in the river pile makes a Full-House?
...
In my E-lements code pile isn't coupled to evt.detail.id either,
CustomEvents only send functions
.say() and .on() are my custom methods (on every element) for dispatchEvent and addEventListener
I now have a handfull of E-lements that can be used to create any card game
No need for any libraries, write your own 'Message Bus'
My element.on() method is only a few lines of code wrapped around the addEventListener function, so they can easily be removed:
$Element_addEventListener(
name,
func,
options = {}
) {
let BigBrotherFunc = evt => { // wrap every Listener function
if (evt.detail && evt.detail.reply) {
el.warn(`can catch ALL replies '${evt.type}' here`, evt);
}
func(evt);
}
el.addEventListener(name, BigBrotherFunc, options);
return [name, () => el.removeEventListener(name, BigBrotherFunc)];
},
on(
//!! no parameter defintions, because function uses ...arguments
) {
let args = [...arguments]; // get arguments array
let target = el; // default target is current element
if (args[0] instanceof HTMLElement) target = args.shift(); // if first element is another element, take it out the args array
args[0] = ___eventName(args[0]) || args[0]; // proces eventNR
$Element_ListenersArray.push(target.$Element_addEventListener(...args));
},
.say( ) is a oneliner:
say(
eventNR,
detail, //todo some default something here ??
options = {
detail,
bubbles: 1, // event bubbles UP the DOM
composed: 1, // !!! required so Event bubbles through the shadowDOM boundaries
}
) {
el.dispatchEvent(new CustomEvent(___eventName(eventNR) || eventNR, options));
},
Custom Events is the best solution if you want to deal with loosely coupled custom elements.
On the contrary if one custom element know the other by its reference, it can invoke its custom property or method:
//in chatForm element
chatHistory.attachedForm = this
chatHistory.addMessage( message )
chatHistory.api.addMessage( message )
In the last example above communication is done through a dedecated object exposed via the api property.
You could also use a mix of Events (in one way) and Methods (in the other way) depending on how custom elements are linked.
Lastly in some situations where messages are basic, you could communicate (string) data via HTML attributes:
chatHistory.setAttributes( 'chat', 'active' )
chatHistory.dataset.username = `$(this.name)`
I faced the same issue and as I couldn't find any fitting library I decided to write one on my own.
So here you go: https://www.npmjs.com/package/seawasp
SeaWasp is a WebRTC data layer which allows communication between components (or frameworks etc).
You simply import it, register a connection (aka tentacle ;) ) and you can send and receive messages.
I'm actively working on it so if you have any feedback /needed features, just tell me :).
For the case where the parent and child know about each other, like in a toaster example.
<toaster-host>
<toast-msg show-for='5s'>Success</toast-msg>
</toaster-host>
Lots of options but for:
Parent passing data to the child -> attributes or observedAttributes for primitives. If complex objects need to be passed either expose a function or a property aka domProperty that can be set. If a domProperty needs to react to being updated, it can be wrapped in a proxy.
Child passing data to parent -> can use events, or can query for the parent using .closest('toaster-host') and call a function or set a property. I prefer to query and call a function. Typescript helps with this type of approach.
In cases like the toaster example, the toaster-host and the toast-item will always be used together, so the argument about loose coupling is academic at best. They are different elements mainly because they have different jobs. If you wanted to swap out implementations of the toast-msg you could do that when you define the custom element, or even by changing the import statement to point to a different file.
What's the correct approach to accessing a component in an event handler? Or, what should I be doing instead?
I have a UI section that is essentially a div-button. When the user clicks it, then the "button" is replaced with an input. When the user is finished inputting, it goes back to a "button".
Because I'm porting this from Backbone, I was using jQuery to flip visibility on the two pieces and set focus on the input. After reading about how config functions in components are given the component, I was wondering if I should be given the component's DOM element, or take another approach entirely (maybe conditional within m() functions?).
{
controller: function () {
this.clickPlus = () => {
$('#newActivityPlusIcon').hide()
$('#newActivityPlaceholder').css('background-color', 'lightgray')
const $newActivityInput = $('#newActivityInput')
$newActivityInput.show()
$newActivityInput.focus()
}
this.keyUpInput = (event) => {
//If ESC key pressed
if (event.keyCode === 27) {
const $newActivityInput = $('#newActivityInput');
$newActivityInput.hide()
$newActivityInput.val('')
$('#newActivityPlaceholder').css('background-color', '')
$('#newActivityPlusIcon').show()
}
}
},
view: (ctrl) => {
return m('section', {id: 'newActivity'}, [
m('article', {id: 'newActivityPlaceholder', class: 'activityBox', onclick: ctrl.clickPlus}, [
m('span', {id: 'newActivityPlusIcon'}, '+'),
m('input', {id: 'newActivityInput', placeholder: 'type name', onkeyup: ctrl.keyUpInput}),
])
])
}
}
The crucial practical difference between Mithril & Backbone components is that Mithril views are written in Javascript. Some of the old best practices of web MVC are turned on their head as a result of the opportunities this opens up:
You can easily express conditional logic in views themselves. As Bryce shows, ternary operations ( condition ? true : false ) are a great way of making a view self-complete: in other words, the view function can express all possible states for the DOM, and everything it needs to do.
You can also define event handlers and element bindings in the view itself, meaning you no longer need the controller to be aware of the structure of the view, or define view logic. This enables you to write controllers whose sole function is to define the state and holistic actions of a component.
The special config property is only necessary if you absolutely need to pass a reference to a DOM element to the controller or another component - which is rarely the case - or to trigger special logic like animation the first time the element is rendered. In this case, it isn't necessary.
In the code below, I've managed to isolate 2 properties, input and active, and one action, cancel, that resets those properties. These describe all the state the component needs. The rest is purely the view's concern.
The view decides how to read from and write to these properties as dictated by the concerns of the DOM API. For example, how to read an input's value is a concern of the input itself: likewise, determining that the keyCode 27 signifies 'esc' is DOM logic and can be kept separate from the controller logic of changing state.
Separating these concerns becomes useful when UX and business requirements change: as an example, I've taken the arbitrary decision to make clicking off or tabbing away from the input also clear the input and loose active state. This doesn't require adding an extra action in the controller - we bind the same cancel function to the onblur event handler.
Other small tweaks:
Expressing the view as an arrow function looses the return keyword, which IMO makes it easier to read the view as a single declarative statement.
We can loose the IDs, since we no longer need to reference the view from the controller: all our state bindings are made directly in the view itself.
Static attributes like class and placeholder can be expressed directly in the selector string. This makes it clear that these attributes are always the same: now the DOM attributes object is only invoked for dynamic code - functions and conditional properties.
Array brackets to group children are redundant: you can simply insert the children directly one after the other.
https://jsbin.com/xopinoy/edit?js,output
{
controller: function () {
this.active = m.prop( false )
this.input = m.prop( '' )
this.cancel = () => {
this.active( false )
this.input( '' )
}
},
view: ctrl =>
m( 'section',
m( 'article.activityBox', {
onclick: event =>
ctrl.active(true),
style : {
background :
ctrl.active()
? 'lightgrey'
: ''
}
},
ctrl.active()
? m( 'input[placeholder="type name"]', {
value: ctrl.input(),
oninput: event =>
ctrl.input( event.target.value ),
onkeyup: event => {
if( event.keyCode === 27 )
ctrl.cancel()
},
onblur: ctrl.cancel
} )
: m( 'span', '+' )
)
)
}
Personally, I would expect to go with a conditional which means that the DOM only contains the element that's currently pertinent.
A rough JSBin example of how I think I'd structure this: http://jsbin.com/razisicelo/edit?html,js,output
The use of ctrl.inputValue is just illustrative.
Let us say that I have the following simplified and generic view model, which uses KnockoutJS and the Knockout Validation library to create/manipulate and validate observables, respectively.
// view declared
// KnockoutJS loaded
// KnockoutValidation loaded
var ChildNodePropertiesVM = function(properties) {
var self = this;
/* data within properties object are assigned to
observables within VM */
};
var ChildNode = function() {
var self = this;
var options = // data from ajax request sent to web service
// list of POJOs
this.availableParentNodes = ko.observableArray();
// a specific POJO from the list above, selected from an HTML select element
// Knockout Validation ensures that a value is present (required)
this.associatedParentNode = ko.observable().extend({
required: {
params: true,
message: "Please choose a parent"
}
});
// a view model, declared above, constructed with value from options
this.childProperties = new ChildNodePropertiesVM(options.childProps);
/* Many more model-members follow, and may be any of the above types
(observable, observableArray, view model, etc) */
};
// apply bindings to view here (in this case, ko.applyBindingsWithValidation)
What I am interested in is, if I want to create a "reset" procedure to clear a majority of the members of a view model (of which there may be many), and keep a minority of the fields as they were, what is the most maintainable way in which I could do so? To be more specific, consider the availableParentNodes object to be one of the observables that I would like to keep, while associatedParentNode must be cleared (as in, self.associatedParentNode(undefined)), and half of the (undeclared) model-members of childProperties must be set to null, as part of the reset process.
SO and search engine queries either suggest that each observable/variable be cleared manually, or that a new view model (in this case, the ChildNode view model) simply be created to replace the old one. Since I have specific fields that I would like to keep, the latter is not an option, and since there are so many observables in these models, the former is unmaintainable.
An option would be to create a generic reset function, with a whitelist parameter for the observables you would like to keep. Here's an example (polyfill indexOf if you need to support outdated browsers):
var reset = function ( obj, whitelist ) {
for ( var prop in obj ) {
if ( obj.hasOwnProperty( prop ) && ko.isObservable( obj[ prop ] ) && whitelist.indexOf( prop ) === -1 ) {
obj[ prop ]( undefined );
}
}
};
Then you can curry this function with your whitelist on your individual models.
SomeModel.prototype.reset = function () {
reset( this, [ 'something', 'somethingElse' ] );
};
The above would set every observable except the ones named something and somethingElse in your model to undefined.
Here's a JSFiddle example.
this could be done in a simple way by creating a function
self.Reset = function(){
self.someobservable(null)
self.someobservbleArray([])
}
And now simply call it
self.Reset()
The values will be reset.
I'm using a Backbone.js to keep track of the state in a visualization application.
The model has attributes such as:
indicatorX : "income"
indicatorY : "emissions"
indicatorXScale : "lin"
indicatorYScale : "log"
year : 1980
layout : {leftPanel : {[...]}, rightPanel : {[...]}}
1. What is a good way of handling "dependent attributes" in a Backbone.js model?
For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
2. How can I handle "lingering" model attributes? Example:
The model contains this:
indicatorX : "income"
indicatorXScale : "log"
If only indicatorX is set on the model, the scale should be set to the default:
model.set( {indicatorX : "emissions"} )
if("indicatorX" in changedAttrs){
indicatorXScale = dataSource[indicatorX].defaultScale
}
What if however the user wants to override the default scale which is "lin" in the case of the "emissions" indicator?
model.set( {indicatorX : "emissions", indicatorXScale : log} )
As the model attribute indicatorXScale already is set to "log", the changed attribute is not recorded. How can I then make sure that the defaultScale is not loaded in this case - but instead the one passed to the model?
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
In this way controllers can listen for one attribute instead of specifying handlers for combinations of attributes. These are the alternatives:
Alt 1. Controller has handlers for specific attributes:
this.model.bind("change:year", this.render);
this.model.bind("change:layout", this.updateLayout);
Alt 2. Controller has handler for model change and render() figures out what to do:
this.model.bind("change", this.render);
render() {
var changedAttributes = this.model.changedAttributes
if (a,b && c in changedAttributes) x()
if (a,d in changedAttributes) y()
}
Alt 3. Let model describe what a combination of attribute changes signify:
this.model.bind("change:action", this.render);
render() {
var changedAttributes = this.model.changedAttributes
var action = this.model.get("action")
if (action == gui_changeIndicator) x()
if (action == gui_opacity) y()
}
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Is it for example expensive to perform isEqual on the layout state that I try to keep in my model? Also, when setting the model, objects are passed by reference, so it better be a new object for the comparison to work?
1. What is a good way of handling "dependent attributes" in a Backbone.js model? For example, when changing the indicatorX attribute, I'd also like the model to update the indicatorXScale attribute.
IMHO, extend the model and bind into the change events. For example:
MyModel = Backbone.Model.extend({
initialize: function() {
this.bind('change:width', this.updateArea);
this.bind('change:height', this.updateArea);
this.updateArea();
},
updateArea: function () {
this.area = this.get('width') * this.get('height');
}
});
var model = new MyModel({height: 10, width: 10});
console.log(model.area); //100
model.set({width: 15});
console.log(model.area); //150
This is pretty basic, but change events are called per key and as a whole 'change'.. so you can bind into certain changes and update as necessary. If it's a large model with lots of keys that are updated intermittently this is definitely the way to go. If it's just those two keys.. well.. you could probably just bind to the regular ol' change event once.
2. How can I handle "lingering" model attributes?
Override the set method and add in some of your own code. Example:
MyModel = Backbone.Model.extend({
constructor: function (obj) {
this.touched = {}; //Set before the prototype constructor for anything being set
Backbone.Model.prototype.constructor.call(this, obj);
//set after for only things that the program has updated.
return this;
},
set: function(attributes, options) {
if(attributes.keyIWantToListenFor !== undefined && !this.touched.keyIWantToListenFor) {
console.log("keyIWantToListenFor was set, let's fire off the right methods");
this.touched.keyIWantToListenFor = true;
}
Backbone.Model.prototype.set.call(this, attributes, options);
return this;
}
});
var model = new MyModel({height: 10, width: 10});
model.set({keyIWantToListenFor: 15});
This keeps absolute "has the key been set at all" on the model. It may not be quite specific enough for your needs, but it does work.. so feel free to use and hack away at it.
3. Is it a good idea to let the model use an extra attribute "action" to describe changes in the model?
The way that the Backbone folks have it set up is that, as you already know, change:key is specifically for the change event on a certain key. By relying on a change:action you're kind of adding 'gotcha!'s to your code. I don't see how the other two methods are any better than the first, especially considering now you have logic thrown into an event listener to determine what to fire off.. instead of just attaching that code directly to the appropriate listeners. Given a choice, I'd stick with the first one - it is a clear "This key has updated, so we're going to do X". Not a "something has updated so let's go figure out what it is!" and potentially have to go through a dozen if statements or switches.
4. Are there any pitfalls to watch out for when using objects as attributes in a Backbone.js model?
Well, isEqual performs a deep comparison.. so you're running the risk of doing all of that comparison code plus the risk of recursion. So, yes, that could certainly be a pitfall if you're doing it a number of times.
The object by reference is certainly an issue - I've got a nice little hole in the wall where I've put my head through a few times wondering why something changed in a completely unrelated.. oh wait..
To remedy this a bit, you could override the get method to, in cases where it is returning an object, return something like $.extend(true, {}, this.get(key));
Also, you don't really know what exactly changed in the object based on plain Backbone. So, if you're doing lots of 'stuff' on a change (rebuilding a view, etc), you're potentially going to run into performance issues even if all you did was add another attribute to that object and it isn't used for any of said changes. (i.e. set({layout: layoutObj}) vs set({layoutPageTitle: 'blah'}) which may only update the title.. instead of causing the entire view to reload).
Otherwise, at least in the app that I'm working on, we've had no real issues with objects in backbone. They sync fairly well, and it's certainly better than .get('layout.leftPanel[0]') and having some magical translation to make that work. Just be careful of the reference part.
Hope that helps at least a little!
When a GUI is composed of several subcomponents that I treat as individual Views with their own Presenter and Models, is there a pattern for gluing them together? Some subcomponents are persistently on the screen while others would get swapped in and out.
What would be a good factory pattern for instantiating the respective MVP triad for a subcomponent that gets added to the GUI at runtime?
How do you glue the subcomponents with the persistent "container" part of the GUI and with each other? Would there be a "God Presenter" that ties other presenters together?
Update: I'm now shooting for something similar to Eclipse's extension mechanism. Plug-ins register themselves to a global registry for the functionality that they provide. When a functionality such as returning data or rendering a View is needed, the registry is queried and the returned functions are invoked (this is pure JavaScript, so I'm not using interfaces). I'm going with a pure-plug-in approach where everything (even the main View) is a plug-in. I might also use an Event Bus to let the various Presenters communicate agnostically.
Now my more specific question becomes, when a plug-in is about to contribute a View, how should I go about initializing the MVP triad and getting the View rendered into a parent container (outside the module). I probably need to let the View render itself into a container passed from outside and inject the View and Model (if needed) into the Presenter. An alternative would be for the View to return a component that can be placed inside a container, but this would be against my tentative ideal of keeping everything that is GUI-framework-specific inside View implementations. I prefer if the factory/glue mechanism can be framework-agnostic.
OK I'll stop yammering now and wait for some feedback, and then perhaps add more clarifications on where exactly I'm stuck...
I think the design pattern you're about is mediator.
I've written a javascript framework that consisted of a mediator.
It works like this:
You create a global instance of the
mediator,
register objects under
certain names,
use the mediator in your implementation to call methods
from registered objects within any of
the objects.
If something isn't present - no errors fly around.
If there are multiple instances - they all get the call.
This is the basic code for that:
(An extract of my code. I will make a jquery plugin including that in a while. If you're willing to use it push me to do it faster ;) )
function Mediator(){
function log(a){
try {console.log(a);} catch(e){
try {opera.postError(a);} catch(e){
//alert(a);
}
}
}
var __reg={}; // { "what": [object, ...], ... } //registers an object
//what=key that will identify, obj=an object
this._register = function(what,obj){
if(__reg[what]===undefined){
__reg[what]=[];
}
__reg[what].push(obj);
} //unregisters multiple objects and deletes a key
this._unregisterAll = function(what){
if(__reg[what]===undefined){log('Overlord:_unregisterAll - no registers'); return false; }
__reg[what]=null;
return true;
}
//unregisters a single element key
this._unregister = function(what){
if(this._countRegisters()==1){
__reg[what]=null;
return true;
} else { log('Overlord:_unregister - no registers'); return false; }
}
//unregisters last added element
this._unregisterLast = function(what){
var cnt=this._countRegisters(what);
if(cnt==0) { log('Overlord:_unregisterLast - no registers'); return false; }
if(cnt==1) {
__reg[what]=null;
return true;
} else {
__reg[what][cnt-1]=null;
return true;
}
}
//returns number of registered items
this._countRegisters = function(what){
try{
return __reg[what].length;
} catch(e){log(e);
return 0;
}
} //calls a method from all objects registered under 'what' with an array of parameters. returns true if there was at least one successful call
this._call = function(what,method,params){
var occured=false;
for(var i in __reg[what]) {
try {
__reg[what][i][method](params);
occured=true;
} catch(e) {log(e);//auto reakcja
}
}
return occured;
}
//does the call, but also returns an array of values retuurned by function
this._returnAll = function(what,method,params){
var re=[];
for(var i in __reg[what]){
try {
re.push(__reg[what][i][method](params));
} catch(e) {log(e);//auto reakcja
}
}
return re;
}
//runs a method from first object for a given key
this._returnFirst = function(what,method,params){
try {
return __reg[what][0][method](params);
} catch(e) {log(e);//auto reakcja
return null;
}
}
}
I guess that "keeping the GUI-framework-specific inside View implementations" is an overall application-level design choice, rather than an absolute must (at least when you think to "view implementation" as "plugin view implementation").
You could - for example - have a very thin view layer at plugin level, and implement a super-view layer within the parent that calls the plugins: thinking to a system of plugins that all add a column to a table, you could well have the bulk of the view code at parent level ("table") and have your plugins to just pass little more than raw data: you would avoid to repeat yourself and would make your code more flexible and maintainable.
On the other hand, if your plugins provide very different types of functionality that never interact directly (for example if they are the different subsystems of a flight simulator) you will want to keep everything that is related to views at plugin level, so that the parent object would not have to even know what a given plugin deals with, but just place it's returned value somewhere in the GUI.
Other factors that would probably influence your choice are the language and framework (if any) that you are using: in my personal experience, design patterns tend to be far from language-agnostic, as each language (and framework) has its own strengths / weaknesses which make certain choices obvious and certain others very difficult to implement.
Just my 2ยข to the discussion, anyhow! :)
For now, I'm going with this approach:
An extender (an extension implementation that a plug-in exposes) that is contributing a GUI component has a getTriad (will come up with a better name later) method that instantiates, wires and returns a MVP triad. The View has a getComponent method that renders and returns a framework-specific GUI element container that can be added to a parent container (the framework-specific details of which are encapsulated within the parent Views). I'm not letting the View render itself into a container but instead letting the parent View render the child into itself. I think this is better in terms of ensuring child Views don't directly mess with parent Views.