General structure when using React.js - javascript

Looking at the Virtual DOM in React.js and by doing a few performance tests, I'm very interested in this library. It seems like the perfect add-on to Backbone's awesome model, router and collection structure.
However, due to the lack of quality tutorials and courses out there, I'm left with a few questions I hope someone here will be able to answer:
HTML templates
Does React completely do away with the notion of HTML templates? I'm talking about having your view markup in a separate HTML file (Or on the same page in a <script type=text/template> tag). You know, like you do with underscore.js Handlebars etc ...
The Starter kit examples all seem to have the JSX or React.DOM functions right inside your view classes, which seems a little messy to me, and I can see this getting a little out of hand, as your views grow in complexity.
Here's an example that renders 2 values and the sum of them, using a basic Twitter Bootstrap panel element with a nested table.
var CustomView = React.createClass({
render: function() {
var x = this.props.x;
var y = this.props.y;
return (
<div className="panel panel-primary">
<div className="panel-heading">
<h1 className="panel-title">Should I put all this markup somewhere else?</h1>
</div>
<div className="panel-body">
<table className="table">
<thead>
<tr>
<th>X</th>
<th>Y</th>
<th>Combined val</th>
</tr>
</thead>
<tbody>
<tr>
<td>{x}</td>
<td>{y}</td>
<td>{x + y}</td>
</tr>
</tbody>
</table>
</div>
</div>
);
}
});
I'm not interested in knowing whether it's possible or not to move this stuff to a separate file, rather, I'm trying to understand what's considered the best practise when working with React.
Updating and setting data
The Why React page states the following:
Simply express how your app should look at any given point in time, and React will automatically manage all UI updates when your underlying data changes.
I'm not fully understanding how this works. For instance, take the React component from before <CustomView x="20" y="10">. Initially I would render it like so:
var x = 20;
var y = 10;
React.renderComponent(
<CustomView x={x} y={y} />,
document.getElementById('view-container')
);
Now, when I want to update CustomView any time x changes, how should I proceed? React is supposed to be an alternative to the data binding you find in Angular and Ember, without doing a 2-way binding, so how do I make this happen? How do I tell CustomView to keep an eye on x and automatically re-render when it changes?
Naturally, just assigning x a new value does nothing.
I know there's the setState method, but I still manually have to call that, right? So if I was working with a React view and a Backbone model, the code could look something like this:
// Data model: Backbone.js
var model = new Backbone.Model({text: "Please help! :)"});
// Create view class
var View = React.CreateClass({
render: function() {
return (
<p>{this.props.text}</p>
);
}
});
// Instantiate new view
var view = React.renderComponent(
<View text={model.get("text")}>,
document.getElementById('view-container')
);
// Update view
model.on("change:text", function(model, newValue) {
view.setState({
text: newValue
});
});
// Change data
model.set("text", "I do not understand this ...");
That seems like a really strange setup, and I'm almost sure this can't be the way you're supposed to do it.
I would love some pointers to help me move in the right direction here.
Thank you in advance for any feedback and help.

Does React completely do away with the notion of HTML templates?
Yes, in favor of declaring your views with JavaScript. It also allows the Virtual DOM structure to work efficiently.
The Starter kit examples all seem to have the JSX or React.DOM functions right inside your view classes, which seems a little messy to me, and I can see this getting a little out of hand, as your views grow in complexity.
You shouldn't allow your view to grow in complexity. Make big components from small components, and you won't have an issue. If you feel it's getting complex, you can always reorganize it.
I know there's the setState method, but I still manually have to call that, right? So if I was working with a React view and a Backbone model [...]
You should search for "react backbone", and you'll find some blog posts and code examples. They're often used together. Feel free to add any links you found helpful here.

You're on the right path, however there are two things to fix. One is a bug, the other is a preferred pattern.
The bug: In the View, you are using this.props.text (good!), but you are using setState in the model listener. This sets the this.state.text value, which you are not using, so it won't work. setState should 'only' be used from inside the component itself - for all intents and purposes, think of it as a protected method. Instead, there is the setProps function, which is intended to be used only from outside the component.
The preferred pattern: The usage of setProps will soon be deprecated, as it causes a number of subtle issues. The better thing to do is just re-render the whole component each time. The right code in your case is:
// Data model: Backbone.js
var model = new Backbone.Model({text: "Please help! :)"});
// Create view class
var View = React.CreateClass({
render: function() {
return (
<p>{this.props.text}</p>
);
}
});
function rerender() {
React.renderComponent(
<View text={model.get("text")}>,
document.getElementById('view-container')
);
}
// Update view
model.on("change:text", function(model, newValue) {
rerender();
});
rerender();

