Как загрузить загруженные модели в Backbone.js при использовании AMD (require.js)

Документация Backbone.js позволяет загружать загруженные модели следующим образом:

<script>
var Accounts = new Backbone.Collection;
Accounts.reset(<%= @accounts.to_json %>);
var Projects = new Backbone.Collection;
Projects.reset(<%= @projects.to_json(:collaborators => true) %>);
</script>

Но это шаблон, который нельзя использовать в подход AMD (с использованием require.js)

Единственное возможное решение - объявить глобальную переменную, хранящую данные JSON, и использовать эту переменную позже в соответствующих методах инициализации.

Есть ли лучший способ для этого (без глобалов)?

Ответ 1

Вот как мы загружаем данные таким образом, чтобы они не загрязняли глобальное пространство имен. Вместо этого он использует require.js исключительно. Это также поможет вам обеспечить первоначальную конфигурацию приложения на основе переменных внутри шаблона.

В пределах отображаемой страницы

<script src="require.js"></script>
<script>
define('config', function() {
  return {
    bootstrappedAccounts: <%= @accounts.to_json %>,
    bootstrappedProjects: <%= @projects.to_json(:collaborators => true) %>
  };
});
</script>
<script src="app.js"></script>

globals.js

Этот файл проверяет конфигурацию и расширяет себя с помощью любого из возвращаемых данных

define([
  'config',
  'underscore'
], function(config) {

  var globals = {
  };
  _.extend(globals, config);
  return globals;

});

config.js

Этот файл необходим, если вы хотите иметь возможность загружать приложение независимо от того, если вы указали config на странице.

define(function() {
  // empty array for cases where `config` is not defined in-page
  return {};
});

app.js

require([
  'globals',
  'underscore',
  'backbone'
], function(globals) {

  if (globals.bootstrappedAccounts) {
    var accounts = new Backbone.Collection(globals.bootstrappedAccounts);
  }
  if (globals.bootstrappedProjects) {
    var projects = new Backbone.Collection(globals.bootstrappedProjects);
  }

});

Ответ 2

Похоже, вы можете использовать функцию require.config() или "require" global с опцией "config", чтобы передавать данные в модуль через специальный модуль "зависимостей". См. http://requirejs.org/docs/api.html#config-moduleconfig:

Существует общая необходимость передавать конфигурационную информацию в модуль. Что информация о конфигурации обычно известна как часть приложения, и должен быть способ передать это до модуля. В RequireJS, это делается с опцией config для requirejs.config(). Модули может затем прочитать эту информацию, запросив специальный модуль "модуль", и вызов module.config().

Итак, для моделей начальной загрузки мы имеем на странице HTML верхнего уровня:

<script>
var require = {
    config: {
        'app': {
            bootstrappedAccounts: <%= @accounts.to_json %>
            bootstrappedProjects: <%= @projects.to_json(:collaborators => true) %>
        }
    }
};
</script>
<script src="scripts/require.js"></script>

Затем в модуле приложения (app.js) мы имеем:

define(['module'], function (module) {
    var accounts = new Backbone.Collection( module.config().bootstrappedAccounts );
    var bootstrappedProjects = new Backbone.Collection( module.config().bootstrappedProjects );
});

Здесь "модуль" - это специальная зависимость, предоставляемая для этих типов случаев.

Это непроверено, но выглядит довольно убедительно из документации.

Ответ 3

В RequireJS это делается с опцией конфигурации для requirejs.config(). Затем модули могут прочитать эту информацию, запросив специальный модуль "и", и вызывая module.config(). Пример:

index.html

<script>
  var require = {
    config: {
      'app': {
        'api_key': '0123456789-abc'
      }
    }
  };
</script>
<script src="js/libs/require.js" data-main="js/main"></script>

main.js

require( ['app'], function(App) {
  new App();
});

app.js

define( ['module'], function(module) {
  var App = function() {
    console.log( 'API Key:', module.config().api_key );
  };

  return App;
});

