Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions dist/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ <h3>Order Entry Form</h3>
<form>
<label for="symbol">Symbol</label>
<select name="symbol">
<!-- Option entries should be added here using JavaScript -->
</select>
<label for="price-target">Price</label>
<input type="number" name="price-target" step="0.10" min="0.00" placeholder="100.00" />
Expand All @@ -83,9 +82,6 @@ <h3>Order Entry Form</h3>
</footer>

<script type="text/template" id="quote-template">
<!-- The provided styles assume that you will insert this template
within an element with the class "quote" applied -->
<!-- <li class="quote"> -->
<div class="row small-12 columns">
<h3 class="symbol"><%- symbol %></h3>
<h3 class="price">$<%- price.toFixed(2) %></h3>
Expand All @@ -94,7 +90,6 @@ <h3 class="price">$<%- price.toFixed(2) %></h3>
<button class="btn-sell success button">Sell</button>
</div>
</div>
<!-- </li> -->
</script>

<script type="text/template" id="trade-template">
Expand Down
54 changes: 54 additions & 0 deletions spec/models/order_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import Order from 'models/order';

describe('Order spec', () => {
describe('Order validations', () => {
let order;
beforeEach(() => {
order = new Order({
symbol: 'HUMOR',
targetPrice: '78.70',
currentPrice: '88.50',
buy: true,
});
});

it('returns false if order is valid', () => {

expect(order.validate(order.attributes)).toEqual(false);
});

it('requires a symbol to be valid', () => {
order.set('symbol', '');

expect(order.isValid()).toBeFalsy();
});

it('requires a targetPrice to be valid', () => {
order.set('targetPrice', '');

expect(order.isValid()).toBeFalsy();
});

it('is invalid if the buy price is higher than the current price', () =>{
order.set('targetPrice', '92');

expect(order.isValid()).toBeFalsy();
});

it('is invalid if the sell price is lower than the current price', () => {
order.set('buy', false);

expect(order.isValid()).toBeFalsy();
});

it('is invalid if the target price is 0 or not a number', () => {
order.set('targetPrice', '0');

expect(order.isValid()).toBeFalsy();

order.set('targetPrice', 'pfue');

expect(order.isValid()).toBeFalsy();
});
});
});
55 changes: 52 additions & 3 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@ 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 QuoteList from 'collections/quote_list';
import Simulator from './models/simulator';
//import Quote from './models/quote';
import QuoteList from './collections/quote_list';
//import QuoteView from './views/quote_view';
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 = [
{
Expand All @@ -25,11 +33,52 @@ const quoteData = [
},
];

const quotes = new QuoteList(quoteData);

const orderData = [
{
buy: true,
currentPrice: quotes.findWhere({symbol: 'HUMOR'}).get('price'),
targetPrice: 77.1,
symbol: 'HUMOR',
},
{
buy: false,
currentPrice: quotes.findWhere({symbol: 'CLOTH'}).get('price'),
targetPrice: 120.4,
symbol: 'CLOTH',
},
]

const orders = new OrderList();

let bus = {};
bus = _.extend(bus, Backbone.Events);

$(document).ready(function() {
const quotes = new QuoteList(quoteData);

const simulator = new Simulator({
quotes: quotes,
});

simulator.start();
const quoteListView = new QuoteListView ({
model: quotes,
quoteTemplate: _.template($('#quote-template').html()),
tradeTemplate: _.template($('#trade-template').html()),
el: 'main',
bus: bus,
});

quoteListView.render();

const orderListView = new OrderListView ({
model: orders,
orderTemplate: _.template($('#order-template').html()),
el: '#order-workspace',
quotes: quotes,
bus: bus,
});

orderListView.render();
});
8 changes: 8 additions & 0 deletions src/collections/order_list.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import Backbone from 'backbone';
import Order from '../models/order';

const OrderList = Backbone.Collection.extend({
model: Order,
});

export default OrderList;
2 changes: 1 addition & 1 deletion src/collections/quote_list.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Backbone from 'backbone';
import Quote from 'models/quote';
import Quote from '../models/quote';

const QuoteList = Backbone.Collection.extend({
model: Quote,
Expand Down
31 changes: 31 additions & 0 deletions src/models/order.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Backbone from 'backbone';

const Order = Backbone.Model.extend({
validate: function(attributes) {
const errors = {};

if (!attributes.symbol) {
errors['symbol'] = ["Symbol is required"];
}

if (!attributes.targetPrice || attributes.targetPrice <= 0) {
errors['price_target'] = ["Target price is required"];
}

if (attributes.buy && attributes.targetPrice >= attributes.currentPrice) {
errors['price_target'] = ["Buy order target price cannot be greater than the current market price."]
}

if (!attributes.buy && attributes.targetPrice <= attributes.currentPrice) {
errors['price_target'] = ["Sell order target price cannot be less than the current market price."]
}

if (Object.keys(errors).length > 0) {
return errors;
} else {
return false;
}
},
});

export default Order;
10 changes: 6 additions & 4 deletions src/models/quote.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ const Quote = Backbone.Model.extend({
symbol: 'UNDEF',
price: 0.00
},

buy() {
// Implement this function to increase the price by $1.00
this.set('price', this.get('price') + 1);
this.set('buy', true);
this.trigger('tradeMe', this);
},

sell() {
// Implement this function to decrease the price by $1.00
this.set('price', this.get('price') - 1);
this.set('buy', false);
this.trigger('tradeMe', this);
},
});

Expand Down
74 changes: 74 additions & 0 deletions src/views/order_list_view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import Backbone from 'backbone';
import _ from 'underscore';
import OrderView from '../views/order_view';
import Order from '../models/order';

const OrderListView = Backbone.View.extend({
initialize(params) {
this.orderTemplate = params.orderTemplate;
this.quotes = params.quotes;
this.bus = params.bus;
this.listenTo(this.model, 'update', this.render);
},
render() {
this.$('#orders').empty();
this.model.each((order) => {
const orderView = new OrderView({
model: order,
orderTemplate: this.orderTemplate,
tagName: 'li',
className: 'order',
bus: this.bus,
});
this.$('#orders').append(orderView.render().$el);
});
return this;
},
events: {
'click button.btn-buy': 'addOrder',
'click button.btn-sell': 'addOrder',
},
addOrder: function(event) {
event.preventDefault();
let symbol = this.$('.order-entry-form [name=symbol]').val();
const newOrder = new Order({
symbol: symbol,
targetPrice: Number(this.$('.order-entry-form [name=price-target]').val()),
quote: this.quotes.findWhere({symbol: symbol}),
});

if (event.target.innerHTML === 'Buy') {
newOrder.set('buy', true);
} else {
newOrder.set('buy', false)
}
newOrder.set('currentPrice', this.quotes.findWhere({symbol: symbol}).attributes['price']);

if (newOrder.isValid()) {
this.model.add(newOrder);
this.updateStatusMessageWith(`New order for ${newOrder.get('symbol')} has been saved.`)
this.clearForm();
} else {
this.updateStatusMessageFrom(newOrder.validationError);
}
},
updateStatusMessageFrom: function(messageHash) {
const statusMessagesEl = this.$('.form-errors');
statusMessagesEl.empty();
_.each(messageHash, (messageType) => {
messageType.forEach((message) => {
statusMessagesEl.append(`<p>${message}</p>`);
});
});
},
updateStatusMessageWith: function(message) {
const statusMessagesEl = this.$('.form-errors');
statusMessagesEl.empty();
statusMessagesEl.append(`<p>${message}</p>`);
},
clearForm: function() {
this.$('.order-entry-form input').val('')
},
});

export default OrderListView;
35 changes: 35 additions & 0 deletions src/views/order_view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Backbone from 'backbone';
import Order from '../models/order';

const OrderView = Backbone.View.extend({
initialize(params) {
this.template = params.orderTemplate;
this.bus = params.bus;
this.listenTo(this.bus, `check${this.model.get('symbol')}`, this.checkQuote);
},
render() {
const compiledTemplate = this.template(this.model.toJSON());
this.$el.html(compiledTemplate);
return this;
},
events: {
'click button.btn-cancel': 'cancelOrder',
},
checkQuote: function(quote) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like business logic and would make some sense to put into the Model

if (this.model.get('buy') && quote.get('price') < this.model.get('targetPrice')) {
quote.buy();
this.cancelOrder();
}

if (!this.model.get('buy') && quote.get('price') > this.model.get('targetPrice')) {
quote.sell();
this.cancelOrder();
}
},
cancelOrder: function(event) {
this.model.destroy();
this.remove();
},
});

export default OrderView;
35 changes: 35 additions & 0 deletions src/views/quote_list_view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import Backbone from 'backbone';
import _ from 'underscore';
import QuoteView from '../views/quote_view';

const QuoteListView = Backbone.View.extend({
initialize(params) {
this.quoteTemplate = params.quoteTemplate;
this.tradeTemplate = params.tradeTemplate;
this.bus = params.bus;
this.listenTo(this.model, 'update', this.render);
this.listenTo(this.model, 'tradeMe', this.addTrade);
},
render() {
this.$('#quotes').empty();
this.$('form select').empty();
this.model.each((quote) => {
const quoteView = new QuoteView({
model: quote,
template: this.quoteTemplate,
tagName: 'li',
className: 'quote',
bus: this.bus,
});
this.$('#quotes').append(quoteView.render().$el);
this.$('form select').append(`<option value="${ quoteView.model.get('symbol') }">${ quoteView.model.get('symbol') }</option>`);
});
return this;
},
addTrade: function(quote) {
const compiledTemplate = this.tradeTemplate(quote.toJSON());
this.$('#trades').prepend(compiledTemplate);
},
});

export default QuoteListView;
28 changes: 28 additions & 0 deletions src/views/quote_view.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
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() {
const compiledTemplate = this.template(this.model.toJSON());
this.$el.html(compiledTemplate);
this.bus.trigger(`check${this.model.get('symbol')}`, this.model);
return this;
},
events: {
'click button.btn-buy': 'buyQuote',
'click button.btn-sell': 'sellQuote',
},
buyQuote: function(event) {
this.model.buy();
},
sellQuote: function(event) {
this.model.sell();
},
});

export default QuoteView;