How does the text! plugin use the baseUrl? - requirejs

I'm having an issue getting the text! plugin to work in my requirejs site. It's always including lib/ in the request url, however all of the other files (not using text!) are being successfully found and loaded. Here is my directory structure:
WebContent/
|--backbone/
|--Bunch of folders and files
|--config/
|--config.js
|--lib/
|--jquery.js
|--text.js
|--require.js
|--index.html
my index.html file is:
<body>
<div id="siteLayoutContainer"></div>
<script data-main='config/config' src="lib/require.js"></script>
</body>
The config file is:
requirejs.config({
baseUrl: './',
paths: {
jquery: 'lib/jquery.js',
backbone: 'lib/backbone.js',
text: 'lib/text',
application: 'backbone/application'
},
text: {
env: 'xhr'
}
});
require(['application'], function(App) {
App.start();
});
I'm using the text! plugin like so:
define([
'jquery',
'text!backbone/templates/SomeTemplate.html'
], function(jQuery, NotFoundHtml) {
//Some code here
}
So, in the above script, the url being used for the template is:
http://localhost/lib/backbone/templates/SomeTemplate.html
and I am expecting it to be:
http://localhost/backbone/templates/SomeTemplate.html
I've tried the following:
Moving the text.js and require.js files out into the WebContent
directory but I get the same results. Also something interesting is
if I put a space after text! and then the path, that works fine and
doesn't include the lib/ directory in the request to get the html
template. However the optimizer includes the space and can't find the
template.
Not defining a baseUrl - same results.
Moved the
require config.js content into index.html in it's own script tag that runs
before the require.js script tag - same results.
Getting rid of the the text options in the config file
Oh yeah, forgot I've also tried 'text!../backbone/templates/SomeTemplate.html - same results
So I'm stuck and can't figure out what I'm missing. I'm obviously not understanding how the text! plugin uses the baseUrl or how it determines the url it's going to use to fetch the defined file.

After your edits to your question, it now contains all the information to diagnose the problem. As you guessed in one of your comments, the issue is indeed that this path:
backbone: 'lib/backbone.js',
is throwing off the resolution of the template you give to the text plugin. When the text plugin loads what you give to it, it takes the path after the ! symbol and treats it as if it were a module name, and it goes through the module resolution process. The way module resolution works is that it checks if there is a prefix that matches any of the keys in paths and will change the prefix with the value associated with the key, which gives the result you obtained. One way to fix the issue would be to add this to your paths configuration:
"backbone/templates": "backbone/templates"
This will make it so that anything you request under backbone/templates won't get messed up by the backbone path.
Note: it is preferable to avoid putting extensions in module names so you should remove it from the values you have for jQuery and Backbone.

Related

Using 3rd party jquery plugins in Apostrophe cms

In my Apostrophe cms I have a portion in the header like this (in the outerLayout.html file):
<div id="sticky-header">
...
</div>
In the footer I have done the following:
<script src="/thirdparty/jquery/jquery-3.2.1.min.js"></script>
<script src="/thirdparty/sticky/jquery.sticky.js"></script>
I understand that apostrophe somehow includes jQuery, but if I do not include it myself I get an error in the console:
jquery.sticky.js:22 Uncaught ReferenceError: jQuery is not defined
at jquery.sticky.js:22
at jquery.sticky.js:24
I also have the following in one of the always.js files
$("#sticky-header").sticky({
topSpacing:0,
zIndex:1000
});
And that generates the error:
always.js:109 Uncaught TypeError: $(...).sticky is not a function
at always.js:109
How can I solve this?
In your case, the reason you need to push your own copy of jQuery is that including files from outerLayout is running front-end javascript OUTSIDE of Apostrophe's front-end asset pipeline.
To include your 3rd party / custom javascript INSIDE Apostrophe's asset pipeline (which is recommended and where jQuery is initially run) you need to push the javascript files from an Apostrophe module.
The quickest path forward is to push the asset from the apostrophe-assets module which should already be in your project.
in app.js
...
'apostrophe-assets': {
scripts: [
{
name: 'yourFile'
}
]
},
...
This will load lib/modules/apostrophe-assets/public/js/yourFile.js
More on pushing assets to the browser here http://apostrophecms.org/docs/tutorials/getting-started/pushing-assets.html
Down the road you may want to organize front-end assets by their appropriate module instead of pushing them all in a heap, this would be a good reference
http://apostrophecms.org/docs/tutorials/getting-started/custom-widgets.html#adding-a-java-script-widget-player-on-the-browser-side
Also, what you can expect to be there when you do push javascript
http://apostrophecms.org/docs/tutorials/getting-started/custom-widgets.html#what-39-s-available-in-the-browser
Thanx a lot Stuart - that definitely pushed me in the right direction :)
However what I ended up doing to make it work was to first put the files in lib/modules/apostrophe-assets/public/js/ like you suggested, and then edit the lib/modules/apostrophe-assets/index.js file:
module.exports = {
stylesheets: [
{
name: 'site'
}
],
scripts: [
{
name: 'bootstrap/js/bootstrap.min'
},
{
name: 'sticky/jquery.sticky'
},
{
name: 'scrollto/jquery.scrollTo.min'
}
]
};

