how to make sails-linker load different assets in sub/child pages? - javascript

Please see attachment.
That's the template structure. login.jade extends layout.jade.
My problem is that I want:
a global.css file loaded in all pages (done)
login.css file loaded only when viewing login.jade (problem)
I already created a working pipeline configuration. The task looks like this:
devStyles: {
options: {
startTag: '<!--STYLES-->',
endTag: '<!--STYLES END-->',
fileTmpl: '<link rel="stylesheet" href="%s">',
appRoot: '.tmp/public'
},
files: {
'.tmp/public/**/*.html': require('../pipeline').frontendGlobalCssFiles,
'views/**/*.html': require('../pipeline').frontendGlobalCssFiles,
'views/**/*.jade': require('../pipeline').frontendGlobalCssFiles,
'views/auth/login.jade': require('../pipeline').frontendLoginCssFiles
}
}
The problem is that the last files rule doesn't work. I'm sure that frontendLoginCssFiles is ok, since if I load it with the 'views/**/*.jade' path, it works. So what's the problem here?

Ok, solved. Basically, the problem is not strictly related to jade inheritance. What sails-linker really does is add the assets to the physical files, no matter if they're parents, children or partials. All it does is take the passed files, search for the proper start/end Tags and add the related html BEFORE compiling the real served pages.
My problem was that I was not adding the start/end tags to the login (child) page.
Anyway, even doing so, we've got then the problem that ALL the compiled pages will load ALL the assets. So for example, the login assets will be also loaded on the home page. Not so good.
To solve, briefly, I used specific start/end tags for page-specific assets.
Following, the 'long' story:
Note: since we're speaking about jade templates, in sails-linker we are looking into the *Jade tasks, and therefore the comments are written with jade syntax (ex. // STYLES and not <!--STYLES-->)
First, we'll isolate 'global assets' > the ones we want to load on every page:
Change all the // STYLES instances to something like // GLOBAL STYLES. Both in sails-linker and eventually on .jade views.
/tasks/pipeline.js: rename default filelists to something like globalCssFiles, jsfiles etc, and change all the instances of that name.
/tasks/config/sails-linker.js: rename all the instances of point 2 modifications.
Second, we'll add 'page assets':
On child jade pages, use something like // PAGE STYLES comments.
/tasks/pipeline.js: create page-specific filelists, so ex:
var frontendLoginCss = ['styles/login.css'];
and below:
module.exports.frontendLoginCss = frontendLoginCss.map(function(path) {
return '.tmp/public/' + path;
});
/tasks/config/sails-linker.js: create page-specific tasks, like:
devPageStylesJade: {
options: {
startTag: '// PAGE STYLES',
endTag: '// PAGE STYLES END',
fileTmpl: 'link(rel="stylesheet", href="%s")',
appRoot: '.tmp/public'
},
files: {
'views/auth/login.jade': require('../pipeline').frontendLoginCss
}
}
/tasks/register/*: add the relevant tasks in all the files where you should to. For example, my linkAssets.js could be like this:
module.exports = function (grunt) {
grunt.registerTask('linkAssets', [
'sails-linker:devJs',
'sails-linker:devStyles',
'sails-linker:devTpl',
'sails-linker:devJsJade',
'sails-linker:devPageJsJade', //added
'sails-linker:devStylesJade',
'sails-linker:devPageStylesJade', //added
'sails-linker:devTplJade'
]);
};
In jade templates, use block syntax for importing the styles/js, so you can use append to append the assets to the block. Ex, the general 'parent' layout.jade will have:
block styles
// GLOBAL STYLES
// GLOBAL STYLES END
while the child template login.jade will have:
append styles
// PAGE STYLES
// PAGE STYLES END
Final tip: it really doesn't matter where you write the append directives in child templates, they'll always appended where the parent template did defined them. So I'll write all appends on the bottom of my child templates (more clean).
Hope will be usefull for people with same issues!

Related

Apostrophe-pieces-pages can't register page type

I try to create a new pieces page, kind of a blog style in apostrophe cms. I have trainings, trainings-widgets and trainings-pages folders. I registered them all in app.js; pieces by themselves and widgets work beautifully. The problem is when I try to add page of Trainings type. I get Error: template not found: pages/trainings-page.html in console.
In apostrophe-pages/index.js I have it configured:
module.exports = {
types: [{
name: 'home',
label: 'Home'
},
{
name: 'trainings-page',
label: 'Trainings'
}
]
}
And in trainings-pages/index.js:
module.exports = {
name: 'trainings-page',
label: 'Training Page',
extend: 'apostrophe-pieces-pages',
piecesFilters: []
}
I can't figure out what might be wrong here.
Edit: A showcase git:
https://github.com/msdsk/apostrophe-error
In addition to the module directory name being incorrect, as noted:
lib/modules/trainigs-pages should be:
lib/modules/trainings-pages
When we fix this we graduate to a new error:
Error: template not found: trainings-pages:layout.html
You are extending layout.html. But that file is in a different module, apostrophe-pages, that trainings-pages does not extend, and so it is never found. This is our fault for not recommending a more universal location for layout.html in the tutorial.
You can solve it two ways:
OPTION ONE
Move layout.html to lib/modules/apostrophe-templates/views/layout.html.
Always extend it like this:
{% extends 'apostrophe-templates:layout.html' %}
This "cross-module" path syntax will find it in the apostrophe-templates module no matter what module you're in.
OPTION TWO
Another option is to set the viewsFolderFallback option of apostrophe-templates in app.js, like this:
'apostrophe-templates': {
viewsFolderFallback: __dirname + '/views'
},
And create a views/ folder at the top level of the project and move layout.html there. Then you don't have to use the special syntax, you can just write layout.html and if the module doesn't have its own, it'll look in the fallback folder. This is what we usually do in-house.
I'll discuss with the rest of the Apostrophe core team whether the tutorial should recommend option one or option two, and adjust the apostrophe CLI to set up option two automatically if that is preferred.
(There is a third option: you can put things in lib/modules/apostrophe-module/views and it works as a fallback without any configuration. That's because all modules eventually inherit from apostrophe-module. But our feeling in the past has been that this approach is not very intuitive.)
There is a typo error on your module directory's name : lib/modules/trainigs-pages should be lib/modules/training-pages

Workaround to dynamic includes in Pug/Jade

I understand that Pug does not support dynamic includes or extends in templates. Ie
extend path/to/template
works but not
extend #{dynamic_path_to_template}
Is there a workaround (however convoluted) that will allow the same goal of modifying the template used by a view at runtime
Context: My use case is that I am developing an npm module and the template being used to extend other views is located inside the module. After the module is published and installed, the path will be defined (ie. node_modules/my_module/path/to/template) but during the development phase, I need to just be able to "npm link" to the module and have the templates work. I also would prefer not to hard code the links so I can publish the same code as tested.
I had this issue aswell and found this question while searching for a solution. My solution is similar to Nikolay Schambergs Answer, but i thought i should share it.
I've created a function that renders templates by giving it a path and passed it to the options object. Maybe it helps in your case aswell
const includeFunc = (pathToPug, options = {}) => {
return pug.renderFile(pathToPug, options); //render the pug file
}
const html = pug.renderFile('template.pug', {include: includeFunc});
and then use it as followed in your template:
body
h1 Hello World
|!{include(dynamicPugFilePathFromVariable)}
There is no way to do this for now, but you can work out your application architecture without dynamic extends.
Possible solution #1
Make a layout.jade that conditionally include multiple layouts:
layout.jade:
if conditionalVariable
include firstLayout.jade
else
include otherLayout
In your view, extend layout.jade, and define conditionalVariable in the controller (true/false):
view.jade:
extends layout
block content
p here goes my content!
Possible solution #2
Pass configurations to the layout
- var lang = req.getLocale();
doctype html
block modifyLayout
split the project into multiple entrances, each entrance extends the layout and passes its different configs, and includes different things in different blocks
extends ../layout
block modifyLayout
- var lang = "en" //force language to be en in this page.
block body
include my-page-body
Possible solution #3
use something like terraform which uses pug as its rendering engine, but it enables you to use dynamic partials like this
!= partial(dynamicFileFromVariable)
It works!
First, set res.locals middleware.
middlewares/setResLocals.js
const pug = require('pug')
const path = require('path')
module.exports = function(req, res, next) {
res.locals.include = (pathToPug, options = {}) => { // used for imitate includ pug function
return pug.renderFile(pathToPug, options); //render the pug file
}
res.locals.__PATH__ = path.join(__dirname, '../')
next()
}
server/index.js
app.use(require('../middlewares/setResLocals'))
file.pug
|!{include(`${__PATH__}/${something}`)}
In order to do dynamic include, you will have to use Unescaped String Interpolation, inserting pug contents that are pre-compiled before your main .pug file inside your route. In other words it works as follows:
1) Some .pug files are pre-compiled into HTML
2) The HTML gets fed into another .pug file compilation process
Here's an example how to do it
Inside your router file (routes.js or whatever)
var pug = require('pug')
var html = []
var files = ['file1','file2'] // file names in your views folders
let dir = path.resolve(path.dirname(require.main.filename) + `/app/server/views/`)
//dir is the folder with your templates
app.get('/some-route', (req,res) => {
for (let n = 0; n < files.length; n++) {
let file = path.resolve(dir + '/' + files[n] + `.pug`)
fs.access(file, fs.constants.F_OK, (err) => {
if (!err) {
html.push(pug.renderFile(file, data))
if (n === files.length - 1) {
res.render('dashboard', {html})
}
}
else {
res.status(500).json({code:500,status:"error", error:"system-error"})
}
})
}
})
Inside your desired .pug file:
for item in html
.
!{item}
The example above is specific to my own use case, but it should be easy enough to adapt it.
I know, this is a bit late for answering. But I found a possibility suitable for my purpose by this bit of information from the pug docs:
If the path is absolute (e.g., include /root.pug), it is resolved by
prepending options.basedir. Otherwise, paths are resolved relative to
the current file being compiled.
So, I provide most of my pug modules by relative paths and the stuff I want to exchange dynamically is organised in pug files of the same name but in different folders (think theme) and include them by absolute paths . Then I change the basedir option to dynamically choose a set of pug files (like choosing the theme).
May this help others, too.

How to use UX component in ExtJS 6?

I'm trying to use this component: Colorpick button (xtype: colorbutton)
I'm pretty new to ExtJS and I don't know how and where to correctly define this type of button. Where should I put the source code and how should I include it correctly ?
I'm using ExtJS 6.0.0 for a webmapping application. I have the "ext-6.0.0" folder in the directory where I have my web pages so that I can include easily the ext-all.js file.
My main javascript file which contains all my panels has 2 mains components:
Ext.require([
'GeoExt.component.Map',
'GeoExt.data.store.LayersTree',
]);
and
Ext.application({
name: 'BasicTree',
launch: function(){
[... all my code here ...]
}
})
This file (named panel.js) is included in my index.html file.
Thank you !
It works like every other component. When you want to use a normal button, you would look into the docs, which tell you Ext.button.Button xtype: button, and then you write
Ext.define('MyApp.view.XYZ',{
extend
requires:['Ext.button.Button'], // <- defining the requirement to load the file
items:[{
xtype:'button' // <- using xtype to get an instance of the component
}]
...
In this case, the docs state Ext.ux.colorpick.Button xtype: colorbutton, so you write
Ext.define('MyApp.view.XYZ',{
extend: ...
requires:['Ext.ux.colorpick.Button'], // <- defining the requirement to load the file
items:[{
xtype:'colorbutton' // <- using xtype to get an instance of the component
}]
...
For this to work, you have to have the file
<working_dir>/ext/classic/classic/src/ux/colorpick/Button.js
because otherwise the UX component cannot be loaded. UX components are, unlike most other Ext components, not part of ext-all.js.
I found the solution.
1) Copied the content of the directory \ext-6.0.0\packages\ux\classic\src to \ext-6.0.0\ux .
2) Include the Ext directory to the paths of the load in index.html:
Ext.Loader.setConfig({
enabled: true,
paths: {
'GeoExt': 'src/geoext3-master/src/',
'Ext': 'src/ext-6.0.0'
}
3) Added the required item at the top of my JavaScript file:
Ext.require([
'GeoExt.component.Map',
'GeoExt.data.store.LayersTree',
'Ext.ux.colorpick.Button'
]);
You can set path of ux folder from library in Ext.loader.setPath() method to load js files from ux folder.
Ext.Loader.setConfig({enabled: true});
Ext.Loader.setPath('Ext.ux', '../ux');
You have to set this config before Ext.onReady() or Ext.application.
Please refer example at Grid filters Ux

CSS in an angular directive

I'm writing an Angular directive to display some information about a music album, currently it shows the information below the album art, but if the element gets too small then it should shrink the album art and put the information next to it. At the moment I just have the html in the page directly and have css to do the changes in the main page, but this causes the page to be quite monolithic as it also display other things, which is why I want to seperate it out into directives.
However I can't see how to include CSS in the directive, I don't want to include it inline in the html, and I could put a style tag in the html and put it in there, but then it would be repeated every time I use the directive. Is there some way of injecting a link to a CSS file into the head from the directive? Like there is a templateUrl field is there a stylesheetUrl or something?
You could check this module: angular-css-injector.
Be warned it's currently only compatible with angular 1.2.x...
You can inject css in your directive like this:
var head = document.getElementsByTagName('head')[0];
var cs = document.createElement('link');
cs.rel = 'stylesheet';
cs.href = 'css/myStylesheet.css';
head.appendChild(cs);
So a directive would look like this:
app.directive('myDirective', function () {
var head = document.getElementsByTagName('head')[0];
var cs = document.createElement('link');
cs.rel = 'stylesheet';
cs.href = 'css/myStylesheet.css';
head.appendChild(cs);
return {
templateUrl:'templates/myTemplate.html',
link: function (scope, elm, attrs, ctrl) {
}
};
});
Structuring an angular app is one of the hardest things about learning angular. Angular tries hard to modularise code, but (the current state of) html, css, and javascript doesn't really allow you to package the things together, so you have to find a way that works well for you.
The way I keep things separate (yet together) is generally using my build system (I use gulp), and a CSS preprocessor (for me, Stylus).
My process for creating a new directive is as follows:
Define a new angular module (in its own file) my-albums.coffee:
angular.module('my-albums', [])
.directive('myAlbumDirective', ()->
restrict: 'A'
templateUrl: 'SomeTemplate.jade'
# etc.
)
Create a jade template my-album-directive.jade
.album
img(ng-src="{{album.imageUrl}})
span.name {{album.name}}
Create the stylus file with the same name as the module prefixed with an underscore: _my-albums.styl. In here I will include module specific css.
[myAlbumDirective]
.album
display flex
flex-direction column
#media screen and (min-width: 600px)
flex-direction row
Then, whenever I import an angular module into my app.coffee (which contains a long list of module imports), I also import its style in my main.styl stylesheet:
#import '../my-albums/_my-albums.styl'
When I run my build system, it (among other things):
Automatically compiles .jade files into a app.templates angular module (pre-populating the $templateCache (app.templates is included in the imports in app.coffee
Compiles and concatenates all coffeescript into script.js
Compiles and concatenates all stylus files whose filenames do not begin with an underscore into style.css
Then inside my index page I just have two imports:
script(src='js/app.js')
link(rel='stylesheet', href='css/style.css')
TL;DR:
There's no easy way of keeping your directive code separate from the rest of the page, but if you research build systems and other people's angular project structures, you'll find something you like.
Note
SoonTM things will be neater (see web components and angular 2.0)

RequireJS: Best method to run page specific modules?

Example:
mysite.com/page1 depends on scripts in module1.js
mysite.com/page2 depends on scripts in module2.js
mysite.com/page3 depends on scripts in module3.js
Does anyone have any best practices for only running the Javascript required for that specific page. Before I started using RequireJS I would use only one Javascript file and init only the modules I needed for that page. like this
In page <head>:
var page = "pageTitle";
In Main JS File:
var myModules = {};
myModules.pageTitle = (function(){
return {
init: function(){
alert('this module has been initiated');
}
}
})();
myModules[page].init();
Not really sure sure how a technique like this would work with RequireJS. Would love some feedback and advice on how others are doing this.
I assume you have one main.js file for all your pages?
Anyway, typically you would use the data-main attribute of the script tag as explained in the API documentation, which would mean you have one main js file per page. This way, you can get rid of the literal javascript code in you page, and take full advantage of the RequireJS optimization step.
Example:
Develop you main.js file as a RequireJS module:
define([<dependencies go here>], function(){
return function(pageTitle){
//do you page dependent logic here
}
}
In your html, you'll have something like:
<html>
<head>
<script src="require.js"></script>
<script>
require(["main.js"], function(init){
init("pageTitle");
});
</script>
1) What language do you use at back-end?
You can keep your script-configuration in database or in configuration files. (For example: page page1 has modules: module1, module2, and module4, etc).
I have such a php template file for generating <script> tags on my page:
<script src="http://requirejs.org/docs/release/1.0.1/minified/require.js"></script>
<script>
require([
<?php echo "'". implode("',\n\t'", $this->scripts) . "'\n"; ?>
], function(a){
function run(page) {
if ( window.hasOwnProperty(page) ) {
window[page].start();
}
}
var page = '<?php echo $this->page; ?>';
run('all'); // activating scripts needed for every page
run(page); // and for current page
});
</script>
P.S. the script is asking for window[page] variable. I meant, that every .js script for a page -- for example index.js for index page is making window.index variable. ( I know, it's not so good - read P.P.S ;) )
P.P.S. I'm novice to requireJS (I've knew about it only today), and it my first draft, and I think, I'll make it in another way:
2) As a concept for now :)
You keep your scripts as AMD modules (not as usual scripts, but as modules for requireJS). Modules map you can keep in a .json file:
{
'index' : [ 'news', 'banners' ],
'contacts' : [ 'maps', 'banners', 'donate' ],
'otherpage' : [ 'module1', 'module2' ]
}
You should pass the page name or page id to the main.js (you can pass this value in DOM element - in templates of site, or in template variables ).
So main.js knows the page name, and load your modules.json file. It gets specific modules and requires them.
main.js also can keep dependencies that are need on every page ( for example jquery, some jquery plugins, etc) ( jquery plugins better to wrap as modules )
P.S. sorry for my English
The creator of RequireJS actually made an example project doing excactly this: https://github.com/requirejs/example-multipage

Categories