34

I'm changing a project from an "old" browser-style module structure to a "new" browser-or-server-side-javascript module structure with require.js.

On the client I'm using an offsite hosted jQuery, so I started from the example they give in the "use priority config" technique of the README:

<title>My Page</title>
<script src="scripts/require.js"></script>
<script>
require({
    baseUrl: 'scripts',
    paths: {
        jquery: 'http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min',
        jqueryui: ...,
        ...
        ... // bunch more paths here
    },
    priority: ['jquery']
}, [ 'main' ]);
</script>

This is actually working all right. But I'd like to export functionality from main to the HTML webpage itself. For instance:

<a class="button" href="#" onclick="MyApi.foo();">
    <img src="foo.png" alt="foo" />Click for: <b>Foo!</b>
</a>

Before fitting into the AMD module pattern, I'd exposed functionality from my various files by creating a dictionary object in the global space:

// main.js

var MyApi = {};

jQuery(document).ready(function($) {
    // ...unexported code goes here...

    // export function via MyApi
    MyApi.foo = function() {
        alert("Foo!");
    };
});

But I don't know what the right approach in require.js is. Is it okay to put in the HTML more require statements inside of <script> tags, and then name modules so that it can be used from within the webpage? Or should this always be done dynamically inside of main.js, like $('#foobutton').click(...)?

4 Answers4

32

One benefit from using AMD is to drop the need for namespaces. Trying to create them again with RequireJS will go against the patterns AMD promotes.

With regard to using main.js, this is only really appropriate when you have a single page app and all your code be reference from one place. You're free to make additional calls to require and load other modules as you need them.

Using your example from above, you could approach it this way:

foo.js

define(['jquery'], function($) {

    // Some set up 
    // code here

    // Return module with methods
    return {
        bar: function() {

        }
    }


});

page.js

require(['foo'], function(foo) {

    // jQuery loaded by foo module so free to use it
    $('.button').on('click', function(e) {
        foo.bar();
        e.preventDefault();
    });

});

Then in your page request the page.js file with require.

Using your example above:

require({
    // config stuff here
}, [ 'page' ]);

Or loading it in the page further down:

<script>
    require(['page']);
</script>

Some additional points

  • Using the pattern above, page.js could easily require many other modules to load other page related functionality.

  • Where before you would attach members to your global namespace, you now split that code into separate modules that can be re-used on any page. All without reliance on a global object.

  • Using require this way to attach events to your DOM elements will most likely rely on the DOM Ready module provided by RequireJS

Simon Smith
  • 8,024
  • 2
  • 30
  • 40
  • Hey, thanks (just getting back to this project and looking in detail at the answer). You put into words the reason why putting code in onclick isn't a fit with this methodology; handlers directly in the page's HTML would require any API they call to have the same imported name across all .js files that are loaded (er...right?). But by that token...in this pattern, shouldn't page.js be explicitly mentioning the jquery mapping to `$` instead of inheriting that just because that's what foo imported it as? – HostileFork says dont trust SE May 08 '12 at 13:39
  • I've always thought of modules in the sense of them being re-usable, categorized bits of code. But with this approach everything has to be thrown into a module and there's no clear-cut distinction between regular stuff like event binding and actual modules - if you get what I mean. Is there no other way? – backdesk May 22 '13 at 21:12
  • In the question the DOM is dependant on the global MMyApi object. In this answer the page module is dependant on the DOM, which is also global. There has to be some kind of dependency. Which one to use is perhaps a subjective choice. – Arne Evertsson Feb 25 '14 at 14:46
16

You can also set the reference on javascript's window class.

At the bottom of the application module window.MyApi = MyApi;

<a class="button" href="#" onclick="MyApi.foo();"></a>

jonperl
  • 871
  • 1
  • 10
  • 22
3

putting onclick="" inside your markup feels dirty -- mixing presentation with content. I'd recommend you add a <script> block and put the require in it, and once inside the callback, and inside another inner $(document).ready() if necessary then wire up your event handlers in the normal way: $('#node').click(...). If you can do this generically enough that it's applicable to multiple pages, then put it inside an external file that is minified, combined, and cached. But typically these things end up being page-specific, so a <script> tag at the bottom of the page is a good solution to avoid yet another external resource request.

robrich
  • 13,017
  • 7
  • 36
  • 63
  • 2
    +1 *a few days ago :-)* ... yes on presentation and content, though it feels like web programming is such sticks and glue that the concrete value of the convoluted "best practice" is often outweighed by simpler methods. I saw a similar argument made about the HTML5 boilerplate with a "when did we let this stuff go to our heads". ( http://csswizardry.com/2011/01/the-real-html5-boilerplate/ ) But @SimonSmith's *"if you're going to use require.js then importing to page scope is bad, and thats what you'd have to do for code in onclick to see anything"* makes sense to me in an "if A then B" way... – HostileFork says dont trust SE May 08 '12 at 13:58
  • Personally, I think the utility of being able to tell what a button does, without having to search the code and probably still not find it, strongly outweighs any possible advantages of splitting that off. When you see a button tag with an "onclick" in it, there's no guessing about what happens when the user clicks that button. HTML is not pure presentation and never will be, and that's ok with me. – Jasmine May 22 '17 at 21:59
2

Another solution is just use code like that (of course requirejs must be added to the page):

<input type="button" onclick="(function() { require('mymodule').do_work() })()"/>
feeeper
  • 2,865
  • 4
  • 28
  • 42