Merge branch 'develop'

paypal, pikpay and gift support
This commit is contained in:
Senad Uka
2016-02-07 19:54:08 +01:00
416 changed files with 1191 additions and 135926 deletions

View File

@@ -1,6 +1,5 @@
source 'https://rubygems.org'
gem 'rails_admin'
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '4.2.0'
@@ -25,8 +24,11 @@ gem 'jbuilder', '~> 2.0'
# bundle exec rake doc:rails generates the API under doc/api.
gem 'sdoc', '~> 0.4.0', group: :doc
gem 'tzinfo-data'
gem "nokogiri", ">= 1.6.6"
# great server
gem 'puma'
gem 'puma', '~> 2.15.3'
# for uploading images
gem 'cloudinary'

View File

@@ -79,6 +79,7 @@ GEM
execjs (2.2.2)
faraday (0.9.1)
multipart-post (>= 1.2, < 3)
ffi (1.9.10-x64-mingw32)
font-awesome-rails (4.3.0.0)
railties (>= 3.2, < 5.0)
globalid (0.3.0)
@@ -111,11 +112,13 @@ GEM
multipart-post (2.0.0)
nested_form (0.3.2)
netrc (0.10.2)
nokogiri (1.6.5)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
nokogiri (1.6.6.2-x64-mingw32)
mini_portile (~> 0.6.0)
pg (0.18.1)
puma (2.10.2)
rack (>= 1.1, < 2.0)
pg (0.18.1-x64-mingw32)
puma (2.15.3)
rack (1.6.0)
rack-pjax (0.8.0)
nokogiri (~> 1.5)
@@ -167,6 +170,10 @@ GEM
rest-client (1.7.2)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rest-client (1.7.2-x64-mingw32)
ffi (~> 1.9)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
safe_yaml (1.0.4)
sass (3.4.9)
sass-rails (5.0.1)
@@ -199,6 +206,8 @@ GEM
coffee-rails
tzinfo (1.2.2)
thread_safe (~> 0.1)
tzinfo-data (1.2015.7)
tzinfo (>= 1.0.0)
uglifier (2.7.0)
execjs (>= 0.3.0)
json (>= 1.8.0)
@@ -210,6 +219,7 @@ GEM
PLATFORMS
ruby
x64-mingw32
DEPENDENCIES
active_scaffold!
@@ -220,8 +230,9 @@ DEPENDENCIES
jbuilder (~> 2.0)
jquery-rails
jquery-ui-rails
nokogiri (>= 1.6.6)
pg
puma
puma (~> 2.15.3)
rails (= 4.2.0)
rails_admin
sass-rails (~> 5.0)
@@ -229,8 +240,9 @@ DEPENDENCIES
spring
tabulous
turbolinks
tzinfo-data
uglifier (>= 1.3.0)
web-console (~> 2.0)
BUNDLED WITH
1.10.3
1.10.6

View File

@@ -0,0 +1,5 @@
class CheckItemsConstraint
def self.matches?(request)
request.params[:commit] == 'Check'
end
end

View File

@@ -0,0 +1,5 @@
class DeleteItemsConstraint
def self.matches?(request)
request.params[:commit] == 'Delete Items'
end
end

View File