Просто помните, что имя объекта конфигурации должно соответствовать имени модуля. В моем примере имя модуля было app, поэтому имя объекта конфигурации необходимо было называть app. В модуле вам нужно будет включить ['module'] в качестве зависимости и вызвать module.config()[property name] для получения данных конфигурации.

Прочитайте документацию об этом: http://requirejs.org/docs/api.html#config-moduleconfig

Ответ 4

Некоторые из ответов здесь приблизили меня к моей аналогичной проблеме, но ничего не прибило ее. В частности, верхний ранжированный и принятый ответ, казалось, дал мне неприятное состояние гонки, где иногда исходный объект загружался первым. Это также произошло в 100% случаев при использовании с оптимизатором. Он также использует явные имена строк для модуля, которые, как правило, требуют от вас документации, не рекомендуется.

Вот как я работал. Подобно Brave Dave, я использую объект config для захвата параметров (в моем случае с страницы jsp), например,

<script type="text/javascript">
    var require = {
        config: {
            options : { 
                bootstrappedModels : ${models}
            }
        }
    }
</script>

В частности, обратите внимание, что мои параметры находятся в объекте, называемом параметрами. Это имя не является обязательным! Несмотря на то, что в документации не упоминается об этом, ниже приведен пример того, как требуется загрузить вашу конфигурацию (строка 564 в requirejs 2.1.1):

config: function () {
    return (config.config && config.config[mod.map.id]) || {};
},

Ключевым моментом является то, что в объекте конфигурации должно быть свойство с ключом mod.map.id, который разрешает "параметры".

Теперь вы можете получить доступ к таким моделям

define(['module'], function(module){
    console.log(module.config().bootstrappedModels);
    //...
});

Ответ 5

Вы можете добавить функцию конца в конец вашего модуля AMD, чтобы проверить, когда определен метод init (чтобы он мог быть заполнен после тела или загружен из include). таким образом, модуль гарантированно доступен, и инициализация может произойти, когда он готов.

require(...,function (...) {
   //define models collections, etc..

   var initme = function () {
     if(document.initThisModule) 
       document.initThisModule();
     else
       setTimeout(initme, 10);
   }();
});

Ответ 6

Я не слишком хорошо знаком с подходом AMD, но вместо того, чтобы использовать глобальную переменную, почему бы вам не добавить JSON в dom.

например:

var json = ...,
$jsonContainer = $(json).wrap("<script id='json-container' type='text/javascript'>").appendTo($("body"));

Затем вместо встроенного тега script, предложенного базовой документацией, в документе готов:

$(function(){
    MyCollection.reset($("#json-container").html());
    ...
});

Ответ 7

Как сделать что-то вроде этого:

<script>
define('Models', ['backbone'], function(Backbone) {
    var Models = {
        Accounts: new Backbone.Collection,
        Projects: new Backbone.Collection
    };

    Models.Accounts.reset(<%= @accounts.to_json %>);
    Models.Projects.reset(<%= @projects.to_json(:collaborators => true) %>);

    return Models;
});
</script>

Затем вы сможете использовать Модели в других модулях, например:

var models = require(['Models']);
models.Accounts.doWhatYouNeed();

или это:

define(['any', 'dependencies', 'and', 'Models'], function(a, b, c, Models) {
   // Models will be available here
});

Ответ 8

Как описано выше, "модуль данных" (или конфиг, или все, что вы хотите назвать) может быть включен в файл, который уже сгенерирован в любом случае (например, index.html), но я думаю, что это довольно уродливо.

Другим способом было бы объявить его в своем собственном файле модуля, но для этого потребуется дополнительный переход к серверу в производственных средах. Как только вы захотите построить и оптимизировать свои зависимости requirejs, модуль данных не может быть включен, поскольку он динамически генерируется при загрузке страницы.

