jasny-bootstrap.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966
  1. /*!
  2. * Jasny Bootstrap v4.0.0 (http://jasny.github.io/bootstrap)
  3. * Copyright 2012-2019 Arnold Daniels
  4. * Licensed under ()
  5. */
  6. if (typeof jQuery === 'undefined') { throw new Error('Jasny Bootstrap\'s JavaScript requires jQuery') }
  7. /* ========================================================================
  8. * Bootstrap: transition.js v4.0.0
  9. * http://getbootstrap.com/javascript/#transitions
  10. * ========================================================================
  11. * Copyright 2011-2014 Twitter, Inc.
  12. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
  13. * ======================================================================== */
  14. +function ($) {
  15. 'use strict';
  16. // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/)
  17. // ============================================================
  18. function transitionEnd() {
  19. var el = document.createElement('bootstrap')
  20. var transEndEventNames = {
  21. WebkitTransition : 'webkitTransitionEnd',
  22. MozTransition : 'transitionend',
  23. OTransition : 'oTransitionEnd otransitionend',
  24. transition : 'transitionend'
  25. }
  26. for (var name in transEndEventNames) {
  27. if (el.style[name] !== undefined) {
  28. return { end: transEndEventNames[name] }
  29. }
  30. }
  31. return false // explicit for ie8 ( ._.)
  32. }
  33. if ($.support.transition !== undefined) return // Prevent conflict with vanilla Bootstrap
  34. // http://blog.alexmaccaw.com/css-transitions
  35. $.fn.emulateTransitionEnd = function (duration) {
  36. var called = false, $el = this
  37. $(this).one($.support.transition.end, function () { called = true })
  38. var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
  39. setTimeout(callback, duration)
  40. return this
  41. }
  42. $(function () {
  43. $.support.transition = transitionEnd()
  44. })
  45. }(window.jQuery);
  46. /* ========================================================================
  47. * Bootstrap: offcanvas.js v4.0.0
  48. * http://jasny.github.io/bootstrap/javascript/#offcanvas
  49. * ========================================================================
  50. * Copyright 2013-2014 Arnold Daniels
  51. *
  52. * Licensed under the Apache License, Version 2.0 (the "License")
  53. * you may not use this file except in compliance with the License.
  54. * You may obtain a copy of the License at
  55. *
  56. * http://www.apache.org/licenses/LICENSE-2.0
  57. *
  58. * Unless required by applicable law or agreed to in writing, software
  59. * distributed under the License is distributed on an "AS IS" BASIS,
  60. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  61. * See the License for the specific language governing permissions and
  62. * limitations under the License.
  63. * ======================================================================== */
  64. +function ($) { "use strict"
  65. // OFFCANVAS PUBLIC CLASS DEFINITION
  66. // =================================
  67. var isIphone = (navigator.userAgent.match(/iPhone/i)) || (navigator.userAgent.match(/iPod/i))
  68. var OffCanvas = function (element, options) {
  69. this.$element = $(element)
  70. this.options = $.extend({}, OffCanvas.DEFAULTS, options)
  71. this.state = null
  72. this.placement = null
  73. this.$calcClone = null
  74. this.calcClone()
  75. if (this.options.recalc) {
  76. $(window).on('resize', $.proxy(this.recalc, this))
  77. }
  78. if (this.options.autohide && !this.options.modal) {
  79. var eventName = (navigator.userAgent.match(/(iPad|iPhone)/i) === null) ? 'click' : 'touchstart'
  80. $(document).on('click touchstart', $.proxy(this.autohide, this))
  81. }
  82. // Backdrop is added to dropdown on it's open, if device is touchable (or desctop FF, https://github.com/twbs/bootstrap/issues/13748)
  83. // and dropdown is not inside .navbar-nav. So we remove it
  84. $(this.$element).on('shown.bs.dropdown', $.proxy(function(e) {
  85. $(this.$element).find('.dropdown .dropdown-backdrop').remove()
  86. }, this))
  87. if (typeof(this.options.disablescrolling) === "boolean") {
  88. this.options.disableScrolling = this.options.disablescrolling
  89. delete this.options.disablescrolling
  90. }
  91. if (this.options.toggle) this.toggle()
  92. }
  93. OffCanvas.DEFAULTS = {
  94. toggle: true,
  95. placement: 'auto',
  96. autohide: true,
  97. recalc: true,
  98. disableScrolling: true,
  99. modal: false,
  100. backdrop: false,
  101. exclude: null
  102. }
  103. OffCanvas.prototype.setWidth = function () {
  104. var size = this.$element.outerWidth()
  105. var max = $(window).width()
  106. max -= 68 //Minimum space between menu and screen edge
  107. this.$element.css('width', size > max ? max : size)
  108. }
  109. OffCanvas.prototype.offset = function () {
  110. switch (this.placement) {
  111. case 'left':
  112. case 'right': return this.$element.outerWidth()
  113. case 'top':
  114. case 'bottom': return this.$element.outerHeight()
  115. }
  116. }
  117. OffCanvas.prototype.calcPlacement = function () {
  118. if (this.options.placement !== 'auto') {
  119. this.placement = this.options.placement
  120. return
  121. }
  122. if (!this.$element.hasClass('in')) {
  123. this.$element.css('visiblity', 'hidden !important').addClass('in')
  124. }
  125. var horizontal = $(window).width() / this.$element.outerWidth()
  126. var vertical = $(window).height() / this.$element.outerHeight()
  127. var element = this.$element
  128. function ab(a, b) {
  129. if (element.css(b) === 'auto') return a
  130. if (element.css(a) === 'auto') return b
  131. var size_a = parseInt(element.css(a), 10)
  132. var size_b = parseInt(element.css(b), 10)
  133. return size_a > size_b ? b : a
  134. }
  135. this.placement = horizontal > vertical ? ab('left', 'right') : ab('top', 'bottom')
  136. if (this.$element.css('visibility') === 'hidden !important') {
  137. this.$element.removeClass('in').css('visiblity', '')
  138. }
  139. }
  140. OffCanvas.prototype.opposite = function (placement) {
  141. switch (placement) {
  142. case 'top': return 'bottom'
  143. case 'left': return 'right'
  144. case 'bottom': return 'top'
  145. case 'right': return 'left'
  146. }
  147. }
  148. OffCanvas.prototype.getCanvasElements = function() {
  149. // Return a set containing the canvas plus all fixed elements
  150. var canvas = this.options.canvas ? $(this.options.canvas) : this.$element
  151. var fixed_elements = canvas.find('*').filter(function() {
  152. return getComputedStyle(this).getPropertyValue('position') === 'fixed'
  153. }).not(this.options.exclude)
  154. return canvas.add(fixed_elements)
  155. }
  156. OffCanvas.prototype.slide = function (elements, offset, callback) {
  157. // Use jQuery animation if CSS transitions aren't supported
  158. if (!$.support.transition) {
  159. var anim = {}
  160. anim[this.placement] = "+=" + offset
  161. anim[this.opposite(this.placement)] = "-=" + offset
  162. return elements.animate(anim, 350, callback)
  163. }
  164. var placement = this.placement
  165. var opposite = this.opposite(placement)
  166. elements.each(function() {
  167. if ($(this).css(placement) !== 'auto') {
  168. $(this).css(placement, (parseInt($(this).css(placement), 10) || 0) + offset)
  169. }
  170. if ($(this).css(opposite) !== 'auto') {
  171. $(this).css(opposite, (parseInt($(this).css(opposite), 10) || 0) - offset)
  172. }
  173. })
  174. this.$element
  175. .one($.support.transition.end, callback)
  176. .emulateTransitionEnd(350)
  177. }
  178. OffCanvas.prototype.disableScrolling = function() {
  179. var bodyWidth = $('body').width()
  180. var prop = 'padding-right'
  181. if ($('body').data('offcanvas-style') === undefined) {
  182. $('body').data('offcanvas-style', $('body').attr('style') || '')
  183. }
  184. $('body').css('overflow', 'hidden')
  185. //Fix iPhone scrolling
  186. if (isIphone) {
  187. $('body').addClass('lockIphone')
  188. }
  189. if ($('body').width() > bodyWidth) {
  190. var padding = parseInt($('body').css(prop), 10) + $('body').width() - bodyWidth
  191. setTimeout(function() {
  192. $('body').css(prop, padding)
  193. }, 1)
  194. }
  195. //disable scrolling on mobiles (they ignore overflow:hidden)
  196. $('body').on('touchmove.bs', function(e) {
  197. if (!$(event.target).closest('.offcanvas').length)
  198. e.preventDefault()
  199. })
  200. }
  201. OffCanvas.prototype.enableScrolling = function() {
  202. $('body').off('touchmove.bs')
  203. $('body').removeClass('lockIphone')
  204. }
  205. OffCanvas.prototype.show = function () {
  206. if (this.state) return
  207. var startEvent = $.Event('show.bs.offcanvas')
  208. this.$element.trigger(startEvent)
  209. if (startEvent.isDefaultPrevented()) return
  210. this.hideOthers($.proxy(function() {
  211. this.state = 'slide-in'
  212. this.$element.css('width', '')
  213. this.calcPlacement()
  214. this.setWidth()
  215. var elements = this.getCanvasElements()
  216. var placement = this.placement
  217. var opposite = this.opposite(placement)
  218. var offset = this.offset()
  219. if (elements.index(this.$element) !== -1) {
  220. $(this.$element).data('offcanvas-style', $(this.$element).attr('style') || '')
  221. this.$element.css(placement, -1 * offset)
  222. this.$element.css(placement) // Workaround: Need to get the CSS property for it to be applied before the next line of code
  223. }
  224. elements.addClass('canvas-sliding').each(function() {
  225. var $this = $(this)
  226. if ($this.data('offcanvas-style') === undefined) $this.data('offcanvas-style', $this.attr('style') || '')
  227. if ($this.css('position') === 'static' && !isIphone) $this.css('position', 'relative')
  228. if (($this.css(placement) === 'auto' || $this.css(placement) === '0px') &&
  229. ($this.css(opposite) === 'auto' || $this.css(opposite) === '0px')) {
  230. $this.css(placement, 0)
  231. }
  232. })
  233. if (this.options.disableScrolling) this.disableScrolling()
  234. if (this.options.modal || this.options.backdrop) this.toggleBackdrop()
  235. var complete = function () {
  236. if (this.state != 'slide-in') return
  237. this.state = 'slid'
  238. elements.removeClass('canvas-sliding').addClass('canvas-slid')
  239. this.$element.trigger('shown.bs.offcanvas')
  240. }
  241. setTimeout($.proxy(function() {
  242. this.$element.addClass('in')
  243. this.slide(elements, offset, $.proxy(complete, this))
  244. }, this), 1)
  245. }, this))
  246. }
  247. //Hide other opened offcanvas menus, and then open this one
  248. OffCanvas.prototype.hideOthers = function (callback) {
  249. var doHide = false
  250. var id = this.$element.attr('id')
  251. var $clones = $('.offcanvas-clone:not([data-id="' + id + '"])')
  252. if (!$clones.length) return callback()
  253. $clones.each(function(index, clone) {
  254. var id = $(clone).attr('data-id')
  255. var $menu = $('#' + id)
  256. doHide = $menu.hasClass('canvas-slid')
  257. if (!doHide) return
  258. $menu.one('hidden.bs.offcanvas', callback)
  259. $menu.offcanvas('hide')
  260. })
  261. if (!doHide) callback()
  262. }
  263. OffCanvas.prototype.hide = function () {
  264. if (this.state !== 'slid') return
  265. var startEvent = $.Event('hide.bs.offcanvas')
  266. this.$element.trigger(startEvent)
  267. if (startEvent.isDefaultPrevented()) return
  268. this.state = 'slide-out'
  269. var elements = $('.canvas-slid')
  270. var placement = this.placement
  271. var offset = -1 * this.offset()
  272. var complete = function () {
  273. if (this.state != 'slide-out') return
  274. this.state = null
  275. this.placement = null
  276. this.$element.removeClass('in')
  277. elements.removeClass('canvas-sliding')
  278. elements.add(this.$element).add('body').each(function() {
  279. $(this).attr('style', $(this).data('offcanvas-style')).removeData('offcanvas-style')
  280. })
  281. this.$element.css('width', '')
  282. this.$element.trigger('hidden.bs.offcanvas')
  283. }
  284. if (this.options.disableScrolling) this.enableScrolling()
  285. if (this.options.modal || this.options.backdrop) this.toggleBackdrop()
  286. elements.removeClass('canvas-slid').addClass('canvas-sliding')
  287. setTimeout($.proxy(function() {
  288. this.slide(elements, offset, $.proxy(complete, this))
  289. }, this), 1)
  290. }
  291. OffCanvas.prototype.toggle = function () {
  292. if (this.state === 'slide-in' || this.state === 'slide-out') return
  293. this[this.state === 'slid' ? 'hide' : 'show']()
  294. }
  295. OffCanvas.prototype.toggleBackdrop = function (callback) {
  296. callback = callback || $.noop
  297. var time = 150
  298. if (this.state == 'slide-in') {
  299. var $body = $('body')
  300. var doAnimate = $.support.transition
  301. this.$backdrop = $('<div class="modal-backdrop fade" />')
  302. if (this.options.backdrop) {
  303. this.$backdrop.addClass('allow-navbar')
  304. if (this.options.canvas && $(this.options.canvas)[0] !== $body[0]) {
  305. $(this.options.canvas).addClass('limit-backdrop')
  306. this.$backdrop.appendTo(this.options.canvas)
  307. } else {
  308. this.$backdrop.insertAfter(this.$element)
  309. }
  310. } else {
  311. this.$backdrop.insertAfter(this.$element)
  312. }
  313. if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
  314. $body.addClass('modal-open')
  315. this.$backdrop.addClass('show').show()
  316. this.$backdrop.on('click.bs', $.proxy(this.autohide, this))
  317. doAnimate ?
  318. this.$backdrop
  319. .one($.support.transition.end, callback)
  320. .emulateTransitionEnd(time) :
  321. callback()
  322. } else if (this.state == 'slide-out' && this.$backdrop) {
  323. var self = this
  324. this.$backdrop.hide().removeClass('show')
  325. $('body').removeClass('modal-open').off('touchmove.bs')
  326. if ($.support.transition) {
  327. this.$backdrop
  328. .one($.support.transition.end, function() {
  329. self.$backdrop.remove()
  330. callback()
  331. self.$backdrop = null
  332. })
  333. .emulateTransitionEnd(time)
  334. } else {
  335. this.$backdrop.remove()
  336. this.$backdrop = null
  337. callback()
  338. }
  339. if (this.options.canvas && $(this.options.canvas)[0] !== $('body')[0]) {
  340. var canvas = this.options.canvas
  341. setTimeout(function() {
  342. $(canvas).removeClass('limit-backdrop')
  343. }, time)
  344. }
  345. } else if (callback) {
  346. callback()
  347. }
  348. }
  349. OffCanvas.prototype.calcClone = function() {
  350. var id = this.$element.attr('id')
  351. this.$calcClone = $('.offcanvas-clone[data-id="' + id + '"]')
  352. if (!this.$calcClone.length) {
  353. this.$calcClone = this.$element.clone()
  354. .addClass('offcanvas-clone')
  355. .attr('data-id', id)
  356. .removeAttr('id')
  357. .appendTo($('body'))
  358. .html('')
  359. }
  360. this.$calcClone.removeClass('in')
  361. }
  362. OffCanvas.prototype.recalc = function () {
  363. if (this.$calcClone.css('display') === 'none' || (this.state !== 'slid' && this.state !== 'slide-in')) return
  364. this.state = null
  365. this.placement = null
  366. var elements = this.getCanvasElements()
  367. this.$element.trigger('hide.bs.offcanvas')
  368. this.$element.removeClass('in')
  369. elements.removeClass('canvas-slid')
  370. elements.add(this.$element).add('body').each(function() {
  371. $(this).attr('style', $(this).data('offcanvas-style')).removeData('offcanvas-style')
  372. })
  373. this.$element.trigger('hidden.bs.offcanvas')
  374. }
  375. OffCanvas.prototype.autohide = function (e) {
  376. var $target = $(e.target)
  377. var doHide = !$target.hasClass('dropdown-backdrop') && $target.closest(this.$element).length === 0
  378. if (doHide) this.hide()
  379. }
  380. // OFFCANVAS PLUGIN DEFINITION
  381. // ==========================
  382. var old = $.fn.offcanvas
  383. $.fn.offcanvas = function (option) {
  384. return this.each(function () {
  385. var $this = $(this)
  386. var data = $this.data('bs.offcanvas')
  387. var options = $.extend({}, OffCanvas.DEFAULTS, $this.data(), typeof option === 'object' && option)
  388. //In case if user does smth like $('.navmenu-fixed-left').offcanvas('hide'),
  389. //thus selecting also menu clone (that can cause issues)
  390. if ($this.hasClass('offcanvas-clone')) return
  391. if (!data) $this.data('bs.offcanvas', (data = new OffCanvas(this, options)))
  392. if (typeof option === 'string') data[option]()
  393. })
  394. }
  395. $.fn.offcanvas.Constructor = OffCanvas
  396. // OFFCANVAS NO CONFLICT
  397. // ====================
  398. $.fn.offcanvas.noConflict = function () {
  399. $.fn.offcanvas = old
  400. return this
  401. }
  402. // OFFCANVAS DATA-API
  403. // =================
  404. $(document).on('click.bs.offcanvas.data-api', '[data-toggle=offcanvas]', function (e) {
  405. var $this = $(this), href
  406. var target = $this.attr('data-target')
  407. || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
  408. var $canvas = $(target)
  409. var data = $canvas.data('bs.offcanvas')
  410. var option = data ? 'toggle' : $.extend($this.data(), $canvas.data())
  411. e.preventDefault()
  412. e.stopPropagation()
  413. if (data) data.toggle()
  414. else $canvas.offcanvas(option)
  415. })
  416. }(window.jQuery)
  417. /* ============================================================
  418. * Bootstrap: rowlink.js v4.0.0
  419. * http://jasny.github.io/bootstrap/javascript/#rowlink
  420. * ============================================================
  421. * Copyright 2012-2014 Arnold Daniels
  422. *
  423. * Licensed under the Apache License, Version 2.0 (the "License");
  424. * you may not use this file except in compliance with the License.
  425. * You may obtain a copy of the License at
  426. *
  427. * http://www.apache.org/licenses/LICENSE-2.0
  428. *
  429. * Unless required by applicable law or agreed to in writing, software
  430. * distributed under the License is distributed on an "AS IS" BASIS,
  431. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  432. * See the License for the specific language governing permissions and
  433. * limitations under the License.
  434. * ============================================================ */
  435. +function ($) { "use strict";
  436. var Rowlink = function (element, options) {
  437. this.$element = $(element)
  438. this.options = $.extend({}, Rowlink.DEFAULTS, options)
  439. this.$element.on('click.bs.rowlink mouseup.bs.rowlink', 'td:not(.rowlink-skip)', $.proxy(this.click, this))
  440. }
  441. Rowlink.DEFAULTS = {
  442. target: "a"
  443. }
  444. Rowlink.prototype.click = function(e, ctrlKey) {
  445. var target = $(e.currentTarget).closest('tr').find(this.options.target)[0]
  446. if (typeof target === 'undefined' || $(e.target)[0] === target) return
  447. if (e.type === 'mouseup' && e.which !== 2) return
  448. e.preventDefault();
  449. ctrlKey = ctrlKey || e.ctrlKey || (e.type === 'mouseup' && e.which === 2)
  450. if (!ctrlKey && target.click) {
  451. target.click()
  452. } else if (document.createEvent) {
  453. var evt = new MouseEvent("click", {
  454. view: window,
  455. bubbles: true,
  456. cancelable: true,
  457. ctrlKey: ctrlKey
  458. });
  459. target.dispatchEvent(evt);
  460. }
  461. }
  462. // ROWLINK PLUGIN DEFINITION
  463. // ===========================
  464. var old = $.fn.rowlink
  465. $.fn.rowlink = function (options) {
  466. return this.each(function () {
  467. var $this = $(this)
  468. var data = $this.data('bs.rowlink')
  469. if (!data) $this.data('bs.rowlink', (data = new Rowlink(this, options)))
  470. })
  471. }
  472. $.fn.rowlink.Constructor = Rowlink
  473. // ROWLINK NO CONFLICT
  474. // ====================
  475. $.fn.rowlink.noConflict = function () {
  476. $.fn.rowlink = old
  477. return this
  478. }
  479. // ROWLINK DATA-API
  480. // ==================
  481. $(document).on('click.bs.rowlink.data-api mouseup.bs.rowlink.data-api', '[data-link="row"]', function (e) {
  482. if (e.type === 'mouseup' && e.which !== 2) return
  483. if ($(e.target).closest('.rowlink-skip').length !== 0) return
  484. var $this = $(this)
  485. if ($this.data('bs.rowlink')) return
  486. $this.rowlink($this.data())
  487. var ctrlKey = e.ctrlKey || e.which === 2
  488. $(e.target).trigger('click.bs.rowlink', [ctrlKey])
  489. })
  490. }(window.jQuery);
  491. /* ===========================================================
  492. * Bootstrap: fileinput.js v4.0.0
  493. * http://jasny.github.com/bootstrap/javascript/#fileinput
  494. * ===========================================================
  495. * Copyright 2012-2014 Arnold Daniels
  496. *
  497. * Licensed under the Apache License, Version 2.0 (the "License")
  498. * you may not use this file except in compliance with the License.
  499. * You may obtain a copy of the License at
  500. *
  501. * http://www.apache.org/licenses/LICENSE-2.0
  502. *
  503. * Unless required by applicable law or agreed to in writing, software
  504. * distributed under the License is distributed on an "AS IS" BASIS,
  505. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  506. * See the License for the specific language governing permissions and
  507. * limitations under the License.
  508. * ========================================================== */
  509. +function ($) { "use strict";
  510. var isIE = window.navigator.appName == 'Microsoft Internet Explorer'
  511. // FILEUPLOAD PUBLIC CLASS DEFINITION
  512. // =================================
  513. var Fileinput = function (element, options) {
  514. this.$element = $(element)
  515. this.options = $.extend({}, Fileinput.DEFAULTS, options)
  516. this.$input = this.$element.find(':file')
  517. if (this.$input.length === 0) return
  518. this.name = this.$input.attr('name') || options.name
  519. this.$hidden = this.$element.find('input[type=hidden][name="' + this.name + '"]')
  520. if (this.$hidden.length === 0) {
  521. this.$hidden = $('<input type="hidden">').insertBefore(this.$input)
  522. }
  523. this.$preview = this.$element.find('.fileinput-preview')
  524. var height = this.$preview.css('height')
  525. if (this.$preview.css('display') !== 'inline' && height !== '0px' && height !== 'none') {
  526. this.$preview.css('line-height', height)
  527. }
  528. this.original = {
  529. exists: this.$element.hasClass('fileinput-exists'),
  530. preview: this.$preview.html(),
  531. hiddenVal: this.$hidden.val()
  532. }
  533. this.listen()
  534. this.reset()
  535. }
  536. Fileinput.DEFAULTS = {
  537. clearName: true
  538. }
  539. Fileinput.prototype.listen = function() {
  540. this.$input.on('change.bs.fileinput', $.proxy(this.change, this))
  541. $(this.$input[0].form).on('reset.bs.fileinput', $.proxy(this.reset, this))
  542. this.$element.find('[data-trigger="fileinput"]').on('click.bs.fileinput', $.proxy(this.trigger, this))
  543. this.$element.find('[data-dismiss="fileinput"]').on('click.bs.fileinput', $.proxy(this.clear, this))
  544. },
  545. Fileinput.prototype.verifySizes = function(files) {
  546. if (typeof this.options.maxSize === 'undefined') return true
  547. var max = parseFloat(this.options.maxSize)
  548. if (max !== this.options.maxSize) return true
  549. for (var i = 0; i < files.length; i++) {
  550. var size = typeof files[i].size !== 'undefined' ? files[i].size : null
  551. if (size === null) continue
  552. size = size / 1000 / 1000 /* convert from bytes to MB */
  553. if (size > max) return false
  554. }
  555. return true
  556. }
  557. Fileinput.prototype.change = function(e) {
  558. var files = e.target.files === undefined ? (e.target && e.target.value ? [{ name: e.target.value.replace(/^.+\\/, '')}] : []) : e.target.files
  559. e.stopPropagation()
  560. if (files.length === 0) {
  561. this.clear()
  562. this.$element.trigger('clear.bs.fileinput')
  563. return
  564. }
  565. if (!this.verifySizes(files)) {
  566. this.$element.trigger('max_size.bs.fileinput')
  567. this.clear()
  568. this.$element.trigger('clear.bs.fileinput')
  569. return
  570. }
  571. this.$hidden.val('')
  572. this.$hidden.attr('name', '')
  573. this.$input.attr('name', this.name)
  574. var file = files[0]
  575. if (this.$preview.length > 0 && (typeof file.type !== "undefined" ? file.type.match(/^image\/(gif|png|jpeg|svg\+xml)$/) : file.name.match(/\.(gif|png|jpe?g|svg)$/i)) && typeof FileReader !== "undefined") {
  576. var Fileinput = this
  577. var reader = new FileReader()
  578. var preview = this.$preview
  579. var element = this.$element
  580. reader.onload = function(re) {
  581. var $img = $('<img>')
  582. $img[0].src = re.target.result
  583. files[0].result = re.target.result
  584. element.find('.fileinput-filename').text(file.name)
  585. // if parent has max-height, using `(max-)height: 100%` on child doesn't take padding and border into account
  586. if (preview.css('max-height') != 'none') {
  587. var mh = parseInt(preview.css('max-height'), 10) || 0
  588. var pt = parseInt(preview.css('padding-top'), 10) || 0
  589. var pb = parseInt(preview.css('padding-bottom'), 10) || 0
  590. var bt = parseInt(preview.css('border-top'), 10) || 0
  591. var bb = parseInt(preview.css('border-bottom'), 10) || 0
  592. $img.css('max-height', mh - pt - pb - bt - bb)
  593. }
  594. preview.html($img)
  595. if (Fileinput.options.exif) {
  596. //Fix image tranformation if this is possible
  597. Fileinput.setImageTransform($img, file);
  598. }
  599. element.addClass('fileinput-exists').removeClass('fileinput-new')
  600. element.trigger('change.bs.fileinput', files)
  601. }
  602. reader.readAsDataURL(file)
  603. } else {
  604. var text = file.name
  605. var $nameView = this.$element.find('.fileinput-filename')
  606. if (files.length > 1) {
  607. text = $.map(files, function(file) {
  608. return file.name;
  609. }).join(', ')
  610. }
  611. $nameView.text(text)
  612. this.$preview.text(file.name)
  613. this.$element.addClass('fileinput-exists').removeClass('fileinput-new')
  614. this.$element.trigger('change.bs.fileinput')
  615. }
  616. },
  617. Fileinput.prototype.setImageTransform = function($img, file) {
  618. var Fileinput = this;
  619. var reader = new FileReader();
  620. reader.onload = function(me) {
  621. var transform = false;
  622. var view = new DataView(reader.result);
  623. var exif = Fileinput.getImageExif(view);
  624. if (exif) {
  625. Fileinput.resetOrientation($img, exif);
  626. }
  627. }
  628. reader.readAsArrayBuffer(file);
  629. }
  630. Fileinput.prototype.getImageExif = function(view) {
  631. if (view.getUint16(0, false) != 0xFFD8) {
  632. return -2;
  633. }
  634. var length = view.byteLength, offset = 2;
  635. while (offset < length) {
  636. var marker = view.getUint16(offset, false);
  637. offset += 2;
  638. if (marker == 0xFFE1) {
  639. if (view.getUint32(offset += 2, false) != 0x45786966) {
  640. return -1;
  641. }
  642. var little = view.getUint16(offset += 6, false) == 0x4949;
  643. offset += view.getUint32(offset + 4, little);
  644. var tags = view.getUint16(offset, little);
  645. offset += 2;
  646. for (var i = 0; i < tags; i++) {
  647. if (view.getUint16(offset + (i * 12), little) == 0x0112) {
  648. return view.getUint16(offset + (i * 12) + 8, little);
  649. }
  650. }
  651. }
  652. else if ((marker & 0xFF00) != 0xFF00){
  653. break;
  654. } else {
  655. offset += view.getUint16(offset, false);
  656. }
  657. }
  658. return -1;
  659. }
  660. Fileinput.prototype.resetOrientation = function($img, transform) {
  661. var img = new Image();
  662. img.onload = function() {
  663. var width = img.width,
  664. height = img.height,
  665. canvas = document.createElement('canvas'),
  666. ctx = canvas.getContext("2d");
  667. // set proper canvas dimensions before transform & export
  668. if ([5,6,7,8].indexOf(transform) > -1) {
  669. canvas.width = height;
  670. canvas.height = width;
  671. } else {
  672. canvas.width = width;
  673. canvas.height = height;
  674. }
  675. // transform context before drawing image
  676. switch (transform) {
  677. case 2: ctx.transform(-1, 0, 0, 1, width, 0); break;
  678. case 3: ctx.transform(-1, 0, 0, -1, width, height ); break;
  679. case 4: ctx.transform(1, 0, 0, -1, 0, height ); break;
  680. case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
  681. case 6: ctx.transform(0, 1, -1, 0, height , 0); break;
  682. case 7: ctx.transform(0, -1, -1, 0, height , width); break;
  683. case 8: ctx.transform(0, -1, 1, 0, 0, width); break;
  684. default: ctx.transform(1, 0, 0, 1, 0, 0);
  685. }
  686. // draw image
  687. ctx.drawImage(img, 0, 0);
  688. // export base64
  689. $img.attr('src', canvas.toDataURL());
  690. };
  691. img.src = $img.attr('src');
  692. };
  693. Fileinput.prototype.clear = function(e) {
  694. if (e) e.preventDefault()
  695. this.$hidden.val('')
  696. this.$hidden.attr('name', this.name)
  697. if (this.options.clearName) this.$input.attr('name', '')
  698. //ie8+ doesn't support changing the value of input with type=file so clone instead
  699. if (isIE) {
  700. var inputClone = this.$input.clone(true);
  701. this.$input.after(inputClone);
  702. this.$input.remove();
  703. this.$input = inputClone;
  704. } else {
  705. this.$input.val('')
  706. }
  707. this.$preview.html('')
  708. this.$element.find('.fileinput-filename').text('')
  709. this.$element.addClass('fileinput-new').removeClass('fileinput-exists')
  710. if (e !== undefined) {
  711. this.$input.trigger('change')
  712. this.$element.trigger('clear.bs.fileinput')
  713. }
  714. },
  715. Fileinput.prototype.reset = function() {
  716. this.clear()
  717. this.$hidden.val(this.original.hiddenVal)
  718. this.$preview.html(this.original.preview)
  719. this.$element.find('.fileinput-filename').text('')
  720. if (this.original.exists) this.$element.addClass('fileinput-exists').removeClass('fileinput-new')
  721. else this.$element.addClass('fileinput-new').removeClass('fileinput-exists')
  722. this.$element.trigger('reseted.bs.fileinput')
  723. },
  724. Fileinput.prototype.trigger = function(e) {
  725. this.$input.trigger('click')
  726. e.preventDefault()
  727. }
  728. // FILEUPLOAD PLUGIN DEFINITION
  729. // ===========================
  730. var old = $.fn.fileinput
  731. $.fn.fileinput = function (options) {
  732. return this.each(function () {
  733. var $this = $(this),
  734. data = $this.data('bs.fileinput')
  735. if (!data) $this.data('bs.fileinput', (data = new Fileinput(this, options)))
  736. if (typeof options == 'string') data[options]()
  737. })
  738. }
  739. $.fn.fileinput.Constructor = Fileinput
  740. // FILEINPUT NO CONFLICT
  741. // ====================
  742. $.fn.fileinput.noConflict = function () {
  743. $.fn.fileinput = old
  744. return this
  745. }
  746. // FILEUPLOAD DATA-API
  747. // ==================
  748. $(document).on('click.fileinput.data-api', '[data-provides="fileinput"]', function (e) {
  749. var $this = $(this)
  750. if ($this.data('bs.fileinput')) return
  751. $this.fileinput($this.data())
  752. var $target = $(e.target).closest('[data-dismiss="fileinput"],[data-trigger="fileinput"]');
  753. if ($target.length > 0) {
  754. e.preventDefault()
  755. $target.trigger('click.bs.fileinput')
  756. }
  757. })
  758. }(window.jQuery);