pickadate-rails 1.3.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,21 +1,11 @@
1
1
 
2
2
  /*!
3
- * pickadate.js v3.3.2, 2013/12/28
3
+ * pickadate.js v3.4.0, 2014/02/15
4
4
  * By Amsul, http://amsul.ca
5
5
  * Hosted on http://amsul.github.io/pickadate.js
6
6
  * Licensed under MIT
7
7
  */
8
8
 
9
- /*jshint
10
- debug: true,
11
- devel: true,
12
- browser: true,
13
- asi: true,
14
- unused: true,
15
- boss: true,
16
- eqnull: true
17
- */
18
-
19
9
  (function ( factory ) {
20
10
 
21
11
  // Register as an anonymous module.
@@ -42,7 +32,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
42
32
  var
43
33
  // The state of the picker.
44
34
  STATE = {
45
- id: Math.abs( ~~( Math.random() * 1e9 ) )
35
+ id: ELEMENT.id || 'P' + Math.abs( ~~(Math.random() * new Date()) )
46
36
  },
47
37
 
48
38
 
@@ -93,162 +83,26 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
93
83
  ELEMENT.autofocus = ELEMENT == document.activeElement
94
84
  ELEMENT.type = 'text'
95
85
  ELEMENT.readOnly = !SETTINGS.editable
86
+ ELEMENT.id = ELEMENT.id || STATE.id
96
87
 
97
88
 
98
89
  // Create a new picker component with the settings.
99
- P.component = new COMPONENT( P, SETTINGS )
100
-
101
-
102
- // Create the picker root with a new wrapped holder and bind the events.
103
- P.$root = $( PickerConstructor._.node( 'div', createWrappedComponent(), CLASSES.picker ) ).
104
- on({
105
-
106
- // When something within the root is focused, stop from bubbling
107
- // to the doc and remove the “focused” state from the root.
108
- focusin: function( event ) {
109
- P.$root.removeClass( CLASSES.focused )
110
- event.stopPropagation()
111
- },
112
-
113
- // When something within the root holder is clicked, stop it
114
- // from bubbling to the doc.
115
- 'mousedown click': function( event ) {
116
-
117
- var target = event.target
118
-
119
- // Make sure the target isn’t the root holder so it can bubble up.
120
- if ( target != P.$root.children()[ 0 ] ) {
121
-
122
- event.stopPropagation()
90
+ P.component = new COMPONENT(P, SETTINGS)
123
91
 
124
- // * For mousedown events, cancel the default action in order to
125
- // prevent cases where focus is shifted onto external elements
126
- // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
127
- // Also, for Firefox, don’t prevent action on the `option` element.
128
- if ( event.type == 'mousedown' && !$( target ).is( ':input' ) && target.nodeName != 'OPTION' ) {
129
92
 
130
- event.preventDefault()
131
-
132
- // Re-focus onto the element so that users can click away
133
- // from elements focused within the picker.
134
- ELEMENT.focus()
135
- }
136
- }
137
- }
138
- }).
139
-
140
- // If there’s a click on an actionable element, carry out the actions.
141
- on( 'click', '[data-pick], [data-nav], [data-clear]', function() {
142
-
143
- var $target = $( this ),
144
- targetData = $target.data(),
145
- targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),
146
-
147
- // * For IE, non-focusable elements can be active elements as well
148
- // (http://stackoverflow.com/a/2684561).
149
- activeElement = document.activeElement
150
- activeElement = activeElement && ( activeElement.type || activeElement.href )
151
-
152
- // If it’s disabled or nothing inside is actively focused, re-focus the element.
153
- if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
154
- ELEMENT.focus()
155
- }
156
-
157
- // If something is superficially changed, update the `highlight` based on the `nav`.
158
- if ( targetData.nav && !targetDisabled ) {
159
- P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
160
- }
161
-
162
- // If something is picked, set `select` then close with focus.
163
- else if ( PickerConstructor._.isInteger( targetData.pick ) && !targetDisabled ) {
164
- P.set( 'select', targetData.pick ).close( true )
165
- }
166
-
167
- // If a “clear” button is pressed, empty the values and close with focus.
168
- else if ( targetData.clear ) {
169
- P.clear().close( true )
170
- }
171
- }) //P.$root
93
+ // Create the picker root with a holder and then prepare it.
94
+ P.$root = $( PickerConstructor._.node('div', createWrappedComponent(), CLASSES.picker, 'id="' + ELEMENT.id + '_root"') )
95
+ prepareElementRoot()
172
96
 
