Skip to content

Instantly share code, notes, and snippets.

@knjname
Last active January 3, 2016 09:09
Show Gist options
  • Select an option

  • Save knjname/8440723 to your computer and use it in GitHub Desktop.

Select an option

Save knjname/8440723 to your computer and use it in GitHub Desktop.
global = @
$ ->
# モデル
model =
golds: 0
products: []
events: {}
greeting: ''
cart: {}
itemBag: {}
cartTotal: 0
get: (property) -> @[property]
set: (property, value) ->
console.log "Property #{property} has been changed: #{@[property]} => #{value}"
@[property] = value
@fire property
calculateCartTotal: ->
total = 0
for itemName, count of @cart
total += count * (p.price for p in @products when p.name is itemName)[0]
total
put: (property, key, value) ->
(@[property] ?= {})[key] = value
if property is 'cart'
@set 'greeting', if value is 0 then 'おうおう買わないのかい?' else 'たくさん買ってくれよ!'
@fire property
buy: ->
cartTotal = @calculateCartTotal()
if cartTotal is 0
@set 'greeting', '何か商品選んでくれよ'
else if cartTotal > @golds
@set 'greeting', '悪いけど、金が足りないみたいだな。'
else
@set 'greeting', 'まいどあり!'
@set 'golds', @golds - cartTotal
for itemName, count of @cart
@put 'itemBag', itemName, (@itemBag[itemName] or 0) + count
@set 'cart', {}
add: (property, item) ->
(@[property] ?= []).push item
@fire property
fire: (property) ->
for fn in (@events[property] ?= [])
fn()
on: (property, fn) ->
(@events[property] ?= []).push fn
# ビュー
$frame = $('#itemshop')
$greeting = $frame.find('.greeting')
$golds = $frame.find('.golds')
$products = $frame.find('.products')
$cart_total = $frame.find('.cart_total')
$buy = $frame.find('.buy')
$item_bag = $frame.find('.item_bag')
# (無限ループ対策 モデル変更 → ビュー変更 → モデル変更 ... の無限ループ回避)
# 本来なら、ループ検出したら打ち切る実装にしたほうがいいね
whileEventProcess = false
whileEvent = (fn) ->
try
whileEventProcess = true
fn()
finally
whileEventProcess = false
# コントローラ
controller =
init: ->
model.on 'golds', ->
$golds.text(model.get 'golds')
model.on 'products', ->
$trs = for p in model.get('products')
$('<tr>').append [
$('<td>').text p.name
$('<td>').text "#{p.price} ゴールド"
$('<td>').append (
do =>
$select = $('<select>').data(product: p).addClass('itemCount').append [
$('<option>').attr({value: cnt}).text(cnt) for cnt in [ 0..9 ]
]...
$select.val (model.get('cart')[p.name] or 0)
$select
)
]...
$products.html('').append $trs...
model.on 'cart', ->
$cart_total.text(model.calculateCartTotal())
for select in $products.find('select')
$(select).val 0
for itemName, count of model.get 'cart' when itemName is $(select).data('product').name
$(select).val count
model.on 'greeting', ->
$greeting.text(model.get 'greeting')
model.on 'itemBag', ->
$item_bag.text(JSON.stringify model.get('itemBag'))
$frame.on
change: ->
controller.onItemCountChange $(@).data('product').name, + $(@).val()
, 'select.itemCount'
$buy.click ->
controller.onBuy()
model.set 'golds', 1000
model.set 'greeting', 'へいいらっしゃい!'
model.add 'products', { name: 'ポーション', price: 50 }
model.add 'products', { name: 'ハイポーション', price: 250 }
model.add 'products', { name: 'エーテル', price: 500 }
model.add 'products', { name: 'フェニックスの尾', price: 1000 }
onItemCountChange: (itemName, count) -> whileEvent =>
model.put 'cart', itemName, count
onBuy: -> whileEvent =>
model.buy()
setTimeout (-> model.add 'products', {name: '10秒滞在記念アイテム', price: 200}), 10 * 1000
setTimeout (-> model.add 'products', {name: '50秒滞在記念アイテム', price: 1400}), 50 * 1000
$frame.find('.make_money').click ->
model.set 'golds', (model.get 'golds') + 1000
controller.init()
// Generated by CoffeeScript 1.6.3
(function() {
var global;
global = this;
$(function() {
var $buy, $cart_total, $frame, $golds, $greeting, $item_bag, $products, controller, model, whileEvent, whileEventProcess;
model = {
golds: 0,
products: [],
events: {},
greeting: '',
cart: {},
itemBag: {},
cartTotal: 0,
get: function(property) {
return this[property];
},
set: function(property, value) {
console.log("Property " + property + " has been changed: " + this[property] + " => " + value);
this[property] = value;
return this.fire(property);
},
calculateCartTotal: function() {
var count, itemName, p, total, _ref;
total = 0;
_ref = this.cart;
for (itemName in _ref) {
count = _ref[itemName];
total += count * ((function() {
var _i, _len, _ref1, _results;
_ref1 = this.products;
_results = [];
for (_i = 0, _len = _ref1.length; _i < _len; _i++) {
p = _ref1[_i];
if (p.name === itemName) {
_results.push(p.price);
}
}
return _results;
}).call(this))[0];
}
return total;
},
put: function(property, key, value) {
(this[property] != null ? this[property] : this[property] = {})[key] = value;
if (property === 'cart') {
this.set('greeting', value === 0 ? 'おうおう買わないのかい?' : 'たくさん買ってくれよ!');
}
return this.fire(property);
},
buy: function() {
var cartTotal, count, itemName, _ref;
cartTotal = this.calculateCartTotal();
if (cartTotal === 0) {
return this.set('greeting', '何か商品選んでくれよ');
} else if (cartTotal > this.golds) {
return this.set('greeting', '悪いけど、金が足りないみたいだな。');
} else {
this.set('greeting', 'まいどあり!');
this.set('golds', this.golds - cartTotal);
_ref = this.cart;
for (itemName in _ref) {
count = _ref[itemName];
this.put('itemBag', itemName, (this.itemBag[itemName] || 0) + count);
}
return this.set('cart', {});
}
},
add: function(property, item) {
(this[property] != null ? this[property] : this[property] = []).push(item);
return this.fire(property);
},
fire: function(property) {
var fn, _base, _i, _len, _ref, _results;
_ref = ((_base = this.events)[property] != null ? (_base = this.events)[property] : _base[property] = []);
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
fn = _ref[_i];
_results.push(fn());
}
return _results;
},
on: function(property, fn) {
var _base;
return ((_base = this.events)[property] != null ? (_base = this.events)[property] : _base[property] = []).push(fn);
}
};
$frame = $('#itemshop');
$greeting = $frame.find('.greeting');
$golds = $frame.find('.golds');
$products = $frame.find('.products');
$cart_total = $frame.find('.cart_total');
$buy = $frame.find('.buy');
$item_bag = $frame.find('.item_bag');
whileEventProcess = false;
whileEvent = function(fn) {
try {
whileEventProcess = true;
return fn();
} finally {
whileEventProcess = false;
}
};
controller = {
init: function() {
model.on('golds', function() {
return $golds.text(model.get('golds'));
});
model.on('products', function() {
var $trs, p, _ref;
$trs = (function() {
var _i, _len, _ref, _ref1, _results,
_this = this;
_ref = model.get('products');
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
p = _ref[_i];
_results.push((_ref1 = $('<tr>')).append.apply(_ref1, [
$('<td>').text(p.name), $('<td>').text("" + p.price + " ゴールド"), $('<td>').append((function() {
var $select, cnt, _ref1;
$select = (_ref1 = $('<select>').data({
product: p
}).addClass('itemCount')).append.apply(_ref1, [
(function() {
var _j, _results1;
_results1 = [];
for (cnt = _j = 0; _j <= 9; cnt = ++_j) {
_results1.push($('<option>').attr({
value: cnt
}).text(cnt));
}
return _results1;
})()
]);
$select.val(model.get('cart')[p.name] || 0);
return $select;
})())
]));
}
return _results;
}).call(this);
return (_ref = $products.html('')).append.apply(_ref, $trs);
});
model.on('cart', function() {
var count, itemName, select, _i, _len, _ref, _results;
$cart_total.text(model.calculateCartTotal());
_ref = $products.find('select');
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
select = _ref[_i];
$(select).val(0);
_results.push((function() {
var _ref1, _results1;
_ref1 = model.get('cart');
_results1 = [];
for (itemName in _ref1) {
count = _ref1[itemName];
if (itemName === $(select).data('product').name) {
_results1.push($(select).val(count));
}
}
return _results1;
})());
}
return _results;
});
model.on('greeting', function() {
return $greeting.text(model.get('greeting'));
});
model.on('itemBag', function() {
return $item_bag.text(JSON.stringify(model.get('itemBag')));
});
$frame.on({
change: function() {
return controller.onItemCountChange($(this).data('product').name, +$(this).val());
}
}, 'select.itemCount');
$buy.click(function() {
return controller.onBuy();
});
model.set('golds', 1000);
model.set('greeting', 'へいいらっしゃい!');
model.add('products', {
name: 'ポーション',
price: 50
});
model.add('products', {
name: 'ハイポーション',
price: 250
});
model.add('products', {
name: 'エーテル',
price: 500
});
return model.add('products', {
name: 'フェニックスの尾',
price: 1000
});
},
onItemCountChange: function(itemName, count) {
var _this = this;
return whileEvent(function() {
return model.put('cart', itemName, count);
});
},
onBuy: function() {
var _this = this;
return whileEvent(function() {
return model.buy();
});
}
};
setTimeout((function() {
return model.add('products', {
name: '10秒滞在記念アイテム',
price: 200
});
}), 10 * 1000);
setTimeout((function() {
return model.add('products', {
name: '50秒滞在記念アイテム',
price: 1400
});
}), 50 * 1000);
$frame.find('.make_money').click(function() {
return model.set('golds', (model.get('golds')) + 1000);
});
return controller.init();
});
}).call(this);
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>My MVC notions</title>
<style>
.show_case{ border: 1px solid #ccc ; padding: 1em; margin-bottom:1em; }
#simple_case .input_history{ max-height:10em; overflow:scroll; border: 1px solid #666; }
</style>
</head>
<body>
<h1>MVCってなんだろう?</h1>
<section id="simple_case">
<h2>シンプルなケース</h2>
<ul>
<li>オブジェクトはモデルとビューとコントローラと役割分担している</li>
<li>モデルを更新するとビューが追従して、モデルの値を参照しながら自分を更新する</li>
<li>ビューの修正はコントローラに伝わる → コントローラはモデルの操作メソッドを呼ぶ</li>
</ul>
<section id="simple_case" class="show_case">
<h3>実装例 〜 BMI計算機</h3>
<dl>
<dt>モデル</dt>
<dd>体重、身長、入力履歴、BMI計算、適正体重計算、診断コメント</dd>
<dt>コントローラ</dt>
<dd>コントローラ</dd>
<dt>ビュー</dt>
<dd>身長入力、体重入力、入力履歴表示、BMI表示、適正体重表示、診断コメント</dd>
</dl>
<p>
<label>
身長入力:
<input class='height'/>cm
</label>
<label>
体重入力:
<input class="weight"/>kg
</label>
</p>
<p>
あなたのBMIは<span class="bmi">○○</span>です。
適切体重は<span class="best_weight">☓XX</span>kgです。
<span class="bmi_message"></span>
</p>
<p>
<label>
<input class="manipulate_model_randomly" type="checkbox"/> 適当にモデルをいじくりまわす
</label>
<label>
<input class="operate_view_randomly" type="checkbox"/> 適当にビューを操作させてみる
</label>
</p>
<p>
入力履歴:
</p>
<ul class="input_history">
</ul>
</section>
<section id="itemshop" class="show_case">
<h3>実装例 〜 アイテム購入画面</h3>
<dl>
<dt>モデル</dt>
<dd>販売アイテム、所持ゴールド、アイテム購入予定表、アイテム購入予定額計算、道具袋</dd>
<dt>コントローラ</dt>
<dd>コントローラ</dd>
<dt>ビュー</dt>
<dd>あいさつ、所持金、アイテム購入表、購入総額、購入ボタン、道具袋</dd>
</dl>
<p>
店主「<span class="greeting"></span>」
</p>
<table border="1">
<thead>
<tr>
<td>アイテム名</td><td>値段</td><td>購入個数</td>
</tr>
</thead>
<tbody class="products">
</tbody>
</table>
<p>
購入総額:<span class="cart_total">0</span>ゴールド / 所持金:<span class="golds">0</span>ゴールド
<input type='button' class='buy' value="購入する" >
</p>
<p>
道具袋:
</p>
<pre class="item_bag">
</pre>
<p>
<input type='button' class='make_money' value="1000ゴールド手に入 れる" >
</p>
</section>
</section>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="simple_case.js"></script>
<script src="itemshop.js"></script>
<script src="simple_chat.js"></script>
</body>
</html>
global = @
$ ->
# シンプルなケース
calculate_bmi = (h, w) -> Math.floor( 1000000 * w / h / h + 0.5) / 100
calculate_best_weight = (h) -> Math.floor( 22 * h * h / 100 + 0.5 ) / 100
bmi_message = (bmi) ->
if bmi < 18.5
'痩せすぎぃ'
else if bmi < 25
'普通すぎぃ'
else if bmi < 30
'ぽっちゃりすぎぃ'
else if bmi < 35
'デブすぎぃ'
else
'病院行け'
# モデル
model =
height: 160
weight: 50
events: {}
bmi: -> calculate_bmi(@height, @weight)
bestWeight: -> calculate_best_weight(@height)
bmiMessage: -> bmi_message @bmi()
inputHistory: []
addInputHistory: (history) ->
@inputHistory.shift() if(@inputHistory.length > 100)
@inputHistory.push history
@fire 'inputHistory'
get: (property) -> @[property]
set: (property, value) ->
console.log "Property #{property} has been changed: #{@[property]} => #{value}"
@[property] = value
@fire property
fire: (property) ->
for fn in @events[property]
fn()
on: (property, fn) ->
(@events[property] ?= []).push fn
# もっとモデル追加してもいいかも
global.simple_model = model
# ビュー
$frame = $('#simple_case')
$height = $frame.find('.height')
$weight = $frame.find('.weight')
$bmi = $frame.find('.bmi')
$best_weight = $frame.find('.best_weight')
$bmi_message = $frame.find('.bmi_message')
$input_history = $frame.find('.input_history')
# (無限ループ対策 モデル変更 → ビュー変更 → モデル変更 ... の無限ループ回避)
whileEventProcess = false
whileEvent = (fn) ->
try
whileEventProcess = true
fn()
finally
whileEventProcess = false
# コントローラ
controller =
init: ->
modifyBmiView = ->
$bmi.text model.bmi()
$bmi_message.text model.bmiMessage()
$best_weight.text model.bestWeight()
# モデル -> ビュー
model.on 'height', modifyBmiView
model.on 'weight', modifyBmiView
model.on 'height', ->
$height.val(model.get('height')) unless whileEventProcess
model.on 'weight', ->
$weight.val(model.get('weight')) unless whileEventProcess
model.on 'inputHistory', ->
$li = ( $('<li>').text(history) for history in model.get('inputHistory') )
$li.reverse()
$input_history.html('').append $li...
# ビュー -> コントローラ
$height.on 'change', @onChangeHeight
$weight.on 'change', @onChangeWeight
model.set 'height', 180
model.set 'weight', 70
onChangeHeight: ->
whileEvent ->
# コントローラからモデルを操作
height = $height.val()
model.set 'height', + height
model.addInputHistory "身長 #{height} を入力。"
onChangeWeight: ->
whileEvent ->
# コントローラからモデルを操作
weight = $weight.val()
model.set 'weight', + weight
model.addInputHistory "体重 #{weight} を入力。"
controller.init()
# 適当にモデルをいじくりまわす機能(モデルの変更にいろいろ連動することがわかる)
ranged_random = (from, to) -> Math.floor( from + ( Math.random() * (to - from) ))
$manipulate_model_randomly = $frame.find('.manipulate_model_randomly')
$manipulate_model_randomly.change ->
activated = => $(@).is(':checked')
manipulateModel = (prop, gen) ->
if activated()
model.set prop, gen()
setTimeout (-> manipulateModel prop, gen), ranged_random(0, 1000)
manipulateModel 'height', -> ranged_random(100, 200)
manipulateModel 'weight', -> ranged_random(20, 100)
$operate_view_randomly = $frame.find('.operate_view_randomly')
$operate_view_randomly.change ->
activated = => $(@).is(':checked')
operateView = ($view) ->
if activated()
$view.val (1 + ( + $view.val()))
$view.change()
setTimeout (-> operateView $view), ranged_random(0, 100)
operateView $height
operateView $weight
// Generated by CoffeeScript 1.6.3
(function() {
var global;
global = this;
$(function() {
var $best_weight, $bmi, $bmi_message, $frame, $height, $input_history, $manipulate_model_randomly, $operate_view_randomly, $weight, bmi_message, calculate_best_weight, calculate_bmi, controller, model, ranged_random, whileEvent, whileEventProcess;
calculate_bmi = function(h, w) {
return Math.floor(1000000 * w / h / h + 0.5) / 100;
};
calculate_best_weight = function(h) {
return Math.floor(22 * h * h / 100 + 0.5) / 100;
};
bmi_message = function(bmi) {
if (bmi < 18.5) {
return '痩せすぎぃ';
} else if (bmi < 25) {
return '普通すぎぃ';
} else if (bmi < 30) {
return 'ぽっちゃりすぎぃ';
} else if (bmi < 35) {
return 'デブすぎぃ';
} else {
return '病院行け';
}
};
model = {
height: 160,
weight: 50,
events: {},
bmi: function() {
return calculate_bmi(this.height, this.weight);
},
bestWeight: function() {
return calculate_best_weight(this.height);
},
bmiMessage: function() {
return bmi_message(this.bmi());
},
inputHistory: [],
addInputHistory: function(history) {
if (this.inputHistory.length > 100) {
this.inputHistory.shift();
}
this.inputHistory.push(history);
return this.fire('inputHistory');
},
get: function(property) {
return this[property];
},
set: function(property, value) {
console.log("Property " + property + " has been changed: " + this[property] + " => " + value);
this[property] = value;
return this.fire(property);
},
fire: function(property) {
var fn, _i, _len, _ref, _results;
_ref = this.events[property];
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
fn = _ref[_i];
_results.push(fn());
}
return _results;
},
on: function(property, fn) {
var _base;
return ((_base = this.events)[property] != null ? (_base = this.events)[property] : _base[property] = []).push(fn);
}
};
global.simple_model = model;
$frame = $('#simple_case');
$height = $frame.find('.height');
$weight = $frame.find('.weight');
$bmi = $frame.find('.bmi');
$best_weight = $frame.find('.best_weight');
$bmi_message = $frame.find('.bmi_message');
$input_history = $frame.find('.input_history');
whileEventProcess = false;
whileEvent = function(fn) {
try {
whileEventProcess = true;
return fn();
} finally {
whileEventProcess = false;
}
};
controller = {
init: function() {
var modifyBmiView;
modifyBmiView = function() {
$bmi.text(model.bmi());
$bmi_message.text(model.bmiMessage());
return $best_weight.text(model.bestWeight());
};
model.on('height', modifyBmiView);
model.on('weight', modifyBmiView);
model.on('height', function() {
if (!whileEventProcess) {
return $height.val(model.get('height'));
}
});
model.on('weight', function() {
if (!whileEventProcess) {
return $weight.val(model.get('weight'));
}
});
model.on('inputHistory', function() {
var $li, history, _ref;
$li = (function() {
var _i, _len, _ref, _results;
_ref = model.get('inputHistory');
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
history = _ref[_i];
_results.push($('<li>').text(history));
}
return _results;
})();
$li.reverse();
return (_ref = $input_history.html('')).append.apply(_ref, $li);
});
$height.on('change', this.onChangeHeight);
$weight.on('change', this.onChangeWeight);
model.set('height', 180);
return model.set('weight', 70);
},
onChangeHeight: function() {
return whileEvent(function() {
var height;
height = $height.val();
model.set('height', +height);
return model.addInputHistory("身長 " + height + " を入力。");
});
},
onChangeWeight: function() {
return whileEvent(function() {
var weight;
weight = $weight.val();
model.set('weight', +weight);
return model.addInputHistory("体重 " + weight + " を入力。");
});
}
};
controller.init();
ranged_random = function(from, to) {
return Math.floor(from + (Math.random() * (to - from)));
};
$manipulate_model_randomly = $frame.find('.manipulate_model_randomly');
$manipulate_model_randomly.change(function() {
var activated, manipulateModel,
_this = this;
activated = function() {
return $(_this).is(':checked');
};
manipulateModel = function(prop, gen) {
if (activated()) {
model.set(prop, gen());
return setTimeout((function() {
return manipulateModel(prop, gen);
}), ranged_random(0, 1000));
}
};
manipulateModel('height', function() {
return ranged_random(100, 200);
});
return manipulateModel('weight', function() {
return ranged_random(20, 100);
});
});
$operate_view_randomly = $frame.find('.operate_view_randomly');
return $operate_view_randomly.change(function() {
var activated, operateView,
_this = this;
activated = function() {
return $(_this).is(':checked');
};
operateView = function($view) {
if (activated()) {
$view.val(1 + (+$view.val()));
$view.change();
return setTimeout((function() {
return operateView($view);
}), ranged_random(0, 100));
}
};
operateView($height);
return operateView($weight);
});
});
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment