Example using TroopJS #120

Closed
wants to merge 12 commits into
from

Projects

None yet

3 participants

@mikaelkaron
Contributor

A TodoMVC example using TroopJS

@sindresorhus
Member

Thanks for the contribution :D

Can you explain what TroopJS is? Couldn't find any info on it.

@mikaelkaron
Contributor

Just another front-end framework. Aims to package current javascript technologies and 'bind' them together with minimal effort for the developer.

  • jQuery for DOM operations
  • RequireJS for modularity
  • ComposeJS for object composition
  • Has.js for feature detection

On top of this we've added a pubsub system, templating, weaving (connect widget to dom) and auto wiring.

The TroopJS moto goes something like

Provide as little as possible to do as much as possible

The name is taken from what you'd call a group of monkeys (troop) as originally it was targeted at an 'embedded' environment using spidermonkey.

We're using todomvc as the onboarding project for new developers, so there's a step-by-step guide (in progress) that explains how we created this app.

@mikaelkaron
Contributor

simplified the file structure quite a bit and provided an example using troopjs-bundle instead of individual modules.

@mikaelkaron
Contributor

Not really sure what else I can do to get this merged. Perhaps use the new template from #126 when its ready?

@sindresorhus
Member

Sorry for the delay. I've been on holiday.

It looks pretty good :)

There are some things I'd appreciate if you could fix:

  • Use the latest template.
    As you've seen, I just landed a new UI. The HTML changes are minimal: You can see the changes in the diff here.
  • Make sure you follow the newly released App Specification.
    Please let me know if anything is unclear or could be improved. It's not field tested yet. The spine.js example, not yet landed, can be used as a reference.
  • This project uses tab indention and follows the Idiomatic guide to writing JavaScript. Please ensure your code follows this closely. If something is unclear, follow the existing style. Use double-quotes in HTML and single-quotes in JS and CSS.

I'll review it more thoroughly when the above changes are implemented :D


Some comments to your excellent TroopJS Todo app intro:

Ours is located in js/lib. As none of the other folders (css, js and img) were pluralized, we thought that it was silly to do it here.

I agree, changed in master.

We do this, but we felt it was natural to do the same when the user removes focus from the input box.

This is specified in the new App Specification.

Since the specification does not define what this checkbox should do when only some of the tasks are marked as completed, we've added an indeterminate state that covers this usecase.

Also in the App Specification.

Do let us know if you have any more feedback or if something is unclear :D

@sindresorhus
Member

@mikaelkaron Any updates?

@mikaelkaron
Contributor

hacking along on 0.0.2 ;) I'll make an updated todomvc as soon as I have that (using the new UI)

@addyosmani
Member

@mikaelkaron Thanks for the update! Do you think 0.0.2 might be ready sometime over the next few weeks? Im trying to establish whether you'll have time to bring the TroopJS app in line with specs for our next major version (probably aiming to go out in a month). If you do, I'll mark the target release for this as 1.0 otherwise we can do it in 1.1 (probably 1..a few months after).

We appreciate your contributions!

@mikaelkaron
Contributor

I'll try to land 0.0.2 ASAP, so please target this for 1.0

@addyosmani
Member

Marked accordingly. Let us know if you need any help :)

@sindresorhus
Member

@mikaelkaron Any updates? The 1.0 release is close, and we need to get those apps that are going into the release merged.

@mikaelkaron
Contributor

I've been a bit slow over here. I'll take a stab at it today!

@mikaelkaron
Contributor

landed a bunch of stuff in my troopjs-todos repo (what I eventually 'copy' to todomvc). basically implemented all the requirements except routing. will take a stab at that tomorrow.

@mikaelkaron
Contributor

a bit of reset and a commit later. done.

