While trying to split up my meteor application in separate packages I encountered a problem when trying to encapsulate templates. The package name would be gs-users:
packages/gs-users/package.js
Package.onUse(function(api) {
api.versionsFrom('1.1.0.2');
api.use('mquandalle:jade');
api.addFiles('views/list.jade');
api.addFiles('gs-users.js');
});
My template file packages/gs-users/views/list.jade:
template(name="GsUsersList")
p Ola seƱior!!
Inside my main applications route definition (lib/routes.js):
Router.route('/', function() {
this.render('GsUsersList');
});
Meteor now complains: Couldn't find a template named "GsUsersList" or "gsUsersList". Are you sure you defined it?
When using the templating package instead of mquandalle:jade and .html files instead of .jade files inside the gs-users package everything works fine. But I really hate plain HTML ;)
The solution is more simple than I thought. Just use waitingkuo:jade instead of mquandalle:jade and add templating as a dependency:
Package.onUse(function(api) {
api.versionsFrom('1.1.0.3');
api.use('waitingkuo:jade');
api.use('templating');
api.addFiles([
'le-template.jade',
], ['client']);
});
Works like a charm!
Related
I'm looking for a simple solution for server side includes of regular HTML and CSS (for repeated components like headers, footers, navigation, etc) that doesn't require extensive frameworks.
You're looking for the
require()
function. Check out the documentation for this and other Node.js things here
If you want to use newer import statement, you may do so; it's not yet fully implemented in Node, but you can use it by using the .mjs extension on the file you need to import and then using the following command:
node --experimental-modules someFile.mjs
In node js to include repetitive files like headers, footers etc. you will need a templating language such as ejs.
Using ejs here is a sample code snippet with the include tag
<%- include('./path/to/your/html/file') %>
You can use require to require multiple files. However, since node caches the files, you will need to delete them from the cache if you want to used an un-cached version of the file.
index.js
app.get('/path', (req, res) => {
clear()
let headers1 = require('/headers/a.js')
let headers2 = require('/headers/b.js')
res.set(headers1)
res.set(headers2)
})
// Remove from require cache
function clear() {
delete require.cache[require.resolve('/headers/a.js')]
delete require.cache[require.resolve('/headers/b.js')]
}
headers/a.js
module.exports = {
'Content-Type': 'application/json'
}
headers/b.js
module.exports = {
'Custom-Header': 'Brass Monkey'
}
I think you like require "view components". Exists multiple view engines for nodejs, for example pug , or ejs. In this case, you use include
To make random strings in pug template I want to use random-string javascript module.
First I install it via npm like this :
npm install random-string
Then in pug template I used this :
.site
.title
- var string = randomString({length: 20});
| #{string}
But while compiling files I got this error :
randomString is not a function
How Can I use third party javascript functions in pug js temaplte?
Your pug file won't have scope of randomString unless it is passed in when you call render() in your file that is calling it (such as your controller).
e.g.
this.render("[pugFilename]", {
randomString = require("randomstring") // whatever package name you're using
}
Personally, I prefer doing any non-view stuff outside the view and in the script that is requesting the view to be rendered, where I can.
The syntax in Pug can start to look very messy otherwise and become difficult to follow.
You can switch out the code above with the code in your question and it should work fine, although I'd recommend changing your variable name (or key) to something more meaningful.
e.g
this.render("[pugFilename]", {
randomStr: randomString({ length: 20 })
});
Update for phpStorm File Watcher
Firstly, install pug-cli and random-string locally,
npm install pug-cli random-string --save
Then setup File Watcher,
Original answer
Set and require random-string as a local variable, then you can access it in the templates. For example,
template.pug
.site
.title
- var string = randomString({length: 20});
| #{string}
compile it using pug
const pug = require('pug');
// Compile template.pug, and render a set of data
console.log(pug.renderFile('template.pug', {
randomString: require('random-string')
}));
//-> <div class="site"><div class="title">o0rGsvgEEOHrxj7niivt</div></div>
If you are using pug-cli, install random-string in the same scope of pug-cli, and specify options through a string or a file.
pug -O "{randomString: require('random-string')}" template.pug
TinyTest seems to be concerned only with unit testing; however, may Meteor packages have UI elements, and it would be helpful to pull in a pre-crafted HTML file that exercises a widget. For instance, we might want to transform a <table> into a grid with DataTables.net, then test if the instantiation was correct.
How can external HTML files be used in a TinyTest?
package.js:
Package.onTest(function (api) {
api.use(packageName, where);
api.use(['tinytest', 'http'], where);
// TODO we should just bring in src/test.html - but how to do that with TinyTest?
api.addFiles('src/test.html', where); // this won't magically display the HTML anywhere
api.addFiles('meteor/test.js', where);
});
test.js:
Tinytest.addAsync('Visual check', function (test, done) {
var iconsDropZone = document.createElement('div');
document.body.appendChild(iconsDropZone);
// TODO ideally we'd get src/test.html straight from this repo, but no idea how to do this from TinyTest
HTTP.get('https://rawgit.com/FortAwesome/Font-Awesome/master/src/test.html', function callback(error, result) {
if (error) {
test.fail('Error getting the icons. Do we have an Internet connection to rawgit.com?');
} else {
iconsDropZone.innerHTML = result.content;
test.ok({message: 'Test passed if the icons look OK.'});
}
done();
});
});
I personally think TinyTest is not the right tool for the job! You may get away with finding out how to include the Asset package or writing your own file loader, but you'll soon face the problem of needing to query the DOM in your tests.
Here are some options I can think of:
Option 1:
You can get access to a fully rendered page by using xolvio:webdriver. If you include this package in your onTest block, then you should have access to wdio in your TinyTest tests. I say should as I don't use TinyTest at all but I designed the webdriver package to be usable by any framework. Follow the instructions on the package readme and then do something like this:
browser.
init().
url('https://rawgit.com/FortAwesome/Font-Awesome/master/src/test.html').
getSource(function(err, source) {
// you have a fully rendered source here and can compare to what you like
}).
end();
It's a heavyweight option but might be suitable for you.
Option 2:
If you're willing to move away from TinyTest, another option is to use Jasmine. It supports client unit testing so you can load up the unit that does the visuals and isolate it with a unit test.
Option 3:
You can create a test app around your package. So you would have:
/package
/package/src
/package/example
/package/example/main.html
/package/example/tests
/package/example/tests/driver.js
And now the example directory is a Meteor app. In main.html you would use your package and under tests directory you can use the framework of your choice (jasmine/mocha/cucumber) in combination with webdriver. I like this pattern for package development as you can test the package as it is intended to be used by apps.
Using a version of what grunt-contrib-watch recommends for compiling only changed files in here: https://github.com/gruntjs/grunt-contrib-watch#compiling-files-as-needed
var changedFiles = Object.create(null);
var onChange = grunt.util._.debounce(function() {
grunt.config('jshint.all.src', Object.keys(changedFiles));
changedFiles = Object.create(null);
}, 200);
grunt.event.on('watch', function(action, filepath) {
changedFiles[filepath] = action;
onChange();
});
This works fine (again with a variation I wrote for it here: https://gist.github.com/pgilad/6897875)
The problem is when using include inside Jade templates, meaning you are including other Jade templates in order to build the complete html file.
Using the singular solution for compile doesn't work because if a .jade file you are working on is embeded using include current_working_jade.jade - the including file won't get recompiled.
Are there any workarounds for this besides compiling all of your jade files from scratch? This causes a problem when you have around ~60 large jade files to compile every time.
The only possible solution I can think of is either mapping jade templates dependencies either externally or with directories, but I don't know any tools/plugins which do that...
After already starting to work on a scaffold that will generate a sortof jade sourcemap I found this great project, that already solves this issue:
Jade Inheritance
Usage is as follows:
Install package using: npm install jade-inheritance --save-dev
Where you want to get a list of dependent files from a jade:
var JadeInheritance = require('jade-inheritance');
var inheritance = new JadeInheritance(file, basedirname, {basedir:basedirname});
Then when you want to get the file:
depenedentFiles = inheritance.files;
The project also demonstrates how to apply the concept with grunt.watch in order to compile only changed jade files with their dependents, exactly what I needed:
Using jade-inheritance with grunt watch
I imagine something like checking all jade files and if they include your changed file then recompile that as well. Shouldn't be too hard. Pseudo code:
var allFiles = getAllJadeFileWithIncludesAndProjectPathMap();
//allFiles now contains something like this
{
'jade/index.jade': ['jade/menu.jade', 'jade/home.jade'],
'jade/about.jade': ['jade/menu.jade']
}
var rootFiles = [];
_.each(allFiles, function (includes, parent) {
_.each(includes, function (includePath) {
var parent;
while (parent = getParentPath(includePath)) {
//nothing needed if getParentPath returns null for files that aren't included
}
if (rootFiles.indexOf(parent) !== -1) {
rootFiles.push(parent);
}
});
});
Now add these files to the compile task.
Update I want to avoid compiling the templates client-side, and have them compile during my local ant build process. Perhaps something like loading jQuery and jQuery templates into rhino, passing the $.template() function the contents of each .jst file in turn, and building a "templates.js" which should contain:
$.template['model-view'] = resultingFunction.toString();
// 1 for each .jst file
This way, I can maintain each template in a seperate file, and avoid having all clients redundantly compile the same template.
I'm using jQuery templates, and was hoping to separate them out into their own files (eg. model-view.jst) that are compiled into functions when the project is built and made available in the jQuery .tmpl() scope for later use.
For example, given the file model-view.jst
<li>${name}</li>
This file and all other .jst files should be picked up on build, compiled into a function that can later be used anywhere in the program like so:
$.tmpl('model-view', {
name: 'Matt'
});
I solved this problem using Node.js and coffeescript by making directory of partial templated into executable, pre-compiled functions. Hope this helps.
https://github.com/wookiehangover/jquery-tmpl-jst
I let you decide if you like it or not :)
in you common js library define this function:
function loadTemplate(templateName) {
$.ajax({
url: templateName + '.jst',
success: function(data) {
$.template(templateName, data);
}});
}
Then in you master hml file <head></head> section you can add:
<script type="text/javascript">loadTemplate('model-view');</script>
<script type="text/javascript">loadTemplate('another-model-view');</script>
so you can use anywhere in your code
$.tmpl('model-view', your-data)
$.tmpl('another-model-view', your-data)
Hope it helps