Marionette + RequireJS: Doing a require inside ItemView.render() - requirejs

I'm trying to load and render additional views async and append them to the ItemView.
Simplified code - why is $el not defined in the require() block in render() - what am I missing here? Am I not using RequireJS properly, or Marionette, or just my inexperience with javascript?
What is the recommended way of doing this? It needs to be dynamic as additional section views could be available at runtime that I don't know about yet as registered by plugins.
define(['require','marionette', 'App', 'swig', 'backbone.wreqr','text!./settings.html'],
function (require,Marionette, App,Swig, Wreqr, settingsHtml )
{
var sectionViews = ['./settingscontent/GeneralView'];
var SettingsView = Marionette.ItemView.extend(
{
template: Swig.compile(settingsHtml),
commands: new Wreqr.Commands(),
initialize: function ()
{
this.commands.addHandler('save', function (options, callback)
{
callback();
});
Marionette.ItemView.prototype.initialize.apply(this, arguments);
},
render: function()
{
Marionette.ItemView.prototype.render.call(this);
var $el = this.$el;
var self = this;
require(sectionViews, function (View)
{
$el.find('div.tab-content').append(new View(self.model).render().$el);
// $el is not defined
// self != outer this - $el is an empty div
});
return this;
}
}
return SettingsView;
})

Why are you trying to overload itemview.render?
Why not use the built in onrender event
https://github.com/marionettejs/backbone.marionette/blob/master/docs/marionette.itemview.md#render--onrender-event
from that documentation :
Backbone.Marionette.ItemView.extend({
onRender: function(){
// manipulate the `el` here. it's already
// been rendered, and is full of the view's
// HTML, ready to go.
}
});
seems easier and more typical of marionette usage

You need to bind this inside the function to the SettingsView object. Something like:
render: function()
{
Marionette.ItemView.prototype.render.call(this);
var $el = this.$el;
var self = this;
require(sectionViews, _.bind(function (View)
{
...
}, this));
return this;
}
The local variables will not be visible inside the bound function. You can use this and this.$el safely however.

Related

how to render UI with multiplte service calls

I just have a general question for Javascript. If I have to invoke two services for a UI and those two services calls have their own call backs, but UI template has to be rendered only after both the callbacks have finished execution, what should be the best Javascript practice to do it?
invokeServices() {
invokeService1(param1, param2, svcCallback1);
invokeService2(param1, param2, svcCallback2);
//where to render the template???
}
function svcCallback1 (){
//where to render the template???
}
function svcCallback2 (){
//where to render the template???
}
This post might help: Marko vs React: An In-depth Look. Specifically, look for the section on Async for how to use promises and the <await/> tag to delay rendering until all the data is present.
import fsp from 'fs-promise';
$ var filePath = __dirname + '/hello.txt';
$ var readPromise = fsp.readFile(filePath, {encoding: 'utf8'});
<await(helloText from readPromise)>
<p>
${helloText}
</p>
</await>
1. Track Completion of Callbacks
function invokeServicesAndRender() {
let remaining = 2;
let service1Data;
let service2Data;
invokeService1(param1, param2, (err, data) => {
service1Data = data;
if (!--remaining) done();
});
invokeService2(param1, param2, (err, data) => {
service2Data = data;
if (!--remaining) done();
});
function done() {
// all data is here now, we can render the ui
}
}
2. Promisify and use Promise.all
Node has a built-in method to promisify callbacks: util.promisify
const promisify = require('util').promisify;
const promiseService1 = promisify(invokeService1);
const promiseService2 = promisify(invokeService2);
function invokeServicesAndRender() {
Promise.all([
promiseService1(param1, param2),
promiseService2(param1, param2),
]).then(([service1Data, service2Data]) => {
// all data is here now, we can render the ui
});
}
3. If you're using Marko, render immediately and pass promises to the template
I see you tagged this question marko, so I'll mention that with Marko it is recommended to begin rendering immediately and only wait to render chunks that actually need the data. This allows you to flush out content to the user faster.
const promisify = require('util').promisify;
const promiseService1 = promisify(invokeService1);
const promiseService2 = promisify(invokeService2);
function invokeServicesAndRender() {
let service1DataPromise = promiseService1(param1, param2);
let service2DataPromise = promiseService2(param1, param2);
template.render({ service1DataPromise, service2DataPromise });
}
In your template you can use the <await> tag to wait for the data where it is needed:
<h1>Hello World</h1>
<p>this content gets rendered immediately</p>
<await(service1Data from input.service1DataPromise)>
<!-- render content here that needs the service data -->
</await>

Can we reuse an exported function in NodeJS?

I have this in my .js file:
exports.generate = function(details) {
// bunch of code
// returns Promise
}
exports.save = function(details){
exports.generate(details)
.then(function(id){
//save in db
})
}
Is it okay to use an exported function like this? Or is there a better way..?
It depends on if you want consumers of the module to be able to influence the module's behavior by overwriting exports.generate (e.g. require('foo').generate = function() {...}).
If you don't want users to be able to influence it in this way, then your best bet is going to be pulling out the generate() function and naming it, then exporting that and using the function directly by name inside save():
function generate(details) {
// ...
}
exports.generate = generate;
exports.save = function(details) {
generate(details).then(function(id) {
// ...
});
};
Otherwise if you do want to allow users to override the generate() functionality, then what you are currently doing is fine.
var _this=this;
exports.save = function(details) {
_this.generate(details) ...
};

Knockout Declarations

I am new for knockout. I would like to know the differences between these three and which one is best as per latest framework.
Approach 1 (InvoiceTypes): Declaring all variables and methods using comma and finally using return statement to expose public interface methods.
Approach 2 (OrderTypes): It is similar to Approach 1 but used semicolon to differentiate each variable or method.
Apprach 3 (ModelTypes): It is similar to Approach 2 but with out return statement.
Finally specified calling way of start method in each approach in jquery read.
Approach 1
window.Domain = window.Domain || {}
window.Domain.InvoiceTypes = function () {
var types = ko.observableArray(),
getTypes = function() { return types; },
start = function() {
types.push({name:"knockout"});
},
submit = function() {
alert("submit");
};
return {
getTypes: getTypes,
start: start,
submit: submit
};
}();
Approach 2
window.Domain.OrderTypes = function () {
var types = ko.observableArray();
var getTypes = function() { return types; };
var start = function() {
types.push({name:"knockout"});
};
var submit = function() {
alert("submit");
};
return {
getTypes: getTypes,
start: start,
submit: submit
};
}();
Approach 3
window.Domain.ModelTypes = function () {
var self = this;
self.types = ko.observableArray();
self.getTypes = function() { return types; };
self.start = function() {
types.push({name:"knockout"});
};
self.submit = function() {
alert("submit");
};
};
<script type="text/javascript">
$(document).ready(function() {
window.Domain.InvoiceTypes.start();
window.Domain.OrderTypes.start();
var obj = new window.Domain.ModelTypes();
obj.start();
});
</script>
The difference I can see clearly is, single var declaration & return statement and using of self by this keyword.
Please provides your inputs.
Approaches #1 and #2 are akin to static factory methods. Rather than creating an object with the new keyword, you are calling a static "factory" function that creates and returns a new type for you. As for the comma versus semicolon approach, both are valid, but I prefer semicolons over commas. Why? Because javascript is more forgiving with semicolons than with commas. You can omit a semicolon here and there and the script will still run, whereas commas must always be present for the javascript to be correctly parsed. It is also easier to read with semicolons when your script gets larger.
Approach #3 is what most knockout developers do, or should to. Firstly, because you don't need the little () at the end of your function declaration, so it looks more like a class. However, it looks like you have an error in there:
self.start = function() {
//types.push({name:"knockout"}); // types was not declared,
self.types.push({name:"knockout"}); // only self.types was declared
};
... same error here
self.getTypes = function() {
//return types; types was never declared
return self.types;
};

hogan.js how to debug a variable

i'm very familiar with the javascript console.log(), and the php_dump() functions that allows us to see what's in a variable, i want to know if there is some function like this in hogan.js that let us inspect the content of a variable.
add some method to your data and include it at the loctation you need to inspect the scope
var data = {
...
// your vars,
...
inspect: function () {
return function () {
console.log(this);
}
}
};
template.render(data);
anywhere you use {{inspect}} it will log the current render context in the console
I slightly modified it to add the function to the data packet that is passed to Hogan in a centralized position, which, in my code, is a function called render().
Thank you for this clever trick.
function render(template, data, destination) {
data.inspect = function() {
return function() {
console.log("inspect:")
console.log(this);
};
};
// localized strings
data.strings = app.strings;
var tmpl = Hogan.compile(template);
var content = tmpl.render(data);
document.querySelector(destination).innerHTML = content;
}

Accessing Marionette Apps from Views (using Require.js)

I'm trying to share a Marionette App with some of its Views. I've read the wiki here, but the example leaves me with a question.
I've got a file with a couple of views in it that will all need to use the request/response system and possibly the commands. I don't want to do var MyApp = require('app'); in all of the Views in the file. I came up with the following, but I think there's probably a better way to do it.
Example:
//Views.js
define( ["marionette"], function (Marionette) {
var App = function(){
return require('app');
};
var ExampleItemView = Marionette.ItemView.extend({
initialize: function(){
App().request("getInfo", "aboutStuff");
}
});
return Marionette.CollectionView.extend({
itemView: ExampleItemView,
initialize: function(){
App().request("getInfo", "aboutStuff");
}
});
Is there a better way to do this?
I'd definitely not inject your app into your views since it can create circular dependencies which are ALWAYS a code smell (regardless of whether they can be solved or not) The simples solution by far is to create a separate (singleton) reqres object which is handled by the app and injected into the views.
//reqres.js
define(['backbone.wreqr'], function( Wreqr ){
return new Wreqr.RequestResponse();
});
//app
define(['reqres'], function(reqres){
reqres.setHandlers({
'getInfo' : function(){
return 'foo';
}
});
});
//Views.js
define( ["marionette", "reqres"], function (Marionette, reqres) {
var ExampleItemView = Marionette.ItemView.extend({
initialize: function(){
reqres.request("getInfo", "aboutStuff");
}
});
return Marionette.CollectionView.extend({
itemView: ExampleItemView,
initialize: function(){
reqres.request("getInfo", "aboutStuff");
}
});
Backbone Wreqr is to be replaced by Backbone Radio in the next major release of Marionette, v3.
To use Backbone Radio you could create a channel and do the following:
/**
* App.js (for example)
*/
var Radio = require('backbone.radio');
// Use the 'data' channel - this could be whatever channel name you want
var dataChannel = Radio.channel('data');
// Handler for a request
dataChannel.reply('getMessage', function(name) {
return 'Hello ' + name + '. Alba gu brath!';
});
/**
* View.js (for example)
*/
var Radio = require('backbone.radio');
var dataChannel = Radio.channel('data');
// Make the request
console.log( dataChannel.request('getMessage', 'Craig') ); // -> Hello Craig. Alba gu brath!

Resources