pact_broker 2.40.0 → 2.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +36 -0
  3. data/DEVELOPER_DOCUMENTATION.md +11 -0
  4. data/db/ddl_statements/head_pact_tags.rb +10 -0
  5. data/db/ddl_statements/latest_pact_consumer_version_orders.rb +16 -0
  6. data/db/ddl_statements/latest_pact_publications_by_consumer_versions.rb +16 -0
  7. data/db/ddl_statements/latest_tagged_pact_consumer_version_orders.rb +23 -0
  8. data/db/ddl_statements/latest_verification_ids_for_consumer_version_tags.rb +15 -0
  9. data/db/ddl_statements/{latest_verification_ids_for_pact_versions_v001.rb → latest_verification_ids_for_pact_versions.rb} +0 -0
  10. data/db/migrations/000028_create_all_pact_publications.rb +1 -0
  11. data/db/migrations/20180311_optimise_head_matrix.rb +1 -9
  12. data/db/migrations/20180722_recreate_views.rb +6 -19
  13. data/db/migrations/20191028_optimise_latest_tagged_pact_cv_orders.rb +13 -0
  14. data/db/migrations/20191030_optimise_latest_pact_publications_by_consumer_versions_.rb +13 -0
  15. data/db/migrations/20191031_optimise_latest_verification_ids_for_consumer_version_tags.rb +13 -0
  16. data/db/migrations/20191101_create_head_pact_tags.rb +11 -0
  17. data/lib/db.rb +1 -0
  18. data/lib/pact_broker/api/contracts/dry_validation_predicates.rb +15 -0
  19. data/lib/pact_broker/api/contracts/dry_validation_workarounds.rb +38 -0
  20. data/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema.rb +34 -0
  21. data/lib/pact_broker/api/contracts/verifiable_pacts_query_schema.rb +12 -20
  22. data/lib/pact_broker/api/decorators/matrix_decorator.rb +25 -6
  23. data/lib/pact_broker/api/decorators/relationships_csv_decorator.rb +4 -12
  24. data/lib/pact_broker/api/decorators/verifiable_pact_decorator.rb +5 -2
  25. data/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator.rb +21 -9
  26. data/lib/pact_broker/api/renderers/integrations_dot_renderer.rb +1 -1
  27. data/lib/pact_broker/api/resources/base_resource.rb +1 -1
  28. data/lib/pact_broker/api/resources/can_i_deploy.rb +2 -9
  29. data/lib/pact_broker/api/resources/matrix.rb +2 -8
  30. data/lib/pact_broker/api/resources/matrix_for_consumer_and_provider.rb +1 -0
  31. data/lib/pact_broker/api/resources/provider_pacts_for_verification.rb +36 -7
  32. data/lib/pact_broker/app.rb +1 -0
  33. data/lib/pact_broker/db/data_migrations/migrate_webhook_headers.rb +1 -1
  34. data/lib/pact_broker/db/seed_example_data.rb +8 -5
  35. data/lib/pact_broker/diagnostic/resources/base_resource.rb +0 -8
  36. data/lib/pact_broker/domain/index_item.rb +47 -16
  37. data/lib/pact_broker/domain/pact.rb +12 -4
  38. data/lib/pact_broker/domain/version.rb +2 -0
  39. data/lib/pact_broker/hash_refinements.rb +48 -0
  40. data/lib/pact_broker/index/page.rb +12 -0
  41. data/lib/pact_broker/index/service.rb +156 -21
  42. data/lib/pact_broker/integrations/integration.rb +22 -0
  43. data/lib/pact_broker/locale/en.yml +2 -0
  44. data/lib/pact_broker/logging.rb +0 -1
  45. data/lib/pact_broker/matrix/aggregated_row.rb +3 -9
  46. data/lib/pact_broker/matrix/deployment_status_summary.rb +5 -5
  47. data/lib/pact_broker/matrix/head_row.rb +2 -0
  48. data/lib/pact_broker/matrix/quick_row.rb +7 -7
  49. data/lib/pact_broker/matrix/repository.rb +2 -4
  50. data/lib/pact_broker/pacticipants/repository.rb +1 -1
  51. data/lib/pact_broker/pacts/pact_publication.rb +27 -4
  52. data/lib/pact_broker/pacts/repository.rb +41 -18
  53. data/lib/pact_broker/pacts/service.rb +23 -3
  54. data/lib/pact_broker/pacts/verifiable_pact.rb +8 -2
  55. data/lib/pact_broker/pacts/verifiable_pact_messages.rb +14 -8
  56. data/lib/pact_broker/string_refinements.rb +36 -1
  57. data/lib/pact_broker/tags/head_pact_tags.rb +12 -0
  58. data/lib/pact_broker/tags/tag_with_latest_flag.rb +0 -1
  59. data/lib/pact_broker/test/test_data_builder.rb +1 -1
  60. data/lib/pact_broker/ui/controllers/index.rb +23 -2
  61. data/lib/pact_broker/ui/view_models/index_item.rb +7 -1
  62. data/lib/pact_broker/ui/view_models/index_items.rb +10 -8
  63. data/lib/pact_broker/ui/view_models/matrix_line.rb +14 -21
  64. data/lib/pact_broker/ui/view_models/matrix_tag.rb +6 -8
  65. data/lib/pact_broker/ui/views/index/_pagination.haml +31 -0
  66. data/lib/pact_broker/ui/views/index/show-with-tags.haml +6 -3
  67. data/lib/pact_broker/ui/views/index/show.haml +5 -2
  68. data/lib/pact_broker/ui/views/matrix/show.haml +6 -9
  69. data/lib/pact_broker/version.rb +1 -1
  70. data/lib/pact_broker/webhooks/webhook.rb +2 -2
  71. data/lib/pact_broker/webhooks/webhook_execution_result.rb +3 -18
  72. data/public/javascripts/matrix.js +26 -1
  73. data/public/javascripts/pagination.js +1127 -0
  74. data/public/stylesheets/index.css +13 -0
  75. data/public/stylesheets/matrix.css +10 -1
  76. data/spec/features/get_provider_pacts_for_verification_spec.rb +44 -9
  77. data/spec/features/pending_pacts_spec.rb +1 -1
  78. data/spec/features/wip_pacts_spec.rb +138 -0
  79. data/spec/integration/app_spec.rb +1 -1
  80. data/spec/lib/pact_broker/api/contracts/verifiable_pacts_json_query_schema_spec.rb +90 -0
  81. data/spec/lib/pact_broker/api/contracts/verifiable_pacts_query_schema_spec.rb +26 -4
  82. data/spec/lib/pact_broker/api/decorators/matrix_decorator_spec.rb +36 -2
  83. data/spec/lib/pact_broker/api/decorators/verifiable_pact_decorator_spec.rb +24 -1
  84. data/spec/lib/pact_broker/api/decorators/verifiable_pacts_query_decorator_spec.rb +62 -18
  85. data/spec/lib/pact_broker/api/resources/base_resource_spec.rb +10 -0
  86. data/spec/lib/pact_broker/api/resources/provider_pacts_for_verification_spec.rb +75 -6
  87. data/spec/lib/pact_broker/hash_refinements_spec.rb +24 -0
  88. data/spec/lib/pact_broker/index/service_spec.rb +38 -2
  89. data/spec/lib/pact_broker/integrations/integration_spec.rb +79 -41
  90. data/spec/lib/pact_broker/pacts/pact_publication_spec.rb +2 -0
  91. data/spec/lib/pact_broker/pacts/repository_find_wip_pact_versions_for_provider_spec.rb +17 -2
  92. data/spec/lib/pact_broker/pacts/service_spec.rb +56 -0
  93. data/spec/lib/pact_broker/pacts/verifiable_pact_messages_spec.rb +12 -1
  94. data/spec/lib/pact_broker/ui/controllers/index_spec.rb +28 -6
  95. data/spec/lib/pact_broker/ui/view_models/index_items_spec.rb +8 -29
  96. data/spec/lib/pact_broker/ui/view_models/matrix_line_spec.rb +41 -0
  97. metadata +24 -3
@@ -5,26 +5,28 @@ module PactBroker
5
5
  module ViewDomain
