Skip to content

Commit

Permalink
Remove dependency on window throughout Ember
Browse files Browse the repository at this point in the history
Previously, Ember used the `window` object for
three concerns:

* `imports`: libraries that Ember requires in
  order to work, such as `jQuery`, `Handlebars`,
  and `Metamorph`
* `exports`: the variables that Ember itself
  exports (`Ember` and `Em`)
* `lookup`: the global lookup path for application
  code (e.g. when used in Handlebars templates).

Now, these three concerns are looked up on
`Ember.imports`, `Ember.exports` and
`Ember.lookup` respectively.

For convenience, all three of these are assigned
to `window` by default.

You can change the defaults by assigning a new
Ember object before loading Ember:

```javascript
var imports = {
  Handlebars: Handlebars,
  Metamorph: Metamorph,
  jQuery: jQuery
};

var exports = {}, lookup = {};

Ember = {
  imports: imports,
  exports: exports,
  lookup: lookup
}
```

You can use this to package Ember for external
module loaders like AMD:

```javascript
define("Ember",
  ["exports",
   "App",
   "handlebars",
   "metamorph",
    "jQuery"],
  function(exports, App, Handlebars, Metamorph, jQuery) {
    var imports = {
      Handlebars: Handlebars,
      Metamorph: Metamorph,
      jQuery: jQuery
    };

    var Ember = {
      imports: imports,
      exports: exports,
      lookup: { App: App }
    }

    // distributed ember.js
  });

define("App", [], function() {
  return {};
});

define("Post",
  ["App", "Ember"],
  function(App, Ember) {
    App.Post = Ember.Object.extend({
      // stuff
    });
  });
```

You can use a similar technique to package Ember
Runtime for Node or other environments with
CommonJS-style modules.
  • Loading branch information
wycats committed Oct 14, 2012
1 parent 451ef3d commit 305202f
Show file tree
Hide file tree
Showing 33 changed files with 306 additions and 196 deletions.
27 changes: 16 additions & 11 deletions packages/ember-application/tests/system/application_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,21 @@ test("you cannot make two default applications without a rootElement error", fun
});

test("acts like a namespace", function() {
var app;
Ember.run(function() {
app = window.TestApp = Ember.Application.create({rootElement: '#two'});
});
app.Foo = Ember.Object.extend();
equal(app.Foo.toString(), "TestApp.Foo", "Classes pick up their parent namespace");
Ember.run(function() {
app.destroy();
});
window.TestApp = undefined;
var originalLookup = Ember.lookup;

try {
var lookup = Ember.lookup = {}, app;
Ember.run(function() {
app = lookup.TestApp = Ember.Application.create({rootElement: '#two'});
});
app.Foo = Ember.Object.extend();
equal(app.Foo.toString(), "TestApp.Foo", "Classes pick up their parent namespace");
Ember.run(function() {
app.destroy();
});
} finally {
Ember.lookup = originalLookup;
}
});

var app;
Expand Down Expand Up @@ -334,4 +339,4 @@ test("Minimal Application initialized with an application template and injection
});

equal(Ember.$('#qunit-fixture').text(), 'Hello Kris!');
});
});
12 changes: 6 additions & 6 deletions packages/ember-handlebars/lib/ext.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
/*globals Handlebars */

require("ember-views/system/render_buffer");

