fleetio_spark 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +43 -0
  4. data/app/assets/javascripts/spark/_cookie.js +23 -0
  5. data/app/assets/javascripts/spark/_esvg.js +73 -0
  6. data/app/assets/javascripts/spark/_icons.js +19 -0
  7. data/app/assets/javascripts/spark/_modal.js +421 -0
  8. data/app/assets/javascripts/spark/_search.js +79 -0
  9. data/app/assets/javascripts/spark/_sidebar.js +13 -0
  10. data/app/assets/javascripts/spark/_stack.js +248 -0
  11. data/app/assets/javascripts/spark/_tree.js +68 -0
  12. data/app/assets/javascripts/spark/shims/_dataset.js +241 -0
  13. data/app/assets/javascripts/spark/spark.js +31 -0
  14. data/app/assets/stylesheets/spark/_icons.scss +15 -0
  15. data/app/assets/stylesheets/spark/_index.scss +5 -0
  16. data/app/assets/stylesheets/spark/components/_index.scss +6 -0
  17. data/app/assets/stylesheets/spark/components/_modal.scss +73 -0
  18. data/app/assets/stylesheets/spark/components/_nav-menu.scss +102 -0
  19. data/app/assets/stylesheets/spark/components/_sidebar.scss +280 -0
  20. data/app/assets/stylesheets/spark/components/_tooltip.scss +42 -0
  21. data/app/assets/stylesheets/spark/components/_tree-nav.scss +13 -0
  22. data/app/assets/stylesheets/spark/components/header/_app-admin-header.scss +82 -0
  23. data/app/assets/stylesheets/spark/components/header/_app-nav-header.scss +154 -0
  24. data/app/assets/stylesheets/spark/components/header/_index.scss +5 -0
  25. data/app/assets/stylesheets/spark/components/header/_search-results.scss +29 -0
  26. data/app/assets/stylesheets/spark/components/header/_search.scss +195 -0
  27. data/app/assets/stylesheets/spark/components/header/_trial-status.scss +43 -0
  28. data/app/assets/stylesheets/spark/core/_animations.scss +154 -0
  29. data/app/assets/stylesheets/spark/core/_base.scss +11 -0
  30. data/app/assets/stylesheets/spark/core/_colors.scss +94 -0
  31. data/app/assets/stylesheets/spark/core/_index.scss +7 -0
  32. data/app/assets/stylesheets/spark/core/_layout.scss +39 -0
  33. data/app/assets/stylesheets/spark/core/_mixins.scss +86 -0
  34. data/app/assets/stylesheets/spark/core/_text.scss +5 -0
  35. data/app/assets/stylesheets/spark/core/_vars.scss +76 -0
  36. data/app/assets/stylesheets/spark/form/_base.scss +124 -0
  37. data/app/assets/stylesheets/spark/form/_check-switch.scss +99 -0
  38. data/app/assets/stylesheets/spark/form/_index.scss +2 -0
  39. data/app/assets/stylesheets/spark/spark.scss +1 -0
  40. data/app/assets/svgs/spark/add.svg +3 -0
  41. data/app/assets/svgs/spark/admin-user.svg +5 -0
  42. data/app/assets/svgs/spark/chevron-down.svg +3 -0
  43. data/app/assets/svgs/spark/chevron-up.svg +3 -0
  44. data/app/assets/svgs/spark/close.svg +3 -0
  45. data/app/assets/svgs/spark/contact.svg +6 -0
  46. data/app/assets/svgs/spark/dashboard.svg +3 -0
  47. data/app/assets/svgs/spark/fuel.svg +3 -0
  48. data/app/assets/svgs/spark/inspection.svg +3 -0
  49. data/app/assets/svgs/spark/issue.svg +3 -0
  50. data/app/assets/svgs/spark/leaderboard.svg +5 -0
  51. data/app/assets/svgs/spark/logo.svg +1 -0
  52. data/app/assets/svgs/spark/map.svg +4 -0
  53. data/app/assets/svgs/spark/nav-menu.svg +5 -0
  54. data/app/assets/svgs/spark/part.svg +6 -0
  55. data/app/assets/svgs/spark/place.svg +5 -0
  56. data/app/assets/svgs/spark/question.svg +10 -0
  57. data/app/assets/svgs/spark/reminder.svg +3 -0
  58. data/app/assets/svgs/spark/report.svg +5 -0
  59. data/app/assets/svgs/spark/search.svg +3 -0
  60. data/app/assets/svgs/spark/service.svg +3 -0
  61. data/app/assets/svgs/spark/settings.svg +3 -0
  62. data/app/assets/svgs/spark/trip.svg +4 -0
  63. data/app/assets/svgs/spark/vehicle.svg +3 -0
  64. data/app/assets/svgs/spark/vendor.svg +4 -0
  65. data/app/helpers/spark/application_helper.rb +42 -0
  66. data/app/helpers/spark/icon_helper.rb +40 -0
  67. data/app/helpers/spark/input_helper.rb +163 -0
  68. data/app/helpers/spark/menu_helper.rb +113 -0
  69. data/app/helpers/spark/modal_helper.rb +52 -0
  70. data/app/helpers/spark/nav_menu_helper.rb +167 -0
  71. data/app/helpers/spark/tag_helper.rb +27 -0
  72. data/app/helpers/spark/trial_helper.rb +23 -0
  73. data/app/views/layouts/spark/application.html.slim +28 -0
  74. data/app/views/layouts/spark/blank.html.slim +9 -0
  75. data/app/views/layouts/spark/default.html.slim +13 -0
  76. data/config/autoprefixer.yml +4 -0
  77. data/config/esvg.yml +20 -0
  78. data/lib/fleetio_spark.rb +14 -0
  79. data/lib/fleetio_spark/helper.rb +163 -0
  80. data/lib/fleetio_spark/version.rb +3 -0
  81. data/public/spark-0.1.0.css +1442 -0
  82. data/public/spark-0.1.0.css.gz +0 -0
  83. data/public/spark-0.1.0.js +11927 -0
  84. data/public/spark-0.1.0.js.gz +0 -0
  85. metadata +211 -0