Thank you for the replies guys,
So, am I correct in assuming that if I want the views to observe the data models, what I end up with is actually pretty close to Backbone view code, where you hook up event listeners in the intialize method? Here's a quick example that works:
var model = new Backbone.Model({
text: "Hey there :)"
});
var TextBox = React.createClass({
getInitialState: function() {
return this.props.model.toJSON();
},
componentWillMount: function() {
this.props.model.on("change:text", function(model, newText) {
this.setState({
text: newText
});
}, this);
},
render: function() {
return (
<p>{this.state.text}</p>
);
}
});
React.renderComponent(
<TextBox model={model} />,
document.getElementById('view-holder')
);
As I said this does work as intended. The view re-renders whenever the model's text property changes. Would this be considered "Good" React code, or should I hook this up differently?

Related

React.js Dataflow Paradigm - how are data.props, state, and JSX beneficial?

I'm using React and created a small page that has 4 components (React classes, what is the preferred term? I'll call them components in this post):
Component Breakdown
a parent "App" component that includes and manages the other components
a "Form" component that lets the user interact with the page
a "String View" component that displays the input from the form as text
a "Visual View" (I know, bad name...) component that interprets the string view and performs actions to adjust the visual.
Dataflow
The communication of these components using states and props is as follows:
The Form has onChange handlers that pass the new state to the App
The App funnels the state data to the String View
The String View updates and passes the updated state to the App
The App funnels the new state data to the Visual View
Finally, the Visual View now updates based on the new state.
Sample Code
var App = React.createClass({
handleFormChange: function(formData) {
this.setState({formData:formData});
},
handleStringChange: function(stringData) {
this.setState({stringData:stringData});
},
render: function() {
return (
<div className="app">
<FormView onFormChange={this.handleFormChange}/>
<StringView formData={this.state.formData} onStringChange={this.handleStringChange}/>
<VisualView stringData={this.state.stringData}/>
</div>
);
}
});
var FormView = React.createClass({
handleFormChange: function(e) {
this.props.onFormChange(e.target.value);
}
render: function() {
return(
<div className="formView">
<select onChange={this.handleFormChange}>
<option value="1">Option 1</option>
<option value="2">Option 2</option>
</select>
</div>
);
}
});
var StringView = React.createClass({
componentDidUpdate: function() {
this.props.onStringChange({newString:'newStringState'});
},
render: function() {
this.props.formData;
// process formData and update state
return (
<div className="stringView">
{this.props.formData}
</div>
);
}
});
var VisualView = React.createClass({
render: function() {
var selection = this.props.stringData,
output = '';
if (selection === 1) {
output = 'Hooray, 1!';
} else {
output = 'Yes! 2!';
}
return (
<div className="stringView">
{output}
</div>
);
}
});
Questions
Is this the correct dataflow paradigm that React is trying to enforce (components only talk to parents, not siblings)?
Compared to how I would have written this in just regular JavaScript, this seems terribly constrained. Am I missing the big picture? Is this dataflow paradigm designed to prevent future problems (if so, which ones? Any that can't be solved with disciplined regular JavaScript?), or is there some other purpose that I'm missing?
I'm getting a lot of repeated function names (handleFormChange for example, it's used in App and Form View), is there a good way to make these distinguishable? Or, are repeated function names across components desirable?
When the components actually build, the JSX stuff gets transpiled down into real JavaScript. Is there an advantage to using JSX? Would writing components in the already transpiled JavaScript have an advantage?
To start, I think it is ok to call "components", and I've seen lot of people call that way. I will answer your questions below, in an order that I think is better to make my answers make sense.
When the components actually build, the JSX stuff gets transpiled down into real JavaScript. Is there an advantage to using JSX? Would writing components in the already transpiled JavaScript have an advantage?
JSX kinda mixes JavaScript and HTML, so, it makes your code "friendly". You will create your components, and just "call" them as HTML tags. Below you can see the difference between writing JSX and pure JavaScript.
return <div className="my-component"><p>Awesome</p></div>;
return ReactDOM.div({
className: 'my-component'
}, ReactDOM.p({}, "Awesome"));
I don't know you, but I would get tired to write this amount of code just to render a div with a paragraph.
You can check more benefits of using it here:
https://hchen1202.gitbooks.io/learning-react-js/content/benefits_of_jsx.html
I'm getting a lot of repeat function names (handleFormChange for example, it's used in App and Form View), is there a good way to make these distinguishable? Or, are repeated function names across components desirable?
It is not bad, also, your app is a "demo" one, if it would be a "real" one, it would have some better names for the components (i.e. <FormView> would be <ContactForm>) and maybe your method names would be different. But it is not bad at all. For example, inside <ContactForm> you may call the submit handler as onSubmit, but outside (the prop that you pass), you may call onContactFormSubmit, or, in a more semantic way, onContactFormFilled.
If your application starts to grow and you have lots of things repeated in the same component (that is the case of your <App>), you may try to split your components, therefore, each of your component will "know" about a "domain", and it would not appear to have lots of repeated stuff.
Is this the correct dataflow paradigm that React is trying to enforce (components only talk to parents, not siblings)?
First of all, React doesn't "enforce" anything, as some people say, React is the "v" in MVC, so, you have your "presentation" layer described as components, and the data may flow in the way you want.
But you got a point when you say "components only talk to parents, not siblings", because that is the way you can "communicate" between your components when you have multiple components. Since a component can't see its sibling, you need someone to orchestrate this communication, and, in this case, this is the parent's job.
There are other ways to make components "talk" to each other (i.e. using refs), but having a parent to orchestrate is, IMO, the most reliable (and better testable) one.
Compared to how I would have written this in just regular JavaScript, this seems terribly constrained. Am I missing the big picture? Is this dataflow paradigm designed to prevent future problems (if so, which ones? Any that can't be solved with disciplined regular JavaScript?), or is there some other purpose that I'm missing?
I decided to answer that as the last one, to sum up some things.
IMO, React is just great, you start to have your "logic" in the right place (a component), and you can just compose things in order to make your page work well (and by well I mean it is orchestrated correctly).
React also makes it easier to "think" about how you will build your interfaces. This Pete Hunt's blog post is amazing, and you should check it out:
https://facebook.github.io/react/docs/thinking-in-react.html
If you would be writing your code with plain JavaScript, you would have to handle DOM in some way (i.e. using a template engine) and your code would end up mixing DOM manipulation with your application logic. React just abstracts that for you. You can only care about presenting stuff. Another advantage is that, when everything is a component, you can reuse those components, it doesn't matter where they are located. If you pass the props correctly, your component will work as expected.
I know it seems exhaustive to write those components, but as you start to write more components you start to see lots of benefits. One of them is to nevermore wonder about how to present your data (no more concatenating HTML strings or calling template functions). Another one is that it is easy to "split" your interfaces, what makes your code easier to maintain (and that is not straightforward when using plain JavaScript).
To be honest, this application you wrote is really simple, and you may not see lots of advantages of using React for building it. I think you should try to create a more "complex" one, and compare it with plain JavaScript. By "complex", I mean "user interface" complex. For example, create a form that allows user to submit multiple "people". And "people" should have "name" and multiple "pet" (which also have a name). You will see how hard is it to handle "add" and "remove" operations in this case, and how easy React handle that kind of thing.
I think that is it, I hope you and React "click". It changed my mind about how to create complex user interfaces.

Backbone views which don't know about their container, models to be fetched via AJAX, no UI/UX trade-offs and maintainable code

Since I'm not totally sure on which level my issue actually is to be solved best, I'd like to summarise the path I went and the things I tried first:
It's more or less about $el (I think).
As most basic backbone examples state, I started with having the $el defined within its view, like
Invoice.InvoiceView = Backbone.View.extend({
el: $('#container'),
template: ..,
..
});
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
Following this article's advice, I switched to passing $el over to the view while calling the render()-method. Example:
$('#container').html( new WineListView({model: app.wineList}).render().el );
So far so good - but now render() gets called, while it maybe shouldn't (yet).
For example the View asynchronously fetches a model in its initialize()-routine. Adding a binding to reset or sync (e.g. like this.model.bind('sync', this.render, this)) makes sure, render() gets definitely called once the model is fetched, however above stated way, render() still might get called while the model isn't fetched yet.
Not nice, but working(TM), I solved that by checking for the model's existence of its primary key:
render: function() {
if(this.model.get('id')) {
...
}
However, what I didn't expect - and if it really isn't documented (at least I didn't find anything about it) I think it really should be - the fetch operation doesn't seem to be atomic. While the primary key ('id') might be already part of the model, the rest might not, yet. So there's no guarantee the model is fetched completely that way. But that whole checking seemed wrong anyway, so I did some research and got pointed to the deferred.done-callback which sounded exactly what I was looking for, so my code morphed into this:
render: render() {
var self = this;
this.model.deferred.done(function() {
self.model.get('..')
};
return this;
}
..
$('#container').html( new WineListView({model: app.wineList}).render().el);
It works! Nice, hu? Ehrm.. not really. It might be nice from the runtime-flow's point of view, but that code is quite cumbersome (to put it mildly..). But I'd even bite that bullet, if there wouldn't be that little, tiny detail, that this code sets (=replaces) the view instantly, but populates it later (due to the deferred).
Imagine you have two (full-page) views, a show and an edit one - and you'd like to instantly switch between the two (e.g. after hitting save in the edit-view it morphs into the show-view. But using above code it sets (=resets) the view immediately and then renders its content, once the deferred fires (as in, once fetching the model is completed).
This could be a short flickering or a long blank transition page. Either way, not cool.
So, I guess my question is: How to implement views, which don't know about their container, involve models which need to be fetched, views which should be rendered on demand (but only once the model is fetched completely), having no need to accept UI/UX trade-offs and - the cherry on the cake - having maintainable code in the end.
First of all, the first method you found is terrible (hard coding selector in view's constructor)
The second: new WineListView({model: app.wineList}).render().el is very common and ok. This requires you to return the reference to view from render method, and everyone seems to follow this, which is unnecessary.
The best method (imo) is to simply attach the views element to the container, like this
$('#container').html(new WineListView({model: app.wineList}).el);
The WineListView doesn't need to know about where it's going to be used, and whatever is initializing WineListView doesn't need to worry about when to render the WineListView view instance:
because the el is a live reference to an HTML Element, the view instance can modify it anytime it wants to, and the changes will reflect wherever it is attached in DOM/ when it gets attached in DOM.
For example,
WineListView = Backbone.View.extend({
initialize: function(){
this.render(); // maybe call it here
this.model.fetch({
success: _.bind(this,function(){
this.render(); // or maybe here
})
});
}
});
Regarding flickering: this hardly has to do anything with rendering or backbone, it's just that you're replacing one element with another and there will be an emptiness for a tiny bit of time even if your new view renders instantly. You should handle this using general techniques like transitions, loaders etc, or avoid having to switch elements (For example convert labels into inputs in the same view, without switching view)
First off, the linked example is outdated. It's using version 0.9.2,
whereas the current version (2016-04-19) is 1.3.3. I recommend
you have look at the change log and note the differences, there are many.
Using the el property is fine. Like everything though, there's a time and place.
It didn't feel right, that the view is supposed to know about its parent (=container). The paragraph 'Decouple Views from other DOM elements' written on http://coenraets.org/blog/2012/01/backbone-js-lessons-learned-and-improved-sample-app/) perfectly puts it into words.
I wouldn't define an el property on every view, but sometimes it makes sense, such as your example. Which is why, I'm assuming, Backbone allows the use of the el property. If you know container is already in the DOM, why not use it?
You have a few options:
The approach outlined in my original answer, a work-around.
fetch the model, and in the success callback, insert the view element into the DOM:
model.fetch({
success:function() {
$('#container').html(new View({model:model}).render().el);
}
});
Another work-around.
Define an el property on the view and fetch the model in the view initialize function. The new content will be rendered in the container element (also the view), when the content/model data is ready, by ready, I mean when the model has finished fetching from the server.
In short,
If you don't want to define an el property, go with number 1.
If you don't want to let the view fetch the model, go with number 2.
If you want to use the el property, go with number 3.
So, I guess my question is: How to implement views, which don't know about their container
In your example, I would use the el property, it's simple a solution with the least amount of code. Not using the el property here, turns into hacky work-arounds that involve more code (complexity) without adding any value (power).
Here's what the code looks like using el:
var Model = Backbone.Model.extend({url:'/model_url'});
var model = new Model();
// set-up a view
var View = Backbone.View.extend({
el:'#container',
template:'model_template',
initialize:function() {
this.model.fetch();
this.listenTo(this.model,'sync',this.render);
},
render:function() {
this.$el.html(this.template(this.model.toJSON()));
return this;
}
});
var view = new View({model:model});
Check out the documentation for el.
Here is an updated working example.
If there is an obvious flicker because, your model takes a noticeable amount of time
to be fetched from the server...maybe you should think about displaying a loading bar/variation thereof
while fetching the model. Otherwise instead of seeing the flicker, it will appear the
application is slow, delayed, or hanging..but in reality - it's waiting to render the next view,
waiting for the model to finish fetching from the server. Sitting on old content, just waiting for
the model to load new data to show new content.

Knockout JS not clearing components

So here's a weird KnockoutJS problem I've never actually come across before.
I'm working on an application that uses Knockout Components very heavily.
In one part of the app, I have an editor page that's built dynamically from a JSON driven backend, and which populates a front end page with a number of widgets, depending on what it's told from the back end data.
Example
The back end might send
[{"widget": "textBox"},{"widget": "textBox"},{"widget": "comboBox"},{"widget": "checkBox"}]
Which would cause the front end to build up a page containing
<html>
....
<textbox></textbox>
<textbox></textbox>
<combobox></combobox>
<checkbox></checkbox>
....
</html>
Each of the custom tags is an individual KnockoutJS component, compiled as an AMD module and loaded using RequireJS, each component is based on the same boiler plate:
/// <amd-dependency path="text!application/components/pagecontrols/template.html" />
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) {
var Template = require("text!application/components/pagecontrols/template.html");
var ViewModel = (function () {
function ViewModel(params) {
var _this = this;
this.someDataBoundVar = ko.observable("");
}
ViewModel.prototype.somePublicFunction = function () {
postbox.publish("SomeMessage", { data: "some data" });
};
return ViewModel;
})();
return { viewModel: ViewModel, template: Template };
});
The components communicate with each other and with the page using "Knockout Postbox" in a pub sub fashion.
And when I put them into the page I do so in the following manor:
<div data-bind="foreach: pageComponentsToDisplay">
<!-- ko if: widget == "textBox" -->
<textBox params="details: $data"></textBox>
<!-- /ko -->
<!-- ko if: widget == "comboBox" -->
<comboBox params="details: $data"></comboBox>
<!-- /ko -->
<!-- ko if: widget == "checkBox" -->
<checkBox params="details: $data"></checkBox>
<!-- /ko -->
</div>
and where pageComponentsToDisplay is a simple knockout observable array that I just push the objects received from the backend onto:
pageComponentsToDisplay = ko.observableArray([]);
pageComponentsToDisplay(data);
Where 'data' is as shown in JSON above
Now all of this works great, but here-in now lies the ODD part.
If I have to do a "reload" of the page, I simply
pageComponentsToDisplay = ko.observableArray([]);
to clear the array, and consequently, all my components also disappear from the page, as expected, however when I load the new data in, again using:
pageComponentsToDisplay(data);
I get my new components on screen as expected, BUT the old ones appear to be still present and active in memory, even though there not visible.
The reason I know the controls are still there, because when I issue one of my PubSub messages to ask the controls for some state info, ALL of them reply.
It seems to me that when I clear the array, and when KO clears the view model, it actually does not seem to be destroying the old copies.
Further more, if I refresh again, I then get 3 sets of components responding, refresh again and it's 4, and this keeps increasing as expected.
This is the first time I've encountered this behaviour with knockout, and I've used this kind of pattern for years without an issue.
If you want a good overview of how the entire project is set up, I have a sample skeleton layout on my github page:
https://github.com/shawty/dotnetnotts15
If anyone has any ideas on what might be happening here I'd love to hear them.
As a final note, I'm actually developing all this using Typescript, but since this is a runtime problem, I'm documenting it from a JS point of view.
Regards
Shawty
Update 1
So after digging further (and with a little 'new thinking' thanks to cl3m's answer) I'm a little bit further forward.
In my initial post, I did mention that I was using Ryan Niemeyer's excelent PubSub extension for Knockout 'ko postbox'.
It turn's out, that my 'Components' are being disposed of and torn down BUT the subscription handlers that are being created to respond to postbox are not.
The result is, that the VM (or more specifically the values that the subscription uses in the VM) are being kept in memory, along with the postbox subscription handler.
This means when my master broadcasts a message asking for component values, the held reference responds, followed by the visibly active component.
What I need to now do is figure out a way to dispose these subscriptions, which because I'm using postbox directly, and NOT assigning them to an observable in my model, means I don't actually have a var or object reference to target them with.
The quest continues.
Update 2
See my self answer to the question below.
I'm not sure this will help but, as per my comment, here is how I use the ko.utils.domNodeDisposal.addDisposeCallback() in my custom bindings. Perhaps there is a way to use it in knockout components:
ko.bindingHandlers.tooltip = {
init: function(element, valueAccessor) {
$(element).tooltip(options);
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
$(element).tooltip('destroy');
});
}
}
More reading on Ryan Niemeyer's website
The problem it seems was due to Knockout hanging onto subscriptions set up by postbox when the actual components where active.
In my case, I use postbox purely as a messaging platform, so all i'm doing is
ko.postbox.subscribe("foo", function(payload) { ... });
all the time, since I was only ever using single shot subscriptions in this fashion, I was never paying ANY Attention to the values returned by the postbox subscription call.
I did things this way, simply because in many of the components I create there is a common API that they all use, but to which they all respond in different ways, so all I ever needed was a simple this is what to do when your called handler that was component specific, but not application specific.
It turns out however that when you use postbox in this manner, there is no observable for you to target, and as such there is nothing to dispose. (Your not saving the return, so you have nothing to work with)
What the Knockout and Postbox documentation does not mention, is that the return value from postbox.subscribe is a general Knockout subscription function, and by assigning the return from it to a property within your model, you then have a means to call the functionality available on it, one of those functions provides the ability to "dispose" the instance, which NOT ONLY removes the physical manifestation of the component from it's collection, BUT ALSO ensures that any subscriptions or event handlers connected to it are also correctly torn down.
Couple with that, the fact that you can pass a dispose handler to your VM when you register it, the final solution is to make sure you do the following
/// <amd-dependency path="text!application/components/pagecontrols/template.html" />
define(["require", "exports", "knockout", 'knockout.postbox', "text!application/components/pagecontrols/template.html"], function (require, exports, ko, postbox) {
var Template = require("text!application/components/pagecontrols/template.html");
var ViewModel = (function () {
function ViewModel(params) {
var _this = this;
this.someDataBoundVar = ko.observable("");
this.mySubscriptionHandler = ko.postbox.subscribe("foo", function(){
// do something here to handle subscription message
});
}
ViewModel.prototype.somePublicFunction = function () {
postbox.publish("SomeMessage", { data: "some data" });
};
return ViewModel;
ViewModel.prototype.dispose = function () {
this.mySubscriptionHandler.dispose();
};
return ViewModel;
})();
return { viewModel: ViewModel, template: Template, dispose: this.dispose };
});
You'll notice that the resulting class has a "dispose" function too, this is something that KnockoutJS provides on component classes, and if your class is managed as a component by the main KO library, KO will look for and execute if found, that function when your component class goes out of scope.
As you can see in my example, Iv'e saved the return from the subscription handler as previously mentioned, then in this hook point that we know will get called, used that to ensure that I also call dispose on each subscription.
Of course this ONLY shows one subscription, if you have multiple subscriptions, then you need multiple saves, and multiple calls at the end. An easy way of achieving this, especially if your using Typescript as I am, is to use Typescripts generics functionality and save all your subscriptions into a typed array, meaning at the end all you need to do is loop over that array and call dispose on every entry in it.

What is the difference between a ractive template, partial and component

I've built a ractive.js app using partials. These partials are loaded via fetch/ajax - and all works nicely.
I then decided I wanted to encapsulate data along with the partial so looked at components - as I understood a component to do just that: Isolate a template/partial with its data.
I then looked to load the components in: http://ractivejs.github.io/ractive-load/
However, I don't really see the advantage of this approach - as it appears with the loader you can only load in the components template, not the entire encapsulated component (data, templates etc). You still have to put the data onto the main ractive instance (as you would with a partial).
I'm trying to dyanamically update the component. I'm also using page.js for routing. I'm trying to separate out all the concerns.
I'm probably not explaining myself very well - here is my code... most of it was taken from martydpx's answer here How to create Ractive's subcomponents dynamically and change them programmatically )
....
<dynamic name='{{name}}'/>
</script>
<script>
// Component loader
Ractive.load({
home: '/components/home.html', // seems this can only contain a template. Is it possible for it to contain everything - data and all?
packs: '/components/packs.html',
....
addplayer: '/components/addplayer.html',
notfound: '/components/notfound.html',
}).then( function ( components ) {
Ractive.components[ 'home' ] = components.home;
Ractive.components[ 'packs' ] = components.packs;
....
Ractive.components[ 'addplayer' ] = components.addplayer;
Ractive.components[ 'notfound' ] = components.notfound;
// dynamically load component based on route
Ractive.components.dynamic = Ractive.extend({
template: '<component/>',
components: {
component: function() {
this.set('foo','bar'); // I can dynamically set the data here.. but how would I add defaults for each component, within the component?
return this.get('route');
}
},
oninit: function(){
this.observe('route', function(){
this.reset();
},
{ init: false}
);
}
});
var r = new Ractive({
el: document.body,
template: '#template',
data: {
route: 'home'
}
});
// Routing. Sets the route... which triggers the component
page('/', index);
...
page();
function index() {
console.log('index');
r.set('route','home')
}
EDIT
I've read this - which has been a great help :)
https://github.com/ractivejs/component-spec/blob/master/authors.md
In the dynamic component scenario - how would I dynamically update component specific data. I seem to be able to do it when the component tag is hardwired into the page... but not when the component tag is dynamically created. After much playing about in the console - its as if it doesn't see the dynamic component. So things like r.findComponent('home').get() don't work.
Yet, if I put a <home/> tag in the template - it does work.
Also, do components automatically 'tear down' when they're un-rendered?
I'm not 100% sure what you are looking for.
First you create a child component -
var MyWidget = Ractive.extend({
template: '<div>{{message}}</div>',
data: {
message: 'No message specified, using the default'
}
});
You register this with Ractive runtime
Ractive.components.widget = MyWidget;
Then you create a parent component
var Parent = Ractive.extend({
template: '<div>
<MyWidget message={{widget}} />
</div>'
});
You use the parent instance to pass the data to child
// Live instance of parent
new Parent({
el: 'id',
data : {
widget: {
message : 'Waddup kiddo'
}
}
});
data.widget gets mapped to MyWidget's data, in-turn gets the message data.
For more info refer this
Generally there are 3 types of components you will be creating & using -
Self-sufficient Components - It knows everything it needs to know by itself. You don't pass anything to it. It creates it's own data or knows where to get it from. Ex: A logo component which knows by itself where to get the image from.
Dumb Components - They have no intelligence and all the data that it needs should be passed from parent. Like in our example - MyWidget has no idea where and what message stands for. Just renders it. No questions asked. Parent will fetch message and just pass it on.
Smart Components - Components which do some heavy lifting. An example would be Profile component. Parent will pass just a profileID to this, and it knows where to get profile data from, does some ajax calls, knows how to parse and interpret the data, may be even starts a socket and listens to changes etc.
So you decide how you want to make your components, who takes responsibility and think about data-encapsulation then.

Backbone showing/hiding rendered views best practices

New to using Backbone and have a very simple application. Basically there are Clients and ClientItems. I have a view to show all Clients and if you click on a Client you get taken to their ClientItems. Going to this ClientItems view should just hide the Clients view and going back to Clients should hide ClientItems. Now, in my render() function for each view, it is going through the collections and dynamically adding stuff to the page. When I go back and forth between the two (using the back button) I don't really need to fully render again as all the data is there in the page, just hidden. Where should this logic go? Right now I have it in the render() function but it feels sloppy, what is the preferred way of handling this?
We are using a global variable App with several common function used across application:
var App = {
initialize : function() {
App.views = {
clientView : new ClientsView(),
clientItemView : new ClientsItemsView()
}
},
showView: function(view){
if(App.views.current != undefined){
$(App.views.current.el).hide();
}
App.views.current = view;
$(App.views.current.el).show();
},
...
}
And then I use this App from other parts of application:
App.showView(App.views.clientView);
IntoTheVoid's solution is good – it's nice to have a single place to hide/show views. But how do you activate the logic?
In my experience, routers are the best place for this. When a route changes and the appropriate function is called, you should update the active, visible view(s).
What if you need multiple views to be visible at once? If you have a primary view that always changes when the route changes, and multiple subsidiary sticky views, you need not worry. But if it's more complex than that, think of creating a ComboView that neatly packages all the relevant views into one containing el node. That way the above logic still works, and your router functions are not littered with logic for managing what views are visible at the moment.

Categories