/**
Expand All @@ -9,8 +7,8 @@ require("ember-views/system/render_buffer");

var objectCreate = Ember.create;


Ember.assert("Ember Handlebars requires Handlebars 1.0.beta.5 or greater", window.Handlebars && window.Handlebars.VERSION.match(/^1\.0\.beta\.[56789]$|^1\.0\.rc\.[123456789]+/));
var Handlebars = Ember.imports.Handlebars;
Ember.assert("Ember Handlebars requires Handlebars 1.0.beta.5 or greater", Handlebars && Handlebars.VERSION.match(/^1\.0\.beta\.[56789]$|^1\.0\.rc\.[123456789]+/));

/**
Prepares the Handlebars templating library for use inside Ember's view
Expand Down Expand Up @@ -234,8 +232,10 @@ Ember.Handlebars.getPath = function(root, path, options) {

value = Ember.get(root, path);

if (value === undefined && root !== window && Ember.isGlobalPath(path)) {
value = Ember.get(window, path);
// If the path starts with a capital letter, look it up on Ember.lookup,
// which defaults to the `window` object in browsers.
if (value === undefined && root !== Ember.lookup && Ember.isGlobalPath(path)) {
value = Ember.get(Ember.lookup, path);
}
return value;
};
Expand Down
9 changes: 6 additions & 3 deletions packages/ember-handlebars/tests/controls/button_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ var button, dispatcher;

var get = Ember.get, set = Ember.set;

var originalLookup = Ember.lookup, lookup;

module("Ember.Button", {
setup: function() {
lookup = Ember.lookup = {};

Ember.TESTING_DEPRECATION = true;
dispatcher = Ember.EventDispatcher.create();
dispatcher.setup();
Expand All @@ -16,6 +20,7 @@ module("Ember.Button", {
dispatcher.destroy();
});
Ember.TESTING_DEPRECATION = false;
Ember.lookup = originalLookup;
}
});

Expand Down Expand Up @@ -178,7 +183,7 @@ test("should not trigger an action when another key is pressed", function() {
test("should trigger an action on a String target when clicked", function() {
var wasClicked = false;

window.MyApp = {
lookup.MyApp = {
myActionObject: Ember.Object.create({
myAction: function() {
wasClicked = true;
Expand All @@ -199,8 +204,6 @@ test("should trigger an action on a String target when clicked", function() {
synthesizeEvent('mouseup', button);

ok(wasClicked);

window.MyApp = undefined;
});

test("should not trigger action if mouse leaves area before mouseup", function() {
Expand Down
40 changes: 26 additions & 14 deletions packages/ember-handlebars/tests/handlebars_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ var appendView = function() {
};

var additionalTeardown;
var originalLookup = Ember.lookup, lookup;
var TemplateTests;

/**
This module specifically tests integration with Handlebars and Ember-specific
Expand All @@ -72,7 +74,8 @@ var additionalTeardown;
*/
module("Ember.View - handlebars integration", {
setup: function() {
window.TemplateTests = Ember.Namespace.create();
Ember.lookup = lookup = { Ember: Ember };
lookup.TemplateTests = TemplateTests = Ember.Namespace.create();
},

teardown: function() {
Expand All @@ -82,7 +85,7 @@ module("Ember.View - handlebars integration", {
});
view = null;
}
window.TemplateTests = undefined;
Ember.lookup = originalLookup;
}
});

Expand Down Expand Up @@ -198,8 +201,6 @@ test("should not escape HTML if string is a Handlebars.SafeString", function() {
equal(view.$('i').length, 1, "creates an element when value is updated");
});

TemplateTests = {};