6
6
  class IndexItems
7
7
 
8
+ attr_reader :pagination_record_count
9
+
8
10
  def initialize index_items
11
+ # Why are we sorting twice!?
9
12
  @index_items = index_items.collect{ |index_item| IndexItem.new(index_item) }.sort
13
+ # until the feature flag is turned on
14
+ @pagination_record_count = index_items.size
15
+ @pagination_record_count = index_items.pagination_record_count if index_items.respond_to?(:pagination_record_count)
10
16
  end
11
17
 
12
18
  def each(&block)
13
19
  index_items.each(&block)
14
20
  end
15
21
 
16
- def size_label
17
- case index_items.size
18
- when 1 then "1 pact"
19
- else
20
- "#{index_items.size} pacts"
21
- end
22
- end
23
-
24
22
  def empty?
25
23
  index_items.empty?
26
24
  end
27
25
 
26
+ def size
27
+ index_items.size
28
+ end
29
+
28
30
  private
29
31
 
30
32
  attr_reader :index_items
@@ -3,13 +3,14 @@ require 'pact_broker/ui/helpers/url_helper'
3
3
  require 'pact_broker/date_helper'
4
4
  require 'pact_broker/ui/view_models/matrix_tag'
5
5
  require 'pact_broker/versions/abbreviate_number'
6
+ require 'pact_broker/messages'
6
7
 
7
8
  module PactBroker
8
9
  module UI
9
10
  module ViewDomain
10
11
  class MatrixLine
11
-
12
12
  include PactBroker::Api::PactBrokerUrls
13
+ include PactBroker::Messages
13
14
 
14
15
  def initialize line
15
16
  @line = line
@@ -36,6 +37,10 @@ module PactBroker
36
37
  @line.pact_version_sha
37
38
  end
38
39
 
40
+ def pact_version_sha_message
41
+ "The highlighted pact(s) have content that has a SHA of #{pact_version_sha}"
42
+ end
43
+
39
44
  # verification number, used in verification_url method
40
45
  def number
41
46
  @line.verification_number
@@ -45,10 +50,6 @@ module PactBroker
45
50
  @line.pact_revision_number
46
51
  end
47
52
 
48
- def consumer_name
49
- @line.consumer_name
50
- end
51
-
52
53
  def consumer_version_number
53
54
  @line.consumer_version_number
54
55
  end
@@ -66,10 +67,6 @@ module PactBroker
66
67
  @line.consumer_version_order
67
68
  end
68
69
 
69
- def provider_name
70
- @line.provider_name
71
- end
72
-
73
70
  def provider_version_number
74
71
  @line.provider_version_number
75
72
  end
@@ -78,10 +75,6 @@ module PactBroker
78
75
  PactBroker::Versions::AbbreviateNumber.call(provider_version_number)
79
76
  end
80
77
 
81
- def provider_version_order
82
- @line.provider_version_order
83
- end
84
-
85
78
  def provider_version_number_url
86
79
  params = { pacticipant_name: provider_name, version_number: provider_version_number }
87
80
  hal_browser_url(version_url_from_params(params))
@@ -97,25 +90,25 @@ module PactBroker
97
90
 
98
91
  def latest_consumer_version_tags
99
92
  @line.consumer_version_tags
100
- .select{ | tag | tag.latest }
93
+ .select(&:latest)
101
94
  .collect{ | tag | MatrixTag.new(tag.to_hash.merge(pacticipant_name: consumer_name, version_number: consumer_version_number)) }
102
95
  end
103
96
 
104
97
  def other_consumer_version_tags
105
98
  @line.consumer_version_tags
106
- .select{ | tag | !tag.latest }
99
+ .reject(&:latest)
107
100
  .collect{ | tag | MatrixTag.new(tag.to_hash.merge(pacticipant_name: consumer_name, version_number: consumer_version_number)) }
108
101
  end
109
102
 
110
103
  def latest_provider_version_tags
111
104
  @line.provider_version_tags
112
- .select{ | tag | tag.latest }
105
+ .select(&:latest)
113
106
  .collect{ | tag | MatrixTag.new(tag.to_hash.merge(pacticipant_name: provider_name, version_number: provider_version_number)) }
114
107
  end
115
108
 
116
109
  def other_provider_version_tags
117
110
  @line.provider_version_tags
118
- .select{ | tag | !tag.latest }
111
+ .reject(&:latest)
119
112
  .collect{ | tag | MatrixTag.new(tag.to_hash.merge(pacticipant_name: provider_name, version_number: provider_version_number)) }
120
113
  end
121
114
 
@@ -127,7 +120,7 @@ module PactBroker
127
120
  (self.orderable_fields <=> other.orderable_fields) * -1
128
121
  end
129
122
 
130
- def pseudo_branch_verification_status
123
+ def verification_status
131
124
  if @line.verification_executed_at
132
125
  DateHelper.distance_of_time_in_words(@line.verification_executed_at, DateTime.now) + " ago"
133
126
  else
@@ -176,12 +169,12 @@ module PactBroker
176
169
  @overwritten = overwritten
177
170
  end
178
171
 
179
- def inherited_verification_message
172
+ def pre_verified_message
180
173
  if @line.verification_executed_at && @line.pact_created_at > @line.verification_executed_at
181
- "The verification date is before the pact publication date because this verification has been inherited from a previously verified pact with identical content."
174
+ message("messages.matrix.pre_verified")
182
175
  end
183
176
  end
184
177
  end
185
178
  end
186
179
  end
187
- end
180
+ end
@@ -9,28 +9,26 @@ module PactBroker
9
9
 
10
10
  include PactBroker::Api::PactBrokerUrls
11
11
 
12
+ attr_reader :name, :pacticipant_name, :version_number
13
+
12
14
  def initialize params
13
- @params = params
14
15
  @name = params[:name]
16
+ @pacticipant_name = params[:pacticipant_name]
15
17
  @version_number = params[:version_number]
16
18
  @created_at = params[:created_at]
17
19
  @latest = !!params[:latest]
18
20
  end
19
21
 
20
- def name
21
- @params[:name]
22
- end
23
-
24
22
  def tooltip
25
23
  if @latest
26
- "Version #{@version_number} is the latest version with tag #{@name}. Tag created #{relative_date(@created_at)}."
24
+ "This is the latest version of #{pacticipant_name} with tag \"#{@name}\". Tag created #{relative_date(@created_at)}."
27
25
  else
28
- "Tag created #{relative_date(@created_at)}."
26
+ "Tag created #{relative_date(@created_at)}. A more recent version of #{pacticipant_name} with tag \"#{name}\" exists."
29
27
  end
30
28
  end
31
29
 
32
30
  def url
33
- hal_browser_url("/pacticipants/#{ERB::Util.url_encode(@params[:pacticipant_name])}/versions/#{@params[:version_number]}/tags/#{@params[:name]}")
31
+ hal_browser_url("/pacticipants/#{ERB::Util.url_encode(pacticipant_name)}/versions/#{ERB::Util.url_encode(version_number)}/tags/#{ERB::Util.url_encode(name)}")
34
32
  end
35
33
 
36
34
  def relative_date date
@@ -0,0 +1,31 @@
1
+ %script{type: 'text/javascript', src:'/javascripts/pagination.js'}
2
+
3
+ :javascript
4
+ const PAGE_NUMBER = #{page_number};
5
+ const PAGE_SIZE = #{page_size};
6
+ const TOTAL_NUMBER = #{pagination_record_count}
7
+ const CURRENT_PAGE_SIZE = #{current_page_size}
8
+
9
+ $(document).ready(function(){
10
+ function createPageLink(pageNumber, pageSize) {
11
+ const url = new URL(window.location)
12
+ url.searchParams.set('page', pageNumber)
13
+ url.searchParams.set('pageSize', pageSize)
14
+ return url.toString()
15
+ }
16
+
17
+ function createFooter(currentPage, totalPage, totalNumber) {
18
+ return `<div class='nav-footer'>${CURRENT_PAGE_SIZE} of ${totalNumber} pacts</div>`
19
+ }
20
+
21
+ $('div.pagination').pagination({
22
+ dataSource: [],
23
+ totalNumber: TOTAL_NUMBER,
24
+ pageNumber: PAGE_NUMBER,
25
+ pageSize: PAGE_SIZE,
26
+ pageRange: 2,
27
+ pageLink: createPageLink,
28
+ ulClassName: 'pagination',
29
+ footer: createFooter
30
+ })
31
+ });
@@ -38,7 +38,7 @@
38
38
  %td.consumer
