diff --git a/app/assets/javascripts/admin/all.js b/app/assets/javascripts/admin/all.js index 050f01aecd..76a286cc43 100644 --- a/app/assets/javascripts/admin/all.js +++ b/app/assets/javascripts/admin/all.js @@ -14,6 +14,8 @@ //= require jquery.powertip //= require jquery.cookie //= require jquery.jstree/jquery.jstree +//= require jquery.vAlign +//= require jquery.horizontalNav //= require angular //= require angular-resource //= require angular-animate @@ -25,8 +27,13 @@ //= require lodash.underscore.js // spree +//= require spree +//= require admin/spree-select2 //= require admin/spree_backend //= require modernizr +//= require spin +//= require jquery.adaptivemenu +//= require equalize //= require css_browser_selector_dev //= require responsive-tables //= require admin/spree_paypal_express diff --git a/app/assets/javascripts/admin/spree/base.js.erb b/app/assets/javascripts/admin/spree/base.js.erb new file mode 100644 index 0000000000..b9ba57d9ff --- /dev/null +++ b/app/assets/javascripts/admin/spree/base.js.erb @@ -0,0 +1,228 @@ +//= require_self +//= require admin/handlebar_extensions +//= require admin/variant_autocomplete + +/** +This is a collection of javascript functions and whatnot +under the spree namespace that do stuff we find helpful. +Hopefully, this will evolve into a propper class. +**/ + +jQuery(function($) { + // Make main menu use full width + mainMenu = $('.fullwidth-menu') + if (typeof mainMenu.horizontalNav === 'function' ) + mainMenu.horizontalNav({ + tableDisplay: false, + responsiveDelay: 0 + }); + + // Vertical align of checkbox fields + if (typeof $('.field.checkbox label').vAlign === 'function' ) + $('.field.checkbox label').vAlign() + + // if (typeof Spree !== 'undefined') { + // $('.main-menu-wrapper ul').AdaptiveMenu({ + // text: " " + Spree.translations.more + "", + // klass: "dropdown" + // }); + // } + + // Add some tips + if (typeof $('.with-tip').powerTip === 'function' ) { + $('.with-tip').powerTip({ + smartPlacement: true, + fadeInTime: 50, + fadeOutTime: 50, + intentPollInterval: 300 + }); + + $('.with-tip').on({ + powerTipPreRender: function(){ + $('#powerTip').addClass($(this).attr("data-action")); + $('#powerTip').addClass($(this).attr("data-tip-color")); + }, + powerTipClose: function(){ + $('#powerTip').removeClass($(this).attr("data-action")) + } + }); + } + + // Make flash messages dissapear + setTimeout('$(".flash").fadeOut()', 5000); + + // Highlight hovered table column + $('table tbody tr td.actions a').hover(function(){ + var tr = $(this).closest('tr'); + var klass = 'highlight action-' + $(this).attr('data-action') + tr.addClass(klass) + tr.prev().addClass('before-' + klass); + }, function(){ + var tr = $(this).closest('tr'); + var klass = 'highlight action-' + $(this).attr('data-action') + tr.removeClass(klass) + tr.prev().removeClass('before-' + klass); + }); + + // Trunkate text in page_title that didn't fit + var truncate_elements = $('.truncate'); + + truncate_elements.each(function(){ + $(this).trunk8(); + }); + $(window).resize(function (event) { + truncate_elements.each(function(){ + $(this).trunk8(); + }) + }); + + // Make height of dt/dd elements the same + if (typeof $("dl").equalize === 'function' ) + $("dl").equalize('outerHeight'); +}); + + +$.fn.visible = function(cond) { this[cond ? 'show' : 'hide' ]() }; + +// Overriding a broken function in Spree. Bug report at +// https://github.com/spree/spree/issues/4032 +show_flash_error = function(message) { + error_div = $('.flash.error'); + if (error_div.length > 0) { + error_div.html(message); + error_div.show(); + } else { + if ($("#content .toolbar").length > 0) { + $("#content .toolbar").before('
' + message + '
'); + } else { + $("#progress").before('
' + message + '
'); + } + } +} + +// Apply to individual radio button that makes another element visible when checked +$.fn.radioControlsVisibilityOfElement = function(dependentElementSelector){ + if(!this.get(0)){ return } + showValue = this.get(0).value; + radioGroup = $("input[name='" + this.get(0).name + "']"); + radioGroup.each(function(){ + $(this).click(function(){ + $(dependentElementSelector).visible(this.checked && this.value == showValue) + }); + if(this.checked){ this.click() } + }); +} + +$(document).ready(function() { + if (typeof Spree !== 'undefined') { + handle_date_picker_fields = function(){ + $('.datepicker').datepicker({ + dateFormat: Spree.translations.date_picker, + dayNames: Spree.translations.abbr_day_names, + dayNamesMin: Spree.translations.abbr_day_names, + monthNames: Spree.translations.month_names, + prevText: Spree.translations.previous, + nextText: Spree.translations.next, + showOn: "focus" + }); + + // Correctly display range dates + $('.date-range-filter .datepicker-from').datepicker('option', 'onSelect', function(selectedDate) { + $(".date-range-filter .datepicker-to" ).datepicker( "option", "minDate", selectedDate ); + }); + $('.date-range-filter .datepicker-to').datepicker('option', 'onSelect', function(selectedDate) { + $(".date-range-filter .datepicker-from" ).datepicker( "option", "maxDate", selectedDate ); + }); + } + + handle_date_picker_fields(); + } + + $(".observe_field").on('change', function() { + target = $(this).attr("data-update"); + ajax_indicator = $(this).attr("data-ajax-indicator") || '#busy_indicator'; + $(target).hide(); + $(ajax_indicator).show(); + $.ajax({ dataType: 'html', + url: $(this).attr("data-base-url")+encodeURIComponent($(this).val()), + type: 'get', + success: function(data){ + $(target).html(data); + $(ajax_indicator).hide(); + $(target).show(); + } + }); + }); + + $('.spree_add_fields').click(function() { + var target = $(this).data("target"); + var new_table_row = $(target + ' tr:visible:last').clone(); + var new_id = new Date().getTime(); + new_table_row.find("input, select").each(function () { + var el = $(this); + el.val(""); + if (typeof el.attr("id") !== 'undefined') el.attr("id", el.attr("id").replace(/\d+/, new_id)) + if (typeof el.attr("name") !== 'undefined') el.attr("name", el.attr("name").replace(/\d+/, new_id)) + }) + // When cloning a new row, set the href of all icons to be an empty "#" + // This is so that clicking on them does not perform the actions for the + // duplicated row + new_table_row.find("a").each(function () { + var el = $(this); + el.attr('href', '#'); + }) + $(target).prepend(new_table_row); + }) + + // Fix sortable helper + var fixHelper = function(e, ui) { + ui.children().each(function() { + $(this).width($(this).width()); + }); + return ui; + }; + + $('table.sortable').ready(function(){ + var td_count = $(this).find('tbody tr:first-child td').length + + if (typeof $('table.sortable tbody').sortable !== 'function' ) + return + + $('table.sortable tbody').sortable( + { + handle: '.handle', + helper: fixHelper, + placeholder: 'ui-sortable-placeholder', + update: function(event, ui) { + $("#progress").show(); + positions = {}; + $.each($('table.sortable tbody tr'), function(position, obj){ + reg = /spree_(\w+_?)+_(\d+)/; + parts = reg.exec($(obj).attr('id')); + if (parts) { + positions['positions['+parts[2]+']'] = position; + } + }); + $.ajax({ + type: 'POST', + dataType: 'script', + url: $(ui.item).closest("table.sortable").data("sortable-link"), + data: positions, + success: function(data){ $("#progress").hide(); } + }); + }, + start: function (event, ui) { + // Set correct height for placehoder (from dragged tr) + ui.placeholder.height(ui.item.height()) + // Fix placeholder content to make it correct width + ui.placeholder.html("") + }, + stop: function (event, ui) { + // Fix odd/even classes after reorder + $("table.sortable tr:even").removeClass("odd even").addClass("even"); + $("table.sortable tr:odd").removeClass("odd even").addClass("odd"); + } + + }); + }); +}); diff --git a/app/assets/javascripts/admin/spree/progress.coffee b/app/assets/javascripts/admin/spree/progress.coffee new file mode 100644 index 0000000000..edc20a541b --- /dev/null +++ b/app/assets/javascripts/admin/spree/progress.coffee @@ -0,0 +1,27 @@ +$(document).ready -> + opts = + lines: 11 + length: 2 + width: 3 + radius: 9 + corners: 1 + rotate: 0 + color: '#fff' + speed: 0.8 + trail: 48 + shadow: false + hwaccel: true + className: 'spinner' + zIndex: 2e9 + top: 'auto' + left: 'auto' + + target = document.getElementById("spinner") + + $(document).ajaxStart -> + $("#progress").fadeIn() + spinner = new Spinner(opts).spin(target) + + $(document).ajaxStop -> + $("#progress").fadeOut() + diff --git a/app/assets/javascripts/admin/spree/spree-select2.js.erb b/app/assets/javascripts/admin/spree/spree-select2.js.erb new file mode 100644 index 0000000000..30f4fc0794 --- /dev/null +++ b/app/assets/javascripts/admin/spree/spree-select2.js.erb @@ -0,0 +1,8 @@ +//= require select2 +jQuery(function($) { + // Make select beautiful + if (typeof $('select.select2').select2 === 'function' ) + $('select.select2').select2({ + allowClear: true + }); +}) diff --git a/app/assets/javascripts/admin/util.js.erb b/app/assets/javascripts/admin/util.js.erb index 37ff1cb9ca..aabd313b0f 100644 --- a/app/assets/javascripts/admin/util.js.erb +++ b/app/assets/javascripts/admin/util.js.erb @@ -16,22 +16,6 @@ $(document).ready(function() { }); }); -// Overriding a broken function in Spree. Bug report at -// https://github.com/spree/spree/issues/4032 -show_flash_error = function(message) { - error_div = $('.flash.error'); - if (error_div.length > 0) { - error_div.html(message); - error_div.show(); - } else { - if ($("#content .toolbar").length > 0) { - $("#content .toolbar").before('
' + message + '
'); - } else { - $("#progress").before('
' + message + '
'); - } - } -} - $(document).ready(function(){ $('a.close').click(function(event){ event.preventDefault(); diff --git a/app/assets/stylesheets/admin/all.scss b/app/assets/stylesheets/admin/all.scss index 50f6c25477..a3200f1d91 100644 --- a/app/assets/stylesheets/admin/all.scss +++ b/app/assets/stylesheets/admin/all.scss @@ -14,7 +14,6 @@ *= require_self */ - //************************************************************************// //************************************************************************// @import 'globals/variables'; diff --git a/app/assets/stylesheets/admin/components/progress.scss b/app/assets/stylesheets/admin/components/progress.scss new file mode 100644 index 0000000000..189689c764 --- /dev/null +++ b/app/assets/stylesheets/admin/components/progress.scss @@ -0,0 +1,38 @@ +@import 'admin/globals/variables'; +@import 'admin/globals/mixins'; + +#progress { + display: none; + position: fixed; + top: 0; + z-index: 1000; + opacity: 0.8; + width: 100%; + + .wrapper { + @include border-radius(10px); + top: -10px; + position: absolute; + left: 50%; + width: 200px; + margin-left: -100px; + padding: 11px 0; + background-color: $color-3; + color: $color-1; + text-align: center; + } + + #spinner { + position: absolute; + top: 10px; + left: 50%; + margin-left: -5px; + } + + .progress-message { + font-size: 120%; + font-weight: $font-weight-bold; + margin-top: 20px; + text-transform: uppercase; + } +} \ No newline at end of file diff --git a/app/assets/stylesheets/admin/globals/functions.scss b/app/assets/stylesheets/admin/globals/functions.scss new file mode 100644 index 0000000000..0abf3546f6 --- /dev/null +++ b/app/assets/stylesheets/admin/globals/functions.scss @@ -0,0 +1,25 @@ +// Make color very close to white +@function very-light($color, $adjust: 3){ + @if type-of($adjust) == 'number' and $adjust > 0 { + @for $i from 0 through 100 { + @if lighten($color, $i) == white and ($i - $adjust) > $adjust { + @return lighten($color, $i - $adjust); + } + } + } + @else { + @debug "Please correct $adjust value. It should be number and larger then 0. Currently it is '#{type-of($adjust)}' with value '#{$adjust}'" + } +}; + +// Quick fix for dynamic variables missing in SASS +@function get-value($prop, $val, $search) { + $n1: index($prop, $search); + $n2: index($val, $search); + + @if($n1) { + @return nth($val, $n1); + } @else { + @return nth($prop, $n2); + } +} diff --git a/app/assets/stylesheets/admin/plugins/select2.scss b/app/assets/stylesheets/admin/plugins/select2.scss new file mode 100644 index 0000000000..d290394819 --- /dev/null +++ b/app/assets/stylesheets/admin/plugins/select2.scss @@ -0,0 +1,193 @@ +@import 'admin/globals/functions'; +@import 'admin/globals/variables'; +@import 'admin/globals/mixins'; + +@import 'admin/shared/forms'; + +@import 'admin/plugins/font-awesome'; + +.select2-container { + &:hover .select2-choice, &.select2-container-active .select2-choice { + background-color: $color-sel-hover-bg !important; + border-color: $color-sel-hover-bg !important; + } + .select2-choice { + background-image: none !important; + background-color: $color-sel-bg; + border: none !important; + box-shadow: none !important; + @include border-radius($border-radius); + color: $color-1 !important; + font-size: 90%; + height: 31px; + line-height: inherit !important; + padding: 5px 15px 7px; + + span { + display: block; + padding: 2px; + } + + .select2-search-choice-close { + background-image: none !important; + font-size: 100% !important; + @extend .icon-remove; + @extend [class^="icon-"]:before; + margin-top: 2px; + } + } + + &.select2-container-active { + .select2-choice { + box-shadow: none !important; + } + + &.select2-dropdown-open .select2-choice div b { + @extend .icon-caret-up + } + } +} + +.select2-drop { + border-color: $color-sel-hover-bg; + box-shadow: none !important; + z-index: 1000000; + + &.select2-drop-above { + border-color: $color-sel-hover-bg; + } +} + +.select2-search { + @extend .icon-search; + + font-size: 100%; + color: darken($color-border, 15); + padding: 0 9px 0 0; + + &:before { + @extend [class^="icon-"]:before; + + position: absolute; + top: 13px; + left: 13px; + } + + input { + @extend input[type="text"]; + + padding: 6px 0 6px 25px; + margin: 5px 0 0 5px; + font-family: $base-font-family; + font-size: 90%; + box-shadow: none; + background-image: none; + } +} + +.select2-container .select2-choice .select2-arrow { + background-image: none; + background: transparent; + border: 0; + + b { + padding-top: 7px; + display: block; + width: 100%; + height: 100%; + background: none; + font-family: FontAwesome; + font-weight: 200 !important; + + &:before { + content: "\f0d7"; + } + } +} + +.select2-results { + padding-left: 0 !important; + + li { + font-size: 85% !important; + + &.select2-highlighted { + .select2-result-label { + &, h6 { + color: $color-1 !important; + } + } + } + + .select2-result-label { + color: $color-body-text; + min-height: 22px; + clear: both; + overflow: auto; + } + + &.select2-no-results, &.select2-searching { + padding: 5px; + background-color: transparent; + color: $color-body-text; + text-align: center; + font-weight: $font-weight-bold; + text-transform: uppercase; + } + } + + .select2-highlighted { + background-color: $color-sel-bg; + } +} + +.select2-container-multi { + &.select2-container-active, &.select2-dropdown-open { + .select2-choices { + border-color: $color-sel-hover-bg !important; + box-shadow: none; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } + } + .select2-choices { + @extend input[type="text"]; + padding: 6px 3px 3px 3px; + box-shadow: none; + background-image: none !important; + + .select2-search-choice { + @include border-radius($border-radius); + margin: 0 0 3px 3px; + background-image: none; + background-color: $color-sel-bg; + border: none; + box-shadow: none; + color: $color-1 !important; + font-size: 85%; + + &:hover { + background-color: $color-sel-hover-bg; + } + + .select2-search-choice-close { + background-image: none !important; + font-size: 85% !important; + @extend .icon-remove; + @extend [class^="icon-"]:before; + margin-left: 2px; + color: $color-1; + } + } + } +} + +label .select2-container { + margin-top: -6px; + .select2-choice { + span { + text-transform: none; + font-weight: normal; + } + } +} diff --git a/vendor/assets/javascripts/equalize.js b/vendor/assets/javascripts/equalize.js new file mode 100644 index 0000000000..776be00aef --- /dev/null +++ b/vendor/assets/javascripts/equalize.js @@ -0,0 +1,41 @@ +/** + * equalize.js + * Author & copyright (c) 2012: Tim Svensen + * Dual MIT & GPL license + * + * Page: http://tsvensen.github.com/equalize.js + * Repo: https://github.com/tsvensen/equalize.js/ + * + * The jQuery plugin for equalizing the height or width of elements. + * + * Equalize will accept any of the jQuery Dimension methods: + * height, outerHeight, innerHeight, + * width, outerWidth, innerWidth. + * + * EXAMPLE + * $('.parent').equalize(); // defaults to 'height' + * $('.parent').equalize('width'); // equalize the widths + */ +(function($, window, document, undefined) { + + $.fn.equalize = function(equalize) { + var $containers = this, // this is the jQuery object + equalize = equalize || 'height', + type = (equalize.indexOf('eight') > 0) ? 'height' : 'width'; + + if (!$.isFunction($.fn[equalize])) { return false; } + + return $containers.each(function() { + var $children = $(this).children(), + max = 0; // reset for each container + + $children.each(function() { + var value = $(this)[equalize](); // call height(), outerHeight(), etc. + if (value > max) { max = value; } // update max + }); + + $children.css(type, max +'px'); // add CSS to children + }); + }; + +}(jQuery, window, document)); diff --git a/vendor/assets/javascripts/jquery.horizontalNav.js b/vendor/assets/javascripts/jquery.horizontalNav.js new file mode 100755 index 0000000000..a008355a25 --- /dev/null +++ b/vendor/assets/javascripts/jquery.horizontalNav.js @@ -0,0 +1,141 @@ +/** + * jQuery Horizontal Navigation 1.0 + * https://github.com/sebnitu/horizontalNav + * + * By Sebastian Nitu - Copyright 2012 - All rights reserved + * Author URL: http://sebnitu.com + */ +(function($) { + + $.fn.horizontalNav = function(options) { + + // Extend our default options with those provided. + var opts = $.extend({}, $.fn.horizontalNav.defaults, options); + + return this.each(function () { + + // Save our object + var $this = $(this); + + // Build element specific options + // This lets me access options with this syntax: o.optionName + var o = $.meta ? $.extend({}, opts, $this.data()) : opts; + + // Save the wrapper. The wrapper is the element that + // we figure out what the full width should be + if ($this.is('ul')) { + var ul_wrap = $this.parent(); + } else { + var ul_wrap = $this; + } + + // let's append a clearfixing element to the ul wrapper + ul_wrap.css({ 'zoom' : '1' }).append('
'); + $('.clearHorizontalNav').css({ + 'display' : 'block', + 'overflow' : 'hidden', + 'visibility' : 'hidden', + 'width' : 0, + 'height' : 0, + 'clear' : 'both' + }); + + // Grab elements we'll need and add some default styles + var ul = $this.is('ul') ? $this : ul_wrap.find('> ul'), // The unordered list element + li = ul.find('> li'), // All list items + li_last = li.last(), // Last list item + li_count = li.size(), // The number of navigation elements + li_a = li.find('> a'); // Remove padding from the links + + // If set to responsive, re-construct after every browser resize + if ( o.responsive === true ) { + // Only need to do this for IE7 and below + // or if we set tableDisplay to false + if ( (o.tableDisplay != true) || ($.browser.msie && parseInt($.browser.version, 10) <= 7) ) { + resizeTrigger( _construct, o.responsiveDelay ); + } + } + + // Initiate the plugin + _construct(); + + // Returns the true inner width of an element + // Essentially it's the inner width without padding. + function trueInnerWidth(element) { + return element.innerWidth() - ( + parseInt(element.css('padding-left')) + parseInt(element.css('padding-right')) + ); + } + + // Call funcion on browser resize + function resizeTrigger(callback, delay) { + // Delay before function is called + delay = delay || 100; + // Call function on resize + var resizeTimer; + $(window).resize(function() { + clearTimeout(resizeTimer); + resizeTimer = setTimeout(function() { + callback(); + }, delay); + }); + } + + // The heavy lifting of this plugin. This is where we + // find and set the appropriate widths for list items + function _construct() { + + if ( (o.tableDisplay != true) || ($.browser.msie && parseInt($.browser.version, 10) <= 7) ) { + + // IE7 doesn't support the "display: table" method + // so we need to do it the hard way. + + // Add some styles + ul.css({ 'float' : 'left' }); + li.css({ 'float' : 'left', 'width' : 'auto' }); + li_a.css({ 'padding-left' : 0, 'padding-right' : 0 }); + + // Grabbing widths and doing some math + var ul_width = trueInnerWidth(ul), + ul_width_outer = ul.outerWidth(true), + ul_width_extra = ul_width_outer - ul_width, + + full_width = trueInnerWidth(ul_wrap), + extra_width = (full_width - ul_width_extra) - ul_width, + li_padding = Math.floor( extra_width / li_count ); + + // Cycle through the list items and give them widths + li.each(function(index) { + var li_width = trueInnerWidth( $(this) ); + $(this).css({ 'width' : (li_width + li_padding) + 'px' }); + }); + + // Get the leftover pixels after we set every itms width + var li_last_width = trueInnerWidth(li_last) + ( (full_width - ul_width_extra) - trueInnerWidth(ul) ); + // I hate to do this but for some reason Firefox (v13.0) and IE are always + // one pixel off when rendering. So this is a quick fix for that. + if ($.browser.mozilla || $.browser.msie) { + li_last_width = li_last_width - 1; + } + // Add the leftovers to the last navigation item + li_last.css({ 'width' : li_last_width + 'px' }); + + } else { + // Every modern browser supports the "display: table" method + // so this is the best way to do it for them. + ul.css({ 'display' : 'table', 'float' : 'none', 'width' : '100%' }); + li.css({ 'display' : 'table-cell', 'float' : 'none' }); + } + } + + }); // @end of return this.each() + + }; + + $.fn.horizontalNav.defaults = { + responsive : true, + responsiveDelay : 100, + tableDisplay : true + }; + +})(jQuery); \ No newline at end of file diff --git a/vendor/assets/javascripts/jquery.vAlign.js b/vendor/assets/javascripts/jquery.vAlign.js new file mode 100644 index 0000000000..d0e2f08882 --- /dev/null +++ b/vendor/assets/javascripts/jquery.vAlign.js @@ -0,0 +1,11 @@ +(function ($) { + // VERTICALLY ALIGN FUNCTION + $.fn.vAlign = function() { + return this.each(function(i){ + var ah = $(this).height(); + var ph = $(this).parent().height(); + var mh = Math.ceil((ph-ah) / 2); + $(this).css('margin-top', mh); + }); + }; +})(jQuery); \ No newline at end of file diff --git a/vendor/assets/javascripts/spin.js b/vendor/assets/javascripts/spin.js new file mode 100644 index 0000000000..2a8642f311 --- /dev/null +++ b/vendor/assets/javascripts/spin.js @@ -0,0 +1,319 @@ +//fgnass.github.com/spin.js#v1.2.6 +!function(window, document, undefined) { + + /** + * Copyright (c) 2011 Felix Gnass [fgnass at neteye dot de] + * Licensed under the MIT license + */ + + var prefixes = ['webkit', 'Moz', 'ms', 'O'] /* Vendor prefixes */ + , animations = {} /* Animation rules keyed by their name */ + , useCssAnimations + + /** + * Utility function to create elements. If no tag name is given, + * a DIV is created. Optionally properties can be passed. + */ + function createEl(tag, prop) { + var el = document.createElement(tag || 'div') + , n + + for(n in prop) el[n] = prop[n] + return el + } + + /** + * Appends children and returns the parent. + */ + function ins(parent /* child1, child2, ...*/) { + for (var i=1, n=arguments.length; i> 1) : parseInt(o.left, 10) + mid) + 'px', + top: (o.top == 'auto' ? tp.y-ep.y + (target.offsetHeight >> 1) : parseInt(o.top, 10) + mid) + 'px' + }) + } + + el.setAttribute('aria-role', 'progressbar') + self.lines(el, self.opts) + + if (!useCssAnimations) { + // No CSS animation support, use setTimeout() instead + var i = 0 + , fps = o.fps + , f = fps/o.speed + , ostep = (1-o.opacity) / (f*o.trail / 100) + , astep = f/o.lines + + ;(function anim() { + i++; + for (var s=o.lines; s; s--) { + var alpha = Math.max(1-(i+s*astep)%f * ostep, o.opacity) + self.opacity(el, o.lines-s, alpha, o) + } + self.timeout = self.el && setTimeout(anim, ~~(1000/fps)) + })() + } + return self + }, + + stop: function() { + var el = this.el + if (el) { + clearTimeout(this.timeout) + if (el.parentNode) el.parentNode.removeChild(el) + this.el = undefined + } + return this + }, + + lines: function(el, o) { + var i = 0 + , seg + + function fill(color, shadow) { + return css(createEl(), { + position: 'absolute', + width: (o.length+o.width) + 'px', + height: o.width + 'px', + background: color, + boxShadow: shadow, + transformOrigin: 'left', + transform: 'rotate(' + ~~(360/o.lines*i+o.rotate) + 'deg) translate(' + o.radius+'px' +',0)', + borderRadius: (o.corners * o.width>>1) + 'px' + }) + } + + for (; i < o.lines; i++) { + seg = css(createEl(), { + position: 'absolute', + top: 1+~(o.width/2) + 'px', + transform: o.hwaccel ? 'translate3d(0,0,0)' : '', + opacity: o.opacity, + animation: useCssAnimations && addAnimation(o.opacity, o.trail, i, o.lines) + ' ' + 1/o.speed + 's linear infinite' + }) + + if (o.shadow) ins(seg, css(fill('#000', '0 0 4px ' + '#000'), {top: 2+'px'})) + + ins(el, ins(seg, fill(o.color, '0 0 1px rgba(0,0,0,.1)'))) + } + return el + }, + + opacity: function(el, i, val) { + if (i < el.childNodes.length) el.childNodes[i].style.opacity = val + } + + }) + + ///////////////////////////////////////////////////////////////////////// + // VML rendering for IE + ///////////////////////////////////////////////////////////////////////// + + /** + * Check and init VML support + */ + ;(function() { + + function vml(tag, attr) { + return createEl('<' + tag + ' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">', attr) + } + + var s = css(createEl('group'), {behavior: 'url(#default#VML)'}) + + if (!vendor(s, 'transform') && s.adj) { + + // VML support detected. Insert CSS rule ... + sheet.addRule('.spin-vml', 'behavior:url(#default#VML)') + + Spinner.prototype.lines = function(el, o) { + var r = o.length+o.width + , s = 2*r + + function grp() { + return css( + vml('group', { + coordsize: s + ' ' + s, + coordorigin: -r + ' ' + -r + }), + { width: s, height: s } + ) + } + + var margin = -(o.width+o.length)*2 + 'px' + , g = css(grp(), {position: 'absolute', top: margin, left: margin}) + , i + + function seg(i, dx, filter) { + ins(g, + ins(css(grp(), {rotation: 360 / o.lines * i + 'deg', left: ~~dx}), + ins(css(vml('roundrect', {arcsize: o.corners}), { + width: r, + height: o.width, + left: o.radius, + top: -o.width>>1, + filter: filter + }), + vml('fill', {color: o.color, opacity: o.opacity}), + vml('stroke', {opacity: 0}) // transparent stroke to fix color bleeding upon opacity change + ) + ) + ) + } + + if (o.shadow) + for (i = 1; i <= o.lines; i++) + seg(i, -2, 'progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)') + + for (i = 1; i <= o.lines; i++) seg(i) + return ins(el, g) + } + + Spinner.prototype.opacity = function(el, i, val, o) { + var c = el.firstChild + o = o.shadow && o.lines || 0 + if (c && i+o < c.childNodes.length) { + c = c.childNodes[i+o]; c = c && c.firstChild; c = c && c.firstChild + if (c) c.opacity = val + } + } + } + else + useCssAnimations = vendor(s, 'animation') + })() + + if (typeof define == 'function' && define.amd) + define(function() { return Spinner }) + else + window.Spinner = Spinner + +}(window, document)