CakePHP: Correct way to organize JavaScript - javascript

I'm building a CakePHP application. The code is structured logically so that each controller is considered it's own mini application.
I'm using jQuery for all my JavaScript functionality.
My question is: what is the right way to structure the JavaScript so that only the pages that require it have it?
Should I have separate js files per app. That way App 1 will have app1.js included in all of it's views, App 2 will have only app2.js and so on. I can then have a main.js which has all general functionality, and it's included in all the pages.
Note: I'd prefer not to use the JsHelper and I don't want to write JS inline.

I would recommend you to take a look on this Autoload Plugin written by this marvelous guy ... oops, by me :).
So, using it you can easily split your javascript and even CSS into separate directories and you will have separate files included on each controller.
If you want to follow the trend - use RequireJS which give you ability to modularize your code and include only those pieces which you needed.
Although Autoload is my creation, I switched to RequireJS and I am quite happy with the results.
I will explain my approach with RequireJS.
What you need is the following code in the head section of your layout:
<script>
<?php if(is_file(
__DIR__.'/../../webroot/js/action/'.
$this->request->params['controller'].'/'.
$this->request->params['action'].'.js'
)){ ?>
var rPath = root+'js/app/action/<?php echo
$this->request->params['controller'].'/'.
$this->request->params['action']; ?>';
<?php } ?>
</script>
At the bottom of layout you need to include the requirejs file:
<script
data-main="<?php echo $this->Html->url('/'); ?>js/app"
src="<?php echo $this->Html->url('/'); ?>js/libs/requirejs.js">
</script>
The first piece just check if there is file for that specific controller and action in the folder like:
/wwwroot/js/actions/Posts/write.js
and if so, add a the var rPath which contain reference to that file.
Here is the example of RequireJS config file which I am using:
require.config({
baseUrl: root+'js/',
paths: {
jquery : 'libs/jquery.min',
//...your convenient shortcuts
}
});
//That's where the magic happen
if(typeof(rPath) !== 'undefined'){
requirejs([rPath]);
}
Finally if you need some javascript in your controller Posts and your action write you need to create a file in:
/wwwroot/js/app/Posts/write.js with the following content:
define([
'jquery', //reference to jquery
'app/Posts/controller' //module which you wrote for controller specific functions.
//other libs or modules if needed.
], function($){
//your functions for wirite action
});
Take a look on RequireJS documentation for more information.
Hope that helps.

Related

How to properly configure requireJS

