diff --git a/spec/models/order_spec.js b/spec/models/order_spec.js new file mode 100644 index 0000000..b73770b --- /dev/null +++ b/spec/models/order_spec.js @@ -0,0 +1,67 @@ +import Order from 'models/order'; + +describe('Order spec', () => { + + describe('Initialize', () => { + it('Can access all instance variables.', () => { + const order = new Order({ + currentQuote: 'HELLO', + currentPrice: 100.00, + buy: true, + targetPrice: 101.00 + }); + + expect(order.get('currentQuote')).toEqual('HELLO'); + expect(order.get('currentPrice')).toEqual(100.00); + expect(order.get('buy')).toEqual(true); + expect(order.get('targetPrice')).toEqual(101.00); + }); + }); + + describe('Creates a new Order.', () => { + + it('Creates a valid instance of Order', () => { + const order = new Order({ + currentQuote: 'HELLO', + currentPrice: 100.00, + buy: true, + targetPrice: 50.00, + }); + + expect(order.isValid()).toEqual(true); + }); + + it ('Rejects an empty price.', () => { + const order = new Order({ + symbol: 'HELLO', + currentPrice: '', + action: true, + }); + + expect(order.isValid()).toEqual(false); + }); + + it('Can not create an order with target price greater than or equal to the current price when buying.', () => { + const order = new Order({ + currentQuote: 'HELLO', + currentPrice: 100.00, + buy: true, + targetPrice: 101.00, + }); + + expect(order.isValid()).toEqual(false); + }); + + it('Can not create an order with target price less than or equal to the current price when selling.', () => { + const order = new Order({ + currentQuote: 'HELLO', + currentPrice: 100.00, + buy: false, + targetPrice: 99.00, + }); + + expect(order.isValid()).toEqual(false); + }); + + }); +}); diff --git a/src/app.js b/src/app.js index 03ec910..b828170 100644 --- a/src/app.js +++ b/src/app.js @@ -2,9 +2,18 @@ import 'foundation-sites/dist/foundation.css'; import 'css/app.css'; import $ from 'jquery'; +import _ from 'underscore'; +import Backbone from 'backbone'; import Simulator from 'models/simulator'; + +import Quote from 'models/quote'; import QuoteList from 'collections/quote_list'; +import QuoteListView from './views/quote_list_view'; + +import Order from 'models/order'; +import OrderList from 'collections/order_list'; +import OrderListView from './views/order_list_view'; const quoteData = [ { @@ -27,9 +36,36 @@ const quoteData = [ $(document).ready(function() { const quotes = new QuoteList(quoteData); + const orders = new OrderList(); const simulator = new Simulator({ quotes: quotes, }); + const quoteListView = new QuoteListView({ + model: quotes, + template: _.template($('#quote-template').html()), + el: '#quotes-container' + + }); + quoteListView.render(); + + const formDropDown = function formDropDown() { + const $formSelect = $('select[name=symbol]'); + + quotes.forEach((quote) => { + const quoteSymbol = quote.get('symbol'); + $formSelect.append(``); + }); + }; + formDropDown(); + + const orderListView = new OrderListView({ + model: orders, + template: _.template($('#order-template').html()), + quoteList: quotes, + el: '#order-workspace' + }); + orderListView.render(); + simulator.start(); }); diff --git a/src/collections/order_list.js b/src/collections/order_list.js new file mode 100644 index 0000000..8d7cabd --- /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..b3aaa01 --- /dev/null +++ b/src/models/order.js @@ -0,0 +1,31 @@ +import Backbone from 'backbone'; + +const Order = Backbone.Model.extend({ + initialize(params) { + this.buy = params.buy, + this.currentQuote = params.currentQuote, + this.currentPrice = params.currentPrice, + this.targetPrice = params.targetPrice + }, + + validate(attributes) { + const error = {}; + if (this.buy && this.targetPrice >= this.currentPrice) { + error.targetPrice = ['Price is higher than market, aim lower!']; + } + else if (!this.buy && this.targetPrice <= this.currentPrice) { + error.targetPrice = ['Price is lower than market, aim higher!']; + } + else if (!attributes.targetPrice) { + error.targetPrice = ['Price must not be blank!']; + } + + if (Object.keys(error).length < 1) { + return error; + } else { + return false; + } + }, +}); + +export default Order; diff --git a/src/models/quote.js b/src/models/quote.js index 4fbf466..62583ec 100644 --- a/src/models/quote.js +++ b/src/models/quote.js @@ -7,11 +7,11 @@ const Quote = Backbone.Model.extend({ }, buy() { - // Implement this function to increase the price by $1.00 + this.set('price', this.get('price') + 1.00); }, sell() { - // Implement this function to decrease the price by $1.00 + this.set('price', this.get('price') - 1.00); }, }); diff --git a/src/views/order_list_view.js b/src/views/order_list_view.js new file mode 100644 index 0000000..a97495a --- /dev/null +++ b/src/views/order_list_view.js @@ -0,0 +1,75 @@ +import _ from 'underscore'; +import $ from 'jquery'; + +import Backbone from 'backbone'; + +import OrderView from '../views/order_view'; +import Order from '../models/order' + +const OrderListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.quoteList = params.quoteList; + 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); + }); + return this; + }, + + events: { + 'click button.btn-buy': 'buyOrder', + 'click button.btn-sell': 'sellOrder' + }, + + buyOrder: function(event) { + event.preventDefault(); + const orderData = { + buy: true, + symbol: this.$('select[name=symbol]').val(), + targetPrice: parseFloat(this.$('input[name=price-target]').val()), + currentPrice: 0, + currentQuote: 0 + }; + + const newOrder = new Order(orderData); + + if (newOrder.isValid()) { + this.model.add(newOrder); + } else { + console.log('Invalid Order!', newOrder.validationError); + } + }, + + sellOrder: function(event) { + event.preventDefault(); + + const orderData = { + buy: false, + symbol: this.$('select[name=symbol]').val(), + targetPrice: parseFloat(this.$('input[name=price-target]').val()), + }; + + const newOrder = new Order(orderData); + + if (newOrder.isValid()) { + this.model.add(newOrder); + } else { + console.log('Invalid Order!'); + } + }, + + +}); + +export default OrderListView; diff --git a/src/views/order_view.js b/src/views/order_view.js new file mode 100644 index 0000000..289cf99 --- /dev/null +++ b/src/views/order_view.js @@ -0,0 +1,30 @@ +import $ from 'jquery'; +import _ from 'underscore'; + +import Backbone from 'backbone'; + +import Order from '../models/order'; + +const OrderView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.orderTemplate = params.orderTemplate; + 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-cancel': 'cancelOrder', + }, + + cancelOrder(){ + this.model.destroy(); + }, +}); + +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..4978bce --- /dev/null +++ b/src/views/quote_list_view.js @@ -0,0 +1,28 @@ +import _ from 'underscore'; +import Backbone from 'backbone'; + +import QuoteView from '../views/quote_view'; +import Quote from '../models/quote'; + +const QuoteListView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.listenTo(this.model, 'update', this.render); + }, + + render() { + this.$('#quotes').empty(); + this.model.each((quote) => { + const quoteView = new QuoteView({ + model: quote, + template: this.template, + tagName: 'li', + className: 'quote', + }); + this.$('#quotes').append(quoteView.render().$el); + }); + return this; + }, +}); + +export default QuoteListView; diff --git a/src/views/quote_view.js b/src/views/quote_view.js new file mode 100644 index 0000000..3de4d2c --- /dev/null +++ b/src/views/quote_view.js @@ -0,0 +1,41 @@ +import $ from 'jquery'; +import _ from 'underscore'; + +import Backbone from 'backbone'; +import Quote from '../models/quote'; + +const QuoteView = Backbone.View.extend({ + initialize(params) { + this.template = params.template; + this.listenTo(this.model, "change", this.render); + }, + + // This is the portion of the code, that needs to be listening to the buttons for click events, as it is rendering the buttons on the page. + render() { + const compiledTemplate = this.template(this.model.toJSON()); + this.$el.html(compiledTemplate); + + return this; + }, + + events: { + 'click button.btn-buy': 'buyQuote', + 'click button.btn-sell': 'sellQuote', + }, + + buyQuote: function() { + this.model.set('buy', true); + let tradeTemplate = _.template($('#trade-template').html()); + $('#trades').prepend(tradeTemplate(this.model.attributes)); + this.model.buy(); + }, + + sellQuote: function() { + this.model.set('buy', false); + let tradeTemplate = _.template($('#trade-template').html()); + $('#trades').prepend(tradeTemplate(this.model.attributes)); + this.model.sell(); + }, +}); + +export default QuoteView;