From 317af21c5aded9a8844901af722600c64667a61c Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Mon, 11 Dec 2017 16:30:43 -0800 Subject: [PATCH 01/14] Wave 1, quoteListView rendered --- src/app.js | 17 ++++++++- src/views/quote_list_view.js | 72 ++++++++++++++++++++++++++++++++++++ src/views/quote_view.js | 35 ++++++++++++++++++ 3 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 src/views/quote_list_view.js create mode 100644 src/views/quote_view.js diff --git a/src/app.js b/src/app.js index 03ec910..66247ec 100644 --- a/src/app.js +++ b/src/app.js @@ -2,9 +2,12 @@ import 'foundation-sites/dist/foundation.css'; import 'css/app.css'; import $ from 'jquery'; +import _ from 'underscore'; import Simulator from 'models/simulator'; +// import Quote from 'models/quote'; import QuoteList from 'collections/quote_list'; +import QuoteListView from 'views/quote_list_view'; const quoteData = [ { @@ -24,12 +27,24 @@ const quoteData = [ price: 83.10, }, ]; +const quotes = new QuoteList(quoteData); +let quoteTemplate; $(document).ready(function() { - const quotes = new QuoteList(quoteData); + quoteTemplate = _.template($('#quote-template').html()); + + const quoteListView = new QuoteListView({ + model: quotes, + template: quoteTemplate, + el: '#quotes-container' + }); + const simulator = new Simulator({ quotes: quotes, }); simulator.start(); + + quoteListView.render(); + }); diff --git a/src/views/quote_list_view.js b/src/views/quote_list_view.js new file mode 100644 index 0000000..ae1811a --- /dev/null +++ b/src/views/quote_list_view.js @@ -0,0 +1,72 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; +import Quote from '../models/quote'; +import QuoteView from '../views/quote_view'; + +const QuoteListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.listenTo(this.model, 'update', this.render); + }, + render() { + // Clear the unordered list + this.$('#quotes').empty(); + // Iterate through the list rendering each Quote + this.model.each((quote) => { + // Create a new QuoteView with the model & template + const quoteView = new QuoteView({ + model: quote, + template: this.template, + tagName: 'li', + className: 'quote', + }); + // Then render the QuoteView + // And append the resulting HTML to the DOM. + this.$('#quotes').append(quoteView.render().$el); + }); + return this; + }, + // events: { + // 'click #add-new-quote': 'addQuote' + // }, + // addQuote: function(event) { + // event.preventDefault(); + // + // const quoteData = {}; + // ['quote_name', 'assignee'].forEach( (field) => { + // const val = this.$(`input[name=${field}]`).val(); + // if (val != '') { + // quoteData[field] = val; + // } + // }); + // const newQuote = new Quote(quoteData); + // + // if (newQuote.isValid()) { + // this.model.add(newQuote); + // this.updateStatusMessageWith(`New quote added: ${newQuote.get('quote_name')}`); + // } else { + // this.updateStatusMessageFrom(newQuote.validationError); + // } + // }, + // // helper method for updating the DOM with the status from a hash + // updateStatusMessageFrom: function(messageHash){ + // const statusMessagesEl = this.$('#status-messages'); + // statusMessagesEl.empty(); + // + // _.each(messageHash, (messageType) => { + // messageType.forEach((message) => { + // statusMessagesEl.append(`
  • ${message}
  • `); + // }) + // }); + // statusMessagesEl.show(); + // }, + // // helper method for updating the DOM with the status from a string + // updateStatusMessageWith: function(message) { + // const statusMessagesEl = this.$('#status-messages'); + // statusMessagesEl.empty(); + // statusMessagesEl.append(`
  • ${message}
  • `); + // statusMessagesEl.show(); + // } +}); + +export default QuoteListView; diff --git a/src/views/quote_view.js b/src/views/quote_view.js new file mode 100644 index 0000000..207d97a --- /dev/null +++ b/src/views/quote_view.js @@ -0,0 +1,35 @@ +import Backbone from 'backbone'; +// import Task from '../models/task'; + +const QuoteView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + + // Listen to changes in the model and call render when they occur. + this.listenTo(this.model, 'change', this.render); + }, + render() { + const compiledTemplate = this.template(this.model.toJSON()); + this.$el.html(compiledTemplate); + // if (this.model.get('is_complete')) { + // this.$el.addClass('is-complete') + // } else { + // this.$el.removeClass('is-complete') + // } + return this; + }, + // events: { + // 'click button.delete': 'deleteQuote', + // 'click .toggle-complete': 'toggleComplete', + // }, + // deleteQuote: function() { + // this.model.destroy(); + // this.remove(); + // }, + // toggleComplete: function() { + // this.model.set('is_complete', !this.model.get('is_complete')); + // // this.$el.toggleClass('is-complete'); + // } +}); + +export default QuoteView; From 38e392c402d07e4bd848511f5a304080e5f30966 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Mon, 11 Dec 2017 16:46:20 -0800 Subject: [PATCH 02/14] increase and decrease quote prices with add/sell --- src/models/quote.js | 2 ++ src/views/quote_view.js | 18 +++++------------- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/models/quote.js b/src/models/quote.js index 4fbf466..26c07b8 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -8,10 +8,12 @@ const Quote = Backbone.Model.extend({ buy() { // Implement this function to increase the price by $1.00 + this.set('price', this.get('price') + 1); }, sell() { // Implement this function to decrease the price by $1.00 + this.set('price', this.get('price') - 1); }, }); diff --git a/src/views/quote_view.js b/src/views/quote_view.js index 207d97a..daa0bbd 100644 --- a/src/views/quote_view.js +++ b/src/views/quote_view.js @@ -1,5 +1,5 @@ import Backbone from 'backbone'; -// import Task from '../models/task'; +import Quote from '../models/quote'; const QuoteView = Backbone.View.extend({ initialize(params) { @@ -18,18 +18,10 @@ const QuoteView = Backbone.View.extend({ // } return this; }, - // events: { - // 'click button.delete': 'deleteQuote', - // 'click .toggle-complete': 'toggleComplete', - // }, - // deleteQuote: function() { - // this.model.destroy(); - // this.remove(); - // }, - // toggleComplete: function() { - // this.model.set('is_complete', !this.model.get('is_complete')); - // // this.$el.toggleClass('is-complete'); - // } + events: { + 'click button.btn-buy': function() {this.model.buy();}, + 'click .btn-sell': function() {this.model.sell();}, + }, }); export default QuoteView; From ee6ab245d1d4786e9919b5b2ed47b65a86214670 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Tue, 12 Dec 2017 09:38:22 -0800 Subject: [PATCH 03/14] Wave 2, added trade history feature --- src/app.js | 2 +- src/models/quote.js | 6 +++--- src/views/quote_view.js | 20 +++++++++++--------- src/views/trade_list_view.js | 27 +++++++++++++++++++++++++++ src/views/trade_view.js | 23 +++++++++++++++++++++++ 5 files changed, 65 insertions(+), 13 deletions(-) create mode 100644 src/views/trade_list_view.js create mode 100644 src/views/trade_view.js diff --git a/src/app.js b/src/app.js index 66247ec..033df38 100644 --- a/src/app.js +++ b/src/app.js @@ -36,7 +36,7 @@ $(document).ready(function() { const quoteListView = new QuoteListView({ model: quotes, template: quoteTemplate, - el: '#quotes-container' + el: 'main', }); const simulator = new Simulator({ diff --git a/src/models/quote.js b/src/models/quote.js index 26c07b8..78b22a0 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -3,17 +3,17 @@ import Backbone from 'backbone'; const Quote = Backbone.Model.extend({ defaults: { symbol: 'UNDEF', - price: 0.00 + price: 0.00, }, buy() { - // Implement this function to increase the price by $1.00 this.set('price', this.get('price') + 1); + return {symbol: this.get('symbol'), price: this.get('price'), buy: true} }, sell() { - // Implement this function to decrease the price by $1.00 this.set('price', this.get('price') - 1); + return {symbol: this.get('symbol'), price: this.get('price'), buy: false} }, }); diff --git a/src/views/quote_view.js b/src/views/quote_view.js index daa0bbd..0dd1d2f 100644 --- a/src/views/quote_view.js +++ b/src/views/quote_view.js @@ -1,26 +1,28 @@ import Backbone from 'backbone'; -import Quote from '../models/quote'; +import $ from 'jquery'; +import _ from 'underscore'; const QuoteView = Backbone.View.extend({ initialize(params) { this.template = params.template; + this.tradeTemplate = _.template($('#trade-template').html()); - // Listen to changes in the model and call render when they occur. this.listenTo(this.model, 'change', this.render); }, render() { const compiledTemplate = this.template(this.model.toJSON()); this.$el.html(compiledTemplate); - // if (this.model.get('is_complete')) { - // this.$el.addClass('is-complete') - // } else { - // this.$el.removeClass('is-complete') - // } return this; }, events: { - 'click button.btn-buy': function() {this.model.buy();}, - 'click .btn-sell': function() {this.model.sell();}, + 'click button.btn-buy': function() { + let trade = this.model.buy(); + $('#trades').prepend(this.tradeTemplate(trade)); + }, + 'click .btn-sell': function() { + let trade = this.model.sell(); + $('#trades').prepend(this.tradeTemplate(trade)); + }, }, }); diff --git a/src/views/trade_list_view.js b/src/views/trade_list_view.js new file mode 100644 index 0000000..abb8b77 --- /dev/null +++ b/src/views/trade_list_view.js @@ -0,0 +1,27 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; +import Quote from '../models/quote'; +import TradeView from '../views/trade_view'; + +const TradeListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.listenTo(this.model, 'update', this.render); + }, + render() { + // this.$('#trades').empty(); + + this.model.each((trade) => { + const tradeView = new TradeView({ + model: trade, + template: this.template, + tagName: 'li', + className: 'trade', + }); + this.$('#trades').prepend(tradeView.render().$el); + }); + return this; + }, +}); + +export default TradeListView; diff --git a/src/views/trade_view.js b/src/views/trade_view.js new file mode 100644 index 0000000..f884b43 --- /dev/null +++ b/src/views/trade_view.js @@ -0,0 +1,23 @@ +import Backbone from 'backbone'; +import Quote from '../models/quote'; + +const TradeView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.listenTo(this.model, 'change', this.render); + }, + render() { + const compiledTemplate = this.template(this.model.toJSON()); + this.$el.html(compiledTemplate); + return this; + }, + events: { + 'click button.btn-buy': function() { + console.log(this.model); + // this.model.add(new Quote({task_name: "Put rendering logic in Backbone Views", assignee: "Me"})); + }, + 'click .btn-sell': function() {this.model.sell();}, + }, +}); + +export default TradeView; From 9454fa4c5f0d49698374f6d906af3e1d5c41c54e Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Thu, 14 Dec 2017 15:15:31 -0800 Subject: [PATCH 04/14] Refactored Wave 2 to use eventBus with Walker- PlutoVR --- dist/index.html | 4 ++++ src/app.js | 23 ++++++++++++++++++++++- src/models/quote.js | 16 +++++++++++----- src/views/quote_list_view.js | 4 ++-- src/views/quote_view.js | 5 +---- src/views/trade_list_view.js | 20 +++++--------------- 6 files changed, 45 insertions(+), 27 deletions(-) diff --git a/dist/index.html b/dist/index.html index 8a046fa..74ba58b 100644 --- a/dist/index.html +++ b/dist/index.html @@ -105,6 +105,10 @@

    $<%- price.toFixed(2) %>

    + + diff --git a/src/app.js b/src/app.js index b26eb7e..c81f33c 100644 --- a/src/app.js +++ b/src/app.js @@ -9,6 +9,7 @@ import Simulator from 'models/simulator'; import QuoteList from 'collections/quote_list'; import QuoteListView from 'views/quote_list_view'; import OrderList from 'collections/order_list'; +import OrderListView from 'views/order_list_view'; import TradeListView from 'views/trade_list_view'; import OrderEntryView from 'views/order_entry_view'; @@ -55,6 +56,12 @@ $(document).ready(function() { el: '#trades-container', }); + const orderListView = new OrderListView({ + model: orders, + template: _.template($('#order-template').html()), + el: '.orders-list-container' + }); + const orderEntryView = new OrderEntryView({ model: {quotes, orders}, // shorthand notation template: _.template($('#order-list-option-template').html()), diff --git a/src/models/order.js b/src/models/order.js index 6bb0a9d..ab1cc95 100644 --- a/src/models/order.js +++ b/src/models/order.js @@ -3,8 +3,9 @@ import Backbone from 'backbone'; const Order = Backbone.Model.extend({ defaults: { symbol: 'UNDEF', - priceTarget: 0.00, // camelCase from form kebab-case - isBuy: null, + priceTarget: 0.00, // TODO: camelCase from form kebab-case, but template uses targetPrice and this is working. WHY?! + buy: null, + quote: null, }, // initialize() { // this.bus = this.get('bus'); // NOTE: Because bus doesn't change diff --git a/src/views/order_entry_view.js b/src/views/order_entry_view.js index ec2a1ff..6bcbc1b 100644 --- a/src/views/order_entry_view.js +++ b/src/views/order_entry_view.js @@ -1,5 +1,4 @@ import Backbone from 'backbone'; -import _ from 'underscore'; import $ from 'jquery'; import Order from '../models/order'; @@ -21,6 +20,7 @@ const OrderEntryView = Backbone.View.extend({ event.preventDefault(); const orderData = {}; + orderData.buy = event.target.className.includes('btn-buy') ? true : false; const fields = {select: 'symbol', input: 'price-target'}; for (const field in fields) { @@ -31,7 +31,8 @@ const OrderEntryView = Backbone.View.extend({ } } - orderData.isBuy = event.target.className.includes('btn-buy') ? true : false; + orderData.priceTarget = Number(orderData.priceTarget) + orderData.quote = this.model.quotes.where({symbol: orderData.symbol}); const newOrder = new Order(orderData); this.model.orders.add(newOrder); diff --git a/src/views/order_list_view.js b/src/views/order_list_view.js new file mode 100644 index 0000000..9cc5f8d --- /dev/null +++ b/src/views/order_list_view.js @@ -0,0 +1,23 @@ +import Backbone from 'backbone'; +import OrderView from '../views/order_view'; + +const OrderListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.listenTo(this.model, 'update', this.render); + }, + render() { + this.$('#orders').empty(); + this.model.each((order) => { + const orderView = new OrderView({ + model: order, + template: this.template, + tagName: 'li', + className: 'order', + }); + this.$('#orders').append(orderView.render().$el); + }); + } +}); + +export default OrderListView; diff --git a/src/views/order_view.js b/src/views/order_view.js new file mode 100644 index 0000000..13f3543 --- /dev/null +++ b/src/views/order_view.js @@ -0,0 +1,36 @@ +import Backbone from 'backbone'; +import $ from 'jquery'; +import _ from 'underscore'; + +const OrderView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.order = params.model + this.quote = this.order.attributes.quote[0]; + this.listenTo(this.quote, 'change', this.executeTrade); + }, + render() { + const compiledTemplate = this.template(this.model.toJSON()); + this.$el.html(compiledTemplate); + return this; + }, + executeTrade() { + if (this.quote.get('price') >= this.order.get('priceTarget') && !this.order.buy) { + this.quote.sell(); + this.remove(); + } else if (this.quote.get('price') <= this.order.get('priceTarget') && this.order.buy) { + this.quote.buy(); + this.remove(); + } + } + // events: { + // 'click button.btn-buy': function() { + // let trade = this.model.buy(); + // }, + // 'click .btn-sell': function() { + // let trade = this.model.sell(); + // }, + // }, +}); + +export default OrderView; diff --git a/src/views/quote_view.js b/src/views/quote_view.js index 32433e4..3d0cd47 100644 --- a/src/views/quote_view.js +++ b/src/views/quote_view.js @@ -5,7 +5,6 @@ import _ from 'underscore'; const QuoteView = Backbone.View.extend({ initialize(params) { this.template = params.template; - // this.orderListOptionTemplate = _.template($('#order-list-option-template').html()); this.listenTo(this.model, 'change', this.render); }, render() { From 04953df32308ba03660ee1fde2b26f64da7dacd3 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Sat, 16 Dec 2017 23:17:36 -0800 Subject: [PATCH 08/14] Wave 3, finished Order fulfilment. BUG- Sell happens when in decimal range below target price --- src/models/order.js | 16 +--------------- src/models/quote.js | 16 ++++++++-------- src/views/order_entry_view.js | 2 -- src/views/order_view.js | 18 +++++++----------- src/views/trade_list_view.js | 1 - 5 files changed, 16 insertions(+), 37 deletions(-) diff --git a/src/models/order.js b/src/models/order.js index ab1cc95..f7166aa 100644 --- a/src/models/order.js +++ b/src/models/order.js @@ -3,24 +3,10 @@ import Backbone from 'backbone'; const Order = Backbone.Model.extend({ defaults: { symbol: 'UNDEF', - priceTarget: 0.00, // TODO: camelCase from form kebab-case, but template uses targetPrice and this is working. WHY?! + priceTarget: 0.00, buy: null, quote: null, }, - // initialize() { - // this.bus = this.get('bus'); // NOTE: Because bus doesn't change - // this.symbol = this.get('symbol'); - // }, - // changePrice(newPrice, isBuy) { - // this.set('price', newPrice); - // this.bus.trigger('trade', {symbol: this.symbol, price: newPrice, buy: isBuy}) - // }, - // buy() { - // this.changePrice(this.get('price') + 1, true); - // }, - // sell() { - // this.changePrice(this.get('price') - 1, false); - // }, }); export default Order; diff --git a/src/models/quote.js b/src/models/quote.js index 33b0e6c..cce2043 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -6,19 +6,19 @@ const Quote = Backbone.Model.extend({ price: 0.00, bus: null, }, - initialize() { - this.bus = this.get('bus'); // NOTE: Because bus doesn't change + initialize() { // NOTE: Because bus and symbol doesn't change + this.bus = this.get('bus'); this.symbol = this.get('symbol'); }, - changePrice(newPrice, isBuy) { - this.set('price', newPrice); - this.bus.trigger('trade', {symbol: this.symbol, price: newPrice, buy: isBuy}) - }, buy() { - this.changePrice(this.get('price') + 1, true); + const price = this.get('price'); + this.bus.trigger('trade', {symbol: this.symbol, price: price, buy: true}); + this.set('price', price + 1); }, sell() { - this.changePrice(this.get('price') - 1, false); + const price = this.get('price') - 1; + this.bus.trigger('trade', {symbol: this.symbol, price: price, buy: false}); + this.set('price', price - 1); }, }); diff --git a/src/views/order_entry_view.js b/src/views/order_entry_view.js index 6bcbc1b..ff175e8 100644 --- a/src/views/order_entry_view.js +++ b/src/views/order_entry_view.js @@ -37,8 +37,6 @@ const OrderEntryView = Backbone.View.extend({ const newOrder = new Order(orderData); this.model.orders.add(newOrder); }, - // TODO: Make an order list view that listens for updates, - // can my order get matching quote model, everytime time it updates check the price, since it has model it can execute trade and remove itself. order model with have listener. }); export default OrderEntryView; diff --git a/src/views/order_view.js b/src/views/order_view.js index 13f3543..c3429e4 100644 --- a/src/views/order_view.js +++ b/src/views/order_view.js @@ -5,7 +5,7 @@ import _ from 'underscore'; const OrderView = Backbone.View.extend({ initialize(params) { this.template = params.template; - this.order = params.model + this.order = params.model; this.quote = this.order.attributes.quote[0]; this.listenTo(this.quote, 'change', this.executeTrade); }, @@ -15,22 +15,18 @@ const OrderView = Backbone.View.extend({ return this; }, executeTrade() { - if (this.quote.get('price') >= this.order.get('priceTarget') && !this.order.buy) { + if (this.quote.get('price') >= this.order.get('priceTarget') && !this.order.get('buy')) { // TODO: Figure out why it sells below selling price. ex: sell @ 90, sold @ 89.7 + this.stopListening(); this.quote.sell(); + this.order.destroy(); this.remove(); - } else if (this.quote.get('price') <= this.order.get('priceTarget') && this.order.buy) { + } else if (this.quote.get('price') <= this.order.get('priceTarget') && this.order.get('buy')) { + this.stopListening(); this.quote.buy(); + this.order.destroy(); this.remove(); } } - // events: { - // 'click button.btn-buy': function() { - // let trade = this.model.buy(); - // }, - // 'click .btn-sell': function() { - // let trade = this.model.sell(); - // }, - // }, }); export default OrderView; diff --git a/src/views/trade_list_view.js b/src/views/trade_list_view.js index 1c04985..8e46b6c 100644 --- a/src/views/trade_list_view.js +++ b/src/views/trade_list_view.js @@ -1,6 +1,5 @@ import Backbone from 'backbone'; import _ from 'underscore'; -import Quote from '../models/quote'; const TradeListView = Backbone.View.extend({ initialize(params) { From b0e0d8ef931e7184c62ce24814f89170299a4d46 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Sat, 16 Dec 2017 23:30:34 -0800 Subject: [PATCH 09/14] fixed sell price bug --- src/models/quote.js | 2 +- src/views/order_view.js | 13 +++++++------ src/views/quote_view.js | 4 ++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/models/quote.js b/src/models/quote.js index cce2043..fce7dba 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -16,7 +16,7 @@ const Quote = Backbone.Model.extend({ this.set('price', price + 1); }, sell() { - const price = this.get('price') - 1; + const price = this.get('price'); this.bus.trigger('trade', {symbol: this.symbol, price: price, buy: false}); this.set('price', price - 1); }, diff --git a/src/views/order_view.js b/src/views/order_view.js index c3429e4..be1b644 100644 --- a/src/views/order_view.js +++ b/src/views/order_view.js @@ -15,16 +15,17 @@ const OrderView = Backbone.View.extend({ return this; }, executeTrade() { - if (this.quote.get('price') >= this.order.get('priceTarget') && !this.order.get('buy')) { // TODO: Figure out why it sells below selling price. ex: sell @ 90, sold @ 89.7 - this.stopListening(); + const quotePrice = this.quote.get('price'); + const target = this.order.get('priceTarget'); + const isBuy = this.order.get('buy'); + if (quotePrice >= target && !isBuy) { + this.stopListening().remove(); this.quote.sell(); this.order.destroy(); - this.remove(); - } else if (this.quote.get('price') <= this.order.get('priceTarget') && this.order.get('buy')) { - this.stopListening(); + } else if (quotePrice <= target && isBuy) { + this.stopListening().remove(); this.quote.buy(); this.order.destroy(); - this.remove(); } } }); diff --git a/src/views/quote_view.js b/src/views/quote_view.js index 3d0cd47..f6a1210 100644 --- a/src/views/quote_view.js +++ b/src/views/quote_view.js @@ -14,10 +14,10 @@ const QuoteView = Backbone.View.extend({ }, events: { 'click button.btn-buy': function() { - let trade = this.model.buy(); + this.model.buy(); }, 'click .btn-sell': function() { - let trade = this.model.sell(); + this.model.sell(); }, }, }); From 1f506d149e793d7afe8631dedbb482f84d0cccf3 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Sat, 16 Dec 2017 23:35:43 -0800 Subject: [PATCH 10/14] Wave 3, added cancel handler --- src/views/order_view.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/views/order_view.js b/src/views/order_view.js index be1b644..26afb7f 100644 --- a/src/views/order_view.js +++ b/src/views/order_view.js @@ -14,6 +14,9 @@ const OrderView = Backbone.View.extend({ this.$el.html(compiledTemplate); return this; }, + events: { + 'click button.btn-cancel': 'cancelOrder', + }, executeTrade() { const quotePrice = this.quote.get('price'); const target = this.order.get('priceTarget'); @@ -27,7 +30,11 @@ const OrderView = Backbone.View.extend({ this.quote.buy(); this.order.destroy(); } - } + }, + cancelOrder() { + this.stopListening().remove(); + this.order.destroy(); + }, }); export default OrderView; From 659d4c923e743e4392de5b2def43954bfe1378f2 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Sat, 16 Dec 2017 23:43:41 -0800 Subject: [PATCH 11/14] refactored quote buy and sell fx --- src/models/quote.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/models/quote.js b/src/models/quote.js index fce7dba..f2803fb 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -10,15 +10,17 @@ const Quote = Backbone.Model.extend({ this.bus = this.get('bus'); this.symbol = this.get('symbol'); }, + changePrice(newPrice, oldPrice, isBuy) { + this.set('price', newPrice); + this.bus.trigger('trade', {symbol: this.symbol, price: oldPrice, buy: isBuy}); + }, buy() { const price = this.get('price'); - this.bus.trigger('trade', {symbol: this.symbol, price: price, buy: true}); - this.set('price', price + 1); + this.changePrice(price + 1, price, true); }, sell() { const price = this.get('price'); - this.bus.trigger('trade', {symbol: this.symbol, price: price, buy: false}); - this.set('price', price - 1); + this.changePrice(price - 1, price, false); }, }); From b6918e1d7e8503402d6b571e1f04897f579df375 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Sun, 17 Dec 2017 01:05:08 -0800 Subject: [PATCH 12/14] added basic error handling on Order form --- dist/index.html | 2 ++ src/models/order.js | 31 +++++++++++++++++++++++++++++++ src/views/order_entry_view.js | 15 ++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/dist/index.html b/dist/index.html index f42ecea..af5fdc8 100644 --- a/dist/index.html +++ b/dist/index.html @@ -69,6 +69,8 @@

    Order Entry Form

    +
      +
    diff --git a/src/models/order.js b/src/models/order.js index f7166aa..0d1026e 100644 --- a/src/models/order.js +++ b/src/models/order.js @@ -7,6 +7,37 @@ const Order = Backbone.Model.extend({ buy: null, quote: null, }, + validate: function(attributes) { + const errors = {}; + if (!attributes.symbol) { + errors['symbol'] = ['You must select a stock']; // Not sure necessary + } + + if (attributes.buy === null) { + errors['Trade'] = ['You must select buy or sell']; // Not sure necessary + } + + if (!attributes.quote) { + errors['Stock'] = ['Uh Oh! Something went terribly wrong']; // Not sure necessary + } + const quotePrice = attributes.quote[0].attributes.price; + + if (!attributes.priceTarget) { + errors['Price'] = ['Cannot be blank']; + } else if (isNaN(attributes.priceTarget)) { + errors['Price'] = ['Must be a numeric value']; + } else if (attributes.priceTarget <= quotePrice && attributes.buy === false) { + errors['Price'] = ['Price is lower than Market Price!']; + } else if (attributes.priceTarget >= quotePrice && attributes.buy) { + errors['Price'] = ['Price is higher than Market Price!']; + } + + if (Object.keys(errors).length > 0) { + return errors; + } else { + return false; + } + } }); export default Order; diff --git a/src/views/order_entry_view.js b/src/views/order_entry_view.js index ff175e8..843141a 100644 --- a/src/views/order_entry_view.js +++ b/src/views/order_entry_view.js @@ -1,5 +1,6 @@ import Backbone from 'backbone'; import $ from 'jquery'; +import _ from 'underscore'; import Order from '../models/order'; const OrderEntryView = Backbone.View.extend({ @@ -9,6 +10,7 @@ const OrderEntryView = Backbone.View.extend({ }, renderForm() { this.$('select[name="symbol"]').empty(); + // this.$('input[name="price-target"]').empty(); this.model.quotes.each((quote) => { this.$('select[name="symbol"]').append(this.template(quote)); }) @@ -35,8 +37,19 @@ const OrderEntryView = Backbone.View.extend({ orderData.quote = this.model.quotes.where({symbol: orderData.symbol}); const newOrder = new Order(orderData); - this.model.orders.add(newOrder); + if (newOrder.isValid()) { + this.model.orders.add(newOrder); + this.renderForm(); + } else { + this.renderValidationFailure(newOrder.validationError); + } }, + renderValidationFailure(errorsHash) { + this.$('.form-errors ul').empty(); + for (const key in errorsHash) { + this.$('.form-errors ul').append(`
  • ${key}: ${errorsHash[key]}
  • `); + } + } }); export default OrderEntryView; From cf6589e8c070f0b3c65233cdbbeea986a562ea5c Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Sun, 17 Dec 2017 21:47:42 -0800 Subject: [PATCH 13/14] Refactored error messaging a bit --- spec/models/quote_spec.js | 47 ++++++++++++++++++++++++++-------- src/models/order.js | 4 +-- src/views/order_entry_view.js | 4 +-- src/views/order_view.js | 2 -- src/views/quote_list_view.js | 48 ----------------------------------- src/views/quote_view.js | 2 -- src/views/trade_list_view.js | 1 - 7 files changed, 39 insertions(+), 69 deletions(-) diff --git a/spec/models/quote_spec.js b/spec/models/quote_spec.js index be2e376..6835444 100644 --- a/spec/models/quote_spec.js +++ b/spec/models/quote_spec.js @@ -1,31 +1,56 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; import Quote from 'models/quote'; describe('Quote spec', () => { + // let spyEvent; let quote; + beforeEach(() => { quote = new Quote({ symbol: 'HELLO', price: 100.00, + bus: _.extend({}, Backbone.Events), }); }); - describe('Buy function', () => { - it('increases the price by $1.00', () => { - const startPrice = quote.get('price'); + describe('changePrice function', () => { + it('sets a new price', () => { + quote.changePrice(50, 10, true); - quote.buy(); + expect(quote.get('price')).toEqual(50); + }); - expect(quote.get('price')).toEqual(startPrice + 1.00); + it('triggers a trade', () => { + // TODO: Figure out how to handle triggers + // spyEvent = spyOn(quote.get('bus'), 'trade'); + // quote.changePrice(50, 10, true); + // + // + // expect(spyEvent).toHaveBeenTriggered(); }); }); - describe('Sell function', () => { - it('decreases the price by $1.00', () => { - const startPrice = quote.get('price'); +describe('Buy function', () => { + it('increases the price by $1.00', () => { + const startPrice = quote.get('price'); - quote.sell(); + quote.buy(); - expect(quote.get('price')).toEqual(startPrice - 1.00); - }); + expect(quote.get('price')).toEqual(startPrice + 1.00); }); }); + +describe('Sell function', () => { + it('decreases the price by $1.00', () => { + const startPrice = quote.get('price'); + + quote.sell(); + + expect(quote.get('price')).toEqual(startPrice - 1.00); + }); +}); +// TODO: Test any custom methods +// TODO: Create Order spec to test order models (esp validations) +// TODO: Optional write tests which verify that limit orders are executed and destroyed when the relevant stock reaches the orders target price +}); diff --git a/src/models/order.js b/src/models/order.js index 0d1026e..7ad83f9 100644 --- a/src/models/order.js +++ b/src/models/order.js @@ -23,9 +23,9 @@ const Order = Backbone.Model.extend({ const quotePrice = attributes.quote[0].attributes.price; if (!attributes.priceTarget) { - errors['Price'] = ['Cannot be blank']; + errors['Price'] = ['Price cannot be blank']; } else if (isNaN(attributes.priceTarget)) { - errors['Price'] = ['Must be a numeric value']; + errors['Price'] = ['Price must be a numeric value']; } else if (attributes.priceTarget <= quotePrice && attributes.buy === false) { errors['Price'] = ['Price is lower than Market Price!']; } else if (attributes.priceTarget >= quotePrice && attributes.buy) { diff --git a/src/views/order_entry_view.js b/src/views/order_entry_view.js index 843141a..406cd8c 100644 --- a/src/views/order_entry_view.js +++ b/src/views/order_entry_view.js @@ -1,6 +1,4 @@ import Backbone from 'backbone'; -import $ from 'jquery'; -import _ from 'underscore'; import Order from '../models/order'; const OrderEntryView = Backbone.View.extend({ @@ -47,7 +45,7 @@ const OrderEntryView = Backbone.View.extend({ renderValidationFailure(errorsHash) { this.$('.form-errors ul').empty(); for (const key in errorsHash) { - this.$('.form-errors ul').append(`
  • ${key}: ${errorsHash[key]}
  • `); + this.$('.form-errors ul').append(`

    ${errorsHash[key]}

    `); } } }); diff --git a/src/views/order_view.js b/src/views/order_view.js index 26afb7f..9cd6ad5 100644 --- a/src/views/order_view.js +++ b/src/views/order_view.js @@ -1,6 +1,4 @@ import Backbone from 'backbone'; -import $ from 'jquery'; -import _ from 'underscore'; const OrderView = Backbone.View.extend({ initialize(params) { diff --git a/src/views/quote_list_view.js b/src/views/quote_list_view.js index d181165..5a8b890 100644 --- a/src/views/quote_list_view.js +++ b/src/views/quote_list_view.js @@ -1,6 +1,4 @@ import Backbone from 'backbone'; -// import _ from 'underscore'; -// import Quote from '../models/quote'; import QuoteView from '../views/quote_view'; const QuoteListView = Backbone.View.extend({ @@ -9,64 +7,18 @@ const QuoteListView = Backbone.View.extend({ this.listenTo(this.model, 'update', this.render); }, render() { - // Clear the unordered list this.$('#quotes').empty(); - // Iterate through the list rendering each Quote this.model.each((quote) => { - // Create a new QuoteView with the model & template const quoteView = new QuoteView({ model: quote, template: this.template, tagName: 'li', className: 'quote', }); - // Then render the QuoteView - // And append the resulting HTML to the DOM. this.$('#quotes').append(quoteView.render().$el); }); return this; }, - // events: { - // 'click #add-new-quote': 'addQuote' - // }, - // addQuote: function(event) { - // event.preventDefault(); - // - // const quoteData = {}; - // ['quote_name', 'assignee'].forEach( (field) => { - // const val = this.$(`input[name=${field}]`).val(); - // if (val != '') { - // quoteData[field] = val; - // } - // }); - // const newQuote = new Quote(quoteData); - // - // if (newQuote.isValid()) { - // this.model.add(newQuote); - // this.updateStatusMessageWith(`New quote added: ${newQuote.get('quote_name')}`); - // } else { - // this.updateStatusMessageFrom(newQuote.validationError); - // } - // }, - // // helper method for updating the DOM with the status from a hash - // updateStatusMessageFrom: function(messageHash){ - // const statusMessagesEl = this.$('#status-messages'); - // statusMessagesEl.empty(); - // - // _.each(messageHash, (messageType) => { - // messageType.forEach((message) => { - // statusMessagesEl.append(`
  • ${message}
  • `); - // }) - // }); - // statusMessagesEl.show(); - // }, - // // helper method for updating the DOM with the status from a string - // updateStatusMessageWith: function(message) { - // const statusMessagesEl = this.$('#status-messages'); - // statusMessagesEl.empty(); - // statusMessagesEl.append(`
  • ${message}
  • `); - // statusMessagesEl.show(); - // } }); export default QuoteListView; diff --git a/src/views/quote_view.js b/src/views/quote_view.js index f6a1210..357bf00 100644 --- a/src/views/quote_view.js +++ b/src/views/quote_view.js @@ -1,6 +1,4 @@ import Backbone from 'backbone'; -import $ from 'jquery'; -import _ from 'underscore'; const QuoteView = Backbone.View.extend({ initialize(params) { diff --git a/src/views/trade_list_view.js b/src/views/trade_list_view.js index 8e46b6c..14e048a 100644 --- a/src/views/trade_list_view.js +++ b/src/views/trade_list_view.js @@ -1,5 +1,4 @@ import Backbone from 'backbone'; -import _ from 'underscore'; const TradeListView = Backbone.View.extend({ initialize(params) { From a8a7539b875b421112bffcc23ea0fcd060957fa2 Mon Sep 17 00:00:00 2001 From: Jessica Owens Date: Mon, 18 Dec 2017 10:03:22 -0800 Subject: [PATCH 14/14] Tried to handle a trigger --- spec/models/quote_spec.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/spec/models/quote_spec.js b/spec/models/quote_spec.js index 6835444..2b8a2ae 100644 --- a/spec/models/quote_spec.js +++ b/spec/models/quote_spec.js @@ -5,6 +5,7 @@ import Quote from 'models/quote'; describe('Quote spec', () => { // let spyEvent; let quote; + let listener = _.extend({}, Backbone.Events); beforeEach(() => { quote = new Quote({ @@ -19,9 +20,28 @@ describe('Quote spec', () => { quote.changePrice(50, 10, true); expect(quote.get('price')).toEqual(50); + + quote.changePrice(); // NOTE: It does whatever the first param is regardless... room for refactoring. + + expect(quote.get('price')).toBeUndefined(); + + quote.changePrice(10, true); + + expect(quote.get('price')).toEqual(10); + + quote.changePrice(true); + + expect(quote.get('price')).toBeTruthy(); + + quote.changePrice('PIE'); + + expect(quote.get('price')).toEqual('PIE'); }); it('triggers a trade', () => { + listener.listenTo(quote.bus, 'trade', () => { + expect(true).toBeTruthy(); + }); // NOTE: Is this sufficient to test a trigger exists? // TODO: Figure out how to handle triggers // spyEvent = spyOn(quote.get('bus'), 'trade'); // quote.changePrice(50, 10, true);