@@ -0,0 +1,79 @@
1
+ var toolbox = require( 'compose-toolbox' ),
2
+ Event = toolbox.event,
3
+ modal = require( './_modal' ),
4
+ stack = require( './_stack' )
5
+
6
+ // Focus on search when `/` key is pressed
7
+ Event.keyOn( '/', function(event){
8
+ var q = document.querySelector('[name=q]')
9
+ if ( q ) {
10
+ event.preventDefault()
11
+ q.focus()
12
+ }
13
+ })
14
+
15
+ // Reset search results panel when query is cleared
16
+ Event.ready( function() {
17
+ var searchStack = stack.get( '#search-stack' )
18
+ var q = document.querySelector('[name=q]')
19
+ var searchModal = modal.get( '#search-results' )
20
+
21
+ if ( !searchModal || !q ) return
22
+
23
+ function keyWatcher ( event ) {
24
+ var focusEl = document.activeElement
25
+
26
+ // If a letter, number, or backspace is pressed focus on search input
27
+ if ( focusEl != q && event.key.length === 1 || Event.key.isPressed("backspace") ) {
28
+ q.focus()
29
+
30
+ // Restore default search input behavior
31
+ // When foucsed, esc clears input
32
+ } else if ( focusEl == q && Event.key.isPressed( "esc" ) ) {
33
+ q.value = ''
34
+ }
35
+ }
36
+
37
+ // Scroll all navs back to top
38
+ function resetScroll () {
39
+ toolbox.each( searchStack.root.querySelectorAll( 'nav' ), function (nav) {
40
+ nav.scrollTop = 0
41
+ })
42
+ }
43
+
44
+ searchModal.on( 'open', function() {
45
+ Event.on( document, 'keydown', keyWatcher )
46
+ })
47
+
48
+ // Reset stack when modal is closed and query is clear
49
+ searchModal.on( 'close', function() {
50
+ // Restore state of search panel
51
+ if ( document.querySelector( 'input[name=q]' ).value == '' ) {
52
+ searchStack.reset()
53
+ resetScroll()
54
+ }
55
+
56
+ Event.off( document, 'keydown', keyWatcher )
57
+ })
58
+
59
+ // Show first panel stack when query is cleared
60
+ Event.on( document, 'input', '[name=q]', Event.debounce( function ( event ) {
61
+ if ( event.currentTarget.value == '' ) {
62
+ searchStack.show( 0 )
63
+ resetScroll()
64
+ }
65
+ }, 50 ))
66
+
67
+ // Wire up clear search input behavior
68
+ Event.on( document, 'click', '.clear-search', function( event ) {
69
+ var button = event.currentTarget
70
+ event.preventDefault()
71
+ event.stopImmediatePropagation()
72
+
73
+ var searchInput = toolbox.getClosest( button, 'label' ).querySelector( '.input-container input' )
74
+ searchInput.value = ''
75
+ Event.fire( searchInput, 'input' )
76
+
77
+ searchInput.focus()
78
+ }, { useCapture: true })
79
+ })
@@ -0,0 +1,13 @@
1
+ var toolbox = require( 'compose-toolbox' ),
2
+ cookie = require( './_cookie' ),
3
+ Event = toolbox.event
4
+
5
+ Event.ready( function() {
6
+ Event.on( document, 'click', '.sidebar-nav-toggle', function( event ) {
7
+ document.body.classList.toggle( 'hide-sidebar' )
8
+
9
+ var visible = !document.body.classList.contains( 'hide-sidebar' )
10
+ cookie.update( 'sidebar-nav', { sidebar: visible } )
11
+ })
12
+ if ( cookie.get( 'sidebar-nav' ).sidebar === false ) document.body.classList.add( 'hide-sidebar' )
13
+ })
@@ -0,0 +1,248 @@
1
+ var toolbox = require( 'compose-toolbox' ),
2
+ Event = toolbox.event,
3
+ Stacks = []
4
+
5
+ var Stack = {
6
+ new: function( root ) {
7
+
8
+ if ( !isElement( root ) ) root = document.querySelector( root )
9
+
10
+ // Don't add a new stack if no element found or if stack already exists
11
+ if ( !isElement( root ) || root.dataset.stackId ) return
12
+
13
+ // Set up stack by searching DOM node under it.
14
+ // The first child found will be set up as a default
15
+ // After that each child will be added as a stack push
16
+
17
+ var stack = {
18
+ root: root,
19
+ current: 0,
20
+ panels: [],
21
+ focus: [],
22
+
23
+ add: function ( panelEl, name ) {
24
+ panelEl.dataset.stackIndex = stack.panels.length
25
+
26
+ // Hide all panels but the first panel
27
+ if ( stack.panels.length == 0 ) {
28
+ showEl( panelEl )
29
+ } else {
30
+ hideEl( panelEl )
31
+ }
32
+
33
+ stack.panels.push( panelEl )
34
+ },
35
+
36
+ // Reset stack to first panel
37
+ reset: function () {
38
+
39
+ // Already reset? We're done.
40
+ if ( stack.current == 0 ) return
41
+
42
+ // If hidden, manually reset all
43
+ if ( stack.root.offsetParent == null ) {
44
+ stack.focus = [] // Clear focus stack
45
+
46
+ // Hide and reset scroll on all panels
47
+ stack.panels.forEach( function( el ) {
48
+ el.scrollTop = 0
49
+ hideEl( el )
50
+ })
51
+
52
+ // Show first panel
53
+ stack.current = 0
54
+ showEl( stack.panels[0] )
55
+ }
56
+
57
+ // If not hidden, dismiss current panel and reset to first panel
58
+ else stack.show( 0 )
59
+ },
60
+
61
+ // Show a specific panel, hiding all others
62
+ show: function ( name ) {
63
+ var panel = stack.findPanel( name )
64
+
65
+ // If a panel isn't the current panel
66
+ // dismiss it before going to the requested panel
67
+ if ( panel && panel != stack.currentPanel() ) {
68
+
69
+ // Dismiss the current panel and with the callback load in the next panel
70
+ return stack.dismiss( stack.direction( panel ), function() {
71
+ stack.enter( panel )
72
+ })
73
+
74
+ }
75
+ },
76
+
77
+ enter: function ( el ) {
78
+ var direction = stack.direction( el )
79
+ el.dataset.direction = direction
80
+ stack.current = Number( el.dataset.stackIndex )
81
+
82
+ Event.afterAnimation( el, function() {
83
+ el.dataset.direction = ''
84
+ el.classList.remove( 'enter' )
85
+ }, 50 )
86
+
87
+ el.classList.add( 'enter' )
88
+ showEl( el )
89
+
90
+ if ( direction == 'forward' ) {
91
+ // If the previous focused element was in the stack
92
+ //if ( toolbox.childOf( stack.lastFocus(), stack.root ) ) {
93
+
94
+ // focus on the first input
95
+ //var firstItem = el.querySelector( 'input:not([hidden]), textarea, select, a[tabindex]' )
96
+ //if ( firstItem ) firstItem.focus()
97
+
98
+ //}
99
+
100
+ // Focus on the previous focused element
101
+ } else if ( direction == 'reverse' && stack.focus.length > 0 ) {
102
+ stack.focus.pop().focus()
103
+ }
104
+ },
105
+
106
+ dismiss: function ( direction, callback ) {
107
+
108
+ var el = stack.currentPanel()
109
+
110
+ if ( direction == 'forward' )
111
+ stack.focus.push( document.activeElement )
112
+
113
+ el.classList.remove( 'enter' )
114
+ el.classList.add( 'exit' )
115
+ el.dataset.direction = direction
116
+
117
+ Event.afterAnimation( el, function() {
118
+ // Ensure panel is scrolled to top
119
+ if ( direction == 'reverse' ) el.scrollTop = 0
120
+
121
+ hideEl( el )
122
+ el.classList.remove( 'exit' )
123
+ el.dataset.direction = ''
124
+
125
+ if ( typeof callback === 'function' ) callback()
126
+
127
+ }, 50 )
128
+
129
+ },
130
+
131
+ lastFocus: function() {
132
+ return stack.focus[ stack.focus.length -1 ]
133
+ },
134
+
135
+ // Find a panel by name or index
136
+ findPanel: function ( name ) {
137
+ if ( isElement( name ) )
138
+ return name
139
+ else
140
+ return root.querySelector( '[data-stack="'+name+'"], [data-stack-index="'+name+'"]' )
141
+ },
142
+
143
+ // Walk backwards in the stack, showing the panel previous to the current panel
144
+ back: function () {
145
+ stack.show( stack.panels[ stack.current - 1 ] )
146
+ },
147
+
148
+ // add aria-hidden=false to the first hidden element
149
+ next: function () {
150
+ stack.show( stack.panels[ stack.current + 1 ] )
151
+ },
152
+
153
+ direction: function( panel ) {
154
+ var index = stack.panelIndex( panel )
155
+
156
+ if ( index == stack.current ) return 'none'
157
+ else return ( stack.current < index ) ? 'forward' : 'reverse'
158
+
159
+ },
160
+
161
+ currentPanel: function ( name ) {
162
+ if ( name ) {
163
+ return stack.panels[ stack.current ] == stack.findPanel( name )
164
+ }
165
+ return stack.panels[ stack.current ]
166
+ },
167
+
168
+ panelIndex: function ( panel ) {
169
+ return Number( panel.dataset.stackIndex )
170
+ }
171
+ }
172
+
173
+ // Add a queryable stack id
174
+ root.dataset.stackId = Stacks.length
175
+
176
+ Stacks.push( stack )
177
+ toolbox.each( root.children, stack.add )
178
+
179
+ return stack
180
+ }
181
+ }
182
+
183
+ function showEl ( el ) {
184
+ el.setAttribute( 'aria-hidden', false )
185
+ }
186
+
187
+ function hideEl ( el ) {
188
+ el.setAttribute( 'aria-hidden', true )
189
+ }
190
+
191
+ function isElement ( item ) {
192
+ return item.constructor.toString().search(/HTML.+Element/) > -1
193
+ }
194
+
195
+ function navClick ( event ) {
196
+ var el = event.currentTarget
197
+ var panel = el.dataset.stackNav
198
+ var stack = getStack( toolbox.getClosest( el, '[data-stack="root"]' ) )
199
+ if ( !stack ) return
200
+
201
+ if ( panel == 'next' ) stack.next()
202
+ else if ( panel == 'back' ) stack.back()
203
+ else if ( stack.findPanel( panel ) ) stack.show( panel )
204
+ else return
205
+
206
+ //event.preventDefault()
207
+ }
208
+
209
+ function prevPanel ( event ) {
210
+ var stack = getStack( toolbox.getClosest( event.target, '[data-stack="root"]' ) )
211
+ stack.back()
212
+ event.preventDefault()
213
+ }
214
+
215
+ function setup () {
216
+ Event.change( function() {
217
+ toolbox.each( document.querySelectorAll( '[data-stack="root"]'), Stack.new )
218
+ })
219
+
220
+ // Localise clicks to a stack root
221
+ // Future: consider expanding this to allow controls to live anywhere and point to a stack
222
+ Event.on( document, 'click', '[data-stack="root"] [data-stack-nav]', navClick )
223
+ }
224
+
225
+ function getStack ( search ) {
226
+
227
+ if ( isElement( search ) ) {
228
+ if ( search.dataset.stackId ) return Stacks[ search.dataset.stackId ]
229
+ }
230
+
231
+ if ( typeof search === 'string' ) {
232
+
233
+ // Well, let's hope it's a DOM selector
234
+ var stack = document.querySelector( search )
235
+
236
+ // If it is woo hoo
237
+ if ( stack && stack.dataset.stackId ) {
238
+ return Stacks[ stack.dataset.stackId ]
239
+ }
240
+ }
241
+ }
242
+
243
+ module.exports = {
244
+ new: Stack.new,
245
+ setup: setup,
246
+ get: getStack,
247
+ stacks: Stacks
248
+ }
@@ -0,0 +1,68 @@
1
+ // Purpose: Toggles expansion of tree nav and stores cookies to retain state
2
+ //
3
+ // Usage:
4
+ // - Trees must have a [data-tree='tree-key']
5
+ // - Nodes are named by [data-node='node-name']
6
+ //
7
+ // Cookies are stored as tree-key => { node-name: true }
8
+ // node boolean matches aria-expanded state
9
+ //
10
+
11
+ var toolbox = require( 'compose-toolbox' ),
12
+ cookie = require( './_cookie' ),
13
+ Event = toolbox.event
14
+
15
+ function setCookie ( node ) {
16
+ if ( node.dataset.node ) {
17
+ var tree = toolbox.getClosest( node, '[data-tree]' )
18
+ if ( tree ) {
19
+ data = {}
20
+ data[ node.dataset.node ] = node.getAttribute( 'aria-expanded' ) == 'true'
21
+ cookie.update( tree.dataset.tree, data )
22
+ }
23
+ }
24
+ }
25
+
26
+ function click ( event ) {
27
+ var target = event.currentTarget,
28
+ parent = target.parentElement,
29
+ expanded = parent.getAttribute( 'aria-expanded' )
30
+
31
+ // Add a classname to indicate that it was manually triggered
32
+ // This allows for animating interactions, but not other attribute changes
33
+ parent.classList.add( 'triggered' )
34
+ parent.setAttribute( 'aria-expanded', expanded != 'true' )
35
+
36
+ setCookie( parent )
37
+ }
38
+
39
+ // Sets tree nav state based off of cookies
40
+ function restoreNav () {
41
+ toolbox.each( document.querySelectorAll( '[data-tree]' ), function( tree ) {
42
+ var key = tree.dataset.tree
43
+ if ( key ) {
44
+ var data = cookie.get( key )
45
+
46
+ Object.keys( data ).forEach( function( node ) {
47
+ var nodeEl = tree.querySelector( '[data-node='+node+']' )
48
+ // Use cookies to expand.
49
+ if ( nodeEl && data[node] == 'true' ) nodeEl.setAttribute( 'aria-expanded', data[node] )
50
+ })
51
+ }
52
+ })
53
+ }
54
+
55
+ function setup() {
56
+ Event.ready( function() {
57
+
58
+ // The first child of a node is the trigger
59
+ Event.on( document, 'click', '[aria-expanded] > *:first-child', click )
60
+
61
+ // Set states according to cookies
62
+ //restoreNav()
63
+ })
64
+ }
65
+
66
+ module.exports = {
67
+ setup: setup
68
+ }
@@ -0,0 +1,241 @@
1
+ /**
2
+ * Add dataset support to elements
3
+ * No globals, no overriding prototype with non-standard methods,
4
+ * handles CamelCase properly, attempts to use standard
5
+ * Object.defineProperty() (and Function bind()) methods,
6
+ * falls back to native implementation when existing
7
+ * Inspired by http://code.eligrey.com/html5/dataset/
8
+ * (via https://github.com/adalgiso/html5-dataset/blob/master/html5-dataset.js )
9
+ * Depends on Function.bind and Object.defineProperty/Object.getOwnPropertyDescriptor (shims below)
10
+ * Licensed under the X11/MIT License
11
+ */
12
+
13
+ // Inspired by https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind#Compatibility
14
+ if (!Function.prototype.bind) {
15
+ Function.prototype.bind = function (oThis) {
16
+ 'use strict';
17
+ if (typeof this !== "function") {
18
+ // closest thing possible to the ECMAScript 5 internal IsCallable function
19
+ throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
20
+ }
21
+
22
+ var aArgs = Array.prototype.slice.call(arguments, 1),
23
+ fToBind = this,
24
+ FNOP = function () {},
25
+ fBound = function () {
26
+ return fToBind.apply(
27
+ this instanceof FNOP && oThis ? this : oThis,
28
+ aArgs.concat(Array.prototype.slice.call(arguments))
29
+ );
30
+ };
31
+
32
+ FNOP.prototype = this.prototype;
33
+ fBound.prototype = new FNOP();
34
+
35
+ return fBound;
36
+ };
37
+ }
38
+
39
+ /*
40
+ * Xccessors Standard: Cross-browser ECMAScript 5 accessors
41
+ * http://purl.eligrey.com/github/Xccessors
42
+ *
43
+ * 2010-06-21
44
+ *
45
+ * By Eli Grey, http://eligrey.com
46
+ *
47
+ * A shim that partially implements Object.defineProperty,
48
+ * Object.getOwnPropertyDescriptor, and Object.defineProperties in browsers that have
49
+ * legacy __(define|lookup)[GS]etter__ support.
50
+ *
51
+ * Licensed under the X11/MIT License
52
+ * See LICENSE.md
53
+ */
54
+
55
+ // Removed a few JSLint options as Notepad++ JSLint validator complaining and
56
+ // made comply with JSLint; also moved 'use strict' inside function
57
+ /*jslint white: true, undef: true, plusplus: true,
58
+ bitwise: true, regexp: true, newcap: true, maxlen: 90 */
59
+
60
+ /*! @source http://purl.eligrey.com/github/Xccessors/blob/master/xccessors-standard.js*/
61
+
62
+ (function () {
63
+ 'use strict';
64
+ var ObjectProto = Object.prototype,
65
+ defineGetter = ObjectProto.__defineGetter__,
66
+ defineSetter = ObjectProto.__defineSetter__,
67
+ lookupGetter = ObjectProto.__lookupGetter__,
68
+ lookupSetter = ObjectProto.__lookupSetter__,
69
+ hasOwnProp = ObjectProto.hasOwnProperty;
70
+
71
+ if (defineGetter && defineSetter && lookupGetter && lookupSetter) {
72
+
73
+ if (!Object.defineProperty) {
74
+ Object.defineProperty = function (obj, prop, descriptor) {
75
+ if (arguments.length < 3) { // all arguments required
76
+ throw new TypeError("Arguments not optional");
77
+ }
78
+
79
+ prop += ""; // convert prop to string
80
+
81
+ if (hasOwnProp.call(descriptor, "value")) {
82
+ if (!lookupGetter.call(obj, prop) && !lookupSetter.call(obj, prop)) {
83
+ // data property defined and no pre-existing accessors
84
+ obj[prop] = descriptor.value;
85
+ }
86
+
87
+ if ((hasOwnProp.call(descriptor, "get") ||
88
+ hasOwnProp.call(descriptor, "set")))
89
+ {
90
+ // descriptor has a value prop but accessor already exists
91
+ throw new TypeError("Cannot specify an accessor and a value");
92
+ }
93
+ }
94
+
95
+ // can't switch off these features in ECMAScript 3
96
+ // so throw a TypeError if any are false
97
+ if (!(descriptor.writable && descriptor.enumerable &&
98
+ descriptor.configurable))
99
+ {
100
+ throw new TypeError(
101
+ "This implementation of Object.defineProperty does not support" +
102
+ " false for configurable, enumerable, or writable."
103
+ );
104
+ }
105
+
106
+ if (descriptor.get) {
107
+ defineGetter.call(obj, prop, descriptor.get);
108
+ }
109
+ if (descriptor.set) {
110
+ defineSetter.call(obj, prop, descriptor.set);
111
+ }
112
+
113
+ return obj;
114
+ };
115
+ }
116
+
117
+ if (!Object.getOwnPropertyDescriptor) {
118
+ Object.getOwnPropertyDescriptor = function (obj, prop) {
119
+ if (arguments.length < 2) { // all arguments required
120
+ throw new TypeError("Arguments not optional.");
121
+ }
122
+
123
+ prop += ""; // convert prop to string
124
+
125
+ var descriptor = {
126
+ configurable: true,
127
+ enumerable : true,
128
+ writable : true
129
+ },
130
+ getter = lookupGetter.call(obj, prop),
131
+ setter = lookupSetter.call(obj, prop);
132
+
133
+ if (!hasOwnProp.call(obj, prop)) {
134
+ // property doesn't exist or is inherited
135
+ return descriptor;
136
+ }
137
+ if (!getter && !setter) { // not an accessor so return prop
138
+ descriptor.value = obj[prop];
139
+ return descriptor;
140
+ }
141
+
142
+ // there is an accessor, remove descriptor.writable;
143
+ // populate descriptor.get and descriptor.set (IE's behavior)
144
+ delete descriptor.writable;
145
+ descriptor.get = descriptor.set = undefined;
146
+
147
+ if (getter) {
148
+ descriptor.get = getter;
149
+ }
150
+ if (setter) {
151
+ descriptor.set = setter;
152
+ }
153
+
154
+ return descriptor;
155
+ };
156
+ }
157
+
158
+ if (!Object.defineProperties) {
159
+ Object.defineProperties = function (obj, props) {
160
+ var prop;
161
+ for (prop in props) {
162
+ if (hasOwnProp.call(props, prop)) {
163
+ Object.defineProperty(obj, prop, props[prop]);
164
+ }
165
+ }
166
+ };
167
+ }
168
+ }
169
+ }());
170
+
171
+ // Begin dataset code
172
+
173
+ if (!document.documentElement.dataset &&
174
+ // FF is empty while IE gives empty object
175
+ (!Object.getOwnPropertyDescriptor(Element.prototype, 'dataset') ||
176
+ !Object.getOwnPropertyDescriptor(Element.prototype, 'dataset').get)
177
+ ) {
178
+ var propDescriptor = {
179
+ enumerable: true,
180
+ get: function () {
181
+ 'use strict';
182
+ var i,
183
+ that = this,
184
+ HTML5_DOMStringMap,
185
+ attrVal, attrName, propName,
186
+ attribute,
187
+ attributes = this.attributes,
188
+ attsLength = attributes.length,
189
+ toUpperCase = function (n0) {
190
+ return n0.charAt(1).toUpperCase();
191
+ },
192
+ getter = function () {
193
+ return this;
194
+ },
195
+ setter = function (attrName, value) {
196
+ return (typeof value !== 'undefined') ?
197
+ this.setAttribute(attrName, value) :
198
+ this.removeAttribute(attrName);
199
+ };
200
+ try { // Simulate DOMStringMap w/accessor support
201
+ // Test setting accessor on normal object
202
+ ({}).__defineGetter__('test', function () {});
203
+ HTML5_DOMStringMap = {};
204
+ }
205
+ catch (e1) { // Use a DOM object for IE8
206
+ HTML5_DOMStringMap = document.createElement('div');
207
+ }
208
+ for (i = 0; i < attsLength; i++) {
209
+ attribute = attributes[i];
210
+ // Fix: This test really should allow any XML Name without
211
+ // colons (and non-uppercase for XHTML)
212
+ if (attribute && attribute.name &&
213
+ (/^data-\w[\w\-]*$/).test(attribute.name)) {
214
+ attrVal = attribute.value;
215
+ attrName = attribute.name;
216
+ // Change to CamelCase
217
+ propName = attrName.substr(5).replace(/-./g, toUpperCase);
218
+ try {
219
+ Object.defineProperty(HTML5_DOMStringMap, propName, {
220
+ enumerable: this.enumerable,
221
+ get: getter.bind(attrVal || ''),
222
+ set: setter.bind(that, attrName)
223
+ });
224
+ }
225
+ catch (e2) { // if accessors are not working
226
+ HTML5_DOMStringMap[propName] = attrVal;
227
+ }
228
+ }
229
+ }
230
+ return HTML5_DOMStringMap;
231
+ }
232
+ };
233
+ try {
234
+ // FF enumerates over element's dataset, but not
235
+ // Element.prototype.dataset; IE9 iterates over both
236
+ Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
237
+ } catch (e) {
238
+ propDescriptor.enumerable = false; // IE8 does not allow setting to true
239
+ Object.defineProperty(Element.prototype, 'dataset', propDescriptor);
240
+ }
241
+ }