Hi I'm trying to make starting template for SPA project mainly using:
RequireJS, KnockoutJS, TypeScript, etc.
I'm having hard time figuring out how to configure paths and folder structure for RequireJS to work properly...
here is my folder structure:
Scripts
app
components
main.js
lib
knockout.js
jquery.js
here is my RequireJS config file:
var config = {
waitSeconds: 15,
paths: {
app: '../app',
'knockout': '/lib/knockout-3.4.2.',
sammy: '/lib/sammy-0.7.5.',
jquery: '../scripts/lib/jquery-1.10.2.'
}
};
This is my attempt for main.js:
define(['jquery', 'PageOne', 'PageTwo'], function ($, pageOne, pageTwo) {
$(document).ready(function () {
var app = Sammy('#main', function () {
this.get('#/pageOne', function () {
pageOne.activate();
});
this.get('#/pageTwo', function () {
pageTwo.activate();
});
});
app.run();
});
});
Here is my Index.cshtml script tag:
<script src="~/Scripts/lib/require.js" data-main="scripts/app/components/main"></script>
I saw in different project that config is called in header so this is in html header:
<script src="~/Scripts/app/config/require.config.js"></script>
My problem is that in main.js it looks for jquery under path defined in data-main (scripts/app/components/), but my jquery is in scripts/lib folder.
I'm trying to figure out by reading online the whole day but it's too much time for me I need someone to give me some hints how is this supposed to work?
Seriously having hard time figuring this out and RequireJS website just isn't helping me atm.
Note: I am beginner in JavaScript based projects, first SPA attempt,
never used RequireJS...
Your configuration file does not do anything. I'm assuming from your description that the script element that loads it is located before the script element that loads RequireJS. That's one valid way to configure RequireJS, but if you want RequireJS to pick up the configuration, you need to set the global variable require before you load RequireJS, and RequireJS will use the value of require as its configuration. Right now you are setting config, which is ignored by RequireJS. So:
var require = {
waitSeconds: 15,
// etc...
And once the configuration is in effect, you should be able to reduce your data-main to data-main="components/main".
I see some of your paths in the paths configuration end with a dot. That's most likely a mistake on your part, or you have some very strange file names.

Remove scripts from index.html through gulp

I'am using gulp in our application, we have 2 flows in Gulpfile.js, one for production and second for development, but I dont want to keep 2 index.html files e.g. index.html and index.dev. html, I want to have one index.html file, but for production build I have scripts which are no needed e.g .
<!--dev depends -->
<script src="angular-mocks/angular-mocks.js"></script>
<script src="server.js"></script>
<!--dev depends -->
question is: How can I remove something from html through Gulp ?
You can use the gulp-html-replace plugin which is intended for this specific purpose :
https://www.npmjs.org/package/gulp-html-replace
You could approach is slightly differently: templatize your index.html and use the gulp-template plugin to process the template in your build:
var template = require('gulp-template');
//production task
gulp.task('prod', function () {
return gulp.src('src/index.html').pipe(template({
scripts : []
})).pipe(gulp.dest('dist'));
});
//dev task
gulp.task('prod', function () {
return gulp.src('src/index.html').pipe(template({
scripts : ['angular-mocks/angular-mocks.js', 'server.js']
})).pipe(gulp.dest('dist'));
});
and your index.html could be turned into a template like so:
<% _.forEach(scripts, function(name) { %><script src="<%- name %>" type="text/javascript"></script><% }); %>
Of course you could write your own plugin / pass-through stream that removes scripts from your index.html but it would require actual parsing / re-writing of the index.html. personally I find the template-based solution easier to put in place and more "elegant".

Adding RequireJS module that uses jquery on a page that already has jquery as a global

I have an add-on to an application (call it appX) that allows users to create their own customizations using javascript, css and appX's webservices api.
Usually customizations are small and do not involve a lot of external libraries/plugins but when they do have external libs the typical users' library of choice is jQuery.
In the next version of appX they are using jQuery natively which I know is going to break some of the customizations.
So I have a need to modularize this situation. I have some other problems that are coming up and RequireJS seems like a good solution to these issues. I just need to figure out how to apply RequireJS properly for this situation
In my POC I'm loading require.js as follows:
<!--A bunch of other script including jQuery (but not require) are loaded already -->
<script type="text/javascript" src="/custom/js/require.js"></script>
<script type="text/javascript" src="/custom/js/dostuff.js"></script>
We'll call the jQuery loaded with appX jqueryx and the one I want to load jqueryp (p for private)
jQuery utilizes AMD and by default uses this definition internally:
define( "jquery", [], function () { return jQuery; } );
But in this case RequireJS is loaded AFTER jQuery (jqueryx) so there will be no default 'jquery' definition correct?
Some more background before I show you my problem... the file structure is like this:
appx
/js:
jqueryx.js
other.js
appx
/custom/js:
jqueryp.js
dostuff.js
Looking at the RequireJS api it seems that I should be doing something like this:
require.config({
baseUrl : 'custom/js',
paths : { 'jquery' : 'jqueryp'},
map: {
'*': { 'jquery': 'jquery-private' },
'jquery-private': { 'jquery': 'jquery' }
}
});
define(['jquery'], function (jq) {
return jq.noConflict( true );
});
require(['jquery'], function(jq){
console.log(jq.fn.jquery);
});
But when I do this I get an error:
Mismatched anonymous define() module: function (jq)...
I've played around with switching references to jquery, jquery-private as it's kind of confusing but with no progress.
How should I be applying RequireJS to this situation?
Almost a year late but better than no answer at all...
The following part should be moved into a "jquery-private.js" file:
define(['jquery'], function (jq) {
return jq.noConflict( true );
});
You can't define a module in your main entry point. Even if you could, the module has no name so how would you reference it?

requirejs - combine several files to single js file that not dependent on requirejs

I am writing jQuery plugin that contains a lot of code. Therefore I decided to separate the code and make it more modular for me (the developer). For this I use require.js.
Now I have 6 js files:
utils.js
base-row.jas
a-row.js
b-row.js
my-table.js
main.js
Files 1 to 5 defines JavaScript "classes" and they have dependecies between themselves. The "primary" class that operates all the concert is my-table.js. main.js has dependency only to my-table.js and creates a plugin from it:
require([
'my-table'
], function(MyTable) {
jQuery.fn.myTable = function(options) {
var table = new MyTable(this, options);
this.data('myTable', table);
return this;
};
});
Now I want to create from those files one big js file that contains all the 6 files without any dependency (except jQuery that the user should put reference to it). For this I used r.js (http://requirejs.org/docs/optimization.html) and as a result I got one big js file that depend on require.js (and contains calls to define and require). I followed this: http://requirejs.org/docs/faq-optimization.html#wrap and used almond.js in order to combined all my files for usage that is not dependent on require.js. This works fine.
The problem is why do I need all the define and require method calls and almond.js? Why couldn't the optimizer concatenate only the function results (as describe in this question: Why do concatenated RequireJS AMD modules need a loader?) like this:
(function() {
var utils = «function() {
....
return Utils;
}»();
var baseRow = «function(A) {
....
return BaseRow;
}»(utils);
....
....
var myTable = .....
//<--This is require call and therefore doesn't return a thing
(function(MyTable) {
jQuery.fn.myTable = function(options) {
var table = new MyTable(this, options);
this.data('myTable', table);
return this;
};
})(myTable);
})();
As a result of this process, I decided to check things out and combined manually all the files to one minified file. I end up with a file smaller by 3k then the almond version!
I don't find the logic behind the r.js optimizer creating require.js dependent result. In my case, no one will need to use any of the files, my primary js file is the only consumer. What do you think?
The default value for the findNestedDependencies option in the optimizer is "false", meaning that even after the scripts are optimized, there could still be a nested require or define call that would require a module loader. A loader is also needed for external dependencies.
I agree, however, that if findNestedDependencies is set to "true" and no external dependencies are part of the project, the optimizer should be able to remove its need for a loader.
You can just include the files needed in the file. You can do that with a getScript call.
$.getScript("my_lovely_script.js", function(){
//whatever you want here.
});

Any way to do conditional including in javascript?

We're developing a portal with lots of portlets (independent application within the page/portal). Each portlets have to be independent : They have to be able to run on stand-alone page from within the portal.
We've been ask not to add tons of javascript files to the portal base-page (the one that calls everything). It also comes with dojo (but no one uses it).
Are there any way to load javascript files (including jQuery aka, it can't be the solution) if they are not loaded yet? The answer can use dojo
Right now we though of
if (!window.jQuery) {
document.write('<script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"><' + '/script>');
}
if (!window.jQuery.ui) {
document.write('<script src="/Scripts/jquery-ui-1.8.11.min.js" type="text/javascript"></scr' + 'ipt>');
}
[...] other includes
The problem with this is that jquery isn't loaded when the jQuery.ui test is done, so an error is thrown and the 2nd file is not loaded.
Edit
Re-writing the issue : The problem is that we could have 4 portlets, each requiring jQuery + jQuery-ui + differents others plugins/files. So they need to all include code to load all those files independantly. But we don't want to load jQuery and jQuery-ui 4 times either.
The solution to this seems to be to use separate script blocks. Apparently the document.write will not effect the loading of the scripts, until the script block closes.
That is, try this:
<script>
if (!window.jQuery) {
document.write('<script src="/Scripts/jquery-1.5.1.min.js" type="text/javascript"><' + '/script>');
}
</script>
<script>
if (!window.jQuery.ui) {
document.write('<script src="/Scripts/jquery-ui-1.8.11.min.js" type="text/javascript"></scr' + 'ipt>');
}
</script>
Works for me. Tested in IE and Firefox.
Misread the question slightly (can and can't look very similar).
If you're willing to use another library to handle it, there are some good answers here.
loading js files and other dependent js files asynchronously
I've always injected js files via js DOM manipulation
if (typeof jQuery == 'undefined') {
var DOMHead = document.getElementsByTagName("head")[0];
var DOMScript = document.createElement("script");
DOMScript.type = "text/javascript";
DOMScript.src = "http://code.jquery.com/jquery.min.js";
DOMHead.appendChild(DOMScript);
}
but it's a bit picky and may not work in all situations
Just write your own modules (in Dojo format, which since version 1.6 has now switched to the standard AMD async-load format) and dojo.require (or require) them whenver a portlet is loaded.
The good thing about this is that a module will always only load once (even when a portlet type is loaded multiple times), and only at the first instance it is needed -- dojo.require (or require) always first checks if a module is already loaded and will do nothing if it is. In additional, Dojo makes sure that all dependencies are also automatically loaded and executed before the module. You can have a very complex dependency tree and let Dojo do everything for you without you lifting a finger.
This is very standard Dojo infrastructure. Then entire Dojo toolkit is built on top of it, and you can use it to build your own modules as well. In fact, Dojo encourages you to break your app down into manageable chunks (my opinion is the smaller the better) and dynamically load them when necessary. Also, leverage class hierachies and mixins support. There are a lot of Dojo intrastructure provided to enable you to do just that.
You should also organize your classes/modules by namespaces for maximal manageability. In my opinion, this type of huge enterprise-level web apps is where Dojo truely shines with respect to other libraries like jQuery. You don't usually need such infrastructure for a few quick-and-dirty web pages with some animations, but you really appreciate it when you're building complicated and huge apps.
For example, pre-1.6 style:
portletA.js:
dojo.provide("myNameSpace.portletA.class1");
dojo.declare("myNameSpace.portletA.class1", myNameSpace.portletBase.baseClass, function() { ...
});
main.js:
dojo.require("myNameSpace.portletA.class1");
var myClass1 = new myNameSpace.portletA.class1(/* Arguments */);
Post-1.6 style:
portletA.js:
define("myNameSpace/portletA/class1", "myNameSpace/portletBase/baseClass", function(baseClass) { ...
return dojo.declare(baseClass, function() {
});
});
main.js:
var class1 = require("myNameSpace/portletA/class1");
var myClass1 = new class1(/* Arguments */);
Pyramid is a dependency library that can handle this situation well. Basically, you can define you dependencies(in this case, javascript libraries) in a dependencyLoader.js file and then use Pyramid to load the appropriate dependencies. Note that it only loads the dependencies once (so you don't have to worry about duplicates). You can maintain your dependencies in a single file and then load them dynamically as required. Here is some example code.
File: dependencyLoader.js
//Set up file dependencies
Pyramid.newDependency({
name: 'standard',
files: [
'standardResources/jquery.1.6.1.min.js'
//other standard libraries
]
});
Pyramid.newDependency({
name:'core',
files: [
'styles.css',
'customStyles.css',
'applyStyles.js',
'core.js'
],
dependencies: ['standard']
});
Pyramid.newDependency({
name:'portal1',
files: [
'portal1.js',
'portal1.css'
],
dependencies: ['core']
});
Pyramid.newDependency({
name:'portal2',
files: [
'portal2.js',
'portal2.css'
],
dependencies: ['core']
});
Html Files
<head>
<script src="standardResources/pyramid-1.0.1.js"></script>
<script src="dependencyLoader.js"></script>
</head>
...
<script type="text/javascript">
Pyramid.load('portal1');
</script>
...
<script type="text/javascript">
Pyramid.load('portal2');
</script>
So shared files only get loaded once. And you can choose how you load your dependencies. You can also just define a further dependency group such as
Pyramid.newDependency({
name:'loadAll',
dependencies: ['portal1','portal2']
});
And in your html, just load the dependencies all at once.
<head>
<script src="standardResources/pyramid-1.0.1.js"></script>
<script src="dependencyLoader.js"></script>
<script type="text/javascript">
Pyramid.load('loadAll');
</script>
</head>
Some other features that might also help is that it can handle other file types (like css) and also can combine your separate development files into a single file when ready for a release. Check out the details here - Pyramid Docs
note: I am biased since I worked on Pyramid.

Categories

Resources