39
39
  %a{:href => index_item.consumer_group_url }
40
40
  = escape_html(index_item.consumer_name)
41
- %td.consumer-version-number
41
+ %td.consumer-version-number{"data-text": index_item.consumer_version_order}
42
42
  %div.clippable
43
43
  = escape_html(index_item.consumer_version_number)
44
44
  - if index_item.consumer_version_number
@@ -82,8 +82,11 @@
82
82
  %td
83
83
  - if index_item.show_settings?
84
84
  %span.integration-settings.glyphicon.glyphicon-option-horizontal{ 'aria-hidden': true }
85
- %div.relationships-size
86
- = index_items.size_label
85
+
86
+ %div.pagination
87
+
88
+ - pagination_locals = { page_number: page_number, page_size: page_size, pagination_record_count: pagination_record_count, current_page_size: current_page_size }
89
+ = render :haml, :'index/_pagination', :layout => false, locals: pagination_locals
87
90
 
88
91
  :javascript
89
92
  $(function(){
@@ -55,8 +55,11 @@
55
55
  %span.glyphicon.glyphicon-warning-sign{ 'aria-hidden': true }
56
56
  %td
57
57
  %span.integration-settings.glyphicon.glyphicon-option-horizontal{ 'aria-hidden': true }
58
- %div.relationships-size
59
- = index_items.size_label
58
+ %div.pagination
59
+
60
+ - pagination_locals = { page_number: page_number, page_size: page_size, pagination_record_count: pagination_record_count, current_page_size: current_page_size }
61
+ = render :haml, :'index/_pagination', :layout => false, locals: pagination_locals
62
+
60
63
 
61
64
  :javascript
62
65
  $(function(){
@@ -119,7 +119,7 @@
119
119
  %a{href: tag.url}
120
120
  .tag.label.label-default
121
121
  = tag.name
122
- %td.pact-published{'data-sort-value' => line.pact_published_order, "title": line.inherited_verification_message, "data-toggle": "tooltip"}
122
+ %td.pact-published{'data-sort-value' => line.pact_published_order, "data-toggle": "tooltip", "title": line.pact_version_sha_message, "data-placement": "right", "data-pact-version-sha": line.pact_version_sha}
123
123
  %a{href: line.pact_publication_date_url}
124
124
  - if options.all_rows_checked
125
125
  = "#{line.pact_publication_date} (revision #{line.pact_revision_number})"
@@ -146,14 +146,11 @@
146
146
  %a{href: tag.url}
147
147
  .tag.label.label-default
148
148
  = tag.name
149
- %td.verification-result{class: line.verification_status_class, "title": line.inherited_verification_message, "data-toggle": "tooltip"}
149
+ %td.verification-result{class: line.verification_status_class, "title": line.pre_verified_message, "data-toggle": "tooltip", "data-placement": "left"}
150
150
  %a{href: line.verification_status_url}
151
151
  - if options.all_rows_checked && line.number
152
- = "#{line.pseudo_branch_verification_status} (number #{line.number})"
152
+ = "#{line.verification_status} (number #{line.number})"
153
153
  - else
154
- = line.pseudo_branch_verification_status
155
-
156
- :javascript
157
- $(document).ready(function(){
158
- initializeClipper(".clippable");
159
- });
154
+ = line.verification_status
155
+ - if line.pre_verified_message
156
+ %span.glyphicon.glyphicon-time.pre-verified-icon{"aria-hidden": true}
@@ -1,3 +1,3 @@
1
1
  module PactBroker
2
- VERSION = '2.40.0'
2
+ VERSION = '2.41.0'
3
3
  end
@@ -68,8 +68,8 @@ module PactBroker
68
68
  end
69
69
  end
70
70
 
71
- def is_for? relationship
72
- (consumer_id == relationship.consumer_id || !consumer_id) && (provider_id == relationship.provider_id || !provider_id)
71
+ def is_for? integration
72
+ (consumer_id == integration.consumer_id || !consumer_id) && (provider_id == integration.provider_id || !provider_id)
73
73
  end
74
74
 
75
75
  private
@@ -4,8 +4,9 @@ require 'pact_broker/webhooks/http_response_with_utf_8_safe_body'
4
4
  module PactBroker
5
5
  module Webhooks
6
6
  class WebhookExecutionResult
7
+ attr_reader :request, :response, :logs, :error
7
8
 
8
- def initialize request, response, logs, error = nil
9
+ def initialize(request, response, logs, error = nil)
9
10
  @request = PactBroker::Webhooks::HttpRequestWithRedactedHeaders.new(request)
10
11
  @response = response ? PactBroker::Webhooks::HttpResponseWithUtf8SafeBody.new(response) : nil
11
12
  @logs = logs
@@ -13,23 +14,7 @@ module PactBroker
13
14
  end
14
15
 
15
16
  def success?
16
- !@response.nil? && @response.code.to_i < 300
17
- end
18
-
19
- def request
20
- @request
21
- end
22
-
23
- def response
24
- @response
25
- end
26
-
27
- def error
28
- @error
29
- end
30
-
31
- def logs
32
- @logs
17
+ !response.nil? && response.code.to_i < 300
33
18
  end
34
19
  end
35
20
  end
@@ -53,6 +53,26 @@ function disableFieldsThatShouldNotBeSubmitted() {
53
53
  $('.version-selectorizor').prop('disabled', 'disabled');
54
54
  }
55
55
 
56
+ function highlightPactPublicationsWithSameContent(td) {
57
+ const pactVersionSha = $(td).data('pact-version-sha');
58
+ $('*[data-pact-version-sha="' + pactVersionSha +'"]').addClass('bg-info');
59
+ }
60
+
61
+ function unHighlightPactPublicationsWithSameContent(td, event) {
62
+ var destinationElement = $(event.toElement || event.relatedTarget);
63
+ // Have to use mouseout instead of mouseleave, because the tooltip is a child
64
+ // of the td, and the mouseleave will consider that hovering over the tooltip
65
+ // does not count as leaving. Unfortunately, if you then leave the tooltip,
66
+ // the div gets removed without firing the mouseleave event, so the cells remain
67
+ // highlighted.
68
+ // The tooltip needs to be a child of the td so that we can style the one showing
69
+ // the SHA so that it's wide enough to fit the SHA in.
70
+ if (!$(td).find('a').is(destinationElement)) {
71
+ const pactVersionSha = $(td).data('pact-version-sha');
72
+ $('*[data-pact-version-sha="' + pactVersionSha +'"]').removeClass('bg-info');
73
+ }
74
+ }
75
+
56
76
  $(document).ready(function(){
57
77
  $('.version-selectorizor').change(handleSelectorizorChanged);
58
78
  $('.version-selectorizor').each(function(){ showApplicableTextBoxes($(this)); });
@@ -65,6 +85,11 @@ $(document).ready(function(){
65
85
  });
66
86
 
67
87
  $('[data-toggle="tooltip"]').each(function(index, el){
68
- $(el).tooltip({container: $(el)});
88
+ $(el).tooltip({container: $(el)})
69
89
  });
90
+
91
+ initializeClipper('.clippable');
92
+
93
+ $('td.pact-published').mouseover(function(event) { highlightPactPublicationsWithSameContent(this) });
94
+ $('td.pact-published').mouseout(function(event) { unHighlightPactPublicationsWithSameContent(this, event)});
70
95
  });
@@ -0,0 +1,1127 @@
1
+ // A hacked version of pagination.js to get just the nav renderiing functionality.
2
+ // TODO Remove all the unnecessary code!
3
+ /*
4
+ * pagination.js 2.1.5
5
+ * A jQuery plugin to provide simple yet fully customisable pagination.
6
+ * https://github.com/superRaytin/paginationjs
7
+ *
8
+ * Homepage: http://pagination.js.org
9
+ *
10
+ * Copyright 2014-2100, superRaytin
11
+ * Released under the MIT license.
12
+ */
13
+
14
+ (function(global, $) {
15
+
16
+ if (typeof $ === 'undefined') {
17
+ throwError('Pagination requires jQuery.');
18
+ }
19
+
20
+ var pluginName = 'pagination';
21
+
22
+ var pluginHookMethod = 'addHook';
23
+
24
+ var eventPrefix = '__pagination-';
25
+
26
+ // Conflict, use backup
27
+ if ($.fn.pagination) {
28
+ pluginName = 'pagination2';
29
+ }
30
+
31
+ $.fn[pluginName] = function(options) {
32
+
33
+ if (typeof options === 'undefined') {
34
+ return this;
35
+ }
36
+
37
+ var container = $(this);
38
+
39
+ var attributes = $.extend({}, $.fn[pluginName].defaults, options);
40
+
41
+ var pagination = {
42
+
43
+ initialize: function() {
44
+ var self = this;
45
+
46
+ // Cache attributes of current instance
47
+ if (!container.data('pagination')) {
48
+ container.data('pagination', {});
49
+ }
50
+
51
+ if (self.callHook('beforeInit') === false) return;
52
+
53
+ // Pagination has been initialized, destroy it
54
+ if (container.data('pagination').initialized) {
55
+ $('.paginationjs', container).remove();
56
+ }
57
+
58
+ // Whether to disable Pagination at the initialization
59
+ self.disabled = !!attributes.disabled;
60
+
61
+ // Model will be passed to the callback function
62
+ var model = self.model = {
63
+ pageRange: attributes.pageRange,
64
+ pageSize: attributes.pageSize
65
+ };
66
+
67
+ // dataSource`s type is unknown, parse it to find true data
68
+ self.parseDataSource(attributes.dataSource, function(dataSource) {
69
+
70
+ // Currently in asynchronous mode
71
+ self.isAsync = Helpers.isString(dataSource);
72
+ if (Helpers.isArray(dataSource)) {
73
+ model.totalNumber = attributes.totalNumber; // = dataSource.length;
74
+ }
75
+
76
+ // Currently in asynchronous mode and a totalNumberLocator is specified
77
+ self.isDynamicTotalNumber = self.isAsync && attributes.totalNumberLocator;
78
+
79
+ var el = self.render(true);
80
+
81
+ // Add extra className to the pagination element
82
+ if (attributes.className) {
83
+ el.addClass(attributes.className);
84
+ }
85
+
86
+ model.el = el;
87
+
88
+ // Append/prepend pagination element to the container
89
+ container[attributes.position === 'bottom' ? 'append' : 'prepend'](el);
90
+
91
+ // Bind events
92
+ //self.observer();
93
+
94
+ // Pagination is currently initialized
95
+ container.data('pagination').initialized = true;
96
+
97
+ // Will be invoked after initialized
98
+ self.callHook('afterInit', el);
99
+ });
100
+ },
101
+
102
+ render: function(isBoot) {
103
+ var self = this;
104
+ var model = self.model;
105
+ var el = model.el || $('<nav class="paginationjs"></nav>');
106
+ var isForced = isBoot !== true;
107
+
108
+ self.callHook('beforeRender', isForced);
109
+
110
+ var currentPage = model.pageNumber || attributes.pageNumber;
111
+ var pageRange = attributes.pageRange || 0;
112
+ var totalPage = self.getTotalPage();
113
+
114
+ var rangeStart = currentPage - pageRange;
115
+ var rangeEnd = currentPage + pageRange;
116
+
117
+ if (rangeEnd > totalPage) {
118
+ rangeEnd = totalPage;
119
+ rangeStart = totalPage - pageRange * 2;
120
+ rangeStart = rangeStart < 1 ? 1 : rangeStart;
121
+ }
122
+
123
+ if (rangeStart <= 1) {
124
+ rangeStart = 1;
125
+ rangeEnd = Math.min(pageRange * 2 + 1, totalPage);
126
+ }
127
+
128
+ el.html(self.generateHTML({
129
+ currentPage: currentPage,
130
+ pageRange: pageRange,
131
+ rangeStart: rangeStart,
132
+ rangeEnd: rangeEnd
133
+ }));
134
+
135
+ // There is only one page
136
+ if (attributes.hideWhenLessThanOnePage) {
137
+ el[totalPage <= 1 ? 'hide' : 'show']();
138
+ }
139
+
140
+ self.callHook('afterRender', isForced);
141
+
142
+ return el;
143
+ },
144
+
145
+ // Generate HTML of the pages
146
+ generatePageNumbersHTML: function(args) {
147
+ var self = this;
148
+ var currentPage = args.currentPage;
149
+ var totalPage = self.getTotalPage();
150
+ var rangeStart = args.rangeStart;
151
+ var rangeEnd = args.rangeEnd;
152
+ var pageSize = attributes.pageSize;
153
+ var html = '';
154
+ var i;
155
+
156
+ var pageLink = attributes.pageLink;
157
+ var ellipsisText = attributes.ellipsisText;
158
+
159
+ var classPrefix = attributes.classPrefix;
160
+ var activeClassName = attributes.activeClassName;
161
+ var disableClassName = attributes.disableClassName;
162
+
163
+ // Disable page range, display all the pages
164
+ if (attributes.pageRange === null) {
165
+ for (i = 1; i <= totalPage; i++) {
166
+ if (i == currentPage) {
167
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page ' + activeClassName + '" data-num="' + i + '"><a>' + i + '<\/a><\/li>';
168
+ } else {
169
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink(i, pageSize) + '">' + i + '<\/a><\/li>';
170
+ }
171
+ }
172
+ return html;
173
+ }
174
+
175
+ if (rangeStart <= 3) {
176
+ for (i = 1; i < rangeStart; i++) {
177
+ if (i == currentPage) {
178
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page ' + activeClassName + '" data-num="' + i + '"><a>' + i + '<\/a><\/li>';
179
+ } else {
180
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink(i, pageSize) + '">' + i + '<\/a><\/li>';
181
+ }
182
+ }
183
+ } else {
184
+ if (attributes.showFirstOnEllipsisShow) {
185
+ html += '<li class="' + classPrefix + '-page ' + classPrefix + '-first J-paginationjs-page" data-num="1"><a href="' + pageLink(1, pageSize) + '">1<\/a><\/li>';
186
+ }
187
+ html += '<li class="' + classPrefix + '-ellipsis ' + disableClassName + '"><a>' + ellipsisText + '<\/a><\/li>';
188
+ }
189
+
190
+ for (i = rangeStart; i <= rangeEnd; i++) {
191
+ if (i == currentPage) {
192
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page ' + activeClassName + '" data-num="' + i + '"><a>' + i + '<\/a><\/li>';
193
+ } else {
194
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink(i, pageSize) + '">' + i + '<\/a><\/li>';
195
+ }
196
+ }
197
+
198
+ if (rangeEnd >= totalPage - 2) {
199
+ for (i = rangeEnd + 1; i <= totalPage; i++) {
200
+ html += '<li class="' + classPrefix + '-page J-paginationjs-page" data-num="' + i + '"><a href="' + pageLink(i, pageSize) + '">' + i + '<\/a><\/li>';
201
+ }
202
+ } else {
203
+ html += '<li class="' + classPrefix + '-ellipsis ' + disableClassName + '"><a>' + ellipsisText + '<\/a><\/li>';
204
+
205
+ if (attributes.showLastOnEllipsisShow) {
206
+ html += '<li class="' + classPrefix + '-page ' + classPrefix + '-last J-paginationjs-page" data-num="' + totalPage + '"><a href="' + pageLink(totalPage, pageSize) + '">' + totalPage + '<\/a><\/li>';
207
+ }
208
+ }
209
+
210
+ return html;
211
+ },
212
+
213
+ // Generate HTML content from the template
214
+ generateHTML: function(args) {
215
+ var self = this;
216
+ var currentPage = args.currentPage;
217
+ var totalPage = self.getTotalPage();
218
+
219
+ var totalNumber = self.getTotalNumber();
220
+ var pageSize = attributes.pageSize;
221
+
222
+ var showPrevious = attributes.showPrevious;
223
+ var showNext = attributes.showNext;
224
+ var showPageNumbers = attributes.showPageNumbers;
225
+ var showNavigator = attributes.showNavigator;
226
+ var showGoInput = attributes.showGoInput;
227
+ var showGoButton = attributes.showGoButton;
228
+
229
+ var pageLink = attributes.pageLink;
230
+ var prevText = attributes.prevText;
231
+ var nextText = attributes.nextText;
232
+ var goButtonText = attributes.goButtonText;
233
+
234
+ var classPrefix = attributes.classPrefix;
235
+ var disableClassName = attributes.disableClassName;
236
+ var ulClassName = attributes.ulClassName;
237
+
238
+ var html = '';
239
+ var goInput = '<input type="text" class="J-paginationjs-go-pagenumber">';
240
+ var goButton = '<input type="button" class="J-paginationjs-go-button" value="' + goButtonText + '">';
241
+ var formattedString;
242
+
243
+ var formatNavigator = $.isFunction(attributes.formatNavigator) ? attributes.formatNavigator(currentPage, totalPage, totalNumber) : attributes.formatNavigator;
244
+ var formatGoInput = $.isFunction(attributes.formatGoInput) ? attributes.formatGoInput(goInput, currentPage, totalPage, totalNumber) : attributes.formatGoInput;
245
+ var formatGoButton = $.isFunction(attributes.formatGoButton) ? attributes.formatGoButton(goButton, currentPage, totalPage, totalNumber) : attributes.formatGoButton;
246
+
247
+ var autoHidePrevious = $.isFunction(attributes.autoHidePrevious) ? attributes.autoHidePrevious() : attributes.autoHidePrevious;
248
+ var autoHideNext = $.isFunction(attributes.autoHideNext) ? attributes.autoHideNext() : attributes.autoHideNext;
249
+
250
+ var header = $.isFunction(attributes.header) ? attributes.header(currentPage, totalPage, totalNumber) : attributes.header;
251
+ var footer = $.isFunction(attributes.footer) ? attributes.footer(currentPage, totalPage, totalNumber) : attributes.footer;
252
+
253
+ // Whether to display header
254
+ if (header) {
255
+ formattedString = self.replaceVariables(header, {
256
+ currentPage: currentPage,
257
+ totalPage: totalPage,
258
+ totalNumber: totalNumber
259
+ });
260
+ html += formattedString;
261
+ }
262
+
263
+ if (showPrevious || showPageNumbers || showNext) {
264
+ html += '<div class="paginationjs-pages">';
265
+
266
+ if (ulClassName) {
267
+ html += '<ul class="' + ulClassName + '">';
268
+ } else {
269
+ html += '<ul>';
270
+ }
271
+
272
+ // Whether to display the Previous button
273
+ if (showPrevious) {
274
+ if (currentPage <= 1) {
275
+ if (!autoHidePrevious) {
276
+ html += '<li class="' + classPrefix + '-prev ' + disableClassName + '"><a>' + prevText + '<\/a><\/li>';
277
+ }
278
+ } else {
279
+ html += '<li class="' + classPrefix + '-prev J-paginationjs-previous" data-num="' + (currentPage - 1) + '" title="Previous page"><a href="' + pageLink(currentPage - 1, pageSize) + '">' + prevText + '<\/a><\/li>';
280
+ }
281
+ }
282
+
283
+ // Whether to display the pages
284
+ if (showPageNumbers) {
285
+ html += self.generatePageNumbersHTML(args);
286
+ }
287
+
288
+ // Whether to display the Next button
289
+ if (showNext) {
290
+ if (currentPage >= totalPage) {
291
+ if (!autoHideNext) {
292
+ html += '<li class="' + classPrefix + '-next ' + disableClassName + '"><a>' + nextText + '<\/a><\/li>';
293
+ }
294
+ } else {
295
+ html += '<li class="' + classPrefix + '-next J-paginationjs-next" data-num="' + (currentPage + 1) + '" title="Next page"><a href="' + pageLink(currentPage + 1, pageSize) + '">' + nextText + '<\/a><\/li>';
296
+ }
297
+ }
298
+ html += '<\/ul><\/div>';
299
+ }
300
+
301
+ // Whether to display the navigator
302
+ if (showNavigator) {
303
+ if (formatNavigator) {
304
+ formattedString = self.replaceVariables(formatNavigator, {
305
+ currentPage: currentPage,
306
+ totalPage: totalPage,
307
+ totalNumber: totalNumber
308
+ });
309
+ html += '<div class="' + classPrefix + '-nav J-paginationjs-nav">' + formattedString + '<\/div>';
310
+ }
311
+ }
312
+
313
+ // Whether to display the Go input
314
+ if (showGoInput) {
315
+ if (formatGoInput) {
316
+ formattedString = self.replaceVariables(formatGoInput, {
317
+ currentPage: currentPage,
318
+ totalPage: totalPage,
319
+ totalNumber: totalNumber,
320
+ input: goInput
321
+ });
322
+ html += '<div class="' + classPrefix + '-go-input">' + formattedString + '</div>';
323
+ }
324
+ }
325
+
326
+ // Whether to display the Go button
327
+ if (showGoButton) {
328
+ if (formatGoButton) {
329
+ formattedString = self.replaceVariables(formatGoButton, {
330
+ currentPage: currentPage,
331
+ totalPage: totalPage,
332
+ totalNumber: totalNumber,
333
+ button: goButton
334
+ });
335
+ html += '<div class="' + classPrefix + '-go-button">' + formattedString + '</div>';
336
+ }
337
+ }
338
+
339
+ // Whether to display footer
340
+ if (footer) {
341
+ formattedString = self.replaceVariables(footer, {
342
+ currentPage: currentPage,
343
+ totalPage: totalPage,
344
+ totalNumber: totalNumber
345
+ });
346
+ html += formattedString;
347
+ }
348
+
349
+ return html;
350
+ },
351
+
352
+ // Find totalNumber from the remote response
353
+ // Only available in asynchronous mode
354
+ findTotalNumberFromRemoteResponse: function(response) {
355
+ var self = this;
356
+ self.model.totalNumber = attributes.totalNumberLocator(response);
357
+ },
358
+
359
+ // Go to the specified page
360
+ // go: function(number, callback) {
361
+ // var self = this;
362
+ // var model = self.model;
363
+
364
+ // if (self.disabled) return;
365
+
366
+ // var pageNumber = number;
367
+ // pageNumber = parseInt(pageNumber);
368
+
369
+ // // Page number is out of bounds
370
+ // if (!pageNumber || pageNumber < 1) return;
371
+
372
+ // var pageSize = attributes.pageSize;
373
+ // var totalNumber = self.getTotalNumber();
374
+ // var totalPage = self.getTotalPage();
375
+
376
+ // // Page number is out of bounds
377
+ // if (totalNumber > 0) {
378
+ // if (pageNumber > totalPage) return;
379
+ // }
380
+
381
+ // // Pick data fragment in synchronous mode
382
+ // if (!self.isAsync) {
383
+ // render(self.getDataFragment(pageNumber));
384
+ // return;
385
+ // }
386
+
387
+ // var postData = {};
388
+ // var alias = attributes.alias || {};
389
+ // postData[alias.pageSize ? alias.pageSize : 'pageSize'] = pageSize;
390
+ // postData[alias.pageNumber ? alias.pageNumber : 'pageNumber'] = pageNumber;
391
+
392
+ // var ajaxParams = $.isFunction(attributes.ajax) ? attributes.ajax() : attributes.ajax;
393
+ // var formatAjaxParams = {
394
+ // type: 'get',
395
+ // cache: false,
396
+ // data: {},
397
+ // contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
398
+ // dataType: 'json',
399
+ // async: true
400
+ // };
401
+
402
+ // $.extend(true, formatAjaxParams, ajaxParams);
403
+ // $.extend(formatAjaxParams.data, postData);
404
+
405
+ // formatAjaxParams.url = attributes.dataSource;
406
+ // formatAjaxParams.success = function(response) {
407
+ // if (self.isDynamicTotalNumber) {
408
+ // self.findTotalNumberFromRemoteResponse(response);
409
+ // } else {
410
+ // self.model.totalNumber = attributes.totalNumber;
411
+ // }
412
+
413
+ // var finalData = self.filterDataByLocator(response);
414
+ // render(finalData);
415
+ // };
416
+ // formatAjaxParams.error = function(jqXHR, textStatus, errorThrown) {
417
+ // attributes.formatAjaxError && attributes.formatAjaxError(jqXHR, textStatus, errorThrown);
418
+ // self.enable();
419
+ // };
420
+
421
+ // self.disable();
422
+
423
+ // $.ajax(formatAjaxParams);
424
+
425
+ // function render(data) {
426
+ // // Will be invoked before paging
427
+ // if (self.callHook('beforePaging', pageNumber) === false) return false;
428
+
429
+ // // Pagination direction
430
+ // model.direction = typeof model.pageNumber === 'undefined' ? 0 : (pageNumber > model.pageNumber ? 1 : -1);
431
+
432
+ // model.pageNumber = pageNumber;
433
+
434
+ // self.render();
435
+
436
+ // if (self.disabled && self.isAsync) {
437
+ // // enable pagination
438
+ // self.enable();
439
+ // }
440
+
441
+ // // cache model data
442
+ // container.data('pagination').model = model;
443
+
444
+ // // format result data before callback invoked
445
+ // if (attributes.formatResult) {
446
+ // var cloneData = $.extend(true, [], data);
447
+ // if (!Helpers.isArray(data = attributes.formatResult(cloneData))) {
448
+ // data = cloneData;
449
+ // }
450
+ // }
451
+
452
+ // container.data('pagination').currentPageData = data;
453
+
454
+ // // invoke callback
455
+ // self.doCallback(data, callback);
456
+
457
+ // self.callHook('afterPaging', pageNumber);
458
+
459
+ // // pageNumber now is the first page
460
+ // if (pageNumber == 1) {
461
+ // self.callHook('afterIsFirstPage');
462
+ // }
463
+
464
+ // // pageNumber now is the last page
465
+ // if (pageNumber == self.getTotalPage()) {
466
+ // self.callHook('afterIsLastPage');
467
+ // }
468
+ // }
469
+ // },
470
+
471
+ doCallback: function(data, customCallback) {
472
+ var self = this;
473
+ var model = self.model;
474
+
475
+ if ($.isFunction(customCallback)) {
476
+ customCallback(data, model);
477
+ } else if ($.isFunction(attributes.callback)) {
478
+ attributes.callback(data, model);
479
+ }
480
+ },
481
+
482
+ destroy: function() {
483
+ // Before destroy
484
+ if (this.callHook('beforeDestroy') === false) return;
485
+
486
+ this.model.el.remove();
487
+ container.off();
488
+
489
+ // Remove style element
490
+ $('#paginationjs-style').remove();
491
+
492
+ // After destroyed
493
+ this.callHook('afterDestroy');
494
+ },
495
+
496
+ previous: function(callback) {
497
+ this.go(this.model.pageNumber - 1, callback);
498
+ },
499
+
500
+ next: function(callback) {
501
+ this.go(this.model.pageNumber + 1, callback);
502
+ },
503
+
504
+ disable: function() {
505
+ var self = this;
506
+ var source = self.isAsync ? 'async' : 'sync';
507
+
508
+ // Before disabled
509
+ if (self.callHook('beforeDisable', source) === false) return;
510
+
511
+ self.disabled = true;
512
+ self.model.disabled = true;
513
+
514
+ // After disabled
515
+ self.callHook('afterDisable', source);
516
+ },
517
+
518
+ enable: function() {
519
+ var self = this;
520
+ var source = self.isAsync ? 'async' : 'sync';
521
+
522
+ // Before enabled
523
+ if (self.callHook('beforeEnable', source) === false) return;
524
+
525
+ self.disabled = false;
526
+ self.model.disabled = false;
527
+
528
+ // After enabled
529
+ self.callHook('afterEnable', source);
530
+ },
531
+
532
+ refresh: function(callback) {
533
+ this.go(this.model.pageNumber, callback);
534
+ },
535
+
536
+ show: function() {
537
+ var self = this;
538
+
539
+ if (self.model.el.is(':visible')) return;
540
+
541
+ self.model.el.show();
542
+ },
543
+
544
+ hide: function() {
545
+ var self = this;
546
+
547
+ if (!self.model.el.is(':visible')) return;
548
+
549
+ self.model.el.hide();
550
+ },
551
+
552
+ // Parse variables in the template
553
+ replaceVariables: function(template, variables) {
554
+ var formattedString;
555
+
556
+ for (var key in variables) {
557
+ var value = variables[key];
558
+ var regexp = new RegExp('<%=\\s*' + key + '\\s*%>', 'img');
559
+
560
+ formattedString = (formattedString || template).replace(regexp, value);
561
+ }
562
+
563
+ return formattedString;
564
+ },
565
+
566
+ // Get data fragment
567
+ getDataFragment: function(number) {
568
+ var pageSize = attributes.pageSize;
569
+ var dataSource = attributes.dataSource;
570
+ var totalNumber = this.getTotalNumber();
571
+
572
+ var start = pageSize * (number - 1) + 1;
573
+ var end = Math.min(number * pageSize, totalNumber);
574
+
575
+ return dataSource.slice(start - 1, end);
576
+ },
577
+
578
+ // Get total number
579
+ getTotalNumber: function() {
580
+ return this.model.totalNumber || attributes.totalNumber || 0;
581
+ },
582
+
583
+ // Get total page
584
+ getTotalPage: function() {
585
+ return Math.ceil(this.getTotalNumber() / attributes.pageSize);
586
+ },
587
+
588
+ // Get locator
589
+ getLocator: function(locator) {
590
+ var result;
591
+
592
+ if (typeof locator === 'string') {
593
+ result = locator;
594
+ } else if ($.isFunction(locator)) {
595
+ result = locator();
596
+ } else {
597
+ throwError('"locator" is incorrect. (String | Function)');
598
+ }
599
+
600
+ return result;
601
+ },
602
+
603
+ // Filter data by "locator"
604
+ filterDataByLocator: function(dataSource) {
605
+ var locator = this.getLocator(attributes.locator);
606
+ var filteredData;
607
+
608
+ // Datasource is an Object, use "locator" to locate the true data
609
+ if (Helpers.isObject(dataSource)) {
610
+ try {
611
+ $.each(locator.split('.'), function(index, item) {
612
+ filteredData = (filteredData ? filteredData : dataSource)[item];
613
+ });
614
+ }
615
+ catch (e) {
616
+ }
617
+
618
+ if (!filteredData) {
619
+ throwError('dataSource.' + locator + ' is undefined.');
620
+ } else if (!Helpers.isArray(filteredData)) {
621
+ throwError('dataSource.' + locator + ' must be an Array.');
622
+ }
623
+ }
624
+
625
+ return filteredData || dataSource;
626
+ },
627
+
628
+ // Parse dataSource
629
+ parseDataSource: function(dataSource, callback) {
630
+ var self = this;
631
+
632
+ if (Helpers.isObject(dataSource)) {
633
+ callback(attributes.dataSource = self.filterDataByLocator(dataSource));
634
+ } else if (Helpers.isArray(dataSource)) {
635
+ callback(attributes.dataSource = dataSource);
636
+ } else if ($.isFunction(dataSource)) {
637
+ attributes.dataSource(function(data) {
638
+ if (!Helpers.isArray(data)) {
639
+ throwError('The parameter of "done" Function should be an Array.');
640
+ }
641
+ self.parseDataSource.call(self, data, callback);
642
+ });
643
+ } else if (typeof dataSource === 'string') {
644
+ if (/^https?|file:/.test(dataSource)) {
645
+ attributes.ajaxDataType = 'jsonp';
646
+ }
647
+ callback(dataSource);
648
+ } else {
649
+ throwError('Unexpected type of "dataSource".');
650
+ }
651
+ },
652
+
653
+ callHook: function(hook) {
654
+ var paginationData = container.data('pagination');
655
+ var result;
656
+
657
+ var args = Array.prototype.slice.apply(arguments);
658
+ args.shift();
659
+
660
+ if (attributes[hook] && $.isFunction(attributes[hook])) {
661
+ if (attributes[hook].apply(global, args) === false) {
662
+ result = false;
663
+ }
664
+ }
665
+
666
+ if (paginationData.hooks && paginationData.hooks[hook]) {
667
+ $.each(paginationData.hooks[hook], function(index, item) {
668
+ if (item.apply(global, args) === false) {
669
+ result = false;
670
+ }
671
+ });
672
+ }
673
+
674
+ return result !== false;
675
+ },
676
+
677
+ observer: function() {
678
+ var self = this;
679
+ var el = self.model.el;
680
+
681
+ // Go to specified page number
682
+ // container.on(eventPrefix + 'go', function(event, pageNumber, done) {
683
+ // pageNumber = parseInt($.trim(pageNumber));
684
+
685
+ // if (!pageNumber) return;
686
+
687
+ // if (!$.isNumeric(pageNumber)) {
688
+ // throwError('"pageNumber" is incorrect. (Number)');
689
+ // }
690
+
691
+ // self.go(pageNumber, done);
692
+ // });
693
+
694
+ // Page number button click
695
+ // el.delegate('.J-paginationjs-page', 'click', function(event) {
696
+ // var current = $(event.currentTarget);
697
+ // var pageNumber = $.trim(current.attr('data-num'));
698
+
699
+ // if (!pageNumber || current.hasClass(attributes.disableClassName) || current.hasClass(attributes.activeClassName)) return;
700
+
701
+ // // Before page button clicked
702
+ // if (self.callHook('beforePageOnClick', event, pageNumber) === false) return false;
703
+
704
+ // self.go(pageNumber);
705
+
706
+ // // After page button clicked
707
+ // self.callHook('afterPageOnClick', event, pageNumber);
708
+
709
+ // if (!attributes.pageLink) return false;
710
+ // });
711
+
712
+ // Previous button click
713
+ // el.delegate('.J-paginationjs-previous', 'click', function(event) {
714
+ // var current = $(event.currentTarget);
715
+ // var pageNumber = $.trim(current.attr('data-num'));
716
+
717
+ // if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
718
+
719
+ // // Before previous clicked
720
+ // if (self.callHook('beforePreviousOnClick', event, pageNumber) === false) return false;
721
+
722
+ // self.go(pageNumber);
723
+
724
+ // // After previous clicked
725
+ // self.callHook('afterPreviousOnClick', event, pageNumber);
726
+
727
+ // if (!attributes.pageLink) return false;
728
+ // });
729
+
730
+ // Next button click
731
+ // el.delegate('.J-paginationjs-next', 'click', function(event) {
732
+ // var current = $(event.currentTarget);
733
+ // var pageNumber = $.trim(current.attr('data-num'));
734
+
735
+ // if (!pageNumber || current.hasClass(attributes.disableClassName)) return;
736
+
737
+ // // Before next clicked
738
+ // if (self.callHook('beforeNextOnClick', event, pageNumber) === false) return false;
739
+
740
+ // self.go(pageNumber);
741
+
742
+ // // After next clicked
743
+ // self.callHook('afterNextOnClick', event, pageNumber);
744
+
745
+ // if (!attributes.pageLink) return false;
746
+ // });
747
+
748
+ // Go button click
749
+ // el.delegate('.J-paginationjs-go-button', 'click', function(event) {
750
+ // var pageNumber = $('.J-paginationjs-go-pagenumber', el).val();
751
+
752
+ // // Before Go button clicked
753
+ // if (self.callHook('beforeGoButtonOnClick', event, pageNumber) === false) return false;
754
+
755
+ // container.trigger(eventPrefix + 'go', pageNumber);
756
+
757
+ // // After Go button clicked
758
+ // self.callHook('afterGoButtonOnClick', event, pageNumber);
759
+ // });
760
+
761
+ // go input enter
762
+ // el.delegate('.J-paginationjs-go-pagenumber', 'keyup', function(event) {
763
+ // if (event.which === 13) {
764
+ // var pageNumber = $(event.currentTarget).val();
765
+
766
+ // // Before Go input enter
767
+ // if (self.callHook('beforeGoInputOnEnter', event, pageNumber) === false) return false;
768
+
769
+ // container.trigger(eventPrefix + 'go', pageNumber);
770
+
771
+ // // Regains focus
772
+ // $('.J-paginationjs-go-pagenumber', el).focus();
773
+
774
+ // // After Go input enter
775
+ // self.callHook('afterGoInputOnEnter', event, pageNumber);
776
+ // }
777
+ // });
778
+
779
+ // Previous page
780
+ // container.on(eventPrefix + 'previous', function(event, done) {
781
+ // self.previous(done);
782
+ // });
783
+
784
+ // // Next page
785
+ // container.on(eventPrefix + 'next', function(event, done) {
786
+ // self.next(done);
787
+ // });
788
+
789
+ // // Disable
790
+ // container.on(eventPrefix + 'disable', function() {
791
+ // self.disable();
792
+ // });
793
+
794
+ // // Enable
795
+ // container.on(eventPrefix + 'enable', function() {
796
+ // self.enable();
797
+ // });
798
+
799
+ // // Refresh
800
+ // container.on(eventPrefix + 'refresh', function(event, done) {
801
+ // self.refresh(done);
802
+ // });
803
+
804
+ // // Show
805
+ // container.on(eventPrefix + 'show', function() {
806
+ // self.show();
807
+ // });
808
+
809
+ // // Hide
810
+ // container.on(eventPrefix + 'hide', function() {
811
+ // self.hide();
812
+ // });
813
+
814
+ // // Destroy
815
+ // container.on(eventPrefix + 'destroy', function() {
816
+ // self.destroy();
817
+ // });
818
+
819
+ // Whether to load the default page
820
+ var validTotalPage = Math.max(self.getTotalPage(), 1)
821
+ var defaultPageNumber = attributes.pageNumber;
822
+ // Default pageNumber should be 1 when totalNumber is dynamic
823
+ if (self.isDynamicTotalNumber) {
824
+ defaultPageNumber = 1;
825
+ }
826
+ if (attributes.triggerPagingOnInit) {
827
+ container.trigger(eventPrefix + 'go', Math.min(defaultPageNumber, validTotalPage));
828
+ }
829
+ }
830
+ };
831
+
832
+ // Pagination has been initialized
833
+ if (container.data('pagination') && container.data('pagination').initialized === true) {
834
+ // Handle events
835
+ if ($.isNumeric(options)) {
836
+ // eg: container.pagination(5)
837
+ container.trigger.call(this, eventPrefix + 'go', options, arguments[1]);
838
+ return this;
839
+ } else if (typeof options === 'string') {
840
+ var args = Array.prototype.slice.apply(arguments);
841
+ args[0] = eventPrefix + args[0];
842
+
843
+ switch (options) {
844
+ case 'previous':
845
+ case 'next':
846
+ case 'go':
847
+ case 'disable':
848
+ case 'enable':
849
+ case 'refresh':
850
+ case 'show':
851
+ case 'hide':
852
+ case 'destroy':
853
+ container.trigger.apply(this, args);
854
+ break;
855
+ // Get selected page number
856
+ case 'getSelectedPageNum':
857
+ if (container.data('pagination').model) {
858
+ return container.data('pagination').model.pageNumber;
859
+ } else {
860
+ return container.data('pagination').attributes.pageNumber;
861
+ }
862
+ // Get total page
863
+ case 'getTotalPage':
864
+ return Math.ceil(container.data('pagination').model.totalNumber / container.data('pagination').model.pageSize);
865
+ // Get data of selected page
866
+ case 'getSelectedPageData':
867
+ return container.data('pagination').currentPageData;
868
+ // Whether pagination has been disabled
869
+ case 'isDisabled':
870
+ return container.data('pagination').model.disabled === true;
871
+ default:
872
+ throwError('Unknown action: ' + options);
873
+ }
874
+ return this;
875
+ } else {
876
+ // Uninstall the old instance before initializing a new one
877
+ uninstallPlugin(container);
878
+ }
879
+ } else {
880
+ if (!Helpers.isObject(options)) throwError('Illegal options');
881
+ }
882
+
883
+ // Check parameters
884
+ parameterChecker(attributes);
885
+
886
+ pagination.initialize();
887
+
888
+ return this;
889
+ };
890
+
891
+ // Instance defaults
892
+ $.fn[pluginName].defaults = {
893
+
894
+ // Data source
895
+ // Array | String | Function | Object
896
+ //dataSource: '',
897
+
898
+ // String | Function
899
+ //locator: 'data',
900
+
901
+ // Find totalNumber from remote response, the totalNumber will be ignored when totalNumberLocator is specified
902
+ // Function
903
+ //totalNumberLocator: function() {},
904
+
905
+ // Total entries
906
+ totalNumber: 0,
907
+
908
+ // Default page
909
+ pageNumber: 1,
910
+
911
+ // entries of per page
912
+ pageSize: 10,
913
+
914
+ // Page range (pages on both sides of the current page)
915
+ pageRange: 2,
916
+
917
+ // Whether to display the 'Previous' button
918
+ showPrevious: true,
919
+
920
+ // Whether to display the 'Next' button
921
+ showNext: true,
922
+
923
+ // Whether to display the page buttons
924
+ showPageNumbers: true,
925
+
926
+ showNavigator: false,
927
+
928
+ // Whether to display the 'Go' input
929
+ showGoInput: false,
930
+
931
+ // Whether to display the 'Go' button
932
+ showGoButton: false,
933
+
934
+ // Page link
935
+ pageLink: function(){},
936
+
937
+ // 'Previous' text
938
+ prevText: '&laquo;',
939
+
940
+ // 'Next' text
941
+ nextText: '&raquo;',
942
+
943
+ // Ellipsis text
944
+ ellipsisText: '...',
945
+
946
+ // 'Go' button text
947
+ goButtonText: 'Go',
948
+
949
+ // Additional className for Pagination element
950
+ //className: '',
951
+
952
+ classPrefix: 'paginationjs',
953
+
954
+ // Default active class
955
+ activeClassName: 'active',
956
+
957
+ // Default disable class
958
+ disableClassName: 'disabled',
959
+
960
+ //ulClassName: '',
961
+
962
+ // Whether to insert inline style
963
+ inlineStyle: true,
964
+
965
+ formatNavigator: '<%= currentPage %> / <%= totalPage %>',
966
+
967
+ formatGoInput: '<%= input %>',
968
+
969
+ formatGoButton: '<%= button %>',
970
+
971
+ // Pagination element's position in the container
972
+ position: 'bottom',
973
+
974
+ // Auto hide previous button when current page is the first page
975
+ autoHidePrevious: false,
976
+
977
+ // Auto hide next button when current page is the last page
978
+ autoHideNext: false,
979
+
980
+ //header: '',
981
+
982
+ //footer: '',
983
+
984
+ // Aliases for custom pagination parameters
985
+ //alias: {},
986
+
987
+ // Whether to trigger pagination at initialization
988
+ triggerPagingOnInit: true,
989
+
990
+ // Whether to hide pagination when less than one page
991
+ hideWhenLessThanOnePage: false,
992
+
993
+ showFirstOnEllipsisShow: true,
994
+
995
+ showLastOnEllipsisShow: true,
996
+
997
+ // Pagination callback
998
+ callback: function() {}
999
+ };
1000
+
1001
+ // Hook register
1002
+ $.fn[pluginHookMethod] = function(hook, callback) {
1003
+ if (arguments.length < 2) {
1004
+ throwError('Missing argument.');
1005
+ }
1006
+
1007
+ if (!$.isFunction(callback)) {
1008
+ throwError('callback must be a function.');
1009
+ }
1010
+
1011
+ var container = $(this);
1012
+ var paginationData = container.data('pagination');
1013
+
1014
+ if (!paginationData) {
1015
+ container.data('pagination', {});
1016
+ paginationData = container.data('pagination');
1017
+ }
1018
+
1019
+ !paginationData.hooks && (paginationData.hooks = {});
1020
+
1021
+ //paginationData.hooks[hook] = callback;
1022
+ paginationData.hooks[hook] = paginationData.hooks[hook] || [];
1023
+ paginationData.hooks[hook].push(callback);
1024
+
1025
+ };
1026
+
1027
+ // Static method
1028
+ $[pluginName] = function(selector, options) {
1029
+ if (arguments.length < 2) {
1030
+ throwError('Requires two parameters.');
1031
+ }
1032
+
1033
+ var container;
1034
+
1035
+ // 'selector' is a jQuery object
1036
+ if (typeof selector !== 'string' && selector instanceof jQuery) {
1037
+ container = selector;
1038
+ } else {
1039
+ container = $(selector);
1040
+ }
1041
+
1042
+ if (!container.length) return;
1043
+
1044
+ container.pagination(options);
1045
+
1046
+ return container;
1047
+ };
1048
+
1049
+ // ============================================================
1050
+ // helpers
1051
+ // ============================================================
1052
+
1053
+ var Helpers = {};
1054
+
1055
+ // Throw error
1056
+ function throwError(content) {
1057
+ throw new Error('Pagination: ' + content);
1058
+ }
1059
+
1060
+ // Check parameters
1061
+ function parameterChecker(args) {
1062
+ if (!args.dataSource) {
1063
+ throwError('"dataSource" is required.');
1064
+ }
1065
+
1066
+ if (typeof args.dataSource === 'string') {
1067
+ if (args.totalNumberLocator === undefined) {
1068
+ if (args.totalNumber === undefined) {
1069
+ throwError('"totalNumber" is required.');
1070
+ } else if (!$.isNumeric(args.totalNumber)) {
1071
+ throwError('"totalNumber" is incorrect. (Number)');
1072
+ }
1073
+ } else {
1074
+ if (!$.isFunction(args.totalNumberLocator)) {
1075
+ throwError('"totalNumberLocator" should be a Function.');
1076
+ }
1077
+ }
1078
+ } else if (Helpers.isObject(args.dataSource)) {
1079
+ if (typeof args.locator === 'undefined') {
1080
+ throwError('"dataSource" is an Object, please specify "locator".');
1081
+ } else if (typeof args.locator !== 'string' && !$.isFunction(args.locator)) {
1082
+ throwError('' + args.locator + ' is incorrect. (String | Function)');
1083
+ }
1084
+ }
1085
+
1086
+ if (args.formatResult !== undefined && !$.isFunction(args.formatResult)) {
1087
+ throwError('"formatResult" should be a Function.');
1088
+ }
1089
+ }
1090
+
1091
+ // uninstall plugin
1092
+ function uninstallPlugin(target) {
1093
+ var events = ['go', 'previous', 'next', 'disable', 'enable', 'refresh', 'show', 'hide', 'destroy'];
1094
+
1095
+ // off events of old instance
1096
+ $.each(events, function(index, value) {
1097
+ target.off(eventPrefix + value);
1098
+ });
1099
+
1100
+ // reset pagination data
1101
+ target.data('pagination', {});
1102
+
1103
+ // remove old
1104
+ $('.paginationjs', target).remove();
1105
+ }
1106
+
1107
+ // Object type detection
1108
+ function getObjectType(object, tmp) {
1109
+ return ( (tmp = typeof(object)) == "object" ? object == null && "null" || Object.prototype.toString.call(object).slice(8, -1) : tmp ).toLowerCase();
1110
+ }
1111
+
1112
+ $.each(['Object', 'Array', 'String'], function(index, name) {
1113
+ Helpers['is' + name] = function(object) {
1114
+ return getObjectType(object) === name.toLowerCase();
1115
+ };
1116
+ });
1117
+
1118
+ /*
1119
+ * export via AMD or CommonJS
1120
+ * */
1121
+ if (typeof define === 'function' && define.amd) {
1122
+ define(function() {
1123
+ return $;
1124
+ });
1125
+ }
1126
+
1127
+ })(this, window.jQuery);