Skip to content

Commit 0fba4a8

Browse files
committed
Use Backbone.NativeView
1 parent 0ededee commit 0fba4a8

File tree

4 files changed

+68
-57
lines changed

4 files changed

+68
-57
lines changed

app/javascript/application.ts

Lines changed: 21 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import "mousetrap";
77
import "jquery-visible";
88
import _ from "underscore";
99
import Backbone from "backbone";
10+
import "backbone.nativeview";
1011

1112
import "./controllers/index";
1213

@@ -98,14 +99,14 @@ var Story = Backbone.Model.extend({
9899
}
99100
});
100101

101-
var StoryView = Backbone.View.extend({
102+
var StoryView = Backbone.NativeView.extend({
102103
className: "story",
103104
events: {
104105
"click .story-preview" : "storyClicked"
105106
},
106107

107108
initialize: function() {
108-
this.template = _.template($(this.template).html());
109+
this.template = _.template(document.querySelector(this.template).innerHTML);
109110
this.listenTo(this.model, 'add', this.render);
110111
this.listenTo(this.model, 'change:selected', this.itemSelected);
111112
this.listenTo(this.model, 'change:open', this.itemOpened);
@@ -118,33 +119,37 @@ var StoryView = Backbone.View.extend({
118119
},
119120

120121
itemOpened: function() {
122+
var storyLead = this.el.querySelector(".story-lead");
121123
if (this.model.get("open")) {
122-
this.$el.addClass("open");
123-
$(".story-lead", this.$el).fadeOut(1000);
124-
window.scrollTo(0, this.$el.offset().top);
124+
this.el.classList.add("open");
125+
if (storyLead) storyLead.style.display = "none";
126+
window.scrollTo(0, this.el.getBoundingClientRect().top + window.scrollY);
125127
} else {
126-
this.$el.removeClass("open");
127-
$(".story-lead", this.$el).show();
128+
this.el.classList.remove("open");
129+
if (storyLead) storyLead.style.display = "";
128130
}
129131
},
130132

131133
itemRead: function() {
132-
this.$el.toggleClass("read", this.model.get("is_read"));
134+
this.el.classList.toggle("read", this.model.get("is_read"));
133135
},
134136

135137
itemSelected: function() {
136-
this.$el.toggleClass("cursor", this.model.get("selected"));
137-
if (!this.$el.visible()) window.scrollTo(0, this.$el.offset().top);
138+
this.el.classList.toggle("cursor", this.model.get("selected"));
139+
var rect = this.el.getBoundingClientRect();
140+
if (rect.top < 0 || rect.bottom > window.innerHeight) {
141+
window.scrollTo(0, rect.top + window.scrollY);
142+
}
138143
},
139144

140145
render: function() {
141146
var jsonModel = this.model.toJSON();
142-
this.$el.html(this.template(jsonModel));
147+
this.el.innerHTML = this.template(jsonModel);
143148
if (jsonModel.is_read) {
144-
this.$el.addClass('read');
149+
this.el.classList.add('read');
145150
}
146151
if (jsonModel.keep_unread) {
147-
this.$el.addClass('keepUnread');
152+
this.el.classList.add('keepUnread');
148153
}
149154
Object.assign(this.el.dataset, {
150155
controller: "star-toggle keep-unread-toggle",
@@ -166,7 +171,7 @@ var StoryView = Backbone.View.extend({
166171
if (this.model.shouldSave()) this.model.save(null, { headers: requestHeaders() });
167172
} else {
168173
this.model.toggle();
169-
window.scrollTo(0, this.$el.offset().top);
174+
window.scrollTo(0, this.el.getBoundingClientRect().top + window.scrollY);
170175
}
171176
},
172177

@@ -257,21 +262,20 @@ var StoryList = Backbone.Collection.extend({
257262
}
258263
});
259264

260-
var AppView = Backbone.View.extend({
265+
var AppView = Backbone.NativeView.extend({
261266
addAll: function() {
262267
this.stories.each(this.addOne, this);
263268
},
264269

265270
addOne: function(story) {
266271
var view = new StoryView({model: story});
267-
this.$("#story-list").append(view.render().el);
272+
this.el.querySelector("#story-list").appendChild(view.render().el);
268273
},
269274

270275
el: "#stories",
271276

272277
initialize: function(collection) {
273278
this.stories = collection;
274-
this.el = $(this.el);
275279

276280
this.listenTo(this.stories, 'add', this.addOne);
277281
this.listenTo(this.stories, 'reset', this.addAll);

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"@rails/actioncable": "^8.1.100",
1111
"@rails/activestorage": "^8.1.100",
1212
"backbone": "1.6.1",
13+
"backbone.nativeview": "^0.3.4",
1314
"bootstrap": "3.1.1",
1415
"font-awesome": "4.7.0",
1516
"jquery": "2.2.4",

pnpm-lock.yaml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

spec/javascript/spec/views/story_view_spec.ts

Lines changed: 38 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,72 +28,72 @@ describe("StoryView", function () {
2828
});
2929

3030
it("should render li.story items", function () {
31-
expect(view.$el.hasClass("story")).toBe(true);
31+
expect(view.el.classList.contains("story")).toBe(true);
3232
});
3333

3434
var assertTagExists = function (el, tagName, count) {
3535
count = typeof count !== "undefined" ? count : 1;
36-
expect(el.find(tagName)).toHaveLength(count);
36+
expect(el.querySelectorAll(tagName)).toHaveLength(count);
3737
};
3838

3939
var assertNoTagExists = function (el, tagName) {
40-
expect(el.find(tagName)).toHaveLength(0);
40+
expect(el.querySelectorAll(tagName)).toHaveLength(0);
4141
};
4242

4343
var assertPropertyRendered = function (el, model, propName) {
44-
expect(el.html()).toContain(model.get(propName));
44+
expect(el.innerHTML).toContain(model.get(propName));
4545
};
4646

4747
it("should render blog title", function () {
48-
assertTagExists(view.$el, ".blog-title");
49-
assertPropertyRendered(view.$el, story, "source");
48+
assertTagExists(view.el, ".blog-title");
49+
assertPropertyRendered(view.el, story, "source");
5050
});
5151

5252
it("should render story headline", function () {
53-
assertTagExists(view.$el, ".story-title");
54-
assertPropertyRendered(view.$el, story, "headline");
53+
assertTagExists(view.el, ".story-title");
54+
assertPropertyRendered(view.el, story, "headline");
5555
});
5656

5757
it("should render story lead", function () {
58-
assertTagExists(view.$el, ".story-lead");
59-
assertPropertyRendered(view.$el, story, "lead");
58+
assertTagExists(view.el, ".story-lead");
59+
assertPropertyRendered(view.el, story, "lead");
6060
});
6161

6262
it("should render story full title", function () {
63-
assertTagExists(view.$el, ".story-body");
64-
assertPropertyRendered(view.$el, story, "title");
63+
assertTagExists(view.el, ".story-body");
64+
assertPropertyRendered(view.el, story, "title");
6565
});
6666

6767
it("should render story full title as link", function () {
68-
assertTagExists(view.$el, ".story-body h1 a");
68+
assertTagExists(view.el, ".story-body h1 a");
6969
});
7070

7171
it("should render story full body", function () {
72-
assertTagExists(view.$el, ".story-body");
73-
assertPropertyRendered(view.$el, story, "body");
72+
assertTagExists(view.el, ".story-body");
73+
assertPropertyRendered(view.el, story, "body");
7474
});
7575

7676
it("should render story date", function () {
77-
assertTagExists(view.$el, ".story-published");
78-
assertPropertyRendered(view.$el, story, "pretty_date");
77+
assertTagExists(view.el, ".story-published");
78+
assertPropertyRendered(view.el, story, "pretty_date");
7979
});
8080

8181
it("should render story permalink", function () {
82-
assertTagExists(view.$el, ".story-permalink");
83-
assertPropertyRendered(view.$el, story, "permalink");
82+
assertTagExists(view.el, ".story-permalink");
83+
assertPropertyRendered(view.el, story, "permalink");
8484
});
8585

8686
it("should render keep as unread button", function () {
87-
assertTagExists(view.$el, ".story-keep-unread");
87+
assertTagExists(view.el, ".story-keep-unread");
8888
});
8989

9090
it("should autofill unread button based on item", function () {
91-
assertTagExists(view.$el, ".story-keep-unread .fa-square-o");
91+
assertTagExists(view.el, ".story-keep-unread .fa-square-o");
9292

9393
story.set("keep_unread", true);
9494
view.render();
9595

96-
assertTagExists(view.$el, ".story-keep-unread .fa-check");
96+
assertTagExists(view.el, ".story-keep-unread .fa-check");
9797
});
9898

9999
it("should set keep-unread-toggle Stimulus data attributes", function () {
@@ -108,40 +108,40 @@ describe("StoryView", function () {
108108

109109
it("should wire keep-unread action to Stimulus controller", function () {
110110
view.render();
111-
var keepUnreadDiv = view.$el.find(".story-keep-unread");
112-
expect(keepUnreadDiv.attr("data-action")).toContain("keep-unread-toggle#toggle");
111+
var keepUnreadDiv = view.el.querySelector(".story-keep-unread");
112+
expect(keepUnreadDiv.getAttribute("data-action")).toContain("keep-unread-toggle#toggle");
113113
});
114114

115115
it("should set keep-unread-toggle target on icon", function () {
116116
view.render();
117-
var icon = view.$el.find(".story-keep-unread i");
118-
expect(icon.attr("data-keep-unread-toggle-target")).toBe("icon");
117+
var icon = view.el.querySelector(".story-keep-unread i");
118+
expect(icon.getAttribute("data-keep-unread-toggle-target")).toBe("icon");
119119
});
120120

121121
it("should render two instances of the star button", function () {
122-
assertTagExists(view.$el, ".story-actions .story-starred");
123-
assertTagExists(view.$el, ".story-preview .story-starred");
122+
assertTagExists(view.el, ".story-actions .story-starred");
123+
assertTagExists(view.el, ".story-preview .story-starred");
124124
});
125125

126126
it("should autofill star button based on item", function () {
127-
assertTagExists(view.$el, ".story-starred .fa-star-o", 2);
127+
assertTagExists(view.el, ".story-starred .fa-star-o", 2);
128128

129129
story.set("is_starred", true);
130130
view.render();
131131

132-
assertTagExists(view.$el, ".story-starred .fa-star", 2);
132+
assertTagExists(view.el, ".story-starred .fa-star", 2);
133133
});
134134

135135
it("should not render enclosure link when not present", function () {
136-
assertNoTagExists(view.$el, ".story-enclosure");
136+
assertNoTagExists(view.el, ".story-enclosure");
137137
});
138138

139139
it("should render enclosure link when present", function () {
140140
story.set("enclosure_url", "http://example.com/enclosure");
141141
view.render();
142142

143-
assertTagExists(view.$el, ".story-enclosure");
144-
assertPropertyRendered(view.$el, story, "enclosure_url");
143+
assertTagExists(view.el, ".story-enclosure");
144+
assertPropertyRendered(view.el, story, "enclosure_url");
145145
});
146146

147147
describe("Handling click on story", function () {
@@ -156,22 +156,20 @@ describe("StoryView", function () {
156156
});
157157

158158
it("should open story when clicked on it", function () {
159-
view.$(".story-preview").click();
159+
view.el.querySelector(".story-preview").click();
160160
expect(toggleStub).toHaveBeenCalledOnce();
161161
});
162162

163163
it("should not open story when clicked on it with metaKey pressed", function () {
164-
var e = jQuery.Event("click");
165-
e.metaKey = true;
166-
view.$(".story-preview").trigger(e);
164+
var e = new MouseEvent("click", { bubbles: true, metaKey: true });
165+
view.el.querySelector(".story-preview").dispatchEvent(e);
167166

168167
expect(toggleStub).not.toHaveBeenCalled();
169168
});
170169

171170
it("should not open story when clicked on it with ctrlKey pressed", function () {
172-
var e = jQuery.Event("click");
173-
e.ctrlKey = true;
174-
view.$(".story-preview").trigger(e);
171+
var e = new MouseEvent("click", { bubbles: true, ctrlKey: true });
172+
view.el.querySelector(".story-preview").dispatchEvent(e);
175173

176174
expect(toggleStub).not.toHaveBeenCalled();
177175
});

0 commit comments

Comments
 (0)