Permalink
Browse files

Remove dependency on `window` throughout Ember

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 305202fa51aca55ce1ff935aa9fb8ebe3da4a0d4
Showing with 306 additions and 196 deletions.
  1. +16 −11 packages/ember-application/tests/system/application_test.js
  2. +6 −6 packages/ember-handlebars/lib/ext.js
  3. +6 −3 packages/ember-handlebars/tests/controls/button_test.js
  4. +26 −14 packages/ember-handlebars/tests/handlebars_test.js
  5. +7 −2 packages/ember-handlebars/tests/helpers/each_test.js
  6. +9 −3 packages/ember-handlebars/tests/helpers/with_test.js
  7. +5 −6 packages/ember-handlebars/tests/helpers/yield_test.js
  8. +5 −2 packages/ember-handlebars/tests/loader_test.js
  9. +12 −8 packages/ember-handlebars/tests/views/collection_view_test.js
  10. +3 −3 packages/ember-metal/lib/accessors.js
  11. +2 −2 packages/ember-metal/lib/binding.js
  12. +12 −9 packages/ember-metal/lib/core.js
  13. +5 −5 packages/ember-metal/lib/mixin.js
  14. +17 −16 packages/ember-metal/tests/accessors/setPath_test.js
  15. +12 −8 packages/ember-metal/tests/observer_test.js
  16. +2 −2 packages/ember-metal/tests/utils/meta_test.js
  17. +1 −1 packages/ember-routing/lib/routable.js
  18. +19 −0 packages/ember-routing/lib/router.js
  19. +29 −23 packages/ember-routing/tests/routable_test.js
  20. +1 −1 packages/ember-runtime/lib/mixins/target_action_support.js
  21. +1 −1 packages/ember-runtime/lib/system/namespace.js
  22. +3 −1 packages/ember-runtime/tests/core/isArray_test.js
  23. +39 −31 packages/ember-runtime/tests/legacy_1x/mixins/observable/observable_test.js
  24. +12 −4 packages/ember-runtime/tests/mixins/target_action_support_test.js
  25. +2 −3 packages/ember-runtime/tests/suites/enumerable/forEach.js
  26. +2 −2 packages/ember-runtime/tests/suites/enumerable/map.js
  27. +16 −11 packages/ember-runtime/tests/system/namespace/base_test.js
  28. +3 −2 packages/ember-views/lib/core.js
  29. +1 −1 packages/ember-views/lib/views/view.js
  30. +6 −4 packages/ember-views/tests/system/controller_test.js
  31. +7 −3 packages/ember-views/tests/views/view/attribute_bindings_test.js
  32. +13 −4 packages/ember-views/tests/views/view/init_test.js
  33. +6 −4 packages/ember-views/tests/views/view/view_lifecycle_test.js
@@ -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;
@@ -334,4 +339,4 @@ test("Minimal Application initialized with an application template and injection
});
equal(Ember.$('#qunit-fixture').text(), 'Hello Kris!');
});
});
@@ -1,5 +1,3 @@
/*globals Handlebars */
require("ember-views/system/render_buffer");
/**
@@ -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
@@ -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;
};
@@ -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();
@@ -16,6 +20,7 @@ module("Ember.Button", {
dispatcher.destroy();
});
Ember.TESTING_DEPRECATION = false;
Ember.lookup = originalLookup;
}
});
@@ -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;
@@ -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() {
@@ -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
@@ -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() {
@@ -82,7 +85,7 @@ module("Ember.View - handlebars integration", {
});
view = null;
}
window.TemplateTests = undefined;
Ember.lookup = originalLookup;
}
});
@@ -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\"}}"),
@@ -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",
@@ -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();
});
});
@@ -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
});
@@ -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"
});
});
@@ -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();
});
});
@@ -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); };
@@ -1958,6 +1965,7 @@ module("Ember.View - handlebars integration", {
}
Ember.Logger.log = originalLog;
Ember.lookup = originalLookup;
}
});
@@ -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;
}
});
@@ -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' });
@@ -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();
});
});
@@ -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" }]);
@@ -16,7 +20,7 @@ module("the #each helper", {
templateMyView = templateFor("{{name}}");
window.MyView = Ember.View.extend({
lookup.MyView = Ember.View.extend({
template: templateMyView
});
@@ -28,6 +32,7 @@ module("the #each helper", {
view.destroy();
view = null;
});
Ember.lookup = originalLookup;
}
});
@@ -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}}")
});
@@ -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",
@@ -21,6 +24,7 @@ module("Handlebars {{#with}} helper", {
Ember.run(function(){
view.destroy();
});
Ember.lookup = originalLookup;
}
});
@@ -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}}")
});
@@ -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");
@@ -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(){
@@ -15,8 +15,7 @@ module("Support for {{yield}} helper (#307)", {
}}
);
window.TemplateTests = undefined;
Ember.lookup = originalLookup;
}
});
@@ -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;
}
});
Oops, something went wrong.

4 comments on commit 305202f

@kitsunet

This comment has been minimized.

Show comment
Hide comment
@kitsunet

kitsunet Oct 16, 2012

Thanks! This helps encapsulation in our project a lot 👍

kitsunet replied Oct 16, 2012

Thanks! This helps encapsulation in our project a lot 👍

@wycats

This comment has been minimized.

Show comment
Hide comment
@wycats

wycats Oct 16, 2012

Member

@kitsunet Awesome!

Member

wycats replied Oct 16, 2012

@kitsunet Awesome!

@rodrigoalvesvieira

This comment has been minimized.

Show comment
Hide comment
@rodrigoalvesvieira

rodrigoalvesvieira replied Oct 16, 2012

Great coding, @wycats!

@mysterycommand

This comment has been minimized.

Show comment
Hide comment
@mysterycommand

mysterycommand Dec 23, 2012

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?

mysterycommand replied Dec 23, 2012

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.