Третий вариант может состоять в том, чтобы добавить его в один из файлов, которые вы обслуживаете (например, оптимизированный файл requirejs), но я не знаю, как это сделать/если это можно сделать.

Ответ 9

Ansewr by @dlrust work, но он не способен расширять параметр и проходить больше, чем с одного места в коде. Если вы попытаетесь сделать что-то подобное в своем шаблоне рендеринга:

<script>
    define('config', function() {
        return {
            bootstrappedAccounts: <%= @accounts.to_json %>,
            bootstrappedProjects: <%= @projects.to_json(:collaborators => true) %>
        };
    });
</script>

а в другом файле добавьте некоторые данные

<script>
    define('config', function() {
        return {
            goods: <%= some data %>,
            showcaseList: <%= some json or array %>
        };
    });
</script>

он был перезаписан (НЕ РАСПРОСТРАНЯТЬ!!!). В config будут только последние объявленные данные.

Мое решение: используется модель Backbone с данными set/get.

app.js

define("App", [], function() {
    window.App = {
        // модели
        Model: {},
        // коллекции
        Collection: {},
        // виды
        View: {},
        // роутеры
        Router: {},
        // модальные окна
        Modal: {},
        // UI компоненты
        UI: {}
    };
    return window.App;
});

global.js

define(["App", "underscore", "backbone"], function(App, _, Backbone) {
    "use strict";

    // модель глобальных данных
    App.Model.Global = Backbone.Model.extend({
        defaults: {}
    });

    return new App.Model.Global;    
});

index.php

<!DOCTYPE html>
<html>
    <head>
        <!--HEAD_START-->
        <script type="text/javascript" data-main="/app/init" src="/app/require/require.js"></script>
        <!--HEAD_END-->
    </head>

    <body>          
        <div id="tm-inner-wrap">
            <div id="loader"><i class="uk-icon-refresh uk-icon-spin"></i></div>
            <!--HEADER_START-->
            <?= $this->includeTpl('header_view'); ?>
            <!--HEADER_END-->

            <!--CONTENT_START-->
            <div>your html content data</div>
            <!--CONTENT_END-->

            <!--FOOTER_START-->
            <?= $this->includeTpl('footer_view');?>
            <!--FOOTER_END-->

            <script>
                require(["global"], function(Global) {
                    Global.set("notifyList", <?=json_encode($this->notifyList);?>);
                });
            </script>
        </div>
    </body>
</html>

другой шаблон

someTemplate.php

<div class="tm-inner-body">
    <div class="uk-container uk-container-center">
        // content data
    </div>
</div>

<script>    
    require(["global", "module/index"], function(Global) {
        Global.set("goodList", <?=json_encode($this->goodList);?>);
    });
</script>

index.js

require(["App", "core", "jquery", "uikit!uikit-addons-min", "underscore", "backbone", "global", "module/good/goodView"], function(App, Core, $, UIkit, _, Backbone, Global, goodView) {
    "use strict";

    // Global.get("notifyList"); its too able

    App.Collection.Good = new Backbone.Collection(Global.get("showcaseList")["items"]);

    // вид списка товаров
    App.View.GoodList = Backbone.View.extend({
        // елемент
        el: ".tm-good-list",
        // init
        initialize: function() {
            this.collection = App.Collection.Good;
            // список товаров
            this.drawList();
        },
        // отрисовка списка
        drawList: function() {
            this.$el.empty();
            this.collection.each(function(item, index) {
                this.$el.append(this.drawItem(item));
            }, this);
        },
        // отрисовка елемента
        drawItem: function(data) {
            var good = new goodView({model: data});
            return good.render().el;
        }
    });

    App.View.Index = Backbone.View.extend({
        el: "body",
        // пользовательские события
        events: {
            //
        },
        // init
        initialize: function() {
            var $this = this;
            if(Global.get("showcaseList")) new App.View.GoodList();
        }
    });

    new App.View.Index();
});

Структура файла:

файловая структура