gulp-inject: missing end tag for start tag when using pug

I have a rather specific problem: I am trying to build a complex stack using Sails.js with machinepack-sailsgulpify.
In order to inject assets into my templates I use gulp-inject plugin, as the machinepack suggests. The problem is that for anything other than html and ejs the injector doesn't work. It simply doesn't change anything. No errors, nothing.
My task looks like this:
gulp.task('sails-linker-gulp:devViews', function() {
return gulp.src('views/**/*.?(html|ejs|jade|pug|haml|slim|dust)') // Read templates
.pipe(
plugins.inject(
gulp.src(require('../pipeline').jsFilesToInject, {read: false}), // Link the javaScript
{
starttag: generateScriptStartTag,
endtag: generateScriptEndTag,
ignorePath: '.tmp/public',
transform: (filepath, file, i, length) => {
return `script(src="${filepath}")`;
}
}
)
)
.pipe(
plugins.inject(
gulp.src(require('../pipeline').cssFilesToInject, {read: false}), // Link the styles
{
starttag: generateStyleStartTag,
endtag: generateStyleEndTag,
ignorePath: '.tmp/public'
}
)
)
.pipe(
plugins.inject(
gulp.src( ['.tmp/public/jst.js'], {read: false}), // Link the JST Templates
{
starttag: generateTemplateStartTag,
endtag: generateTemplateEndTag,
ignorePath: '.tmp/public'
}
)
)
.pipe(gulp.dest('views/'))// Write modified files...
Don't worry about the generateScriptStartTag and such functions, they are just there for control and I am 1000% sure they work correctly, tested a lot. They generate the tags kind of like this:
//- SCRIPTS
//- SCRIPTS END
depending on the template language.
Adding custom transform function did not work. If I use ejs or html or really anything that resembles html syntax it works fine.
Now, about Sails: I can NOT add a gulp task to compile the template before injecting because Sails renders templates on request in development, it doesn't actually pre-compile them into any directory. And honestly: why should I? The injection is just adding lines to my .jade/.pug files in views, the files are there already, so I don't see why there's a problem there. Can someone advise?
UPDATE:
Rather frustrating inspection of the code revealed that the 'matches' property when running the inject function of has length 0 and when inspecting the content of the stream in node inspector, I did not see the comments, they were stripped away, despite the fact that they are clearly there in the file.
UPDATE #2:
It appears that I was wrong about ejs. ONLY HTML files are getting processed. Also it works OK when it doesn't detect the injection comments. However if it does the end event simply never emits for that file and nothing gets injected. This is true for ALL templating engines, only static HTML files have injection working fine.
UPDATE #3:
After another 5 hours of debugging I found the problem, however my understanding of streams isn't good enough to get me any closer to the solution. The problem is that in inject function of the plugin there's a loop that doesn't quit properly, and while it perfectly injects the required tags into the stream, it then runs that loop again on the same stream (with injected tags), and throws an error.
Why that error never showed up in any console I don't know but there you go. Can someone please help? I am completely lost with this... Is it a bug in the plugin?
I had to figure this out on my own.
It is a bug in gulp-inject. The regex that this plugin generates to test against the injection tags does not match the whole line, it's simply matches the first occurrence. This means that if I have my tags set like so:
//SCRIPTS
//SCRIPTS END
The regex will match the starttag: //SCRIPTS twice:
And the end tag will only be matched once. This was causing the second faulty loop with the error for missing end tag.
A workaround is to avoid repeating start of tags.
//SCRIPTS
//END SCRIPTS
That's not a solution, however. A solution would be to alter the regex so that it only allows whitespace and newline characters in order to match the tag, when using an indent-based template language.
Something like this would work: /\/\/-\s*SCRIPTS(?=\s*\n|$)/ig
Can't believe nobody has stumbled upon this until now, seems like it would be a more common problem...

How to use Magento2 with RequireJS?

I'm just migrating from Magento 1.x to Magento 2.x. I found that Magento2 uses RequireJS for handling JavaScript files. So I learnt what RequireJS is, and how to use it.
I found that most of the examples uses data-main="main" to define the configuration file.
In Magento2's default_head_blocks.xml file, I found the script tag like this:
<script src="requirejs/require.js"/>
Here they did not specify any data-main.
These are my questions:
How Magento2/RequireJS knows which JS should be loaded for configuration? (I found requirejs-config.js for this in multiple places)
By default Magento2 loads lots of JS (more than 20), how can I limit them?
I could not find enough documentation on this.
The best place to get all your answers for Magento 2 JS development is the Magento 2 docs, it really is a useful resource on this. http://devdocs.magento.com/guides/v2.0/javascript-dev-guide/javascript/js_init.html explains in detail about component initialisation.
To answer your two questions above -
Q.1. How Magento2/RequireJS knows which JS should be loaded for
configuration? (I found requirejs-config.js for this in multiple
places)
In each Magento 2 module there is a requirejs-config.js file to load all that modules configuration. i.e.
var config = {
map: {
'*': {
compareItems: 'Magento_Catalog/js/compare',
compareList: 'Magento_Catalog/js/list',
relatedProducts: 'Magento_Catalog/js/related-products',
upsellProducts: 'Magento_Catalog/js/upsell-products',
productListToolbarForm: 'Magento_Catalog/js/product/list/toolbar',
catalogGallery: 'Magento_Catalog/js/gallery',
priceBox: 'Magento_Catalog/js/price-box',
priceOptionDate: 'Magento_Catalog/js/price-option-date',
priceOptionFile: 'Magento_Catalog/js/price-option-file',
priceOptions: 'Magento_Catalog/js/price-options',
priceUtils: 'Magento_Catalog/js/price-utils',
catalogAddToCart: 'Magento_Catalog/js/catalog-add-to-cart'
}
}
};
This is telling requirejs where all the required JavaScript files are located.
There are multiple way to tell Magento when to use your JS file -
data-mage-init on a HTML element. e.g. <div class="block upsell" data-mage-init="{"upsellProducts":{}}" data-limit="0" data-shuffle="0">
script tag on the page e.g
<script type="text/x-magento-init">
{
"[data-role=tocart-form], .form.map.checkout": {
"catalogAddToCart": {}
}
}
</script>
within a JS file e.g. $('.yourSelector').yourPlugin();
Q.2. By default Magento2 loads lot's of JS (more than 20), how can I
limit them?
The sheer number of JS files that are loaded as a result of multiple modules is one of the downsides, however, with the correct usage of full page caching with a reverse proxy like Varnish the performance reduction is negligible, even in a development server.

sails.js use different layout with different js libraries

I'm creating my first nodejs/sails.js project, I want to use 3 different layout for 3 different occasions:
frontend_layout.ejs
admin_layout.ejs
mobile_layout.ejs
In frontend_layout.ejs, I want to load bootstrap.css, jquery.js and
bootstrap.js.
In admin_layout.ejs, I want to load bootstrap.css, angular.js and
ui-bootstrap-tpls.js(angular-ui js library).
In mobile_layout.ejs, I want to load ionic.css and ionic.bundle.js
I have created 3 folders in sailsProject/views/ which are admin_pages, mobile_pages and frontend_pages, the 3 layout.ejs files reside in each of these folders respectively, but no matter which layout I load, it always include all the css/js files in assets/js and assets/styles. I know I need to do something to pipeline.js, but how exactly? I'm not efficient at grunt, so I would really appreciate if anyone could point me which config files need to be modified and how... Thanks!
I wanted something very similar in my project, except I also wanted to take advantage of Sail's cool built-in ability to auto minimize/uglify javascript files for "sails lift --prod" in various layouts with different sets of javascript files. This answer only deals with the JS files, but you can make similar changes to support the same concept with your CSS files.
In my project I had 2 different layouts -- layout.ejs and layoutadmin.ejs. I created a new /assets/jsadmin folder which holds my admin javascript files. I left the sails existing /assets/js folder as-is to hold the javascript files for the public web pages.
My goal was for the /assets/js folder contents to be inserted between these tags (sails does this by default and these tags are used in the layout.ejs file):
<!--SCRIPTS-->
<!--SCRIPTS END-->
While the /assets/jsadmin folder contents was to be inserted between these tags (I made up these "custom" tag names and they are used in the layoutadmin.ejs file. I will add add support for this new tag in the rest of this answer):
<!--SCRIPTS_ADMIN-->
<!--SCRIPTS_ADMIN END-->
I created a full code sample demo of this here.
For development...
(sails lift), I modified so sails would populate my custom tags with the assets/jsadmin js files upon lifting.
I modified tasks/pipeline.js by adding a new variable called jsAdminFilesToInject which is very similar to the existing jsFilesToInject except it collects the js files from the jsAdmin folder.
var jsAdminFilesToInject = [
// Load sails.io before everything else
//'jsAdmin/dependencies/sails.io.js',
// Dependencies like jQuery, or Angular are brought in here
'jsAdmin/dependencies/**/*.js',
// All of the rest of your client-side js files
// will be injected here in no particular order.
'jsAdmin/**/*.js'
];
Note: I also had to export this new variable at the bottom of the pipeline.js file.
module.exports.jsAdminFilesToInject = jsAdminFilesToInject.map(function(path) {
return '.tmp/public/' + path;
});
I modified tasks/config/sails-linker.js by adding a new devJsAdmin task where it looks for tags and calls the new .jsAdminFilesToInject added in the pipeline.js file above.
devJsAdmin: {
options: {
startTag: '<!--SCRIPTS_ADMIN-->',
endTag: '<!--SCRIPTS_ADMIN END-->',
fileTmpl: '<script src="%s"></script>',
appRoot: '.tmp/public'
},
files: {
'.tmp/public/**/*.html': require('../pipeline').jsAdminFilesToInject,
'views/**/*.html': require('../pipeline').jsAdminFilesToInject,
'views/**/*.ejs': require('../pipeline').jsAdminFilesToInject
}
},
I Added a new task step to the tasks/register/linkAssets.js file which calls the devJsAdmin added above.
'sails-linker:devJsAdmin',
To test, run sails in demo mode:
sails lift
Browse to http://localhost:1337/home - you will see it is using the layout.ejs template and viewing the source will show the following at the bottom (files pulled from js folder):
<!--SCRIPTS-->
<script src="/js/dependencies/sails.io.js"></script>
<script src="/js/jquery-1.10.2.js"></script>
<!--SCRIPTS END-->
Browse to http://localhost:1337/admin - you will see it is using the layoutadmin.ejs template and viewing the source will show the following at the bottom of the source (files pulled from jsAdmin folder):
<!--SCRIPTS_ADMIN-->
<script src="/jsAdmin/dependencies/jquery-1.10.2.js"></script>
<script src="/jsAdmin/knockout-3.3.0.debug.js"></script>
<!--SCRIPTS_ADMIN END-->
For production...
(sails lift --prod), I wanted to do the same as development except I first wanted to concat and uglify the production javascript that goes in my new SCRIPTS_ADMIN tags.
I added a new jsAdmin section in the grunt tasks/config/concat.js file which pulls in the files from the previously added jsAdminFilesToInject in the pipeline.js to produce a concat/productionAdmin.js output file.
jsAdmin: {
src: require('../pipeline').jsAdminFilesToInject,
dest: '.tmp/public/concat/productionAdmin.js'
},
I added a new distAdmin section in the grunt tasks/config/uglify.js file which makes the concat/productionAdmin.js "ugly" by producing a new min/productionAdmin.min.js file.
distAdmin: {
src: ['.tmp/public/concat/productionAdmin.js'],
dest: '.tmp/public/min/productionAdmin.min.js'
}
I added a new prodJSAdmin section in the tasks/config/sails-linker.js file which adds the min/productionAdmin.min.js file between the SCRIPTS_ADMIN tags.
prodJsAdmin: {
options: {
startTag: '<!--SCRIPTS_ADMIN-->',
endTag: '<!--SCRIPTS_ADMIN END-->',
fileTmpl: '<script src="%s"></script>',
appRoot: '.tmp/public'
},
files: {
'.tmp/public/**/*.html': ['.tmp/public/min/productionAdmin.min.js'],
'views/**/*.html': ['.tmp/public/min/productionAdmin.min.js'],
'views/**/*.ejs': ['.tmp/public/min/productionAdmin.min.js']
}
},
Finally, I called this new prodJSAdmin from the prod grunt task by adding a line in the prod.js file.
'sails-linker:prodJsAdmin',
Run sails in production mode:
sails lift --prod
Browse to http://localhost:1337/home - you will see it is using the layout template and viewing the source will show the following at the bottom (using production.min.js):
<!--SCRIPTS-->
<script src="/min/production.min.js"></script>
<!--SCRIPTS END-->
Browse to http://localhost:1337/admin - you will see it is using the layoutadmin.ejs template and viewing the source will show the following at the bottom of the source (using productionAdmin.min.js):
<!--SCRIPTS_ADMIN-->
<script src="/min/productionAdmin.min.js"></script>
<!--SCRIPTS_ADMIN END-->
By default, Sails automatically insert all your css files (assets/styles) into tags between STYLES and STYLES END and js files (assets/js) into tags between SCRIPTS and SCRIPTS END.
<!--STYLES-->
<!--STYLES END-->
.
.
.
<!--SCRIPTS-->
<!--SCRIPTS END-->
This is set in pipeline.js file. By default it has set to get all css files from assets/styles. You can find it in cssFilesToInject section.
'styles/**/*.css'
You can change it as you wish. you can comment or delete it simply. (keep in mind if you want to put some css files common to every layout you can put them in here.)
Same for the js files. By default it has set to get all js files from assets/js. You can find it in jsFilesToInject section. Remove or add js files according to your requirement. You can find more information about grunt globbing patterns in here which helps to understand filtering pattern.
So easiest thing you can do now is put your layout specific files out side those tags(STYLES and SCRIPTS)
For example look following code sample,
<!--STYLES-->
<!--STYLES END-->
<!--STYLES SPECIFIC TO THIS LAYOUT-->
<link rel="stylesheet" href="/styles/some_layout_specific.css">

Yeoman - Javascript loading error

This is my app structure.
Yeoman with angular and coffee server(node+express) which gets view and public files via /app/.
View files:
app.set("view_engine", "html").engine "html", (path, options, fn) ->
if "function" is typeof options
fn = options
options = {}
fs.readFile path, "utf8", fn
Public files:
app.use express.static(path.resolve(__dirname + "/app/"))
I do load a lot of components like bootstrap, theme files,etc. HOwever, Javascript inside a view file doesnt work. It does work normally.
For example, if i remove and replace with for morris charts, it works. The same with ng-app and a view file with does not load the chart.
I think the problem is loading the js files first, since when i tried logging, the javascript file sends message before the controller for the view. So i guess the js file loads before the view thereby making the id inaccessible for the javascript file.
Please tell me how to solve this. It has been bugging me for over two days.
Thanks in advance.
Probably answered here: https://stackoverflow.com/a/12200540/1794563
Are you including jQuery before angularJS? If not, angularJS is using jqLite, which won't handle a situation like that.

Resources