@sindresorhus sindresorhus and 1 other commented on an outdated diff May 30, 2012
architecture-examples/troopjs/css/app.css
@@ -0,0 +1,9 @@
+/* base.css overrides */
+#footer, #main {
+ display: none;
+}
+
+.filter-active .completed,
+.filter-completed .active {
+ display: none;
@sindresorhus
sindresorhus May 30, 2012 Member

CSS shouldn't be tied to the filters functionality.

Instead you should just hide/show the items directly.

@mikaelkaron
mikaelkaron May 30, 2012 Contributor

just trying to save myself some coding for when you have a filter applied and add/remove items. I'll take a look at if it can be done without CSS.

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/create.js
@@ -0,0 +1,24 @@
+define( [ "troopjs-core/component/widget" ], function CreateModule(Widget) {
+ var RE = /^\s+|\s+$/;
+ var ENTER = 13;
@sindresorhus sindresorhus and 1 other commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/create.js
@@ -0,0 +1,24 @@
+define( [ "troopjs-core/component/widget" ], function CreateModule(Widget) {
+ var RE = /^\s+|\s+$/;
+ var ENTER = 13;
+ var EMPTY = "";
+
+ return Widget.extend({
+ "dom/keyup" : function onKeyUp(topic, $event) {
+ var self = this;
+ var $element = self.$element;
+ var value;
+
+ switch($event.keyCode) {
+ case ENTER:
+ value = $element.val().replace(RE, EMPTY);
@sindresorhus
sindresorhus May 30, 2012 Member

Don't need regex for this: val().trim() instead

@mikaelkaron
mikaelkaron May 30, 2012 Contributor

does that trim of tabs etc?

@mikaelkaron
mikaelkaron May 30, 2012 Contributor

right. I was looking at $.trim. I'll update

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/create.js
@@ -0,0 +1,24 @@
+define( [ "troopjs-core/component/widget" ], function CreateModule(Widget) {
+ var RE = /^\s+|\s+$/;
+ var ENTER = 13;
+ var EMPTY = "";
@sindresorhus
sindresorhus May 30, 2012 Member

Unnecessary constant, just compare against an empty string directly.

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/filters.js
@@ -0,0 +1,20 @@
+define( [ "troopjs-core/component/widget", "jquery" ], function FiltersModule(Widget, $) {
+
+ var SELECTED = "selected";
@sindresorhus
sindresorhus May 30, 2012 Member

Don't need a constant here either.

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/item.html
@@ -0,0 +1,13 @@
+<%
+var i = data.i;
+var item = data.item;
+var completed = item.completed;
+%>
+<li class="<%= (completed ? 'completed' : 'active') %>">
@sindresorhus
sindresorhus May 30, 2012 Member

-> <li class="<%= (completed ? 'completed' : '') %>">

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/list.js
@@ -0,0 +1,255 @@
+define( [ "troopjs-core/component/widget", "troopjs-core/store/local", "jquery", "template!./item.html" ], function ListModule(Widget, store, $, template) {
+ var RE = /^\s+|\s+$/;
+ var ENTER = 13;
+ var EMPTY = "";
@sindresorhus
sindresorhus May 30, 2012 Member

Same as earlier comments

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/list.js
+ var ENTER = 13;
+ var EMPTY = "";
+ var DISABLED = "disabled";
+ var CHECKED = "checked";
+ var FILTER_ACTIVE = "filter-active";
+ var FILTER_COMPLETED = "filter-completed";
+
+ function filter(item, index) {
+ return item === null;
+ }
+
+ return Widget.extend(function ListWidget(element, name) {
+ var self = this;
+
+ // Defer initialization
+ $.Deferred(function deferredInit(dfdInit) {
@sindresorhus
sindresorhus May 30, 2012 Member

Use something like deferInit, instead of the ambiguous dfdInit, same with the below.

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/list.js
+ "hub/todos/mark" : function onMark(topic, value) {
+ this.$element.find(":checkbox").prop(CHECKED, value).change();
+ },
+
+ "hub/todos/clear" : function onClear(topic) {
+ this.$element.find(".completed .destroy").click();
+ },
+
+ "hub:memory/todos/filter" : function onFilter(topic, filter) {
+ var $element = this.$element;
+
+ switch (filter) {
+ case "/completed":
+ $element
+ .removeClass(FILTER_ACTIVE)
+ .addClass(FILTER_COMPLETED);
@sindresorhus
sindresorhus May 30, 2012 Member

.toggeClass( FILTER_COMPLETED );

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/list.js
+ // Set items and resolve set
+ store.set(self.config.store, items, dfdSet);
+ });
+ })
+ .done(function doneSet(items) {
+ self.publish("todos/change", items);
+ });
+ },
+
+ "dom/action/delete.click" : function onDelete(topic, $event, index) {
+ var self = this;
+
+ // Update UI
+ $($event.target)
+ .closest("li")
+ .slideUp("slow", function hidden() {
@sindresorhus
sindresorhus May 30, 2012 Member

Don't animate

@sindresorhus sindresorhus and 1 other commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/list.js
+ self.publish("todos/change", items);
+ });
+ },
+
+ "dom/action/prepare.dblclick" : function onPrepare(topic, $event, index) {
+ var self = this;
+
+ // Get LI and update
+ var $li = $($event.target)
+ .closest("li")
+ .addClass("editing");
+
+ // Get INPUT and disable
+ var $input = $li
+ .find("input")
+ .prop(DISABLED, true);
@sindresorhus
sindresorhus May 30, 2012 Member

Why do you disable the input? it's only visible when in editing mode anyway.

@mikaelkaron
mikaelkaron May 30, 2012 Contributor

right now we know that localstorage is fast, so there's no need to disable the UI during write. But I'd like to have it there simply if one would try to implement a remote storage module, in which case it would disable the input box during write callback.

@sindresorhus
sindresorhus May 30, 2012 Member

Ok, sure, I agree ;)

@sindresorhus sindresorhus commented on an outdated diff May 30, 2012
architecture-examples/troopjs/js/widget/list.js
+ });
+ },
+
+ "dom/action/commit.keyup" : function onCommitKeyUp(topic, $event) {
+ switch($event.originalEvent.keyCode) {
+ case ENTER:
+ $($event.target).focusout();
+ }
+ },
+
+ "dom/action/commit.focusout" : function onCommitFocusOut(topic, $event, index) {
+ var self = this;
+ var $target = $($event.target);
+ var title = $target
+ .val()
+ .replace(RE, EMPTY);
@sindresorhus
sindresorhus May 30, 2012 Member

Same as before val().trim()

@sindresorhus
Member

Thanks for finishing this. I have to say, I like to good use of $.Deferred ;)

I've done a review of your app and have some comments in addition to the inline ones:

  • Move it to the labs/architecture-examples folder. Remember to update all the references in the HTML.

Develop in a topic branch (not master) and submit against the labs folder in the master branch

  • Counter should say e.g: 2 items left
  • When in editing mode and remove the todo contents and hit enter, i get this error msg:

Uncaught Error: NOT_FOUND_ERR: DOM Exception 8

  • Drop the animation stuff
  • Use the jQuery in the assets folder
  • Minified:

Use minified version of third-party code and put it in the lib folder.

  • Too much use of unnessecary constants
@mikaelkaron
Contributor

I'll get on the CSS update tomorrow. Do you want me to make a topic branch for all of this as per:

Develop in a topic branch (not master) and submit against the labs folder in the master branch

or are you ok with this pull request as it is (besides the outstanding CSS fix)?

I was also unable to reproduce your error, do you still have it?

@sindresorhus
Member

I'll get on the CSS update tomorrow. Do you want me to make a topic branch for all of this as per:

No not necessary, but keep it in mind for your next pull request. It makes your life a lot easier if you've ever need to submit multiple pull requests to the same project.

  • I've just added require.js to our assets folder. Can you use that instead? (Note that it's Require 2.0)

I was also unable to reproduce your error, do you still have it?

Yes, I still have that issue, Chrome 19 Mac Lion:

Steps: Double-click the item, remove all the text, hit enter

Uncaught Error: NOT_FOUND_ERR: DOM Exception 8 jquery.min.js:4
f.fn.extend.remove jquery.min.js:4
onDelete list.js:139
b troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
q troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
r troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
f.each.f.fn.(anonymous function) jquery.min.js:3
onCommitFocusOut list.js:208
b troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
q troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
r troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
f.each.f.fn.(anonymous function) jquery.min.js:3
onCommitKeyUp list.js:194
b troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
q troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3
f.event.trigger jquery.min.js:3
f.fn.extend.trigger jquery.min.js:3
e.extend.each jquery.min.js:2
e.fn.e.each jquery.min.js:2
f.fn.extend.trigger jquery.min.js:3
r troopjs-bundle.js:11
f.event.dispatch jquery.min.js:3
f.event.add.h.handle.i jquery.min.js:3

Other than that, it looks good :)

@mikaelkaron
Contributor

Ok, I'm able to reproduce in chrome. I'll take a look at that now.

@mikaelkaron
Contributor

that requirejs 2.0 thing sort of threw a wrench into my schedule (had to do some updates to the bundle to support 2.0). I have a version of the bundle that is 2.0 compatible, but as there are still quite a few bugs with 2.0 and rjs I'd like to stick to 1.0 if possible.

@sindresorhus
Member

that requirejs 2.0 thing sort of threw a wrench into my schedule (had to do some updates to the bundle to support 2.0). I have a version of the bundle that is 2.0 compatible, but as there are still quite a few bugs with 2.0 and rjs I'd like to stick to 1.0 if possible.

Didn't mean to make a lot of extra work for you. Noticed it while surfing and it looked like and easy upgrade. But sure, stick with 1.0 if that's more stable ;)

Just make sure to also minify the require.js file.

@addyosmani
Member

1.0 should be okay for now :) I know James Burke mentioned there's still a few weeks of work left to wrap up 2.0 so we should be okay to upgrade at a later point as needed.

@sindresorhus
Member

@addyosmani Actually 2.0 has been released.

@addyosmani
Member

@sindresorhus Oh wow! That I did not know. He must have had a few late nights to wrap it all up so quickly :)

@mikaelkaron
Contributor

There. Fixed what needed to be fixed to be RequireJS 2.0 'compatible'.

@sindresorhus
Member

Cool, just need that error fixed and we're golden ;)

@mikaelkaron
Contributor

Fixed.

Funny problem there actually, it's jQuery that triggers a simulated focusout event on the containing input as I .remove() the li, thereby re-triggering the whole chain (I click() a corresponding .destroy when the input is empty and we lose focus)

@sindresorhus
Member

Right, when I think about it I think we had another app with the same problem, turned out to actually be a Chrome bug.

Landed. Thanks for this addition to our collection ;)

@mikaelkaron
Contributor

Well, I was only able to reproduce in chrome, and the underlying error is that jQuery will try to remove a node that does not exist anymore. I guess it's possible that this is the same problem (were you removing an element that had input descendants?).

@sindresorhus
Member

Can't remember, but here is the Chromium bug: http://code.google.com/p/chromium/issues/detail?id=104397

And here's the jQuery bug: http://bugs.jquery.com/ticket/11663

@mikaelkaron
Contributor

looks like the same to me. nice to know ppl are aware about it.

@sindresorhus
Member

But unfortunately not a lot of activity on the Chrome ticket...

@gustaff-weldon gustaff-weldon pushed a commit to gustaff-weldon/todomvc that referenced this pull request Dec 23, 2013
@mikaelkaron @sindresorhus mikaelkaron + sindresorhus Close GH-120: Example using TroopJS. e9c365d
@dichen001 dichen001 referenced this pull request in dichen001/Paper-Reading Jun 28, 2016
Open

Summary of the 20 issues in Herbsleb's 2014 FSE paper. #6

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