173
97
 
174
98
  // If there’s a format for the hidden input element, create the element.
175
99
  if ( SETTINGS.formatSubmit ) {
176
-
177
- P._hidden = $(
178
- '<input ' +
179
- 'type=hidden ' +
180
-
181
- // Create the name by using the original input plus a prefix and suffix.
182
- 'name="' + ( typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '' ) +
183
- ELEMENT.name +
184
- ( typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit' ) +
185
- '"' +
186
-
187
- // If the element has a value, set the hidden value as well.
188
- ( $ELEMENT.data( 'value' ) || ELEMENT.value ?
189
- ' value="' + PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.formatSubmit, P.component.item.select ] ) + '"' :
190
- ''
191
- ) +
192
- '>'
193
- )[ 0 ]
100
+ prepareElementHidden()
194
101
  }
195
102
 
196
103
 
197
104
  // Prepare the input element.
198
- $ELEMENT.
199
-
200
- // Store the picker data by component name.
201
- data( NAME, P ).
202
-
203
- // Add the “input” class name.
204
- addClass( CLASSES.input ).
205
-
206
- // If there’s a `data-value`, update the value of the element.
207
- val( $ELEMENT.data( 'value' ) ? PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.format, P.component.item.select ] ) : ELEMENT.value ).
208
-
209
- // Insert the hidden input after the element.
210
- after( P._hidden ).
211
-
212
- // On focus/click, open the picker and adjust the root “focused” state.
213
- on( 'focus.P' + STATE.id + ' click.P' + STATE.id, focusToOpen ).
214
-
215
- // If the value changes, update the hidden input with the correct format.
216
- on( 'change.P' + STATE.id, function() {
217
- if ( P._hidden ) {
218
- P._hidden.value = ELEMENT.value ? PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.formatSubmit, P.component.item.select ] ) : ''
219
- }
220
- })
221
-
222
- // Only bind keydown events if the element isn’t editable.
223
- if ( !SETTINGS.editable ) $ELEMENT.
224
-
225
- // Handle keyboard event based on the picker being opened or not.
226
- on( 'keydown.P' + STATE.id, function( event ) {
227
-
228
- var keycode = event.keyCode,
229
-
230
- // Check if one of the delete keys was pressed.
231
- isKeycodeDelete = /^(8|46)$/.test( keycode )
232
-
233
- // For some reason IE clears the input value on “escape”.
234
- if ( keycode == 27 ) {
235
- P.close()
236
- return false
237
- }
238
-
239
- // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
240
- if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[ keycode ] ) {
241
-
242
- // Prevent it from moving the page and bubbling to doc.
243
- event.preventDefault()
244
- event.stopPropagation()
245
-
246
- // If `delete` was pressed, clear the values and close the picker.
247
- // Otherwise open the picker.
248
- if ( isKeycodeDelete ) { P.clear().close() }
249
- else { P.open() }
250
- }
251
- })
105
+ prepareElement()
252
106
 
253
107
 
254
108
  // Insert the root as specified in the settings.
@@ -322,7 +176,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
322
176
  // the events (after a tick for IE - see `P.close`).
323
177
  $ELEMENT.removeClass( CLASSES.input ).removeData( NAME )
324
178
  setTimeout( function() {
325
- $ELEMENT.off( '.P' + STATE.id )
179
+ $ELEMENT.off( '.' + STATE.id )
326
180
  }, 0)
