Commit f04cca90 by Tran Hoang Viet

Merge branch 'vietth_feat_stock_management' into 'develop'

VietTH: Feature stock management

See merge request !9
parents 1cca49df ceb74a0c
......@@ -91,3 +91,7 @@ group :development do
end
gem 'rubocop', '~> 0.32.1'
gem 'cartman', '~> 2.1.2'
gem 'font-awesome-rails', '~> 4.3.0.0'
\ No newline at end of file
......@@ -70,6 +70,8 @@ GEM
activesupport (>= 3.2.0)
json (>= 1.7)
mime-types (>= 1.16)
cartman (2.1.2)
redis
celluloid (0.16.0)
timers (~> 4.0.0)
coderay (1.1.0)
......@@ -110,6 +112,8 @@ GEM
i18n (~> 0.5)
figaro (1.1.1)
thor (~> 0.14)
font-awesome-rails (4.3.0.0)
railties (>= 3.2, < 5.0)
font-awesome-sass (4.3.2.1)
sass (~> 3.2)
globalid (0.3.5)
......@@ -324,6 +328,7 @@ DEPENDENCIES
capistrano-sidekiq (~> 0.5.2)
capistrano3-unicorn
carrierwave (~> 0.10.0)
cartman (~> 2.1.2)
coffee-rails (~> 4.1.0)
database_cleaner (~> 1.4.1)
devise (~> 3.5.1)
......@@ -331,6 +336,7 @@ DEPENDENCIES
factory_girl_rails (~> 4.5.0)
faker (~> 1.4.3)
figaro (~> 1.1.1)
font-awesome-rails (~> 4.3.0.0)
font-awesome-sass
haml-rails (~> 0.9)
jbuilder (~> 2.0)
......@@ -359,3 +365,6 @@ DEPENDENCIES
unicorn
vacuum (~> 1.3.0)
web-console (~> 2.0)
BUNDLED WITH
1.10.5
......@@ -11,9 +11,13 @@
// about supported directives.
//
//= require jquery
//= require jquery.spinner.min
//= require jquery_ujs
//= require jquery.validate.min
//= require additional-methods
//= require twitter/bootstrap
//= require jquery_methods
//= require product
//= require order
//= require main
//= require ready
//= require additional-methods
@mainLib = (->
return @
)()
\ No newline at end of file
@orderLib = (->
@options = {}
self = @
@validateCheckoutForm = ->
$('#checkout-cart form').validate()
jQuery.validator.addClassRules 'input-add-quantity', {
required: true
number: true
min: 1
}
$('.input-add-quantity').each ->
$(this).rules 'add', {max: $(this).data('max')}
@setOptions = (options)-> @options = options
@setEventChangeQuantity = (element, max)->
@updateTotalPrice()
$('.customize-spinner').spinner 'changed', (e, newVal, oldVal)->
self.updateQuantity($(e.target).data('id'), newVal)
self.updateTotalPrice()
@updateQuantity = (id, quantity)->
index = null
$.each @options.items, (i, item)->
index = i if parseInt(item.data.id) == id
@options.items[index].data.quantity = quantity
@updateTotalPrice = ->
total = 0
$.each @options.items, (index, item)->
total += parseInt(item.data.quantity) * parseInt(item.data.unit_cost)
$('#total-price').text(total)
return @
)()
\ No newline at end of file
......@@ -11,5 +11,16 @@
required: true
accept: "image/*"
@validateQuantity = ->
$('.form-quantity').each ->
$(this).validate
rules:
quantity:
required: true
number: true
min: 1
max: $(this).find('.max-quantity').val()
errorPlacement: -> {}
return @
)()
\ No newline at end of file
$(document).ready ->
window.productLib.validateForm()
window.productLib.validateQuantity()
window.orderLib.validateCheckoutForm()
......@@ -12,6 +12,8 @@
*
*= require_self
*= require bootstrap_and_overrides
*= require font-awesome
*= require bootstrap-spinner
*= require layout
*= require product
*= require cart
......
#cart-info{
margin-bottom: 30px;
}
#checkout-cart{
table{
td.quantity{
width: 100px;
input{
width: 100%;
}
}
#total-price, #total-price-title{
font-size: 25px;
}
#total-price{
color: blue;
}
}
}
.input-group.spinner{
.input-group-addon{
padding: 7px 12px;
}
i.fa.fa-sort-down {
margin-top: -9px;
vertical-align: middle;
}
}
\ No newline at end of file
......@@ -31,10 +31,17 @@ label.error{
#content{
.products-list-detail{
.product-item{
height: 70px;
.product-image img{
width: 51px;
height: 32px;
margin-bottom: 30px;
.product-image{
position: relative;
&.disable{
opacity: 0.4;
}
img{
height: 180px;
min-width: 185px;
margin-bottom: 5px;
}
}
.product-title{
......@@ -42,18 +49,47 @@ label.error{
}
}
}
#recommend-items{
.product-item{
height: 130px;
.product-image img{
width: 71px;
height: 38px;
.stock{
display: inline-block;
bottom: 0;
padding: 7px 10px;
opacity: 1;
color: white;
border: 1px solid #ccc;
height: 35px;
}
.product-title{
margin: 10px 0;
.stock{
background-color: #000;
float: left;
}
.product-addto-cart{
text-align: right;
width: 90px;
float: right;
.quantity{
width: 40px;
height: 35px;
display: inline-block;
text-align: center;
}
.btn-add{
width: 50px;
height: 35px;
display: inline-block;
border-radius: 0;
border-left: 0;
}
}
.product-price{
font-size: 20px;
color: #007BFF;
width: 100px;
margin: 0 auto 10px;
background-color: #ddd;
}
}
......@@ -73,4 +109,31 @@ footer{
background-color: #ccc;
padding: 15px 0 0 0;
height: 45px;
margin-top: 20px;
}
#cart-info{
.list-cart-item{
.item{
float: left;
width: 100%;
padding: 5px 0;
}
}
.total{
font-weight: bold;
margin: 11px 0;
font-size: 20px;
}
}
.no-border{
border-top: 0 !important;
border-bottom: 0 !important;
border-left: 0 !important;
border-right: 0 !important;
}
.text-left{
text-align: left !important;
}
\ No newline at end of file
module OrdersManagement extend ActiveSupport::Concern
included do
def add_to_cart product
order_item = {
'id' => product.id,
'title' => product.title,
'price' => product.price,
'quantity' => 1
}
unless session['cart_info']['items'].include?(order_item)
session['cart_info']['items'].push(order_item)
session['cart_info']['total'] += 1
end
end
def clear_cart
session['cart_info'] = nil
end
def session_cart
session['cart_info'].with_indifferent_access
end
end
end
\ No newline at end of file
class OrdersController < ApplicationController
include OrdersManagement
before_action :authenticate_user!
def index
@order = Order.new
end
def create
if order_service.create(order_params)
clear_cart
result = cart_service.update_cart(cart_params['items'].values)
if result[:status].present?
result = order_service.create_from_cart(current_user.cart)
if result[:status].present?
redirect_to(root_path, notice: 'Checkout is successful.')
else
flash[:alert] = 'Checkout is failed.'
render :index
end
else
redirect_to(orders_path, alert: "Cart information isn't update. Error(#{result[:errors]})")
end
end
private
def order_params
params.require(:order).permit(:total, order_items_attributes: [:product_id, :quantity])
def cart_params
params.require(:cart).permit(items: [:id, :quantity])
end
def order_service
@order_service ||= OrderService.new(current_user, params)
end
def cart_service
@cart_service ||= CartService.new(current_user, params)
end
end
\ No newline at end of file
class ProductsController < ApplicationController
include OrdersManagement
before_action :set_product, only: [:show, :add_cart]
before_action :set_categories, only: [:new]
before_action :set_categories, only: [:new, :create]
before_action :add_breadcrumb_home
before_action :authenticate_user!, only: [:new, :create]
before_action :authenticate_user!, only: [:new, :create, :add_cart]
def new
@product = Product.new
......@@ -26,8 +24,12 @@ class ProductsController < ApplicationController
end
def add_cart
add_to_cart(@product)
redirect_to(@product, notice: 'Product is added to cart.')
result = cart_service.add_to_cart(@product)
if result[:status].present?
redirect_to :back
else
redirect_to :back, alert: "Quanity must have less than #{@product.stock}"
end
end
def search
......@@ -46,7 +48,11 @@ class ProductsController < ApplicationController
end
def product_params
params.require(:product).permit(:title, :price, :category_id, :image)
params.require(:product).permit(:title, :price, :category_id, :image, :stock)
end
def cart_service
@cart_service ||= CartService.new(current_user, params)
end
end
\ No newline at end of file
......@@ -6,15 +6,15 @@ class ProductDecorator < Draper::Decorator
end
def image_sm_url
object.amazon? ? object.image_sm_url : object.image.small.url
(object.amazon? && object.image_sm_url) ? object.image_sm_url : object.image.small.url
end
def image_md_url
object.amazon? ? object.image_md_url : object.image.medium.url
(object.amazon? && object.image_md_url) ? object.image_md_url : object.image.medium.url
end
def image_lg_url
object.amazon? ? object.image_lg_url : object.image.large.url
(object.amazon? && object.image_lg_url) ? object.image_lg_url : object.image.large.url
end
def short_title
......
module ApplicationHelper
def session_cart
(session[:cart_info] ||= {items: [], total: 0}).with_indifferent_access
def cart_valid?
user_signed_in? && !current_user.cart.count.zero?
end
def format_price(price, delimiter = '.', separator = ',')
number_with_delimiter(price.ceil, delimiter: delimiter, separator: separator)
end
end
class Category < ActiveRecord::Base
default_scope { order(:title) }
JSON_DEFAULT = %i(id title)
JSON_DEFAULT = %i(id title products_count)
has_many :products, dependent: :destroy
......
class OrderItem < ActiveRecord::Base
belongs_to :order
belongs_to :product
after_save :update_product_quantity
def update_product_quantity
self.product.decrement!(:stock, self.quantity)
end
end
......@@ -6,16 +6,17 @@ class Product < ActiveRecord::Base
scope :newest, ->{ self.order(created_at: :desc).limit(Settings.limit_product_newest) }
# relations
belongs_to :category
belongs_to :category, counter_cache: true
belongs_to :user
# validates
validates :title, presence: true, length: {maximum: 255}
validates :category, presence: true
validates :stock, presence: true, numericality: {greater_than: 0}
enum product_type: %i(system amazon)
mount_uploader :image, ImageUploader
solr_options per_page: 5, fields: [:id, :title]
solr_options per_page: Settings.limit_category, fields: [:id, :title]
end
......@@ -8,4 +8,9 @@ class User < ActiveRecord::Base
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def cart
Cartman::Cart.new(self.id)
end
end
class CartService < BaseService
def add_to_cart(product)
quantity = if current_user.cart.contains?(product)
cart_item = current_user.cart.find(product)
cart_item.destroy
params[:quantity].to_i + cart_item.quantity.to_i
else
params[:quantity].to_i
end
if quantity <= product.stock
current_user.cart.add_item(
id: product.id,
name: product.title,
unit_cost: product.price,
cost: (product.price || 0) * quantity,
quantity: quantity,
type: Product,
stock: product.stock
)
{status: true}
else
{status: false}
end
end
def update_cart(items)
products = Product.where(items.map { |item| item['id'] })
products = products.inject({}) do |list, product|
list[product.id.to_s] = product
list
end
errors = []
items.each do |item|
product = products[item['id']]
if item['quantity'].to_i > product.stock
errors.push(product.id)
else
cart_item = current_user.cart.find(product)
cart_item.quantity = item['quantity']
end
end
if errors.present?
{status: false, errors: errors}
else
{status: true}
end
end
end
\ No newline at end of file
class OrderService < BaseService
def create(order_params)
order = current_user.orders.build(order_params)
product_ids = []
quantites = {}
order_params['order_items_attributes'].each do |item|
id = item.last['product_id']
product_ids << id
quantites[id] = item.last['quantity']
def create_from_cart(cart)
order_items_attributes = cart.items.map do |item|
{product_id: item.id, quantity: item.quantity, price: item.unit_cost}
end
products = Product.where(id: [product_ids]).select(:id, :price)
total = products.inject(0) do |sum, product|
sum + (product.price.to_i * quantites[product.id.to_s].to_i)
end
order.total = total
order = current_user.orders.build(
order_items_attributes: order_items_attributes,
total: cart.total
)
if order.save
cart.destroy!
send_checkout_email(order)
{status: true}
else
......
......@@ -57,4 +57,8 @@ class ImageUploader < CarrierWave::Uploader::Base
# "something.jpg" if original_filename
# end
def default_url
ActionController::Base.helpers.asset_path('no-image.jpg')
end
end
%li
= link_to category.decorate.title, category
\ No newline at end of file
= link_to "#{category.decorate.title} (#{category.decorate.products_count})", category
\ No newline at end of file
- products.each do |product|
.product-item.col-md-4.text-center
.product-title
= link_to product.decorate.short_title, product
.product-image.img-thumbnail
= image_tag product.decorate.image_md_url
= render products
\ No newline at end of file
......@@ -3,5 +3,6 @@
.module
#category-items.products-list-detail
= render @products
.clearfix
.pagination
= paginate @products
\ No newline at end of file
.module
%h3.title Recommended Items
.module
#recommend-items
#recommend-items.products-list-detail
= render 'categories/recommended', products: @recommended_items
.clearfix
......
#cart-info
%h3.title Category
= "Total: #{pluralize(session_cart[:total], 'item')}"
= link_to 'Checkout', orders_path, class: 'btn btn-default pull-right'
\ No newline at end of file
- if cart_valid?
#cart-info
%h3.title Cart info
.col-md-9 Title
.col-md-3.text-right Quantity
.list-cart-item
- current_user.cart.items.each do |item|
.item
.col-md-9
= link_to item.name.truncate(25, separator: ' '), product_path(item.id)
.col-md-3.text-right
= item.quantity
.clearfix
.total.col-md-12
Total:
= current_user.cart.total
.checkout.col-md-12
= link_to 'Checkout', orders_path, class: 'btn btn-primary'
......@@ -32,6 +32,7 @@
.col-md-10
= yield
.clearfix
%footer.footer
.container
%p.text-muted Footer
\ No newline at end of file
#checkout-cart
= form_for @order do |f|
%h3.title Checkout
- if cart_valid?
#checkout-cart
= form_for Order.new do |f|
%table.table
%thead
%tr
%td.text-right #
%td Title
%td Price
%td Stock
%td Quantity
%tbody
- session_cart[:items].each_with_index do |order_item, index|
- current_user.cart.items.each_with_index do |order_item, index|
%tr
%td.number.text-right
= index + 1
%td.title
= link_to order_item[:title], product_path(order_item[:id])
= hidden_field_tag "order[order_items_attributes][#{index}][product_id]", order_item[:id]
= link_to order_item.name, product_path(order_item.id)
= hidden_field_tag "cart[items][#{index}][id]", order_item.id
%td.price
= order_item[:price]
= order_item.unit_cost
%td
= order_item.stock
%td.quantity
= text_field_tag "order[order_items_attributes][#{index}][quantity]", order_item[:quantity]
.customize-spinner.input-group.spinner{"data-trigger" => "spinner"}
= text_field_tag "cart[items][#{index}][quantity]", order_item.quantity, data: {max: order_item.stock, min: 1, step: 1, id: order_item.id}, class: 'form-control'
.input-group-addon
%a.spin-up{"data-spin" => "up", href: "javascript:;"}
%i.fa.fa-sort-up
%a.spin-down{"data-spin" => "down", href: "javascript:;"}
%i.fa.fa-sort-down
%tfoot
%tr
%td.text-right{colspan: 4}
%td#total-price-title.text-right{colspan: 2} Total:
%td#total-price{colspan: 3}
= format_price(current_user.cart.total)
%tr
%td.no-border.text-right{colspan: 5}
= f.submit 'Checkout', class: 'btn btn-primary'
:javascript
var items = #{current_user.cart.items.to_json.html_safe}
window.orderLib.setOptions({items: items})
window.orderLib.setEventChangeQuantity()
\ No newline at end of file
.product-addto-cart.input-group
- max_quantity = product.stock - current_user.cart.find(product).try(:quantity).to_i
= form_tag add_cart_product_path(product), method: :get, class: 'form-quantity' do
= text_field_tag :quantity, 1, class: 'form-control quantity'
= hidden_field_tag :stock, max_quantity, class: 'max-quantity'
.input-group-btn
= submit_tag 'Add', class: "btn-add btn btn-default", disabled: product.stock.zero?
.row.product-item
.col-md-1
.product-image.img-thumbnail
%img{src: product.decorate.image_sm_url}
.col-md-9
.product-item.col-md-4.text-center
.product-title
= link_to product.decorate.short_title, product_path(product)
- if local_assigns.has_key?(:show_price) && show_price.present?
.col-md-2
= link_to product.decorate.short_title, product
.product-price
Price:
= product.decorate.price
.product-image.img-thumbnail{class: "#{'disable' if product.stock.zero?}"}
= image_tag product.decorate.image_lg_url, class: 'img-reponsive'
.clearfix
%span.stock
Stock:
= product.stock
= render 'products/add_to_cart_btn', product: product
......@@ -8,7 +8,24 @@
.form-group
= f.label :price, class: 'col-sm-2 control-label'
.col-sm-4
= f.text_field :price, class: 'form-control'
.customize-spinner.input-group.spinner{"data-trigger" => "spinner"}
= f.text_field :price, class: 'form-control text-left', data: {min: 0, step: 1}
.input-group-addon
%a.spin-up{"data-spin" => "up", href: "javascript:;"}
%i.fa.fa-sort-up
%a.spin-down{"data-spin" => "down", href: "javascript:;"}
%i.fa.fa-sort-down
.form-group
= f.label :stock,'Stock*', class: 'col-sm-2 control-label'
.col-sm-4
.customize-spinner.input-group.spinner{"data-trigger" => "spinner"}
= f.text_field :stock, class: 'form-control text-left', data: {min: 1, step: 1}
.input-group-addon
%a.spin-up{"data-spin" => "up", href: "javascript:;"}
%i.fa.fa-sort-up
%a.spin-down{"data-spin" => "down", href: "javascript:;"}
%i.fa.fa-sort-down
.form-group
= f.label :category, class: 'col-sm-2 control-label'
......
#product-detail
.product-title
%h4= @product.title
.product-image
= image_tag @product.decorate.image_lg_url, class: 'img-thumbnail'
#product-detail.products-list-detail
= render 'product', product: @product
.clearfix
.col-md-4.text-center
.product-id
Item ID:
= @product.id
.product-price
Price:
= @product.decorate.price
.product-date
Date:
= l(@product.created_at)
.clearfix
\ No newline at end of file
.product-addto-cart
= link_to 'Buy', add_cart_product_path(@product), class: 'btn btn-primary'
\ No newline at end of file
......@@ -39,7 +39,7 @@ Rails.application.configure do
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
# config.action_mailer.delivery_method = :letter_opener
config.action_mailer.delivery_method = :letter_opener
config.action_mailer.smtp_settings = {
:address => "smtp.mandrillapp.com",
:port => 587,
......@@ -48,7 +48,7 @@ Rails.application.configure do
}
# ActionMailer Config
config.action_mailer.default_url_options = { :host => 'localhost', port: 3000 }
config.action_mailer.delivery_method = :smtp
# config.action_mailer.delivery_method = :smtp
config.action_mailer.perform_deliveries = true
config.action_mailer.raise_delivery_errors = false
end
Kaminari.configure do |config|
config.default_per_page = 5
config.default_per_page = 6
# config.max_per_page = nil
# config.window = 4
# config.outer_window = 0
......
defaults: &defaults
limit_category: 5
limit_category: 6
limit_product_recommended: 6
limit_product_newest: 4
limit_product_newest: 9
limit_length_category_title: 50
development:
......
class AddStockToProducts < ActiveRecord::Migration
def change
add_column :products, :stock, :integer, default: 0
end
end
class AddProductsCountToCategories < ActiveRecord::Migration
def change
add_column :categories, :products_count, :integer, default: 0
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150708092744) do
ActiveRecord::Schema.define(version: 20150713093724) do
create_table "categories", force: :cascade do |t|
t.datetime "created_at", null: false
......@@ -19,6 +19,7 @@ ActiveRecord::Schema.define(version: 20150708092744) do
t.string "node_id", limit: 255
t.string "title", limit: 255
t.integer "category_type", limit: 4, default: 0
t.integer "products_count", limit: 4, default: 0
end
create_table "order_items", force: :cascade do |t|
......@@ -55,6 +56,7 @@ ActiveRecord::Schema.define(version: 20150708092744) do
t.integer "product_type", limit: 4, default: 0
t.string "image", limit: 255
t.integer "user_id", limit: 4
t.integer "stock", limit: 4, default: 0
end
add_index "products", ["category_id"], name: "index_products_on_category_id", using: :btree
......
......@@ -44,6 +44,7 @@ namespace :aws do
product.image_md_url = item['MediumImage'].try(:[], 'URL')
product.image_sm_url = item['SmallImage'].try(:[], 'URL')
product.price = item['ItemAttributes']['ListPrice'].try(:[], 'Amount').try(:to_i)
product.stock = 5 #item['ItemAttributes']['BinItemCount'].to_i
product.product_type = Product.product_types[:amazon]
product.user = User.first
end
......
/*! jQuery spinner - v0.1.6 - 2015-03-09
* https://github.com/xixilive/jquery-spinner
* Copyright (c) 2015 xixilive; Licensed MIT */
!function(a){"use strict";var b,c=function(b,d){return d=a.extend({},d),this.$el=b,this.options=a.extend({},c.rules.defaults,c.rules[d.rule]||{},d),this.min=parseFloat(this.options.min)||0,this.max=parseFloat(this.options.max)||0,this.$el.on("focus.spinner",a.proxy(function(b){b.preventDefault(),a(document).trigger("mouseup.spinner"),this.oldValue=this.value()},this)).on("change.spinner",a.proxy(function(a){a.preventDefault(),this.value(this.$el.val())},this)).on("keydown.spinner",a.proxy(function(a){var b={38:"up",40:"down"}[a.which];b&&(a.preventDefault(),this.spin(b))},this)),this.oldValue=this.value(),this.value(this.$el.val()),this};c.rules={defaults:{min:null,max:null,step:1,precision:0},currency:{min:0,max:null,step:.01,precision:2},quantity:{min:1,max:999,step:1,precision:0},percent:{min:1,max:100,step:1,precision:0},month:{min:1,max:12,step:1,precision:0},day:{min:1,max:31,step:1,precision:0},hour:{min:0,max:23,step:1,precision:0},minute:{min:1,max:59,step:1,precision:0},second:{min:1,max:59,step:1,precision:0}},c.prototype={spin:function(b){if("disabled"!==this.$el.attr("disabled")){this.oldValue=this.value();var c=a.isFunction(this.options.step)?this.options.step.call(this,b):this.options.step;switch(b){case"up":this.value(this.oldValue+Number(c,10));break;case"down":this.value(this.oldValue-Number(c,10))}}},value:function(c){if(null===c||void 0===c)return this.numeric(this.$el.val());c=this.numeric(c);var e=this.validate(c);0!==e&&(c=-1===e?this.min:this.max),this.$el.val(c.toFixed(this.options.precision)),this.oldValue!==this.value()&&(this.$el.trigger("changing.spinner",[this.value(),this.oldValue]),clearTimeout(b),b=setTimeout(a.proxy(function(){this.$el.trigger("changed.spinner",[this.value(),this.oldValue])},this),d.delay))},numeric:function(a){return a=this.options.precision>0?parseFloat(a,10):parseInt(a,10),!isNaN(parseFloat(a))&&isFinite(a)?a:a||this.options.min||0},validate:function(a){return null!==this.options.min&&a<this.min?-1:null!==this.options.max&&a>this.max?1:0}};var d=function(b,d){d=a.extend({},d),this.$el=b,this.$spinning=a("[data-spin='spinner']",this.$el),0===this.$spinning.length&&(this.$spinning=a(":input[type='text']",this.$el)),this.spinning=new c(this.$spinning,a.extend(this.$spinning.data(),d)),this.$el.on("click.spinner","[data-spin='up'],[data-spin='down']",a.proxy(this.spin,this)).on("mousedown.spinner","[data-spin='up'],[data-spin='down']",a.proxy(this.spin,this)),a(document).on("mouseup.spinner",a.proxy(function(){clearTimeout(this.spinTimeout),clearInterval(this.spinInterval)},this)),d.delay&&this.delay(d.delay),d.changed&&this.changed(d.changed),d.changing&&this.changing(d.changing)};d.delay=500,d.prototype={constructor:d,spin:function(b){var c=a(b.currentTarget).data("spin");switch(b.type){case"click":b.preventDefault(),this.spinning.spin(c);break;case"mousedown":1===b.which&&(this.spinTimeout=setTimeout(a.proxy(this.beginSpin,this,c),300))}},delay:function(a){var b=parseInt(a,10);b>=0&&(this.constructor.delay=b+100)},value:function(){return this.spinning.value()},changed:function(a){this.bindHandler("changed.spinner",a)},changing:function(a){this.bindHandler("changing.spinner",a)},bindHandler:function(b,c){a.isFunction(c)?this.$spinning.on(b,c):this.$spinning.off(b)},beginSpin:function(b){this.spinInterval=setInterval(a.proxy(this.spinning.spin,this.spinning,b),100)}},a.fn.spinner=function(b,c){return this.each(function(){var e=a(this),f=e.data("spinner");f||e.data("spinner",f=new d(e,a.extend({},e.data(),b))),("delay"===b||"changed"===b||"changing"===b)&&f[b](c),"step"===b&&c&&(f.spinning.step=c),"spin"===b&&c&&f.spinning.spin(c)})},a(function(){a('[data-trigger="spinner"]').spinner()})}(jQuery);
\ No newline at end of file
.spinner.input-group .input-group-addon a.spin-up,.spinner.input-group .input-group-addon a.spin-down{height:10px;width:10px;font-size:14px;overflow:hidden;display:block;text-align:center;text-decoration:none;position:relative;color:#999}.spinner.input-group .input-group-addon a.spin-up .icon-sort-down,.spinner.input-group .input-group-addon a.spin-down .icon-sort-down{position:relative;top:-6px}.spinner.input-group .input-group-addon a.spin-up:hover,.spinner.input-group .input-group-addon a.spin-down:hover{color:#555}.spinner.input-group input{text-align:center}.spinner.input-group.input-group-lg .input-group-addon a.spin-up,.spinner.input-group.input-group-lg .input-group-addon a.spin-down{font-size:16px;height:12px}.spinner.input-group.input-group-sm .input-group-addon a.spin-up,.spinner.input-group.input-group-sm .input-group-addon a.spin-down{font-size:12px;height:9px}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment