Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

IE fixes #83

Merged
merged 8 commits into from

4 participants

@eastridge
Owner

Here are some fixes for everyone's favorite platform.

@mogstad there is one failing test "child view re-render will keep dom events intact" which is remaining that seems directly related to the jQuery fix you posted. Would it be possible for you to look into that for us?

@spelley the boilerplate projects are not updated to the latest, we will release a new version next week and I will ping you when it's tested.

@kpdecker We can't run the tests in IE8 because chai doesn't support it:

chaijs/chai#117

I'm not 100% sure that is the only issue though, I'm getting "Object doesn't support property or method 'create'". I've opened this ticket to get a response:

chaijs/chai#124

@eastridge
Owner

Note that the failing build is phantom only, same DOM issues we were running into before.

@eastridge eastridge referenced this pull request
Closed

IE8 - forEach error #82

@spelley

Thanks for the speedy response to this, its been a bit of a show stopper for a project of mine!

@eastridge
Owner

@spelly here's a build of this branch, can't promise that it will work on IE8 but it will work on IE9:

https://gist.github.com/raw/4356083/164b9c5fad307904e93fc9d24f98fb1780097fd5/gistfile1.txt

Need to use that with latest underscore and backbone (0.9.9)

@kpdecker
Owner

Commit comment: :)

Do the getters still work here or are these statements NOPing?

The statements are causing a parse error unless in bracket syntax =P

Owner

Yeah I understand that part but my concern is that they might not be running if the defineProperty API is not supported... which may be why Chai doesn't work on IE8...

Not sure, opened up this ticket:

chaijs/chai#124

Might be worth pinging @logicalparadox to see what the specific issues are preventing it from running.

Yes, IE8's lack of a proper Object.defineProperty is the reason we do not support IE8. As noted in chaijs/chai#124, we are exploring ways to provide some IE8 support in our next major release, but we aren't there yet. Stay tuned to that issue for progress.

If the brackets are a bother, you can just do the following, as they are the same...

expect(something).to.equal(true);
expect(something).to.eq(true);

Also, we provide some capitalized aliases for other scenarios.

expect(myFn).to.Throw(Error);
src/helpers/element.js
@@ -10,15 +10,26 @@ Handlebars.registerHelper('element', function(element, options) {
return new Handlebars.SafeString(Thorax.Util.tag(htmlAttributes));
});
+// IE will lose a reference to the elements if view.el.innerHTML = '';
+// If they are removed one by one the references are not lost
+Thorax.View.on('before:append', function() {
@kpdecker Owner

I don't follow this comment. What is lost where?

If we have to do this lets show this into a IE specific module so others don't have to pay for IE's sins, particularly mobile.

@eastridge Owner

I believe the behavior is if you set innerHTML on a parent the child nodes references you have will become null unless they are explicitly removed.

@kpdecker Owner

Which node references?

@eastridge Owner

view.el

@kpdecker Owner

How exactly does setting innerHTML on a parent DOM object cause a JS heap object to have a reference nulled out?? That's insane. If that actually is the case we need better documentation of what is happening in the source so you don't have a WTF double take in the future... or at least don't have a WTF double take that causes the code to be removed when it's still needed.

@kpdecker Owner

Can you find outside discussion of this bug?

@eastridge Owner

I'm having trouble finding something documenting the behavior on MSDN, here are some links that dance around the issue but don't quite get to the meat of it:

http://ajaxian.com/archives/the-problem-with-innerhtml
http://www.julienlecomte.net/blog/2007/12/38/
http://james.padolsey.com/javascript/replacing-text-in-the-dom-its-not-that-simple/

This fix at the very least will allow all of our unit tests to pass on IE9. Do mind if we merge this in for now and revisit more IE madness when / if we approach IE8?

@kpdecker Owner

I'm fine with merging if the IE disease is limited to the separate module and we document the behavior seen without the code above thoroughly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
src/helpers/view.js
@@ -22,6 +22,16 @@ Handlebars.registerHelper('view', function(view, options) {
return new Handlebars.SafeString(Thorax.Util.tag(htmlAttributes, undefined, expandTokens ? this : null));
});
+// IE will lose a reference to the elements if view.el.innerHTML = '';
+// If they are removed one by one the references are not lost
+Thorax.View.on('before:append', function() {
@kpdecker Owner

Duplicating the code and the event execution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
@eastridge
Owner

@kpdecker, going to push another commit into this and close out. Takeaways for now:

  • Make sure demos run in IE8
  • Switch away from Chai if 2.x will not support IE8 (TBD)
  • Going to move the element fix into an IE specific file that only get's included in the jQuery build
@eastridge eastridge merged commit 8a8bec2 into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Dec 21, 2012
  1. Fix element helper bug in IE

    Ryan Eastridge authored
  2. Remove elements before innerHTML is set for IE

    Ryan Eastridge authored
  3. Is 'fuckyouie8' a reserved word?

    Ryan Eastridge authored
Commits on Dec 26, 2012
  1. Merge branch 'master' into ie-fixes

    Ryan Eastridge authored
Commits on Dec 27, 2012
  1. Merge branch 'master' into ie-fixes

    Ryan Eastridge authored
  2. Move element fix to IE specific file

    Ryan Eastridge authored
This page is out of date. Refresh to see the latest.
View
3  build.json
@@ -24,7 +24,8 @@
"mixins": [
"thorax",
"thorax-helper-tags",
- "thorax-loading"
+ "thorax-loading",
+ "thorax-ie"
]
},
"thorax-mobile": {
View
6 lumbar.json
@@ -39,6 +39,12 @@
]
},
+ "thorax-ie": {
+ "scripts": [
+ {"src": "src/ie.js"}
+ ]
+ },
+
"thorax-helper-tags": {
"scripts": [
{"src": "src/helpers/button-link.js"},
View
8 src/data-object.js
@@ -55,12 +55,12 @@ function dataObject(type, spec) {
_.extend(Thorax.View.prototype, {
bindDataObject: function(dataObject, options) {
var type = getDataObjectType(dataObject);
- if (this._boundDataObjects.indexOf(dataObject) !== -1) {
+ if (this._boundDataObjectsByCid[dataObject.cid]) {
return false;
}
// Collections do not have a cid attribute by default
ensureDataObjectCid(type, dataObject);
- this._boundDataObjects.push(dataObject);
+ this._boundDataObjectsByCid[dataObject.cid] = dataObject;
var options = this._modifyDataObjectOptions(dataObject, _.extend({}, inheritVars[type].defaultOptions, options));
this._objectOptionsByCid[dataObject.cid] = options;
@@ -78,10 +78,10 @@ _.extend(Thorax.View.prototype, {
},
unbindDataObject: function (dataObject) {
- if (this._boundDataObjects.indexOf(dataObject) === -1) {
+ if (!this._boundDataObjectsByCid[dataObject.cid]) {
return false;
}
- this._boundDataObjects = _.without(this._boundDataObjects, dataObject);
+ delete this._boundDataObjectsByCid[dataObject.cid];
dataObject.trigger('freeze');
this.stopListening(dataObject);
delete this._objectOptionsByCid[dataObject.cid];
View
2  src/event.js
@@ -46,7 +46,7 @@ _.extend(Thorax.View, {
_.extend(Thorax.View.prototype, {
freeze: function(options) {
- _.each(this._boundDataObjects, this.unbindDataObject, this);
+ _.each(this._boundDataObjectsByCid, this.unbindDataObject, this);
options = _.defaults(options || {}, {
dom: true,
children: true
View
7 src/helpers/element.js
@@ -12,13 +12,14 @@ Handlebars.registerHelper('element', function(element, options) {
Thorax.View.on('append', function(scope, callback) {
(scope || this.$el).find('[' + elementPlaceholderAttributeName + ']').forEach(function(el) {
- var cid = el.getAttribute(elementPlaceholderAttributeName),
+ var $el = $(el),
+ cid = $el.attr(elementPlaceholderAttributeName),
element = this._elementsByCid[cid];
- // A callback function may be specified as the vaue
+ // A callback function may be specified as the value
if (_.isFunction(element)) {
element = element.call(this);
}
- $(el).replaceWith(element);
+ $el.replaceWith(element);
callback && callback(element);
}, this);
});
View
18 src/ie.js
@@ -0,0 +1,18 @@
+var isIE = (/msie [\w.]+/).exec(navigator.userAgent.toLowerCase());
+
+// IE will lose a reference to the elements if view.el.innerHTML = '';
+// If they are removed one by one the references are not lost.
+// For instance a view's childrens' `el`s will be lost if the view
+// sets it's `el.innerHTML`.
+if (isIE) {
+ Thorax.View.on('before:append', function() {
+ if (this._renderCount > 0) {
+ _.each(this._elementsByCid, function(element, cid) {
+ $(element).remove();
+ });
+ _.each(this.children, function(child, cid) {
+ child.$el.remove();
+ });
+ }
+ });
+}
View
4 src/thorax.js
@@ -46,7 +46,7 @@ Thorax.View = Backbone.View.extend({
var self = this;
this._objectOptionsByCid = {};
- this._boundDataObjects = [];
+ this._boundDataObjectsByCid = {};
// Setup object event tracking
_.each(inheritVars, function(obj) {
@@ -203,6 +203,8 @@ Thorax.View = Backbone.View.extend({
if (typeof html === 'undefined') {
return this.el.innerHTML;
} else {
+ // Event for IE element fixes
+ this.trigger('before:append');
this.el.innerHTML = "";
var element;
if (this.collection && this._objectOptionsByCid[this.collection.cid] && this._renderCount) {
View
16 test/src/collection.js
@@ -15,8 +15,8 @@ describe('collection', function() {
Thorax.View.extend({name: 'letter-empty'});
it("should implement isPopulated", function() {
- expect(letterCollection.isPopulated()).to.be.true;
- expect(letterCollection.at(0).isPopulated()).to.be.true;
+ expect(letterCollection.isPopulated()).to.be['true'];
+ expect(letterCollection.at(0).isPopulated()).to.be['true'];
});
it("collection view binding", function() {
@@ -598,12 +598,12 @@ describe('collection', function() {
collection: new (Thorax.Collection.extend({url: false}))()
});
view.render();
- expect(view.$('ul').hasClass('a')).to.be.true;
+ expect(view.$('ul').hasClass('a')).to.be['true'];
var model = new Thorax.Model({key: 'value'});
view.collection.add(model);
- expect(view.$('ul').hasClass('a')).to.be.false;
+ expect(view.$('ul').hasClass('a')).to.be['false'];
view.collection.remove(model);
- expect(view.$('ul').hasClass('a')).to.be.true;
+ expect(view.$('ul').hasClass('a')).to.be['true'];
//with default arg
view = new Thorax.View({
@@ -611,12 +611,12 @@ describe('collection', function() {
collection: new (Thorax.Collection.extend({url: false}))()
});
view.render();
- expect(view.$('ul').hasClass('empty')).to.be.true;
+ expect(view.$('ul').hasClass('empty')).to.be['true'];
var model = new Thorax.Model({key: 'value'});
view.collection.add(model);
- expect(view.$('ul').hasClass('empty')).to.be.false;
+ expect(view.$('ul').hasClass('empty')).to.be['false'];
view.collection.remove(model);
- expect(view.$('ul').hasClass('empty')).to.be.true;
+ expect(view.$('ul').hasClass('empty')).to.be['true'];
});
it("helper and local scope collision", function() {
View
2  test/src/layout.js
@@ -120,7 +120,7 @@ describe('layout', function() {
layoutWithTemplateWithoutLayoutTag.setView(new Thorax.View({
template: '<div class="inner"></div>'
}));
- }).to.throw();
+ }).to['throw']();
});
});
View
24 test/src/loading.js
@@ -16,12 +16,12 @@ describe('loading', function() {
view = new Thorax.View({name: 'food', render: function() {}, model: model});
view.on('load:start', spy);
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
model.loadStart();
this.clock.tick(1000);
expect(spy).to.have.been.calledOnce;
- expect($(view.el).hasClass('loading')).to.be.true;
+ expect($(view.el).hasClass('loading')).to.be['true'];
});
it('views should see load start from collection', function() {
var spy = this.spy(),
@@ -34,13 +34,13 @@ describe('loading', function() {
view.bindDataObject(view.myCollection);
view.on('load:start', spy);
view.render();
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
collection.loadStart();
this.clock.tick(1000);
expect(spy).to.have.been.calledOnce;
- expect($(view.el).hasClass('loading')).to.be.true;
+ expect($(view.el).hasClass('loading')).to.be['true'];
});
it('views should not see load start after destroy', function() {
var spy = this.spy(),
@@ -48,14 +48,14 @@ describe('loading', function() {
view = new Thorax.View({name: 'food', render: function() {}, model: model});
view.on('load:start', spy);
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
view.destroy();
model.loadStart();
this.clock.tick(1000);
expect(spy).to.not.have.been.called;
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
});
it('views should see load end from model', function() {
@@ -70,7 +70,7 @@ describe('loading', function() {
this.clock.tick(1000);
expect(spy).to.have.been.calledOnce;
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
});
it('views should see load end from collection', function() {
var collection = new Thorax.Collection({url: 'foo'});
@@ -89,7 +89,7 @@ describe('loading', function() {
this.clock.tick(1000);
expect(spy).to.have.been.calledOnce;
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
});
it('views should see load end after destroy', function() {
var spy = this.spy(),
@@ -98,11 +98,11 @@ describe('loading', function() {
endSpy = this.spy(view, 'onLoadEnd');
view.on('load:start', spy);
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
model.loadStart();
this.clock.tick(1000);
- expect($(view.el).hasClass('loading')).to.be.true;
+ expect($(view.el).hasClass('loading')).to.be['true'];
view.destroy();
model.loadEnd();
@@ -111,7 +111,7 @@ describe('loading', function() {
this.clock.tick(1000);
expect(spy).to.have.been.calledOnce;
expect(endSpy).to.have.been.calledOnce;
- expect($(view.el).hasClass('loading')).to.be.false;
+ expect($(view.el).hasClass('loading')).to.be['false'];
});
});
@@ -697,7 +697,7 @@ describe('loading', function() {
expect(collectionLoadingTemplateView.$('li').length).to.equal(3);
expect(collectionLoadingTemplateView.$('li.empty-item').length).to.equal(0);
expect(collectionLoadingTemplateView.$('li.loading-item').length).to.equal(1);
- expect($(collectionLoadingTemplateView.$('li')[2]).hasClass('loading-item')).to.be.true;
+ expect($(collectionLoadingTemplateView.$('li')[2]).hasClass('loading-item')).to.be['true'];
collectionLoadingTemplateView.myCollection.add([{"number": "three"}, {"number": "four"}]);
collectionLoadingTemplateView.myCollection.loadEnd();
this.clock.tick(loadEndTimeout);
View
8 test/src/model.js
@@ -6,7 +6,7 @@ describe('model', function() {
expect(Thorax.Util.shouldFetch(a, options)).to.not.be.ok;
var b = new (type.Model.extend({urlRoot: '/'}))();
- expect(Thorax.Util.shouldFetch(b, options)).to.be.true;
+ expect(Thorax.Util.shouldFetch(b, options)).to.be['true'];
var c = new (type.Model.extend({urlRoot: '/'}))();
c.set({key: 'value'});
@@ -16,7 +16,7 @@ describe('model', function() {
expect(Thorax.Util.shouldFetch(d, options)).to.not.be.ok;
var e = new (type.Collection.extend({url: '/'}))();
- expect(Thorax.Util.shouldFetch(e, options)).to.be.true;
+ expect(Thorax.Util.shouldFetch(e, options)).to.be['true'];
});
});
@@ -53,8 +53,8 @@ describe('model', function() {
});
it("isPopulated", function() {
- expect((new Thorax.Model()).isPopulated()).to.be.false;
- expect((new Thorax.Model({key: 'value'})).isPopulated()).to.be.true;
+ expect((new Thorax.Model()).isPopulated()).to.be['false'];
+ expect((new Thorax.Model({key: 'value'})).isPopulated()).to.be['true'];
});
it("$.fn.model", function() {
View
2  test/src/thorax.js
@@ -162,7 +162,7 @@ describe('core', function() {
var view = new Thorax.View();
expect(function() {
view.render();
- }).to.throw();
+ }).to['throw']();
});
it("render() subclassing", function() {
Something went wrong with that request. Please try again.