327
181
 
328
182
  // Restore the element state
@@ -350,9 +204,11 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
350
204
 
351
205
  // Add the “active” class.
352
206
  $ELEMENT.addClass( CLASSES.active )
207
+ aria( ELEMENT, 'expanded', true )
353
208
 
354
209
  // Add the “opened” class to the picker root.
355
210
  P.$root.addClass( CLASSES.opened )
211
+ aria( P.$root[0], 'hidden', false )
356
212
 
357
213
  // If we have to give focus, bind the element and doc events.
358
214
  if ( dontGiveFocus !== false ) {
@@ -364,7 +220,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
364
220
  $ELEMENT.trigger( 'focus' )
365
221
 
366
222
  // Bind the document events.
367
- $document.on( 'click.P' + STATE.id + ' focusin.P' + STATE.id, function( event ) {
223
+ $document.on( 'click.' + STATE.id + ' focusin.' + STATE.id, function( event ) {
368
224
 
369
225
  var target = event.target
370
226
 
@@ -372,14 +228,17 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
372
228
  // * Don’t worry about clicks or focusins on the root because those don’t bubble up.
373
229
  // Also, for Firefox, a click on an `option` element bubbles up directly
374
230
  // to the doc. So make sure the target wasn't the doc.
375
- if ( target != ELEMENT && target != document ) {
231
+ // * In Firefox stopPropagation() doesn’t prevent right-click events from bubbling,
232
+ // which causes the picker to unexpectedly close when right-clicking it. So make
233
+ // sure the event wasn’t a right-click.
234
+ if ( target != ELEMENT && target != document && event.which != 3 ) {
376
235
 
377
236
  // If the target was the holder that covers the screen,
378
237
  // keep the element focused to maintain tabindex.
379
238
  P.close( target === P.$root.children()[0] )
380
239
  }
381
240
 
382
- }).on( 'keydown.P' + STATE.id, function( event ) {
241
+ }).on( 'keydown.' + STATE.id, function( event ) {
383
242
 
384
243
  var
385
244
  // Get the keycode.
@@ -440,17 +299,20 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
440
299
  // ....ah yes! It would’ve been incomplete without a crazy workaround for IE :|
441
300
  // The focus is triggered *after* the close has completed - causing it
442
301
  // to open again. So unbind and rebind the event at the next tick.
443
- $ELEMENT.off( 'focus.P' + STATE.id ).trigger( 'focus' )
302
+ $ELEMENT.off( 'focus.' + STATE.id ).trigger( 'focus' )
444
303
  setTimeout( function() {
445
- $ELEMENT.on( 'focus.P' + STATE.id, focusToOpen )
304
+ $ELEMENT.on( 'focus.' + STATE.id, focusToOpen )
446
305
  }, 0 )
447
306
  }
448
307
 
449
308
  // Remove the “active” class.
450
309
  $ELEMENT.removeClass( CLASSES.active )
310
+ aria( ELEMENT, 'expanded', false )
451
311
 
452
312
  // Remove the “opened” and “focused” class from the picker root.
453
313
  P.$root.removeClass( CLASSES.opened + ' ' + CLASSES.focused )
314
+ aria( P.$root[0], 'hidden', true )
315
+ aria( P.$root[0], 'selected', false )
454
316
 
455
317
  // If it’s already closed, do nothing more.
456
318
  if ( !STATE.open ) return P
@@ -459,7 +321,7 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
459
321
  STATE.open = false
460
322
 
461
323
  // Unbind the document events.
462
- $document.off( '.P' + STATE.id )
324
+ $document.off( '.' + STATE.id )
463
325
 
464
326
  // Trigger the queued “close” events.
465
327
  return P.trigger( 'close' )
@@ -500,14 +362,14 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
500
362
  thingValue = thingObject[ thingItem ]
501
363
 
502
364
  // First, if the item exists and there’s a value, set it.
503
- if ( P.component.item[ thingItem ] ) {
365
+ if ( thingItem in P.component.item ) {
504
366
  P.component.set( thingItem, thingValue, options )
505
367
  }
506
368
 
507
369
  // Then, check to update the element value and broadcast a change.
508
370
  if ( thingItem == 'select' || thingItem == 'clear' ) {
509
- $ELEMENT.val( thingItem == 'clear' ? '' :
510
- PickerConstructor._.trigger( P.component.formats.toString, P.component, [ SETTINGS.format, P.component.get( thingItem ) ] )
371
+ $ELEMENT.val( thingItem == 'clear' ?
372
+ '' : P.get( thingItem, SETTINGS.format )
511
373
  ).trigger( 'change' )
512
374
  }
513
375
  }
@@ -540,9 +402,13 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
540
402
  }
541
403
 
542
404
  // Check if a component item exists, return that.
543
- if ( P.component.item[ thing ] ) {
405
+ if ( thing in P.component.item ) {
544
406
  if ( typeof format == 'string' ) {
545
- return PickerConstructor._.trigger( P.component.formats.toString, P.component, [ format, P.component.get( thing ) ] )
407
+ return PickerConstructor._.trigger(
408
+ P.component.formats.toString,
409
+ P.component,
410
+ [ format, P.component.get( thing ) ]
411
+ )
546
412
  }
547
413
  return P.component.get( thing )
548
414
  }
@@ -584,6 +450,23 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
584
450
  }, //on
585
451
 
586
452
 
453
+
454
+ /**
455
+ * Unbind events on the things.
456
+ */
457
+ off: function() {
458
+ var i, thingName,
459
+ names = arguments;
460
+ for ( i = 0, namesCount = names.length; i < namesCount; i += 1 ) {
461
+ thingName = names[i]
462
+ if ( thingName in STATE.methods ) {
463
+ delete STATE.methods[thingName]
464
+ }
465
+ }
466
+ return P
467
+ },
468
+
469
+
587
470
  /**
588
471
  * Fire off method events.
589
472
  */
@@ -637,6 +520,196 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
637
520
  } //createWrappedComponent
638
521
 
639
522
 
523
+
524
+ /**
525
+ * Prepare the input element with all bindings.
526
+ */
527
+ function prepareElement() {
528
+
529
+ $ELEMENT.
530
+
531
+ // Store the picker data by component name.
532
+ data(NAME, P).
533
+
534
+ // Add the “input” class name.
535
+ addClass(CLASSES.input).
536
+
537
+ // If there’s a `data-value`, update the value of the element.
538
+ val( $ELEMENT.data('value') ?
539
+ P.get('select', SETTINGS.format) :
540
+ ELEMENT.value
541
+ ).
542
+
543
+ // On focus/click, open the picker and adjust the root “focused” state.
544
+ on('focus.' + STATE.id + ' click.' + STATE.id, focusToOpen)
545
+
546
+
547
+ // Only bind keydown events if the element isn’t editable.
548
+ if ( !SETTINGS.editable ) {
549
+
550
+ // Handle keyboard event based on the picker being opened or not.
551
+ $ELEMENT.on('keydown.' + STATE.id, function(event) {
552
+
553
+ var keycode = event.keyCode,
554
+
555
+ // Check if one of the delete keys was pressed.
556
+ isKeycodeDelete = /^(8|46)$/.test(keycode)
557
+
558
+ // For some reason IE clears the input value on “escape”.
559
+ if ( keycode == 27 ) {
560
+ P.close()
561
+ return false
562
+ }
563
+
564
+ // Check if `space` or `delete` was pressed or the picker is closed with a key movement.
565
+ if ( keycode == 32 || isKeycodeDelete || !STATE.open && P.component.key[keycode] ) {
566
+
567
+ // Prevent it from moving the page and bubbling to doc.
568
+ event.preventDefault()
569
+ event.stopPropagation()
570
+
571
+ // If `delete` was pressed, clear the values and close the picker.
572
+ // Otherwise open the picker.
573
+ if ( isKeycodeDelete ) { P.clear().close() }
574
+ else { P.open() }
575
+ }
576
+ })
577
+ }
578
+
579
+
580
+ // Update the aria attributes.
581
+ aria(ELEMENT, {
582
+ haspopup: true,
583
+ expanded: false,
584
+ readonly: false,
585
+ owns: ELEMENT.id + '_root' + (P._hidden ? ' ' + P._hidden.id : '')
586
+ })
587
+ }
588
+
589
+
590
+ /**
591
+ * Prepare the root picker element with all bindings.
592
+ */
593
+ function prepareElementRoot() {
594
+
595
+ P.$root.
596
+
597
+ on({
598
+
599
+ // When something within the root is focused, stop from bubbling
600
+ // to the doc and remove the “focused” state from the root.
601
+ focusin: function( event ) {
602
+ P.$root.removeClass( CLASSES.focused )
603
+ aria( P.$root[0], 'selected', false )
604
+ event.stopPropagation()
605
+ },
606
+
607
+ // When something within the root holder is clicked, stop it
608
+ // from bubbling to the doc.
609
+ 'mousedown click': function( event ) {
610
+
611
+ var target = event.target
612
+
613
+ // Make sure the target isn’t the root holder so it can bubble up.
614
+ if ( target != P.$root.children()[ 0 ] ) {
615
+
616
+ event.stopPropagation()
617
+
618
+ // * For mousedown events, cancel the default action in order to
619
+ // prevent cases where focus is shifted onto external elements
620
+ // when using things like jQuery mobile or MagnificPopup (ref: #249 & #120).
621
+ // Also, for Firefox, don’t prevent action on the `option` element.
622
+ if ( event.type == 'mousedown' && !$( target ).is( ':input' ) && target.nodeName != 'OPTION' ) {
623
+
624
+ event.preventDefault()
625
+
626
+ // Re-focus onto the element so that users can click away
627
+ // from elements focused within the picker.
628
+ ELEMENT.focus()
629
+ }
630
+ }
631
+ }
632
+ }).
633
+
634
+ // If there’s a click on an actionable element, carry out the actions.
635
+ on( 'click', '[data-pick], [data-nav], [data-clear]', function() {
636
+
637
+ var $target = $( this ),
638
+ targetData = $target.data(),
639
+ targetDisabled = $target.hasClass( CLASSES.navDisabled ) || $target.hasClass( CLASSES.disabled ),
640
+
641
+ // * For IE, non-focusable elements can be active elements as well
642
+ // (http://stackoverflow.com/a/2684561).
643
+ activeElement = document.activeElement
644
+ activeElement = activeElement && ( activeElement.type || activeElement.href ) && activeElement
645
+
646
+ // If it’s disabled or nothing inside is actively focused, re-focus the element.
647
+ if ( targetDisabled || activeElement && !$.contains( P.$root[0], activeElement ) ) {
648
+ ELEMENT.focus()
649
+ }
650
+
651
+ // If something is superficially changed, update the `highlight` based on the `nav`.
652
+ if ( targetData.nav && !targetDisabled ) {
653
+ P.set( 'highlight', P.component.item.highlight, { nav: targetData.nav } )
654
+ }
655
+
656
+ // If something is picked, set `select` then close with focus.
657
+ else if ( PickerConstructor._.isInteger( targetData.pick ) && !targetDisabled ) {
658
+ P.set( 'select', targetData.pick ).close( true )
659
+ }
660
+
661
+ // If a “clear” button is pressed, empty the values and close with focus.
662
+ else if ( targetData.clear ) {
663
+ P.clear().close( true )
664
+ }
665
+ }) //P.$root
666
+
667
+ aria( P.$root[0], 'hidden', true )
668
+ }
669
+
670
+
671
+ /**
672
+ * Prepare the hidden input element along with all bindings.
673
+ */
674
+ function prepareElementHidden() {
675
+
676
+ var id = [
677
+ typeof SETTINGS.hiddenPrefix == 'string' ? SETTINGS.hiddenPrefix : '',
678
+ typeof SETTINGS.hiddenSuffix == 'string' ? SETTINGS.hiddenSuffix : '_submit'
679
+ ]
680
+
681
+ P._hidden = $(
682
+ '<input ' +
683
+ 'type=hidden ' +
684
+
685
+ // Create the name and ID by using the original
686
+ // input’s with a prefix and suffix.
687
+ 'name="' + id[0] + ELEMENT.name + id[1] + '"' +
688
+ 'id="' + id[0] + ELEMENT.id + id[1] + '"' +
689
+
690
+ // If the element has a value, set the hidden value as well.
691
+ (
692
+ $ELEMENT.data('value') || ELEMENT.value ?
693
+ ' value="' + P.get('select', SETTINGS.formatSubmit) + '"' :
694
+ ''
695
+ ) +
696
+ '>'
697
+ )[0]
698
+
699
+ $ELEMENT.
700
+
701
+ // If the value changes, update the hidden input with the correct format.
702
+ on('change.' + STATE.id, function() {
703
+ P._hidden.value = ELEMENT.value ?
704
+ P.get('select', SETTINGS.formatSubmit) :
705
+ ''
706
+ }).
707
+
708
+ // Insert the hidden input after the element.
709
+ after(P._hidden)
710
+ }
711
+
712
+
640
713
  // Separated for IE
641
714
  function focusToOpen( event ) {
642
715
 
@@ -644,7 +717,10 @@ function PickerConstructor( ELEMENT, NAME, COMPONENT, OPTIONS ) {
644
717
  event.stopPropagation()
645
718
 
646
719
  // If it’s a focus event, add the “focused” class to the root.
647
- if ( event.type == 'focus' ) P.$root.addClass( CLASSES.focused )
720
+ if ( event.type == 'focus' ) {
721
+ P.$root.addClass( CLASSES.focused )
722
+ aria( P.$root[0], 'selected', true )
723
+ }
648
724
 
649
725
  // And then finally open the picker.
650
726
  P.open()
@@ -791,7 +867,13 @@ PickerConstructor._ = {
791
867
  */
792
868
  isInteger: function( value ) {
793
869
  return {}.toString.call( value ).indexOf( 'Number' ) > -1 && value % 1 === 0
794
- }
870
+ },
871
+
872
+
873
+ /**
874
+ * Create ARIA attribute strings.
875
+ */
876
+ ariaAttr: ariaAttr
795
877
  } //PickerConstructor._
796
878
 
797
879
 
@@ -835,6 +917,37 @@ PickerConstructor.extend = function( name, Component ) {
835
917
 
836
918
 
837
919
 
920
+ function aria(element, attribute, value) {
921
+ if ( $.isPlainObject(attribute) ) {
922
+ for ( var key in attribute ) {
923
+ ariaSet(element, key, attribute[key])
924
+ }
925
+ }
926
+ else {
927
+ ariaSet(element, attribute, value)
928
+ }
929
+ }
930
+ function ariaSet(element, attribute, value) {
931
+ element.setAttribute(
932
+ (attribute == 'role' ? '' : 'aria-') + attribute,
933
+ value
934
+ )
935
+ }
936
+ function ariaAttr(attribute, data) {
937
+ if ( !$.isPlainObject(attribute) ) {
938
+ attribute = { attribute: data }
939
+ }
940
+ data = ''
941
+ for ( var key in attribute ) {
942
+ var attr = (key == 'role' ? '' : 'aria-') + key,
943
+ attrVal = attribute[key]
944
+ data += attrVal == null ? '' : attr + '="' + attribute[key] + '"'
945
+ }
946
+ return data
947
+ }
948
+
949
+
950
+
838
951
  // Expose the picker constructor.
839
952
  return PickerConstructor
840
953