test("child views can be inserted using the {{view}} Handlebars helper", function() {
var templates = Ember.Object.create({
nester: Ember.Handlebars.compile("<h1 id='hello-world'>Hello {{world}}</h1>{{view \"TemplateTests.LabelView\"}}"),
Expand Down Expand Up @@ -1140,8 +1141,10 @@ test("{{view}} should be able to point to a local view", function() {
});

test("{{view}} should evaluate class bindings set to global paths", function() {
var App;

Ember.run(function() {
window.App = Ember.Application.create({
lookup.App = App = Ember.Application.create({
isApp: true,
isGreat: true,
directClass: "app-direct",
Expand Down Expand Up @@ -1172,7 +1175,7 @@ test("{{view}} should evaluate class bindings set to global paths", function() {
ok(view.$('input').hasClass('disabled'), "evaluates ternary operator in classBindings");

Ember.run(function() {
window.App.destroy();
lookup.App.destroy();
});
});

Expand Down Expand Up @@ -1205,8 +1208,10 @@ test("{{view}} should evaluate class bindings set in the current context", funct
});

test("{{view}} should evaluate class bindings set with either classBinding or classNameBindings", function() {
var App;

Ember.run(function() {
window.App = Ember.Application.create({
lookup.App = App = Ember.Application.create({
isGreat: true,
isEnabled: true
});
Expand Down Expand Up @@ -1236,13 +1241,13 @@ test("{{view}} should evaluate class bindings set with either classBinding or cl
ok(view.$('input').hasClass('really-disabled'), "evaluates ternary operator in classBindings");

Ember.run(function() {
window.App.destroy();
lookup.App.destroy();
});
});

test("{{view}} should evaluate other attribute bindings set to global paths", function() {
Ember.run(function() {
window.App = Ember.Application.create({
lookup.App = Ember.Application.create({
name: "myApp"
});
});
Expand All @@ -1256,7 +1261,7 @@ test("{{view}} should evaluate other attribute bindings set to global paths", fu
equal(view.$('input').attr('value'), "myApp", "evaluates attributes bound to global paths");

Ember.run(function() {
window.App.destroy();
lookup.App.destroy();
});
});

Expand Down Expand Up @@ -1944,6 +1949,8 @@ test("should be able to explicitly set a view's context", function() {

module("Ember.View - handlebars integration", {
setup: function() {
Ember.lookup = lookup = { Ember: Ember };

originalLog = Ember.Logger.log;
logCalls = [];
Ember.Logger.log = function(arg) { logCalls.push(arg); };
Expand All @@ -1958,6 +1965,7 @@ module("Ember.View - handlebars integration", {
}

Ember.Logger.log = originalLog;
Ember.lookup = originalLookup;
}
});

Expand Down Expand Up @@ -2003,16 +2011,18 @@ test("should be able to log `this`", function() {
equal(logCalls[1], 'two', "should call log with item two");
});

var MyApp;

module("Templates redrawing and bindings", {
setup: function(){
MyApp = Ember.Object.create({});
Ember.lookup = lookup = { Ember: Ember };
MyApp = lookup.MyApp = Ember.Object.create({});
},
teardown: function(){
Ember.run(function() {
if (view) view.destroy();
});
window.MyApp = null;
Ember.lookup = originalLookup;
}
});

Expand Down Expand Up @@ -2184,8 +2194,10 @@ test("bindings can be 'this', in which case they *are* the current context", fun
test("should not enter an infinite loop when binding an attribute in Handlebars", function() {
expect(0);

var App;

Ember.run(function() {
window.App = Ember.Application.create();
lookup.App = App = Ember.Application.create();
});

App.test = Ember.Object.create({ href: 'test' });
Expand Down Expand Up @@ -2216,7 +2228,7 @@ test("should not enter an infinite loop when binding an attribute in Handlebars"
});

Ember.run(function() {
window.App.destroy();
lookup.App.destroy();
});
});

Expand Down
9 changes: 7 additions & 2 deletions packages/ember-handlebars/tests/helpers/each_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ var templateFor = function(template) {
return Ember.Handlebars.compile(template);
};

var originalLookup = Ember.lookup, lookup;

module("the #each helper", {
setup: function() {
Ember.lookup = lookup = { Ember: Ember };

template = templateFor("{{#each people}}{{name}}{{/each}}");
people = Ember.A([{ name: "Steve Holt" }, { name: "Annabelle" }]);

Expand All @@ -16,7 +20,7 @@ module("the #each helper", {


templateMyView = templateFor("{{name}}");
window.MyView = Ember.View.extend({
lookup.MyView = Ember.View.extend({
template: templateMyView
});

Expand All @@ -28,6 +32,7 @@ module("the #each helper", {
view.destroy();
view = null;
});
Ember.lookup = originalLookup;
}
});

Expand Down Expand Up @@ -211,7 +216,7 @@ test("it supports {{itemViewClass=}} with tagName", function() {

test("it supports {{itemViewClass=}} with in format", function() {

window.MyView = Ember.View.extend({
lookup.MyView = Ember.View.extend({
template: templateFor("{{person.name}}")
});

Expand Down
12 changes: 9 additions & 3 deletions packages/ember-handlebars/tests/helpers/with_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,12 @@ var appendView = function(view) {
};

var view;
var originalLookup = Ember.lookup, lookup;

module("Handlebars {{#with}} helper", {
setup: function() {
Ember.lookup = lookup = { Ember: Ember };

view = Ember.View.create({
template: Ember.Handlebars.compile("{{#with person as tom}}{{title}}: {{tom.name}}{{/with}}"),
title: "Señor Engineer",
Expand All @@ -21,6 +24,7 @@ module("Handlebars {{#with}} helper", {
Ember.run(function(){
view.destroy();
});
Ember.lookup = originalLookup;
}
});

Expand Down Expand Up @@ -56,7 +60,9 @@ test("updating a property on the view should update the HTML", function() {

module("Handlebars {{#with}} globals helper", {
setup: function() {
window.Foo = { bar: 'baz' };
Ember.lookup = lookup = { Ember: Ember };

lookup.Foo = { bar: 'baz' };
view = Ember.View.create({
template: Ember.Handlebars.compile("{{#with Foo.bar as qux}}{{qux}}{{/with}}")
});
Expand All @@ -66,17 +72,17 @@ module("Handlebars {{#with}} globals helper", {

teardown: function() {
Ember.run(function(){
window.Foo = null;
view.destroy();
});
Ember.lookup = originalLookup;
}
});

test("it should support #with Foo.bar as qux", function() {
equal(view.$().text(), "baz", "should be properly scoped");

Ember.run(function() {
Ember.set(Foo, 'bar', 'updated');
Ember.set(lookup.Foo, 'bar', 'updated');
});

equal(view.$().text(), "updated", "should update");
Expand Down
11 changes: 5 additions & 6 deletions packages/ember-handlebars/tests/helpers/yield_test.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/*global TemplateTests*/

var set = Ember.set, get = Ember.get;

var view;
var originalLookup = Ember.lookup, lookup, TemplateTests, view;

module("Support for {{yield}} helper (#307)", {
setup: function() {
window.TemplateTests = Ember.Namespace.create();
Ember.lookup = lookup = { Ember: Ember };

lookup.TemplateTests = TemplateTests = Ember.Namespace.create();
},
teardown: function() {
Ember.run(function(){
Expand All @@ -15,8 +15,7 @@ module("Support for {{yield}} helper (#307)", {
}}
);


window.TemplateTests = undefined;
Ember.lookup = originalLookup;
}
});

Expand Down
7 changes: 5 additions & 2 deletions packages/ember-handlebars/tests/loader_test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
/*global Tobias:true*/
var originalLookup = Ember.lookup, lookup, Tobias;

module("test Ember.Handlebars.bootstrap", {
setup: function() {
Ember.lookup = lookup = { Ember: Ember };
},
teardown: function() {
Ember.TEMPLATES = {};
window.Tobias = undefined;
Ember.lookup = originalLookup;
}
});

Expand Down
Loading

4 comments on commit 305202f

@kitsunet
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! This helps encapsulation in our project a lot 👍

@wycats
Copy link
Member Author

@wycats wycats commented on 305202f Oct 16, 2012

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kitsunet Awesome!

@rodrigoalvesvieira
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great coding, @wycats!

@mysterycommand
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I'm pretty new to Ember. I'm interested in packaging it for Require/AMD, but I'm not sure what this commit description "means" in practice. In the define('Ember' call, where does "exports" come from, and where/when/how does this define call return a module? Does anyone have a working example of loading Ember via AMD in this way? Does this also keep Handlebars and jQuery out of the global namespace? Is there a way to do that?

Please sign in to comment.