@@ -15,4 +15,80 @@ class ItemsController < ApplicationController
@missing_from_database = (@codes_to_check_array - items_to_check) || []
@missing_from_codes = (items_to_check - @codes_to_check_array) || []
end
def delete_items
@suppliers = Supplier.order(:name).all
@selected_supplier = Supplier.find_by_id(params[:supplier_id])
@items = @selected_supplier.try(:items) || []
@codes_to_check = params[:codes] || ""
@codes_to_check_array = @codes_to_check.split("\n").reject { |code| code.strip.blank? }.map(&:strip)
items_to_check = @items.map { |i| i.code.strip }
@items_for_delete = items_to_check & @codes_to_check_array
Item.where(:code => @items_for_delete).destroy_all
@not_deleted_items = (@codes_to_check_array - @items_for_delete) || []
render :template => "items/check_availability"
end
def export_import
@tasks = [ ["Validate items", "validate_items"],
["Import items", "import_items"],
["Update prices", "update_prices"] ]
@task = "validate_items"
end
def export_import_post
@tasks = [ ["Validate items", "validate_items"],
["Import items", "import_items"],
["Update prices", "update_prices"] ]
@task = params[:task]
@csv_content = params[:csv_content]
@error_message = ""
@output = []
if params[:csv_content] == ""
@error_message = "Format of CSV is wrong (CSV content is empty)"
else
begin
csv_parsed = CSV.parse(params[:csv_content])
rescue CSV::MalformedCSVError => er
@error_message = "Format of CSV is wrong (#{er.message})"
end
unless csv_parsed.nil?
csv_file = ItemsHelper::create_csv(csv_parsed)
begin
ENV["INPUT"] = csv_file.path
case @task
when 'validate_items'
@output = ItemsHelper::execute_command("rake ribica:validate_items -f #{Rails.root}/Rakefile")
@output.collect{|x| x.sub! "\n", "" }
when 'import_items'
@output = ItemsHelper::execute_command("rake ribica:import_items -f #{Rails.root}/Rakefile")
@output.collect{|x| x.sub! "\n", "" }
%x(rake ribica:reindex -f #{Rails.root}/Rakefile) unless RakeTasksHelper::is_error_occurred @output
when 'update_prices'
@output = ItemsHelper::execute_command("rake ribica:update_prices -f #{Rails.root}/Rakefile")
%x(rake ribica:reindex -f #{Rails.root}/Rakefile) unless RakeTasksHelper::is_error_occurred @output
else
@error_message = "There is no such task"
end
ensure
csv_file.unlink
end
end
@output = @output.join("<br/>")
end
render :template => "items/export_import"
end
end

View File

@@ -1,5 +1,27 @@
module ItemsHelper
def self.create_csv(data)
file = Tempfile.new([Rails.root.join('tmp/').to_s, ".csv"], "")
csv = CSV.new(file)
data.each do |row|
csv << row
end
file.rewind
file.close
file
end
def self.execute_command(command)
buffer = []
Open3.popen3(command) do |stdin, stdout, stderr|
begin
while line = stdout.readline
buffer << line
end
rescue
end
end
end
buffer
end
end

View File

@@ -0,0 +1,9 @@
module RakeTasksHelper
def self.task_error_message
"Rake task execution error"
end
def self.is_error_occurred(buffer)
buffer.include? self.task_error_message
end
end

View File

@@ -5,7 +5,9 @@ class Cart < ActiveRecord::Base
belongs_to :delivery_destination
def delivery_cost
place = Place.by_code_or_default(delivery_destination.place)
place = delivery_destination.gift ? Place.by_code_or_default(delivery_destination.recipient_place)
: Place.by_code_or_default(delivery_destination.place)
if delivery_destination.instant_delivery
place.instant_delivery_price
else
@@ -21,5 +23,4 @@ class Cart < ActiveRecord::Base
def confirmed_at
delivery_destination.updated_at.in_time_zone('Europe/Sarajevo')
end
end

View File

@@ -1,2 +1,14 @@
class DeliveryDestination < ActiveRecord::Base
class DeliveryDestination < ActiveRecord::Base
def get_payment_string
case self.payment_method
when 'cash_on_delivery'
'Prilikom preuzimanja'
when 'paypal'
'Paypal'
when 'pikpay'
'Pikpay'
else
'Nepoznato'
end
end
end

View File

@@ -124,12 +124,13 @@ class Item < ActiveRecord::Base
end
end
puts "Nonexistent codes: "
puts nonexistent_codes.join("\n")
puts "#{nonexistent_codes.join("\n")}"
end
def we_must_earn_money
if list_price.to_f <= current_input_price.to_f
errors[:list_price] << "#{code} Ulazna cijena veca od izlazne"
puts "#{code} Ulazna cijena veca od izlazne"
end
end

View File

@@ -3,14 +3,14 @@
.tg td{ }
.tg th{ }
</style>
<%
<%
dd = @record.delivery_destination
c = @record
%>
<p>
<p><strong>Ime:</strong> <%= dd.name %><br /><br /></p>
<p><strong>Adresa:</strong><br />
<p><strong>Adresa:</strong><br />
<%= dd.address %><br />
<%= dd.place.to_s.strip %> <%= Place.name_from_code(dd.place.to_s) %><br />
Bosna i Hercegovina<br /><br />
@@ -22,15 +22,27 @@ Bosna i Hercegovina<br /><br />
<p><strong>Telefon: </strong> +387 <%= dd.phone %><br /><br />
</p>
<p><strong>Plaćanje: </strong><%= dd.get_payment_string %></p><br />
<p><strong>Napomena: </strong><br />
<%= dd.note %><br /><br />
</p>
</p>
<% if dd.gift %>
<p><strong>Ovo je poklon</strong><br /><br />
<p><strong>Ime primaoca: </strong><%= dd.recipient_name %><br />
<p><strong>Adresa primaoca: </strong><%= dd.recipient_address %><br />
<p><strong>Postanski broj primaoca: </strong><%= dd.recipient_postal_code %><br />
<p><strong>Grad primaoca: </strong><%= dd.recipient_place %><br />
<p><strong>Email primaoca: </strong><%= dd.recipient_email %><br />
<p><strong>Telefon primaoca: </strong><%= dd.recipient_phone %><br /><br />
<% end %>
<p>
<strong>Naručeno:</strong> <%= c.confirmed_at.in_time_zone("Europe/Sarajevo").strftime("%A %d.%m.%Y. %H:%M") %>
<br />
<br />
<br />
<br />
</p>
<% if dd.instant_delivery %>
<p style="font-size: 120%;">
@@ -47,7 +59,7 @@ OVO JE NARUDŽBA ZA INSTANT DOSTAVU
<th style="text-align: right; ">Total</th>
</tr>
<% @record.item_in_carts.each do |iic| %>
<tr>
<tr>
<td class="tg-031e"><%= iic.item.code %></td>
<td class="tg-031e"><%= iic.item.name %></td>
<td style="text-align: right; "><%= iic.count %></td>
@@ -70,15 +82,6 @@ OVO JE NARUDŽBA ZA INSTANT DOSTAVU
<td style="text-align: right; "><%= money(c.total) %></td>
</tr>
<tr><td><strong> </strong></td></tr>
</table>
<br /><br />

View File

@@ -2,33 +2,48 @@
<p><label for="supplier_id">Supplier: </label>
<%= select_tag "supplier_id", options_from_collection_for_select(@suppliers, "id", "name", @selected_supplier.try(:id)) %></p>
<p><label for="codes">
<p><label for="codes">
Codes to check:
</label><br />
<%= text_area_tag "codes", @codes_to_check, rows: 20, columns: 50 %></p>
<p><%= submit_tag "Check" %></p>
<p><%= submit_tag "Delete Items" %></p>
<% end %>
<div>
<h2> In database: <%= @items.length %>, in file: <%= @codes_to_check_array.length %> </h2>
</div>
<% if controller.action_name == 'delete_items' %>
<h2> Delete successful </h2>
<% if @not_deleted_items.length > 0 %>
<p>Not deleted items</p>
<% @not_deleted_items.each do |code| %>
<%= code %><br />
<% end %>
<% end %>
<% else %>
<div>
<h2> In database: <%= @items.length %>, in file: <%= @codes_to_check_array.length %> </h2>
</div>
<div>
<h2>Not in database (<%= @missing_from_database.length %>): </h2>
<br />
<% @missing_from_database.each do |code| %>
<%= code %><br />
<% end %>
</div>
<div>
<h2>Not in file (<%= @missing_from_codes.length %>): </h2>
<br />
<% @missing_from_codes.each do |code| %>
<%= code %><br />
<% end %>
</div>
<div>
<h2>Not in database (<%= @missing_from_database.length %>): </h2>
<br />
<% @missing_from_database.each do |code| %>
<%= code %><br />
<% end %>
</div>
<div>
<h2>Not in file (<%= @missing_from_codes.length %>): </h2>
<br />
<% @missing_from_codes.each do |code| %>
<%= code %><br />
<% end %>
</div>

View File

@@ -0,0 +1,21 @@
<%= form_tag('./export_import') do %>
<p><label for="codes">
CSV content goes here:
</label><br />
<textarea id="csv_content" name="csv_content" rows=20 style="width: 100%"><%= @csv_content %></textarea></p>
<p><label for="task">Task: </label>
<%= select_tag "task", options_for_select(@tasks, @task) %></p>
<p><%= submit_tag "Run task" %></p>
<% if !@error_message.nil? && @error_message != "" %>
<div>Error message: <span style="color: red"><%= @error_message %></span></div>
<% end %>
<br />
<p><label for="output_area">Output area: </label>
<div id="output_area" style="width: 100%;height: 14em;outline: 1px solid #A9A9A9; overflow: scroll"><%= unless @output.nil?
@output.html_safe end %>
</div>
<% end %>

View File

@@ -1,6 +1,8 @@
require File.expand_path('../boot', __FILE__)
require 'rails/all'
require 'rake'
require 'open3'
# Require the gems listed in Gemfile, including any gems
# you've limited to :test, :development, or :production.
@@ -25,5 +27,3 @@ module Ribicabackoffice
config.assets.precompile += %w(*.svg *.eot *.woff *.ttf *.gif *.png *.ico)
end
end

View File

@@ -35,6 +35,7 @@ RailsAdmin.config do |config|
config.navigation_static_links = {
'Orders to Confirm' => './reports/orders_to_confirm',
'Items to Order' => './backreports/items_to_order',
'Check availability' => './items/check_availability'
'Check availability' => './items/check_availability',
'Export/import' => './items/export_import'
}
end

View File

@@ -15,12 +15,15 @@ Rails.application.routes.draw do
resources :multi_media_descriptions do as_routes end
resources :sections do as_routes end
resources :media_types do as_routes end
resources :items do
collection do
resources :items do
collection do
get 'check_availability'
post 'check_availability'
post 'check_availability' => 'items#check_availability', constraints: CheckItemsConstraint
post 'check_availability' => 'items#delete_items', constraints: DeleteItemsConstraint
get 'export_import'
post 'export_import' => 'items#export_import_post'
end
as_routes
as_routes
end
resources :units do as_routes end
resources :sub_categories do as_routes end

View File

@@ -223,6 +223,7 @@ def import_single_item(row, index, logger)
success = true
rescue Exception => e
logger.error "Could not import item on row number #{index} (#{row[lookup[:name_on_ribica]]}), reason: #{e}"
puts "Could not import item on row number #{index} (#{row[lookup[:name_on_ribica]]}), reason: #{e}"
success = false
end
@@ -242,14 +243,15 @@ def do_import(validate_only)
input_file = ENV['INPUT']
if input_file.to_s == ""
puts "Input file is missing! Please provide input file in form INPUT=somefile.csv"
puts RakeTasksHelper.task_error_message
return
end
lookup = get_column_lookup
path = Rails.root.join(input_file)
log_filename = "import.log"
log_filename = "import_validate.log" if validate_only
log_filename = Rails.root.join("import.log")
log_filename = Rails.root.join("import_validate.log") if validate_only
logger = Logger.new(log_filename)
@@ -274,6 +276,7 @@ def do_import(validate_only)
if should_rollback
puts "Import failed, please check the import log file for error details."
logger.info "Rolling back because of errors"
puts RakeTasksHelper.task_error_message
end
raise ActiveRecord::Rollback
@@ -281,8 +284,11 @@ def do_import(validate_only)
end
rescue Exception => e
puts "Import failed, please check the import log file for error details."
puts "Error while importing: #{e}"
logger.error "Error while importing: #{e}"
puts RakeTasksHelper.task_error_message
end
puts "Import done"
logger.info "Import done"
end
@@ -359,7 +365,7 @@ namespace :ribica do
begin
es_client.indices.delete index: 'ribica'
rescue
logger.warn "Ribica index could not be deleted. Continuing with indexing operation..."
Rails.logger.warn "Ribica index could not be deleted. Continuing with indexing operation..."
end
# now index items

View File

@@ -24,6 +24,7 @@ GEM
backports (3.6.6)
bcrypt (3.1.10)
bcrypt (3.1.10-java)
bcrypt (3.1.10-x64-mingw32)
builder (3.2.2)
celluloid (0.16.0)
timers (~> 4.0.0)
@@ -39,12 +40,14 @@ GEM
multipart-post (>= 1.2, < 3)
ffi (1.9.10)
ffi (1.9.10-java)
ffi (1.9.10-x64-mingw32)
hitimes (1.2.3)
hitimes (1.2.3-java)
i18n (0.7.0)
jdbc-postgres (9.4.1200)
jruby-openssl (0.9.11-java)
json (1.8.3)
json (1.8.3-java)
listen (2.10.1)
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
@@ -71,6 +74,10 @@ GEM
rest-client (1.7.3)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rest-client (1.7.3-x64-mingw32)
ffi (~> 1.9)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
ruby-trello (1.2.1)
activemodel (>= 3.2.0)
addressable (~> 2.3)
@@ -96,6 +103,7 @@ GEM
tilt (>= 1.3, < 3)
smtpapi (0.1.0)
thread_safe (0.3.5)
thread_safe (0.3.5-java)
tilt (2.0.1)
timers (4.0.4)
hitimes
@@ -106,6 +114,7 @@ GEM
PLATFORMS
java
ruby
x64-mingw32
DEPENDENCIES
activerecord
@@ -125,4 +134,4 @@ DEPENDENCIES
xxhash (~> 0.3.0)
BUNDLED WITH
1.10.3
1.10.6

View File

@@ -14,11 +14,11 @@ require 'sendgrid-ruby'
Trello.configure do |config|
# API key generated by visiting https://trello.com/1/appKey/generate
config.developer_public_key = "f13dd6c2dcc65f48b9a56c6d420e32e7"
config.developer_public_key = RibicaConfig::TRELLO_DEVELOPER_PUBLIC_KEY
# Member token
# larry-price.com/blog/2014/03/18/connecting-to-the-trello-api/
config.member_token = "e8e1885d794dbc4d9d8d1ae586a84e580be224b2737254de6064d7d7219f3064"
config.member_token = RibicaConfig::TRELLO_MEMBER_TOKEN
end
Dir[File.dirname(__FILE__) + '/models/*.rb'].each {|file| require file }
@@ -37,9 +37,11 @@ before do
'Access-Control-Expose-Headers' => 'X-Total-Count',
'Access-Control-Allow-Credentials' => 'true'
request.body.rewind
json_string = request.body.read
@json_params = JSON.parse json_string if json_string.length > 1
unless Helper::do_not_parse_as_json.include? env['PATH_INFO']
request.body.rewind
json_string = request.body.read
@json_params = JSON.parse json_string if json_string.length > 1
end
if request.request_method == 'OPTIONS'
halt 200

View File

@@ -10,3 +10,23 @@ ActiveRecord::Base.establish_connection(
:database => db.path[1..-1],
:encoding => 'utf8'
)
module RibicaConfig
ROOT_ADDRESS = "https://ribica.ba"
SENDGRID_API_USER = "ribica"
SENDGRID_API_KEY = "(enter key here)"
BACKOFFICE_ORDER_EMAIL_TO = "narudzbe@ribica.ba"
BACKOFFICE_ORDER_EMAIL_FROM = "draga@ribica.ba"
BACKOFFICE_ORDER_EMAIL_FROM_NAME = "Prodavnica Ribica"
# API key generated by visiting https://trello.com/1/appKey/generate
TRELLO_DEVELOPER_PUBLIC_KEY = "f13dd6c2dcc65f48b9a56c6d420e32e7"
# Member token
# larry-price.com/blog/2014/03/18/connecting-to-the-trello-api/
TRELLO_MEMBER_TOKEN = "(enter token here)"
TRELLO_BOARD_NAME = "FqDO1eFL"
BAM_TO_EURO_CONVERSION_RATE = 0.51
end

View File

@@ -44,7 +44,9 @@ end
update_delivery_destination = ->() {
cart = Cart.just_find(anonymous_id, logged_in_user_id)
allowed_keys = ["name", "address", "place", "postal_code", "phone", "email", "note"]
allowed_keys = ["name", "address", "place", "postal_code", "phone", "email", "note", "payment_method", "gift",
"recipient_name", "recipient_address", "recipient_place", "recipient_postal_code", "recipient_phone", "recipient_email"]
params = @json_params.reject { |key,_| !allowed_keys.include?(key) }
cart.delivery_destination.update_attributes(params)
cart.delivery_destination.save!
@@ -53,11 +55,10 @@ update_delivery_destination = ->() {
put '/cart/delivery_destination', &update_delivery_destination
post '/cart/delivery_destination', &update_delivery_destination
def report_to_trello(cart)
Thread.new do
Thread.new do
@cart = cart
board = Trello::Board.find('FqDO1eFL')
board = Trello::Board.find(RibicaConfig.TRELLO_BOARD_NAME)
list = board.lists.first
card = Trello::Card.new
card.list_id = list.id
@@ -69,18 +70,18 @@ def report_to_trello(cart)
end
def send_order_email(cart)
Thread.new do
Thread.new do
client = SendGrid::Client.new(
api_user: "ribica",
api_key: "plava*kutija*svjetlo*torba07"
api_user: RibicaConfig.SENDGRID_API_USER,
api_key: RibicaConfig.SENDGRID_API_KEY
)
email = SendGrid::Mail.new do |m|
m.to = "narudzbe@ribica.ba"
m.from = "draga@ribica.ba"
m.to = RibicaConfig.BACKOFFICE_ORDER_EMAIL_TO
m.from = RibicaConfig.BACKOFFICE_ORDER_EMAIL_FROM
m.from_name = "Prodavnica Ribica"
m.subject = "Nova Narudžba: #{cart.id}"
m.html = "Mušterija naručila nešto. <br /> Pogledati https://www.ribica.ba/backoffice/carts/#{cart.id}"
m.html = "Mušterija naručila nešto. <br /> Pogledati #{RibicaConfig.ROOT_ADDRESS}/backoffice/carts/#{cart.id}"
end
client.send(email)
end
@@ -101,3 +102,44 @@ post '/cart/confirmation' do
send_order_email(cart)
"OK".to_json
end
post '/payment/confirmation' do
data = JSON.parse params[:custom]
puts "Data #{data.inspect}"
anonymous = data["anonymous_id_string"]
user = data["user_id"]
user ||= -1
user = user.to_i
cart = Cart.just_find(anonymous, user)
if cart.item_in_carts.length > 0
cart.ordered = true
cart.save!
end
Cart.find_or_create(anonymous, user)
report_to_trello(cart)
send_order_email(cart)
"OK".to_json
end
get '/pikpay/confirmation' do
anonymous = params["anonymous_id_string"]
user = params["user_id"]
user ||= -1
user = user.to_i
cart = Cart.just_find(anonymous, user)
if cart.item_in_carts.length > 0
cart.ordered = true
cart.save!
end
Cart.find_or_create(anonymous, user)
report_to_trello(cart)
send_order_email(cart)
redirect "#{RibicaConfig::ROOT_ADDRESS}/hvala"
end

View File

@@ -1,35 +1,45 @@
get '/search' do
es_client = Elasticsearch::Client.new log: true
q = params[:q]
# for now we do the basic query
results = es_client.search index: 'ribica', type: 'items', body: { query: { match: { _all: q } } }
ids = results["hits"]["hits"].map do |r|
r["_id"]
results = { }
begin
es_client = Elasticsearch::Client.new log: true
q = params[:q]
# for now we do the basic query
results = es_client.search index: 'ribica', type: 'items', body: { query: { match: { _all: q } } }
rescue Exception => error
puts error.inspect
results = { "hits" => {"hits" => [ {"_id" => Item.first.id, "_score" => 2 }, {"_id" => Item.last.id, "_score" => 1 } ]}}
end
ids = results["hits"]["hits"].map do |r|
r["_id"]
end
ids_with_score = {}
results["hits"]["hits"].each do |r|
ids_with_score[r["_id"].to_i] = {:score => r["_score"], :item => nil}
ids_with_score[r["_id"].to_i] = {:score => r["_score"], :item => nil}
end
if ids.length > 0
res = Item.where(:id => ids).to_a
# make sure we have correct relevance order, since `where in` does not guarantee order
res.each do |ii|
ids_with_score[ii.id][:item] = ii
end
final = []
ids_with_score.each do |k,v|
final << v
end
final.sort_by! {|v| -v[:score]}
res = Item.where(:id => ids).to_a
# make sure we have correct relevance order, since `where in` does not guarantee order
res.each do |ii|
ids_with_score[ii.id][:item] = ii
end
final = []
ids_with_score.each do |k,v|
final << v
end
final.sort_by! {|v| -v[:score]}
final = final.map do |f|
f[:item]
end
prepare_items_for_mass_display(final)
final = final.map do |f|
f[:item]
end
prepare_items_for_mass_display(final)
else
[].to_json
[].to_json
end
end

View File

@@ -0,0 +1,5 @@
class AddPaymentMethodToDeliveryDestination < ActiveRecord::Migration
def change
add_column :delivery_destinations, :payment_method, :string
end
end

View File

@@ -0,0 +1,5 @@
class AddGiftToDeliveryDestination < ActiveRecord::Migration
def change
add_column :delivery_destinations, :gift, :boolean, default: false
end
end

View File

@@ -0,0 +1,10 @@
class AddRecipientDestinationToDeliveryDestination < ActiveRecord::Migration
def change
add_column :delivery_destinations, :recipient_name, :string
add_column :delivery_destinations, :recipient_address, :string
add_column :delivery_destinations, :recipient_place, :string
add_column :delivery_destinations, :recipient_postal_code, :string
add_column :delivery_destinations, :recipient_phone, :string
add_column :delivery_destinations, :recipient_email, :string
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151124061357) do
ActiveRecord::Schema.define(version: 20160122145944) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -67,6 +67,14 @@ ActiveRecord::Schema.define(version: 20151124061357) do
t.integer "user_id"
t.string "anonymous_id_string"
t.boolean "instant_delivery", default: false
t.string "payment_method"
t.boolean "gift", default: false
t.string "recipient_name"
t.string "recipient_address"
t.string "recipient_place"
t.string "recipient_postal_code"
t.string "recipient_phone"
t.string "recipient_email"
end
create_table "delivery_time_estimations", force: :cascade do |t|

View File

@@ -2,4 +2,16 @@ class Helper
def self.money(amount)
sprintf('%.2f KM', amount.to_f)
end
end
def self.get_bam_to_euro_conversion_rate
RibicaConfig::BAM_TO_EURO_CONVERSION_RATE
end
def self.bam_to_euro(amount)
self.get_bam_to_euro_conversion_rate * amount
end
def self.do_not_parse_as_json
['/payment/confirmation']
end
end

View File

@@ -1,5 +1,5 @@
class Cart < ActiveRecord::Base
has_many :item_in_carts, -> { order "created_at" }
has_many :item_in_carts, -> { order "created_at" }
belongs_to :delivery_destination
def self.find_or_create(anonymous_id, user_id)
@@ -74,7 +74,7 @@ class Cart < ActiveRecord::Base
def title
number = id
name = delivery_destination.name
name = delivery_destination.name
value = Helper.money(total)
phone = "0#{delivery_destination.phone}"
"BR: #{number} za #{name} (#{phone}) - #{value}"

View File

@@ -1,4 +1,4 @@
class DeliveryDestination < ActiveRecord::Base
class DeliveryDestination < ActiveRecord::Base
has_one :cart
belongs_to :user
@@ -6,9 +6,32 @@ class DeliveryDestination < ActiveRecord::Base
dd = DeliveryDestination.where(["user_id is not null and user_id = ?", user_id]).order("id desc").first
dd ||= DeliveryDestination.where(["anonymous_id_string is not null and anonymous_id_string = ?", anonymous_id]).order("id desc").first
dd ||= DeliveryDestination.new({user_id: user_id, anonymous_id_string: anonymous_id })
dd.payment_method ||= "cash_on_delivery"
dd.gift = false
dd.recipient_name = ""
dd.recipient_phone = ""
dd.recipient_email = ""
dd.recipient_address = ""
dd.recipient_place = ""
dd.recipient_postal_code = ""
attributes = dd.as_json
attributes.delete("id")
result = DeliveryDestination.create!(attributes)
return result
end
def get_payment_string
case self.payment_method
when 'cash_on_delivery'
'Prilikom preuzimanja'
when 'paypal'
'Paypal'
when 'pikpay'
'Pikpay'
else
'Nepoznato'
end
end
end

View File

@@ -1,10 +1,10 @@
<%
<%
dd = @cart.delivery_destination
c = @cart
%>
**Ime:** <%= dd.name %>
**Ime:** <%= dd.name %>
**Adresa:**
<%= dd.address %>
@@ -17,9 +17,23 @@ Bosna i Hercegovina
**Telefon: ** +387 <%= dd.phone %>
**Plaćanje: ** <%= dd.get_payment_string %>
**Napomena: **
<%= dd.note %>
<% if dd.gift %>
**Poklon **
**Name: **<%= dd.recipient_name %>
**Postal code: **<%= dd.recipient_postal_code %>
**Place: **<%= dd.recipient_place %>
**Address: **<%= dd.recipient_address %>
**Phone: **<%= dd.recipient_phone %>
**Email: **<%= dd.recipient_email %>
<% end %>
<% if dd.instant_delivery %>
**Naručeno:** <%= @cart.updated_at.in_time_zone("Europe/Sarajevo").strftime("%A %d.%m.%Y. %H:%M") %>
@@ -37,16 +51,7 @@ Bosna i Hercegovina
Dostava
1 x <%= Helper.money(c.delivery_cost) %> = <%= Helper.money(c.delivery_cost) %>
**UKUPNO:** <%= Helper.money(c.total) %>
[Pogledati OVAJ LINK](https://www.ribica.ba/backoffice/carts/<%= @cart.id %>)

View File

@@ -75,4 +75,4 @@ var CartActions = {
}
};
module.exports = CartActions;
module.exports = CartActions;

View File

@@ -5,25 +5,54 @@ var React = require('react'),
NavigationActions = require('../../actions/navigationActions'),
SingleItem = require('../items/singleItem'),
Globals = require('../../globals'),
CartTotal = require('./cartTotal'),
CartTotal = require('./cartTotal'),
LinkBanner = require('../linkBanner/linkBanner'),
RibicaFormError = require('../shared/ribicaFormError');
PaymentSelect = require('../payment/paymentSelect'),
RibicaFormError = require('../shared/ribicaFormError'),
PaypalButton = require('../payment/paypalButton'),
PikpayButton = require('../payment/pikpayButton'),
CashOnDeliveryButton = require('../payment/cashOnDeliveryButton');
var Router = require('react-router');
var CheckoutPage = React.createClass({
render: function() {
var choosePayment = (
<div className="payment-btn">
<PaymentSelect deliveryDestination={this.state.deliveryDestination}
amount={CartStore.getAmount()}
deliveryCost={CartStore.getDeliveryCost(false)}
disabled={!this.state.isDeliveryDestinationValid}
cashOnDeliveryDisabled={!this.state.isDeliveryDestinationValid || this.state.deliveryDestination.get('gift')}
onCashClick={this._onOrderClick}
cartId={this.state.cart.get('id')}
/>
</div>
);
var last_used_payment;
if(this.state.deliveryDestination.get('payment_method') == 'paypal') {
last_used_payment = (
<PaypalButton disabled={!this.state.isDeliveryDestinationValid} onSubmitPaypal={this._handleOnSubmitPaypal} deliveryDestination={this.state.deliveryDestination} amount={CartStore.getAmount()} deliveryCost={CartStore.getDeliveryCost(false)} cartId={this.state.cart.get('id')}/>
);
} else if(this.state.deliveryDestination.get('payment_method') == 'pikpay') {
last_used_payment = (
<PikpayButton amount={CartStore.getAmount()} deliveryCost={CartStore.getDeliveryCost(false)} disabled={!this.state.isDeliveryDestinationValid} deliveryDestination={this.state.deliveryDestination} cartId={this.state.cart.get('id')}/>
);
} else {
last_used_payment = (
<CashOnDeliveryButton onCashClick={this._onOrderClick} disabled={!this.state.isDeliveryDestinationValid || this.state.deliveryDestination.get('gift')} cartId={this.state.cart.get('id')}/>
);
}
var supportedPlaceOptions = CartStore.getSupportedPlaces().map ( function (p) { return (<option value={p.code}>{p.placeLabel}</option>)});
var content = (
<div className="checkout-page center">
<div className="form-horizontal checkout_form_margin">
<fieldset>
<legend>Dostava</legend>
<legend>Podaci o naručiocu</legend>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="name">Prezime i Ime</label>
@@ -41,15 +70,15 @@ var CheckoutPage = React.createClass({
<span className="help-block">adresa na koju će roba biti isporučena</span>
</div>
</div>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="place">Mjesto</label>
<div className="col-md-4">
<RibicaFormError componentName="place" errorMessagesObject={this.state.deliveryDestinationErrors} />
<select id="place" name="place" className="form-control" value={this.state.deliveryDestination.get('place')} onChange={this._onFieldChange} >
{supportedPlaceOptions}
{supportedPlaceOptions}
</select>
</div>
</div>
@@ -78,22 +107,88 @@ var CheckoutPage = React.createClass({
<textarea className="form-control" id="note" name="note" value={this.state.deliveryDestination.get('note')} onChange={this._onFieldChange} ></textarea>
</div>
</div>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="order"></label>
<div className="col-md-8">
<div> Roba: <CartTotal items={this.state.items} itemCounts={this.state.itemCounts} /><br />
<span className={this.state.deliveryDestinationErrors['place'] ? 'hidden' : 'shown'}>
Dostava: <CartTotal deliveryCosts={this.state.deliveryCosts} /><br />
Ukupno: <CartTotal items={this.state.items} itemCounts={this.state.itemCounts} deliveryCosts={this.state.deliveryCosts} />
<div className="form-group">
<label className="col-md-4 control-label" htmlFor="order"></label>
<div className="col-md-8">
<div> Roba: <CartTotal items={this.state.items} itemCounts={this.state.itemCounts} /><br />
<span className={this.state.deliveryDestinationErrors[this.state.deliveryCostsTarget] ? 'hidden' : 'shown'}>
Dostava: <CartTotal deliveryCosts={this.state.deliveryCosts} /><br />
Ukupno: <CartTotal items={this.state.items} itemCounts={this.state.itemCounts} deliveryCosts={this.state.deliveryCosts} />
</span>
</div>
<div><button id="order" name="order" className="mybutton" disabled={!this.state.isDeliveryDestinationValid} onClick={this._onOrderClick}>Završi narudžbu</button></div>
</div>
</div>
<br />
<div className="checkbox">
<label><input type="checkbox" name="gift" checked={this.state.deliveryDestination.get('gift')} onChange={this._onFieldChange} />Poklon</label>
</div>
</div>
</fieldset>
</div>
</div>
);
</div>
</fieldset>
<fieldset className={this.state.deliveryDestination.get('gift') ? 'shown' : 'hidden'}>
<legend>Podaci o dostavi</legend>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="recipient_name">Prezime i Ime</label>
<div className="col-md-4">
<RibicaFormError componentName="recipient_name" errorMessagesObject={this.state.deliveryDestinationErrors} />
<input id="recipient_name" name="recipient_name" type="text" placeholder="Prezime Ime" className="form-control input-md" required="" value={this.state.deliveryDestination.get('recipient_name')} onChange={this._onFieldChange} />
<span className="help-block">ime osobe koja prima pošiljku</span>
</div>
</div>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="recipient_address">Adresa</label>
<div className="col-md-4">
<RibicaFormError componentName="recipient_address" errorMessagesObject={this.state.deliveryDestinationErrors} />
<input id="recipient_address" name="recipient_address" type="text" placeholder="Ulica i broj" className="form-control input-md" required="" value={this.state.deliveryDestination.get('recipient_address')} onChange={this._onFieldChange} />
<span className="help-block">adresa na koju će roba biti isporučena</span>
</div>
</div>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="recipient_place">Mjesto</label>
<div className="col-md-4">
<RibicaFormError componentName="recipient_place" errorMessagesObject={this.state.deliveryDestinationErrors} />
<select id="recipient_place" name="recipient_place" className="form-control" value={this.state.deliveryDestination.get('recipient_place')} onChange={this._onFieldChange} >
{supportedPlaceOptions}
</select>
</div>
</div>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="recipient_phone">Telefon</label>
<div className="col-md-4">
<RibicaFormError componentName="recipient_phone" errorMessagesObject={this.state.deliveryDestinationErrors} />
<div className="input-group">
<span className="input-group-addon">+387 </span>
<input id="recipient_phone" name="recipient_phone" className="form-control" placeholder="061 222 333" type="text" required="" value={this.state.deliveryDestination.get('recipient_phone')} onChange={this._onFieldChange} />
</div>
<p className="help-block">broj mobitela - mora biti sa jedne od mreža u BiH</p>
</div>
</div>
<div className="form-group ">
<label className="col-md-4 control-label" htmlFor="recipient_email">E - mail</label>
<div className="col-md-4">
<RibicaFormError componentName="recipient_email" errorMessagesObject={this.state.deliveryDestinationErrors} />
<input id="recipient_email" name="recipient_email" type="text" placeholder="ime@nekimail.com" className="form-control input-md" required="" value={this.state.deliveryDestination.get('recipient_email')} onChange={this._onFieldChange} />
<span className="help-block">E - mail adresa na koju će vam biti poslano obavještenje o narudžbi</span>
</div>
</div>
</fieldset>
<div className="payment-select-container">
{choosePayment}
</div>
</div>
</div>
);
if(CartStore.isAddressColapsed()) {
@@ -109,16 +204,27 @@ var CheckoutPage = React.createClass({
Ukupno: <CartTotal items={this.state.items} itemCounts={this.state.itemCounts} deliveryCosts={this.state.deliveryCosts} />
</p>
<p>
<button id="order" name="order" className="mybutton" disabled={!this.state.isDeliveryDestinationValid} onClick={this._onOrderClick}>Završi narudžbu</button> ili <button className="btn btn-default" onClick={this._onUncolapseClick}>Promijeni adresu</button>
</p>
<div className="hidden-xs">
<button className="btn btn-default gift-btn" onClick={this._onGiftBtnClicked}>Poklon</button>
<br />
<p className="collapsed-address-container">{last_used_payment} ili <button className="btn btn-default" onClick={this._onUncolapseClick}>Promijeni način plaćanja ili adresu</button></p>
</div>
<div className="collapsed-address-container-mobile btn-group-vertical visible-xs">
<button className="btn btn-default" onClick={this._onGiftBtnClicked}>Poklon</button>
{last_used_payment}
<button className="btn btn-default" onClick={this._onUncolapseClick}>Promijeni način plaćanja ili adresu</button>
</div>
<div className="form-group">
<label className="col-md-4 control-label" htmlFor="order"></label>
<div className="col-md-8">
<div> </div>
<div></div>
</div>
</div>
</div>
</div>);
}
@@ -144,10 +250,15 @@ var CheckoutPage = React.createClass({
},
_onFieldChange: function (event) {
CartActions.changeDeliveryDestinationProperty(event.target.name, event.target.value);
if(event.target.name === "gift") {
CartActions.changeDeliveryDestinationProperty(event.target.name, $(event.target).is(':checked'));
} else {
CartActions.changeDeliveryDestinationProperty(event.target.name, event.target.value);
}
},
_onOrderClick: function (event) {
CartActions.changeDeliveryDestinationProperty("payment_method", "cash_on_delivery");
CartActions.confirmDelivery();
},
@@ -155,6 +266,16 @@ var CheckoutPage = React.createClass({
CartActions.setAddressColapsed(false);
},
_onGiftBtnClicked: function (event) {
CartActions.changeDeliveryDestinationProperty('gift', true);
CartActions.setAddressColapsed(false);
},
_handleOnSubmitPaypal: function(event) {
CartActions.changeDeliveryDestinationProperty('payment_method', 'paypal');
return false;
},
getInitialState: function () {
return CartStore.getWholeCartState();
}
@@ -162,4 +283,4 @@ var CheckoutPage = React.createClass({
});
module.exports = CheckoutPage;
module.exports = CheckoutPage;

View File

@@ -0,0 +1,17 @@
var React = require('react');
var ItemActions = require('../../actions/itemActions');
var NavigationActions = require('../../actions/navigationActions');
var NavigationStore = require('../../stores/navigationStore');
var Globals = require('../../globals');
var Router = require('react-router');
var CashOnDeliveryButton = React.createClass({
render: function() {
return (
<button type="button" className="btn mybutton payment-btn" disabled={this.props.disabled} onClick={this.props.onCashClick}>Plati prilikom preuzimanja</button>
);
}
});
module.exports = CashOnDeliveryButton;

View File

@@ -0,0 +1,36 @@
var React = require('react');
var ItemActions = require('../../actions/itemActions');
var NavigationActions = require('../../actions/navigationActions');
var NavigationStore = require('../../stores/navigationStore');
var Globals = require('../../globals');
var Router = require('react-router');
var PaypalButton = require('./paypalButton');
var PikpaylButton = require('./pikpayButton');
var CashOnDeliveryButton = require('./cashOnDeliveryButton');
var PaymentSelect = React.createClass({
render: function() {
var cashOnDeliveryBtn = ( <CashOnDeliveryButton onCashClick={this.props.onCashClick} disabled={this.props.cashOnDeliveryDisabled} cartId={this.props.cartId}/> );
var pikpayBtn = ( <PikpaylButton amount={this.props.amount} deliveryCost={this.props.deliveryCost} disabled={this.props.disabled} deliveryDestination={this.props.deliveryDestination} cartId={this.props.cartId}/> );
var paypalBtn = ( <PaypalButton disabled={this.props.disabled} deliveryDestination={this.props.deliveryDestination} amount={this.props.amount} deliveryCost={this.props.deliveryCost} cartId={this.props.cartId}/> );
return (
<div>
<div className="payment_select btn-group payment-select-desktop hidden-xs">
{cashOnDeliveryBtn}
{pikpayBtn}
{paypalBtn}
</div>
<div className="payment_select btn-group-vertical payment-select-mobile visible-xs">
{cashOnDeliveryBtn}
{pikpayBtn}
{paypalBtn}
</div>
</div>);
}
});
module.exports = PaymentSelect;

View File

@@ -0,0 +1,62 @@
var React = require('react');
var ItemActions = require('../../actions/itemActions');
var NavigationActions = require('../../actions/navigationActions');
var NavigationStore = require('../../stores/navigationStore');
var CartStore = require('../../stores/cartStore');
var CartActions = require('../../actions/cartActions');
var Globals = require('../../globals');
var Router = require('react-router');
var PaypalButton = React.createClass({
render: function() {
var deliveryDestination = JSON.stringify(
{
'anonymous_id_string': this.props.deliveryDestination.get('anonymous_id_string'),
'user_id': this.props.deliveryDestination.get('user_id')
});
var amount = Globals.ConvertToEuro(this.props.amount);
var deliveryCost = Globals.ConvertToEuro(this.props.deliveryCost);
var root = location.protocol + '//' + location.host;
var return_url = root + "/hvala";
var cancel_return_url = root + "/dostava";
var notifyUrl = Globals.IsUrlAbsolute(Globals.ApiUrl) ? Globals.ApiUrl : root + Globals.ApiUrl;
notifyUrl += "/payment/confirmation";
return (
<button onClick={this._onPaypalClick} disabled={this.props.disabled} type="button" className="btn mybutton payment-btn">Paypal
<form id="paypal_form" className="hidden" action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="cmd" value="_xclick" />
<input type="hidden" name="business" value={Globals.PaypalId} />
<input type="hidden" name="lc" value="BA" />
<input type="hidden" name="item_name" value="Item" />
<input type="hidden" name="item_number" value={this.props.cartId} />
<input type="hidden" name="amount" value={amount} />
<input type="hidden" name="currency_code" value="EUR" />
<input type="hidden" name="button_subtype" value="services" />
<input type="hidden" name="no_note" value="0" />
<input type="hidden" name="cn" value="Napomena:" />
<input type="hidden" name="no_shipping" value="1" />
<input type="hidden" name="rm" value="1" />
<input type="hidden" name="return" value={return_url} />
<input type="hidden" name="cancel_return" value={cancel_return_url} />
<input type="hidden" name="shipping" value={deliveryCost} />
<input type="hidden" name="bn" value="PP-BuyNowBF:btn_buynowCC_LG.gif:NonHosted" />
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_buynowCC_LG.gif" border="0" name="submit" alt="PayPal - The safer, easier way to pay online!" />
<img alt="" border="0" src="https://www.paypalobjects.com/en_US/i/scr/pixel.gif" width="1" height="1" />
<input name="notify_url" value={notifyUrl} type="hidden" />
<input name="custom" value={deliveryDestination} type="hidden" />
</form>
</button>
);
},
_onPaypalClick: function(e) {
CartActions.changeDeliveryDestinationProperty('payment_method', 'paypal');
CartStore.saveDeliveryDestinationAnd(function() {
$("#paypal_form").submit();
});
}
});
module.exports = PaypalButton;

View File

@@ -0,0 +1,64 @@
var React = require('react');
var ItemActions = require('../../actions/itemActions');
var NavigationActions = require('../../actions/navigationActions');
var NavigationStore = require('../../stores/navigationStore');
var CartStore = require('../../stores/cartStore');
var CartActions = require('../../actions/cartActions');
var Globals = require('../../globals');
var Router = require('react-router');
var sha1 = require('sha1');
var PikpayButton = React.createClass({
render: function() {
var total = this.props.amount + this.props.deliveryCost;
total = total.toFixed(2) * 100;
var order_info = "Info";
var order_number = this.props.cartId;
var key = Globals.PikpayKey;
var authenticity_token = Globals.PikpayAuthenticityToken;
var digest = sha1(key + order_number + total + "BAM");
var rebate_digest = sha1(key + order_number + total + total + "BAM");
var deliveryDestination = this.props.deliveryDestination;
var city = CartStore.getNameOfThePlace(deliveryDestination.get('place'));
var custom = JSON.stringify(
{
'anonymous_id_string': deliveryDestination.get('anonymous_id_string'),
'user_id': deliveryDestination.get('user_id')
});
return (<span></span> /*
<button type="button" onClick={this._onPikpayClick} disabled={this.props.disabled} className="btn mybutton payment-btn">Plati karticom
<form id="pikpay_form" action={Globals.PikpayFormUrl} method="post" target="_top">
<input type="hidden" name="ch_full_name" value={deliveryDestination.get('name')} />
<input type="hidden" name="ch_address" value={deliveryDestination.get('address')} />
<input type="hidden" name="ch_city" value={city} />
<input type="hidden" name="ch_zip" value={deliveryDestination.get('place')} />
<input type="hidden" name="ch_country" value="Bosna i Hercegovina" />
<input type="hidden" name="ch_phone" value={deliveryDestination.get('phone')} />
<input type="hidden" name="ch_email" value={deliveryDestination.get('email')} />
<input type="hidden" name="order_info" value={order_info} />
<input type="hidden" name="order_number" value={order_number} />
<input type="hidden" name="amount" value={total} />
<input type="hidden" name="currency" value="BAM" />
<input type="hidden" name="original_amount" value={total} />
<input type="hidden" name="language" value="hr" />
<input type="hidden" name="transaction_type" value="authorize" />
<input type="hidden" name="authenticity_token" value={authenticity_token} />
<input type="hidden" name="digest" value={digest} />
<input type="hidden" name="rebate_digest" value={rebate_digest} />
<input type="hidden" name="custom_params" value={custom} />
</form>
</button> */
);
},
_onPikpayClick: function(e) {
CartActions.changeDeliveryDestinationProperty('payment_method', 'pikpay');
CartStore.saveDeliveryDestinationAnd(function() {
$("#pikpay_form").submit();
});
}
});
module.exports = PikpayButton;

View File

@@ -34,7 +34,8 @@ var SearchBox = React.createClass({
},
onKeyPress: function(e) {
var enterKeyCode = 13;
if(e.which == enterKeyCode) {
var whichKey = e.key || e.which;
if(whichKey == enterKeyCode) {
this.doSearch();
e.preventDefault();
}

View File

@@ -1,5 +1,33 @@
.checkout_form_margin {
margin-right: 10px !important;
margin-left: 10px !important;
}
.payment-select-container {
text-align: center;
}
.payment-select-desktop .payment-btn {
margin-right: 10px !important;;
}
.payment-select-mobile .payment-btn {
margin-bottom: 6px !important;
}
.collapsed-address-container .mybutton {
margin-top: 0px !important;
width: 240px;
}
.collapsed-address-container-mobile button {
margin-bottom: 6px !important;
margin-left: 10% !important;
margin-right: 10% !important;
width: 80% !important;
}
.gift-btn {
width: 200px;
margin-bottom: 10px;
}

View File

@@ -1,95 +1,77 @@
@import url(http://fonts.googleapis.com/css?family=Roboto:400,300italic,300,100italic,100,400italic,500,500italic,700italic,700,900,900italic);
/* site */
html {
overflow-y: scroll;
overflow-y: scroll;
}
body{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813560/bg_prp6pz.jpg") fixed 100% 100% ;
background-size: cover ;
background-position: center ;
background-repeat: no-repeat;
font-family: 'Roboto', sans-serif;
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813560/bg_prp6pz.jpg") fixed 100% 100% ;
background-size: cover ;
background-position: center ;
background-repeat: no-repeat;
font-family: 'Roboto', sans-serif;
}
#left-nave {
background: blue;
position: absolute;
top: 0px;
left: 0px;
height: 100%;
width: 250px;
}
.beba{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/beba-bg_wip7kj.png");
background-size: cover;
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/beba-bg_wip7kj.png");
background-size: cover;
}
html, body { height: 100%; width: 100%; margin: 0; }
h1 h2 h3 h4 h5{
font-family: 'Roboto', sans-serif;
font-family: 'Roboto', sans-serif;
}
.col-centered{
float: none;
margin: 5% auto;
float: none;
margin: 5% auto;
}
.mybutton{
background-color: #00a8a8;
padding: 6px 11px !important;
border-radius: 3px;
margin-top: 8px;
border: solid #06c3c3 1px;
color: #ffffff !important;
background-color: #00a8a8;
padding: 6px 11px !important;
border-radius: 3px;
margin-top: 8px;
border: solid #06c3c3 1px;
color: #ffffff !important;
}
.mybutton:hover {
background-color: #06c3c3 !important;
padding: 6px 11px !important;
color: black !important;
border-radius: 3px;
margin-top: 8px;
border: solid #06c3c3 1px;
color: #ffffff !important;
background-color: #06c3c3 !important;
padding: 6px 11px !important;
color: black !important;
border-radius: 3px;
margin-top: 8px;
border: solid #06c3c3 1px;
color: #ffffff !important;
}
.add-to-cart-button {
background-color: #00a8a8;
padding: 6px 11px !important;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff !important;
background-color: #00a8a8;
padding: 6px 11px !important;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff !important;
}
.add-to-cart-button:hover {
background-color: #06c3c3 !important;
padding: 6px 11px !important;
color: black !important;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff !important;
background-color: #06c3c3 !important;
padding: 6px 11px !important;
color: black !important;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff !important;
}
a{
font-family: 'Roboto', sans-serif;
font-weight: bold;
font-family: 'Roboto', sans-serif;
font-weight: bold;
}
.mynav li{
display: inline-block;
display: inline-block;
}
.mynav {
margin-top: 6px;
text-align: center;
text-align: center;
}
.mynav a{
text-decoration: none;
@@ -108,27 +90,24 @@ text-decoration: none;
color: #06c3c3;
background: none;
}
.mynav li .mybutton {
background-color: #00a8a8;
padding: 4px 10px !important;
margin-top: 6px;
display: block;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff;
background-color: #00a8a8;
padding: 4px 10px !important;
margin-top: 6px;
display: block;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff;
}
.mynav li .mybutton:hover {
background-color: #06c3c3;
padding: 4px 10px !important;
margin-top: 6px;
display: block;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff;
background-color: #06c3c3;
padding: 4px 10px !important;
margin-top: 6px;
display: block;
border-radius: 3px;
border: solid #06c3c3 1px;
color: #ffffff;
}
.productbox{
cursor: pointer;
-moz-box-shadow: 1px 2px 2px #cccccc;
@@ -143,219 +122,181 @@ margin-bottom: 35px;
height: auto;
border-bottom: solid gray 1px;
width: 100%;
}
.productbox div{
padding: 4px 10px;
}
.productbox a{
color: black;
text-decoration: none;
text-decoration: none;
}
.productbox a:hover{
color: black;
text-decoration: none;
text-decoration: none;
text-decoration: underline;
}
.productbox div span{
color: black;
}
#beba{
padding-left: 45px;
padding-right: 45px;
margin-left: -23px;
margin-right: -23px;
padding-left: 45px;
padding-right: 45px;
margin-left: -23px;
margin-right: -23px;
}
#beba:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/beba-bg_wip7kj.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#dijete:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/dijete-bg_gff80o.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#dijete:hover a{
color: white;
}
#mama:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/mama-bg_rqbnuo.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#mama:hover a{
color: white;
}
#madeinbih:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/made-in-bih-bg_uusqpm.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#madeinbih:hover a{
color: white;
}
#onama:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/made-in-bih-bg_uusqpm.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#onama:hover a{
color: white;
}
#marka:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/marka-bg_cy8hpe.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#marka:hover a{
color: white;
}
#akcija:hover{
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/akcija-bg_xtawus.png");
background-position: center ;
background-size: 100% 100%;
color: white;
background-repeat: no-repeat;
background-repeat: no-repeat;
}
#akcija:hover a{
color: white;
}
.mydropdown li:hover .sub-menu {
visibility: visible;
display: block;
visibility: visible;
display: block;
}
.mydropdown:hover .dropdown-menu {
display: block;
visibility: visible;
display: block;
visibility: visible;
}
.navbar-default{
color: black;
background-color: white;
border-color: lightgray;
}
.navbar-default .navbar-nav > li > a{
color:black;
color:black;
}
.navbar-default .navbar-nav > li{
padding-left: 16px;
padding-right: 16px;
}
.navbar-default .navbar-nav > li > a:focus{
background-color: rgba(255, 255, 255, 0.6) !important;
-moz-box-shadow: 0px 0px 2px #999;
background-color: rgba(255, 255, 255, 0.6) !important;
-moz-box-shadow: 0px 0px 2px #999;
-webkit-box-shadow: 0px 0px 2px #999;
box-shadow: 0px 0px 2px #999;
border: none !important;
border-radius: 2px;
}
.navbar-default .navbar-nav > .dropdown > a .caret{
border-top-color: #fff;
border-bottom-color: #fff;
border-top-color: #fff;
border-bottom-color: #fff;
}
.navbar-default .navbar-brand{
color:#fff;
color:#fff;
}
.menu-large {
position: static !important;
position: static !important;
}
.megamenu{
padding: 20px 0px;
width:94%;
margin-left: 3%;
border-top: none !important;
-moz-box-shadow: 0px 1px 2px #999;
padding: 20px 0px;
width:94%;
margin-left: 3%;
border-top: none !important;
-moz-box-shadow: 0px 1px 2px #999;
-webkit-box-shadow: 0px 1px 2px #999;
box-shadow: 0px 1px 2px #999;
}
.megamenu> li > ul {
padding: 0;
margin: 0;
padding: 0;
margin: 0;
}
.megamenu> li > ul > li {
list-style: none;
padding-left: 10px;
list-style: none;
padding-left: 10px;
}
.megamenu> li > ul > li > a {
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.428571429;
color: black;
white-space: normal;
display: block;
padding: 3px 20px;
clear: both;
font-weight: normal;
line-height: 1.428571429;
color: black;
white-space: normal;
}
.megamenu> li ul > li > a:hover,
.megamenu> li ul > li > a:focus {
text-decoration: none;
color: #262626;
background-color: #f5f5f5;
text-decoration: none;
color: #262626;
background-color: #f5f5f5;
}
.megamenu.disabled > a,
.megamenu.disabled > a:hover,
.megamenu.disabled > a:focus {
color: #999999;
color: #999999;
}
.megamenu.disabled > a:hover,
.megamenu.disabled > a:focus {
text-decoration: none;
background-color: transparent;
background-image: none;
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
cursor: not-allowed;
text-decoration: none;
background-color: transparent;
background-image: none;
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
cursor: not-allowed;
}
.megamenu.dropdown-header {
color: #428bca;
font-size: 18px;
color: #428bca;
font-size: 18px;
}
.dropdown-header p {
border-bottom: solid 1.5px lightgray !important;
@@ -363,193 +304,188 @@ margin-bottom: 2px;
text-transform: uppercase;
color: black;
font-family: 'Roboto', sans-serif;
font-weight: 500;
font-size: 16px;
font-weight: 500;
font-size: 16px;
}
.mycart{
position: absolute;
position: absolute;
background: url("https://res.cloudinary.com/du5pdibul/image/upload/v1428813559/cart-icon_p9u63r.png");
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
width: 42px;
height: 44px;
padding-bottom: 0px;
right:-14px;
bottom: -15px;
text-align: right;
background-repeat: no-repeat;
width: 42px;
height: 44px;
padding-bottom: 0px;
right:-14px;
bottom: -15px;
text-align: right;
}
.mycart div {
text-align: right;
text-align: right;
}
.mycart span{
position: absolute;
left: 13.4px;
top: 4px;
color: #ffffff;
}
.mycart span:hover{
color: white;
}
#myhome{
border-bottom: solid #06c3c3 1px;
border-bottom: solid #06c3c3 1px;
}
#myhome-hover:hover {
border-bottom: solid #06c3c3 1px;
#myhome-hover:hover {
border-bottom: solid #06c3c3 1px;
}
.sidebar ul{
list-style: none;
list-style: none;
}
.sidebar li{
margin-left:10px;
margin-bottom: 5px;
color:black;
padding: 10px;
border-bottom: solid 1px lightgray;
margin-left:10px;
margin-bottom: 5px;
color:black;
padding: 10px;
border-bottom: solid 1px lightgray;
}
.sidebar li a{
text-decoration: none;
color:black;
color:black;
}
.sidebar li a:hover{
color:#06c3c3;
color:#06c3c3;
}
@media (max-width: 768px) {
.megamenu{
margin-left: 0 ;
margin-right: 0 ;
}
.megamenu> li {
margin-bottom: 30px;
}
.megamenu> li:last-child {
margin-bottom: 0;
}
.megamenu.dropdown-header {
padding: 3px 15px !important;
}
.navbar-nav .open .dropdown-menu .dropdown-header{
color:#fff;
}
.megamenu{
margin-left: 0 ;
margin-right: 0 ;
}
.megamenu> li {
margin-bottom: 30px;
}
.megamenu> li:last-child {
margin-bottom: 0;
}
.megamenu.dropdown-header {
padding: 3px 15px !important;
}
.navbar-nav .open .dropdown-menu .dropdown-header{
color:#fff;
}
}
@media (max-width: 1000px) {
.navbar-header {
float: none;
}
.navbar-toggle {
display: block;
}
.navbar-collapse {
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
border-top:none !important;
}
.navbar-collapse.collapse {
display: none!important;
}
.navbar-nav>li {
float: none;
}
.navbar-nav>ul {
margin-bottom: 0px;
}
.navbar-nav>li>a {
padding-top: 10px;
padding-bottom: 10px;
}
.navbar-text {
float: none;
margin: 15px 0;
}
/* since 3.1.0 */
.navbar-collapse.collapse.in {
display: block!important;
}
.collapsing {
overflow: hidden!important;
}
.navbar-header {
float: none;
}
.navbar-toggle {
display: block;
}
.navbar-collapse {
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1);
border-top:none !important;
}
.navbar-collapse.collapse {
display: none!important;
}
.navbar-nav>li {
float: none;
}
.navbar-nav>ul {
margin-bottom: 0px;
}
.navbar-nav>li>a {
padding-top: 10px;
padding-bottom: 10px;
}
.navbar-text {
float: none;
margin: 15px 0;
}
/* since 3.1.0 */
.navbar-collapse.collapse.in {
display: block!important;
}
.collapsing {
overflow: hidden!important;
}
}
.error-message {
color: red;
color: red;
}
.white_button{
background-color: #f9f9f9;
width: 40px;
height: 40px;
padding-bottom: 7px;
/*display: block;*/
border-radius: 1px;
margin-top: 8px;
border: solid #cccccc 1px;
color: #4d4d4d !important;
font-size: 16px;
background-color: #f9f9f9;
width: 40px;
height: 40px;
padding-bottom: 7px;
/*display: block;*/
border-radius: 1px;
margin-top: 8px;
border: solid #cccccc 1px;
color: #4d4d4d !important;
font-size: 16px;
}
.search-box {
background-color: #efefef;
border-radius: 0px;
border: 1px solid #cccccc;
padding-left: 10px;
padding-right: 10px;
background-color: #efefef;
border-radius: 0px;
border: 1px solid #cccccc;
padding-left: 10px;
padding-right: 10px;
}
.search-button {
background-color: #f9f9f9;
color: #31b8b8;
border: 1px solid #cccccc;
border-radius: 0px;
margin-left: 5px;
background-color: #f9f9f9;
color: #31b8b8;
border: 1px solid #cccccc;
border-radius: 0px;
margin-left: 5px;
}
.qty-box {
background-color: #efefef;
border-radius: 0px;
border: 1px solid #cccccc;
background-color: #efefef;
border-radius: 0px;
border: 1px solid #cccccc;
}
.left-inner-addon {
position: relative;
position: relative;
}
.left-inner-addon input {
padding-left: 30px;
padding-left: 30px;
}
.left-inner-addon i {
position: absolute;
padding: 10px 12px;
pointer-events: none;
position: absolute;
padding: 10px 12px;
pointer-events: none;
}
.cart-title {
font-size: 29px;
color: #06c3c3;
font-size: 29px;
color: #06c3c3;
}
.footer {
text-align: center;
width: 100%;
bottom: 20px;
margin-bottom: 20px;
text-align: center;
width: 100%;
bottom: 20px;
margin-bottom: 20px;
}
.message {
font-size: 130%;
text-align: left;
margin-left: 20px;
margin-right: 20px;
}
.payment {
box-sizing: border-box;
color: rgb(102, 102, 102);
display: block;
font-family: Helvetica, arial, nimbussansl, liberationsans, freesans, clean, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol';
font-size: 13px;
font-style: normal;
font-variant: normal;
font-weight: normal;
height: 36px;
line-height: 18.2px;
width: 708px;
word-wrap: break-word;
}
.message {
@@ -568,3 +504,4 @@ text-decoration: none;
}

View File

@@ -1,8 +1,11 @@
var conversionRate = 0.51;
module.exports = {
ApiUrl: '@@apiEndpoint',
DefaultPageSize: 24,
ItemGroupIdOfStartPage: "1",
ItemGroupIdOfEmptyCartPage: "1",
BamToEuroConversionRate: conversionRate,
FormatCurrency: function(amount_s) {
var amount = parseFloat(amount_s);
return (amount.toFixed(2) + " KM")
@@ -11,7 +14,19 @@ module.exports = {
var amount = parseFloat(amount_s);
return (amount.toFixed(2) + "%")
},
ConvertToEuro: function(amount_s) {
amount = amount_s * conversionRate;
return amount.toFixed(2);
},
IsUrlAbsolute: function(url) {
var r = new RegExp('^(?:[a-z]+:)?//', 'i');
return r.test(url);
},
MaxNumberOfItemsToBeAdded: 1000,
PaypalId: "W7GKS2Q9ZGLGY",
PikpayFormUrl: "https://ipgtest.pikpay.ba/form",
PikpayAuthenticityToken: "1bb1eea16bd6492c01262636897c0c2e3291a1ab",
PikpayKey: "Ribica",
Slugify: function(text) {
return text.toString().toLowerCase()

View File

@@ -14,9 +14,9 @@ var DeliveryDestination = Backbone.Model.extend({
},
url: Globals.ApiUrl + '/cart/delivery_destination',
defaults: {
defaults: {
count: 0
}
});
module.exports = DeliveryDestination;
module.exports = DeliveryDestination;

View File

@@ -48,8 +48,8 @@ var router = Router.create({
location: Router.HistoryLocation
});
router.run(function (Handler, state) {
router.run(function (Handler, state) {
});

View File

@@ -10,6 +10,7 @@ var DeliveryDestination = require('../models/deliveryDestination');
var OrderConfirmation = require('../models/orderConfirmation');
var Place = require('../models/place');
var Validation = require('../utils/validation');
var Cart = require('../models/cart');
var _ = require('underscore');
@@ -23,11 +24,12 @@ var _deliveryDestination = new DeliveryDestination();
var _deliveryDestinationErrors = {};
var _deliveryCosts = new Place({
postalCode: _deliveryDestination.get('place')
})
});
var _cart = new Cart();
var _addressColapsed = false;
var supportedPlaces = [{
"code": "-12",
"placeLabel": "Izaberite mjesto"
@@ -37,7 +39,7 @@ var supportedPlaces = [{
}, {
"code": " 71000",
"placeLabel": "Sarajevo"
},
},
{
"code": " 78000",
"placeLabel": "Banja Luka"
@@ -50,7 +52,7 @@ var supportedPlaces = [{
{
"code": " 88000",
"placeLabel": "Mostar"
},
},
{
"code": " 72000",
@@ -2569,30 +2571,31 @@ var nameOfThePlace = function(code) {
}
var loadCart = function() {
_cart.fetch({success: function() {
_itemsInCart.fetch({
success: function() {
states = {}
for (var i = 0; i < _itemsInCart.models.length; i++) {
var itemInCart = _itemsInCart.models[i];
states[itemInCart.get('item_id')] = itemInCart;
}
_itemsForDisplay.fetch({
success: function() {
CartActions.dataLoaded();
_itemsInCart.fetch({
success: function() {
states = {}
for (var i = 0; i < _itemsInCart.models.length; i++) {
var itemInCart = _itemsInCart.models[i];
states[itemInCart.get('item_id')] = itemInCart;
}
_itemsForDisplay.fetch({
success: function() {
CartActions.dataLoaded();
_deliveryDestination.fetch({
success: function() {
validateDeliveryDestinationForm();
collapseAddressIfNeeded();
fetchPlace();
CartActions.dataLoaded();
}
});
}
});
}
});
_deliveryDestination.fetch({
success: function() {
validateDeliveryDestinationForm();
collapseAddressIfNeeded();
fetchPlace();
CartActions.dataLoaded();
}
});
}
});
}
});
}});
_cartDataLoadCalled = true;
};
@@ -2606,8 +2609,10 @@ var collapseAddressIfNeeded = function() {
}
var fetchPlace = function() {
postalCode = _deliveryDestination.get('gift') ? _deliveryDestination.get('recipient_place') : _deliveryDestination.get('place');
_deliveryCosts = new Place({
postalCode: _deliveryDestination.get('place')
postalCode: postalCode
})
_deliveryCosts.fetch({
success: function() {
@@ -2696,13 +2701,13 @@ var addNItems = function(item, count) {
var changeDeliveryDestinationProperty = function(property, value) {
_deliveryDestination.set(property, value);
if (property === 'place') {
if (property === 'place' || property === 'recipient_place' || property === 'gift') {
fetchPlace();
}
validateDeliveryDestinationForm();
};
var confirmOrder = function() {
var oc = new OrderConfirmation({
@@ -2719,14 +2724,20 @@ var confirmOrder = function() {
});
};
var saveDeliveryDestinationAnd = function(successCallback) {
_deliveryDestination.save(null, {
success: function() {
successCallback();
}
})
};
var saveDeliveryDestination = function() {
_deliveryDestination.save(null, {
success: function() {
confirmOrder();
}
})
success: function() {
confirmOrder();
}
});
};
var validateDeliveryDestinationForm = function() {
@@ -2757,7 +2768,34 @@ var validateDeliveryDestinationForm = function() {
_deliveryDestinationErrors['place'] = "Mjesto mora biti izabrano";
}
if(_deliveryDestination.get('gift')) {
if (Validation.safeString(_deliveryDestination.get('recipient_name')).search(nameRegex) < 0) {
_deliveryDestinationErrors['recipient_name'] = "I prezime i ime su obavezni";
}
if (Validation.safeString(_deliveryDestination.get('recipient_address')).search(addressRegex) < 0) {
_deliveryDestinationErrors['recipient_address'] = "Adresa mora biti ispravna";
}
if (_deliveryDestination.get('recipient_email') && Validation.safeString(_deliveryDestination.get('recipient_email')).search(emailRegex) < 0) {
_deliveryDestinationErrors['recipient_email'] = "Email mora biti ispravno upisan";
}
if (Validation.safeString(_deliveryDestination.get('recipient_phone')).search(phoneRegex) < 0) {
_deliveryDestinationErrors['recipient_phone'] = "Telefon mora biti ispravan";
}
if (Validation.safeString(_deliveryDestination.get('recipient_place')).search(placeRegex) < 0) {
_deliveryDestinationErrors['recipient_place'] = "Mjesto mora biti izabrano";
}
}
var requiredFields = ["name", "email", "place", 'address', 'phone'];
if(_deliveryDestination.get('gift')) {
requiredFields = requiredFields.concat(["recipient_name", "recipient_place", 'recipient_address', 'recipient_phone']);
}
for (var i in requiredFields) {
var value = _deliveryDestination.get(requiredFields[i]);
if (value === undefined || value === null || value === "") {
@@ -2768,10 +2806,14 @@ var validateDeliveryDestinationForm = function() {
}
var isDeliveryDestinationValid = function() {
return Object.getOwnPropertyNames(_deliveryDestinationErrors).length === 0;
}
return Object.getOwnPropertyNames(_deliveryDestinationErrors).length === 0;
}
var getDeliveryCostTarget = function() {
return _deliveryDestination.get("gift") ? "recipient_place" : "place";
}
// Extend CartStore with EventEmitter to add eventing capabilities
var CartStore = _.extend({}, EventEmitter.prototype, {
@@ -2792,6 +2834,29 @@ var CartStore = _.extend({}, EventEmitter.prototype, {
return supportedPlaces;
},
getAmount: function() {
var counts = states;
var total = 0;
if (counts && _itemsForDisplay) {
var items = _itemsForDisplay.models;
for (var i = 0; i < items.length; i++) {
var item = items[i];
var count = counts[item.get('id')].get('count');
var price = item.get('list_price');
total += (price * count)
};
}
return total;
},
getDeliveryCost: function(instantDelivery) {
return instantDelivery ? Number(_deliveryCosts.get('instant_delivery_price'))
: Number(_deliveryCosts.get('delivery_price'));
},
getWholeCartState: function() {
var numberOfItems = 0;
@@ -2813,7 +2878,10 @@ var CartStore = _.extend({}, EventEmitter.prototype, {
deliveryDestinationErrors: _deliveryDestinationErrors,
isDeliveryDestinationValid: isDeliveryDestinationValid(),
deliveryCosts: _deliveryCosts,
destinationValid: isDeliveryDestinationValid()
destinationValid: isDeliveryDestinationValid(),
address_colapsed: _addressColapsed,
deliveryCostsTarget: getDeliveryCostTarget(),
cart: _cart
};
return state;
},
@@ -2850,6 +2918,14 @@ var CartStore = _.extend({}, EventEmitter.prototype, {
address.push(_deliveryDestination.get('email'))
return address;
},
getNameOfThePlace: function(code) {
return nameOfThePlace(code);
},
saveDeliveryDestinationAnd: function(successCallback) {
saveDeliveryDestinationAnd(successCallback);
}
});

View File

@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<title>ribica.ba - ispunjava zelje</title>
<title>ribica.ba - prvi online shop sa dostavom u BiH za mame i bebe - kupite igracke, pelene i ostalo</title>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">

View File

@@ -31,6 +31,7 @@
"react": "~0.12.2",
"react-google-analytics": "^0.2.0",
"react-router": "~0.11.6",
"sha1": "^1.1.1",
"superagent": "~0.21.0"
}
}

View File

@@ -1,23 +0,0 @@
# Google analytics for react-router
## How to use
1. `npm install ga-react-router`
2. In your `webpack.config.js` add `new webpack.DefinePlugin({GA_TRACKING_CODE: JSON.stringify('XXXXXXXX')})`
3. Use analytics in your `Router.run` code.
## Example
```js
'use strict';
var React = require('react');
var Router = require('react-router');
var analytics = require('ga-react-router');
var routes = require('./routes');
Router.run(routes, Router.HistoryLocation, function(Handler, state) {
React.render(<Handler />, document.getElementById('content'));
analytics(state);
});
```

View File

@@ -1,50 +0,0 @@
{
"name": "ga-react-router",
"version": "1.3.0",
"description": "Google analytics component for react-router",
"main": "src/index.js",
"peerDependencies": {
"react-router": "0.11.x - 0.13.x"
},
"author": {
"name": "Thomas Coopman @tcoopman"
},
"repository": {
"type": "git",
"url": "git+ssh://git@github.com/tcoopman/ga-react-router.git"
},
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license.php"
}
],
"gitHead": "bd1a0c6d97aef76c38690de4278dec63da13fe9c",
"bugs": {
"url": "https://github.com/tcoopman/ga-react-router/issues"
},
"homepage": "https://github.com/tcoopman/ga-react-router#readme",
"_id": "ga-react-router@1.3.0",
"scripts": {},
"_shasum": "28f51f27d5b0339db55499adf82265033cedc939",
"_from": "ga-react-router@*",
"_npmVersion": "2.8.3",
"_nodeVersion": "1.8.1",
"_npmUser": {
"name": "tcoopman",
"email": "thomas.coopman@gmail.com"
},
"maintainers": [
{
"name": "tcoopman",
"email": "thomas.coopman@gmail.com"
}
],
"dist": {
"shasum": "28f51f27d5b0339db55499adf82265033cedc939",
"tarball": "http://registry.npmjs.org/ga-react-router/-/ga-react-router-1.3.0.tgz"
},
"directories": {},
"_resolved": "https://registry.npmjs.org/ga-react-router/-/ga-react-router-1.3.0.tgz",
"readme": "ERROR: No README data found!"
}

View File

@@ -1,31 +0,0 @@
'use strict';
if(typeof window !== 'undefined' && typeof GA_TRACKING_CODE !== 'undefined') {
(function(window, document, script, url, r, tag, firstScriptTag) {
window['GoogleAnalyticsObject']=r;
window[r] = window[r] || function() {
(window[r].q = window[r].q || []).push(arguments)
};
window[r].l = 1*new Date();
tag = document.createElement(script),
firstScriptTag = document.getElementsByTagName(script)[0];
tag.async = 1;
tag.src = url;
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
})(
window,
document,
'script',
'//www.google-analytics.com/analytics.js',
'ga'
);
var ga = window.ga;
ga('create', GA_TRACKING_CODE, 'auto');
module.exports = function() {
return window.ga.apply(window.ga, arguments);
};
} else {
module.exports = function() {console.log(arguments)};
}

View File

@@ -1,12 +0,0 @@
'use strict';
var ga = require('./ga');
function analytics(state) {
ga('send', 'pageview', {
'page': state.path
});
}
module.exports = analytics;

140
node_modules/react-router/README.md generated vendored
View File

@@ -1,140 +0,0 @@
[![npm package](https://img.shields.io/npm/v/react-router.svg?style=flat-square)](https://www.npmjs.org/package/react-router)
[![build status](https://img.shields.io/travis/rackt/react-router/master.svg?style=flat-square)](https://travis-ci.org/rackt/react-router)
[![dependency status](https://img.shields.io/david/rackt/react-router.svg?style=flat-square)](https://david-dm.org/rackt/react-router)
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/rackt/react-router?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
React Router
============
A complete routing library for React.
Docs
----
- [Guide: Overview](/docs/guides/overview.md)
- [API](/docs/api/)
Important Notes
---------------
### SemVer
Before our `1.0` release, breaking API changes will cause a bump to
`0.x`. For example, `0.4.1` and `0.4.8` will have the same API, but
`0.5.0` will have breaking changes.
Please refer to the [upgrade guide](/UPGRADE_GUIDE.md) and
[changelog](/CHANGELOG.md) when upgrading.
Installation
------------
```sh
npm install react-router
# or
bower install react-router
```
This library is written with CommonJS modules. If you are using
browserify, webpack, or similar, you can consume it like anything else
installed from npm.
There is also a global build available on bower, find the library on
`window.ReactRouter`.
The library is also available on the popular CDN [cdnjs](https://cdnjs.com/libraries/react-router).
Features
--------
- Nested views mapped to nested routes
- Modular construction of route hierarchy
- Sync and async transition hooks
- Transition abort / redirect / retry
- Dynamic segments
- Query parameters
- Links with automatic `.active` class when their route is active
- Multiple root routes
- Hash or HTML5 history (with fallback) URLs
- Declarative Redirect routes
- Declarative NotFound routes
- Browser scroll behavior with transitions
Check out the `examples` directory to see how simple previously complex UI
and workflows are to create.
What's it look like?
--------------------
```js
var routes = (
<Route handler={App} path="/">
<DefaultRoute handler={Home} />
<Route name="about" handler={About} />
<Route name="users" handler={Users}>
<Route name="recent-users" path="recent" handler={RecentUsers} />
<Route name="user" path="/user/:userId" handler={User} />
<NotFoundRoute handler={UserRouteNotFound}/>
</Route>
<NotFoundRoute handler={NotFound}/>
<Redirect from="company" to="about" />
</Route>
);
Router.run(routes, function (Handler) {
React.render(<Handler/>, document.body);
});
// Or, if you'd like to use the HTML5 history API for cleaner URLs:
Router.run(routes, Router.HistoryLocation, function (Handler) {
React.render(<Handler/>, document.body);
});
```
See more in the [overview guide](/docs/guides/overview.md).
Benefits of this Approach
-------------------------
1. **Incredible screen-creation productivity** - There is only one
use-case when a user visits a route: render something. Every user
interface has layers (or nesting) whether it's a simple navbar or
multiple levels of master-detail. Coupling nested routes to these
nested views gets rid of a ton of work for the developer to wire all
of it together when the user switches routes. Adding new screens
could not get faster.
2. **Immediate understanding of application structure** - When routes
are declared in one place, developers can easily construct a mental
image of the application. It's essentially a sitemap. There's not a
better way to get so much information about your app this quickly.
3. **Code tractability** - When a developer gets a ticket to fix a bug
at as specific url they simply 1) look at the route config, then 2)
go find the handler for that route. Every entry point into your
application is represented by these routes.
4. **URLs are your first thought, not an after-thought** - With React
Router, you don't get UI on the page without configuring a url first.
Fortunately, it's wildly productive this way, too.
Related Modules
---------------
- [rnr-constrained-route](https://github.com/bjyoungblood/rnr-constrained-route) - validate paths
and parameters on route handlers.
- [react-router-bootstrap](https://github.com/mtscout6/react-router-bootstrap) - Integration with [react-bootstrap](https://github.com/react-bootstrap/react-bootstrap) components.
- [react-router-proxy-loader](https://github.com/odysseyscience/react-router-proxy-loader) - A Webpack loader to dynamically load react-router components on-demand
Contributing
------------
Please see [CONTRIBUTING](CONTRIBUTING.md)
Thanks, Ember
-------------
This library is highly inspired by the Ember.js routing API. In general,
it's a translation of the Ember router api to React. Huge thanks to the
Ember team for solving the hardest part already.

View File

@@ -1,9 +0,0 @@
/**
* Represents a cancellation caused by navigating away
* before the previous transition has fully resolved.
*/
"use strict";
function Cancellation() {}
module.exports = Cancellation;

View File

@@ -1,30 +0,0 @@
'use strict';
var invariant = require('react/lib/invariant');
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
var History = {
/**
* The current number of entries in the history.
*
* Note: This property is read-only.
*/
length: 1,
/**
* Sends the browser back one entry in the history.
*/
back: function back() {
invariant(canUseDOM, 'Cannot use History.back without a DOM');
// Do this first so that History.length will
// be accurate in location change listeners.
History.length -= 1;
window.history.back();
}
};
module.exports = History;

View File

@@ -1,75 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
/* jshint -W084 */
var PathUtils = require('./PathUtils');
function deepSearch(route, pathname, query) {
// Check the subtree first to find the most deeply-nested match.
var childRoutes = route.childRoutes;
if (childRoutes) {
var match, childRoute;
for (var i = 0, len = childRoutes.length; i < len; ++i) {
childRoute = childRoutes[i];
if (childRoute.isDefault || childRoute.isNotFound) continue; // Check these in order later.
if (match = deepSearch(childRoute, pathname, query)) {
// A route in the subtree matched! Add this route and we're done.
match.routes.unshift(route);
return match;
}
}
}
// No child routes matched; try the default route.
var defaultRoute = route.defaultRoute;
if (defaultRoute && (params = PathUtils.extractParams(defaultRoute.path, pathname))) {
return new Match(pathname, params, query, [route, defaultRoute]);
} // Does the "not found" route match?
var notFoundRoute = route.notFoundRoute;
if (notFoundRoute && (params = PathUtils.extractParams(notFoundRoute.path, pathname))) {
return new Match(pathname, params, query, [route, notFoundRoute]);
} // Last attempt: check this route.
var params = PathUtils.extractParams(route.path, pathname);
if (params) {
return new Match(pathname, params, query, [route]);
}return null;
}
var Match = (function () {
function Match(pathname, params, query, routes) {
_classCallCheck(this, Match);
this.pathname = pathname;
this.params = params;
this.query = query;
this.routes = routes;
}
_createClass(Match, null, [{
key: 'findMatch',
/**
* Attempts to match depth-first a route in the given route's
* subtree against the given path and returns the match if it
* succeeds, null if no match can be made.
*/
value: function findMatch(routes, path) {
var pathname = PathUtils.withoutQuery(path);
var query = PathUtils.extractQuery(path);
var match = null;
for (var i = 0, len = routes.length; match == null && i < len; ++i) match = deepSearch(routes[i], pathname, query);
return match;
}
}]);
return Match;
})();
module.exports = Match;

View File

@@ -1,70 +0,0 @@
'use strict';
var PropTypes = require('./PropTypes');
/**
* A mixin for components that modify the URL.
*
* Example:
*
* var MyLink = React.createClass({
* mixins: [ Router.Navigation ],
* handleClick(event) {
* event.preventDefault();
* this.transitionTo('aRoute', { the: 'params' }, { the: 'query' });
* },
* render() {
* return (
* <a onClick={this.handleClick}>Click me!</a>
* );
* }
* });
*/
var Navigation = {
contextTypes: {
router: PropTypes.router.isRequired
},
/**
* Returns an absolute URL path created from the given route
* name, URL parameters, and query values.
*/
makePath: function makePath(to, params, query) {
return this.context.router.makePath(to, params, query);
},
/**
* Returns a string that may safely be used as the href of a
* link to the route with the given name.
*/
makeHref: function makeHref(to, params, query) {
return this.context.router.makeHref(to, params, query);
},
/**
* Transitions to the URL specified in the arguments by pushing
* a new URL onto the history stack.
*/
transitionTo: function transitionTo(to, params, query) {
this.context.router.transitionTo(to, params, query);
},
/**
* Transitions to the URL specified in the arguments by replacing
* the current URL in the history stack.
*/
replaceWith: function replaceWith(to, params, query) {
this.context.router.replaceWith(to, params, query);
},
/**
* Transitions to the previous URL.
*/
goBack: function goBack() {
return this.context.router.goBack();
}
};
module.exports = Navigation;

View File

@@ -1,153 +0,0 @@
'use strict';
var invariant = require('react/lib/invariant');
var assign = require('object-assign');
var qs = require('qs');
var paramCompileMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$]*)|[*.()\[\]\\+|{}^$]/g;
var paramInjectMatcher = /:([a-zA-Z_$][a-zA-Z0-9_$?]*[?]?)|[*]/g;
var paramInjectTrailingSlashMatcher = /\/\/\?|\/\?\/|\/\?/g;
var queryMatcher = /\?(.*)$/;
var _compiledPatterns = {};
function compilePattern(pattern) {
if (!(pattern in _compiledPatterns)) {
var paramNames = [];
var source = pattern.replace(paramCompileMatcher, function (match, paramName) {
if (paramName) {
paramNames.push(paramName);
return '([^/?#]+)';
} else if (match === '*') {
paramNames.push('splat');
return '(.*?)';
} else {
return '\\' + match;
}
});
_compiledPatterns[pattern] = {
matcher: new RegExp('^' + source + '$', 'i'),
paramNames: paramNames
};
}
return _compiledPatterns[pattern];
}
var PathUtils = {
/**
* Returns true if the given path is absolute.
*/
isAbsolute: function isAbsolute(path) {
return path.charAt(0) === '/';
},
/**
* Joins two URL paths together.
*/
join: function join(a, b) {
return a.replace(/\/*$/, '/') + b;
},
/**
* Returns an array of the names of all parameters in the given pattern.
*/
extractParamNames: function extractParamNames(pattern) {
return compilePattern(pattern).paramNames;
},
/**
* Extracts the portions of the given URL path that match the given pattern
* and returns an object of param name => value pairs. Returns null if the
* pattern does not match the given path.
*/
extractParams: function extractParams(pattern, path) {
var _compilePattern = compilePattern(pattern);
var matcher = _compilePattern.matcher;
var paramNames = _compilePattern.paramNames;
var match = path.match(matcher);
if (!match) {
return null;
}var params = {};
paramNames.forEach(function (paramName, index) {
params[paramName] = match[index + 1];
});
return params;
},
/**
* Returns a version of the given route path with params interpolated. Throws
* if there is a dynamic segment of the route path for which there is no param.
*/
injectParams: function injectParams(pattern, params) {
params = params || {};
var splatIndex = 0;
return pattern.replace(paramInjectMatcher, function (match, paramName) {
paramName = paramName || 'splat';
// If param is optional don't check for existence
if (paramName.slice(-1) === '?') {
paramName = paramName.slice(0, -1);
if (params[paramName] == null) return '';
} else {
invariant(params[paramName] != null, 'Missing "%s" parameter for path "%s"', paramName, pattern);
}
var segment;
if (paramName === 'splat' && Array.isArray(params[paramName])) {
segment = params[paramName][splatIndex++];
invariant(segment != null, 'Missing splat # %s for path "%s"', splatIndex, pattern);
} else {
segment = params[paramName];
}
return segment;
}).replace(paramInjectTrailingSlashMatcher, '/');
},
/**
* Returns an object that is the result of parsing any query string contained
* in the given path, null if the path contains no query string.
*/
extractQuery: function extractQuery(path) {
var match = path.match(queryMatcher);
return match && qs.parse(match[1]);
},
/**
* Returns a version of the given path without the query string.
*/
withoutQuery: function withoutQuery(path) {
return path.replace(queryMatcher, '');
},
/**
* Returns a version of the given path with the parameters in the given
* query merged into the query string.
*/
withQuery: function withQuery(path, query) {
var existingQuery = PathUtils.extractQuery(path);
if (existingQuery) query = query ? assign(existingQuery, query) : existingQuery;
var queryString = qs.stringify(query, { arrayFormat: 'brackets' });
if (queryString) {
return PathUtils.withoutQuery(path) + '?' + queryString;
}return PathUtils.withoutQuery(path);
}
};
module.exports = PathUtils;

View File

@@ -1,31 +0,0 @@
'use strict';
var assign = require('react/lib/Object.assign');
var ReactPropTypes = require('react').PropTypes;
var Route = require('./Route');
var PropTypes = assign({}, ReactPropTypes, {
/**
* Indicates that a prop should be falsy.
*/
falsy: function falsy(props, propName, componentName) {
if (props[propName]) {
return new Error('<' + componentName + '> should not have a "' + propName + '" prop');
}
},
/**
* Indicates that a prop should be a Route object.
*/
route: ReactPropTypes.instanceOf(Route),
/**
* Indicates that a prop should be a Router object.
*/
//router: ReactPropTypes.instanceOf(Router) // TODO
router: ReactPropTypes.func
});
module.exports = PropTypes;

View File

@@ -1,12 +0,0 @@
/**
* Encapsulates a redirect to the given route.
*/
"use strict";
function Redirect(to, params, query) {
this.to = to;
this.params = params;
this.query = query;
}
module.exports = Redirect;

View File

@@ -1,200 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var assign = require('react/lib/Object.assign');
var invariant = require('react/lib/invariant');
var warning = require('react/lib/warning');
var PathUtils = require('./PathUtils');
var _currentRoute;
var Route = (function () {
function Route(name, path, ignoreScrollBehavior, isDefault, isNotFound, onEnter, onLeave, handler) {
_classCallCheck(this, Route);
this.name = name;
this.path = path;
this.paramNames = PathUtils.extractParamNames(this.path);
this.ignoreScrollBehavior = !!ignoreScrollBehavior;
this.isDefault = !!isDefault;
this.isNotFound = !!isNotFound;
this.onEnter = onEnter;
this.onLeave = onLeave;
this.handler = handler;
}
_createClass(Route, [{
key: 'appendChild',
/**
* Appends the given route to this route's child routes.
*/
value: function appendChild(route) {
invariant(route instanceof Route, 'route.appendChild must use a valid Route');
if (!this.childRoutes) this.childRoutes = [];
this.childRoutes.push(route);
}
}, {
key: 'toString',
value: function toString() {
var string = '<Route';
if (this.name) string += ' name="' + this.name + '"';
string += ' path="' + this.path + '">';
return string;
}
}], [{
key: 'createRoute',
/**
* Creates and returns a new route. Options may be a URL pathname string
* with placeholders for named params or an object with any of the following
* properties:
*
* - name The name of the route. This is used to lookup a
* route relative to its parent route and should be
* unique among all child routes of the same parent
* - path A URL pathname string with optional placeholders
* that specify the names of params to extract from
* the URL when the path matches. Defaults to `/${name}`
* when there is a name given, or the path of the parent
* route, or /
* - ignoreScrollBehavior True to make this route (and all descendants) ignore
* the scroll behavior of the router
* - isDefault True to make this route the default route among all
* its siblings
* - isNotFound True to make this route the "not found" route among
* all its siblings
* - onEnter A transition hook that will be called when the
* router is going to enter this route
* - onLeave A transition hook that will be called when the
* router is going to leave this route
* - handler A React component that will be rendered when
* this route is active
* - parentRoute The parent route to use for this route. This option
* is automatically supplied when creating routes inside
* the callback to another invocation of createRoute. You
* only ever need to use this when declaring routes
* independently of one another to manually piece together
* the route hierarchy
*
* The callback may be used to structure your route hierarchy. Any call to
* createRoute, createDefaultRoute, createNotFoundRoute, or createRedirect
* inside the callback automatically uses this route as its parent.
*/
value: function createRoute(options, callback) {
options = options || {};
if (typeof options === 'string') options = { path: options };
var parentRoute = _currentRoute;
if (parentRoute) {
warning(options.parentRoute == null || options.parentRoute === parentRoute, 'You should not use parentRoute with createRoute inside another route\'s child callback; it is ignored');
} else {
parentRoute = options.parentRoute;
}
var name = options.name;
var path = options.path || name;
if (path && !(options.isDefault || options.isNotFound)) {
if (PathUtils.isAbsolute(path)) {
if (parentRoute) {
invariant(path === parentRoute.path || parentRoute.paramNames.length === 0, 'You cannot nest path "%s" inside "%s"; the parent requires URL parameters', path, parentRoute.path);
}
} else if (parentRoute) {
// Relative paths extend their parent.
path = PathUtils.join(parentRoute.path, path);
} else {
path = '/' + path;
}
} else {
path = parentRoute ? parentRoute.path : '/';
}
if (options.isNotFound && !/\*$/.test(path)) path += '*'; // Auto-append * to the path of not found routes.
var route = new Route(name, path, options.ignoreScrollBehavior, options.isDefault, options.isNotFound, options.onEnter, options.onLeave, options.handler);
if (parentRoute) {
if (route.isDefault) {
invariant(parentRoute.defaultRoute == null, '%s may not have more than one default route', parentRoute);
parentRoute.defaultRoute = route;
} else if (route.isNotFound) {
invariant(parentRoute.notFoundRoute == null, '%s may not have more than one not found route', parentRoute);
parentRoute.notFoundRoute = route;
}
parentRoute.appendChild(route);
}
// Any routes created in the callback
// use this route as their parent.
if (typeof callback === 'function') {
var currentRoute = _currentRoute;
_currentRoute = route;
callback.call(route, route);
_currentRoute = currentRoute;
}
return route;
}
}, {
key: 'createDefaultRoute',
/**
* Creates and returns a route that is rendered when its parent matches
* the current URL.
*/
value: function createDefaultRoute(options) {
return Route.createRoute(assign({}, options, { isDefault: true }));
}
}, {
key: 'createNotFoundRoute',
/**
* Creates and returns a route that is rendered when its parent matches
* the current URL but none of its siblings do.
*/
value: function createNotFoundRoute(options) {
return Route.createRoute(assign({}, options, { isNotFound: true }));
}
}, {
key: 'createRedirect',
/**
* Creates and returns a route that automatically redirects the transition
* to another route. In addition to the normal options to createRoute, this
* function accepts the following options:
*
* - from An alias for the `path` option. Defaults to *
* - to The path/route/route name to redirect to
* - params The params to use in the redirect URL. Defaults
* to using the current params
* - query The query to use in the redirect URL. Defaults
* to using the current query
*/
value: function createRedirect(options) {
return Route.createRoute(assign({}, options, {
path: options.path || options.from || '*',
onEnter: function onEnter(transition, params, query) {
transition.redirect(options.to, options.params || params, options.query || query);
}
}));
}
}]);
return Route;
})();
module.exports = Route;

View File

@@ -1,75 +0,0 @@
'use strict';
var invariant = require('react/lib/invariant');
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
var getWindowScrollPosition = require('./getWindowScrollPosition');
function shouldUpdateScroll(state, prevState) {
if (!prevState) {
return true;
} // Don't update scroll position when only the query has changed.
if (state.pathname === prevState.pathname) {
return false;
}var routes = state.routes;
var prevRoutes = prevState.routes;
var sharedAncestorRoutes = routes.filter(function (route) {
return prevRoutes.indexOf(route) !== -1;
});
return !sharedAncestorRoutes.some(function (route) {
return route.ignoreScrollBehavior;
});
}
/**
* Provides the router with the ability to manage window scroll position
* according to its scroll behavior.
*/
var ScrollHistory = {
statics: {
/**
* Records curent scroll position as the last known position for the given URL path.
*/
recordScrollPosition: function recordScrollPosition(path) {
if (!this.scrollHistory) this.scrollHistory = {};
this.scrollHistory[path] = getWindowScrollPosition();
},
/**
* Returns the last known scroll position for the given URL path.
*/
getScrollPosition: function getScrollPosition(path) {
if (!this.scrollHistory) this.scrollHistory = {};
return this.scrollHistory[path] || null;
}
},
componentWillMount: function componentWillMount() {
invariant(this.constructor.getScrollBehavior() == null || canUseDOM, 'Cannot use scroll behavior without a DOM');
},
componentDidMount: function componentDidMount() {
this._updateScroll();
},
componentDidUpdate: function componentDidUpdate(prevProps, prevState) {
this._updateScroll(prevState);
},
_updateScroll: function _updateScroll(prevState) {
if (!shouldUpdateScroll(this.state, prevState)) {
return;
}var scrollBehavior = this.constructor.getScrollBehavior();
if (scrollBehavior) scrollBehavior.updateScrollPosition(this.constructor.getScrollPosition(this.state.path), this.state.action);
}
};
module.exports = ScrollHistory;

View File

@@ -1,74 +0,0 @@
'use strict';
var PropTypes = require('./PropTypes');
/**
* A mixin for components that need to know the path, routes, URL
* params and query that are currently active.
*
* Example:
*
* var AboutLink = React.createClass({
* mixins: [ Router.State ],
* render() {
* var className = this.props.className;
*
* if (this.isActive('about'))
* className += ' is-active';
*
* return React.DOM.a({ className: className }, this.props.children);
* }
* });
*/
var State = {
contextTypes: {
router: PropTypes.router.isRequired
},
/**
* Returns the current URL path.
*/
getPath: function getPath() {
return this.context.router.getCurrentPath();
},
/**
* Returns the current URL path without the query string.
*/
getPathname: function getPathname() {
return this.context.router.getCurrentPathname();
},
/**
* Returns an object of the URL params that are currently active.
*/
getParams: function getParams() {
return this.context.router.getCurrentParams();
},
/**
* Returns an object of the query params that are currently active.
*/
getQuery: function getQuery() {
return this.context.router.getCurrentQuery();
},
/**
* Returns an array of the routes that are currently active.
*/
getRoutes: function getRoutes() {
return this.context.router.getCurrentRoutes();
},
/**
* A helper method to determine if a given route, params, and query
* are active.
*/
isActive: function isActive(to, params, query) {
return this.context.router.isActive(to, params, query);
}
};
module.exports = State;

View File

@@ -1,171 +0,0 @@
'use strict';
var React = require('react');
var RouteHandler = require('./components/RouteHandler');
var PropTypes = require('./PropTypes');
exports.Nested = React.createClass({
displayName: 'Nested',
render: function render() {
return React.createElement(
'div',
null,
React.createElement(
'h1',
{ className: 'Nested' },
'Nested'
),
React.createElement(RouteHandler, null)
);
}
});
exports.Foo = React.createClass({
displayName: 'Foo',
render: function render() {
return React.createElement(
'div',
{ className: 'Foo' },
'Foo'
);
}
});
exports.Bar = React.createClass({
displayName: 'Bar',
render: function render() {
return React.createElement(
'div',
{ className: 'Bar' },
'Bar'
);
}
});
exports.Baz = React.createClass({
displayName: 'Baz',
render: function render() {
return React.createElement(
'div',
{ className: 'Baz' },
'Baz'
);
}
});
exports.Async = React.createClass({
displayName: 'Async',
statics: {
delay: 10,
willTransitionTo: function willTransitionTo(transition, params, query, callback) {
setTimeout(callback, exports.Async.delay);
}
},
render: function render() {
return React.createElement(
'div',
{ className: 'Async' },
'Async'
);
}
});
exports.RedirectToFoo = React.createClass({
displayName: 'RedirectToFoo',
statics: {
willTransitionTo: function willTransitionTo(transition) {
transition.redirect('/foo');
}
},
render: function render() {
return null;
}
});
exports.RedirectToFooAsync = React.createClass({
displayName: 'RedirectToFooAsync',
statics: {
delay: 10,
willTransitionTo: function willTransitionTo(transition, params, query, callback) {
setTimeout(function () {
transition.redirect('/foo');
callback();
}, exports.RedirectToFooAsync.delay);
}
},
render: function render() {
return null;
}
});
exports.Abort = React.createClass({
displayName: 'Abort',
statics: {
willTransitionTo: function willTransitionTo(transition) {
transition.abort();
}
},
render: function render() {
return null;
}
});
exports.AbortAsync = React.createClass({
displayName: 'AbortAsync',
statics: {
delay: 10,
willTransitionTo: function willTransitionTo(transition, params, query, callback) {
setTimeout(function () {
transition.abort();
callback();
}, exports.AbortAsync.delay);
}
},
render: function render() {
return null;
}
});
exports.EchoFooProp = React.createClass({
displayName: 'EchoFooProp',
render: function render() {
return React.createElement(
'div',
null,
this.props.foo
);
}
});
exports.EchoBarParam = React.createClass({
displayName: 'EchoBarParam',
contextTypes: {
router: PropTypes.router.isRequired
},
render: function render() {
return React.createElement(
'div',
{ className: 'EchoBarParam' },
this.context.router.getCurrentParams().bar
);
}
});

View File

@@ -1,75 +0,0 @@
/* jshint -W058 */
'use strict';
var Cancellation = require('./Cancellation');
var Redirect = require('./Redirect');
/**
* Encapsulates a transition to a given path.
*
* The willTransitionTo and willTransitionFrom handlers receive
* an instance of this class as their first argument.
*/
function Transition(path, retry) {
this.path = path;
this.abortReason = null;
// TODO: Change this to router.retryTransition(transition)
this.retry = retry.bind(this);
}
Transition.prototype.abort = function (reason) {
if (this.abortReason == null) this.abortReason = reason || 'ABORT';
};
Transition.prototype.redirect = function (to, params, query) {
this.abort(new Redirect(to, params, query));
};
Transition.prototype.cancel = function () {
this.abort(new Cancellation());
};
Transition.from = function (transition, routes, components, callback) {
routes.reduce(function (callback, route, index) {
return function (error) {
if (error || transition.abortReason) {
callback(error);
} else if (route.onLeave) {
try {
route.onLeave(transition, components[index], callback);
// If there is no callback in the argument list, call it automatically.
if (route.onLeave.length < 3) callback();
} catch (e) {
callback(e);
}
} else {
callback();
}
};
}, callback)();
};
Transition.to = function (transition, routes, params, query, callback) {
routes.reduceRight(function (callback, route) {
return function (error) {
if (error || transition.abortReason) {
callback(error);
} else if (route.onEnter) {
try {
route.onEnter(transition, params, query, callback);
// If there is no callback in the argument list, call it automatically.
if (route.onEnter.length < 4) callback();
} catch (e) {
callback(e);
}
} else {
callback();
}
};
}, callback)();
};
module.exports = Transition;

View File

@@ -1,25 +0,0 @@
/**
* Actions that modify the URL.
*/
'use strict';
var LocationActions = {
/**
* Indicates a new location is being pushed to the history stack.
*/
PUSH: 'push',
/**
* Indicates the current location should be replaced.
*/
REPLACE: 'replace',
/**
* Indicates the most recent entry should be removed from the history stack.
*/
POP: 'pop'
};
module.exports = LocationActions;

View File

@@ -1,29 +0,0 @@
'use strict';
var LocationActions = require('../actions/LocationActions');
/**
* A scroll behavior that attempts to imitate the default behavior
* of modern browsers.
*/
var ImitateBrowserBehavior = {
updateScrollPosition: function updateScrollPosition(position, actionType) {
switch (actionType) {
case LocationActions.PUSH:
case LocationActions.REPLACE:
window.scrollTo(0, 0);
break;
case LocationActions.POP:
if (position) {
window.scrollTo(position.x, position.y);
} else {
window.scrollTo(0, 0);
}
break;
}
}
};
module.exports = ImitateBrowserBehavior;

View File

@@ -1,15 +0,0 @@
/**
* A scroll behavior that always scrolls to the top of the page
* after a transition.
*/
"use strict";
var ScrollToTopBehavior = {
updateScrollPosition: function updateScrollPosition() {
window.scrollTo(0, 0);
}
};
module.exports = ScrollToTopBehavior;

View File

@@ -1,108 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var React = require('react');
var ContextWrapper = require('./ContextWrapper');
var assign = require('react/lib/Object.assign');
var PropTypes = require('../PropTypes');
var REF_NAME = '__routeHandler__';
/**
* A <RouteHandler> component renders the active child route handler
* when routes are nested.
*/
var RouteHandler = (function (_React$Component) {
function RouteHandler() {
_classCallCheck(this, RouteHandler);
if (_React$Component != null) {
_React$Component.apply(this, arguments);
}
}
_inherits(RouteHandler, _React$Component);
_createClass(RouteHandler, [{
key: 'getChildContext',
value: function getChildContext() {
return {
routeDepth: this.context.routeDepth + 1
};
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
this._updateRouteComponent(this.refs[REF_NAME]);
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this._updateRouteComponent(this.refs[REF_NAME]);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this._updateRouteComponent(null);
}
}, {
key: '_updateRouteComponent',
value: function _updateRouteComponent(component) {
this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component);
}
}, {
key: 'getRouteDepth',
value: function getRouteDepth() {
return this.context.routeDepth;
}
}, {
key: 'createChildRouteHandler',
value: function createChildRouteHandler(props) {
var route = this.context.router.getRouteAtDepth(this.getRouteDepth());
if (route == null) {
return null;
}var childProps = assign({}, props || this.props, {
ref: REF_NAME,
params: this.context.router.getCurrentParams(),
query: this.context.router.getCurrentQuery()
});
return React.createElement(route.handler, childProps);
}
}, {
key: 'render',
value: function render() {
var handler = this.createChildRouteHandler();
// <script/> for things like <CSSTransitionGroup/> that don't like null
return handler ? React.createElement(
ContextWrapper,
null,
handler
) : React.createElement('script', null);
}
}]);
return RouteHandler;
})(React.Component);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
RouteHandler.contextTypes = {
routeDepth: PropTypes.number.isRequired,
router: PropTypes.router.isRequired
};
RouteHandler.childContextTypes = {
routeDepth: PropTypes.number.isRequired
};
module.exports = RouteHandler;

View File

@@ -1,38 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
/**
* This component is necessary to get around a context warning
* present in React 0.13.0. It sovles this by providing a separation
* between the "owner" and "parent" contexts.
*/
var React = require('react');
var ContextWrapper = (function (_React$Component) {
function ContextWrapper() {
_classCallCheck(this, ContextWrapper);
if (_React$Component != null) {
_React$Component.apply(this, arguments);
}
}
_inherits(ContextWrapper, _React$Component);
_createClass(ContextWrapper, [{
key: 'render',
value: function render() {
return this.props.children;
}
}]);
return ContextWrapper;
})(React.Component);
module.exports = ContextWrapper;

View File

@@ -1,47 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var PropTypes = require('../PropTypes');
var RouteHandler = require('./RouteHandler');
var Route = require('./Route');
/**
* A <DefaultRoute> component is a special kind of <Route> that
* renders when its parent matches but none of its siblings do.
* Only one such route may be used at any given level in the
* route hierarchy.
*/
var DefaultRoute = (function (_Route) {
function DefaultRoute() {
_classCallCheck(this, DefaultRoute);
if (_Route != null) {
_Route.apply(this, arguments);
}
}
_inherits(DefaultRoute, _Route);
return DefaultRoute;
})(Route);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
DefaultRoute.propTypes = {
name: PropTypes.string,
path: PropTypes.falsy,
children: PropTypes.falsy,
handler: PropTypes.func.isRequired
};
DefaultRoute.defaultProps = {
handler: RouteHandler
};
module.exports = DefaultRoute;

View File

@@ -1,135 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var React = require('react');
var assign = require('react/lib/Object.assign');
var PropTypes = require('../PropTypes');
function isLeftClickEvent(event) {
return event.button === 0;
}
function isModifiedEvent(event) {
return !!(event.metaKey || event.altKey || event.ctrlKey || event.shiftKey);
}
/**
* <Link> components are used to create an <a> element that links to a route.
* When that route is active, the link gets an "active" class name (or the
* value of its `activeClassName` prop).
*
* For example, assuming you have the following route:
*
* <Route name="showPost" path="/posts/:postID" handler={Post}/>
*
* You could use the following component to link to that route:
*
* <Link to="showPost" params={{ postID: "123" }} />
*
* In addition to params, links may pass along query string parameters
* using the `query` prop.
*
* <Link to="showPost" params={{ postID: "123" }} query={{ show:true }}/>
*/
var Link = (function (_React$Component) {
function Link() {
_classCallCheck(this, Link);
if (_React$Component != null) {
_React$Component.apply(this, arguments);
}
}
_inherits(Link, _React$Component);
_createClass(Link, [{
key: 'handleClick',
value: function handleClick(event) {
var allowTransition = true;
var clickResult;
if (this.props.onClick) clickResult = this.props.onClick(event);
if (isModifiedEvent(event) || !isLeftClickEvent(event)) {
return;
}if (clickResult === false || event.defaultPrevented === true) allowTransition = false;
event.preventDefault();
if (allowTransition) this.context.router.transitionTo(this.props.to, this.props.params, this.props.query);
}
}, {
key: 'getHref',
/**
* Returns the value of the "href" attribute to use on the DOM element.
*/
value: function getHref() {
return this.context.router.makeHref(this.props.to, this.props.params, this.props.query);
}
}, {
key: 'getClassName',
/**
* Returns the value of the "class" attribute to use on the DOM element, which contains
* the value of the activeClassName property when this <Link> is active.
*/
value: function getClassName() {
var className = this.props.className;
if (this.getActiveState()) className += ' ' + this.props.activeClassName;
return className;
}
}, {
key: 'getActiveState',
value: function getActiveState() {
return this.context.router.isActive(this.props.to, this.props.params, this.props.query);
}
}, {
key: 'render',
value: function render() {
var props = assign({}, this.props, {
href: this.getHref(),
className: this.getClassName(),
onClick: this.handleClick.bind(this)
});
if (props.activeStyle && this.getActiveState()) props.style = props.activeStyle;
return React.DOM.a(props, this.props.children);
}
}]);
return Link;
})(React.Component);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
Link.contextTypes = {
router: PropTypes.router.isRequired
};
Link.propTypes = {
activeClassName: PropTypes.string.isRequired,
to: PropTypes.oneOfType([PropTypes.string, PropTypes.route]).isRequired,
params: PropTypes.object,
query: PropTypes.object,
activeStyle: PropTypes.object,
onClick: PropTypes.func
};
Link.defaultProps = {
activeClassName: 'active',
className: ''
};
module.exports = Link;

View File

@@ -1,48 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var PropTypes = require('../PropTypes');
var RouteHandler = require('./RouteHandler');
var Route = require('./Route');
/**
* A <NotFoundRoute> is a special kind of <Route> that
* renders when the beginning of its parent's path matches
* but none of its siblings do, including any <DefaultRoute>.
* Only one such route may be used at any given level in the
* route hierarchy.
*/
var NotFoundRoute = (function (_Route) {
function NotFoundRoute() {
_classCallCheck(this, NotFoundRoute);
if (_Route != null) {
_Route.apply(this, arguments);
}
}
_inherits(NotFoundRoute, _Route);
return NotFoundRoute;
})(Route);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
NotFoundRoute.propTypes = {
name: PropTypes.string,
path: PropTypes.falsy,
children: PropTypes.falsy,
handler: PropTypes.func.isRequired
};
NotFoundRoute.defaultProps = {
handler: RouteHandler
};
module.exports = NotFoundRoute;

View File

@@ -1,43 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var PropTypes = require('../PropTypes');
var Route = require('./Route');
/**
* A <Redirect> component is a special kind of <Route> that always
* redirects to another route when it matches.
*/
var Redirect = (function (_Route) {
function Redirect() {
_classCallCheck(this, Redirect);
if (_Route != null) {
_Route.apply(this, arguments);
}
}
_inherits(Redirect, _Route);
return Redirect;
})(Route);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
Redirect.propTypes = {
path: PropTypes.string,
from: PropTypes.string, // Alias for path.
to: PropTypes.string,
handler: PropTypes.falsy
};
// Redirects should not have a default handler
Redirect.defaultProps = {};
module.exports = Redirect;

View File

@@ -1,91 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var React = require('react');
var invariant = require('react/lib/invariant');
var PropTypes = require('../PropTypes');
var RouteHandler = require('./RouteHandler');
/**
* <Route> components specify components that are rendered to the page when the
* URL matches a given pattern.
*
* Routes are arranged in a nested tree structure. When a new URL is requested,
* the tree is searched depth-first to find a route whose path matches the URL.
* When one is found, all routes in the tree that lead to it are considered
* "active" and their components are rendered into the DOM, nested in the same
* order as they are in the tree.
*
* The preferred way to configure a router is using JSX. The XML-like syntax is
* a great way to visualize how routes are laid out in an application.
*
* var routes = [
* <Route handler={App}>
* <Route name="login" handler={Login}/>
* <Route name="logout" handler={Logout}/>
* <Route name="about" handler={About}/>
* </Route>
* ];
*
* Router.run(routes, function (Handler) {
* React.render(<Handler/>, document.body);
* });
*
* Handlers for Route components that contain children can render their active
* child route using a <RouteHandler> element.
*
* var App = React.createClass({
* render: function () {
* return (
* <div class="application">
* <RouteHandler/>
* </div>
* );
* }
* });
*
* If no handler is provided for the route, it will render a matched child route.
*/
var Route = (function (_React$Component) {
function Route() {
_classCallCheck(this, Route);
if (_React$Component != null) {
_React$Component.apply(this, arguments);
}
}
_inherits(Route, _React$Component);
_createClass(Route, [{
key: 'render',
value: function render() {
invariant(false, '%s elements are for router configuration only and should not be rendered', this.constructor.name);
}
}]);
return Route;
})(React.Component);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
Route.propTypes = {
name: PropTypes.string,
path: PropTypes.string,
handler: PropTypes.func,
ignoreScrollBehavior: PropTypes.bool
};
Route.defaultProps = {
handler: RouteHandler
};
module.exports = Route;

View File

@@ -1,108 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var _inherits = function (subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) subClass.__proto__ = superClass; };
var React = require('react');
var ContextWrapper = require('./ContextWrapper');
var assign = require('react/lib/Object.assign');
var PropTypes = require('../PropTypes');
var REF_NAME = '__routeHandler__';
/**
* A <RouteHandler> component renders the active child route handler
* when routes are nested.
*/
var RouteHandler = (function (_React$Component) {
function RouteHandler() {
_classCallCheck(this, RouteHandler);
if (_React$Component != null) {
_React$Component.apply(this, arguments);
}
}
_inherits(RouteHandler, _React$Component);
_createClass(RouteHandler, [{
key: 'getChildContext',
value: function getChildContext() {
return {
routeDepth: this.context.routeDepth + 1
};
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
this._updateRouteComponent(this.refs[REF_NAME]);
}
}, {
key: 'componentDidUpdate',
value: function componentDidUpdate() {
this._updateRouteComponent(this.refs[REF_NAME]);
}
}, {
key: 'componentWillUnmount',
value: function componentWillUnmount() {
this._updateRouteComponent(null);
}
}, {
key: '_updateRouteComponent',
value: function _updateRouteComponent(component) {
this.context.router.setRouteComponentAtDepth(this.getRouteDepth(), component);
}
}, {
key: 'getRouteDepth',
value: function getRouteDepth() {
return this.context.routeDepth;
}
}, {
key: 'createChildRouteHandler',
value: function createChildRouteHandler(props) {
var route = this.context.router.getRouteAtDepth(this.getRouteDepth());
if (route == null) {
return null;
}var childProps = assign({}, props || this.props, {
ref: REF_NAME,
params: this.context.router.getCurrentParams(),
query: this.context.router.getCurrentQuery()
});
return React.createElement(route.handler, childProps);
}
}, {
key: 'render',
value: function render() {
var handler = this.createChildRouteHandler();
// <script/> for things like <CSSTransitionGroup/> that don't like null
return handler ? React.createElement(
ContextWrapper,
null,
handler
) : React.createElement('script', null);
}
}]);
return RouteHandler;
})(React.Component);
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
RouteHandler.contextTypes = {
routeDepth: PropTypes.number.isRequired,
router: PropTypes.router.isRequired
};
RouteHandler.childContextTypes = {
routeDepth: PropTypes.number.isRequired
};
module.exports = RouteHandler;

View File

@@ -1,514 +0,0 @@
/* jshint -W058 */
'use strict';
var React = require('react');
var warning = require('react/lib/warning');
var invariant = require('react/lib/invariant');
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
var LocationActions = require('./actions/LocationActions');
var ImitateBrowserBehavior = require('./behaviors/ImitateBrowserBehavior');
var HashLocation = require('./locations/HashLocation');
var HistoryLocation = require('./locations/HistoryLocation');
var RefreshLocation = require('./locations/RefreshLocation');
var StaticLocation = require('./locations/StaticLocation');
var ScrollHistory = require('./ScrollHistory');
var createRoutesFromReactChildren = require('./createRoutesFromReactChildren');
var isReactChildren = require('./isReactChildren');
var Transition = require('./Transition');
var PropTypes = require('./PropTypes');
var Redirect = require('./Redirect');
var History = require('./History');
var Cancellation = require('./Cancellation');
var Match = require('./Match');
var Route = require('./Route');
var supportsHistory = require('./supportsHistory');
var PathUtils = require('./PathUtils');
/**
* The default location for new routers.
*/
var DEFAULT_LOCATION = canUseDOM ? HashLocation : '/';
/**
* The default scroll behavior for new routers.
*/
var DEFAULT_SCROLL_BEHAVIOR = canUseDOM ? ImitateBrowserBehavior : null;
function hasProperties(object, properties) {
for (var propertyName in properties) if (properties.hasOwnProperty(propertyName) && object[propertyName] !== properties[propertyName]) {
return false;
}return true;
}
function hasMatch(routes, route, prevParams, nextParams, prevQuery, nextQuery) {
return routes.some(function (r) {
if (r !== route) return false;
var paramNames = route.paramNames;
var paramName;
// Ensure that all params the route cares about did not change.
for (var i = 0, len = paramNames.length; i < len; ++i) {
paramName = paramNames[i];
if (nextParams[paramName] !== prevParams[paramName]) return false;
}
// Ensure the query hasn't changed.
return hasProperties(prevQuery, nextQuery) && hasProperties(nextQuery, prevQuery);
});
}
function addRoutesToNamedRoutes(routes, namedRoutes) {
var route;
for (var i = 0, len = routes.length; i < len; ++i) {
route = routes[i];
if (route.name) {
invariant(namedRoutes[route.name] == null, 'You may not have more than one route named "%s"', route.name);
namedRoutes[route.name] = route;
}
if (route.childRoutes) addRoutesToNamedRoutes(route.childRoutes, namedRoutes);
}
}
function routeIsActive(activeRoutes, routeName) {
return activeRoutes.some(function (route) {
return route.name === routeName;
});
}
function paramsAreActive(activeParams, params) {
for (var property in params) if (String(activeParams[property]) !== String(params[property])) {
return false;
}return true;
}
function queryIsActive(activeQuery, query) {
for (var property in query) if (String(activeQuery[property]) !== String(query[property])) {
return false;
}return true;
}
/**
* Creates and returns a new router using the given options. A router
* is a ReactComponent class that knows how to react to changes in the
* URL and keep the contents of the page in sync.
*
* Options may be any of the following:
*
* - routes (required) The route config
* - location The location to use. Defaults to HashLocation when
* the DOM is available, "/" otherwise
* - scrollBehavior The scroll behavior to use. Defaults to ImitateBrowserBehavior
* when the DOM is available, null otherwise
* - onError A function that is used to handle errors
* - onAbort A function that is used to handle aborted transitions
*
* When rendering in a server-side environment, the location should simply
* be the URL path that was used in the request, including the query string.
*/
function createRouter(options) {
options = options || {};
if (isReactChildren(options)) options = { routes: options };
var mountedComponents = [];
var location = options.location || DEFAULT_LOCATION;
var scrollBehavior = options.scrollBehavior || DEFAULT_SCROLL_BEHAVIOR;
var state = {};
var nextState = {};
var pendingTransition = null;
var dispatchHandler = null;
if (typeof location === 'string') location = new StaticLocation(location);
if (location instanceof StaticLocation) {
warning(!canUseDOM || process.env.NODE_ENV === 'test', 'You should not use a static location in a DOM environment because ' + 'the router will not be kept in sync with the current URL');
} else {
invariant(canUseDOM || location.needsDOM === false, 'You cannot use %s without a DOM', location);
}
// Automatically fall back to full page refreshes in
// browsers that don't support the HTML history API.
if (location === HistoryLocation && !supportsHistory()) location = RefreshLocation;
var Router = React.createClass({
displayName: 'Router',
statics: {
isRunning: false,
cancelPendingTransition: function cancelPendingTransition() {
if (pendingTransition) {
pendingTransition.cancel();
pendingTransition = null;
}
},
clearAllRoutes: function clearAllRoutes() {
Router.cancelPendingTransition();
Router.namedRoutes = {};
Router.routes = [];
},
/**
* Adds routes to this router from the given children object (see ReactChildren).
*/
addRoutes: function addRoutes(routes) {
if (isReactChildren(routes)) routes = createRoutesFromReactChildren(routes);
addRoutesToNamedRoutes(routes, Router.namedRoutes);
Router.routes.push.apply(Router.routes, routes);
},
/**
* Replaces routes of this router from the given children object (see ReactChildren).
*/
replaceRoutes: function replaceRoutes(routes) {
Router.clearAllRoutes();
Router.addRoutes(routes);
Router.refresh();
},
/**
* Performs a match of the given path against this router and returns an object
* with the { routes, params, pathname, query } that match. Returns null if no
* match can be made.
*/
match: function match(path) {
return Match.findMatch(Router.routes, path);
},
/**
* Returns an absolute URL path created from the given route
* name, URL parameters, and query.
*/
makePath: function makePath(to, params, query) {
var path;
if (PathUtils.isAbsolute(to)) {
path = to;
} else {
var route = to instanceof Route ? to : Router.namedRoutes[to];
invariant(route instanceof Route, 'Cannot find a route named "%s"', to);
path = route.path;
}
return PathUtils.withQuery(PathUtils.injectParams(path, params), query);
},
/**
* Returns a string that may safely be used as the href of a link
* to the route with the given name, URL parameters, and query.
*/
makeHref: function makeHref(to, params, query) {
var path = Router.makePath(to, params, query);
return location === HashLocation ? '#' + path : path;
},
/**
* Transitions to the URL specified in the arguments by pushing
* a new URL onto the history stack.
*/
transitionTo: function transitionTo(to, params, query) {
var path = Router.makePath(to, params, query);
if (pendingTransition) {
// Replace so pending location does not stay in history.
location.replace(path);
} else {
location.push(path);
}
},
/**
* Transitions to the URL specified in the arguments by replacing
* the current URL in the history stack.
*/
replaceWith: function replaceWith(to, params, query) {
location.replace(Router.makePath(to, params, query));
},
/**
* Transitions to the previous URL if one is available. Returns true if the
* router was able to go back, false otherwise.
*
* Note: The router only tracks history entries in your application, not the
* current browser session, so you can safely call this function without guarding
* against sending the user back to some other site. However, when using
* RefreshLocation (which is the fallback for HistoryLocation in browsers that
* don't support HTML5 history) this method will *always* send the client back
* because we cannot reliably track history length.
*/
goBack: function goBack() {
if (History.length > 1 || location === RefreshLocation) {
location.pop();
return true;
}
warning(false, 'goBack() was ignored because there is no router history');
return false;
},
handleAbort: options.onAbort || function (abortReason) {
if (location instanceof StaticLocation) throw new Error('Unhandled aborted transition! Reason: ' + abortReason);
if (abortReason instanceof Cancellation) {
return;
} else if (abortReason instanceof Redirect) {
location.replace(Router.makePath(abortReason.to, abortReason.params, abortReason.query));
} else {
location.pop();
}
},
handleError: options.onError || function (error) {
// Throw so we don't silently swallow async errors.
throw error; // This error probably originated in a transition hook.
},
handleLocationChange: function handleLocationChange(change) {
Router.dispatch(change.path, change.type);
},
/**
* Performs a transition to the given path and calls callback(error, abortReason)
* when the transition is finished. If both arguments are null the router's state
* was updated. Otherwise the transition did not complete.
*
* In a transition, a router first determines which routes are involved by beginning
* with the current route, up the route tree to the first parent route that is shared
* with the destination route, and back down the tree to the destination route. The
* willTransitionFrom hook is invoked on all route handlers we're transitioning away
* from, in reverse nesting order. Likewise, the willTransitionTo hook is invoked on
* all route handlers we're transitioning to.
*
* Both willTransitionFrom and willTransitionTo hooks may either abort or redirect the
* transition. To resolve asynchronously, they may use the callback argument. If no
* hooks wait, the transition is fully synchronous.
*/
dispatch: function dispatch(path, action) {
Router.cancelPendingTransition();
var prevPath = state.path;
var isRefreshing = action == null;
if (prevPath === path && !isRefreshing) {
return;
} // Nothing to do!
// Record the scroll position as early as possible to
// get it before browsers try update it automatically.
if (prevPath && action === LocationActions.PUSH) Router.recordScrollPosition(prevPath);
var match = Router.match(path);
warning(match != null, 'No route matches path "%s". Make sure you have <Route path="%s"> somewhere in your routes', path, path);
if (match == null) match = {};
var prevRoutes = state.routes || [];
var prevParams = state.params || {};
var prevQuery = state.query || {};
var nextRoutes = match.routes || [];
var nextParams = match.params || {};
var nextQuery = match.query || {};
var fromRoutes, toRoutes;
if (prevRoutes.length) {
fromRoutes = prevRoutes.filter(function (route) {
return !hasMatch(nextRoutes, route, prevParams, nextParams, prevQuery, nextQuery);
});
toRoutes = nextRoutes.filter(function (route) {
return !hasMatch(prevRoutes, route, prevParams, nextParams, prevQuery, nextQuery);
});
} else {
fromRoutes = [];
toRoutes = nextRoutes;
}
var transition = new Transition(path, Router.replaceWith.bind(Router, path));
pendingTransition = transition;
var fromComponents = mountedComponents.slice(prevRoutes.length - fromRoutes.length);
Transition.from(transition, fromRoutes, fromComponents, function (error) {
if (error || transition.abortReason) return dispatchHandler.call(Router, error, transition); // No need to continue.
Transition.to(transition, toRoutes, nextParams, nextQuery, function (error) {
dispatchHandler.call(Router, error, transition, {
path: path,
action: action,
pathname: match.pathname,
routes: nextRoutes,
params: nextParams,
query: nextQuery
});
});
});
},
/**
* Starts this router and calls callback(router, state) when the route changes.
*
* If the router's location is static (i.e. a URL path in a server environment)
* the callback is called only once. Otherwise, the location should be one of the
* Router.*Location objects (e.g. Router.HashLocation or Router.HistoryLocation).
*/
run: function run(callback) {
invariant(!Router.isRunning, 'Router is already running');
dispatchHandler = function (error, transition, newState) {
if (error) Router.handleError(error);
if (pendingTransition !== transition) return;
pendingTransition = null;
if (transition.abortReason) {
Router.handleAbort(transition.abortReason);
} else {
callback.call(Router, Router, nextState = newState);
}
};
if (!(location instanceof StaticLocation)) {
if (location.addChangeListener) location.addChangeListener(Router.handleLocationChange);
Router.isRunning = true;
}
// Bootstrap using the current path.
Router.refresh();
},
refresh: function refresh() {
Router.dispatch(location.getCurrentPath(), null);
},
stop: function stop() {
Router.cancelPendingTransition();
if (location.removeChangeListener) location.removeChangeListener(Router.handleLocationChange);
Router.isRunning = false;
},
getLocation: function getLocation() {
return location;
},
getScrollBehavior: function getScrollBehavior() {
return scrollBehavior;
},
getRouteAtDepth: function getRouteAtDepth(routeDepth) {
var routes = state.routes;
return routes && routes[routeDepth];
},
setRouteComponentAtDepth: function setRouteComponentAtDepth(routeDepth, component) {
mountedComponents[routeDepth] = component;
},
/**
* Returns the current URL path + query string.
*/
getCurrentPath: function getCurrentPath() {
return state.path;
},
/**
* Returns the current URL path without the query string.
*/
getCurrentPathname: function getCurrentPathname() {
return state.pathname;
},
/**
* Returns an object of the currently active URL parameters.
*/
getCurrentParams: function getCurrentParams() {
return state.params;
},
/**
* Returns an object of the currently active query parameters.
*/
getCurrentQuery: function getCurrentQuery() {
return state.query;
},
/**
* Returns an array of the currently active routes.
*/
getCurrentRoutes: function getCurrentRoutes() {
return state.routes;
},
/**
* Returns true if the given route, params, and query are active.
*/
isActive: function isActive(to, params, query) {
if (PathUtils.isAbsolute(to)) {
return to === state.path;
}return routeIsActive(state.routes, to) && paramsAreActive(state.params, params) && (query == null || queryIsActive(state.query, query));
}
},
mixins: [ScrollHistory],
propTypes: {
children: PropTypes.falsy
},
childContextTypes: {
routeDepth: PropTypes.number.isRequired,
router: PropTypes.router.isRequired
},
getChildContext: function getChildContext() {
return {
routeDepth: 1,
router: Router
};
},
getInitialState: function getInitialState() {
return state = nextState;
},
componentWillReceiveProps: function componentWillReceiveProps() {
this.setState(state = nextState);
},
componentWillUnmount: function componentWillUnmount() {
Router.stop();
},
render: function render() {
var route = Router.getRouteAtDepth(0);
return route ? React.createElement(route.handler, this.props) : null;
}
});
Router.clearAllRoutes();
if (options.routes) Router.addRoutes(options.routes);
return Router;
}
module.exports = createRouter;

View File

@@ -1,81 +0,0 @@
/* jshint -W084 */
'use strict';
var React = require('react');
var assign = require('react/lib/Object.assign');
var warning = require('react/lib/warning');
var DefaultRoute = require('./components/DefaultRoute');
var NotFoundRoute = require('./components/NotFoundRoute');
var Redirect = require('./components/Redirect');
var Route = require('./Route');
function checkPropTypes(componentName, propTypes, props) {
componentName = componentName || 'UnknownComponent';
for (var propName in propTypes) {
if (propTypes.hasOwnProperty(propName)) {
var error = propTypes[propName](props, propName, componentName);
if (error instanceof Error) warning(false, error.message);
}
}
}
function createRouteOptions(props) {
var options = assign({}, props);
var handler = options.handler;
if (handler) {
options.onEnter = handler.willTransitionTo;
options.onLeave = handler.willTransitionFrom;
}
return options;
}
function createRouteFromReactElement(element) {
if (!React.isValidElement(element)) {
return;
}var type = element.type;
var props = assign({}, type.defaultProps, element.props);
if (type.propTypes) checkPropTypes(type.displayName, type.propTypes, props);
if (type === DefaultRoute) {
return Route.createDefaultRoute(createRouteOptions(props));
}if (type === NotFoundRoute) {
return Route.createNotFoundRoute(createRouteOptions(props));
}if (type === Redirect) {
return Route.createRedirect(createRouteOptions(props));
}return Route.createRoute(createRouteOptions(props), function () {
if (props.children) createRoutesFromReactChildren(props.children);
});
}
/**
* Creates and returns an array of routes created from the given
* ReactChildren, all of which should be one of <Route>, <DefaultRoute>,
* <NotFoundRoute>, or <Redirect>, e.g.:
*
* var { createRoutesFromReactChildren, Route, Redirect } = require('react-router');
*
* var routes = createRoutesFromReactChildren(
* <Route path="/" handler={App}>
* <Route name="user" path="/user/:userId" handler={User}>
* <Route name="task" path="tasks/:taskId" handler={Task}/>
* <Redirect from="todos/:taskId" to="task"/>
* </Route>
* </Route>
* );
*/
function createRoutesFromReactChildren(children) {
var routes = [];
React.Children.forEach(children, function (child) {
if (child = createRouteFromReactElement(child)) routes.push(child);
});
return routes;
}
module.exports = createRoutesFromReactChildren;

View File

@@ -1,18 +0,0 @@
'use strict';
var invariant = require('react/lib/invariant');
var canUseDOM = require('react/lib/ExecutionEnvironment').canUseDOM;
/**
* Returns the current scroll position of the window as { x, y }.
*/
function getWindowScrollPosition() {
invariant(canUseDOM, 'Cannot get current scroll position without a DOM');
return {
x: window.pageXOffset || document.documentElement.scrollLeft,
y: window.pageYOffset || document.documentElement.scrollTop
};
}
module.exports = getWindowScrollPosition;

View File

@@ -1,31 +0,0 @@
'use strict';
exports.DefaultRoute = require('./components/DefaultRoute');
exports.Link = require('./components/Link');
exports.NotFoundRoute = require('./components/NotFoundRoute');
exports.Redirect = require('./components/Redirect');
exports.Route = require('./components/Route');
exports.ActiveHandler = require('./components/RouteHandler');
exports.RouteHandler = exports.ActiveHandler;
exports.HashLocation = require('./locations/HashLocation');
exports.HistoryLocation = require('./locations/HistoryLocation');
exports.RefreshLocation = require('./locations/RefreshLocation');
exports.StaticLocation = require('./locations/StaticLocation');
exports.TestLocation = require('./locations/TestLocation');
exports.ImitateBrowserBehavior = require('./behaviors/ImitateBrowserBehavior');
exports.ScrollToTopBehavior = require('./behaviors/ScrollToTopBehavior');
exports.History = require('./History');
exports.Navigation = require('./Navigation');
exports.State = require('./State');
exports.createRoute = require('./Route').createRoute;
exports.createDefaultRoute = require('./Route').createDefaultRoute;
exports.createNotFoundRoute = require('./Route').createNotFoundRoute;
exports.createRedirect = require('./Route').createRedirect;
exports.createRoutesFromReactChildren = require('./createRoutesFromReactChildren');
exports.create = require('./createRouter');
exports.run = require('./runRouter');

View File

@@ -1,13 +0,0 @@
'use strict';
var React = require('react');
function isValidChild(object) {
return object == null || React.isValidElement(object);
}
function isReactChildren(object) {
return isValidChild(object) || Array.isArray(object) && object.every(isValidChild);
}
module.exports = isReactChildren;

View File

@@ -1,111 +0,0 @@
'use strict';
var LocationActions = require('../actions/LocationActions');
var History = require('../History');
var _listeners = [];
var _isListening = false;
var _actionType;
function notifyChange(type) {
if (type === LocationActions.PUSH) History.length += 1;
var change = {
path: HashLocation.getCurrentPath(),
type: type
};
_listeners.forEach(function (listener) {
listener.call(HashLocation, change);
});
}
function ensureSlash() {
var path = HashLocation.getCurrentPath();
if (path.charAt(0) === '/') {
return true;
}HashLocation.replace('/' + path);
return false;
}
function onHashChange() {
if (ensureSlash()) {
// If we don't have an _actionType then all we know is the hash
// changed. It was probably caused by the user clicking the Back
// button, but may have also been the Forward button or manual
// manipulation. So just guess 'pop'.
var curActionType = _actionType;
_actionType = null;
notifyChange(curActionType || LocationActions.POP);
}
}
/**
* A Location that uses `window.location.hash`.
*/
var HashLocation = {
addChangeListener: function addChangeListener(listener) {
_listeners.push(listener);
// Do this BEFORE listening for hashchange.
ensureSlash();
if (!_isListening) {
if (window.addEventListener) {
window.addEventListener('hashchange', onHashChange, false);
} else {
window.attachEvent('onhashchange', onHashChange);
}
_isListening = true;
}
},
removeChangeListener: function removeChangeListener(listener) {
_listeners = _listeners.filter(function (l) {
return l !== listener;
});
if (_listeners.length === 0) {
if (window.removeEventListener) {
window.removeEventListener('hashchange', onHashChange, false);
} else {
window.removeEvent('onhashchange', onHashChange);
}
_isListening = false;
}
},
push: function push(path) {
_actionType = LocationActions.PUSH;
window.location.hash = path;
},
replace: function replace(path) {
_actionType = LocationActions.REPLACE;
window.location.replace(window.location.pathname + window.location.search + '#' + path);
},
pop: function pop() {
_actionType = LocationActions.POP;
History.back();
},
getCurrentPath: function getCurrentPath() {
return decodeURI(
// We can't use window.location.hash here because it's not
// consistent across browsers - Firefox will pre-decode it!
window.location.href.split('#')[1] || '');
},
toString: function toString() {
return '<HashLocation>';
}
};
module.exports = HashLocation;

View File

@@ -1,86 +0,0 @@
'use strict';
var LocationActions = require('../actions/LocationActions');
var History = require('../History');
var _listeners = [];
var _isListening = false;
function notifyChange(type) {
var change = {
path: HistoryLocation.getCurrentPath(),
type: type
};
_listeners.forEach(function (listener) {
listener.call(HistoryLocation, change);
});
}
function onPopState(event) {
if (event.state === undefined) {
return;
} // Ignore extraneous popstate events in WebKit.
notifyChange(LocationActions.POP);
}
/**
* A Location that uses HTML5 history.
*/
var HistoryLocation = {
addChangeListener: function addChangeListener(listener) {
_listeners.push(listener);
if (!_isListening) {
if (window.addEventListener) {
window.addEventListener('popstate', onPopState, false);
} else {
window.attachEvent('onpopstate', onPopState);
}
_isListening = true;
}
},
removeChangeListener: function removeChangeListener(listener) {
_listeners = _listeners.filter(function (l) {
return l !== listener;
});
if (_listeners.length === 0) {
if (window.addEventListener) {
window.removeEventListener('popstate', onPopState, false);
} else {
window.removeEvent('onpopstate', onPopState);
}
_isListening = false;
}
},
push: function push(path) {
window.history.pushState({ path: path }, '', path);
History.length += 1;
notifyChange(LocationActions.PUSH);
},
replace: function replace(path) {
window.history.replaceState({ path: path }, '', path);
notifyChange(LocationActions.REPLACE);
},
pop: History.back,
getCurrentPath: function getCurrentPath() {
return decodeURI(window.location.pathname + window.location.search);
},
toString: function toString() {
return '<HistoryLocation>';
}
};
module.exports = HistoryLocation;

View File

@@ -1,31 +0,0 @@
'use strict';
var HistoryLocation = require('./HistoryLocation');
var History = require('../History');
/**
* A Location that uses full page refreshes. This is used as
* the fallback for HistoryLocation in browsers that do not
* support the HTML5 history API.
*/
var RefreshLocation = {
push: function push(path) {
window.location = path;
},
replace: function replace(path) {
window.location.replace(path);
},
pop: History.back,
getCurrentPath: HistoryLocation.getCurrentPath,
toString: function toString() {
return '<RefreshLocation>';
}
};
module.exports = RefreshLocation;

View File

@@ -1,49 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var invariant = require('react/lib/invariant');
function throwCannotModify() {
invariant(false, 'You cannot modify a static location');
}
/**
* A location that only ever contains a single path. Useful in
* stateless environments like servers where there is no path history,
* only the path that was used in the request.
*/
var StaticLocation = (function () {
function StaticLocation(path) {
_classCallCheck(this, StaticLocation);
this.path = path;
}
_createClass(StaticLocation, [{
key: 'getCurrentPath',
value: function getCurrentPath() {
return this.path;
}
}, {
key: 'toString',
value: function toString() {
return '<StaticLocation path="' + this.path + '">';
}
}]);
return StaticLocation;
})();
// TODO: Include these in the above class definition
// once we can use ES7 property initializers.
// https://github.com/babel/babel/issues/619
StaticLocation.prototype.push = throwCannotModify;
StaticLocation.prototype.replace = throwCannotModify;
StaticLocation.prototype.pop = throwCannotModify;
module.exports = StaticLocation;

View File

@@ -1,94 +0,0 @@
'use strict';
var _classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
var invariant = require('react/lib/invariant');
var LocationActions = require('../actions/LocationActions');
var History = require('../History');
/**
* A location that is convenient for testing and does not require a DOM.
*/
var TestLocation = (function () {
function TestLocation(history) {
_classCallCheck(this, TestLocation);
this.history = history || [];
this.listeners = [];
this._updateHistoryLength();
}
_createClass(TestLocation, [{
key: 'needsDOM',
get: function () {
return false;
}
}, {
key: '_updateHistoryLength',
value: function _updateHistoryLength() {
History.length = this.history.length;
}
}, {
key: '_notifyChange',
value: function _notifyChange(type) {
var change = {
path: this.getCurrentPath(),
type: type
};
for (var i = 0, len = this.listeners.length; i < len; ++i) this.listeners[i].call(this, change);
}
}, {
key: 'addChangeListener',
value: function addChangeListener(listener) {
this.listeners.push(listener);
}
}, {
key: 'removeChangeListener',
value: function removeChangeListener(listener) {
this.listeners = this.listeners.filter(function (l) {
return l !== listener;
});
}
}, {
key: 'push',
value: function push(path) {
this.history.push(path);
this._updateHistoryLength();
this._notifyChange(LocationActions.PUSH);
}
}, {
key: 'replace',
value: function replace(path) {
invariant(this.history.length, 'You cannot replace the current path with no history');
this.history[this.history.length - 1] = path;
this._notifyChange(LocationActions.REPLACE);
}
}, {
key: 'pop',
value: function pop() {
this.history.pop();
this._updateHistoryLength();
this._notifyChange(LocationActions.POP);
}
}, {
key: 'getCurrentPath',
value: function getCurrentPath() {
return this.history[this.history.length - 1];
}
}, {
key: 'toString',
value: function toString() {
return '<TestLocation>';
}
}]);
return TestLocation;
})();
module.exports = TestLocation;

View File

@@ -1,50 +0,0 @@
'use strict';
var createRouter = require('./createRouter');
/**
* A high-level convenience method that creates, configures, and
* runs a router in one shot. The method signature is:
*
* Router.run(routes[, location ], callback);
*
* Using `window.location.hash` to manage the URL, you could do:
*
* Router.run(routes, function (Handler) {
* React.render(<Handler/>, document.body);
* });
*
* Using HTML5 history and a custom "cursor" prop:
*
* Router.run(routes, Router.HistoryLocation, function (Handler) {
* React.render(<Handler cursor={cursor}/>, document.body);
* });
*
* Returns the newly created router.
*
* Note: If you need to specify further options for your router such
* as error/abort handling or custom scroll behavior, use Router.create
* instead.
*
* var router = Router.create(options);
* router.run(function (Handler) {
* // ...
* });
*/
function runRouter(routes, location, callback) {
if (typeof location === 'function') {
callback = location;
location = null;
}
var router = createRouter({
routes: routes,
location: location
});
router.run(callback);
return router;
}
module.exports = runRouter;

View File

@@ -1,16 +0,0 @@
'use strict';
function supportsHistory() {
/*! taken from modernizr
* https://github.com/Modernizr/Modernizr/blob/master/LICENSE
* https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js
* changed to avoid false negatives for Windows Phones: https://github.com/rackt/react-router/issues/586
*/
var ua = navigator.userAgent;
if ((ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && ua.indexOf('Mobile Safari') !== -1 && ua.indexOf('Chrome') === -1 && ua.indexOf('Windows Phone') === -1) {
return false;
}
return window.history && 'pushState' in window.history;
}
module.exports = supportsHistory;

View File

@@ -1,26 +0,0 @@
'use strict';
function ToObject(val) {
if (val == null) {
throw new TypeError('Object.assign cannot be called with null or undefined');
}
return Object(val);
}
module.exports = Object.assign || function (target, source) {
var from;
var keys;
var to = ToObject(target);
for (var s = 1; s < arguments.length; s++) {
from = arguments[s];
keys = Object.keys(Object(from));
for (var i = 0; i < keys.length; i++) {
to[keys[i]] = from[keys[i]];
}
}
return to;
};

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,68 +0,0 @@
{
"name": "object-assign",
"version": "2.1.1",
"description": "ES6 Object.assign() ponyfill",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/sindresorhus/object-assign.git"
},
"author": {
"name": "Sindre Sorhus",
"email": "sindresorhus@gmail.com",
"url": "http://sindresorhus.com"
},
"engines": {
"node": ">=0.10.0"
},
"scripts": {
"test": "mocha"
},
"files": [
"index.js"
],
"keywords": [
"object",
"assign",
"extend",
"properties",
"es6",
"ecmascript",
"harmony",
"ponyfill",
"prollyfill",
"polyfill",
"shim",
"browser"
],
"devDependencies": {
"mocha": "*"
},
"gitHead": "4cd0365f5a78dd369473ca0bbd31f7b234148c42",
"bugs": {
"url": "https://github.com/sindresorhus/object-assign/issues"
},
"homepage": "https://github.com/sindresorhus/object-assign",
"_id": "object-assign@2.1.1",
"_shasum": "43c36e5d569ff8e4816c4efa8be02d26967c18aa",
"_from": "object-assign@>=2.0.0 <3.0.0",
"_npmVersion": "2.10.1",
"_nodeVersion": "0.12.4",
"_npmUser": {
"name": "sindresorhus",
"email": "sindresorhus@gmail.com"
},
"dist": {
"shasum": "43c36e5d569ff8e4816c4efa8be02d26967c18aa",
"tarball": "http://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz"
},
"maintainers": [
{
"name": "sindresorhus",
"email": "sindresorhus@gmail.com"
}
],
"directories": {},
"_resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz",
"readme": "ERROR: No README data found!"
}

View File

@@ -1,51 +0,0 @@
# object-assign [![Build Status](https://travis-ci.org/sindresorhus/object-assign.svg?branch=master)](https://travis-ci.org/sindresorhus/object-assign)
> ES6 [`Object.assign()`](http://www.2ality.com/2014/01/object-assign.html) ponyfill
> Ponyfill: A polyfill that doesn't overwrite the native method
## Install
```sh
$ npm install --save object-assign
```
## Usage
```js
var objectAssign = require('object-assign');
objectAssign({foo: 0}, {bar: 1});
//=> {foo: 0, bar: 1}
// multiple sources
objectAssign({foo: 0}, {bar: 1}, {baz: 2});
//=> {foo: 0, bar: 1, baz: 2}
// overwrites equal keys
objectAssign({foo: 0}, {foo: 1}, {foo: 2});
//=> {foo: 2}
// ignores null and undefined sources
objectAssign({foo: 0}, null, {bar: 1}, undefined);
//=> {foo: 0, bar: 1}
```
## API
### objectAssign(target, source, [source, ...])
Assigns enumerable own properties of `source` objects to the `target` object and returns the `target` object. Additional `source` objects will overwrite previous ones.
## Resources
- [ES6 spec - Object.assign](https://people.mozilla.org/~jorendorff/es6-draft.html#sec-object.assign)
## License
MIT © [Sindre Sorhus](http://sindresorhus.com)

View File

@@ -1 +0,0 @@
node_modules

View File

@@ -1,10 +0,0 @@
{
"node": true,
"curly": true,
"latedef": true,
"quotmark": true,
"undef": true,
"unused": true,
"trailing": true
}

View File

@@ -1,18 +0,0 @@
.idea
*.iml
npm-debug.log
dump.rdb
node_modules
results.tap
results.xml
npm-shrinkwrap.json
config.json
.DS_Store
*/.DS_Store
*/*/.DS_Store
._*
*/._*
*/*/._*
coverage.*
lib-cov
complexity.md

View File

@@ -1,6 +0,0 @@
language: node_js
node_js:
- 0.10
- 0.12
- iojs

View File

@@ -1,68 +0,0 @@
## [**2.3.3**](https://github.com/hapijs/qs/issues?milestone=18&state=open)
- [**#59**](https://github.com/hapijs/qs/issues/59) make sure array indexes are &gt;= 0, closes #57
- [**#58**](https://github.com/hapijs/qs/issues/58) make qs usable for browser loader
## [**2.3.2**](https://github.com/hapijs/qs/issues?milestone=17&state=closed)
- [**#55**](https://github.com/hapijs/qs/issues/55) allow merging a string into an object
## [**2.3.1**](https://github.com/hapijs/qs/issues?milestone=16&state=closed)
- [**#52**](https://github.com/hapijs/qs/issues/52) Return &quot;undefined&quot; and &quot;false&quot; instead of throwing &quot;TypeError&quot;.
## [**2.3.0**](https://github.com/hapijs/qs/issues?milestone=15&state=closed)
- [**#50**](https://github.com/hapijs/qs/issues/50) add option to omit array indices, closes #46
## [**2.2.5**](https://github.com/hapijs/qs/issues?milestone=14&state=closed)
- [**#39**](https://github.com/hapijs/qs/issues/39) Is there an alternative to Buffer.isBuffer?
- [**#49**](https://github.com/hapijs/qs/issues/49) refactor utils.merge, fixes #45
- [**#41**](https://github.com/hapijs/qs/issues/41) avoid browserifying Buffer, for #39
## [**2.2.4**](https://github.com/hapijs/qs/issues?milestone=13&state=closed)
- [**#38**](https://github.com/hapijs/qs/issues/38) how to handle object keys beginning with a number
## [**2.2.3**](https://github.com/hapijs/qs/issues?milestone=12&state=closed)
- [**#37**](https://github.com/hapijs/qs/issues/37) parser discards first empty value in array
- [**#36**](https://github.com/hapijs/qs/issues/36) Update to lab 4.x
## [**2.2.2**](https://github.com/hapijs/qs/issues?milestone=11&state=closed)
- [**#33**](https://github.com/hapijs/qs/issues/33) Error when plain object in a value
- [**#34**](https://github.com/hapijs/qs/issues/34) use Object.prototype.hasOwnProperty.call instead of obj.hasOwnProperty
- [**#24**](https://github.com/hapijs/qs/issues/24) Changelog? Semver?
## [**2.2.1**](https://github.com/hapijs/qs/issues?milestone=10&state=closed)
- [**#32**](https://github.com/hapijs/qs/issues/32) account for circular references properly, closes #31
- [**#31**](https://github.com/hapijs/qs/issues/31) qs.parse stackoverflow on circular objects
## [**2.2.0**](https://github.com/hapijs/qs/issues?milestone=9&state=closed)
- [**#26**](https://github.com/hapijs/qs/issues/26) Don&#39;t use Buffer global if it&#39;s not present
- [**#30**](https://github.com/hapijs/qs/issues/30) Bug when merging non-object values into arrays
- [**#29**](https://github.com/hapijs/qs/issues/29) Don&#39;t call Utils.clone at the top of Utils.merge
- [**#23**](https://github.com/hapijs/qs/issues/23) Ability to not limit parameters?
## [**2.1.0**](https://github.com/hapijs/qs/issues?milestone=8&state=closed)
- [**#22**](https://github.com/hapijs/qs/issues/22) Enable using a RegExp as delimiter
## [**2.0.0**](https://github.com/hapijs/qs/issues?milestone=7&state=closed)
- [**#18**](https://github.com/hapijs/qs/issues/18) Why is there arrayLimit?
- [**#20**](https://github.com/hapijs/qs/issues/20) Configurable parametersLimit
- [**#21**](https://github.com/hapijs/qs/issues/21) make all limits optional, for #18, for #20
## [**1.2.2**](https://github.com/hapijs/qs/issues?milestone=6&state=closed)
- [**#19**](https://github.com/hapijs/qs/issues/19) Don&#39;t overwrite null values
## [**1.2.1**](https://github.com/hapijs/qs/issues?milestone=5&state=closed)
- [**#16**](https://github.com/hapijs/qs/issues/16) ignore non-string delimiters
- [**#15**](https://github.com/hapijs/qs/issues/15) Close code block
## [**1.2.0**](https://github.com/hapijs/qs/issues?milestone=4&state=closed)
- [**#12**](https://github.com/hapijs/qs/issues/12) Add optional delim argument
- [**#13**](https://github.com/hapijs/qs/issues/13) fix #11: flattened keys in array are now correctly parsed
## [**1.1.0**](https://github.com/hapijs/qs/issues?milestone=3&state=closed)
- [**#7**](https://github.com/hapijs/qs/issues/7) Empty values of a POST array disappear after being submitted
- [**#9**](https://github.com/hapijs/qs/issues/9) Should not omit equals signs (=) when value is null
- [**#6**](https://github.com/hapijs/qs/issues/6) Minor grammar fix in README
## [**1.0.2**](https://github.com/hapijs/qs/issues?milestone=2&state=closed)
- [**#5**](https://github.com/hapijs/qs/issues/5) array holes incorrectly copied into object on large index

View File

@@ -1 +0,0 @@
Please view our [hapijs contributing guide](https://github.com/hapijs/hapi/blob/master/CONTRIBUTING.md).

View File

@@ -1,28 +0,0 @@
Copyright (c) 2014 Nathan LaFreniere and other contributors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* The names of any contributors may not be used to endorse or promote
products derived from this software without specific prior written
permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* * *
The complete list of contributors can be found at: https://github.com/hapijs/qs/graphs/contributors

View File

@@ -1,8 +0,0 @@
test:
@node node_modules/lab/bin/lab -a code -L
test-cov:
@node node_modules/lab/bin/lab -a code -t 100 -L
test-cov-html:
@node node_modules/lab/bin/lab -a code -L -r html -o coverage.html
.PHONY: test test-cov test-cov-html

View File

@@ -1,233 +0,0 @@
# qs
A querystring parsing and stringifying library with some added security.
[![Build Status](https://secure.travis-ci.org/hapijs/qs.svg)](http://travis-ci.org/hapijs/qs)
Lead Maintainer: [Nathan LaFreniere](https://github.com/nlf)
The **qs** module was originally created and maintained by [TJ Holowaychuk](https://github.com/visionmedia/node-querystring).
## Usage
```javascript
var Qs = require('qs');
var obj = Qs.parse('a=c'); // { a: 'c' }
var str = Qs.stringify(obj); // 'a=c'
```
### Parsing Objects
```javascript
Qs.parse(string, [options]);
```
**qs** allows you to create nested objects within your query strings, by surrounding the name of sub-keys with square brackets `[]`.
For example, the string `'foo[bar]=baz'` converts to:
```javascript
{
foo: {
bar: 'baz'
}
}
```
URI encoded strings work too:
```javascript
Qs.parse('a%5Bb%5D=c');
// { a: { b: 'c' } }
```
You can also nest your objects, like `'foo[bar][baz]=foobarbaz'`:
```javascript
{
foo: {
bar: {
baz: 'foobarbaz'
}
}
}
```
By default, when nesting objects **qs** will only parse up to 5 children deep. This means if you attempt to parse a string like
`'a[b][c][d][e][f][g][h][i]=j'` your resulting object will be:
```javascript
{
a: {
b: {
c: {
d: {
e: {
f: {
'[g][h][i]': 'j'
}
}
}
}
}
}
}
```
This depth can be overridden by passing a `depth` option to `Qs.parse(string, [options])`:
```javascript
Qs.parse('a[b][c][d][e][f][g][h][i]=j', { depth: 1 });
// { a: { b: { '[c][d][e][f][g][h][i]': 'j' } } }
```
The depth limit helps mitigate abuse when **qs** is used to parse user input, and it is recommended to keep it a reasonably small number.
For similar reasons, by default **qs** will only parse up to 1000 parameters. This can be overridden by passing a `parameterLimit` option:
```javascript
Qs.parse('a=b&c=d', { parameterLimit: 1 });
// { a: 'b' }
```
An optional delimiter can also be passed:
```javascript
Qs.parse('a=b;c=d', { delimiter: ';' });
// { a: 'b', c: 'd' }
```
Delimiters can be a regular expression too:
```javascript
Qs.parse('a=b;c=d,e=f', { delimiter: /[;,]/ });
// { a: 'b', c: 'd', e: 'f' }
```
### Parsing Arrays
**qs** can also parse arrays using a similar `[]` notation:
```javascript
Qs.parse('a[]=b&a[]=c');
// { a: ['b', 'c'] }
```
You may specify an index as well:
```javascript
Qs.parse('a[1]=c&a[0]=b');
// { a: ['b', 'c'] }
```
Note that the only difference between an index in an array and a key in an object is that the value between the brackets must be a number
to create an array. When creating arrays with specific indices, **qs** will compact a sparse array to only the existing values preserving
their order:
```javascript
Qs.parse('a[1]=b&a[15]=c');
// { a: ['b', 'c'] }
```
Note that an empty string is also a value, and will be preserved:
```javascript
Qs.parse('a[]=&a[]=b');
// { a: ['', 'b'] }
Qs.parse('a[0]=b&a[1]=&a[2]=c');
// { a: ['b', '', 'c'] }
```
**qs** will also limit specifying indices in an array to a maximum index of `20`. Any array members with an index of greater than `20` will
instead be converted to an object with the index as the key:
```javascript
Qs.parse('a[100]=b');
// { a: { '100': 'b' } }
```
This limit can be overridden by passing an `arrayLimit` option:
```javascript
Qs.parse('a[1]=b', { arrayLimit: 0 });
// { a: { '1': 'b' } }
```
To disable array parsing entirely, set `arrayLimit` to `-1`.
If you mix notations, **qs** will merge the two items into an object:
```javascript
Qs.parse('a[0]=b&a[b]=c');
// { a: { '0': 'b', b: 'c' } }
```
You can also create arrays of objects:
```javascript
Qs.parse('a[][b]=c');
// { a: [{ b: 'c' }] }
```
### Stringifying
```javascript
Qs.stringify(object, [options]);
```
When stringifying, **qs** always URI encodes output. Objects are stringified as you would expect:
```javascript
Qs.stringify({ a: 'b' });
// 'a=b'
Qs.stringify({ a: { b: 'c' } });
// 'a%5Bb%5D=c'
```
Examples beyond this point will be shown as though the output is not URI encoded for clarity. Please note that the return values in these cases *will* be URI encoded during real usage.
When arrays are stringified, by default they are given explicit indices:
```javascript
Qs.stringify({ a: ['b', 'c', 'd'] });
// 'a[0]=b&a[1]=c&a[2]=d'
```
You may override this by setting the `indices` option to `false`:
```javascript
Qs.stringify({ a: ['b', 'c', 'd'] }, { indices: false });
// 'a=b&a=c&a=d'
```
You may use the `arrayFormat` option to specify the format of the output array
```javascript
Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'indices' })
// 'a[0]=b&a[1]=c'
Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'brackets' })
// 'a[]=b&a[]=c'
Qs.stringify({ a: ['b', 'c'] }, { arrayFormat: 'repeat' })
// 'a=b&a=c'
```
Empty strings and null values will omit the value, but the equals sign (=) remains in place:
```javascript
Qs.stringify({ a: '' });
// 'a='
```
Properties that are set to `undefined` will be omitted entirely:
```javascript
Qs.stringify({ a: null, b: undefined });
// 'a='
```
The delimiter may be overridden with stringify as well:
```javascript
Qs.stringify({ a: 'b', c: 'd' }, { delimiter: ';' });
// 'a=b;c=d'
```

View File

@@ -1 +0,0 @@
module.exports = require('./lib/');

View File

@@ -1,15 +0,0 @@
// Load modules
var Stringify = require('./stringify');
var Parse = require('./parse');
// Declare internals
var internals = {};
module.exports = {
stringify: Stringify,
parse: Parse
};

Some files were not shown because too many files have changed in this diff Show More