3

I have the following html/js existing working code that I want to extract in their own files and load them using require.js.

My goal is to componentize it to be able to use it elsewhere.

.html

...    
<ul data-bind="foreach: selectedExams">
        <li>
            <select data-bind="options: $parent.availableExams, optionsText: 'examTypeName', optionsValue:'examTypeId', value: examtype"></select>
            <select data-bind="options: exams,  optionsText: 'examName', optionsValue:'examId',value: exam, enable:exams().length"></select>
            <a href="#" data-bind="click: $parent.remove">Remove</a>
        </li>
    </ul>
    <button data-bind="click: add">Add</button>
...

.js

    var self = this;
...
    self.availableExams = [...];

    self.selectedExams = ko.observableArray([new selectedExam(self)]);
    self.add = function () {
        self.selectedExams.push(new selectedExam(self));
    };
    self.remove = function (exam) { self.selectedExams.remove(exam) }
...

It seemed very simple at first, but I am confused because the viewmodel seems to need a complete rewrite.

Because I want to experiment and see how it works, following the docs, I've created a separate .html file with exactly the required content show above.

And the following autonomous .js file:

function ExamControlViewModel()
{
    var self = this;
    self.availableExams = [...];

    self.selectedExams = ko.observableArray([new selectedExam(self)]);
    self.add = function () {
        self.selectedExams.push(new selectedExam(self));
    };
    self.remove = function (exam) { self.selectedExams.remove(exam) }
}

ko.applyBindings(new ExamControlViewModel());

Next, I've removed the code from the original html/js files and added the following to the js file :

ko.components.register('exam-control', {
    viewModel: { require: 'exam-control-viewmodel' },
    template: { require: 'text!exam-control-view.html' }
})

It fails at runtime with the following error :

Script error for "text", needed by: text!exam-control-view.html_unnormalized2 http://requirejs.org/docs/errors.html#scripterror

Obviously I'm missing the point, This could not be that simple... Could it be ?

Any guidance appreciated.

EDIT 1:

I followed @JotaBe excellent instuctions and ended up with the following :

.js file

define([],function() {
    function ExamControlViewModel()
    {
        var self = this;
        self.availableExams = [...];

        self.selectedExams = ko.observableArray([new selectedExam(self)]);
        self.add = function () {
            self.selectedExams.push(new selectedExam(self));
        };
        self.remove = function (exam) { self.selectedExams.remove(exam) }
    }
    return ExamControlViewModel;
});

html file: => At the beginning

...
<script type='text/javascript' src='Scripts/knockout-3.4.0.js'></script>
<script type='text/javascript' src='Scripts/knockout.validation.js'></script>
<script type='text/javascript' src='Scripts/require.js'></script>
<script type='text/javascript' src='Scripts/text.js'></script>
...

=> The component code was replaced by (forgot to say that when asking the question)

<exam-control></exam-control>

Now at runtime, I have the following error:

Mismatched anonymous define() module

I'll update this thread again as I make progress.

2 Answers2

1

You must do these things:

  1. export your module constructor following the AMD pattern
  2. include the require.js text plugin so that the templates can be loaded by require
  3. configure require.js so that it can find your templates and scripts
  4. register the component

1. Export the viewmodel constructor

The AMD pattern to export the constructor looks like this:

define([/*dependencies*/],function() {
  // define the constructor you want to export
  function ExamControlViewModel() { ... }
  // export it
  return ExamControlViewModel;
});

This must be the only content of your exam-control-viewmodel.js file. You don't need to applyBindings: knockout will copy the template content and apply the binding when both the script and the template are loaded.

I suppose you have loaded knockout globally. If you've done so, keep the definition as explained. However, if you load ko and related stuff (like plugins) by using require.js, you should include it as a dependency, by making this change in your file:

define(['ko'],function(ko) {

Of course you need the right configuration paths to load knockout as ko. If not, you'll have to specify the whole file name (and path).

2. Include the require.js text lugin

The require.js text plugin is available here, and here you can see the related documentation. You can install it by copying it or using any of the available packages formats, like Nuget or npm.

3. Configure require.js

You have to configure require so that it can find the files, as explained here. You must pay special attention to baseUrl.

4. Register the component

The component registration is correct as you've done it. In particular 'text!exam-control-view.html' instructs require.js to use the text plugin to load the template. The .js file must have the specified name.

Becasuse of the way that require.js and the plugin work, as you've done, the .html extension must be included, and the .js must be removed.

If you have specified the correct configuration, the files will be read from the specified baseUrl. If not, you'll have to modify it. If they're not loaded correctly, you can use the browser's console to find what the problem is.

Of course, require.js, and the related configuration must be loaded before you use the components.

Community
  • 1
  • 1
JotaBe
  • 38,030
  • 8
  • 98
  • 117
  • Excellent guidance ! I have updated the question, I think I have a problem exporting the viewmodel constructor. –  Jan 07 '16 at 09:03
  • @omatrot Your question was long enough not to be read. And now that you find a new problem, instead of making a new question, you make your question even longer. You should only modify a question to include new relevant information, or to improve the wording. But please, don't make this kind of edition. Your new question could be this one which has already been answered: http://stackoverflow.com/questions/15371918/mismatched-anonymous-define-module Looking at the browser's console can help you to determine whatthe problem is. – JotaBe Jan 07 '16 at 10:26
  • @omatrot Your constructor AMD module is correct. Please, try to require both your script and template without ko, i.e. using directly `require(['',''], function() {})` in a simple .html page that only includes require. Then include all your scripts one by one. How did you setup require.js? I can't see where you did it. – JotaBe Jan 07 '16 at 10:30
  • require.js was installed via Nuget –  Jan 07 '16 at 10:36
  • No, I didn't mean how you installed it, but how you specified the configuration (the baseUrl and so on). How did you configured it? – JotaBe Jan 07 '16 at 10:41
  • It was point 3. I recommend you creating an additional script file, which I usually call 'require.config.js' which must be loaded before `require.js` itself. When you load `require.js` it detects the existing configuration, and applies it. Please, see this: https://github.com/jrburke/requirejs/wiki/Patterns-for-separating-config-from-the-main-module – JotaBe Jan 07 '16 at 12:36
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/100064/discussion-between-omatrot-and-jotabe). –  Jan 07 '16 at 15:16
  • I've used pattern number 5 to successfully to configure require.js. My component is successfully loaded in simple HTML page. Thanks for your help. –  Jan 08 '16 at 16:29
0

You should include text plugin for RequireJS. Here I have written a more detailed answer.

Community
  • 1
  • 1
Rajab Shakirov
  • 7,265
  • 7
  • 28
  • 42