diff --git a/dist/index.html b/dist/index.html index 8a046fa..44a5f59 100644 --- a/dist/index.html +++ b/dist/index.html @@ -5,7 +5,6 @@ -
@@ -44,7 +43,7 @@

Trade History

-
+

Open Orders

@@ -57,13 +56,13 @@

Open Orders

Order Entry Form

-
+ - - + @@ -85,22 +84,22 @@

Order Entry Form

@@ -110,7 +109,7 @@

<%- symbol %>

<%- buy ? 'buy' : 'sell' %> at - $<%- targetPrice.toFixed(2) %> + ₩<%- targetPrice %>
diff --git a/spec/models/order_spec.js b/spec/models/order_spec.js new file mode 100644 index 0000000..b60f5af --- /dev/null +++ b/spec/models/order_spec.js @@ -0,0 +1,48 @@ +import Quote from 'models/quote'; +import Order from 'models/order'; + +describe('Order spec', () => { + let quote; + let order; + beforeEach(() => { + quote = new Quote({ + symbol: 'CLOTH', + price: 100.00, + }); + order = new Order({ + symbol: 'CLOTH', + quote: quote, + buy: true, + targetPrice: 100, + }) + }); + + describe('Create a new Order', () => { + it('creates a valid instance of an Order', () => { + order.set('targetPrice', 80.00); + order.set('buy', true); + + expect(order.isValid()).toEqual(true); + }) + + it('invalid order if price is not a number', () => { + order.set('targetPrice', 'hello'); + + expect(order.isValid()).toEqual(false); + }) + + it('invalid buy order if price is higher than market', () => { + order.set('targetPrice', 180); + order.set('buy', true); + + expect(order.isValid()).toEqual(false); + }) + + it('invalid sell order if price is lower than market', () => { + order.set('targetPrice', 8); + order.set('buy', false); + + expect(order.isValid()).toEqual(false); + }) + }); +}); diff --git a/src/app.js b/src/app.js index 03ec910..4a2b732 100644 --- a/src/app.js +++ b/src/app.js @@ -2,17 +2,25 @@ import 'foundation-sites/dist/foundation.css'; import 'css/app.css'; import $ from 'jquery'; +import _ from 'underscore'; -import Simulator from 'models/simulator'; -import QuoteList from 'collections/quote_list'; +import Simulator from './models/simulator'; +import QuoteList from './collections/quote_list'; +import Order from './models/order' +import OrderList from './collections/order_list' + +import QuoteListView from './views/quote_list_view'; +import TradeListView from './views/trade_list_view'; +import OrderFormView from './views/order_form_view'; +import OrderListView from './views/order_list_view'; const quoteData = [ { - symbol: 'HUMOR', + symbol: '유머', price: 88.50, }, { - symbol: 'CLOTH', + symbol: '피복', price: 81.70, }, { @@ -25,11 +33,64 @@ const quoteData = [ }, ]; +quoteData.forEach(function(quote) { + $('#dropdown').append(``) +}) + + + $(document).ready(function() { + + + let bus = {}; + bus = _.extend(bus, Backbone.Events); + + let template = _.template($('#quote-template').html()); + let tradeTemplate = _.template($('#trade-template').html()); + let orderTemplate = _.template($('#order-template').html()); + const quotes = new QuoteList(quoteData); const simulator = new Simulator({ quotes: quotes, }); + const orders = new OrderList(); + + const quoteListView = new QuoteListView({ + el: '#quotes-container', + model: quotes, + template: template, + bus: bus, + }); + + const tradeListView = new TradeListView({ + el: '#trades-container', + template: tradeTemplate, + bus: bus, + }) + const orderFormView = new OrderFormView({ + // quoteData: quoteData, + el: '.order-entry-form', + bus: bus, + orderList: orders, + quoteList: quotes, + }); + + const order = new Order({ + bus: bus, + }) + + const orderListView = new OrderListView({ + el: '.orders-list-container', + model: orders, + template: orderTemplate, + bus: bus, + }); + + + orderListView.render(); + quoteListView.render(); simulator.start(); + + }); diff --git a/src/collections/order_list.js b/src/collections/order_list.js new file mode 100644 index 0000000..2e83bb2 --- /dev/null +++ b/src/collections/order_list.js @@ -0,0 +1,8 @@ +import Backbone from 'backbone'; +import Order from '../models/order' + +const OrderList = Backbone.Collection.extend({ + model: Order, +}) + +export default OrderList diff --git a/src/models/order.js b/src/models/order.js new file mode 100644 index 0000000..14069ae --- /dev/null +++ b/src/models/order.js @@ -0,0 +1,28 @@ +import Backbone from 'backbone'; + +const Order = Backbone.Model.extend({ + defaults: { + symbol: 'UNDEF', + targetPrice: 9.00, + buy: true, + }, + validate: function(attributes) { + let error = ''; + + if (attributes.buy && (attributes.targetPrice >= attributes.quote.get('price'))) { + error = 'Price higher than market price!'; + } else if (!attributes.buy && (attributes.targetPrice <= attributes.quote.get('price'))) { + error = 'Price lower than market price!'; + } else if (isNaN(attributes.targetPrice)) { + error = 'Invalid Target Price'; + } + + if (error != '') { + return error + } else { + return false + } + }, +}) + +export default Order diff --git a/src/models/quote.js b/src/models/quote.js index 4fbf466..f869d6d 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -7,11 +7,14 @@ const Quote = Backbone.Model.extend({ }, buy() { - // Implement this function to increase the price by $1.00 - }, + console.log('click into buy()'); + this.set('price', this.get('price') + 1) + console.log(this.get('price')); + }, sell() { - // Implement this function to decrease the price by $1.00 + console.log('click into sell()'); + this.set('price', this.get('price') - 1) }, }); diff --git a/src/models/simulator.js b/src/models/simulator.js index a245046..e709253 100644 --- a/src/models/simulator.js +++ b/src/models/simulator.js @@ -20,7 +20,7 @@ const Simulator = Backbone.Model.extend({ this.quotes.forEach((quote) => { // Calculate a random price movement - const maxChange = 1.00; + const maxChange = 0.10; const minChange = 0.00; let change = _.random(minChange * 10, maxChange * 10) / 10; diff --git a/src/views/order_form_view.js b/src/views/order_form_view.js new file mode 100644 index 0000000..8b3b86e --- /dev/null +++ b/src/views/order_form_view.js @@ -0,0 +1,61 @@ +import Backbone from 'backbone'; + +import Quote from '../models/quote'; +import Order from '../models/order'; +import OrderList from '../collections/order_list'; +import QuoteList from '../collections/quote_list'; + + +const OrderFormView = Backbone.View.extend({ + initialize(params){ + this.bus = params.bus; + this.quoteList = params.quoteList + }, + // quoteData.each((quote) => { + // $('#dropdown').append(``); + // }), + + readOrderFormData(type) { + const orderData = {}; + + const $inputSymbolValue = this.$(`select[name="symbol"]`).val(); + const $inputPriceTargert = this.$(`input[name="price-target"]`) + + + const priceValue = parseFloat($inputPriceTargert.val()).toFixed(2); + // console.log(`priceValue = ${priceValue}`); + + // Don't take empty strings, so that Backbone can + // fill in default values + orderData['symbol'] = $inputSymbolValue; + if (priceValue != '') { + orderData['targetPrice'] = priceValue; + } + orderData['buy'] = type + console.log(this.quoteList); + console.log(orderData['symbol']) + orderData['quote'] = this.quoteList.find({symbol: orderData['symbol']}); + $inputPriceTargert.val(''); + return orderData; + }, + events: { + 'click button.btn-buy': 'buyOrder', + 'click button.btn-sell': 'orderSell', + }, + buyOrder(event) { + event.preventDefault(); + console.log('test click order'); + let orderObject = this.readOrderFormData(true) + + this.bus.trigger('newOrder', orderObject) + }, + + orderSell(event) { + event.preventDefault(); + console.log('test click orderSell'); + let orderObject = this.readOrderFormData(false); + this.bus.trigger('newOrder', orderObject) + } + +}); +export default OrderFormView; diff --git a/src/views/order_list_view.js b/src/views/order_list_view.js new file mode 100644 index 0000000..e07be53 --- /dev/null +++ b/src/views/order_list_view.js @@ -0,0 +1,69 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; + +import Order from '../models/order' +import OrderView from './order_view'; + + +const OrderListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.bus = params.bus; + + this.listenTo(this.model, 'update', this.render) + + this.listenTo(this.bus, 'newOrder', this.addOrder); + this.listenTo(this.bus, 'newOrder', this.render); + // this.listenTo(this.bus, 'update', this.completeOrder) + + }, + render(orderObject) { + this.$('#orders').empty(); + // console.log('render order_list_view'); + // console.log(this.model); + this.model.each((order) => { + // console.log(order); + const orderView = new OrderView({ + model: order, + template: this.template, + tagName:'li', + className: 'order', + bus: this.bus, + }); + // console.log(orderView); + this.$('#orders').append(orderView.render().$el); + }); + return this; + }, + updateStatusMessageFrom(messageHash) { + const $statusMessages = this.$('#status-messages'); + $statusMessages.empty(); + Object.keys(messageHash).forEach((messageType) => { + messageHash[messageType].forEach((message) => { + $statusMessages.append(`
  • ${message}
  • `); + }); + }); + $statusMessages.show(); + }, + updateStatusMessage(message) { + this.updateStatusMessageFrom({ + 'order': [message], + }); + }, + addOrder(orderObject) { + // console.log(orderObject); + const newOrder = new Order(orderObject); + if (newOrder.isValid()) { + console.log('I am a valid order') + this.model.add(newOrder); + // this.clearFormData(); + this.updateStatusMessage(`${newOrder.get('symbol')} Created!`); + + } else { + this.updateStatusMessageFrom(newOrder.validationError); + newOrder.destroy(); + } + }, +}) + +export default OrderListView; diff --git a/src/views/order_view.js b/src/views/order_view.js new file mode 100644 index 0000000..35edbcc --- /dev/null +++ b/src/views/order_view.js @@ -0,0 +1,57 @@ +import Backbone from 'backbone'; +import Order from '../models/order'; + +import _ from 'underscore'; + +const OrderView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.bus = params.bus; + + this.listenTo(this.bus, 'cancelOrder', this.cancelOrder) + this.listenTo(this.model.get('quote'), 'change', this.executeOrder); + + }, + render() { + // console.log('render order_view'); + + const compiledTemplate = this.template(this.model.toJSON()); + this.$el.html(compiledTemplate) + + return this; + }, + events: { + 'click button.btn-cancel': 'cancelOrder' + }, + cancelOrder() { + console.log('i am in cancelOrder'); + console.log(this); + this.model.destroy(); + + }, + executeOrder() { + console.log('I am inside execute order'); + let quote = this.model.get('quote'); + console.log('this.model = '); + console.log(this.model); + + if (this.model.get('buy')) { + if (this.model.get('targetPrice') >= quote.get('price')) { + quote.buy(); + this.model.destroy(); + // After trying to add a trade to the tradeHistroyView I am still getting an error saying the 'buy' attribute is undefined despite a console.log(this.model) = a model with 'buy' defined as true. Any help is appreciated. + this.bus.trigger('destroy', this) + this.remove(); + } + } else { + if (this.model.get('targetPrice') <= quote.get('price')) { + quote.sell(); + this.model.destroy(); + this.remove(); + } + } + }, + +}) + +export default OrderView; diff --git a/src/views/quote_list_view.js b/src/views/quote_list_view.js new file mode 100644 index 0000000..b9c9a0f --- /dev/null +++ b/src/views/quote_list_view.js @@ -0,0 +1,36 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; +import QuoteView from '../views/quote_view'; +// import Quote from '../models/quote'; +// import CurrentQuoteView from './current_selected_view'; + +const QuoteListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.bus = params.bus; + this.listenTo(this.model, 'update', this.render); + }, + render() { + this.$('#quotes').empty(); + console.log(this.model); + this.model.each((quote) => { + const quoteView = new QuoteView({ + model: quote, + template: this.template, + tagName: 'li', + className: 'quote', + bus: this.bus, + }); + this.$('#quotes').append(quoteView.render().$el); + }); + return this; + }, + events: { + 'update this': 'checkQuote', + }, + checkQuote() { + this.bus.trigger('completeOrder', this) + } +}); + +export default QuoteListView; diff --git a/src/views/quote_view.js b/src/views/quote_view.js new file mode 100644 index 0000000..6a91c24 --- /dev/null +++ b/src/views/quote_view.js @@ -0,0 +1,49 @@ +import Backbone from 'backbone'; +import Quote from '../models/quote'; + +const QuoteView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.bus = params.bus; + this.listenTo(this.model, 'change', this.render); + }, + render() { + console.log('I rendered'); + const compiledTemplate = this.template(this.model.toJSON()); + this.$el.html(compiledTemplate) + + return this; + }, + events: { + 'click button.btn-buy': 'buyQuote', + 'click button.btn-sell': 'sellQuote', + 'update this.model': 'makeTradeObject' + }, + buyQuote(event) { + console.log('clicked into buyQuote') + console.log(this.model); + this.makeTradeObject(true), + this.model.buy() + }, + sellQuote(event) { + console.log('clicked into sellQuote'); + this.makeTradeObject(false), + this.model.sell() + }, + makeTradeObject(buy) { + console.log('buy stuff'); + const trade = { + symbol: this.model.get('symbol'), + price: this.model.get('price'), + buy: buy, + }; + console.log('trade object'); + console.log(trade); + this.bus.trigger('makeTrade', trade); + }, + checkQuote() { + this.bus.trigger('change', this.model) + } +}); + +export default QuoteView; diff --git a/src/views/trade_list_view.js b/src/views/trade_list_view.js new file mode 100644 index 0000000..7cf9336 --- /dev/null +++ b/src/views/trade_list_view.js @@ -0,0 +1,25 @@ +import Backbone from 'backbone'; +import _ from 'underscore'; +import Quote from '../models/quote'; + + +const TradeListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template + this.bus = params.bus + + this.listenTo(this.bus, 'makeTrade', this.addTrade); + this.listenTo(this.bus, 'destroy', this.addTrade) + }, + render() { + + }, + addTrade(trade) { + console.log('trade connection'); + console.log(trade); + const compiledTemplate = this.template(trade) + this.$('#trades').prepend(compiledTemplate) + } +}); + +export default TradeListView;